ABDM: enrich discharge FHIR with observations advice and risks
This commit is contained in:
parent
2335006c30
commit
d7fe05e6b0
|
|
@ -2186,6 +2186,119 @@ class AbdmGateway extends BaseController
|
|||
}
|
||||
}
|
||||
|
||||
$observations = [];
|
||||
foreach ($this->ipdRows('ipd_discharge_1_b', ['short_head', 'rdata'], $ipdId, 'ipd_d_id') as $row) {
|
||||
$label = trim((string) ($row['short_head'] ?? ''));
|
||||
$value = trim((string) ($row['rdata'] ?? ''));
|
||||
if ($label === '' || $value === '') {
|
||||
continue;
|
||||
}
|
||||
$observations[] = [
|
||||
'text' => $label,
|
||||
'value' => $value,
|
||||
'category' => 'vital-signs',
|
||||
'category_code' => 'vital-signs',
|
||||
'effective_at' => $this->toIsoDateTimeOrNow($admissionRaw),
|
||||
];
|
||||
}
|
||||
foreach ($this->ipdRows('ipd_discharge_1_b_final', ['short_head', 'rdata'], $ipdId, 'ipd_d_id') as $row) {
|
||||
$label = trim((string) ($row['short_head'] ?? ''));
|
||||
$value = trim((string) ($row['rdata'] ?? ''));
|
||||
if ($label === '' || $value === '') {
|
||||
continue;
|
||||
}
|
||||
$observations[] = [
|
||||
'text' => 'Discharge ' . $label,
|
||||
'value' => $value,
|
||||
'category' => 'vital-signs',
|
||||
'category_code' => 'vital-signs',
|
||||
'effective_at' => $this->toIsoDateTimeOrNow($dischargeRaw),
|
||||
];
|
||||
}
|
||||
|
||||
$investigations = [];
|
||||
foreach ($this->ipdRows('ipd_discharge_1_d', ['short_head', 'rdata'], $ipdId, 'ipd_d_id') as $row) {
|
||||
$label = trim((string) ($row['short_head'] ?? ''));
|
||||
$value = trim((string) ($row['rdata'] ?? ''));
|
||||
if ($label === '') {
|
||||
continue;
|
||||
}
|
||||
$investigations[] = [
|
||||
'text' => $value !== '' ? ($label . ': ' . $value) : $label,
|
||||
'authored_on' => $this->toIsoDateTimeOrNow($admissionRaw),
|
||||
];
|
||||
}
|
||||
foreach ($this->ipdRows('ipd_discharge_1_e', ['short_head', 'rdata'], $ipdId, 'ipd_d_id') as $row) {
|
||||
$label = trim((string) ($row['short_head'] ?? ''));
|
||||
$value = trim((string) ($row['rdata'] ?? ''));
|
||||
if ($label === '') {
|
||||
continue;
|
||||
}
|
||||
$investigations[] = [
|
||||
'text' => $value !== '' ? ($label . ': ' . $value) : $label,
|
||||
'authored_on' => $this->toIsoDateTimeOrNow($admissionRaw),
|
||||
];
|
||||
}
|
||||
|
||||
$allergies = [];
|
||||
$carePlans = [];
|
||||
$instructionRows = $this->ipdRows('ipd_discharge_instructions', ['comp_report', 'comp_remark', 'review_after'], $ipdId);
|
||||
if (! empty($instructionRows)) {
|
||||
$instructionRow = $instructionRows[0];
|
||||
$instructionMeta = $this->parseJsonArray((string) ($instructionRow['comp_report'] ?? ''));
|
||||
$nabhMeta = is_array($instructionMeta['nabh'] ?? null) ? $instructionMeta['nabh'] : [];
|
||||
|
||||
$allergyStatus = strtolower(trim((string) ($nabhMeta['drug_allergy_status'] ?? '')));
|
||||
$allergyDetails = trim((string) ($nabhMeta['drug_allergy_details'] ?? ''));
|
||||
if ($allergyStatus !== '' || $allergyDetails !== '') {
|
||||
$allergies[] = [
|
||||
'text' => $allergyDetails !== '' ? $allergyDetails : ('Drug allergy status: ' . $allergyStatus),
|
||||
'note' => $allergyStatus,
|
||||
'criticality' => $allergyStatus === 'yes' ? 'high' : 'low',
|
||||
'recorded_at' => $this->toIsoDateTimeOrNow($dischargeRaw),
|
||||
];
|
||||
}
|
||||
|
||||
$dietAdvice = trim((string) ($instructionRow['comp_remark'] ?? ''));
|
||||
if ($dietAdvice !== '') {
|
||||
$carePlans[] = [
|
||||
'title' => 'Dietary Advice',
|
||||
'description' => $dietAdvice,
|
||||
'created_at' => $this->toIsoDateTimeOrNow($dischargeRaw),
|
||||
];
|
||||
}
|
||||
|
||||
$otherAdvice = trim((string) ($instructionMeta['other_text'] ?? ''));
|
||||
if ($otherAdvice !== '') {
|
||||
$carePlans[] = [
|
||||
'title' => 'Other Advice',
|
||||
'description' => $otherAdvice,
|
||||
'created_at' => $this->toIsoDateTimeOrNow($dischargeRaw),
|
||||
];
|
||||
}
|
||||
|
||||
$reviewAfter = trim((string) ($instructionRow['review_after'] ?? ''));
|
||||
if ($reviewAfter !== '') {
|
||||
$carePlans[] = [
|
||||
'title' => 'Follow Up',
|
||||
'description' => 'Review After: ' . $reviewAfter,
|
||||
'created_at' => $this->toIsoDateTimeOrNow($dischargeRaw),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$courseRows = $this->ipdRows('ipd_discharge_course_remark', ['comp_remark'], $ipdId);
|
||||
if (! empty($courseRows)) {
|
||||
$courseText = trim((string) ($courseRows[0]['comp_remark'] ?? ''));
|
||||
if ($courseText !== '') {
|
||||
$carePlans[] = [
|
||||
'title' => 'Course In Hospital',
|
||||
'description' => $courseText,
|
||||
'created_at' => $this->toIsoDateTimeOrNow($dischargeRaw),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$hospital = $this->getHospitalProfileForFhir();
|
||||
$hfrId = trim((string) ($hospital['hfr_id'] ?? ''));
|
||||
|
||||
|
|
@ -2212,6 +2325,10 @@ class AbdmGateway extends BaseController
|
|||
'conditions' => $conditions,
|
||||
'procedures' => $procedures,
|
||||
'medications' => $medications,
|
||||
'observations' => $observations,
|
||||
'investigations' => $investigations,
|
||||
'allergies' => $allergies,
|
||||
'care_plans' => $carePlans,
|
||||
];
|
||||
|
||||
$factory = new FhirGeneratorFactory();
|
||||
|
|
@ -2233,9 +2350,9 @@ class AbdmGateway extends BaseController
|
|||
* @param array<int,string> $columns
|
||||
* @return array<int,array<string,mixed>>
|
||||
*/
|
||||
private function ipdRows(string $table, array $columns, int $ipdId): array
|
||||
private function ipdRows(string $table, array $columns, int $ipdId, string $ipdKey = 'ipd_id'): array
|
||||
{
|
||||
if ($ipdId <= 0 || ! $this->db->tableExists($table) || ! $this->db->fieldExists('ipd_id', $table)) {
|
||||
if ($ipdId <= 0 || ! $this->db->tableExists($table) || ! $this->db->fieldExists($ipdKey, $table)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
|
@ -2254,7 +2371,7 @@ class AbdmGateway extends BaseController
|
|||
|
||||
$builder = $this->db->table($table)
|
||||
->select(implode(',', $available))
|
||||
->where('ipd_id', $ipdId);
|
||||
->where($ipdKey, $ipdId);
|
||||
|
||||
if ($this->db->fieldExists('id', $table)) {
|
||||
$builder->orderBy('id', 'ASC');
|
||||
|
|
@ -2263,6 +2380,20 @@ class AbdmGateway extends BaseController
|
|||
return $builder->get()->getResultArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
private function parseJsonArray(string $json): array
|
||||
{
|
||||
$json = trim($json);
|
||||
if ($json === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$decoded = json_decode($json, true);
|
||||
return is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
|
||||
private function normalizeFhirGender(string $gender): string
|
||||
{
|
||||
$value = strtolower(trim($gender));
|
||||
|
|
|
|||
|
|
@ -78,6 +78,24 @@ class FhirDocumentBuilder
|
|||
return $this->addResource($resource);
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $resource */
|
||||
public function addServiceRequest(array $resource): self
|
||||
{
|
||||
return $this->addResource($resource);
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $resource */
|
||||
public function addAllergyIntolerance(array $resource): self
|
||||
{
|
||||
return $this->addResource($resource);
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $resource */
|
||||
public function addCarePlan(array $resource): self
|
||||
{
|
||||
return $this->addResource($resource);
|
||||
}
|
||||
|
||||
/** @param array<string,mixed> $resource */
|
||||
public function addDiagnosticReport(array $resource): self
|
||||
{
|
||||
|
|
|
|||
|
|
@ -93,6 +93,131 @@ class DischargeFhirGenerator extends \App\Libraries\Abdm\Fhir\Generators\Abstrac
|
|||
]);
|
||||
}
|
||||
|
||||
foreach ((array) ($source['observations'] ?? []) as $idx => $obs) {
|
||||
$text = trim((string) ($obs['text'] ?? ''));
|
||||
$value = trim((string) ($obs['value'] ?? ''));
|
||||
if ($text === '' || $value === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$coding = [];
|
||||
$loincCode = trim((string) ($obs['loinc_code'] ?? ''));
|
||||
if ($loincCode !== '') {
|
||||
$coding[] = [
|
||||
'system' => 'http://loinc.org',
|
||||
'code' => $loincCode,
|
||||
'display' => trim((string) ($obs['loinc_display'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
$builder->addObservation([
|
||||
'resourceType' => 'Observation',
|
||||
'id' => 'discharge-observation-' . $recordId . '-' . $idx,
|
||||
'status' => 'final',
|
||||
'category' => [[
|
||||
'text' => trim((string) ($obs['category'] ?? 'vital-signs')),
|
||||
'coding' => [[
|
||||
'system' => 'http://terminology.hl7.org/CodeSystem/observation-category',
|
||||
'code' => trim((string) ($obs['category_code'] ?? 'vital-signs')),
|
||||
]],
|
||||
]],
|
||||
'code' => [
|
||||
'coding' => $coding,
|
||||
'text' => $text,
|
||||
],
|
||||
'subject' => ['reference' => $patientRef],
|
||||
'encounter' => $encounterRef ? ['reference' => $encounterRef] : null,
|
||||
'effectiveDateTime' => (string) ($obs['effective_at'] ?? $timestamp),
|
||||
'valueString' => $value,
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ((array) ($source['investigations'] ?? []) as $idx => $inv) {
|
||||
$text = trim((string) ($inv['text'] ?? ''));
|
||||
if ($text === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$coding = [];
|
||||
$loincCode = trim((string) ($inv['loinc_code'] ?? ''));
|
||||
if ($loincCode !== '') {
|
||||
$coding[] = [
|
||||
'system' => 'http://loinc.org',
|
||||
'code' => $loincCode,
|
||||
'display' => trim((string) ($inv['loinc_display'] ?? '')),
|
||||
];
|
||||
}
|
||||
|
||||
$builder->addServiceRequest([
|
||||
'resourceType' => 'ServiceRequest',
|
||||
'id' => 'discharge-investigation-' . $recordId . '-' . $idx,
|
||||
'status' => 'active',
|
||||
'intent' => 'order',
|
||||
'code' => [
|
||||
'coding' => $coding,
|
||||
'text' => $text,
|
||||
],
|
||||
'subject' => ['reference' => $patientRef],
|
||||
'encounter' => $encounterRef ? ['reference' => $encounterRef] : null,
|
||||
'authoredOn' => (string) ($inv['authored_on'] ?? $timestamp),
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ((array) ($source['allergies'] ?? []) as $idx => $alg) {
|
||||
$text = trim((string) ($alg['text'] ?? ''));
|
||||
if ($text === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$builder->addAllergyIntolerance([
|
||||
'resourceType' => 'AllergyIntolerance',
|
||||
'id' => 'discharge-allergy-' . $recordId . '-' . $idx,
|
||||
'clinicalStatus' => [
|
||||
'coding' => [[
|
||||
'system' => 'http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical',
|
||||
'code' => 'active',
|
||||
]],
|
||||
],
|
||||
'verificationStatus' => [
|
||||
'coding' => [[
|
||||
'system' => 'http://terminology.hl7.org/CodeSystem/allergyintolerance-verification',
|
||||
'code' => 'unconfirmed',
|
||||
]],
|
||||
],
|
||||
'type' => 'allergy',
|
||||
'category' => ['medication'],
|
||||
'criticality' => trim((string) ($alg['criticality'] ?? 'low')),
|
||||
'code' => [
|
||||
'text' => $text,
|
||||
],
|
||||
'patient' => ['reference' => $patientRef],
|
||||
'recordedDate' => (string) ($alg['recorded_at'] ?? $timestamp),
|
||||
'note' => isset($alg['note']) && trim((string) $alg['note']) !== ''
|
||||
? [['text' => trim((string) $alg['note'])]]
|
||||
: null,
|
||||
]);
|
||||
}
|
||||
|
||||
foreach ((array) ($source['care_plans'] ?? []) as $idx => $cp) {
|
||||
$title = trim((string) ($cp['title'] ?? 'Discharge Advice'));
|
||||
$description = trim((string) ($cp['description'] ?? ''));
|
||||
if ($description === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$builder->addCarePlan([
|
||||
'resourceType' => 'CarePlan',
|
||||
'id' => 'discharge-careplan-' . $recordId . '-' . $idx,
|
||||
'status' => 'active',
|
||||
'intent' => 'plan',
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
'subject' => ['reference' => $patientRef],
|
||||
'encounter' => $encounterRef ? ['reference' => $encounterRef] : null,
|
||||
'created' => (string) ($cp['created_at'] ?? $timestamp),
|
||||
]);
|
||||
}
|
||||
|
||||
$bundle = $builder->toBundle();
|
||||
$validation = $this->validator->validate($bundle, 'discharge', ['resolved' => 1, 'unresolved' => 0, 'fallback_used' => 0]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1711,7 +1711,10 @@
|
|||
var allConditions = byType('Condition');
|
||||
var observations = byType('Observation');
|
||||
var medications = byType('MedicationRequest');
|
||||
var procedures = byType('Procedure');
|
||||
var serviceReqs = byType('ServiceRequest');
|
||||
var allergies = byType('AllergyIntolerance');
|
||||
var carePlans = byType('CarePlan');
|
||||
|
||||
var complaints = allConditions.filter(function(c) {
|
||||
return (c.category || []).some(function(cat) { return (cat.text || '').toLowerCase().includes('complaint'); });
|
||||
|
|
@ -1923,6 +1926,20 @@
|
|||
html += '</ol></div></div>';
|
||||
}
|
||||
|
||||
// ── Procedures / Surgeries
|
||||
if (procedures.length) {
|
||||
html += '<div class="card mb-2"><div class="card-header py-1 bg-light"><small class="fw-bold text-uppercase text-secondary">Procedures</small></div><div class="card-body py-2">';
|
||||
html += '<ul class="mb-0 ps-3">';
|
||||
procedures.forEach(function(p) {
|
||||
var text = (p.code || {}).text || ((((p.code || {}).coding) || [])[0] || {}).display || '';
|
||||
var at = fmtDate(p.performedDateTime || '');
|
||||
html += '<li class="mb-1"><strong>' + hesc(text || 'Procedure') + '</strong>';
|
||||
if (at) html += ' <span class="text-muted small">(' + hesc(at) + ')</span>';
|
||||
html += '</li>';
|
||||
});
|
||||
html += '</ul></div></div>';
|
||||
}
|
||||
|
||||
// ── Investigations
|
||||
if (serviceReqs.length) {
|
||||
html += '<div class="card mb-2"><div class="card-header py-1 bg-light"><small class="fw-bold text-uppercase text-secondary">Investigations</small></div><div class="card-body py-2">';
|
||||
|
|
@ -1937,6 +1954,36 @@
|
|||
html += '</ul></div></div>';
|
||||
}
|
||||
|
||||
// ── Allergy & Risk Profile
|
||||
if (allergies.length) {
|
||||
html += '<div class="card mb-2"><div class="card-header py-1 bg-light"><small class="fw-bold text-uppercase text-secondary">Allergies / Risk Profile</small></div><div class="card-body py-2">';
|
||||
html += '<ul class="mb-0 ps-3">';
|
||||
allergies.forEach(function(a) {
|
||||
var text = (a.code || {}).text || '';
|
||||
var note = (((a.note || [])[0]) || {}).text || '';
|
||||
var criticality = (a.criticality || '').toUpperCase();
|
||||
html += '<li class="mb-1"><strong>' + hesc(text || 'Allergy') + '</strong>';
|
||||
if (criticality) html += ' <span class="badge bg-warning text-dark" style="font-size:.7rem;">' + hesc(criticality) + '</span>';
|
||||
if (note) html += ' <small class="text-muted d-block">' + hesc(note) + '</small>';
|
||||
html += '</li>';
|
||||
});
|
||||
html += '</ul></div></div>';
|
||||
}
|
||||
|
||||
// ── Discharge Advice / Follow-up
|
||||
if (carePlans.length) {
|
||||
html += '<div class="card mb-2"><div class="card-header py-1 bg-light"><small class="fw-bold text-uppercase text-secondary">Discharge Advice & Follow-up</small></div><div class="card-body py-2">';
|
||||
html += '<ul class="mb-0 ps-3">';
|
||||
carePlans.forEach(function(cp) {
|
||||
var title = cp.title || 'Care Plan';
|
||||
var desc = cp.description || '';
|
||||
html += '<li class="mb-2"><strong>' + hesc(title) + '</strong>';
|
||||
if (desc) html += '<div class="text-muted small">' + hesc(desc) + '</div>';
|
||||
html += '</li>';
|
||||
});
|
||||
html += '</ul></div></div>';
|
||||
}
|
||||
|
||||
// ── Practitioner
|
||||
if (practitioner) {
|
||||
var pName = ((practitioner.name || [{}])[0].text || '').trim();
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user