Fix FHIR DiagnosticReportRecord datetime and practitioner quality
This commit is contained in:
parent
7595f9fd29
commit
e1d9e85e64
|
|
@ -966,6 +966,7 @@ class AbdmGateway extends BaseController
|
|||
$organization = $hospitalProfile['name'] !== ''
|
||||
? ['name' => $hospitalProfile['name'], 'hfr_id' => $hospitalProfile['hfr_id']]
|
||||
: null;
|
||||
$practitioner = $this->resolveLabPractitionerForFhir($labReqId, $labReq);
|
||||
|
||||
$encounter = [
|
||||
'id' => 'LAB-' . $labReqId,
|
||||
|
|
@ -988,7 +989,7 @@ class AbdmGateway extends BaseController
|
|||
]);
|
||||
}
|
||||
|
||||
$bundle = $fhir->buildLabReportBundle($patient, $diagnosticReport, $observations, null, $organization, $encounter, $pdfAttachment);
|
||||
$bundle = $fhir->buildLabReportBundle($patient, $diagnosticReport, $observations, $practitioner, $organization, $encounter, $pdfAttachment);
|
||||
$bundleJson = (string) json_encode($bundle, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
|
||||
// ── Store health_record ───────────────────────────────────────────────
|
||||
|
|
@ -1138,6 +1139,7 @@ class AbdmGateway extends BaseController
|
|||
$organization = $hospitalProfile['name'] !== ''
|
||||
? ['name' => $hospitalProfile['name'], 'hfr_id' => $hospitalProfile['hfr_id']]
|
||||
: null;
|
||||
$practitioner = $this->resolveLabPractitionerForFhir($labReqId, $labReq);
|
||||
|
||||
$encounter = [
|
||||
'id' => 'LAB-' . $labReqId,
|
||||
|
|
@ -1169,7 +1171,7 @@ class AbdmGateway extends BaseController
|
|||
]);
|
||||
}
|
||||
|
||||
$bundle = $fhir->buildLabReportBundle($patient, $diagnosticReport, $observations, null, $organization, $encounter, $pdfAttachment);
|
||||
$bundle = $fhir->buildLabReportBundle($patient, $diagnosticReport, $observations, $practitioner, $organization, $encounter, $pdfAttachment);
|
||||
|
||||
return $this->response->setJSON([
|
||||
'status' => 'ok',
|
||||
|
|
@ -2472,6 +2474,167 @@ class AbdmGateway extends BaseController
|
|||
return ['name' => $name, 'hfr_id' => $hfrId];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve practitioner details for lab/radiology DiagnosticReportRecord.
|
||||
*
|
||||
* Best-effort strategy:
|
||||
* 1) read doctor id/name/registration from lab_request (when present)
|
||||
* 2) enrich from doctor_master using doctor id
|
||||
*
|
||||
* @param int $labReqId
|
||||
* @param object|array<string,mixed>|null $labReqRow
|
||||
* @return array<string,string>|null
|
||||
*/
|
||||
private function resolveLabPractitionerForFhir(int $labReqId, $labReqRow = null): ?array
|
||||
{
|
||||
if ($labReqId <= 0 || ! $this->db->tableExists('lab_request')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$doctorId = 0;
|
||||
$doctorName = '';
|
||||
$doctorRegNo = '';
|
||||
|
||||
$candidateIdCols = ['doctor_id', 'dr_id', 'doc_id', 'r_doc_id', 'consultant_id', 'reported_by_doctor_id'];
|
||||
$candidateNameCols = ['doctor_name', 'dr_name', 'doc_name', 'r_doc_name', 'consultant_name', 'reported_by_name', 'reported_by'];
|
||||
$candidateRegCols = ['doctor_reg_no', 'registration_no', 'reg_no', 'doctor_registration_no'];
|
||||
|
||||
// Try immediate row first (if caller passed a richer row object/array).
|
||||
if (is_object($labReqRow)) {
|
||||
foreach ($candidateIdCols as $c) {
|
||||
if (isset($labReqRow->{$c}) && (int) $labReqRow->{$c} > 0) {
|
||||
$doctorId = (int) $labReqRow->{$c};
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach ($candidateNameCols as $c) {
|
||||
$v = isset($labReqRow->{$c}) ? trim((string) $labReqRow->{$c}) : '';
|
||||
if ($v !== '') {
|
||||
$doctorName = $v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach ($candidateRegCols as $c) {
|
||||
$v = isset($labReqRow->{$c}) ? trim((string) $labReqRow->{$c}) : '';
|
||||
if ($v !== '') {
|
||||
$doctorRegNo = $v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} elseif (is_array($labReqRow)) {
|
||||
foreach ($candidateIdCols as $c) {
|
||||
if (isset($labReqRow[$c]) && (int) $labReqRow[$c] > 0) {
|
||||
$doctorId = (int) $labReqRow[$c];
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach ($candidateNameCols as $c) {
|
||||
$v = isset($labReqRow[$c]) ? trim((string) $labReqRow[$c]) : '';
|
||||
if ($v !== '') {
|
||||
$doctorName = $v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach ($candidateRegCols as $c) {
|
||||
$v = isset($labReqRow[$c]) ? trim((string) $labReqRow[$c]) : '';
|
||||
if ($v !== '') {
|
||||
$doctorRegNo = $v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$labFields = $this->db->getFieldNames('lab_request') ?? [];
|
||||
$selectCols = ['id'];
|
||||
foreach (array_merge($candidateIdCols, $candidateNameCols, $candidateRegCols) as $c) {
|
||||
if (in_array($c, $labFields, true)) {
|
||||
$selectCols[] = $c;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($selectCols) > 1) {
|
||||
$labDbRow = $this->db->table('lab_request')
|
||||
->select(implode(',', array_unique($selectCols)))
|
||||
->where('id', $labReqId)
|
||||
->get(1)
|
||||
->getRowArray() ?? [];
|
||||
|
||||
if (! empty($labDbRow)) {
|
||||
if ($doctorId <= 0) {
|
||||
foreach ($candidateIdCols as $c) {
|
||||
if (isset($labDbRow[$c]) && (int) $labDbRow[$c] > 0) {
|
||||
$doctorId = (int) $labDbRow[$c];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($doctorName === '') {
|
||||
foreach ($candidateNameCols as $c) {
|
||||
$v = isset($labDbRow[$c]) ? trim((string) $labDbRow[$c]) : '';
|
||||
if ($v !== '') {
|
||||
$doctorName = $v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($doctorRegNo === '') {
|
||||
foreach ($candidateRegCols as $c) {
|
||||
$v = isset($labDbRow[$c]) ? trim((string) $labDbRow[$c]) : '';
|
||||
if ($v !== '') {
|
||||
$doctorRegNo = $v;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// fail-open; continue with what we already have
|
||||
}
|
||||
|
||||
if ($doctorId > 0 && $this->db->tableExists('doctor_master')) {
|
||||
try {
|
||||
$dFields = $this->db->getFieldNames('doctor_master') ?? [];
|
||||
$dSelect = ['id'];
|
||||
foreach (['p_fname', 'p_lname', 'doctor_reg_no', 'registration_no', 'reg_no'] as $f) {
|
||||
if (in_array($f, $dFields, true)) {
|
||||
$dSelect[] = $f;
|
||||
}
|
||||
}
|
||||
|
||||
$dRow = $this->db->table('doctor_master')
|
||||
->select(implode(',', array_unique($dSelect)))
|
||||
->where('id', $doctorId)
|
||||
->get(1)
|
||||
->getRowArray() ?? [];
|
||||
|
||||
if ($doctorName === '' && ! empty($dRow)) {
|
||||
$doctorName = trim(trim((string) ($dRow['p_fname'] ?? '')) . ' ' . trim((string) ($dRow['p_lname'] ?? '')));
|
||||
}
|
||||
if ($doctorRegNo === '' && ! empty($dRow)) {
|
||||
foreach (['doctor_reg_no', 'registration_no', 'reg_no'] as $f) {
|
||||
if (! empty($dRow[$f])) {
|
||||
$doctorRegNo = trim((string) $dRow[$f]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
// fail-open
|
||||
}
|
||||
}
|
||||
|
||||
if ($doctorName === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'name' => $doctorName,
|
||||
'registration_number' => $doctorRegNo,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Map numeric lab_type code to a human-readable title.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -132,7 +132,7 @@ class FhirR4Builder
|
|||
'display' => 'Ambulatory',
|
||||
],
|
||||
'subject' => ['reference' => $patientRef, 'display' => 'Patient'],
|
||||
'period' => ['start' => trim((string) ($encounter['period_start'] ?? '')) !== '' ? (string) $encounter['period_start'] : $issuedAt],
|
||||
'period' => ['start' => $this->normalizeIsoDateTime((string) ($encounter['period_start'] ?? ''), $issuedAt)],
|
||||
];
|
||||
if ($practitionerRef !== '') {
|
||||
$encounterResource['participant'] = [['individual' => ['reference' => $practitionerRef]]];
|
||||
|
|
@ -657,7 +657,7 @@ class FhirR4Builder
|
|||
|
||||
$patientRef = 'urn:uuid:' . $patientUuid;
|
||||
$reportRef = 'urn:uuid:' . $reportUuid;
|
||||
$reportedAt = trim((string) ($diagnosticReport['reported_at'] ?? '')) ?: $issuedAt;
|
||||
$reportedAt = $this->normalizeIsoDateTime((string) ($diagnosticReport['reported_at'] ?? ''), $issuedAt);
|
||||
$reportTitle = trim((string) ($diagnosticReport['title'] ?? 'Diagnostic Report'));
|
||||
$reportStatus = trim((string) ($diagnosticReport['status'] ?? 'final'));
|
||||
$isImaging = (bool) ($diagnosticReport['is_imaging'] ?? false)
|
||||
|
|
@ -734,12 +734,15 @@ class FhirR4Builder
|
|||
],
|
||||
'subject' => ['reference' => $patientRef, 'display' => 'Patient'],
|
||||
];
|
||||
$pStart = trim((string) ($encounter['period_start'] ?? ''));
|
||||
if ($pStart !== '') {
|
||||
$encounterRes['period'] = ['start' => $pStart];
|
||||
$pEnd = trim((string) ($encounter['period_end'] ?? ''));
|
||||
if ($pEnd !== '') {
|
||||
$encounterRes['period']['end'] = $pEnd;
|
||||
$pStartRaw = trim((string) ($encounter['period_start'] ?? ''));
|
||||
if ($pStartRaw !== '') {
|
||||
$encounterRes['period'] = ['start' => $this->normalizeIsoDateTime($pStartRaw, $issuedAt)];
|
||||
$pEndRaw = trim((string) ($encounter['period_end'] ?? ''));
|
||||
if ($pEndRaw !== '') {
|
||||
$pEndIso = $this->normalizeIsoDateTime($pEndRaw);
|
||||
if ($pEndIso !== '') {
|
||||
$encounterRes['period']['end'] = $pEndIso;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($practitionerRef !== '') {
|
||||
|
|
@ -809,7 +812,9 @@ class FhirR4Builder
|
|||
if ($refHigh !== null && is_numeric($refHigh)) {
|
||||
$rr['high'] = ['value' => (float) $refHigh, 'unit' => $unit ?: $ucum, 'system' => 'http://unitsofmeasure.org', 'code' => $ucum];
|
||||
}
|
||||
$obsResource['referenceRange'] = [$rr];
|
||||
if (! empty($rr)) {
|
||||
$obsResource['referenceRange'] = [$rr];
|
||||
}
|
||||
}
|
||||
// Interpretation (H/L/N)
|
||||
$interp = trim((string) ($obs['interpretation'] ?? ''));
|
||||
|
|
@ -1400,6 +1405,28 @@ class FhirR4Builder
|
|||
return (new \DateTime('now', new \DateTimeZone('Asia/Kolkata')))->format('Y-m-d\TH:i:sP');
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize any datetime-like string into ISO 8601 with IST offset.
|
||||
*/
|
||||
private function normalizeIsoDateTime(string $value, string $fallback = ''): string
|
||||
{
|
||||
$raw = trim($value);
|
||||
if ($raw === '') {
|
||||
return trim($fallback);
|
||||
}
|
||||
|
||||
try {
|
||||
$hasTimezone = preg_match('/([+-]\d{2}:\d{2}|Z)$/', $raw) === 1;
|
||||
$dt = $hasTimezone && strpos($raw, 'T') !== false
|
||||
? new \DateTime($raw)
|
||||
: new \DateTime($raw, new \DateTimeZone('Asia/Kolkata'));
|
||||
|
||||
return $dt->setTimezone(new \DateTimeZone('Asia/Kolkata'))->format('Y-m-d\TH:i:sP');
|
||||
} catch (\Throwable $e) {
|
||||
return trim($fallback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $patient
|
||||
*
|
||||
|
|
@ -1435,8 +1462,9 @@ class FhirR4Builder
|
|||
'name' => [$nameEntry],
|
||||
];
|
||||
|
||||
if (! empty($patient['gender'])) {
|
||||
$resource['gender'] = (string) $patient['gender'];
|
||||
$gender = $this->normalizeAdministrativeGender($patient['gender'] ?? null);
|
||||
if ($gender !== '') {
|
||||
$resource['gender'] = $gender;
|
||||
}
|
||||
|
||||
if (! empty($patient['birthDate'])) {
|
||||
|
|
@ -1471,6 +1499,25 @@ class FhirR4Builder
|
|||
return $resource;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert legacy/local gender values to FHIR AdministrativeGender.
|
||||
*/
|
||||
private function normalizeAdministrativeGender($value): string
|
||||
{
|
||||
$raw = strtolower(trim((string) $value));
|
||||
if ($raw === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return match ($raw) {
|
||||
'm', 'male', '1' => 'male',
|
||||
'f', 'female', '2' => 'female',
|
||||
'o', 'other', '3', 'transgender', 'trans' => 'other',
|
||||
'u', 'unknown', '0', 'na', 'n/a', 'not known' => 'unknown',
|
||||
default => in_array($raw, ['male', 'female', 'other', 'unknown'], true) ? $raw : 'unknown',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Map common route-of-administration text to SNOMED CT codes.
|
||||
* Returns empty string when no mapping is found.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user