fix(fhir): fix ABHA PHR app parsing error in discharge FHIR bundle - correct Observation category codes for vitals vs admission/discharge conditions - add missing category to investigation Observations (NRCES profile requirement) - strip empty unit/code from valueQuantity to avoid PHR parser rejection - fix greedy regex in Composition narrative that could produce malformed XHTML - remove 0 from isPlaceholderValue to stop silent stripping of numeric FHIR values

This commit is contained in:
DevSoftTech Server 2026-09-04 19:57:17 +05:30
parent cefa73dbae
commit 6525422af4
2 changed files with 35 additions and 12 deletions

View File

@ -310,14 +310,22 @@ class FhirDocumentBuilder
return $clean === [] ? null : $clean;
}
/** @param mixed $value */
/**
* Returns true only for display-level placeholder strings that should never
* appear in a rendered FHIR bundle (pure "no data" sentinel values).
* NOTE: '0' is intentionally excluded — it is a valid FHIR quantity value,
* coded value, and identifier string. Its earlier inclusion caused silent
* data loss in observation valueQuantity and numeric identifiers.
*
* @param mixed $value
*/
private function isPlaceholderValue($value): bool
{
if (! is_string($value)) {
return false;
}
return in_array(strtoupper(trim($value)), ['NA', 'N/A', 'NULL', 'NOT AVAILABLE'], true);
return in_array(strtoupper(trim($value)), ['NA', 'N/A', 'N / A', 'NULL', 'NOT AVAILABLE', 'UNDEFINED'], true);
}
private function newId(string $prefix): string

View File

@ -270,6 +270,10 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac
$isAdm = (stripos($catText, 'Admission') !== false || stripos($text, 'Admission') !== false);
$isDis = (stripos($catText, 'Discharge') !== false || stripos($text, 'Discharge') !== false);
$obsCategoryDisplay = $isAdm ? 'Condition on Admission Time' : ($isDis ? 'Condition on Discharge Time' : 'Vital Signs');
// ABDM PHR: use exam category for admission/discharge condition observations;
// vital-signs category only for true vital measurements.
$obsCategoryCode = ($isAdm || $isDis) ? 'exam' : 'vital-signs';
$obsCategorySystem = 'http://terminology.hl7.org/CodeSystem/observation-category';
$coding = [];
$loincCode = trim((string) ($obs['loinc_code'] ?? ''));
@ -308,9 +312,9 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac
'category' => [[
'text' => $obsCategoryDisplay,
'coding' => [[
'system' => 'http://terminology.hl7.org/CodeSystem/observation-category',
'code' => 'vital-signs',
'display' => 'Vital Signs',
'system' => $obsCategorySystem,
'code' => $obsCategoryCode,
'display' => $obsCategoryDisplay,
]],
]],
'code' => [
@ -397,12 +401,13 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac
}
} elseif (is_numeric($value)) {
$unitInfo = $this->getVitalSignUnitInfo($text, trim((string) ($obs['unit'] ?? '')));
$obsResource['valueQuantity'] = [
'value' => (float) $value,
'unit' => $unitInfo['unit'],
'system' => 'http://unitsofmeasure.org',
'code' => $unitInfo['code'],
];
$vq = ['value' => (float) $value];
if ($unitInfo['unit'] !== '') {
$vq['unit'] = $unitInfo['unit'];
$vq['system'] = 'http://unitsofmeasure.org';
$vq['code'] = $unitInfo['code'] !== '' ? $unitInfo['code'] : $unitInfo['unit'];
}
$obsResource['valueQuantity'] = $vq;
} else {
$obsResource['valueString'] = $value;
}
@ -457,6 +462,15 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac
'div' => '<div xmlns="http://www.w3.org/1999/xhtml"><p><b>Investigation:</b> ' . htmlspecialchars($text) . ($resultValue !== '' ? (': ' . htmlspecialchars($resultValue)) : '') . '</p></div>',
],
'status' => 'final',
// NRCES Observation profile requires at least one category.
'category' => [[
'coding' => [[
'system' => 'http://terminology.hl7.org/CodeSystem/observation-category',
'code' => 'laboratory',
'display' => 'Laboratory',
]],
'text' => 'Laboratory',
]],
'code' => [
'coding' => $coding,
'text' => $text,
@ -1391,7 +1405,8 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac
if ($secTitle === '' || $secDiv === '') {
continue;
}
if (preg_match('/<div[^>]*>(.*)<\/div>/is', $secDiv, $m)) {
// Non-greedy match to avoid swallowing sibling tags in multi-div strings.
if (preg_match('/<div[^>]*>(.*?)<\/div>/is', $secDiv, $m)) {
$inner = trim($m[1]);
} else {
$inner = $secDiv;