devsofttech_hms_ci4/app/Libraries/FhirR4Builder.php
DevSoftTech Server 062fc7ab32 feat: Add ABDM Task Board module with ABHA workflow
- Add ABDM sidebar heading and Task Board nav link
- Create migration 000040: abdm_work_tasks table
- Add AbdmTaskBoard controller with backfill methods
- Add AbdmWorkTaskService for task creation/deduplication
- Add AbdmGateway controller for ABDM API integration
- Separate task types: patient_abha_create vs patient_abha_update
- Add lab_report_publish and radiology_report_publish task types
- Patient.php: auto-create ABDM tasks on registration
- Diagnosis.php: auto-create tasks on report verify/finalize
- Task board UI: 7 filter buttons with default Unupdated ABHA view
- Fix SQL crash in backfillLabRadiologyTasks (dynamic column detection)
- Improve load_form AJAX error display with HTTP status + response text
- Add ABDM routes in Routes.php
- Add FHIR R4 builder library updates
- Add bridge sync service updates
2026-04-05 23:42:24 +05:30

292 lines
10 KiB
PHP

<?php
namespace App\Libraries;
use CodeIgniter\I18n\Time;
class FhirR4Builder
{
/**
* @param array<string, mixed> $patient
* @param array<string, mixed> $encounter
* @param array<int, array<string, mixed>> $medications
*
* @return array<string, mixed>
*/
public function buildPrescriptionBundle(array $patient, array $encounter, array $medications): array
{
$issuedAt = Time::now('Asia/Kolkata')->toDateTimeString();
$patientRef = 'Patient/' . (string) ($patient['id'] ?? 'unknown');
$encounterRef = 'Encounter/' . (string) ($encounter['id'] ?? 'unknown');
$entries = [[
'resource' => $this->buildPatientResource($patient),
], [
'resource' => [
'resourceType' => 'Encounter',
'id' => (string) ($encounter['id'] ?? 'unknown'),
'status' => (string) ($encounter['status'] ?? 'finished'),
'subject' => ['reference' => $patientRef],
],
]];
foreach ($medications as $index => $medication) {
$entryId = 'medreq-' . ($index + 1);
$entries[] = [
'resource' => [
'resourceType' => 'MedicationRequest',
'id' => $entryId,
'status' => (string) ($medication['status'] ?? 'active'),
'intent' => 'order',
'subject' => ['reference' => $patientRef],
'encounter' => ['reference' => $encounterRef],
'authoredOn' => $issuedAt,
'medicationCodeableConcept' => [
'text' => (string) ($medication['drug_name'] ?? ''),
],
'dosageInstruction' => [[
'text' => (string) ($medication['dosage'] ?? ''),
]],
],
];
}
return [
'resourceType' => 'Bundle',
'type' => 'collection',
'timestamp' => $issuedAt,
'entry' => $entries,
];
}
/**
* @param array<string, mixed> $patient
* @param array<string, mixed> $diagnosticReport
* @param array<int, array<string, mixed>> $observations
*
* @return array<string, mixed>
*/
public function buildLabReportBundle(array $patient, array $diagnosticReport, array $observations): array
{
$issuedAt = Time::now('Asia/Kolkata')->toDateTimeString();
$patientRef = 'Patient/' . (string) ($patient['id'] ?? 'unknown');
$reportId = (string) ($diagnosticReport['id'] ?? 'lab-report-1');
$entries = [[
'resource' => $this->buildPatientResource($patient),
]];
$observationRefs = [];
foreach ($observations as $index => $observation) {
$observationId = 'obs-' . ($index + 1);
$observationRefs[] = ['reference' => 'Observation/' . $observationId];
$entries[] = [
'resource' => [
'resourceType' => 'Observation',
'id' => $observationId,
'status' => (string) ($observation['status'] ?? 'final'),
'code' => [
'text' => (string) ($observation['test_name'] ?? ''),
],
'subject' => ['reference' => $patientRef],
'valueString' => (string) ($observation['value'] ?? ''),
'interpretation' => [[
'text' => (string) ($observation['interpretation'] ?? ''),
]],
],
];
}
$entries[] = [
'resource' => [
'resourceType' => 'DiagnosticReport',
'id' => $reportId,
'status' => (string) ($diagnosticReport['status'] ?? 'final'),
'code' => [
'text' => (string) ($diagnosticReport['title'] ?? 'Laboratory Report'),
],
'subject' => ['reference' => $patientRef],
'issued' => $issuedAt,
'result' => $observationRefs,
'conclusion' => (string) ($diagnosticReport['conclusion'] ?? ''),
],
];
return [
'resourceType' => 'Bundle',
'type' => 'collection',
'timestamp' => $issuedAt,
'entry' => $entries,
];
}
/**
* @param array<string, mixed> $patient
* @param array<string, mixed> $encounter
* @param array<string, mixed> $summary
*
* @return array<string, mixed>
*/
public function buildDischargeSummaryBundle(array $patient, array $encounter, array $summary): array
{
$issuedAt = Time::now('Asia/Kolkata')->toDateTimeString();
$patientId = (string) ($patient['id'] ?? 'unknown');
$encounterId = (string) ($encounter['id'] ?? 'unknown');
return [
'resourceType' => 'Bundle',
'type' => 'document',
'timestamp' => $issuedAt,
'entry' => [[
'resource' => $this->buildPatientResource($patient),
], [
'resource' => [
'resourceType' => 'Composition',
'status' => 'final',
'type' => [
'text' => 'Discharge Summary',
],
'subject' => [
'reference' => 'Patient/' . $patientId,
],
'encounter' => [
'reference' => 'Encounter/' . $encounterId,
],
'date' => $issuedAt,
'title' => (string) ($summary['title'] ?? 'Discharge Summary'),
'section' => [[
'title' => 'Clinical Summary',
'text' => [
'status' => 'generated',
'div' => (string) ($summary['clinical_summary_html'] ?? ''),
],
]],
],
]],
];
}
/**
* @param array<string, mixed> $patient
* @param array<string, mixed> $encounter
* @param array<string, mixed> $claim
*
* @return array<string, mixed>
*/
public function buildClaimBundle(array $patient, array $encounter, array $claim): array
{
$issuedAt = Time::now('Asia/Kolkata')->toDateTimeString();
$patientRef = 'Patient/' . (string) ($patient['id'] ?? 'unknown');
$encounterRef = 'Encounter/' . (string) ($encounter['id'] ?? 'unknown');
$claimId = (string) ($claim['id'] ?? ('claim-' . time()));
$items = is_array($claim['items'] ?? null) ? ($claim['items'] ?? []) : [];
$claimItems = [];
foreach ($items as $index => $item) {
if (! is_array($item)) {
continue;
}
$claimItems[] = [
'sequence' => $index + 1,
'productOrService' => [
'text' => (string) ($item['name'] ?? $item['description'] ?? ''),
],
'quantity' => [
'value' => (float) ($item['qty'] ?? 1),
],
'unitPrice' => [
'value' => (float) ($item['unit_price'] ?? 0),
'currency' => (string) ($item['currency'] ?? 'INR'),
],
'net' => [
'value' => (float) ($item['amount'] ?? 0),
'currency' => (string) ($item['currency'] ?? 'INR'),
],
];
}
$claimResource = [
'resourceType' => 'Claim',
'id' => $claimId,
'status' => (string) ($claim['status'] ?? 'active'),
'use' => (string) ($claim['use'] ?? 'claim'),
'type' => [
'text' => (string) ($claim['type'] ?? 'institutional'),
],
'patient' => ['reference' => $patientRef],
'created' => $issuedAt,
'provider' => [
'display' => (string) ($claim['provider'] ?? ''),
],
'insurer' => [
'display' => (string) ($claim['insurer'] ?? ''),
],
'priority' => [
'text' => (string) ($claim['priority'] ?? 'normal'),
],
'item' => $claimItems,
'total' => [
'value' => (float) ($claim['total'] ?? 0),
'currency' => (string) ($claim['currency'] ?? 'INR'),
],
'encounter' => [[
'reference' => $encounterRef,
]],
];
return [
'resourceType' => 'Bundle',
'type' => 'collection',
'timestamp' => $issuedAt,
'entry' => [[
'resource' => $this->buildPatientResource($patient),
], [
'resource' => [
'resourceType' => 'Encounter',
'id' => (string) ($encounter['id'] ?? 'unknown'),
'status' => (string) ($encounter['status'] ?? 'finished'),
'subject' => ['reference' => $patientRef],
],
], [
'resource' => $claimResource,
]],
];
}
/**
* @param array<string, mixed> $patient
*
* @return array<string, mixed>
*/
private function buildPatientResource(array $patient): array
{
$resource = [
'resourceType' => 'Patient',
'id' => (string) ($patient['id'] ?? 'unknown'),
'name' => [[
'text' => (string) ($patient['name'] ?? ''),
]],
];
if (! empty($patient['gender'])) {
$resource['gender'] = (string) $patient['gender'];
}
if (! empty($patient['birthDate'])) {
$resource['birthDate'] = (string) $patient['birthDate'];
}
$abhaAddress = trim((string) ($patient['abhaAddress'] ?? ''));
if ($abhaAddress !== '') {
$resource['identifier'] = [[
'system' => 'https://healthid.abdm.gov.in/abha-address',
'value' => $abhaAddress,
]];
}
return $resource;
}
}