feat: auto-create new consult medicines in OPD master with AI/local detail enrichment

This commit is contained in:
DevSoftTech Server 2026-04-06 11:38:11 +05:30
parent c4865f6e03
commit f28fcf2717
2 changed files with 194 additions and 4 deletions

View File

@ -4694,8 +4694,15 @@ class Opd_prescription extends BaseController
return $this->response->setJSON(['update' => 0, 'error_text' => 'Unable to create prescription session']);
}
$masterInfo = [
'med_id' => 0,
'is_new' => false,
'details_updated' => false,
'details_source' => '',
];
if ($medId <= 0) {
$medId = $this->resolveMedicineMasterIdByName($medName);
$masterInfo = $this->ensureMedicineMasterRecordFromConsult($medName, $medType);
$medId = (int) ($masterInfo['med_id'] ?? 0);
}
$fields = $this->db->getFieldNames($table);
@ -4768,15 +4775,163 @@ class Opd_prescription extends BaseController
]);
}
$message = 'Medicine added';
if (! empty($masterInfo['is_new'])) {
$message .= ' | New medicine saved in OPD master';
}
if (! empty($masterInfo['details_updated'])) {
$src = trim((string) ($masterInfo['details_source'] ?? 'AI/local'));
if ($src === '') {
$src = 'AI/local';
}
$message .= ' | Master details updated (' . $src . ')';
}
return $this->response->setJSON([
'update' => 1,
'opd_session_id' => $sessionId,
'error_text' => 'Medicine added',
'new_master_medicine' => ! empty($masterInfo['is_new']) ? 1 : 0,
'master_details_updated' => ! empty($masterInfo['details_updated']) ? 1 : 0,
'master_details_source' => (string) ($masterInfo['details_source'] ?? ''),
'error_text' => $message,
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
/**
* @return array{med_id:int,is_new:bool,details_updated:bool,details_source:string}
*/
private function ensureMedicineMasterRecordFromConsult(string $medName, string $medType = ''): array
{
$medName = trim($medName);
$medType = trim($medType);
if ($medName === '') {
return ['med_id' => 0, 'is_new' => false, 'details_updated' => false, 'details_source' => ''];
}
$table = $this->findExistingTable(['opd_med_master']);
if ($table === null) {
return ['med_id' => 0, 'is_new' => false, 'details_updated' => false, 'details_source' => ''];
}
$fields = $this->db->getFieldNames($table);
if (! in_array('id', $fields, true)) {
return ['med_id' => 0, 'is_new' => false, 'details_updated' => false, 'details_source' => ''];
}
$medId = $this->resolveMedicineMasterIdByName($medName);
$isNew = false;
if ($medId <= 0) {
$insert = [];
if (in_array('item_name', $fields, true)) {
$insert['item_name'] = strtoupper($medName);
} elseif (in_array('med_name', $fields, true)) {
$insert['med_name'] = strtoupper($medName);
}
if (in_array('formulation', $fields, true) && $medType !== '') {
$insert['formulation'] = $medType;
}
if (! empty($insert)) {
$this->db->table($table)->insert($insert);
$medId = (int) $this->db->insertID();
$isNew = $medId > 0;
}
}
if ($medId <= 0) {
return ['med_id' => 0, 'is_new' => false, 'details_updated' => false, 'details_source' => ''];
}
$detailsUpdated = false;
$detailsSource = '';
$genericField = $this->resolveFirstField($fields, ['genericname', 'generic_name']);
$saltField = $this->resolveFirstField($fields, ['salt_name', 'sal_name', 'salt', 'saltname']);
$restrictionField = $this->resolveFirstField($fields, ['dosage_restriction', 'dose_restriction', 'restriction_note', 'restriction']);
$row = $this->db->table($table)->where('id', $medId)->get(1)->getRowArray() ?? [];
$hasDetails = false;
if ($genericField !== null && trim((string) ($row[$genericField] ?? '')) !== '') {
$hasDetails = true;
}
if ($saltField !== null && trim((string) ($row[$saltField] ?? '')) !== '') {
$hasDetails = true;
}
if ($restrictionField !== null && trim((string) ($row[$restrictionField] ?? '')) !== '') {
$hasDetails = true;
}
if (! $hasDetails && ($genericField !== null || $saltField !== null || $restrictionField !== null)) {
$details = $this->lookupMedicineDetailsFromMaster($medName, $medType);
if (($details['genericname'] ?? '') !== '' || ($details['salt_name'] ?? '') !== '' || ($details['dosage_restriction'] ?? '') !== '') {
$detailsSource = 'local-master';
}
if (($details['genericname'] ?? '') === '' && ($details['salt_name'] ?? '') === '' && ($details['dosage_restriction'] ?? '') === '') {
$aiServerDetails = $this->fetchMedicineDetailsFromAiServer($medName, $medType);
if (($aiServerDetails['genericname'] ?? '') !== '' || ($aiServerDetails['salt_name'] ?? '') !== '' || ($aiServerDetails['dosage_restriction'] ?? '') !== '') {
$details = $aiServerDetails;
$detailsSource = 'ai-server';
}
}
if (($details['genericname'] ?? '') === '' && ($details['salt_name'] ?? '') === '' && ($details['dosage_restriction'] ?? '') === '') {
$fallback = $this->inferMedicineDetailsFromBrandName($medName, $medType);
if (($fallback['genericname'] ?? '') !== '' || ($fallback['salt_name'] ?? '') !== '' || ($fallback['dosage_restriction'] ?? '') !== '') {
$details = $fallback;
$detailsSource = 'brand-fallback';
}
}
if (($details['genericname'] ?? '') === '' && ($details['salt_name'] ?? '') === '' && ($details['dosage_restriction'] ?? '') === '') {
$prompt = "You are a medicine data enrichment assistant for OPD master data in India.\n"
. "Medicine Name: {$medName}\n"
. "Formulation: {$medType}\n"
. "Return ONLY valid JSON with keys: genericname, salt_name, dosage_restriction, caution_note.";
$aiText = $this->generateClinicalTextWithAi($prompt, 240);
$parsed = $aiText ? $this->parseJsonFromAiResponse($aiText) : null;
if (is_array($parsed)) {
$details = [
'genericname' => trim((string) ($parsed['genericname'] ?? '')),
'salt_name' => trim((string) ($parsed['salt_name'] ?? '')),
'dosage_restriction' => trim((string) ($parsed['dosage_restriction'] ?? '')),
'caution_note' => trim((string) ($parsed['caution_note'] ?? '')),
];
if (($details['genericname'] ?? '') !== '' || ($details['salt_name'] ?? '') !== '' || ($details['dosage_restriction'] ?? '') !== '') {
$detailsSource = 'ai';
}
}
}
$update = [];
if ($genericField !== null && trim((string) ($details['genericname'] ?? '')) !== '') {
$update[$genericField] = trim((string) ($details['genericname'] ?? ''));
}
if ($saltField !== null && trim((string) ($details['salt_name'] ?? '')) !== '') {
$update[$saltField] = trim((string) ($details['salt_name'] ?? ''));
}
if ($restrictionField !== null && trim((string) ($details['dosage_restriction'] ?? '')) !== '') {
$update[$restrictionField] = trim((string) ($details['dosage_restriction'] ?? ''));
}
if (! empty($update)) {
$this->db->table($table)->where('id', $medId)->update($update);
$detailsUpdated = true;
}
}
return [
'med_id' => $medId,
'is_new' => $isNew,
'details_updated' => $detailsUpdated,
'details_source' => $detailsSource,
];
}
private function resolveMedicineMasterIdByName(string $medName): int
{
$medName = trim($medName);
@ -5004,6 +5159,23 @@ class Opd_prescription extends BaseController
return $this->response->setJSON(['update' => 0, 'error_text' => 'Medicine name is required']);
}
$masterInfo = [
'med_id' => 0,
'is_new' => false,
'details_updated' => false,
'details_source' => '',
];
$resolvedMedName = trim((string) ($update['med_name'] ?? $current['med_name'] ?? ''));
$resolvedMedType = trim((string) ($update['med_type'] ?? $current['med_type'] ?? ''));
$resolvedMedId = (int) ($current['med_id'] ?? 0);
if ($resolvedMedId <= 0) {
$masterInfo = $this->ensureMedicineMasterRecordFromConsult($resolvedMedName, $resolvedMedType);
$resolvedMedId = (int) ($masterInfo['med_id'] ?? 0);
}
if ($resolvedMedId > 0 && in_array('med_id', $fields, true)) {
$update['med_id'] = $resolvedMedId;
}
$candidate = array_replace($current, $update);
$sessionId = (int) ($candidate['opd_pre_id'] ?? 0);
if ($sessionId > 0 && $this->isDuplicateMedicineRow($table, $fields, $sessionId, $candidate, $id)) {
@ -5024,7 +5196,7 @@ class Opd_prescription extends BaseController
$this->db->table($table)->where('id', $id)->update($update);
$this->clinicalAuditTrail->logChangedFields('opd_prescription_medicine', $id, $current, array_replace($current, $update), $this->getCurrentUserId());
$masterMedId = (int) ($current['med_id'] ?? 0);
$masterMedId = (int) ($update['med_id'] ?? $current['med_id'] ?? 0);
if ($masterMedId <= 0) {
$masterMedId = $this->resolveMedicineMasterIdByName((string) ($update['med_name'] ?? $current['med_name'] ?? ''));
}
@ -5042,9 +5214,24 @@ class Opd_prescription extends BaseController
]);
}
$message = 'Medicine updated';
if (! empty($masterInfo['is_new'])) {
$message .= ' | New medicine saved in OPD master';
}
if (! empty($masterInfo['details_updated'])) {
$src = trim((string) ($masterInfo['details_source'] ?? 'AI/local'));
if ($src === '') {
$src = 'AI/local';
}
$message .= ' | Master details updated (' . $src . ')';
}
return $this->response->setJSON([
'update' => 1,
'error_text' => 'Medicine updated',
'new_master_medicine' => ! empty($masterInfo['is_new']) ? 1 : 0,
'master_details_updated' => ! empty($masterInfo['details_updated']) ? 1 : 0,
'master_details_source' => (string) ($masterInfo['details_source'] ?? ''),
'error_text' => $message,
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);

View File

@ -5187,12 +5187,15 @@
$('#opd_session_id').val(data.opd_session_id || sid);
clearMedicineForm(true);
loadMedicineList();
$('.jsError').removeClass('text-danger text-muted').addClass('text-success').text(data.error_text || 'Medicine saved.');
// Keep typing flow fast: move cursor back to medicine field.
setTimeout(function() {
var $medName = $('#med_name');
$medName.trigger('focus');
$medName.trigger('select');
}, 20);
} else {
$('.jsError').removeClass('text-success text-muted').addClass('text-danger').text(data.error_text || 'Unable to save medicine.');
}
});
});