fix(abdm): resolve wellness patient mismatch in fetch, sugar LOINC codes, duplicate notifications and hi_type mapping

This commit is contained in:
DevSoftTech Server 2026-10-07 14:20:41 +05:30
parent db76ad9759
commit bd02f895c5
5 changed files with 270 additions and 133 deletions

View File

@ -5080,79 +5080,110 @@ class AbdmGateway extends BaseController
} }
} }
// Strategy 0c: Live On-Demand Fresh Generation for Wellness Record // Strategy 0c: Live Retrieval & On-Demand Fresh Generation for Wellness Record
if (preg_match('/^WELLNESS-(?:(\d+)-)?(?:S)?(\d+)/i', $ref, $wMatch)) { if (str_starts_with(strtoupper($ref), 'WELLNESS-')) {
$wPart1 = (int) ($wMatch[1] ?? 0); $foundBundle = null;
$wPart2 = (int) ($wMatch[2] ?? 0); $foundDisplay = null;
$targetPatientId = 0; // 1. Check health_records first for this exact care context reference
$targetSessionId = 0; if ($db->tableExists('health_records')) {
$hrRow = $db->table('health_records')
->where('care_context_reference', $ref)
->groupStart()
->where('record_data IS NOT NULL AND record_data !=', '')
->orWhere('fhir_bundle_enc IS NOT NULL AND fhir_bundle_enc !=', '')
->groupEnd()
->orderBy('id', 'DESC')
->get(1)
->getRowArray();
// Check if part2 is a date (e.g. 20261006) if ($hrRow && ! empty($hrRow['record_data'])) {
$isDatePart2 = ($wPart2 >= 19700101 && $wPart2 <= 21001231); $bundleData = json_decode((string) $hrRow['record_data'], true);
$candidateEntityId = $isDatePart2 ? $wPart1 : $wPart2; if (is_array($bundleData) && ! empty($bundleData)) {
$candidatePatientId = $isDatePart2 ? 0 : $wPart1; $foundBundle = $bundleData;
$foundDisplay = $hrRow['care_context_display'] ?? null;
if ($db->tableExists('opd_prescription')) { }
// Try finding by prescription id } elseif ($hrRow && ! empty($hrRow['fhir_bundle_enc'])) {
if ($candidateEntityId > 0) { try {
$pRow = $db->table('opd_prescription') $enc = new FhirEncryptionService();
->select('id, p_id, opd_id') $decrypted = $enc->decrypt((string) $hrRow['fhir_bundle_enc']);
->where('id', $candidateEntityId) $bundleData = json_decode($decrypted, true);
->get(1) if (is_array($bundleData) && ! empty($bundleData)) {
->getRowArray(); $foundBundle = $bundleData;
if ($pRow) { $foundDisplay = $hrRow['care_context_display'] ?? null;
$targetSessionId = (int) $pRow['id']; }
$targetPatientId = (int) $pRow['p_id']; } catch (\Throwable) {
} }
} }
// If not found, check by session_id or opd_id }
if ($targetPatientId <= 0 && $candidateEntityId > 0) {
$pRow = $db->table('opd_prescription') // 2. Check patient_wellness_records if not found in health_records
->select('id, p_id, opd_id') if ($foundBundle === null && $db->tableExists('patient_wellness_records')) {
->where('session_id', $candidateEntityId) $wRow = $db->table('patient_wellness_records')->where('care_context_reference', $ref)->get(1)->getRowArray();
->orWhere('opd_id', $candidateEntityId) if (! $wRow && preg_match('/-W(\d+)/i', $ref, $wIdM)) {
->orderBy('id', 'DESC') $wRow = $db->table('patient_wellness_records')->where('id', (int) $wIdM[1])->get(1)->getRowArray();
->get(1)
->getRowArray();
if ($pRow) {
$targetSessionId = (int) $pRow['id'];
$targetPatientId = (int) $pRow['p_id'];
}
} }
// If candidatePatientId is set, or if candidateEntityId was patient_id if ($wRow) {
if ($targetPatientId <= 0) { if (! empty($wRow['fhir_bundle_json'])) {
$testPid = $candidatePatientId > 0 ? $candidatePatientId : $candidateEntityId; $foundBundle = json_decode((string) $wRow['fhir_bundle_json'], true);
if ($testPid > 0) { }
$pRow = $db->table('opd_prescription') if (empty($foundBundle)) {
->select('id, p_id, opd_id') $wellnessPayload = $this->buildWellnessRecordPayload((int) $wRow['patient_id'], (int) $wRow['id'], '');
->where('p_id', $testPid) if ($wellnessPayload !== null && ! empty($wellnessPayload['bundle'])) {
->orderBy('id', 'DESC') $foundBundle = $wellnessPayload['bundle'];
->get(1) $foundDisplay = $wellnessPayload['care_context_display'] ?? null;
->getRowArray();
if ($pRow) {
$targetPatientId = (int) $pRow['p_id'];
$targetSessionId = (int) $pRow['id'];
} }
} }
} }
} }
if ($targetPatientId > 0) { // 3. Fallback: Parse patient ID and OPD session ID for legacy OPD vitals references
try { if ($foundBundle === null) {
$wellnessPayload = $this->buildWellnessRecordPayload($targetPatientId, $targetSessionId, ''); $targetPatientId = 0;
if ($wellnessPayload !== null && !empty($wellnessPayload['bundle'])) { $targetSessionId = 0;
$records[] = [
'careContextReference' => $ref, if (preg_match('/^WELLNESS-(?:(\d+)-)?(?:S(\d+)|W(\d+)|(\d+))(?:-(\d+))?/i', $ref, $wMatch)) {
'hiType' => 'WellnessRecord', $pPart = (int) ($wMatch[1] ?? 0);
'display' => $wellnessPayload['care_context_display'] ?? ('Wellness Record - ' . date('d M Y')), $sPart = (int) (! empty($wMatch[2]) ? $wMatch[2] : (! empty($wMatch[3]) ? $wMatch[3] : ($wMatch[4] ?? 0)));
'bundle' => $wellnessPayload['bundle'], $dPart = (int) ($wMatch[5] ?? 0);
];
continue; if ($pPart > 0) {
$targetPatientId = $pPart;
$targetSessionId = $sPart;
} elseif ($sPart > 0) {
if ($db->tableExists('patient_master') && $db->table('patient_master')->where('id', $sPart)->countAllResults() > 0) {
$targetPatientId = $sPart;
} elseif ($db->tableExists('opd_prescription')) {
$pRow = $db->table('opd_prescription')->select('p_id, id')->where('id', $sPart)->get(1)->getRowArray();
if ($pRow) {
$targetPatientId = (int) $pRow['p_id'];
$targetSessionId = (int) $pRow['id'];
}
}
} }
} catch (\Throwable $ex) {
log_message('warning', 'Live Wellness FHIR generation in recordsFetch failed: ' . $ex->getMessage());
} }
if ($targetPatientId > 0) {
try {
$wellnessPayload = $this->buildWellnessRecordPayload($targetPatientId, $targetSessionId, '');
if ($wellnessPayload !== null && ! empty($wellnessPayload['bundle'])) {
$foundBundle = $wellnessPayload['bundle'];
$foundDisplay = $wellnessPayload['care_context_display'] ?? null;
}
} catch (\Throwable $ex) {
log_message('warning', 'Live Wellness FHIR generation in recordsFetch failed: ' . $ex->getMessage());
}
}
}
if ($foundBundle !== null && ! empty($foundBundle)) {
$records[] = [
'careContextReference' => $ref,
'hiType' => 'WellnessRecord',
'display' => $foundDisplay ?? ('Wellness Record - ' . date('d M Y')),
'bundle' => $foundBundle,
];
continue;
} }
} }
@ -6584,15 +6615,15 @@ class AbdmGateway extends BaseController
]); ]);
} }
} }
if ($this->db->tableExists('opd_prescription')) { if (empty($wRec) && $this->db->tableExists('opd_prescription')) {
$wopd = $this->db->table('opd_prescription')->where('id', $taskEntityId)->get(1)->getRowArray(); $wopd = $this->db->table('opd_prescription')->where('id', $taskEntityId)->where('p_id', $patientId)->get(1)->getRowArray();
if (! empty($wopd)) { if (! empty($wopd)) {
$vDate = ! empty($wopd['date_opd_visit']) ? $wopd['date_opd_visit'] : date('Y-m-d'); $vDate = ! empty($wopd['date_opd_visit']) ? $wopd['date_opd_visit'] : date('Y-m-d');
$dateStr = date('d M Y', strtotime((string) $vDate)); $dateStr = date('d M Y', strtotime((string) $vDate));
$cleanDate = date('Ymd', strtotime((string) $vDate)); $cleanDate = date('Ymd', strtotime((string) $vDate));
// Reference format matching task board (e.g. WELLNESS-33723-20261006) // Reference format matching task board (e.g. WELLNESS-33723-20261006)
$ccRef = 'WELLNESS-' . $taskEntityId . '-' . $cleanDate; $ccRef = 'WELLNESS-' . $taskEntityId . '-' . $cleanDate;
$display = 'WellnessRecord - ' . $dateStr; $display = 'Wellness Record - ' . $dateStr;
$addContext([ $addContext([
'careContextId' => $ccRef, 'careContextId' => $ccRef,
'referenceNumber' => $ccRef, 'referenceNumber' => $ccRef,
@ -6632,7 +6663,10 @@ class AbdmGateway extends BaseController
if (str_starts_with($ccRef, 'OPD-') || str_starts_with($ccRef, 'INVOICE-')) { if (str_starts_with($ccRef, 'OPD-') || str_starts_with($ccRef, 'INVOICE-')) {
continue; continue;
} }
$hiType = trim((string) ($row['hi_type'] ?? 'HealthDocumentRecord')); $hiType = trim((string) ($row['hi_type'] ?? ''));
if ($hiType === '' || ($hiType === 'HealthDocumentRecord' && str_starts_with($ccRef, 'WELLNESS-'))) {
$hiType = str_starts_with($ccRef, 'WELLNESS-') ? 'WellnessRecord' : 'HealthDocumentRecord';
}
$dateStr = date('d M Y', strtotime((string) ($row['created_at'] ?? $row['updated_at'] ?? 'now'))); $dateStr = date('d M Y', strtotime((string) ($row['created_at'] ?? $row['updated_at'] ?? 'now')));
$display = ($hiType === 'WellnessRecord' ? 'Wellness & Vitals Record - ' : ($hiType . ' - ')) . $dateStr; $display = ($hiType === 'WellnessRecord' ? 'Wellness & Vitals Record - ' : ($hiType . ' - ')) . $dateStr;
@ -8478,12 +8512,9 @@ class AbdmGateway extends BaseController
} }
$careContextReference = trim((string) ($data['care_context_reference'] ?? '')); $careContextReference = trim((string) ($data['care_context_reference'] ?? ''));
if (($data['reuse_existing'] ?? false) === true && $careContextReference !== '') { if ($careContextReference !== '') {
$existing = $this->db->table('health_records') $existing = $this->db->table('health_records')
->select('id') ->select('id')
->where('hi_type', (string) ($data['hi_type'] ?? 'unknown'))
->where('entity_type', (string) ($data['entity_type'] ?? ''))
->where('entity_id', (string) ($data['entity_id'] ?? ''))
->where('care_context_reference', $careContextReference) ->where('care_context_reference', $careContextReference)
->orderBy('id', 'DESC') ->orderBy('id', 'DESC')
->get(1) ->get(1)
@ -8491,7 +8522,7 @@ class AbdmGateway extends BaseController
if (! empty($existing)) { if (! empty($existing)) {
$existingId = (int) ($existing['id'] ?? 0); $existingId = (int) ($existing['id'] ?? 0);
$update = $insert; $update = $insert;
unset($update['push_status'], $update['push_at'], $update['created_at']); unset($update['created_at']);
$this->db->table('health_records')->where('id', $existingId)->update($update); $this->db->table('health_records')->where('id', $existingId)->update($update);
return $existingId; return $existingId;
} }
@ -9514,6 +9545,7 @@ class AbdmGateway extends BaseController
'notes' => $ccDisplay, 'notes' => $ccDisplay,
'queue_id' => $ccRef, 'queue_id' => $ccRef,
'record_data' => $bundle, 'record_data' => $bundle,
'skip_auto_link' => true,
]); ]);
$this->logGatewayPushResolution('additional_hi_record', $result); $this->logGatewayPushResolution('additional_hi_record', $result);
$queueId = $this->extractGatewayPushQueueId($result); $queueId = $this->extractGatewayPushQueueId($result);
@ -9590,14 +9622,21 @@ class AbdmGateway extends BaseController
// 1. Check patient_wellness_records first (New M2 Comprehensive Wellness & Vitals) // 1. Check patient_wellness_records first (New M2 Comprehensive Wellness & Vitals)
if ($this->db->tableExists('patient_wellness_records')) { if ($this->db->tableExists('patient_wellness_records')) {
$wBuilder = $this->db->table('patient_wellness_records')->where('patient_id', $patientId); $wRow = null;
if ($opdId > 0) { if ($opdId > 0) {
$wBuilder->groupStart() $wRow = $this->db->table('patient_wellness_records')->where('id', $opdId)->get(1)->getRowArray();
->where('id', $opdId) if ($wRow && (int) ($wRow['patient_id'] ?? 0) !== $patientId) {
->orWhere('patient_id', $patientId) $patientId = (int) $wRow['patient_id'];
->groupEnd(); $patientRow = $this->loadPatientRow($patientId);
}
}
if (empty($wRow)) {
$wRow = $this->db->table('patient_wellness_records')
->where('patient_id', $patientId)
->orderBy('id', 'DESC')
->get(1)
->getRowArray();
} }
$wRow = $wBuilder->orderBy('id', 'DESC')->get(1)->getRowArray();
if (! empty($wRow)) { if (! empty($wRow)) {
$bundle = ! empty($wRow['fhir_bundle_json']) ? json_decode((string) $wRow['fhir_bundle_json'], true) : []; $bundle = ! empty($wRow['fhir_bundle_json']) ? json_decode((string) $wRow['fhir_bundle_json'], true) : [];
$visitDate = date('Y-m-d', strtotime((string) ($wRow['recorded_at'] ?? 'now'))); $visitDate = date('Y-m-d', strtotime((string) ($wRow['recorded_at'] ?? 'now')));
@ -9608,6 +9647,25 @@ class AbdmGateway extends BaseController
: ('WELLNESS-' . $patientId . '-W' . $entityId . '-' . $cleanDate); : ('WELLNESS-' . $patientId . '-W' . $entityId . '-' . $cleanDate);
$ccDisplay = 'Wellness Record - ' . date('d/m/Y', strtotime($visitDate)); $ccDisplay = 'Wellness Record - ' . date('d/m/Y', strtotime($visitDate));
if (empty($bundle) && class_exists('\App\Controllers\DoctorDocument')) {
try {
$docCtrl = new \App\Controllers\DoctorDocument();
$wSource = $docCtrl->buildWellnessRecordSource($patientId, (int) $wRow['id'], 'wellness');
if (! empty($wSource)) {
$wFactory = new FhirGeneratorFactory();
$wGen = $wFactory->wellness()->generate($wSource);
if (! empty($wGen['bundle'])) {
$bundle = $wGen['bundle'];
$this->db->table('patient_wellness_records')->where('id', (int) $wRow['id'])->update([
'fhir_bundle_json' => json_encode($bundle, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
'care_context_reference' => $ccRef,
]);
}
}
} catch (\Throwable $e) {
}
}
if (! empty($bundle)) { if (! empty($bundle)) {
return [ return [
'hi_type' => 'WellnessRecord', 'hi_type' => 'WellnessRecord',

View File

@ -712,7 +712,7 @@ class DoctorDocument extends BaseController
]); ]);
} }
public function buildWellnessRecordSource(int $patientId, int $opdSessionId = 0): array public function buildWellnessRecordSource(int $patientId, int $opdSessionId = 0, string $sourceEntity = ''): array
{ {
if ($patientId <= 0 || ! $this->db->tableExists('patient_master')) { if ($patientId <= 0 || ! $this->db->tableExists('patient_master')) {
return []; return [];
@ -723,8 +723,26 @@ class DoctorDocument extends BaseController
return []; return [];
} }
$wRow = [];
$isWellnessDirect = ($sourceEntity === 'wellness');
if ($this->db->tableExists('patient_wellness_records')) {
if ($opdSessionId > 0) {
$wRow = $this->db->table('patient_wellness_records')->where('id', $opdSessionId)->get(1)->getRowArray() ?? [];
if (! empty($wRow)) {
$isWellnessDirect = true;
if (! empty($wRow['patient_id']) && (int) $wRow['patient_id'] !== $patientId) {
$patientId = (int) $wRow['patient_id'];
$patientRow = $this->db->table('patient_master')->where('id', $patientId)->get(1)->getRowArray() ?? $patientRow;
}
}
}
if (empty($wRow) && $isWellnessDirect) {
$wRow = $this->db->table('patient_wellness_records')->where('patient_id', $patientId)->orderBy('id', 'DESC')->get(1)->getRowArray() ?? [];
}
}
$rxRow = []; $rxRow = [];
if ($this->db->tableExists('opd_prescription')) { if (! $isWellnessDirect && $this->db->tableExists('opd_prescription')) {
$builder = $this->db->table('opd_prescription')->where('p_id', $patientId); $builder = $this->db->table('opd_prescription')->where('p_id', $patientId);
if ($opdSessionId > 0) { if ($opdSessionId > 0) {
$builder->groupStart() $builder->groupStart()
@ -758,53 +776,81 @@ class DoctorDocument extends BaseController
} }
}; };
$addVital('8480-6', 'Systolic blood pressure', $rxRow['bp'] ?? null, 'mmHg', 'mm[Hg]'); if (! empty($wRow)) {
$addVital('8462-4', 'Diastolic blood pressure', $rxRow['diastolic'] ?? null, 'mmHg', 'mm[Hg]'); // Populate comprehensive vitals directly from patient_wellness_records
$addVital('8867-4', 'Heart rate', $rxRow['pulse'] ?? null, '/min', '/min'); $addVital('8480-6', 'Systolic blood pressure', $wRow['bp_systolic'] ?? null, 'mmHg', 'mm[Hg]');
$addVital('8302-2', 'Body height', $rxRow['height'] ?? null, 'cm', 'cm'); $addVital('8462-4', 'Diastolic blood pressure', $wRow['bp_diastolic'] ?? null, 'mmHg', 'mm[Hg]');
$addVital('29463-7', 'Body weight', $rxRow['weight'] ?? null, 'kg', 'kg'); $addVital('8867-4', 'Heart rate', $wRow['pulse_rate'] ?? null, '/min', '/min');
$addVital('39156-5', 'Body Mass Index', $rxRow['bmi'] ?? null, 'kg/m2', 'kg/m2'); $addVital('8302-2', 'Body height', $wRow['height_cm'] ?? null, 'cm', 'cm');
$addVital('29463-7', 'Body weight', $wRow['weight_kg'] ?? null, 'kg', 'kg');
$tempVal = $rxRow['temp'] ?? null; $addVital('39156-5', 'Body Mass Index', $wRow['bmi'] ?? null, 'kg/m2', 'kg/m2');
if ($tempVal !== null && is_numeric($tempVal)) { $tC = $wRow['temperature_c'] ?? null;
$tempVal = (float) $tempVal > 45 ? round((((float) $tempVal - 32) * 5) / 9, 1) : round((float) $tempVal, 1); if ($tC === null && ! empty($wRow['temperature_f'])) {
} $tC = round((((float) $wRow['temperature_f'] - 32) * 5) / 9, 1);
$addVital('8310-5', 'Body temperature', $tempVal, 'Cel', 'Cel');
$addVital('9279-1', 'Respiratory rate', $rxRow['rr_min'] ?? null, '/min', '/min');
$addVital('59408-5', 'Oxygen saturation in Arterial blood by Pulse oximetry', $rxRow['spo2'] ?? null, '%', '%');
$addVital('2339-0', 'Blood Glucose', $rxRow['glucose'] ?? null, 'mg/dL', 'mg/dL');
// If no vitals found in opd_prescription, check patient_wellness_records
if (empty($vitals) && $this->db->tableExists('patient_wellness_records')) {
$wBuilder = $this->db->table('patient_wellness_records')->where('patient_id', $patientId);
if ($opdSessionId > 0) {
$wBuilder->groupStart()
->where('id', $opdSessionId)
->orWhere('patient_id', $patientId)
->groupEnd();
} }
$wRow = $wBuilder->orderBy('id', 'DESC')->get(1)->getRowArray(); $addVital('8310-5', 'Body temperature', $tC, 'Cel', 'Cel');
if (! empty($wRow)) { $addVital('9279-1', 'Respiratory rate', $wRow['resp_rate'] ?? null, '/min', '/min');
$addVital('8480-6', 'Systolic blood pressure', $wRow['bp_systolic'] ?? null, 'mmHg', 'mm[Hg]'); $addVital('59408-5', 'Oxygen saturation in Arterial blood by Pulse oximetry', $wRow['spo2'] ?? null, '%', '%');
$addVital('8462-4', 'Diastolic blood pressure', $wRow['bp_diastolic'] ?? null, 'mmHg', 'mm[Hg]'); $addVital('2339-0', 'Random Blood Glucose', $wRow['sugar_random'] ?? null, 'mg/dL', 'mg/dL');
$addVital('8867-4', 'Heart rate', $wRow['pulse_rate'] ?? null, '/min', '/min'); $addVital('1558-6', 'Fasting Blood Glucose', $wRow['sugar_fasting'] ?? null, 'mg/dL', 'mg/dL');
$addVital('8302-2', 'Body height', $wRow['height_cm'] ?? null, 'cm', 'cm'); $addVital('14760-3', 'Glucose [Mass/volume] in Blood 2 hours post meal', $wRow['sugar_pp'] ?? null, 'mg/dL', 'mg/dL');
$addVital('29463-7', 'Body weight', $wRow['weight_kg'] ?? null, 'kg', 'kg'); $addVital('4548-4', 'Hemoglobin A1c', $wRow['hba1c'] ?? null, '%', '%');
$addVital('39156-5', 'Body Mass Index', $wRow['bmi'] ?? null, 'kg/m2', 'kg/m2'); $addVital('718-7', 'Hemoglobin', $wRow['hemoglobin'] ?? null, 'g/dL', 'g/dL');
$tC = $wRow['temperature_c'] ?? null; $addVital('56115-9', 'Waist circumference', $wRow['waist_circumference_cm'] ?? null, 'cm', 'cm');
if ($tC === null && ! empty($wRow['temperature_f'])) { $addVital('56114-2', 'Hip circumference', $wRow['hip_circumference_cm'] ?? null, 'cm', 'cm');
$tC = round((((float) $wRow['temperature_f'] - 32) * 5) / 9, 1); $addVital('8280-0', 'Waist to hip ratio', $wRow['waist_hip_ratio'] ?? null, 'ratio', '{ratio}');
$addVital('55423-8', 'Number of steps in 24 hour Measured', $wRow['daily_steps'] ?? null, '{steps}', '{steps}');
$addVital('93832-4', 'Sleep duration', $wRow['sleep_hours'] ?? null, 'h', 'h');
$addVital('55411-3', 'Exercise duration', $wRow['exercise_min_per_day'] ?? null, 'min/d', 'min/d');
$addVital('72514-3', 'Pain severity - 0-10 verbal numeric rating', $wRow['pain_score'] ?? null, '{score}', '{score}');
} else {
// OPD prescription vitals fallback
$addVital('8480-6', 'Systolic blood pressure', $rxRow['bp'] ?? null, 'mmHg', 'mm[Hg]');
$addVital('8462-4', 'Diastolic blood pressure', $rxRow['diastolic'] ?? null, 'mmHg', 'mm[Hg]');
$addVital('8867-4', 'Heart rate', $rxRow['pulse'] ?? null, '/min', '/min');
$addVital('8302-2', 'Body height', $rxRow['height'] ?? null, 'cm', 'cm');
$addVital('29463-7', 'Body weight', $rxRow['weight'] ?? null, 'kg', 'kg');
$addVital('39156-5', 'Body Mass Index', $rxRow['bmi'] ?? null, 'kg/m2', 'kg/m2');
$tempVal = $rxRow['temp'] ?? null;
if ($tempVal !== null && is_numeric($tempVal)) {
$tempVal = (float) $tempVal > 45 ? round((((float) $tempVal - 32) * 5) / 9, 1) : round((float) $tempVal, 1);
}
$addVital('8310-5', 'Body temperature', $tempVal, 'Cel', 'Cel');
$addVital('9279-1', 'Respiratory rate', $rxRow['rr_min'] ?? null, '/min', '/min');
$addVital('59408-5', 'Oxygen saturation in Arterial blood by Pulse oximetry', $rxRow['spo2'] ?? null, '%', '%');
$addVital('2339-0', 'Blood Glucose', $rxRow['glucose'] ?? null, 'mg/dL', 'mg/dL');
// If no vitals found in opd_prescription, check patient_wellness_records
if (empty($vitals) && $this->db->tableExists('patient_wellness_records')) {
$wBuilder = $this->db->table('patient_wellness_records')->where('patient_id', $patientId);
if ($opdSessionId > 0) {
$wBuilder->where('id', $opdSessionId);
}
$wRow = $wBuilder->orderBy('id', 'DESC')->get(1)->getRowArray();
if (! empty($wRow)) {
$addVital('8480-6', 'Systolic blood pressure', $wRow['bp_systolic'] ?? null, 'mmHg', 'mm[Hg]');
$addVital('8462-4', 'Diastolic blood pressure', $wRow['bp_diastolic'] ?? null, 'mmHg', 'mm[Hg]');
$addVital('8867-4', 'Heart rate', $wRow['pulse_rate'] ?? null, '/min', '/min');
$addVital('8302-2', 'Body height', $wRow['height_cm'] ?? null, 'cm', 'cm');
$addVital('29463-7', 'Body weight', $wRow['weight_kg'] ?? null, 'kg', 'kg');
$addVital('39156-5', 'Body Mass Index', $wRow['bmi'] ?? null, 'kg/m2', 'kg/m2');
$tC = $wRow['temperature_c'] ?? null;
if ($tC === null && ! empty($wRow['temperature_f'])) {
$tC = round((((float) $wRow['temperature_f'] - 32) * 5) / 9, 1);
}
$addVital('8310-5', 'Body temperature', $tC, 'Cel', 'Cel');
$addVital('9279-1', 'Respiratory rate', $wRow['resp_rate'] ?? null, '/min', '/min');
$addVital('59408-5', 'Oxygen saturation in Arterial blood by Pulse oximetry', $wRow['spo2'] ?? null, '%', '%');
$addVital('2339-0', 'Random Blood Glucose', $wRow['sugar_random'] ?? null, 'mg/dL', 'mg/dL');
$addVital('1558-6', 'Fasting Blood Glucose', $wRow['sugar_fasting'] ?? null, 'mg/dL', 'mg/dL');
$addVital('14760-3', 'Glucose [Mass/volume] in Blood 2 hours post meal', $wRow['sugar_pp'] ?? null, 'mg/dL', 'mg/dL');
$addVital('4548-4', 'Hemoglobin A1c', $wRow['hba1c'] ?? null, '%', '%');
$addVital('718-7', 'Hemoglobin', $wRow['hemoglobin'] ?? null, 'g/dL', 'g/dL');
$addVital('56115-9', 'Waist circumference', $wRow['waist_circumference_cm'] ?? null, 'cm', 'cm');
$addVital('56114-2', 'Hip circumference', $wRow['hip_circumference_cm'] ?? null, 'cm', 'cm');
$addVital('8280-0', 'Waist to hip ratio', $wRow['waist_hip_ratio'] ?? null, 'ratio', '{ratio}');
} }
$addVital('8310-5', 'Body temperature', $tC, 'Cel', 'Cel');
$addVital('9279-1', 'Respiratory rate', $wRow['resp_rate'] ?? null, '/min', '/min');
$addVital('59408-5', 'Oxygen saturation in Arterial blood by Pulse oximetry', $wRow['spo2'] ?? null, '%', '%');
$addVital('2339-0', 'Random Blood Glucose', $wRow['sugar_random'] ?? null, 'mg/dL', 'mg/dL');
$addVital('1558-6', 'Fasting Blood Glucose', $wRow['sugar_fasting'] ?? null, 'mg/dL', 'mg/dL');
$addVital('4548-4', 'Hemoglobin A1c', $wRow['hba1c'] ?? null, '%', '%');
$addVital('718-7', 'Hemoglobin', $wRow['hemoglobin'] ?? null, 'g/dL', 'g/dL');
$addVital('8280-0', 'Waist Circumference', $wRow['waist_circumference_cm'] ?? null, 'cm', 'cm');
$addVital('98337-9', 'Hip Circumference', $wRow['hip_circumference_cm'] ?? null, 'cm', 'cm');
$addVital('72287-6', 'Waist-hip ratio', $wRow['waist_hip_ratio'] ?? null, '{ratio}', '{ratio}');
} }
} }
@ -862,21 +908,36 @@ class DoctorDocument extends BaseController
$hospitalName = (string) constant('H_Name'); $hospitalName = (string) constant('H_Name');
} }
$doctorName = trim(trim((string) ($doctorRow['p_fname'] ?? '')) . ' ' . trim((string) ($doctorRow['p_lname'] ?? ''))); $doctorName = ! empty($wRow['recorded_by'])
? trim((string) $wRow['recorded_by'])
: trim(trim((string) ($doctorRow['p_fname'] ?? '')) . ' ' . trim((string) ($doctorRow['p_lname'] ?? '')));
if ($doctorName === '') { if ($doctorName === '') {
$doctorName = 'Doctor'; $doctorName = 'Attending Medical Officer';
} }
$visitDate = ! empty($rxRow['date_opd_visit']) ? date('Y-m-d', strtotime((string) $rxRow['date_opd_visit'])) : date('Y-m-d'); $visitDate = ! empty($wRow['recorded_at'])
$completedAt = ! empty($rxRow['insert_date']) ? date(DATE_ATOM, strtotime((string) $rxRow['insert_date'])) : date(DATE_ATOM); ? date('Y-m-d', strtotime((string) $wRow['recorded_at']))
: (! empty($rxRow['date_opd_visit']) ? date('Y-m-d', strtotime((string) $rxRow['date_opd_visit'])) : date('Y-m-d'));
$completedAt = ! empty($wRow['recorded_at'])
? date(DATE_ATOM, strtotime((string) $wRow['recorded_at']))
: (! empty($rxRow['insert_date']) ? date(DATE_ATOM, strtotime((string) $rxRow['insert_date'])) : date(DATE_ATOM));
$recId = ! empty($wRow['id']) ? (string) $wRow['id'] : (string) ($rxRow['id'] ?? $patientId);
$rawGender = strtolower(trim((string) ($patientRow['gender'] ?? '')));
$cleanGender = match ($rawGender) {
'1', 'm', 'male' => 'male',
'2', 'f', 'female' => 'female',
default => 'unknown',
};
return [ return [
'record_id' => (string) ($rxRow['id'] ?? $patientId), 'record_id' => $recId,
'session_id' => (string) $opdSessionId, 'session_id' => (string) $opdSessionId,
'visit_date' => $visitDate, 'visit_date' => $visitDate,
'completed_at' => $completedAt, 'completed_at' => $completedAt,
'encounter' => [ 'encounter' => [
'id' => (string) ($rxRow['id'] ?? $patientId), 'id' => $recId,
'class_code' => 'AMB', 'class_code' => 'AMB',
'class_display' => 'ambulatory', 'class_display' => 'ambulatory',
'start' => $completedAt, 'start' => $completedAt,
@ -897,14 +958,14 @@ class DoctorDocument extends BaseController
'id' => (string) $patientId, 'id' => (string) $patientId,
'uhid' => (string) ($patientRow['uhid_no'] ?? $patientRow['uhid'] ?? $patientRow['patient_code'] ?? $patientId), 'uhid' => (string) ($patientRow['uhid_no'] ?? $patientRow['uhid'] ?? $patientRow['patient_code'] ?? $patientId),
'name' => $patientName, 'name' => $patientName,
'gender' => strtolower((string) ($patientRow['gender'] ?? '')) === 'm' ? 'male' : (strtolower((string) ($patientRow['gender'] ?? '')) === 'f' ? 'female' : 'unknown'), 'gender' => $cleanGender,
'dob' => ! empty($patientRow['dob']) ? date('Y-m-d', strtotime((string) $patientRow['dob'])) : '', 'dob' => ! empty($patientRow['dob']) ? date('Y-m-d', strtotime((string) $patientRow['dob'])) : '',
'abha_id' => $abhaDigits, 'abha_id' => $abhaDigits,
'abha_address' => $abhaAddress, 'abha_address' => $abhaAddress,
'mobile' => (string) ($patientRow['mphone1'] ?? ''), 'mobile' => (string) ($patientRow['mphone1'] ?? ''),
], ],
'practitioner' => [ 'practitioner' => [
'id' => (string) $drId, 'id' => (string) ($wRow['recorded_by_id'] ?? $drId ?: 1),
'name' => $doctorName, 'name' => $doctorName,
], ],
]; ];

View File

@ -1510,6 +1510,9 @@ class EAtriaBridgeConnector implements AbdmConnectorInterface
if ($abhaId !== '') { if ($abhaId !== '') {
$recordPushPayload['abha_id'] = $abhaId; $recordPushPayload['abha_id'] = $abhaId;
} }
if (! empty($data['skip_auto_link'])) {
$recordPushPayload['skip_auto_link'] = true;
}
$res = $this->post('/v3/records/push', $recordPushPayload); $res = $this->post('/v3/records/push', $recordPushPayload);
return self::normalizePushRecordResponse($res); return self::normalizePushRecordResponse($res);

View File

@ -91,7 +91,7 @@ class WellnessFhirGenerator extends AbstractModuleFhirGenerator
// Body measurement LOINC codes // Body measurement LOINC codes
$bodyMeasLoincCodes = ['8302-2', '29463-7', '39156-5', '56115-9', '56114-2', '8280-0']; $bodyMeasLoincCodes = ['8302-2', '29463-7', '39156-5', '56115-9', '56114-2', '8280-0'];
// General assessment LOINC codes (blood sugar, lab POC, Hb) // General assessment LOINC codes (blood sugar, lab POC, Hb)
$generalAssessLoincCodes = ['2339-0', '1558-6', '1521-4', '4548-4', '718-7', '25428-4', '1753-3']; $generalAssessLoincCodes = ['2339-0', '1558-6', '1521-4', '14760-3', '2345-7', '4548-4', '718-7', '25428-4', '1753-3'];
// Physical activity LOINC codes // Physical activity LOINC codes
$activityLoincCodes = ['55423-8', '93832-4', '55411-3', '41981-2']; $activityLoincCodes = ['55423-8', '93832-4', '55411-3', '41981-2'];
@ -216,7 +216,7 @@ class WellnessFhirGenerator extends AbstractModuleFhirGenerator
$numericVal = round($numericVal, 1); $numericVal = round($numericVal, 1);
} elseif ($loincCode === '8280-0' || $loincCode === '4548-4') { } elseif ($loincCode === '8280-0' || $loincCode === '4548-4') {
$numericVal = round($numericVal, 2); $numericVal = round($numericVal, 2);
} elseif (in_array($loincCode, ['8867-4', '59408-5', '9279-1', '2339-0', '1558-6', '1521-4'], true)) { } elseif (in_array($loincCode, ['8867-4', '59408-5', '9279-1', '2339-0', '1558-6', '1521-4', '14760-3', '2345-7'], true)) {
$numericVal = (float) round($numericVal); $numericVal = (float) round($numericVal);
} else { } else {
$numericVal = round($numericVal, 2); $numericVal = round($numericVal, 2);

View File

@ -852,6 +852,21 @@
var ref = cb.value; var ref = cb.value;
var display = cb.nextElementSibling ? cb.nextElementSibling.textContent : ref; var display = cb.nextElementSibling ? cb.nextElementSibling.textContent : ref;
var itemHiType = cb.dataset.hiType || ''; var itemHiType = cb.dataset.hiType || '';
if (/^WELLNESS-/i.test(ref)) {
itemHiType = 'WellnessRecord';
} else if (/^DOC-/i.test(ref)) {
itemHiType = 'HealthDocumentRecord';
} else if (/^INVOICE-/i.test(ref)) {
itemHiType = 'InvoiceRecord';
} else if (/^(?:LAB|RAD)-/i.test(ref)) {
itemHiType = 'DiagnosticReportRecord';
} else if (/^IMM-/i.test(ref)) {
itemHiType = 'ImmunizationRecord';
} else if (/^(?:DISCHARGE|IPD)-/i.test(ref)) {
itemHiType = 'DischargeSummaryRecord';
} else if (/^(?:OPD|PRESC)-/i.test(ref)) {
itemHiType = 'OPConsultRecord';
}
if (!dominantHiType && itemHiType) { if (!dominantHiType && itemHiType) {
dominantHiType = itemHiType; dominantHiType = itemHiType;
} }