devsofttech_hms_ci4/app/Controllers/AbdmFhirInspector.php

413 lines
17 KiB
PHP

<?php
namespace App\Controllers;
use App\Libraries\Abdm\AbdmConnectorFactory;
use App\Libraries\Abdm\EAtriaBridgeConnector;
/**
* AbdmFhirInspector
*
* Dedicated standalone ABDM Page for "ABHA Filter & FHIR Inspector":
* - Global ABHA Search & Filter across all patient records and care contexts
* - Live ABDM Gateway Verification (Bridge / National Registry)
* - Full Patient FHIR R4 Bundle Inspector (OPConsult, DiagnosticReport, HealthDocument, Wellness, Invoice, Immunization, DischargeSummary)
* - One-click FHIR Preview with formatted JSON viewer and validation diagnostics
* - Direct Care Context Linking to ABHA
*/
class AbdmFhirInspector extends BaseController
{
protected ?EAtriaBridgeConnector $connector = null;
public function initController(
\CodeIgniter\HTTP\RequestInterface $request,
\CodeIgniter\HTTP\ResponseInterface $response,
\Psr\Log\LoggerInterface $logger
) {
parent::initController($request, $response, $logger);
$this->db = \Config\Database::connect();
try {
$this->connector = AbdmConnectorFactory::bridge();
} catch (\Throwable $e) {
$this->connector = null;
}
}
private function ensureAuthenticated()
{
if (! function_exists('auth') || ! auth()->loggedIn()) {
if ($this->request->isAJAX() || str_contains((string) $this->request->getHeaderLine('Accept'), 'application/json')) {
return $this->response->setStatusCode(401)->setJSON(['ok' => 0, 'error' => 'Authentication required']);
}
return redirect()->to(base_url('login'))->with('error', 'Please log in to access the FHIR Inspector.');
}
return null;
}
/**
* Main page view for ABHA Filter & FHIR Inspector
*/
public function index()
{
if ($deny = $this->ensureAuthenticated()) {
return $deny;
}
$filterAbha = trim((string) ($this->request->getGet('abha_address') ?? $this->request->getGet('abha') ?? ''));
$patientId = (int) ($this->request->getGet('patient_id') ?? $this->request->getGet('id') ?? 0);
$uhid = trim((string) ($this->request->getGet('uhid') ?? ''));
$abhaPatients = $this->getAbhaPatientsList();
return view('abdm/fhir_inspector', [
'filter_abha' => $filterAbha,
'patient_id' => $patientId,
'filter_uhid' => $uhid,
'abha_patients' => $abhaPatients,
'recent_pills' => array_slice($abhaPatients, 0, 8),
]);
}
/**
* AJAX endpoint: Get patient demographics, care contexts, and link status
* GET /AbdmFhirInspector/patient_data?abha_address=...&patient_id=...
*/
public function patientData()
{
if ($deny = $this->ensureAuthenticated()) {
return $deny;
}
$patientId = (int) ($this->request->getGet('patient_id') ?? 0);
$abhaAddress = trim((string) ($this->request->getGet('abha_address') ?? $this->request->getGet('abha') ?? ''));
$pCode = trim((string) ($this->request->getGet('p_code') ?? $this->request->getGet('uhid') ?? ''));
if ($patientId <= 0 && $abhaAddress === '' && $pCode === '') {
return $this->response->setJSON(['ok' => 0, 'error_text' => 'ABHA Address, Patient ID, or UHID is required']);
}
// Delegate to AbdmGateway for canonical patient discovery & care contexts
try {
$gateway = new \App\Controllers\AbdmGateway();
$gateway->initController($this->request, $this->response, service('logger'));
return $gateway->hipPatientCareContexts();
} catch (\Throwable $e) {
log_message('error', '[AbdmFhirInspector::patientData] {msg}', ['msg' => $e->getMessage()]);
return $this->response->setJSON([
'ok' => 0,
'error_text' => 'Unable to load patient care contexts: ' . $e->getMessage(),
]);
}
}
/**
* AJAX endpoint: Live Gateway Link Check for an ABHA address
* GET /AbdmFhirInspector/gateway_check?abha_address=...
*/
public function gatewayCheck()
{
if ($deny = $this->ensureAuthenticated()) {
return $deny;
}
$abha = trim((string) ($this->request->getGet('abha_address') ?? $this->request->getGet('abha') ?? ''));
if ($abha === '') {
return $this->response->setJSON(['ok' => 0, 'error_text' => 'ABHA address is required']);
}
try {
if ($this->connector === null) {
$this->connector = AbdmConnectorFactory::bridge();
}
$result = $this->connector->hipGetPatientLinks(['abha_address' => $abha]);
$careContexts = [];
if (! empty($result['data']) && is_array($result['data'])) {
foreach ($result['data'] as $patientRec) {
foreach ($patientRec['careContexts'] ?? [] as $cc) {
$careContexts[] = [
'reference' => trim((string) ($cc['referenceNumber'] ?? $cc['ref'] ?? '')),
'display' => trim((string) ($cc['display'] ?? '')),
'status' => trim((string) ($cc['status'] ?? 'linked')),
];
}
}
}
return $this->response->setJSON([
'ok' => 1,
'abha_address' => $abha,
'count' => count($careContexts),
'care_contexts' => $careContexts,
'raw_bridge' => $result,
]);
} catch (\Throwable $e) {
return $this->response->setJSON([
'ok' => 0,
'error_text' => 'Bridge check failed: ' . $e->getMessage(),
]);
}
}
/**
* AJAX endpoint: Preview FHIR R4 Bundle for any care context reference
* GET /AbdmFhirInspector/preview_bundle?ref=...&patient_id=...&abha=...
*/
public function previewBundle()
{
if ($deny = $this->ensureAuthenticated()) {
return $deny;
}
$ref = trim((string) ($this->request->getGet('ref') ?? $this->request->getPost('ref') ?? $_GET['ref'] ?? $_REQUEST['ref'] ?? ''));
$patientId = (int) ($this->request->getGet('patient_id') ?? $this->request->getPost('patient_id') ?? $_GET['patient_id'] ?? $_REQUEST['patient_id'] ?? 0);
$abha = trim((string) ($this->request->getGet('abha') ?? $this->request->getPost('abha') ?? $_GET['abha'] ?? $_REQUEST['abha'] ?? ''));
if ($ref === '') {
return $this->response->setStatusCode(400)->setJSON(['ok' => 0, 'error_text' => 'Care Context Reference is required']);
}
// 1. Check health_records table for stored bundle
if ($this->db->tableExists('health_records')) {
$hr = $this->db->table('health_records')
->where('care_context_reference', $ref)
->orderBy('id', 'DESC')
->get(1)
->getRowArray();
if (! empty($hr)) {
$rawBundle = $hr['record_data'] ?? $hr['fhir_bundle'] ?? $hr['bundle'] ?? null;
$decoded = null;
if (is_string($rawBundle) && $rawBundle !== '') {
$decoded = json_decode($rawBundle, true);
} elseif (is_array($rawBundle)) {
$decoded = $rawBundle;
}
if (! empty($decoded)) {
return $this->response->setJSON([
'ok' => 1,
'source' => 'health_records_stored',
'care_context_reference' => $ref,
'care_context_display' => $hr['care_context_display'] ?? $ref,
'hi_type' => $hr['hi_type'] ?? 'HealthRecord',
'bundle' => $decoded,
'fhir_bundle' => $decoded,
'resource_type' => $decoded['resourceType'] ?? 'Bundle',
'total_entries' => count($decoded['entry'] ?? []),
'entry_types' => array_values(array_unique(array_filter(array_map(function ($e) {
return $e['resource']['resourceType'] ?? null;
}, $decoded['entry'] ?? [])))),
]);
}
}
}
// 2. Dynamic generation based on prefix
try {
// OPD Consult
if (preg_match('/^OPD-(\d+)(?:-S(\d+))?/i', $ref, $m)) {
$opdId = (int) $m[1];
$sessionId = ! empty($m[2]) ? (int) $m[2] : $opdId;
$opdController = new \App\Controllers\Opd_prescription();
$opdController->initController($this->request, $this->response, service('logger'));
return $opdController->fhir_bundle_preview($opdId, $sessionId);
}
// Health Document: DOC-file-{id}, DOC-{pid}-{ts}, DOC-{id}
if (preg_match('/^DOC-/i', $ref)) {
$docId = 0;
if (preg_match('/^DOC-file-(\d+)/i', $ref, $m)) {
$docId = (int) $m[1];
} elseif (preg_match('/^DOC-(\d+)-(\d+)/i', $ref, $m)) {
$c1 = (int) $m[1];
$c2 = (int) $m[2];
if ($c2 > 1000000000 && $this->db->tableExists('file_upload_data')) {
$fRow = $this->db->table('file_upload_data')->where('file_name LIKE', '%' . $c2 . '%')->get(1)->getRowArray();
if ($fRow) {
$docId = (int) $fRow['id'];
}
}
if ($docId <= 0) {
$docId = $c1;
}
} elseif (preg_match('/^DOC-(\d+)/i', $ref, $m)) {
$docId = (int) $m[1];
}
if ($docId > 0) {
$docController = new \App\Controllers\DoctorDocument();
$docController->initController($this->request, $this->response, service('logger'));
return $docController->health_document_fhir_preview($docId);
}
}
// Diagnostic Report: LAB-{id} or RAD-{id}
if (preg_match('/^(?:LAB|RAD)-(\d+)/i', $ref, $m)) {
$labId = (int) $m[1];
$gw = new \App\Controllers\AbdmGateway();
$gw->initController($this->request, $this->response, service('logger'));
$_GET['request_id'] = $labId;
$_GET['lab_req_id'] = $labId;
return $gw->diagnosisReportFhirPreview($labId, $patientId, $abha);
}
// Invoices: INVOICE-OPD-{id}, INVOICE-CHG-{id}, INVOICE-IPD-{id}
if (preg_match('/^INVOICE-([A-Za-z]+)-(\d+)/i', $ref, $m)) {
$typeCode = strtoupper($m[1]);
$billId = (int) $m[2];
$source = match ($typeCode) {
'OPD' => 'opd_invoice',
'CHG' => 'charges_invoice',
'IPD' => 'ipd_invoice',
default => 'opd_invoice'
};
$gw = new \App\Controllers\AbdmGateway();
$gw->initController($this->request, $this->response, service('logger'));
$_GET['source'] = $source;
$_GET['bill_id'] = $billId;
$_GET['patient_id'] = $patientId;
return $gw->invoiceFhirPreview($source, $billId, $patientId, $abha);
}
// Immunization: IMM-{id} or IMM-{patientId}-V{id}-{date}
if (preg_match('/^IMM-(?:PAT-(\d+)|(?:(\d+)-V)?(\d+))/i', $ref, $m)) {
$immId = (int) (! empty($m[3]) ? $m[3] : (! empty($m[2]) ? $m[2] : ($m[1] ?? 0)));
if ($patientId <= 0 && $this->db->tableExists('immunization_records')) {
$immRec = $this->db->table('immunization_records')->select('patient_id')->where('id', $immId)->get(1)->getRowArray();
$patientId = (int) ($immRec['patient_id'] ?? 0);
}
$gw = new \App\Controllers\AbdmGateway();
$gw->initController($this->request, $this->response, service('logger'));
$_GET['record_id'] = $immId;
$_GET['patient_id'] = $patientId;
return $gw->immunizationFhirPreview($immId, $patientId, $abha);
}
// Discharge Summary: DISCHARGE-{id}
if (preg_match('/^DISCHARGE-(\d+)/i', $ref, $m)) {
$ipdId = (int) $m[1];
$gw = new \App\Controllers\AbdmGateway();
$gw->initController($this->request, $this->response, service('logger'));
$_GET['ipd_id'] = $ipdId;
return $gw->ipdDischargeFhirPreview($ipdId, $patientId, $abha);
}
// Wellness Record: WELLNESS-{id}
if (preg_match('/^WELLNESS-(?:(\d+)-W)?(\d+)/i', $ref, $m)) {
$wId = ! empty($m[2]) ? (int) $m[2] : (int) $m[1];
$pId = ! empty($m[1]) ? (int) $m[1] : $patientId;
$docController = new \App\Controllers\DoctorDocument();
$docController->initController($this->request, $this->response, service('logger'));
return $docController->wellness_record_fhir_preview($pId, $wId);
}
} catch (\Throwable $e) {
log_message('error', '[AbdmFhirInspector::previewBundle] Generation failed: ' . $e->getMessage());
}
return $this->response->setStatusCode(404)->setJSON([
'ok' => 0,
'error_text' => 'Unable to dynamically generate FHIR bundle for care context: ' . $ref,
]);
}
/**
* Query patient_master for patients with ABHA data
*/
private function getAbhaPatientsList(): array
{
if (! $this->db->tableExists('patient_master')) {
return [];
}
$fields = $this->db->getFieldNames('patient_master') ?? [];
$hasAbhaAddress = in_array('abha_address', $fields, true);
$hasAbhaId = in_array('abha_id', $fields, true);
$hasAbhaNo = in_array('abha_no', $fields, true);
if (! $hasAbhaAddress && ! $hasAbhaId && ! $hasAbhaNo) {
return [];
}
$select = ['id', 'p_code', 'p_fname', 'gender', 'dob'];
if (in_array('p_lname', $fields, true)) {
$select[] = 'p_lname';
}
if (in_array('mphone1', $fields, true)) {
$select[] = 'mphone1';
}
if ($hasAbhaAddress) {
$select[] = 'abha_address';
}
if ($hasAbhaId) {
$select[] = 'abha_id';
}
if ($hasAbhaNo) {
$select[] = 'abha_no';
}
$builder = $this->db->table('patient_master')->select(implode(', ', $select));
$whereOr = [];
if ($hasAbhaAddress) {
$whereOr[] = "NULLIF(TRIM(abha_address), '') IS NOT NULL";
}
if ($hasAbhaId) {
$whereOr[] = "NULLIF(TRIM(abha_id), '') IS NOT NULL";
}
if ($hasAbhaNo) {
$whereOr[] = "NULLIF(TRIM(abha_no), '') IS NOT NULL";
}
if ($whereOr !== []) {
$builder->where('(' . implode(' OR ', $whereOr) . ')', null, false);
}
$rows = $builder->orderBy('id', 'DESC')->limit(300)->get()->getResultArray();
$patients = [];
foreach ($rows as $row) {
$rawAddress = trim((string) ($row['abha_address'] ?? ''));
$rawId = trim((string) ($row['abha_id'] ?? $row['abha_no'] ?? ''));
$addr = '';
$num = '';
if (str_contains($rawAddress, '@')) {
$addr = $rawAddress;
} elseif (str_contains($rawId, '@')) {
$addr = $rawId;
}
$d1 = preg_replace('/\D/', '', $rawId);
$d2 = preg_replace('/\D/', '', $rawAddress);
if (is_string($d1) && strlen($d1) === 14) {
$num = $d1;
} elseif (is_string($d2) && strlen($d2) === 14) {
$num = $d2;
}
if ($addr === '' && $num === '') {
continue;
}
$name = trim(($row['p_fname'] ?? '') . ' ' . ($row['p_lname'] ?? ''));
if ($name === '') {
$name = 'Patient #' . $row['id'];
}
$patients[] = [
'id' => (int) $row['id'],
'p_code' => (string) ($row['p_code'] ?? ('P-' . $row['id'])),
'name' => $name,
'gender' => (string) ($row['gender'] ?? 'M'),
'dob' => (string) ($row['dob'] ?? ''),
'phone' => (string) ($row['mphone1'] ?? ''),
'abha_address' => $addr,
'abha_number' => $num,
];
}
return $patients;
}
}