2710 lines
102 KiB
PHP
2710 lines
102 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 openReportEditorPage($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>';
|
|
}
|
|
|
|
$patientRow = $this->db->table('patient_master')
|
|
->select('gender, age, age_in, age_in_month, dob')
|
|
->where('id', (int) ($reportRow->patient_id ?? 0))
|
|
->get()
|
|
->getRow();
|
|
|
|
$genderRaw = (int) ($patientRow->gender ?? 0);
|
|
$reportRow->gender_text = $genderRaw === 1 ? 'Male' : ($genderRaw === 2 ? 'Female' : '-');
|
|
|
|
$ageVal = (int) ($patientRow->age ?? 0);
|
|
$ageIn = trim((string) ($patientRow->age_in ?? ''));
|
|
$ageInMonth = (int) ($patientRow->age_in_month ?? 0);
|
|
|
|
if ($ageVal > 0) {
|
|
$ageUnit = $ageIn !== '' ? $ageIn : 'Years';
|
|
$reportRow->age_text = $ageVal . ' ' . $ageUnit;
|
|
} elseif ($ageInMonth > 0) {
|
|
$reportRow->age_text = $ageInMonth . ' Months';
|
|
} else {
|
|
$dob = (string) ($patientRow->dob ?? '');
|
|
if ($dob !== '' && $dob !== '0000-00-00') {
|
|
try {
|
|
$dobDate = new \DateTime($dob);
|
|
$today = new \DateTime('today');
|
|
$diff = $dobDate->diff($today);
|
|
$reportRow->age_text = $diff->y > 0 ? ($diff->y . ' Years') : ($diff->m . ' Months');
|
|
} catch (\Throwable $e) {
|
|
$reportRow->age_text = '-';
|
|
}
|
|
} else {
|
|
$reportRow->age_text = '-';
|
|
}
|
|
}
|
|
|
|
$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_editor', [
|
|
'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 imagingUploadGallery($invoiceId, $labType, $labReqId = 0)
|
|
{
|
|
$invoiceId = (int) $invoiceId;
|
|
$labType = (int) $labType;
|
|
$labReqId = (int) $labReqId;
|
|
|
|
if ($invoiceId <= 0 || $labType <= 0 || ! $this->db->tableExists('file_upload_data')) {
|
|
return view('diagnosis/imaging_upload_gallery', [
|
|
'files' => [],
|
|
'study_name' => '',
|
|
]);
|
|
}
|
|
|
|
$studyName = '';
|
|
if ($labReqId > 0) {
|
|
$requestRow = $this->db->table('lab_request')
|
|
->select('id, report_name')
|
|
->where('id', $labReqId)
|
|
->get(1)
|
|
->getRowArray();
|
|
$studyName = trim((string) ($requestRow['report_name'] ?? ''));
|
|
}
|
|
|
|
$builder = $this->db->table('file_upload_data')
|
|
->where('isdelete', 0)
|
|
->where('charge_id', $invoiceId)
|
|
->where('charge_type', $labType);
|
|
|
|
$fields = $this->db->getFieldNames('file_upload_data');
|
|
if ($labReqId > 0 && in_array('repo_id', $fields, true)) {
|
|
$builder->where('repo_id', $labReqId);
|
|
}
|
|
|
|
$rows = $builder->orderBy('id', 'DESC')->get()->getResultArray();
|
|
$files = [];
|
|
foreach ($rows as $row) {
|
|
$fullPath = trim((string) ($row['full_path'] ?? ''));
|
|
$url = $this->buildUploadPublicUrl($fullPath);
|
|
$mime = trim((string) ($row['file_type'] ?? ''));
|
|
$ext = strtolower(pathinfo((string) ($row['file_name'] ?? ''), PATHINFO_EXTENSION));
|
|
$isImage = (int) ($row['is_image'] ?? 0) === 1 || str_starts_with($mime, 'image/');
|
|
$isPdf = $ext === 'pdf' || $mime === 'application/pdf';
|
|
|
|
$files[] = [
|
|
'id' => (int) ($row['id'] ?? 0),
|
|
'name' => (string) ($row['orig_name'] ?? $row['file_name'] ?? 'Uploaded file'),
|
|
'desc' => (string) ($row['file_desc'] ?? ''),
|
|
'url' => $url,
|
|
'is_image' => $isImage,
|
|
'is_pdf' => $isPdf,
|
|
'insert_time' => (string) ($row['insert_time'] ?? $row['insert_date'] ?? ''),
|
|
'ai_status' => (string) ($row['ai_status'] ?? ''),
|
|
'ai_alert_text' => (string) ($row['ai_alert_text'] ?? ''),
|
|
'scan_type' => (string) ($row['scan_type'] ?? ''),
|
|
];
|
|
}
|
|
|
|
return view('diagnosis/imaging_upload_gallery', [
|
|
'files' => $files,
|
|
'study_name' => $studyName,
|
|
]);
|
|
}
|
|
|
|
public function uploadReportFile()
|
|
{
|
|
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']);
|
|
}
|
|
|
|
$invoiceId = (int) $this->request->getPost('invoice_id');
|
|
$labType = (int) $this->request->getPost('lab_type');
|
|
$reqId = (int) $this->request->getPost('req_id');
|
|
$fileDesc = trim((string) $this->request->getPost('file_desc'));
|
|
$scanType = trim((string) $this->request->getPost('scan_type'));
|
|
|
|
if ($invoiceId <= 0 || $labType <= 0) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Missing invoice/lab context']);
|
|
}
|
|
|
|
$upload = $this->request->getFile('report_file');
|
|
if (! $upload || ! $upload->isValid()) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'No valid file uploaded']);
|
|
}
|
|
|
|
$ext = strtolower((string) $upload->getExtension());
|
|
$allowed = ['jpg', 'jpeg', 'png', 'webp', 'bmp', 'gif', 'pdf'];
|
|
if (! in_array($ext, $allowed, true)) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Unsupported file type']);
|
|
}
|
|
|
|
$targetDir = ROOTPATH . 'public' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'diagnosis' . DIRECTORY_SEPARATOR . date('Y') . DIRECTORY_SEPARATOR . date('m');
|
|
if (! is_dir($targetDir) && ! @mkdir($targetDir, 0775, true) && ! is_dir($targetDir)) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Unable to create upload directory']);
|
|
}
|
|
|
|
$storedName = 'diag_' . date('Ymd_His') . '_' . substr(md5((string) mt_rand()), 0, 8) . '.' . $ext;
|
|
|
|
try {
|
|
$upload->move($targetDir, $storedName, true);
|
|
} catch (\Throwable $e) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Upload failed: ' . $e->getMessage()]);
|
|
}
|
|
|
|
$absolutePath = $targetDir . DIRECTORY_SEPARATOR . $storedName;
|
|
$relativePath = 'uploads/diagnosis/' . date('Y') . '/' . date('m') . '/' . $storedName;
|
|
$publicUrl = base_url($relativePath);
|
|
|
|
$user = function_exists('auth') ? auth()->user() : null;
|
|
$uploadBy = trim((string) ($user->username ?? $user->email ?? 'system'));
|
|
$uploadById = (int) ($user->id ?? 0);
|
|
|
|
$fields = $this->db->getFieldNames('file_upload_data');
|
|
$insert = [];
|
|
$fieldMap = [
|
|
'name' => $storedName,
|
|
'file_name' => $storedName,
|
|
'orig_name' => (string) $upload->getClientName(),
|
|
'client_name' => (string) $upload->getClientName(),
|
|
'file_ext' => '.' . $ext,
|
|
'file_type' => (string) $upload->getClientMimeType(),
|
|
'full_path' => str_replace('\\', '/', $absolutePath),
|
|
'file_path' => str_replace('\\', '/', $targetDir) . '/',
|
|
'raw_name' => pathinfo($storedName, PATHINFO_FILENAME),
|
|
'store_file_name' => $storedName,
|
|
'file_size' => round(((float) $upload->getSize()) / 1024, 2),
|
|
'is_image' => in_array($ext, ['jpg', 'jpeg', 'png', 'webp', 'bmp', 'gif'], true) ? 1 : 0,
|
|
'image_type' => $ext,
|
|
'insert_date' => Time::now('Asia/Kolkata')->toDateTimeString(),
|
|
'insert_time' => Time::now('Asia/Kolkata')->toDateTimeString(),
|
|
'file_desc' => $fileDesc !== '' ? $fileDesc : 'Diagnosis Report Upload',
|
|
'repo_id' => $reqId,
|
|
'charge_id' => $invoiceId,
|
|
'charge_type' => $labType,
|
|
'upload_by' => $uploadBy,
|
|
'upload_by_id' => $uploadById,
|
|
'scan_type' => $scanType !== '' ? $scanType : 'upload',
|
|
'show_type' => 0,
|
|
'isdelete' => 0,
|
|
'document_type' => 'diagnosis-report',
|
|
'content_description' => 'Uploaded from diagnosis editor',
|
|
'ai_status' => 'pending',
|
|
'ai_alert_flag' => 0,
|
|
'ai_alert_text' => null,
|
|
];
|
|
|
|
foreach ($fieldMap as $key => $value) {
|
|
if (in_array($key, $fields, true)) {
|
|
$insert[$key] = $value;
|
|
}
|
|
}
|
|
|
|
$ok = $this->db->table('file_upload_data')->insert($insert);
|
|
if (! $ok) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Unable to save upload metadata']);
|
|
}
|
|
|
|
return $this->response->setJSON([
|
|
'update' => 1,
|
|
'error_text' => 'File uploaded successfully',
|
|
'file_id' => (int) $this->db->insertID(),
|
|
'file_name' => $storedName,
|
|
'file_url' => $publicUrl,
|
|
'csrfName' => csrf_token(),
|
|
'csrfHash' => csrf_hash(),
|
|
]);
|
|
}
|
|
|
|
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->parseLabValuesWithAi($ocrText, $panelName);
|
|
if (empty($parsed['values']) || ! is_array($parsed['values'])) {
|
|
$errorText = trim((string) ($parsed['error'] ?? ''));
|
|
if ($errorText === '') {
|
|
$errorText = 'AI parsing failed. Check AI endpoint/settings and try retry.';
|
|
}
|
|
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => $errorText]);
|
|
}
|
|
|
|
$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.',
|
|
'ai_provider' => (string) ($parsed['provider'] ?? 'azure-openai'),
|
|
'ai_model' => $modelName,
|
|
'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(),
|
|
]);
|
|
}
|
|
|
|
public function imagingAiDiagnosis($labReqId)
|
|
{
|
|
if (! $this->request->isAJAX()) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Invalid request']);
|
|
}
|
|
|
|
$labReqId = (int) $labReqId;
|
|
if ($labReqId <= 0) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Invalid request id']);
|
|
}
|
|
|
|
$labRequest = $this->db->table('lab_request')
|
|
->where('id', $labReqId)
|
|
->get(1)
|
|
->getRowArray();
|
|
|
|
if (empty($labRequest)) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Imaging request not found']);
|
|
}
|
|
|
|
$labType = (int) ($labRequest['lab_type'] ?? 0);
|
|
if (! $this->isRadiologyType($labType)) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'AI diagnosis is available for imaging only']);
|
|
}
|
|
|
|
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')) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'AI batch table not found. Run migrations first.']);
|
|
}
|
|
|
|
$invoiceId = (int) ($labRequest['charge_id'] ?? 0);
|
|
$studyName = trim((string) ($labRequest['report_name'] ?? 'Imaging Study'));
|
|
$files = $this->findImagingFilesForRequest($invoiceId, $labType, $labReqId);
|
|
|
|
if (empty($files)) {
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'No uploaded image/PDF found for this study']);
|
|
}
|
|
|
|
$fileIds = array_values(array_filter(array_map(static fn($row) => (int) ($row['id'] ?? 0), $files)));
|
|
$this->updateImagingFileAiState($fileIds, 'processing', 'AI diagnosis is running');
|
|
|
|
$ocrChunks = [];
|
|
$visionImages = [];
|
|
$sourceFiles = [];
|
|
|
|
foreach ($files as $fileRow) {
|
|
$absolutePath = $this->resolveUploadAbsolutePath((string) ($fileRow['full_path'] ?? ''));
|
|
if ($absolutePath === '' || ! is_file($absolutePath)) {
|
|
continue;
|
|
}
|
|
|
|
$fileName = (string) ($fileRow['orig_name'] ?? $fileRow['file_name'] ?? basename($absolutePath));
|
|
$mime = $this->detectMimeType($absolutePath);
|
|
$sourceFiles[] = [
|
|
'id' => (int) ($fileRow['id'] ?? 0),
|
|
'name' => $fileName,
|
|
'scan_type' => (string) ($fileRow['scan_type'] ?? ''),
|
|
];
|
|
|
|
$ocrText = trim($this->extractTextFromLabFile($absolutePath));
|
|
if ($ocrText !== '') {
|
|
$ocrChunks[] = 'FILE: ' . $fileName . "\n" . $ocrText;
|
|
}
|
|
|
|
if (str_starts_with($mime, 'image/') && count($visionImages) < 4) {
|
|
$imagePayload = $this->buildVisionImagePayload($absolutePath, $mime);
|
|
if ($imagePayload !== '') {
|
|
$visionImages[] = [
|
|
'type' => 'image_url',
|
|
'image_url' => ['url' => $imagePayload],
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
$analysis = $this->generateImagingDiagnosisDraft($studyName, $ocrChunks, $visionImages);
|
|
if (($analysis['error'] ?? '') !== '') {
|
|
$this->updateImagingFileAiState($fileIds, 'failed', (string) $analysis['error'], 1);
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => (string) $analysis['error']]);
|
|
}
|
|
|
|
$rawPayload = [
|
|
'type' => 'imaging-ai-diagnosis',
|
|
'request_id' => $labReqId,
|
|
'invoice_id' => $invoiceId,
|
|
'lab_type' => $labType,
|
|
'study_name' => $studyName,
|
|
'title' => (string) ($analysis['title'] ?? $studyName),
|
|
'findings_html' => (string) ($analysis['findings_html'] ?? ''),
|
|
'impression_html' => (string) ($analysis['impression_html'] ?? ''),
|
|
'summary_text' => (string) ($analysis['summary_text'] ?? ''),
|
|
'provider' => (string) ($analysis['provider'] ?? 'azure-openai'),
|
|
'model' => (string) ($analysis['model'] ?? ''),
|
|
'source_files' => $sourceFiles,
|
|
'raw' => $analysis['raw'] ?? [],
|
|
];
|
|
|
|
$ocrText = trim(implode("\n\n", $ocrChunks));
|
|
$batchId = $this->insertLabAiBatch(
|
|
$invoiceId,
|
|
$labType,
|
|
(int) ($fileIds[0] ?? 0),
|
|
$studyName,
|
|
(string) ($analysis['model'] ?? ''),
|
|
$ocrText,
|
|
(string) json_encode($rawPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
|
|
);
|
|
|
|
if ($batchId <= 0) {
|
|
$this->updateImagingFileAiState($fileIds, 'failed', 'Unable to store AI diagnosis result', 1);
|
|
return $this->response->setJSON(['update' => 0, 'error_text' => 'Unable to store AI diagnosis result']);
|
|
}
|
|
|
|
$this->updateImagingFileAiState($fileIds, 'completed', (string) ($analysis['summary_text'] ?? 'AI draft ready'));
|
|
|
|
$this->auditClinicalUpdate('lab_ai_extraction_batches', 'created', $batchId, null, [
|
|
'invoice_id' => $invoiceId,
|
|
'lab_type' => $labType,
|
|
'request_id' => $labReqId,
|
|
'source_file_ids' => $fileIds,
|
|
'mode' => 'imaging-ai-diagnosis',
|
|
]);
|
|
|
|
$html = view('diagnosis/imaging_ai_result_modal', [
|
|
'batch_id' => $batchId,
|
|
'title' => (string) ($analysis['title'] ?? $studyName),
|
|
'study_name' => $studyName,
|
|
'findings_html' => (string) ($analysis['findings_html'] ?? ''),
|
|
'impression_html' => (string) ($analysis['impression_html'] ?? ''),
|
|
'summary_text' => (string) ($analysis['summary_text'] ?? ''),
|
|
'provider' => (string) ($analysis['provider'] ?? 'azure-openai'),
|
|
'model' => (string) ($analysis['model'] ?? ''),
|
|
]);
|
|
|
|
return $this->response->setJSON([
|
|
'update' => 1,
|
|
'error_text' => 'AI diagnosis draft is ready and stored.',
|
|
'batch_id' => $batchId,
|
|
'html' => $html,
|
|
'findings_html' => (string) ($analysis['findings_html'] ?? ''),
|
|
'impression_html' => (string) ($analysis['impression_html'] ?? ''),
|
|
'summary_text' => (string) ($analysis['summary_text'] ?? ''),
|
|
'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 '';
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
private function findImagingFilesForRequest(int $invoiceId, int $labType, int $labReqId): array
|
|
{
|
|
if ($invoiceId <= 0 || $labType <= 0 || ! $this->db->tableExists('file_upload_data')) {
|
|
return [];
|
|
}
|
|
|
|
$builder = $this->db->table('file_upload_data')
|
|
->where('isdelete', 0)
|
|
->where('charge_id', $invoiceId)
|
|
->where('charge_type', $labType);
|
|
|
|
$fields = $this->db->getFieldNames('file_upload_data');
|
|
if ($labReqId > 0 && in_array('repo_id', $fields, true)) {
|
|
$builder->where('repo_id', $labReqId);
|
|
}
|
|
|
|
return $builder->orderBy('id', 'DESC')->get()->getResultArray();
|
|
}
|
|
|
|
private function updateImagingFileAiState(array $fileIds, string $status, string $message = '', int $alertFlag = 0): void
|
|
{
|
|
$fileIds = array_values(array_filter(array_map('intval', $fileIds)));
|
|
if (empty($fileIds) || ! $this->db->tableExists('file_upload_data')) {
|
|
return;
|
|
}
|
|
|
|
$fields = $this->db->getFieldNames('file_upload_data');
|
|
$update = [];
|
|
if (in_array('ai_status', $fields, true)) {
|
|
$update['ai_status'] = $status;
|
|
}
|
|
if (in_array('ai_alert_text', $fields, true)) {
|
|
$update['ai_alert_text'] = $message !== '' ? mb_substr($message, 0, 255) : null;
|
|
}
|
|
if (in_array('ai_alert_flag', $fields, true)) {
|
|
$update['ai_alert_flag'] = $alertFlag > 0 ? 1 : 0;
|
|
}
|
|
|
|
if ($update === []) {
|
|
return;
|
|
}
|
|
|
|
$this->db->table('file_upload_data')->whereIn('id', $fileIds)->update($update);
|
|
}
|
|
|
|
private function buildUploadPublicUrl(string $fullPath): string
|
|
{
|
|
$normalized = str_replace('\\', '/', trim($fullPath));
|
|
if ($normalized === '') {
|
|
return '';
|
|
}
|
|
|
|
$publicPos = stripos($normalized, '/public/');
|
|
if ($publicPos !== false) {
|
|
return base_url(substr($normalized, $publicPos + 8));
|
|
}
|
|
|
|
$uploadsPos = stripos($normalized, 'uploads/');
|
|
if ($uploadsPos !== false) {
|
|
return base_url(substr($normalized, $uploadsPos));
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
private function buildVisionImagePayload(string $absolutePath, string $mime): string
|
|
{
|
|
$bytes = @file_get_contents($absolutePath);
|
|
if ($bytes === false || $bytes === '') {
|
|
return '';
|
|
}
|
|
|
|
return 'data:' . $mime . ';base64,' . base64_encode($bytes);
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $ocrChunks
|
|
* @param array<int, array<string, mixed>> $visionImages
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function generateImagingDiagnosisDraft(string $studyName, array $ocrChunks, array $visionImages): 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 [
|
|
'error' => 'Azure OpenAI settings are missing for imaging AI diagnosis.',
|
|
];
|
|
}
|
|
|
|
if ($apiVersion === '') {
|
|
$apiVersion = '2024-10-21';
|
|
}
|
|
|
|
if ($visionImages === [] && $ocrChunks === []) {
|
|
return [
|
|
'error' => 'No image or OCR context available for AI diagnosis.',
|
|
];
|
|
}
|
|
|
|
$content = [[
|
|
'type' => 'text',
|
|
'text' => "You are assisting a radiologist in drafting an imaging report for an Indian hospital.\n" .
|
|
"Study name: {$studyName}\n" .
|
|
"Review the uploaded imaging files and any OCR text.\n" .
|
|
"Return strict JSON only with keys: title, findings_html, impression_html, summary_text.\n" .
|
|
"Rules:\n" .
|
|
"1. findings_html must be concise HTML paragraphs or bullet list suitable for the report editor.\n" .
|
|
"2. impression_html must be concise HTML suitable for the Impression field.\n" .
|
|
"3. summary_text must be plain text under 220 characters.\n" .
|
|
"4. Do not include markdown fences.\n" .
|
|
"5. If image quality is poor, say that clearly in findings_html/impression_html.\n\n" .
|
|
($ocrChunks !== [] ? ("OCR_CONTEXT:\n" . mb_substr(implode("\n\n", $ocrChunks), 0, 14000)) : 'OCR_CONTEXT: none'),
|
|
]];
|
|
|
|
foreach ($visionImages as $imagePart) {
|
|
$content[] = $imagePart;
|
|
}
|
|
|
|
$url = $endpoint . '/openai/deployments/' . rawurlencode($deployment) . '/chat/completions?api-version=' . rawurlencode($apiVersion);
|
|
$attempts = $this->aiMaxRetries();
|
|
$lastError = 'AI diagnosis generation failed.';
|
|
|
|
for ($attempt = 1; $attempt <= $attempts; $attempt++) {
|
|
try {
|
|
$client = service('curlrequest', $this->httpOptions());
|
|
$response = $client->post($url, [
|
|
'headers' => [
|
|
'api-key' => $apiKey,
|
|
'Content-Type' => 'application/json',
|
|
],
|
|
'json' => [
|
|
'messages' => [[
|
|
'role' => 'user',
|
|
'content' => $content,
|
|
]],
|
|
'temperature' => 0.2,
|
|
'response_format' => ['type' => 'json_object'],
|
|
'max_tokens' => 2200,
|
|
],
|
|
]);
|
|
|
|
if ($response->getStatusCode() < 200 || $response->getStatusCode() >= 300) {
|
|
$lastError = 'Azure OpenAI HTTP ' . $response->getStatusCode();
|
|
continue;
|
|
}
|
|
|
|
$decoded = json_decode((string) $response->getBody(), true);
|
|
$contentText = trim((string) ($decoded['choices'][0]['message']['content'] ?? ''));
|
|
if ($contentText === '') {
|
|
$lastError = 'Azure OpenAI returned empty content.';
|
|
continue;
|
|
}
|
|
|
|
$json = json_decode($contentText, true);
|
|
if (! is_array($json)) {
|
|
$lastError = 'Azure OpenAI returned invalid JSON content.';
|
|
continue;
|
|
}
|
|
|
|
$findingsHtml = trim((string) ($json['findings_html'] ?? ''));
|
|
$impressionHtml = trim((string) ($json['impression_html'] ?? ''));
|
|
if ($findingsHtml === '' && $impressionHtml === '') {
|
|
$lastError = 'AI returned empty report sections.';
|
|
continue;
|
|
}
|
|
|
|
return [
|
|
'provider' => 'azure-openai',
|
|
'model' => (string) ($decoded['model'] ?? $deployment),
|
|
'title' => trim((string) ($json['title'] ?? $studyName)),
|
|
'findings_html' => $findingsHtml,
|
|
'impression_html' => $impressionHtml,
|
|
'summary_text' => trim((string) ($json['summary_text'] ?? 'AI diagnosis draft prepared.')),
|
|
'raw' => $decoded,
|
|
'error' => '',
|
|
];
|
|
} catch (\Throwable $e) {
|
|
$lastError = $e->getMessage() !== '' ? $e->getMessage() : 'Azure OpenAI request timeout/error.';
|
|
}
|
|
}
|
|
|
|
return [
|
|
'error' => $lastError,
|
|
];
|
|
}
|
|
|
|
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 parseLabValuesWithAi(string $ocrText, string $panelName): array
|
|
{
|
|
$external = $this->parseLabValuesWithExternalService($ocrText, $panelName);
|
|
if (! empty($external['values']) && is_array($external['values'])) {
|
|
return $external;
|
|
}
|
|
|
|
$azure = $this->parseLabValuesWithAzureOpenAi($ocrText, $panelName);
|
|
if (! empty($azure['values']) && is_array($azure['values'])) {
|
|
return $azure;
|
|
}
|
|
|
|
$externalError = trim((string) ($external['error'] ?? ''));
|
|
$azureError = trim((string) ($azure['error'] ?? ''));
|
|
return [
|
|
'provider' => 'none',
|
|
'model' => '',
|
|
'values' => [],
|
|
'raw' => [],
|
|
'error' => $externalError !== '' ? $externalError : $azureError,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function parseLabValuesWithExternalService(string $ocrText, string $panelName): array
|
|
{
|
|
$endpoint = rtrim($this->readSetting('DIAGNOSIS_AI_PARSE_ENDPOINT'), '/');
|
|
if ($endpoint === '') {
|
|
return [
|
|
'provider' => 'external',
|
|
'model' => '',
|
|
'values' => [],
|
|
'raw' => [],
|
|
'error' => '',
|
|
];
|
|
}
|
|
|
|
$token = trim($this->readSetting('DIAGNOSIS_AI_PARSE_TOKEN'));
|
|
$headers = [
|
|
'Content-Type' => 'application/json',
|
|
'Accept' => 'application/json',
|
|
];
|
|
if ($token !== '') {
|
|
$headers['Authorization'] = 'Bearer ' . $token;
|
|
}
|
|
|
|
$attempts = $this->aiMaxRetries();
|
|
$lastError = 'External AI service request failed.';
|
|
|
|
for ($attempt = 1; $attempt <= $attempts; $attempt++) {
|
|
try {
|
|
$client = service('curlrequest', $this->httpOptions());
|
|
$response = $client->post($endpoint, [
|
|
'headers' => $headers,
|
|
'json' => [
|
|
'ocr_text' => mb_substr($ocrText, 0, 20000),
|
|
'panel_name' => $panelName,
|
|
],
|
|
]);
|
|
|
|
if ($response->getStatusCode() >= 200 && $response->getStatusCode() < 300) {
|
|
$decoded = json_decode((string) $response->getBody(), true);
|
|
if (! is_array($decoded)) {
|
|
$lastError = 'External AI response is not valid JSON.';
|
|
continue;
|
|
}
|
|
|
|
$values = $decoded['values'] ?? null;
|
|
if (! is_array($values)) {
|
|
$lastError = trim((string) ($decoded['error'] ?? 'External AI response missing values array.'));
|
|
continue;
|
|
}
|
|
|
|
return [
|
|
'provider' => (string) ($decoded['provider'] ?? 'external'),
|
|
'model' => (string) ($decoded['model'] ?? 'external-ai'),
|
|
'values' => $values,
|
|
'raw' => $decoded,
|
|
'error' => '',
|
|
];
|
|
}
|
|
|
|
$lastError = 'External AI HTTP ' . $response->getStatusCode();
|
|
} catch (\Throwable $e) {
|
|
$lastError = $e->getMessage() !== '' ? $e->getMessage() : 'External AI request timeout/error.';
|
|
}
|
|
}
|
|
|
|
return [
|
|
'provider' => 'external',
|
|
'model' => '',
|
|
'values' => [],
|
|
'raw' => [],
|
|
'error' => $lastError,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @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 [
|
|
'provider' => 'azure-openai',
|
|
'model' => '',
|
|
'values' => [],
|
|
'raw' => [],
|
|
'error' => 'Azure OpenAI settings are missing.',
|
|
];
|
|
}
|
|
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);
|
|
|
|
$attempts = $this->aiMaxRetries();
|
|
$lastError = 'Azure AI parsing failed.';
|
|
|
|
for ($attempt = 1; $attempt <= $attempts; $attempt++) {
|
|
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) {
|
|
$lastError = 'Azure OpenAI HTTP ' . $response->getStatusCode();
|
|
continue;
|
|
}
|
|
|
|
$decoded = json_decode((string) $response->getBody(), true);
|
|
$content = trim((string) ($decoded['choices'][0]['message']['content'] ?? ''));
|
|
if ($content === '') {
|
|
$lastError = 'Azure OpenAI returned empty content.';
|
|
continue;
|
|
}
|
|
|
|
$json = json_decode($content, true);
|
|
if (! is_array($json)) {
|
|
$lastError = 'Azure OpenAI returned invalid JSON content.';
|
|
continue;
|
|
}
|
|
|
|
return [
|
|
'provider' => 'azure-openai',
|
|
'model' => (string) ($decoded['model'] ?? $deployment),
|
|
'values' => is_array($json['values'] ?? null) ? $json['values'] : [],
|
|
'raw' => $decoded,
|
|
'error' => '',
|
|
];
|
|
} catch (\Throwable $e) {
|
|
$lastError = $e->getMessage() !== '' ? $e->getMessage() : 'Azure OpenAI request timeout/error.';
|
|
}
|
|
}
|
|
|
|
return [
|
|
'provider' => 'azure-openai',
|
|
'model' => '',
|
|
'values' => [],
|
|
'raw' => [],
|
|
'error' => $lastError,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function httpOptions(): array
|
|
{
|
|
$timeout = $this->aiTimeoutSeconds();
|
|
$options = [
|
|
'timeout' => $timeout,
|
|
'http_errors' => false,
|
|
];
|
|
|
|
if (defined('ENVIRONMENT') && in_array((string) ENVIRONMENT, ['development', 'testing'], true)) {
|
|
$options['verify'] = false;
|
|
}
|
|
|
|
return $options;
|
|
}
|
|
|
|
private function aiTimeoutSeconds(): int
|
|
{
|
|
$timeout = (int) $this->readSetting('DIAGNOSIS_AI_TIMEOUT_SECONDS');
|
|
if ($timeout <= 0) {
|
|
$timeout = 45;
|
|
}
|
|
|
|
if ($timeout > 180) {
|
|
$timeout = 180;
|
|
}
|
|
|
|
return $timeout;
|
|
}
|
|
|
|
private function aiMaxRetries(): int
|
|
{
|
|
$retries = (int) $this->readSetting('DIAGNOSIS_AI_RETRY_ATTEMPTS');
|
|
if ($retries <= 0) {
|
|
$retries = 2;
|
|
}
|
|
|
|
if ($retries > 5) {
|
|
$retries = 5;
|
|
}
|
|
|
|
return $retries;
|
|
}
|
|
|
|
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);
|
|
}
|
|
} |