diff --git a/app/Controllers/AbdmGateway.php b/app/Controllers/AbdmGateway.php index 8b6fae0..b5e650e 100644 --- a/app/Controllers/AbdmGateway.php +++ b/app/Controllers/AbdmGateway.php @@ -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 $columns * @return array> */ - 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 + */ + 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)); diff --git a/app/Libraries/Abdm/Fhir/FhirDocumentBuilder.php b/app/Libraries/Abdm/Fhir/FhirDocumentBuilder.php index 9146403..872d864 100644 --- a/app/Libraries/Abdm/Fhir/FhirDocumentBuilder.php +++ b/app/Libraries/Abdm/Fhir/FhirDocumentBuilder.php @@ -78,6 +78,24 @@ class FhirDocumentBuilder return $this->addResource($resource); } + /** @param array $resource */ + public function addServiceRequest(array $resource): self + { + return $this->addResource($resource); + } + + /** @param array $resource */ + public function addAllergyIntolerance(array $resource): self + { + return $this->addResource($resource); + } + + /** @param array $resource */ + public function addCarePlan(array $resource): self + { + return $this->addResource($resource); + } + /** @param array $resource */ public function addDiagnosticReport(array $resource): self { diff --git a/app/Libraries/Abdm/Fhir/Generators/DischargeFhirGenerator.php b/app/Libraries/Abdm/Fhir/Generators/DischargeFhirGenerator.php index eb8e048..31eec85 100644 --- a/app/Libraries/Abdm/Fhir/Generators/DischargeFhirGenerator.php +++ b/app/Libraries/Abdm/Fhir/Generators/DischargeFhirGenerator.php @@ -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]); diff --git a/app/Views/abdm/task_board.php b/app/Views/abdm/task_board.php index 8065e8b..0d5024c 100644 --- a/app/Views/abdm/task_board.php +++ b/app/Views/abdm/task_board.php @@ -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 += ''; } + // ── Procedures / Surgeries + if (procedures.length) { + html += '
Procedures
'; + html += '
    '; + procedures.forEach(function(p) { + var text = (p.code || {}).text || ((((p.code || {}).coding) || [])[0] || {}).display || ''; + var at = fmtDate(p.performedDateTime || ''); + html += '
  • ' + hesc(text || 'Procedure') + ''; + if (at) html += ' (' + hesc(at) + ')'; + html += '
  • '; + }); + html += '
'; + } + // ── Investigations if (serviceReqs.length) { html += '
Investigations
'; @@ -1937,6 +1954,36 @@ html += '
'; } + // ── Allergy & Risk Profile + if (allergies.length) { + html += '
Allergies / Risk Profile
'; + html += '
    '; + allergies.forEach(function(a) { + var text = (a.code || {}).text || ''; + var note = (((a.note || [])[0]) || {}).text || ''; + var criticality = (a.criticality || '').toUpperCase(); + html += '
  • ' + hesc(text || 'Allergy') + ''; + if (criticality) html += ' ' + hesc(criticality) + ''; + if (note) html += ' ' + hesc(note) + ''; + html += '
  • '; + }); + html += '
'; + } + + // ── Discharge Advice / Follow-up + if (carePlans.length) { + html += '
Discharge Advice & Follow-up
'; + html += '
    '; + carePlans.forEach(function(cp) { + var title = cp.title || 'Care Plan'; + var desc = cp.description || ''; + html += '
  • ' + hesc(title) + ''; + if (desc) html += '
    ' + hesc(desc) + '
    '; + html += '
  • '; + }); + html += '
'; + } + // ── Practitioner if (practitioner) { var pName = ((practitioner.name || [{}])[0].text || '').trim();