devsofttech_hms_ci4/app/Controllers/Diagnosis.php

1963 lines
73 KiB
PHP

<?php
namespace App\Controllers;
use App\Controllers\BaseController;
use CodeIgniter\I18n\Time;
use Exception;
class Diagnosis extends BaseController
{
protected $db;
public function __construct()
{
$this->db = \Config\Database::connect();
}
public function index()
{
// Check if it's an AJAX request
$isAjax = $this->request->isAJAX() || $this->request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest';
if (!$isAjax) {
return view('welcome_message');
}
$user = service('auth')->user();
return view('diagnosis/index', [
'user' => $user
]);
}
public function pathology()
{
return $this->labPath(5);
}
public function biopsy()
{
return $this->labPath(30);
}
public function mri()
{
return $this->labPath(2);
}
public function xray()
{
return $this->labPath(3);
}
public function ultrasound()
{
return $this->labPath(1);
}
public function ctscan()
{
return $this->labPath(4);
}
public function labMaster()
{
return $this->index();
}
public function labPathLegacy($labType)
{
return $this->labPath((int) $labType);
}
private function labPath(int $labType)
{
// Check if it's an AJAX request
$isAjax = $this->request->isAJAX() || $this->request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest';
if (!$isAjax) {
return view('welcome_message');
}
$user = service('auth')->user();
$labTypeName = $this->getLabTypeName($labType);
return view('diagnosis/request_list', [
'user' => $user,
'lab_type' => $labType,
'lab_type_name' => $labTypeName
]);
}
public function searchLab($labType = null)
{
$labType = $labType !== null ? (int) $labType : (int) $this->request->getPost('lab_type');
$searchText = $this->request->getPost('txtsearch');
// Sanitize input
$searchText = preg_replace('/[^A-Za-z0-9_ \-]/', '', $searchText);
if (trim($searchText) == '') {
// Get recent 200 records
$sql = "SELECT m.id as inv_id, m.invoice_code, m.inv_date,
CONCAT(p.p_fname, '/', IF(p.gender=1,'M','F'), '/', p.p_rname) as inv_name,
p.dob, p.age, p.age_in_month, p.estimate_dob,
GROUP_CONCAT(CONCAT_WS(';', i.item_name, i.item_name, i.id,
check_item_request(m.id, i.id)) SEPARATOR '#') as data_array,
r.daily_sr_no, r.lab_test_no
FROM invoice_item i
INNER JOIN invoice_master m ON m.id = i.inv_master_id
INNER JOIN hc_item_type y ON i.item_type = y.itype_id
INNER JOIN patient_master p ON p.id = m.attach_id
LEFT JOIN lab_invoice_request r ON m.id = r.invoice_id
WHERE m.payment_status = 1 AND y.itype_id = ?
GROUP BY m.id
ORDER BY m.id DESC
LIMIT 200";
$query = $this->db->query($sql, [$labType]);
} else {
// Search with criteria
$sql = "SELECT m.id as inv_id, m.invoice_code, m.inv_date,
CONCAT(p.p_fname, '/', IF(p.gender=1,'M','F'), '/', p.p_rname) as inv_name,
p.dob, p.age, p.age_in_month, p.estimate_dob,
GROUP_CONCAT(CONCAT_WS(';', i.item_name, i.item_name, i.id,
check_item_request(m.id, i.id)) SEPARATOR '#') as data_array,
r.daily_sr_no, r.lab_test_no
FROM invoice_item i
INNER JOIN invoice_master m ON m.id = i.inv_master_id
INNER JOIN hc_item_type y ON i.item_type = y.itype_id
INNER JOIN patient_master p ON p.id = m.attach_id
LEFT JOIN lab_invoice_request r ON m.id = r.invoice_id
WHERE m.payment_status = 1 AND y.itype_id = ?
AND (m.invoice_code LIKE ? OR p.p_fname LIKE ? OR p.p_code LIKE ?)
GROUP BY m.id
ORDER BY m.id DESC
LIMIT 50";
$searchPattern = '%' . $searchText . '%';
$query = $this->db->query($sql, [$labType, $searchPattern, $searchPattern, $searchPattern]);
}
$results = $query->getResult();
// Calculate age using PHP helper
foreach ($results as $row) {
$row->age = get_age_1($row->dob ?? null, $row->age ?? '', $row->age_in_month ?? '', $row->estimate_dob ?? '');
}
$data = [
'labreport_preprocess' => $results,
'lab_type' => $labType
];
// Return different view based on lab type
if ($labType == 5 || $labType == 30) { // Pathology or Biopsy
return view('diagnosis/lab_report_tab_path', $data);
} else {
return view('diagnosis/lab_report_tab', $data);
}
}
public function searchLabBySrno($labType = null)
{
$labType = $labType !== null ? (int) $labType : (int) $this->request->getPost('lab_type');
$srNo = $this->request->getPost('txtsearch_srno');
// Sanitize input
$srNo = preg_replace('/[^A-Za-z0-9_ \-]/', '', $srNo);
if (trim($srNo) == '') {
// Get recent 3 days records with serial number
$sql = "SELECT m.id as inv_id, m.invoice_code, m.inv_date,
CONCAT(p.p_fname, '/', IF(p.gender=1,'M','F'), '/', p.p_rname) as inv_name,
p.dob, p.age, p.age_in_month, p.estimate_dob,
GROUP_CONCAT(CONCAT_WS(';', i.item_name, i.item_name, i.id,
check_item_request(m.id, i.id)) SEPARATOR '#') as data_array,
r.daily_sr_no, r.lab_test_no
FROM invoice_item i
INNER JOIN invoice_master m ON m.id = i.inv_master_id
INNER JOIN hc_item_type y ON i.item_type = y.itype_id
INNER JOIN patient_master p ON p.id = m.attach_id
INNER JOIN lab_invoice_request r ON m.id = r.invoice_id AND r.daily_sr_no > 0
WHERE m.payment_status = 1 AND y.itype_id = ?
AND m.inv_date >= DATE_ADD(CURDATE(), INTERVAL -3 DAY)
GROUP BY m.id
ORDER BY m.id DESC
LIMIT 50";
$query = $this->db->query($sql, [$labType]);
} else {
// Search by specific serial number
$sql = "SELECT m.id as inv_id, m.invoice_code, m.inv_date,
CONCAT(p.p_fname, '/', IF(p.gender=1,'M','F'), '/', p.p_rname) as inv_name,
p.dob, p.age, p.age_in_month, p.estimate_dob,
GROUP_CONCAT(CONCAT_WS(';', i.item_name, i.item_name, i.id,
check_item_request(m.id, i.id)) SEPARATOR '#') as data_array,
r.daily_sr_no, r.lab_test_no
FROM invoice_item i
INNER JOIN invoice_master m ON m.id = i.inv_master_id
INNER JOIN hc_item_type y ON i.item_type = y.itype_id
INNER JOIN patient_master p ON p.id = m.attach_id
INNER JOIN lab_invoice_request r ON m.id = r.invoice_id AND r.daily_sr_no > 0
WHERE m.payment_status = 1 AND y.itype_id = ? AND r.daily_sr_no = ?
GROUP BY m.id
ORDER BY m.id DESC
LIMIT 50";
$query = $this->db->query($sql, [$labType, $srNo]);
}
$results = $query->getResult();
// Calculate age using PHP helper
foreach ($results as $row) {
$row->age = get_age_1($row->dob ?? null, $row->age ?? '', $row->age_in_month ?? '', $row->estimate_dob ?? '');
}
$data = [
'labreport_preprocess' => $results,
'lab_type' => $labType
];
return view('diagnosis/lab_report_tab', $data);
}
public function searchLabByLabno($labType = null)
{
$labType = $labType !== null ? (int) $labType : (int) $this->request->getPost('lab_type');
$labNo = $this->request->getPost('txtsearch_labno');
// Sanitize input
$labNo = preg_replace('/[^A-Za-z0-9_ \-]/', '', $labNo);
if (trim($labNo) == '') {
// Get recent 3 days records with lab number
$sql = "SELECT m.id as inv_id, m.invoice_code, m.inv_date,
CONCAT(p.p_fname, '/', IF(p.gender=1,'M','F'), '/', p.p_rname) as inv_name,
p.dob, p.age, p.age_in_month, p.estimate_dob,
GROUP_CONCAT(CONCAT_WS(';', i.item_name, i.item_name, i.id,
check_item_request(m.id, i.id)) SEPARATOR '#') as data_array,
r.daily_sr_no, r.lab_test_no
FROM invoice_item i
INNER JOIN invoice_master m ON m.id = i.inv_master_id
INNER JOIN hc_item_type y ON i.item_type = y.itype_id
INNER JOIN patient_master p ON p.id = m.attach_id
INNER JOIN lab_invoice_request r ON m.id = r.invoice_id AND r.lab_test_no > 0
WHERE m.payment_status = 1 AND y.itype_id = ?
AND m.inv_date >= DATE_ADD(CURDATE(), INTERVAL -3 DAY)
GROUP BY m.id
ORDER BY m.id DESC
LIMIT 50";
$query = $this->db->query($sql, [$labType]);
} else {
// Search by specific lab number
$sql = "SELECT m.id as inv_id, m.invoice_code, m.inv_date,
CONCAT(p.p_fname, '/', IF(p.gender=1,'M','F'), '/', p.p_rname) as inv_name,
p.dob, p.age, p.age_in_month, p.estimate_dob,
GROUP_CONCAT(CONCAT_WS(';', i.item_name, i.item_name, i.id,
check_item_request(m.id, i.id)) SEPARATOR '#') as data_array,
r.daily_sr_no, r.lab_test_no
FROM invoice_item i
INNER JOIN invoice_master m ON m.id = i.inv_master_id
INNER JOIN hc_item_type y ON i.item_type = y.itype_id
INNER JOIN patient_master p ON p.id = m.attach_id
INNER JOIN lab_invoice_request r ON m.id = r.invoice_id AND r.lab_test_no > 0
WHERE m.payment_status = 1 AND y.itype_id = ? AND r.lab_test_no = ?
GROUP BY m.id
ORDER BY m.id DESC
LIMIT 50";
$query = $this->db->query($sql, [$labType, $labNo]);
}
$results = $query->getResult();
// Calculate age using PHP helper
foreach ($results as $row) {
$row->age = get_age_1($row->dob ?? null, $row->age ?? '', $row->age_in_month ?? '', $row->estimate_dob ?? '');
}
$data = [
'labreport_preprocess' => $results,
'lab_type' => $labType
];
return view('diagnosis/lab_report_tab', $data);
}
public function selectLabInvoicePath($invoiceId, $labType)
{
// Allow both AJAX and regular GET requests
$isAjax = $this->request->isAJAX() || $this->request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest';
// Get invoice and patient information
$sql = "SELECT m.id as inv_id, m.invoice_code, m.inv_date, m.attach_id,
p.id, p.p_fname, p.p_relative, p.p_rname, p.gender, p.dob, p.age, p.age_in_month,
p.estimate_dob, p.p_code, p.udai, p.relation_patient_cardholder, p.p_relative as relative_name,
p.mphone1 as phone_number, p.email1 as email, p.add1 as address_line1, p.city, p.state
FROM invoice_master m
INNER JOIN patient_master p ON p.id = m.attach_id
WHERE m.id = ? AND m.payment_status = 1";
$query = $this->db->query($sql, [$invoiceId]);
$invoice = $query->getRow();
if (!$invoice) {
return view('welcome_message');
}
// Calculate age
$invoice->age = get_age_1($invoice->dob ?? null, $invoice->age ?? '', $invoice->age_in_month ?? '', $invoice->estimate_dob ?? '');
// Get invoice items (tests) for this invoice
$testSql = "SELECT i.id, i.item_name, i.item_id, m.inv_date
FROM invoice_item i
INNER JOIN invoice_master m ON m.id = i.inv_master_id
WHERE i.inv_master_id = ? AND i.item_type = ?
ORDER BY i.item_name";
$testQuery = $this->db->query($testSql, [$invoiceId, $labType]);
$tests = $testQuery->getResult();
// Get invoice items to correlate with tests
$itemSql = "SELECT i.id, i.item_name, i.item_id
FROM invoice_item i
WHERE i.inv_master_id = ? AND i.item_type = ?";
$itemQuery = $this->db->query($itemSql, [$invoiceId, $labType]);
$items = $itemQuery->getResult();
// Get lab invoice request for timing information
$labInvoiceSql = "SELECT * FROM lab_invoice_request WHERE invoice_id = ? AND lab_type = ?";
$labInvoiceQuery = $this->db->query($labInvoiceSql, [$invoiceId, $labType]);
$labInvoice = $labInvoiceQuery->getRow();
$data = [
'invoice' => $invoice,
'tests' => $tests ?? [],
'items' => $items ?? [],
'lab_invoice' => $labInvoice,
'lab_type' => $labType,
'lab_type_name' => $this->getLabTypeName($labType),
'lab_type_route' => $this->getLabTypeRoute($labType),
];
return view('diagnosis/pathology_detail', $data);
}
public function labDateShow($invoiceId, $labType)
{
// AJAX endpoint to return lab date/time information
try {
log_message('debug', "labDateShow called: invoiceId={$invoiceId}, labType={$labType}");
$isAjax = $this->request->isAJAX() || $this->request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest';
$sql = "SELECT * FROM lab_invoice_request WHERE invoice_id = ? AND lab_type = ?";
$query = $this->db->query($sql, [$invoiceId, $labType]);
$labInvoice = $query->getRow();
log_message('debug', "Lab invoice request: " . ($labInvoice ? "found" : "not found"));
$data = [
'lab_invoice_request' => $labInvoice ? [$labInvoice] : [],
'invoiceId' => $invoiceId,
'labType' => $labType
];
$view = view('diagnosis/lab_date_show', $data);
log_message('debug', "labDateShow view rendered, length: " . strlen($view));
return $view;
} catch (\Throwable $e) {
log_message('error', "labDateShow exception: " . $e->getMessage());
return '<div class="alert alert-danger">Error loading lab timing: ' . htmlspecialchars($e->getMessage()) . '</div>';
}
}
public function testList($invoiceId, $labType)
{
// AJAX endpoint to return test list
try {
log_message('debug', "testList called: invoiceId={$invoiceId}, labType={$labType}");
$isAjax = $this->request->isAJAX() || $this->request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest';
// Get test list with all details - matching old code structure
$sql = "SELECT m.id as inv_id, m.invoice_code, m.inv_date,
CONCAT(p.p_fname, '/', IF(p.gender=1,'M','F'), '/', p.p_rname) as inv_name,
i.item_name, i.id as test_id, i.item_id,
r.lab_repo_id, y.group_desc as item_type_desc, r.status,
r.id as req_id, r.charge_item_id, r.print_combine
FROM invoice_item i
INNER JOIN invoice_master m ON m.id = i.inv_master_id
INNER JOIN hc_item_type y ON i.item_type = y.itype_id
INNER JOIN patient_master p ON p.id = m.attach_id
LEFT JOIN lab_request r ON i.id = r.charge_item_id
LEFT JOIN lab_repo l ON i.item_id = l.charge_id
WHERE m.payment_status = 1 AND y.itype_id = ? AND m.id = ?
ORDER BY i.item_name";
$query = $this->db->query($sql, [$labType, $invoiceId]);
$testList = $query->getResult();
log_message('debug', "testList query returned " . count($testList) . " records");
// Get lab invoice request for timing and lab test number
$labInvoiceSql = "SELECT * FROM lab_invoice_request WHERE invoice_id = ? AND lab_type = ?";
$labInvoiceQuery = $this->db->query($labInvoiceSql, [$invoiceId, $labType]);
$labInvoice = $labInvoiceQuery->getRow();
log_message('debug', "Lab invoice request: " . ($labInvoice ? "found" : "not found"));
// Check sample collection status for each test (has lab_request record)
foreach ($testList as $test) {
$test->check_sample = (!empty($test->req_id)) ? 1 : 0;
}
$data = [
'testlist' => $testList ?? [],
'lab_invoice_request' => $labInvoice ? [$labInvoice] : [],
'invoiceId' => $invoiceId,
'labType' => $labType,
'lab_type' => $labType,
];
$view = view('diagnosis/lab_invoice_main_test_list', $data);
log_message('debug', "testList view rendered, length: " . strlen($view));
return $view;
} catch (\Throwable $e) {
log_message('error', "testList exception: " . $e->getMessage() . " - " . $e->getTraceAsString());
return '<div class="alert alert-danger">Error loading test list: ' . htmlspecialchars($e->getMessage()) . '</div>';
}
}
private function processSampleCollection(int $testId, int $labType, ?int $invoiceId = null): array
{
if ($testId <= 0 || $labType <= 0) {
return ['status' => 'error', 'message' => 'Invalid parameters'];
}
try {
if (($invoiceId ?? 0) <= 0) {
$invoiceRow = $this->db->table('invoice_item')
->select('inv_master_id')
->where('id', $testId)
->where('item_type', $labType)
->get()
->getRow();
if ($invoiceRow) {
$invoiceId = (int) ($invoiceRow->inv_master_id ?? 0);
}
}
if (($invoiceId ?? 0) <= 0) {
return ['status' => 'error', 'message' => 'Invalid invoice ID'];
}
$sql = "SELECT m.invoice_code, p.p_fname as inv_name, t.item_name, t.id as test_id,
if(l.mstRepoKey is null, 0, l.mstRepoKey) as mstRepoKey,
if(l.mstRepoKey is null, t.item_name, l.Title) as Title, m.inv_date,
r.lab_repo_id, y.group_desc as item_type_desc, y.itype_id,
m.insurance_case_id, m.ipd_id, m.id as inv_id, m.attach_id, m.net_amount
FROM invoice_item t
INNER JOIN invoice_master m ON m.id = t.inv_master_id
INNER JOIN hc_item_type y ON t.item_type = y.itype_id
INNER JOIN patient_master p ON p.id = m.attach_id
LEFT JOIN lab_request r ON t.id = r.charge_item_id
LEFT JOIN lab_repo l ON t.item_id = l.charge_id
WHERE m.payment_status = 1 AND y.itype_id = ? AND r.id IS NULL AND t.id = ?";
$query = $this->db->query($sql, [$labType, $testId]);
$testData = $query->getRow();
if (! $testData) {
return ['status' => 'error', 'message' => 'Test not found'];
}
$user = service('auth')->user();
$userName = ($user) ? ($user->first_name . ' ' . $user->last_name) : 'System';
$labRequestData = [
'lab_repo_id' => $testData->mstRepoKey ?? 0,
'charge_id' => $testData->inv_id,
'lab_type' => $testData->itype_id,
'charge_item_id' => $testData->test_id,
'invoice_code' => $testData->invoice_code,
'patient_name' => $testData->inv_name,
'patient_id' => $testData->attach_id,
'ipd_id' => $testData->ipd_id ?? 0,
'org_id' => $testData->insurance_case_id ?? 0,
'Request_Date' => $testData->inv_date,
'report_name' => $testData->Title,
'insert_by' => $userName,
'collected_time' => date('Y-m-d H:i:s')
];
$this->db->table('lab_request')->insert($labRequestData);
$insertId = (int) $this->db->insertID();
$labInvoiceCheckSql = "SELECT id FROM lab_invoice_request WHERE lab_type = ? AND invoice_id = ?";
$labInvoiceCheckQuery = $this->db->query($labInvoiceCheckSql, [$labType, $testData->inv_id]);
$labInvoiceRequest = $labInvoiceCheckQuery->getRow();
if (! $labInvoiceRequest) {
$countSql = "SELECT COUNT(*) as no_rec FROM lab_invoice_request WHERE lab_type = ? AND DATE(report_insert) = CURDATE()";
$countQuery = $this->db->query($countSql, [$labType]);
$countData = $countQuery->getRow();
$countRec = ((int) ($countData->no_rec ?? 0)) + 1;
$labInvoiceData = [
'invoice_id' => $testData->inv_id,
'invoice_code' => $testData->invoice_code,
'lab_type' => $testData->itype_id,
'collected_time' => date('Y-m-d H:i:s'),
'daily_sr_no' => $countRec
];
$this->db->table('lab_invoice_request')->insert($labInvoiceData);
} else {
$this->db->table('lab_invoice_request')
->where('id', $labInvoiceRequest->id)
->update(['collected_time' => date('Y-m-d H:i:s')]);
}
return [
'status' => 'success',
'message' => 'Sample collection recorded successfully',
'request_id' => $insertId,
'invoice_id' => (int) $testData->inv_id,
'lab_type' => $labType,
];
} catch (\Throwable $e) {
return [
'status' => 'error',
'message' => 'Error: ' . $e->getMessage(),
];
}
}
private function getLabTypeName(int $labType): string
{
$types = [
1 => 'Ultrasound',
2 => 'MRI',
3 => 'X-Ray',
4 => 'CT-Scan',
5 => 'Pathology',
6 => 'Echo',
30 => 'Biopsy'
];
return $types[$labType] ?? 'Diagnosis';
}
private function getLabTypeRoute(int $labType): string
{
$routes = [
1 => 'ultrasound',
2 => 'mri',
3 => 'xray',
4 => 'ctscan',
5 => 'pathology',
6 => 'echo',
30 => 'biopsy',
];
return $routes[$labType] ?? 'diagnosis';
}
private function isRadiologyType(int $labType): bool
{
return in_array($labType, [1, 2, 3, 4, 6], true);
}
public function testData()
{
// Diagnostic endpoint to check for test data
// Find an invoice with pathology tests (lab_type=5)
$sql = "SELECT m.id, m.invoice_code, COUNT(i.id) as test_count
FROM invoice_master m
INNER JOIN invoice_item i ON m.id = i.inv_master_id
WHERE m.payment_status = 1 AND i.item_type = 5
GROUP BY m.id
LIMIT 10";
$query = $this->db->query($sql);
$results = $query->getResult();
return $this->response->setJSON([
'message' => 'Sample pathology invoices with test data',
'invoices' => $results,
'instruction' => 'Use an invoice ID from the results above for testing'
]);
}
public function sampleCollection($testId, $labType)
{
$invoiceId = (int) ($this->request->getPost('invoice_id') ?? 0);
$result = $this->processSampleCollection((int) $testId, (int) $labType, $invoiceId > 0 ? $invoiceId : null);
if (($result['status'] ?? 'error') === 'success') {
return $this->response->setJSON($result);
}
return $this->response->setJSON([
'status' => 'error',
'message' => $result['message'] ?? 'Unknown error',
]);
}
public function labTab1Process($testId, $labType)
{
$result = $this->processSampleCollection((int) $testId, (int) $labType, null);
if (($result['status'] ?? 'error') === 'success') {
return (string) ($result['request_id'] ?? 0);
}
return '0';
}
public function updateLabTiming()
{
// AJAX endpoint to update lab collection and report times
$invoiceId = $this->request->getPost('invoice_id');
$labType = $this->request->getPost('lab_type');
$collectedTime = $this->request->getPost('collected_time');
$reportedTime = $this->request->getPost('reported_time');
if (!$invoiceId || !$labType) {
return $this->response->setJSON(['status' => 'error', 'message' => 'Invalid parameters']);
}
try {
$updateData = [];
if (!empty($collectedTime)) {
$updateData['collected_time'] = $collectedTime;
}
if (!empty($reportedTime)) {
$updateData['reported_time'] = $reportedTime;
}
if (empty($updateData)) {
return $this->response->setJSON(['status' => 'error', 'message' => 'No data to update']);
}
$builder = $this->db->table('lab_invoice_request');
$builder->where('invoice_id', $invoiceId);
$builder->where('lab_type', $labType);
$result = $builder->update($updateData);
if ($result) {
return $this->response->setJSON([
'status' => 'success',
'message' => 'Lab timing updated successfully'
]);
} else {
return $this->response->setJSON([
'status' => 'error',
'message' => 'No records updated'
]);
}
} catch (Exception $e) {
return $this->response->setJSON([
'status' => 'error',
'message' => 'Error: ' . $e->getMessage()
]);
}
}
public function updateLabNo($labReqId)
{
// AJAX endpoint to update lab test number
$labTestNo = $this->request->getPost('lab_test_no');
if (!$labReqId || $labTestNo === '') {
return $this->response->setJSON(['status' => 'error', 'message' => 'Invalid parameters']);
}
try {
$builder = $this->db->table('lab_invoice_request');
$builder->where('id', $labReqId);
$result = $builder->update(['lab_test_no' => $labTestNo]);
if ($result || $this->db->affectedRows() > 0) {
return $this->response->setJSON([
'status' => 'success',
'message' => 'Lab test number updated successfully'
]);
} else {
return $this->response->setJSON([
'status' => 'error',
'message' => 'No records updated'
]);
}
} catch (Exception $e) {
return $this->response->setJSON([
'status' => 'error',
'message' => 'Error: ' . $e->getMessage()
]);
}
}
public function editTestData($labReqId)
{
// AJAX endpoint to load old HMS-style detailed test-entry table
try {
$labReqId = (int) $labReqId;
log_message('debug', "editTestData called: labReqId={$labReqId}");
$masterQuery = $this->db->query('SELECT * FROM lab_request WHERE id = ?', [$labReqId]);
$labRequestMaster = $masterQuery->getRow();
if (! $labRequestMaster) {
return '<div class="alert alert-danger">Test request not found</div>';
}
$repoTestsSql = "SELECT r.mstRepoKey, t.mstTestKey
FROM lab_repo r
JOIN lab_repotests j ON r.mstRepoKey = j.mstRepoKey
JOIN lab_tests t ON j.mstTestKey = t.mstTestKey
WHERE r.mstRepoKey = ?
ORDER BY j.EOrder";
$repoTests = $this->db->query($repoTestsSql, [$labRequestMaster->lab_repo_id])->getResult();
foreach ($repoTests as $repoTest) {
$existsSql = "SELECT id FROM lab_request_item
WHERE lab_request_id = ? AND lab_repo_id = ? AND lab_test_id = ?
LIMIT 1";
$exists = $this->db->query($existsSql, [
$labReqId,
$repoTest->mstRepoKey,
$repoTest->mstTestKey,
])->getRow();
if (! $exists) {
$this->db->table('lab_request_item')->insert([
'lab_request_id' => $labReqId,
'lab_repo_id' => $repoTest->mstRepoKey,
'lab_test_id' => $repoTest->mstTestKey,
]);
}
}
$itemsSql = "SELECT
d.mstTestKey,
d.Test,
d.TestID,
d.Result,
d.Formula,
d.VRule,
d.VMsg,
d.Unit,
d.FixedNormals,
i.lab_test_value,
i.lab_test_remark,
i.id,
s.EOrder,
GROUP_CONCAT(CONCAT(o.id, ':', o.option_text, ':', o.option_value) ORDER BY o.sort_id) AS option_value
FROM lab_request_item i
JOIN lab_tests d ON i.lab_test_id = d.mstTestKey
JOIN lab_repotests s ON d.mstTestKey = s.mstTestKey AND i.lab_repo_id = s.mstRepoKey
LEFT JOIN lab_tests_option o ON d.mstTestKey = o.mstTestKey
WHERE i.lab_request_id = ?
GROUP BY d.mstTestKey
ORDER BY s.EOrder";
$labRequestItems = $this->db->query($itemsSql, [$labReqId])->getResult();
return view('diagnosis/test_data_entry_table', [
'lab_request_master' => [$labRequestMaster],
'lab_request_item_entry' => $labRequestItems,
]);
} catch (\Throwable $e) {
log_message('error', 'editTestData exception: ' . $e->getMessage());
return '<div class="alert alert-danger">Error: ' . htmlspecialchars($e->getMessage()) . '</div>';
}
}
public function updateTestValue()
{
// AJAX endpoint to update individual lab test value in modal table
$itemId = (int) $this->request->getPost('test_id');
$testValue = (string) $this->request->getPost('test_value');
if ($itemId <= 0) {
return $this->response->setJSON([
'status' => 'error',
'message' => 'Invalid test id',
]);
}
try {
$this->db->table('lab_request_item')
->where('id', $itemId)
->update(['lab_test_value' => $testValue]);
return $this->response->setJSON([
'status' => 'success',
'value' => $testValue,
]);
} catch (\Throwable $e) {
return $this->response->setJSON([
'status' => 'error',
'message' => $e->getMessage(),
]);
}
}
public function createReport($labReqId)
{
// Old HMS flow equivalent: generate Report_Data, update status, then show final report editor
$labReqId = (int) $labReqId;
if ($labReqId <= 0) {
return '<div class="alert alert-danger">Invalid request id</div>';
}
try {
$labRequest = $this->db->table('lab_request')->where('id', $labReqId)->get()->getRow();
if (! $labRequest) {
return '<div class="alert alert-danger">Request not found</div>';
}
if ($this->isRadiologyType((int) ($labRequest->lab_type ?? 0))) {
return $this->createReportXray($labReqId);
}
$gender = 1;
if (! empty($labRequest->patient_id)) {
$patient = $this->db->table('patient_master')
->where('id', (int) $labRequest->patient_id)
->get()
->getRow();
if ($patient && isset($patient->gender)) {
$gender = (int) $patient->gender;
}
}
$itemSql = "SELECT
d.Test,
d.TestID,
d.Formula,
d.FixedNormals,
d.isGenderSpecific,
d.FixedNormalsWomen,
i.lab_test_value
FROM lab_request_item i
JOIN lab_tests d ON i.lab_test_id = d.mstTestKey
JOIN lab_repotests s ON d.mstTestKey = s.mstTestKey AND i.lab_repo_id = s.mstRepoKey
WHERE i.lab_request_id = ?
ORDER BY s.EOrder";
$dataLabTestList = $this->db->query($itemSql, [$labReqId])->getResult();
$repoFormat = $this->db->table('lab_repo')
->select('mstRepoKey, Title, HTMLData')
->where('mstRepoKey', (int) $labRequest->lab_repo_id)
->get()
->getRow();
$reportTitle = $repoFormat && ! empty($repoFormat->Title)
? $repoFormat->Title
: ($labRequest->report_name ?? 'Lab Report');
$reportString = $repoFormat->HTMLData ?? '';
$reportHeader = '<table border="0" cellpadding="1" cellspacing="1" style="width:100%"><tbody><tr><td><h3>' .
htmlspecialchars((string) $reportTitle, ENT_QUOTES, 'UTF-8') .
'</h3></td></tr></tbody></table>';
$reportFooter = $labRequest->Remark ?? '';
$labTestArray = [];
foreach ($dataLabTestList as $row) {
$labTestArray[$row->TestID] = $row->lab_test_value;
}
foreach ($dataLabTestList as $row) {
$fixedNormals = (string) ($row->FixedNormals ?? '');
$fixedNormalsWomen = (string) ($row->FixedNormalsWomen ?? '');
$formula = trim((string) ($row->Formula ?? ''));
if ($formula !== '') {
foreach ($labTestArray as $key => $value) {
$replaceValue = is_numeric($value) ? $value : '0';
$formula = str_replace('{' . $key . '}', (string) $replaceValue, $formula);
}
$labTestValue = function_exists('cal_exp') ? round((float) cal_exp($formula), 2) : ($row->lab_test_value ?? '');
} else {
$labTestValue = $row->lab_test_value ?? '';
}
$normalRange = ((int) ($row->isGenderSpecific ?? 0) === 1 && $gender !== 1)
? $fixedNormalsWomen
: $fixedNormals;
if ($normalRange !== '' && is_numeric($labTestValue)) {
$parts = explode('-', $normalRange);
if (count($parts) >= 2 && is_numeric($parts[0]) && is_numeric($parts[1])) {
$min = (float) $parts[0];
$max = (float) $parts[1];
$numValue = (float) $labTestValue;
if (! ($numValue >= $min && $numValue <= $max)) {
$labTestValue = '<b>' . $labTestValue . '</b>';
}
}
}
$reportString = str_replace('{' . $row->TestID . '}', (string) $labTestValue, $reportString);
}
$completeReport = $reportHeader . $reportString . $reportFooter;
$now = date('Y-m-d H:i:s');
$this->db->table('lab_request')
->where('id', $labReqId)
->update([
'Report_Data' => $completeReport,
'status' => 1,
'reported_time' => $now,
]);
$this->db->table('lab_invoice_request')
->where('invoice_id', (int) $labRequest->charge_id)
->where('lab_type', (int) $labRequest->lab_type)
->update([
'reported_time' => $now,
]);
return $this->showReportFinal($labReqId);
} catch (\Throwable $e) {
return '<div class="alert alert-danger">Error: ' . htmlspecialchars($e->getMessage()) . '</div>';
}
}
public function showReportFinal($labReqId)
{
$labReqId = (int) $labReqId;
$row = $this->db->table('lab_request')->where('id', $labReqId)->get()->getRow();
if (! $row) {
return '<div class="alert alert-danger">Report not found</div>';
}
if ($this->isRadiologyType((int) ($row->lab_type ?? 0))) {
return $this->showReportFinalXray($labReqId);
}
return view('diagnosis/lab_final_report_show', [
'report_format' => [$row],
]);
}
public function createReportXray($labReqId)
{
$labReqId = (int) $labReqId;
if ($labReqId <= 0) {
return '<div class="alert alert-danger">Invalid request id</div>';
}
try {
$labRequest = $this->db->table('lab_request')->where('id', $labReqId)->get()->getRow();
if (! $labRequest) {
return '<div class="alert alert-danger">Request not found</div>';
}
$repoFormat = $this->db->table('lab_repo')
->select('Title, HTMLData')
->where('mstRepoKey', (int) ($labRequest->lab_repo_id ?? 0))
->get()
->getRow();
$reportTitle = $repoFormat && ! empty($repoFormat->Title)
? (string) $repoFormat->Title
: (string) ($labRequest->report_name ?? 'Radiology Report');
$reportString = (string) ($repoFormat->HTMLData ?? '');
$reportHeader = '<table border="0" cellpadding="1" cellspacing="1" style="width:100%"><tbody><tr><td><h3>' .
htmlspecialchars($reportTitle, ENT_QUOTES, 'UTF-8') .
'</h3></td></tr></tbody></table>';
$reportFooter = (string) ($labRequest->Remark ?? '');
$completeReport = $reportHeader . $reportString . $reportFooter;
$now = date('Y-m-d H:i:s');
$this->db->table('lab_request')
->where('id', $labReqId)
->update([
'Report_Data' => $completeReport,
'status' => 1,
'reported_time' => $now,
]);
$this->db->table('lab_invoice_request')
->where('invoice_id', (int) ($labRequest->charge_id ?? 0))
->where('lab_type', (int) ($labRequest->lab_type ?? 0))
->update([
'reported_time' => $now,
]);
return $this->showReportFinalXray($labReqId);
} catch (\Throwable $e) {
return '<div class="alert alert-danger">Error: ' . htmlspecialchars($e->getMessage()) . '</div>';
}
}
public function showReportFinalXray($labReqId)
{
$labReqId = (int) $labReqId;
$reportRow = $this->db->table('lab_request')->where('id', $labReqId)->get()->getRow();
if (! $reportRow) {
return '<div class="alert alert-danger">Report not found</div>';
}
$invoiceItem = $this->db->table('invoice_item')
->select('item_id')
->where('id', (int) ($reportRow->charge_item_id ?? 0))
->get()
->getRow();
$templateBuilder = $this->db->table('radiology_ultrasound_template')
->select('id, template_name')
->where('Modality', (int) ($reportRow->lab_type ?? 0));
if (! empty($invoiceItem->item_id)) {
$templateBuilder->groupStart()
->where('charge_id', (int) $invoiceItem->item_id)
->orWhere('charge_id', 0)
->groupEnd();
}
$templates = $templateBuilder
->orderBy('template_name', 'ASC')
->get()
->getResult();
return view('diagnosis/lab_final_report_show_xray', [
'report_format' => [$reportRow],
'radiology_ultrasound_template' => $templates,
]);
}
public function getTemplateXray($templateId)
{
$templateId = (int) $templateId;
if ($templateId <= 0) {
return $this->response->setJSON([
'Findings' => '',
'Impression' => '',
]);
}
$row = $this->db->table('radiology_ultrasound_template')
->select('Findings, Impression')
->where('id', $templateId)
->get()
->getRow();
return $this->response->setJSON([
'Findings' => (string) ($row->Findings ?? ''),
'Impression' => (string) ($row->Impression ?? ''),
]);
}
public function finalUpdateXray($labReqId)
{
$labReqId = (int) $labReqId;
$htmlData = (string) $this->request->getPost('HTMLData');
$impression = (string) $this->request->getPost('report_data_Impression');
if ($labReqId <= 0) {
return $this->response->setJSON(['status' => 'error', 'message' => 'Invalid request id']);
}
try {
$this->db->table('lab_request')
->where('id', $labReqId)
->update([
'Report_Data' => $htmlData,
'report_data_Impression' => $impression,
]);
return $this->response->setJSON(['status' => 'success', 'message' => 'Saved']);
} catch (\Throwable $e) {
return $this->response->setJSON(['status' => 'error', 'message' => $e->getMessage()]);
}
}
public function confirmReportXray($labReqId)
{
return $this->confirmReport($labReqId);
}
public function finalUpdate($labReqId)
{
$labReqId = (int) $labReqId;
$htmlData = (string) $this->request->getPost('HTMLData');
if ($labReqId <= 0) {
return $this->response->setJSON(['status' => 'error', 'message' => 'Invalid request id']);
}
try {
$this->db->table('lab_request')
->where('id', $labReqId)
->update(['Report_Data' => $htmlData]);
return $this->response->setJSON(['status' => 'success', 'message' => 'Report updated']);
} catch (\Throwable $e) {
return $this->response->setJSON(['status' => 'error', 'message' => $e->getMessage()]);
}
}
public function confirmReport($labReqId)
{
$labReqId = (int) $labReqId;
if ($labReqId <= 0) {
return $this->response->setJSON(['status' => 'error', 'message' => 'Invalid request id']);
}
try {
$user = service('auth')->user();
$userName = $user ? trim(($user->first_name ?? '') . ' ' . ($user->last_name ?? '')) : 'System';
$this->db->table('lab_request')
->where('id', $labReqId)
->update([
'confirm_by' => $userName,
'status' => 2,
]);
return $this->response->setJSON(['status' => 'success', 'message' => 'Verified and Ready for Print']);
} catch (\Throwable $e) {
return $this->response->setJSON(['status' => 'error', 'message' => $e->getMessage()]);
}
}
public function printSingleReport($labReqId)
{
$labReqId = (int) $labReqId;
if ($labReqId <= 0) {
return '<h3>Invalid request id</h3>';
}
$row = $this->db->table('lab_request')
->where('id', $labReqId)
->get()
->getRow();
if (! $row) {
return '<h3>Report not found</h3>';
}
return view('diagnosis/print_single_report', [
'report' => $row,
]);
}
public function removeTest($labReqId)
{
// Old HMS equivalent of Lab_Admin/report_remove
$labReqId = (int) $labReqId;
if ($labReqId <= 0) {
return $this->response->setJSON([
'status' => 'error',
'message' => 'Invalid request id',
]);
}
try {
$requestRow = $this->db->table('lab_request')
->where('id', $labReqId)
->get()
->getRowArray();
if (! $requestRow) {
return $this->response->setJSON([
'status' => 'error',
'message' => 'Test request not found',
]);
}
$user = service('auth')->user();
$userName = $user ? trim(($user->first_name ?? '') . ' ' . ($user->last_name ?? '')) : 'System';
$requestRow['remove_update_by'] = trim($userName . ' [' . date('d-m-Y H:i') . ']');
$this->db->transStart();
if ($this->db->tableExists('lab_request_delete')) {
$this->db->table('lab_request_delete')->insert($requestRow);
}
$this->db->table('lab_request_item')->where('lab_request_id', $labReqId)->delete();
$this->db->table('lab_request')->where('id', $labReqId)->delete();
$this->db->transComplete();
if (! $this->db->transStatus()) {
return $this->response->setJSON([
'status' => 'error',
'message' => 'Failed to remove test request',
]);
}
return $this->response->setJSON([
'status' => 'success',
'message' => 'Item Removed',
]);
} catch (\Throwable $e) {
return $this->response->setJSON([
'status' => 'error',
'message' => $e->getMessage(),
]);
}
}
public function updateCombineReport()
{
$itemId = (int) $this->request->getPost('item_id');
$checked = (int) $this->request->getPost('checked');
if ($itemId <= 0) {
return $this->response->setJSON([
'status' => 'error',
'message' => 'Invalid item id',
]);
}
try {
$this->db->table('lab_request')
->where('id', $itemId)
->update(['print_combine' => $checked > 0 ? 1 : 0]);
return $this->response->setJSON([
'status' => 'success',
'message' => 'Value Update',
]);
} catch (\Throwable $e) {
return $this->response->setJSON([
'status' => 'error',
'message' => $e->getMessage(),
]);
}
}
public function reportCompile($invoiceId, $labType)
{
$invoiceId = (int) $invoiceId;
$labType = (int) $labType;
if ($invoiceId <= 0 || $labType <= 0) {
return $this->response->setJSON([
'status' => 'error',
'message' => 'Invalid parameters',
]);
}
try {
$sql = "SELECT l.*, g.RepoGrp
FROM (lab_request l JOIN lab_repo r ON l.lab_repo_id = r.mstRepoKey)
LEFT JOIN lab_rgroups g ON r.GrpKey = g.mstRGrpKey
WHERE l.print_combine = 1 AND l.status = 2 AND l.charge_id = ? AND l.lab_type = ?
ORDER BY g.sort_order";
$compiledRows = $this->db->query($sql, [$invoiceId, $labType])->getResult();
if (empty($compiledRows)) {
return $this->response->setJSON([
'status' => 'error',
'message' => 'No completed report selected for compile',
]);
}
$invoice = $this->db->table('invoice_master')->where('id', $invoiceId)->get()->getRow();
$patient = null;
if ($invoice && ! empty($invoice->attach_id)) {
$patient = $this->db->table('patient_master')->where('id', (int) $invoice->attach_id)->get()->getRow();
}
$header = '<table border="0" cellpadding="2" cellspacing="1" style="width:100%"><tr>';
$header .= '<td width="50%" style="vertical-align:top">';
$header .= '<b>Invoice ID :</b> ' . htmlspecialchars((string) ($invoice->invoice_code ?? ''), ENT_QUOTES, 'UTF-8') . '<br/>';
$header .= '<b>Patient Name :</b> ' . htmlspecialchars((string) ($patient->p_fname ?? ''), ENT_QUOTES, 'UTF-8') . '<br/>';
$header .= '<b>UHID :</b> ' . htmlspecialchars((string) ($patient->p_code ?? ''), ENT_QUOTES, 'UTF-8') . '<br/>';
$header .= '</td>';
$header .= '<td width="50%" style="vertical-align:top">';
$header .= '<b>Print Date :</b> ' . date('d-m-Y h:i:s A') . '<br/>';
$header .= '</td>';
$header .= '</tr></table><hr/>';
$rawData = '';
$groupName = '';
foreach ($compiledRows as $row) {
if (($row->RepoGrp ?? '') !== $groupName) {
$rawData .= '<h1 style="text-align:center; vertical-align:middle">' .
htmlspecialchars((string) ($row->RepoGrp ?? ''), ENT_QUOTES, 'UTF-8') .
'</h1>';
}
$rawData .= (string) ($row->Report_Data ?? '');
$groupName = (string) ($row->RepoGrp ?? '');
}
$invoiceReq = $this->db->table('lab_invoice_request')
->where('invoice_id', $invoiceId)
->where('lab_type', $labType)
->get()
->getRow();
if (! $invoiceReq) {
return $this->response->setJSON([
'status' => 'error',
'message' => 'Invoice report record not found',
]);
}
$user = service('auth')->user();
$userId = $user->id ?? '';
$userName = $user ? trim(($user->first_name ?? '') . ' ' . ($user->last_name ?? '')) : 'System';
$compiledBy = $userName . '[' . $userId . '][' . date('d-m-Y h:i:s') . ']';
$this->db->table('lab_invoice_request')
->where('id', (int) $invoiceReq->id)
->update([
'report_data' => $rawData,
'report_header' => $header,
'report_compile' => $compiledBy,
]);
return $this->response->setJSON([
'status' => 'success',
'message' => 'Data Compile',
]);
} catch (\Throwable $e) {
return $this->response->setJSON([
'status' => 'error',
'message' => $e->getMessage(),
]);
}
}
public function printPdfCreate($invoiceId, $labType, $print = 0, $printOnType = 0)
{
// CI4 equivalent of old Lab_Admin/print_pdf_create/{inv}/{lab}/{print}/{print_on_type}
$invoiceId = (int) $invoiceId;
$labType = (int) $labType;
$print = (int) $print;
$printOnType = (int) $printOnType;
if ($invoiceId <= 0 || $labType <= 0) {
return '<h3>Invalid parameters</h3>';
}
$invoiceRequest = $this->db->table('lab_invoice_request')
->where('invoice_id', $invoiceId)
->where('lab_type', $labType)
->get()
->getRow();
if (! $invoiceRequest) {
return '<h3>Error : Record not Exist in lab_invoice_request</h3>';
}
$invoice = $this->db->table('invoice_master')->where('id', $invoiceId)->get()->getRow();
$patient = null;
if ($invoice && ! empty($invoice->attach_id)) {
$patient = $this->db->table('patient_master')->where('id', (int) $invoice->attach_id)->get()->getRow();
}
$head = $this->db->table('diagnosis_head_name')
->where('d_type', $labType)
->get()
->getRow();
$itemType = $this->db->table('hc_item_type')->where('itype_id', $labType)->get()->getRow();
$reportHead = ($itemType->group_desc ?? 'Lab Report')
. ' / Invoice ID :' . ($invoice->invoice_code ?? '')
. ' / Person Name :' . ($patient->p_fname ?? '');
$isPlainPaper = ($printOnType === 1);
if ($print > 0) {
return view('diagnosis/print_compiled_report', [
'invoiceRequest' => $invoiceRequest,
'invoice' => $invoice,
'patient' => $patient,
'head' => $head,
'reportHead' => $reportHead,
'isPlainPaper' => $isPlainPaper,
'labType' => $labType,
'invoiceId' => $invoiceId,
]);
}
return view('diagnosis/print_compiled_report', [
'invoiceRequest' => $invoiceRequest,
'invoice' => $invoice,
'patient' => $patient,
'head' => $head,
'reportHead' => $reportHead,
'isPlainPaper' => $isPlainPaper,
'labType' => $labType,
'invoiceId' => $invoiceId,
]);
}
public function reportFileList($invoiceId, $labType, $delete = 0)
{
$invoiceId = (int) $invoiceId;
$labType = (int) $labType;
$delete = (int) $delete;
if ($invoiceId <= 0 || $labType <= 0) {
return view('diagnosis/lab_report_file_list', ['lab_file_list' => []]);
}
$files = [];
if ($this->db->tableExists('file_upload_data')) {
$files = $this->db->table('file_upload_data')
->where('isdelete', $delete)
->where('charge_id', $invoiceId)
->where('charge_type', $labType)
->orderBy('id', 'DESC')
->get()
->getResult();
}
return view('diagnosis/lab_report_file_list', [
'lab_file_list' => $files,
'invoice_id' => $invoiceId,
'lab_type' => $labType,
]);
}
public function aiExtractLabReportValues()
{
if (! $this->request->isAJAX()) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Invalid request']);
}
if (! $this->db->tableExists('file_upload_data')) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'file_upload_data table not found']);
}
if (! $this->db->tableExists('lab_ai_extraction_batches') || ! $this->db->tableExists('lab_ai_extraction_values')) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'AI extraction tables not found. Run migrations first.']);
}
$fileId = (int) $this->request->getPost('file_upload_id');
$invoiceId = (int) $this->request->getPost('invoice_id');
$labType = (int) $this->request->getPost('lab_type');
$panelName = trim((string) $this->request->getPost('panel_name'));
if ($fileId <= 0 || $invoiceId <= 0 || $labType <= 0) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Required fields missing']);
}
$fileRow = $this->db->table('file_upload_data')
->where('id', $fileId)
->where('charge_id', $invoiceId)
->where('charge_type', $labType)
->get(1)
->getRowArray();
if (empty($fileRow)) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Report file not found']);
}
$fullPath = trim((string) ($fileRow['full_path'] ?? ''));
$absolutePath = $this->resolveUploadAbsolutePath($fullPath);
if ($absolutePath === '' || ! is_file($absolutePath)) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'File not accessible on server']);
}
$ocrText = $this->extractTextFromLabFile($absolutePath);
if ($ocrText === '') {
return $this->response->setJSON(['update' => 0, 'error_text' => 'OCR failed. Configure AZURE_DOCINTEL_ENDPOINT/KEY or use readable image.']);
}
$parsed = $this->parseLabValuesWithAzureOpenAi($ocrText, $panelName);
if (empty($parsed['values']) || ! is_array($parsed['values'])) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'AI parsing failed. Check Azure OpenAI settings and deployment.']);
}
$modelName = trim((string) ($parsed['model'] ?? ''));
$rawResponse = (string) json_encode($parsed['raw'] ?? [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
$batchId = $this->insertLabAiBatch(
$invoiceId,
$labType,
$fileId,
$panelName,
$modelName,
$ocrText,
$rawResponse
);
if ($batchId <= 0) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Unable to store AI extraction batch']);
}
$storedCount = 0;
foreach ($parsed['values'] as $item) {
if (! is_array($item)) {
continue;
}
$testName = trim((string) ($item['test_name'] ?? ''));
if ($testName === '') {
continue;
}
$ok = $this->db->table('lab_ai_extraction_values')->insert([
'batch_id' => $batchId,
'test_name' => $testName,
'test_value' => trim((string) ($item['value'] ?? '')),
'unit' => trim((string) ($item['unit'] ?? '')),
'reference_range' => trim((string) ($item['reference_range'] ?? '')),
'abnormal_flag' => trim((string) ($item['abnormal_flag'] ?? '')),
'raw_line' => trim((string) ($item['raw_line'] ?? '')),
'created_at' => Time::now('Asia/Kolkata')->toDateTimeString(),
'updated_at' => Time::now('Asia/Kolkata')->toDateTimeString(),
]);
if ($ok) {
$storedCount++;
}
}
$this->auditClinicalUpdate('lab_ai_extraction_batches', 'created', $batchId, null, [
'invoice_id' => $invoiceId,
'lab_type' => $labType,
'file_upload_id' => $fileId,
'stored_values' => $storedCount,
]);
return $this->response->setJSON([
'update' => 1,
'error_text' => 'AI extracted ' . $storedCount . ' values. Doctor verification pending.',
'batch_id' => $batchId,
'stored_values' => $storedCount,
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
public function aiExtractedValues($invoiceId, $labType)
{
$invoiceId = (int) $invoiceId;
$labType = (int) $labType;
if ($invoiceId <= 0 || $labType <= 0 || ! $this->db->tableExists('lab_ai_extraction_batches')) {
return view('diagnosis/lab_ai_extracted_values', [
'batch' => null,
'values' => [],
]);
}
$batch = $this->db->table('lab_ai_extraction_batches')
->where('invoice_id', $invoiceId)
->where('lab_type', $labType)
->orderBy('id', 'DESC')
->get(1)
->getRowArray();
$values = [];
if (! empty($batch['id']) && $this->db->tableExists('lab_ai_extraction_values')) {
$values = $this->db->table('lab_ai_extraction_values')
->where('batch_id', (int) $batch['id'])
->orderBy('id', 'ASC')
->get()
->getResultArray();
}
return view('diagnosis/lab_ai_extracted_values', [
'batch' => $batch,
'values' => $values,
]);
}
public function aiVerifyExtractedValues($batchId)
{
if (! $this->request->isAJAX()) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Invalid request']);
}
$batchId = (int) $batchId;
if ($batchId <= 0 || ! $this->db->tableExists('lab_ai_extraction_batches')) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Invalid batch id']);
}
$user = function_exists('auth') ? auth()->user() : null;
$verifiedBy = (string) ($user->id ?? 'system');
$updated = $this->db->table('lab_ai_extraction_batches')
->where('id', $batchId)
->update([
'doctor_verified' => 1,
'verified_by' => $verifiedBy,
'verified_at' => Time::now('Asia/Kolkata')->toDateTimeString(),
'updated_at' => Time::now('Asia/Kolkata')->toDateTimeString(),
]);
if (! $updated) {
return $this->response->setJSON(['update' => 0, 'error_text' => 'Unable to verify batch']);
}
$this->auditClinicalUpdate('lab_ai_extraction_batches', 'doctor_verified', $batchId, 0, 1, $verifiedBy);
return $this->response->setJSON([
'update' => 1,
'error_text' => 'Marked as doctor-verified',
'csrfName' => csrf_token(),
'csrfHash' => csrf_hash(),
]);
}
private function insertLabAiBatch(
int $invoiceId,
int $labType,
int $fileId,
string $panelName,
string $modelName,
string $ocrText,
string $rawResponse
): int {
$ok = $this->db->table('lab_ai_extraction_batches')->insert([
'invoice_id' => $invoiceId,
'lab_type' => $labType,
'file_upload_id' => $fileId,
'panel_name' => $panelName,
'ai_provider' => 'azure-openai',
'model_name' => $modelName,
'ocr_text' => $ocrText,
'raw_response_json' => $rawResponse,
'status' => 'completed',
'doctor_verified' => 0,
'created_at' => Time::now('Asia/Kolkata')->toDateTimeString(),
'updated_at' => Time::now('Asia/Kolkata')->toDateTimeString(),
]);
if (! $ok) {
return 0;
}
return (int) $this->db->insertID();
}
private function resolveUploadAbsolutePath(string $fullPath): string
{
$fullPath = trim($fullPath);
if ($fullPath === '') {
return '';
}
if (is_file($fullPath)) {
return $fullPath;
}
$normalized = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $fullPath);
if (is_file($normalized)) {
return $normalized;
}
$fileName = basename($normalized);
$candidates = [
ROOTPATH . 'public' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . $fileName,
WRITEPATH . 'uploads' . DIRECTORY_SEPARATOR . $fileName,
];
foreach ($candidates as $candidate) {
if (is_file($candidate)) {
return $candidate;
}
}
return '';
}
private function extractTextFromLabFile(string $absolutePath): string
{
$docEndpoint = rtrim($this->readSetting('AZURE_DOCINTEL_ENDPOINT'), '/');
$docKey = trim($this->readSetting('AZURE_DOCINTEL_KEY'));
if ($docEndpoint !== '' && $docKey !== '') {
$text = $this->extractTextWithDocumentIntelligence($absolutePath, $docEndpoint, $docKey);
if ($text !== '') {
return $text;
}
}
return $this->extractTextWithAzureVisionLlm($absolutePath);
}
private function extractTextWithDocumentIntelligence(string $absolutePath, string $endpoint, string $key): string
{
try {
$client = service('curlrequest', $this->httpOptions());
$bytes = @file_get_contents($absolutePath);
if ($bytes === false) {
return '';
}
$analyzeUrl = $endpoint . '/documentintelligence/documentModels/prebuilt-read:analyze?api-version=2024-02-29-preview';
$submit = $client->post($analyzeUrl, [
'headers' => [
'Content-Type' => 'application/octet-stream',
'Ocp-Apim-Subscription-Key' => $key,
],
'body' => $bytes,
]);
if ($submit->getStatusCode() < 200 || $submit->getStatusCode() >= 300) {
return '';
}
$operationLocation = trim((string) $submit->getHeaderLine('Operation-Location'));
if ($operationLocation === '') {
return '';
}
for ($i = 0; $i < 12; $i++) {
usleep(800000);
$poll = $client->get($operationLocation, [
'headers' => [
'Ocp-Apim-Subscription-Key' => $key,
],
]);
if ($poll->getStatusCode() < 200 || $poll->getStatusCode() >= 300) {
continue;
}
$decoded = json_decode((string) $poll->getBody(), true);
if (! is_array($decoded)) {
continue;
}
$status = strtolower((string) ($decoded['status'] ?? ''));
if ($status === 'succeeded') {
$lines = [];
$pages = $decoded['analyzeResult']['pages'] ?? [];
foreach ($pages as $page) {
$pageLines = $page['lines'] ?? [];
foreach ($pageLines as $line) {
$content = trim((string) ($line['content'] ?? ''));
if ($content !== '') {
$lines[] = $content;
}
}
}
return trim(implode("\n", $lines));
}
if ($status === 'failed') {
return '';
}
}
} catch (\Throwable $e) {
return '';
}
return '';
}
private function extractTextWithAzureVisionLlm(string $absolutePath): string
{
$endpoint = rtrim($this->readSetting('AZURE_OPENAI_ENDPOINT'), '/');
$apiKey = trim($this->readSetting('AZURE_OPENAI_API_KEY'));
$deployment = trim($this->readSetting('AZURE_OPENAI_DEPLOYMENT'));
$apiVersion = trim($this->readSetting('AZURE_OPENAI_API_VERSION'));
if ($endpoint === '' || $apiKey === '' || $deployment === '') {
return '';
}
if ($apiVersion === '') {
$apiVersion = '2024-10-21';
}
$mime = $this->detectMimeType($absolutePath);
if (! str_starts_with($mime, 'image/')) {
return '';
}
$bytes = @file_get_contents($absolutePath);
if ($bytes === false) {
return '';
}
$dataUrl = 'data:' . $mime . ';base64,' . base64_encode($bytes);
$url = $endpoint . '/openai/deployments/' . rawurlencode($deployment) . '/chat/completions?api-version=' . rawurlencode($apiVersion);
try {
$client = service('curlrequest', $this->httpOptions());
$response = $client->post($url, [
'headers' => [
'api-key' => $apiKey,
'Content-Type' => 'application/json',
],
'json' => [
'messages' => [[
'role' => 'user',
'content' => [
['type' => 'text', 'text' => 'Read and return all text from this medical lab report image exactly as plain text.'],
['type' => 'image_url', 'image_url' => ['url' => $dataUrl]],
],
]],
'temperature' => 0,
'max_tokens' => 3000,
],
]);
if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
return '';
}
$decoded = json_decode((string) $response->getBody(), true);
return trim((string) ($decoded['choices'][0]['message']['content'] ?? ''));
} catch (\Throwable $e) {
return '';
}
}
/**
* @return array<string, mixed>
*/
private function parseLabValuesWithAzureOpenAi(string $ocrText, string $panelName): array
{
$endpoint = rtrim($this->readSetting('AZURE_OPENAI_ENDPOINT'), '/');
$apiKey = trim($this->readSetting('AZURE_OPENAI_API_KEY'));
$deployment = trim($this->readSetting('AZURE_OPENAI_DEPLOYMENT'));
$apiVersion = trim($this->readSetting('AZURE_OPENAI_API_VERSION'));
if ($endpoint === '' || $apiKey === '' || $deployment === '') {
return [];
}
if ($apiVersion === '') {
$apiVersion = '2024-10-21';
}
$schemaHint = '{"panel_name":"string","values":[{"test_name":"string","value":"string","unit":"string","reference_range":"string","abnormal_flag":"H|L|N|","raw_line":"string"}] }';
$prompt = "You are an expert lab report parser for Indian hospitals.\n" .
"Extract only measurable pathology values from OCR text.\n" .
"Return strict JSON only, no markdown, no comments, with schema: " . $schemaHint . "\n" .
"If uncertain, keep empty string. panel_name input: " . $panelName . "\n\n" .
"OCR_TEXT:\n" . mb_substr($ocrText, 0, 14000);
$url = $endpoint . '/openai/deployments/' . rawurlencode($deployment) . '/chat/completions?api-version=' . rawurlencode($apiVersion);
try {
$client = service('curlrequest', $this->httpOptions());
$response = $client->post($url, [
'headers' => [
'api-key' => $apiKey,
'Content-Type' => 'application/json',
],
'json' => [
'messages' => [[
'role' => 'user',
'content' => $prompt,
]],
'temperature' => 0,
'response_format' => ['type' => 'json_object'],
'max_tokens' => 2200,
],
]);
if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
return [];
}
$decoded = json_decode((string) $response->getBody(), true);
$content = trim((string) ($decoded['choices'][0]['message']['content'] ?? ''));
if ($content === '') {
return [];
}
$json = json_decode($content, true);
if (! is_array($json)) {
return [];
}
return [
'model' => (string) ($decoded['model'] ?? $deployment),
'values' => is_array($json['values'] ?? null) ? $json['values'] : [],
'raw' => $decoded,
];
} catch (\Throwable $e) {
return [];
}
}
/**
* @return array<string, mixed>
*/
private function httpOptions(): array
{
$options = [
'timeout' => 45,
'http_errors' => false,
];
if (defined('ENVIRONMENT') && in_array((string) ENVIRONMENT, ['development', 'testing'], true)) {
$options['verify'] = false;
}
return $options;
}
private function readSetting(string $name): string
{
if (defined($name)) {
$value = trim((string) constant($name));
if ($value !== '') {
return $value;
}
}
if (! $this->db->tableExists('hospital_setting')) {
return '';
}
$row = $this->db->table('hospital_setting')
->select('s_value')
->where('s_name', $name)
->get(1)
->getRowArray();
return trim((string) ($row['s_value'] ?? ''));
}
private function detectMimeType(string $path): string
{
$mime = '';
if (function_exists('mime_content_type')) {
$mime = (string) @mime_content_type($path);
}
if ($mime === '') {
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
$map = [
'jpg' => 'image/jpeg',
'jpeg' => 'image/jpeg',
'png' => 'image/png',
'webp' => 'image/webp',
'bmp' => 'image/bmp',
'gif' => 'image/gif',
'pdf' => 'application/pdf',
];
$mime = $map[$ext] ?? 'application/octet-stream';
}
return $mime;
}
public function selectLabRadiology($invoiceId, $labType)
{
return $this->selectLabInvoicePath((int) $invoiceId, (int) $labType);
}
}