Show fetched-record sections as plain bold headings (no collapse) and resolve DiagnosticReport result Observations to actual lab values

This commit is contained in:
DevSoftTech Server 2026-07-29 03:22:35 +05:30
parent 37dba1948e
commit 448db54d4c
2 changed files with 120 additions and 32 deletions

View File

@ -389,9 +389,10 @@ class M3HiuDocumentRepository
if ($refStr === '' || ! isset($resourcesByRef[$refStr])) {
continue;
}
$label = $this->describeResourceForSection((array) $resourcesByRef[$refStr]);
if ($label !== '') {
$items[] = $label;
foreach ($this->describeResourceForSection((array) $resourcesByRef[$refStr], $resourcesByRef) as $label) {
if ($label !== '') {
$items[] = $label;
}
}
}
@ -416,10 +417,15 @@ class M3HiuDocumentRepository
}
/**
* Produces a short, human-readable one-line label for a FHIR resource, for
* display inside a Composition section's accordion body.
* Produces one or more short, human-readable one-line labels for a FHIR
* resource, for display inside a Composition section's item list. Most
* resource types resolve to a single line; DiagnosticReport expands into
* one line per linked Observation result (e.g. individual lab test
* values) since that's where the actual pathology/lab values live.
*
* @return array<int, string>
*/
private function describeResourceForSection(array $resource): string
private function describeResourceForSection(array $resource, array $resourcesByRef = []): array
{
$type = trim((string) ($resource['resourceType'] ?? ''));
@ -428,42 +434,130 @@ class M3HiuDocumentRepository
case 'AllergyIntolerance':
case 'Procedure':
case 'ServiceRequest':
return trim((string) (($resource['code']['text'] ?? '') ?: ($resource['code']['coding'][0]['display'] ?? '')));
$label = trim((string) (($resource['code']['text'] ?? '') ?: ($resource['code']['coding'][0]['display'] ?? '')));
return $label !== '' ? [$label] : [];
case 'Observation':
$name = trim((string) (($resource['code']['text'] ?? '') ?: ($resource['code']['coding'][0]['display'] ?? '')));
$val = '';
if (isset($resource['valueQuantity']) && is_array($resource['valueQuantity'])) {
$val = trim((string) (($resource['valueQuantity']['value'] ?? '') . ' ' . ($resource['valueQuantity']['unit'] ?? '')));
} elseif (isset($resource['valueString'])) {
$val = trim((string) $resource['valueString']);
} elseif (isset($resource['valueCodeableConcept'])) {
$val = trim((string) (($resource['valueCodeableConcept']['text'] ?? '') ?: ($resource['valueCodeableConcept']['coding'][0]['display'] ?? '')));
}
return $val !== '' ? ($name . ': ' . $val) : $name;
$label = $this->describeObservation($resource);
return $label !== '' ? [$label] : [];
case 'MedicationRequest':
case 'MedicationStatement':
$med = trim((string) (($resource['medicationCodeableConcept']['text'] ?? '') ?: ($resource['medicationCodeableConcept']['coding'][0]['display'] ?? '')));
$dose = trim((string) ($resource['dosageInstruction'][0]['text'] ?? ($resource['dosage'][0]['text'] ?? '')));
return $dose !== '' ? ($med . ' — ' . $dose) : $med;
$label = $dose !== '' ? ($med . ' — ' . $dose) : $med;
return $label !== '' ? [$label] : [];
case 'DiagnosticReport':
$name = trim((string) (($resource['code']['text'] ?? '') ?: ($resource['code']['coding'][0]['display'] ?? '')));
$conclusion = trim((string) ($resource['conclusion'] ?? ''));
return $conclusion !== '' ? ($name . ' — ' . $conclusion) : $name;
$header = $conclusion !== '' ? ($name . ' — ' . $conclusion) : $name;
$lines = $header !== '' ? [$header] : [];
foreach ((array) ($resource['result'] ?? []) as $resultRef) {
if (! is_array($resultRef)) {
continue;
}
$refStr = trim((string) ($resultRef['reference'] ?? ''));
if ($refStr === '' || ! isset($resourcesByRef[$refStr])) {
continue;
}
$obsResource = (array) $resourcesByRef[$refStr];
if (trim((string) ($obsResource['resourceType'] ?? '')) !== 'Observation') {
continue;
}
$obsLine = $this->describeObservation($obsResource);
if ($obsLine !== '') {
$lines[] = '– ' . $obsLine;
}
}
return $lines;
case 'CarePlan':
return trim((string) (($resource['description'] ?? '') ?: ($resource['title'] ?? '')));
$label = trim((string) (($resource['description'] ?? '') ?: ($resource['title'] ?? '')));
return $label !== '' ? [$label] : [];
case 'DocumentReference':
return trim((string) (($resource['description'] ?? '') ?: ($resource['type']['text'] ?? '') ?: ($resource['type']['coding'][0]['display'] ?? '')));
$label = trim((string) (($resource['description'] ?? '') ?: ($resource['type']['text'] ?? '') ?: ($resource['type']['coding'][0]['display'] ?? '')));
return $label !== '' ? [$label] : [];
default:
return '';
return [];
}
}
/**
* Builds a single "Test name: value unit (Flag)" style line for an
* Observation resource, covering the common FHIR value shapes used by
* lab/pathology reports (valueQuantity, valueString, valueCodeableConcept,
* valueInteger/valueBoolean, and multi-component observations like Blood
* Pressure which report systolic/diastolic as separate `component` entries
* instead of a single top-level value).
*/
private function describeObservation(array $resource): string
{
$name = trim((string) (($resource['code']['text'] ?? '') ?: ($resource['code']['coding'][0]['display'] ?? '')));
$formatValue = static function (array $obs): string {
if (isset($obs['valueQuantity']) && is_array($obs['valueQuantity'])) {
return trim((string) (($obs['valueQuantity']['value'] ?? '') . ' ' . ($obs['valueQuantity']['unit'] ?? ($obs['valueQuantity']['code'] ?? ''))));
}
if (isset($obs['valueString'])) {
return trim((string) $obs['valueString']);
}
if (isset($obs['valueCodeableConcept'])) {
return trim((string) (($obs['valueCodeableConcept']['text'] ?? '') ?: ($obs['valueCodeableConcept']['coding'][0]['display'] ?? '')));
}
if (isset($obs['valueInteger'])) {
return trim((string) $obs['valueInteger']);
}
if (isset($obs['valueBoolean'])) {
return $obs['valueBoolean'] ? 'Yes' : 'No';
}
if (isset($obs['valueDateTime'])) {
return trim((string) $obs['valueDateTime']);
}
if (isset($obs['valueRange']) && is_array($obs['valueRange'])) {
$low = $obs['valueRange']['low']['value'] ?? '';
$high = $obs['valueRange']['high']['value'] ?? '';
return trim($low . ' - ' . $high);
}
return '';
};
$val = $formatValue($resource);
// Multi-component observations (e.g. Blood Pressure: systolic + diastolic)
// carry no top-level value, only a `component[]` array each with its
// own code + value.
if ($val === '' && isset($resource['component']) && is_array($resource['component'])) {
$parts = [];
foreach ((array) $resource['component'] as $component) {
if (! is_array($component)) {
continue;
}
$compName = trim((string) (($component['code']['text'] ?? '') ?: ($component['code']['coding'][0]['display'] ?? '')));
$compVal = $formatValue($component);
if ($compVal === '') {
continue;
}
$parts[] = $compName !== '' ? ($compName . ': ' . $compVal) : $compVal;
}
$val = implode(', ', $parts);
}
$flag = trim((string) (($resource['interpretation'][0]['text'] ?? '') ?: ($resource['interpretation'][0]['coding'][0]['display'] ?? '')));
$label = $val !== '' ? ($name . ': ' . $val) : $name;
if ($flag !== '') {
$label .= ' (' . $flag . ')';
}
return trim($label, ' :');
}
/**
* Extracts attached scanned images/PDFs/generic documents (DocumentReference
* resources, whose content may carry the base64 attachment data inline or

View File

@ -809,7 +809,6 @@ $(function() {
docs.forEach(function(doc, di) {
var summary = doc.summary || {};
var sections = Array.isArray(summary.sections) ? summary.sections : [];
var accId = 'abdmFetchAcc' + di;
html += '<div class="card mb-3">'
+ '<div class="card-body">'
@ -821,9 +820,8 @@ $(function() {
+ '</div>';
if (sections.length) {
html += '<div class="accordion" id="' + accId + '">';
html += '<div class="abdm-fetch-sections">';
sections.forEach(function(sec, si) {
var itemId = accId + '_' + si;
var body = '';
if (sec.items && sec.items.length) {
body = '<ul class="mb-0 small">' + sec.items.map(function(it) { return '<li>' + escHtml(it) + '</li>'; }).join('') + '</ul>';
@ -832,13 +830,9 @@ $(function() {
} else {
body = '<div class="small text-muted">No details recorded.</div>';
}
html += '<div class="accordion-item">'
+ '<h2 class="accordion-header">'
+ '<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#' + itemId + '">' + escHtml(sec.title || 'Section') + '</button>'
+ '</h2>'
+ '<div id="' + itemId + '" class="accordion-collapse collapse" data-bs-parent="#' + accId + '">'
+ '<div class="accordion-body">' + body + '</div>'
+ '</div>'
html += '<div class="mb-2">'
+ '<div class="fw-bold">' + escHtml(sec.title || 'Section') + '</div>'
+ body
+ '</div>';
});
html += '</div>';