From dd14d2ddf571113e51bf83e92bbe8c32b04d4af2 Mon Sep 17 00:00:00 2001 From: Dwi Swandhana Date: Wed, 13 May 2026 19:05:23 +0700 Subject: [PATCH] update --- htdocs/app/Services/AstmMessageService.php | 1035 +++++++++++--------- 1 file changed, 573 insertions(+), 462 deletions(-) diff --git a/htdocs/app/Services/AstmMessageService.php b/htdocs/app/Services/AstmMessageService.php index 65252650..df1b2beb 100644 --- a/htdocs/app/Services/AstmMessageService.php +++ b/htdocs/app/Services/AstmMessageService.php @@ -147,6 +147,569 @@ class AstmMessageService /** * Proses data ASTM Response */ + protected function astmToDateTime(?string $value): ?string + { + if (!$value) return null; + + if (preg_match('/(\d{14})/', $value, $m)) { + return Carbon::createFromFormat( + 'YmdHis', + $m[1] + )->format('Y-m-d H:i:s'); + } + + return null; + } + protected function splitBDAstmMessages(string $raw): array + { + // bersihkan karakter kontrol + $clean = preg_replace('/[\x02\x03\x04\x17]/', '', $raw); + + $lines = preg_split("/\r\n|\n|\r/", $clean); + + $messages = []; + $current = []; + + foreach ($lines as $line) { + + // Ketemu H baru → simpan pesan sebelumnya + if (str_starts_with($line, 'H|') && !empty($current)) { + $messages[] = implode("\n", $current); + $current = []; + } + + $current[] = $line; + + // Akhir pesan + if (str_starts_with($line, 'L|')) { + $messages[] = implode("\n", $current); + $current = []; + } + } + + // sisa buffer + if (!empty($current)) { + $messages[] = implode("\n", $current); + } + + return $messages; + } + protected function reassembleAstmFrames(string $raw): string + { + $buffer = ''; + $frames = explode("\x02", $raw); + + foreach ($frames as $frame) { + if ($frame === '') { + continue; + } + + $content = $frame; + $etxPos = strpos($content, "\x03"); + $etbPos = strpos($content, "\x17"); + + if ($etxPos !== false && ($etbPos === false || $etxPos < $etbPos)) { + $content = substr($content, 0, $etxPos); + } elseif ($etbPos !== false) { + $content = substr($content, 0, $etbPos); + } + + if ($content !== '' && ctype_digit($content[0])) { + $content = substr($content, 1); + } + + $buffer .= $content; + } + + return trim($buffer) !== '' ? trim($buffer) : trim($raw); + } + protected function normalizeBDResult(?string $value): ?string + { + $value = trim((string) $value); + if ($value === '') { + return null; + } + + if (stripos($value, 'NEGATIVE') !== false) { + return 'NEGATIVE'; + } + + if (stripos($value, 'POSITIVE') !== false) { + return 'POSITIVE'; + } + + return explode('^', $value)[0] ?: null; + } + protected function processBDAstmResponse(string $raw, $data): bool + { + try { + // ========================= + // PREPARE + // ========================= + $accnumber= ''; + $resultDateTime = null; + $bdResult = null; + $rawClean = preg_replace('/[\x02\x03\x04\x17]/', '', $this->reassembleAstmFrames($raw)); + $lines = preg_split("/\r\n|\n|\r/", $rawClean); + + $parsed = [ + 'header' => [], + 'patient'=> [], + 'order' => [], + 'result' => [], + 'instrument' => [] + ]; + + // ========================= + // PARSE LOOP + // ========================= + foreach ($lines as $line) { + + if (str_starts_with($line, 'H|')) { + $f = explode('|', $line); + $resultDateTime = $this->astmToDateTime($f[13] ?? null); + + $parsed['header'] = [ + 'sender_name' => $data->alat ?? ($f[4] ?? 'BD Instrument'), + 'version_number' => $f[12] ?? 'V1.0', + 'message_datetime' => $this->astmToDateTime($f[13] ?? null) + ]; + } elseif (str_starts_with($line, 'P|')) { + $f = explode('|', $line); + $name = explode(' ', $f[5] ?? ''); + + $parsed['patient'] = [ + 'patient_id' => $f[3] ?? null, + 'patient_name_last' => $name[0] ?? null, + 'patient_name_first'=> $name[1] ?? null, + 'patient_dob' => $this->astmToDateTime($f[7] ?? null), + 'patient_sex' => $f[8] ?? null, + 'patient_phone' => $f[13] ?? null, + 'address_street' => $f[11] ?? null, + 'hospital_client' => $f[29] ?? null, + ]; + } elseif (str_starts_with($line, 'O|')) { + $f = explode('|', $line); + $test = explode('^', $f[4] ?? ''); + $accnumber = $f[2] ?? null; + $parsed['order'] = [ + 'accession_number' => $f[2] ?? null, + 'test_id' => $test[3] ?? null, + 'specimen_type' => $f[15] ?? null, + 'body_site' => $f[16] ?? null, + 'test_start_datetime'=> $this->astmToDateTime($f[7] ?? null), + ]; + } elseif (str_starts_with($line, 'R|')) { + $f = explode('|', $line); + $test = explode('^', $f[2] ?? ''); + $bdResult = $this->normalizeBDResult($f[3] ?? null) ?? $bdResult; + $resultDateTime = $this->astmToDateTime($f[12] ?? null) + ?? $this->astmToDateTime($f[11] ?? null) + ?? $resultDateTime; + + // Instrument detail di akhir baris + preg_match('/(MGIT960|BACTECFX)[^|]*/', $line, $inst); + + $parsed['result'] = [ + 'organism' => $test[2] ?? null, + 'test_status' => $bdResult ?? explode('^', $f[3] ?? '')[0], + 'result_status_datetime' => $resultDateTime, + ]; + + $parsed['instrument'] = [ + 'instrument_type' => str_contains($line, 'MGIT960') ? 'MGIT960' : 'BACTECFX', + 'instrument_number' => $inst[0] ?? null, + ]; + } else { + $cekline = explode('|F|', $line); + if (isset($cekline[1])){ + $f = explode('|', $cekline[1]); + $resultDateTime2 = $this->astmToDateTime($f[3] ?? null); + if ($resultDateTime2){ + $parsed['result'] = array_merge($parsed['result'], [ + 'result_status_datetime' => $resultDateTime2, + ]); + $parsed['instrument'] = array_merge($parsed['instrument'], [ + 'instrument_type' => $f[4] ?? null, + ]); + } + + } + } + } + + // ========================= + // SAVE TO DATABASE + // ========================= + $resultSample = new ResultSample(); + $resultSample->fill(array_merge( + $parsed['header'], + $parsed['patient'], + $parsed['order'], + $parsed['result'], + $parsed['instrument'] + )); + + $resultSample->additional_result = [ + 'raw_astm' => $raw, + 'source' => 'BD' + ]; + + $resultSample->save(); + if ($accnumber != '' AND isset($resultDateTime)){ + KomponenJawaban::updateOrCreate( + [ + 'accnumber' => $accnumber, + 'komponen' => 'bd_result_date', + 'isidata' => $resultDateTime, + ], + [ + 'template' => 'Kultur', + 'created_by' => 'BD' + ] + ); + } + if ($accnumber != '' && $bdResult){ + KomponenJawaban::updateOrCreate( + [ + 'accnumber' => $accnumber, + 'komponen' => 'bd_result', + 'isidata' => $bdResult, + ], + [ + 'template' => 'Kultur', + 'created_by' => 'BD' + ] + ); + } + //Log::info("Data BD ASTM Berhasil di Parse dan di simpan ", $resultSample->toArray()); + + + return true; + + } catch (\Throwable $e) { + Log::error('BD ASTM parse error', [ + 'msg' => $e->getMessage() + ]); + return false; + } + } + protected function splitHl7LikeMessages(string $raw): array + { + $normalized = str_replace(["\r\n", "\r"], "\n", $raw); + $normalized = preg_replace('/[\x00-\x08\x0B-\x1F\x7F]/', '', $normalized); + $normalized = preg_replace('/(?=MSH\|)/', "\n", $normalized); + $parts = preg_split('/\n\s*\n+/', trim($normalized)) ?: []; + + $messages = []; + foreach ($parts as $part) { + $part = trim($part); + if ($part === '' || !str_contains($part, 'MSH|')) { + continue; + } + + $start = strpos($part, 'MSH|'); + if ($start !== false) { + $part = substr($part, $start); + } + + $messages[] = trim($part); + } + + return $messages; + } + protected function formatLocalInstrumentText(array $parsed, string $alat, ?string $accessionNumber, ?string $messageDateTime): string + { + $msh = $parsed['MSH'][0] ?? []; + $pid = $parsed['PID'][0] ?? []; + $obr = $parsed['OBR'][0] ?? []; + $obx = $parsed['OBX'][0] ?? []; + $nte = $parsed['NTE'][0] ?? []; + $qpd = $parsed['QPD'][0] ?? []; + $qid = $parsed['QID'][0] ?? []; + $spm = $parsed['SPM'][0] ?? []; + + $messageType = $msh[8] ?? '-'; + $patientName = $pid[3] ?? $pid[5] ?? '-'; + $patientName = trim((string) $patientName, "\" \t\n\r\0\x0B"); + $testId = $obr[4] ?? ($qpd[1] ?? '-'); + $receivedAt = $messageDateTime ?? '-'; + $escape = static fn ($value) => e($value ?: '-'); + + if (str_starts_with($alat, 'MYLA')) { + $organism = $obx[5] ?? '-'; + $abnormalFlag = $obx[8] ?? '-'; + $confidence = $obx[9] ?? '-'; + $status = $obx[11] ?? '-'; + $operator = $obx[16] ?? '-'; + $resultAt = $this->astmToDateTime($obx[14] ?? null) ?? '-'; + $comment = $nte[3] ?? '-'; + $specimen = $spm[3] ?? '-'; + + return + '
'. + '
HASIL ALAT MYLA
'. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + '
No. Foto: '.$escape($accessionNumber).'
No. Spesimen: '.$escape($specimen).'
Pasien: '.$escape($patientName).'
Jenis Pesan: '.$escape($messageType).'
Test: '.$escape($testId).'
Hasil: '.$escape($organism).'
Abnormal Flag: '.$escape($abnormalFlag).'
Confidence: '.$escape($confidence).'
Status: '.$escape($status).'
Operator: '.$escape($operator).'
Komentar: '.$escape($comment).'
Tanggal Hasil: '.$escape($resultAt).'
Tanggal Diterima LIS: '.$escape($receivedAt).'
'. + '
'; + } + + $orderAt = $this->astmToDateTime($parsed['ORC'][0][8] ?? null) ?? '-'; + $queryId = $qid[1] ?? ($msh[9] ?? '-'); + + return + '
'. + '
HASIL ALAT GENEXPERT
'. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + ''. + '
No. Foto: '.$escape($accessionNumber).'
Pasien: '.$escape($patientName).'
Jenis Pesan: '.$escape($messageType).'
Test/Assay: '.$escape($testId).'
Order/Run ID: '.$escape($queryId).'
Tanggal Order: '.$escape($orderAt).'
Tanggal Diterima LIS: '.$escape($receivedAt).'
Catatan: Payload alat disimpan sebagai raw untuk ditinjau analis.
'. + '
'; + } + protected function saveInstrumentRawResult(string $raw, string $alat): bool + { + try { + $segments = preg_split("/\r\n|\n|\r/", trim($raw)) ?: []; + $parsed = []; + + foreach ($segments as $segment) { + $segment = trim($segment); + if ($segment === '' || !str_contains($segment, '|')) { + continue; + } + + $fields = explode('|', $segment); + $segmentType = strtoupper(trim($fields[0] ?? '')); + if ($segmentType !== '') { + $parsed[$segmentType][] = $fields; + } + } + + $msh = $parsed['MSH'][0] ?? []; + $pid = $parsed['PID'][0] ?? []; + $obr = $parsed['OBR'][0] ?? []; + $obx = $parsed['OBX'][0] ?? []; + $spm = $parsed['SPM'][0] ?? []; + $nte = $parsed['NTE'][0] ?? []; + $qpd = $parsed['QPD'][0] ?? []; + $qid = $parsed['QID'][0] ?? []; + + $messageDateTime = $this->astmToDateTime($msh[6] ?? null); + $accessionNumber = $spm[3] ?? $pid[2] ?? $pid[3] ?? $qpd[3] ?? $qid[1] ?? null; + if ($accessionNumber) { + $accessionNumber = trim((string) $accessionNumber, "\"@ \t\n\r\0\x0B"); + } + + $patientName = $pid[3] ?? $pid[5] ?? null; + $patientName = $patientName ? trim((string) $patientName, "\" \t\n\r\0\x0B") : null; + $organism = $obx[5] ?? null; + $resultSample = new ResultSample(); + $resultSample->sender_name = $alat; + $resultSample->version_number = $msh[11] ?? null; + $resultSample->message_datetime = $messageDateTime; + $resultSample->patient_id = $accessionNumber; + $resultSample->patient_name_last = $patientName; + $resultSample->accession_number = $accessionNumber; + $resultSample->test_id = $obr[4] ?? ($qpd[1] ?? null); + $resultSample->result_type_code = $obx[3] ?? ($msh[8] ?? null); + $resultSample->organism = $organism; + $resultSample->test_status = $obx[11] ?? null; + $resultSample->result_status_datetime = $this->astmToDateTime($obx[14] ?? null); + $resultSample->instrument_type = $alat; + $resultSample->protocol_name = $obr[4] ?? null; + $resultSample->result_data = $obx ? json_encode($obx) : null; + $resultSample->additional_result = [ + 'source' => 'local_instrument_fallback', + 'raw_astm' => $raw, + 'segments' => array_keys($parsed), + 'nte' => $nte, + 'qpd' => $qpd, + 'qid' => $qid, + ]; + $resultSample->save(); + + if ($accessionNumber) { + $teksHasil = $this->formatLocalInstrumentText($parsed, $alat, $accessionNumber, $messageDateTime); + if ($organism !== '') { + $getnama = explode('^', $organism); + $organism = $getnama[1] ?? $organism; + $sudah = Organisms::where('name', $organism)->count(); + if ($sudah == 0) { + Organisms::create([ + 'kelompok' => 'biakankultur', + 'name' => $organism, + 'category' => $alat, + ]); + } + KomponenJawaban::updateOrCreate( + [ + 'accnumber' => $accessionNumber, + 'komponen' => 'id_bakteri01', + 'isidata' => $organism, + ], + [ + 'template' => 'all', + 'created_by' => $alat + ] + ); + if ($alat == 'MYLA-10.10.120.89') { + $status = 'Data Malditof di Terima'; + } else { + $status = 'Data Genexpert di Terima'; + } + Periksa::where('nofoto', $accessionNumber)->whereNotIn('status', ['Selesai', 'Arsip', 'Dibatalkan (Arsip)', 'Batal'])->update([ + 'status' => $status, + ]); + } + + Riwayat::create([ + 'nofoto' => $accessionNumber, + 'jawaban' => $teksHasil, + 'inputor' => $alat, + 'keterangan' => $messageDateTime ?: date('Y-m-d H:i:s'), + 'verifikasi' => '', + ]); + } + + return true; + } catch (\Throwable $e) { + Log::error('Local instrument fallback parse error', [ + 'alat' => $alat, + 'msg' => $e->getMessage(), + ]); + + return false; + } + } + public function processAstmMessagesgenExpert($response, $alat) + { + // 1. Normalisasi karakter pembatas baris (\r\n, \r, atau \n diubah jadi \n) + $normalizedData = str_replace(["\r\n", "\r"], "\n", $response); + $segments = explode("\n", $normalizedData); + + $patient_id = ''; + $patient_name = ''; + $accession_number = ''; // NoReg + $hasil_list = []; + + // 2. Looping setiap baris data ASTM + foreach ($segments as $rsegmen) { + $cekdata = explode('|', $rsegmen); + if (empty($cekdata[0])) continue; + + $recordType = preg_replace("/[^a-zA-Z]/", "", $cekdata[0]); + + // Ekstrak Data Pasien (P) + if ($recordType === 'P') { + $patient_id = $cekdata[3] ?? ($cekdata[4] ?? ''); + if (isset($cekdata[5])) { + $patient_name = trim(str_replace('^', ' ', $cekdata[5])); + } + } + + // Ekstrak Nomor Order (O) + if ($recordType === 'O') { + $accession_number = $cekdata[2] ?? ''; + } + + // Ekstrak Hasil Analisa (R) + if ($recordType === 'R') { + if (isset($cekdata[2]) && isset($cekdata[3])) { + $test_info = $cekdata[2]; // Format contoh: ^^^MTB-RIF_ULTRA 2^^^MTB^ + $result_val = trim(str_replace('^', '', $cekdata[3])); // Contoh: DETECTED / PASS / INVALID + + // [KUNCI]: Buang baris yang berisi kurva angka (Ct / EndPt) agar tidak nyampah dan kepanjangan + if (strpos($rsegmen, 'Ct|') === false && strpos($rsegmen, 'EndPt|') === false && $result_val !== '') { + + // Ambil nama target spesifik (misal 'MTB' atau 'RIF Resistance' atau 'SPC') + $target_match = explode('^^^', $test_info); + $target_name = ''; + if (count($target_match) > 1) { + $target_name = trim(str_replace('^', '', end($target_match))); + } + + // Simpan ke array jika valid + if ($target_name && $result_val) { + $hasil_list[] = $target_name . ": " . $result_val; + } + } + } + } + } + + if (empty($accession_number)) { + Log::warning("GeneXpert ASTM: Accession number (NoReg) tidak ditemukan.", ['alat' => $alat]); + return false; + } + + // 3. Gabungkan hasil dari array menjadi 1 string ("MTB: DETECTED | RIF Resistance: NOT DETECTED") + $kesimpulan = implode(" | ", $hasil_list); + + // [PENTING] Potong text agar TIDAK ERROR di Postgres (Varchar 100) + $kesimpulan_safe = substr($kesimpulan, 0, 100); + + try { + // Update status tabel Periksa + Periksa::where('nofoto', $accession_number) + ->whereNotIn('status', ['Selesai', 'Arsip', 'Dibatalkan (Arsip)', 'Batal']) + ->update([ + 'status' => 'Data GeneXpert di Terima' + ]); + + // Update atau Insert ke Komponen Jawaban + if (!empty($kesimpulan)) { + KomponenJawaban::updateOrCreate( + [ + 'accnumber' => $accession_number, + 'komponen' => 'genexpert_result', // Ganti key ini sesuai dengan penamaan di frontend Anda + ], + [ + 'isidata' => substr($kesimpulan, 0, 500), // Di tabel ini biasanya lebih panjang + 'template' => 'GeneXpert', + 'created_by' => 'GeneXpert Middleware' + ] + ); + } + + // Opsional: Simpan log sejarah hasil di ResultSample seperti alat Vitek/BD + $resultSample = ResultSample::firstOrNew(['accession_number' => $accession_number]); + $resultSample->sender_name = $alat; + $resultSample->patient_id = substr($patient_id, 0, 50); + $resultSample->patient_name_first = substr($patient_name, 0, 100); + $resultSample->organism = $kesimpulan_safe; // Limit varchar(100) ditaati di sini + $resultSample->additional_result = json_encode($hasil_list); + $resultSample->message_datetime = date('Y-m-d H:i:s'); + $resultSample->save(); + + Log::info("GeneXpert Result Disimpan", ["NoReg" => $accession_number, "Hasil" => $kesimpulan_safe]); + + // Kembalikan TRUE agar update 'processed' => 1 di method processAstmMessagesLokal Anda tereksekusi + return true; + + } catch (\Exception $e) { + Log::error("GeneXpert DB Save Error: " . $e->getMessage()); + return false; + } + } public function processAstmResponse($response, $alat) { // Bersihkan data $astmData = $this->cleanString($response); @@ -648,459 +1211,6 @@ class AstmMessageService } } } - protected function astmToDateTime(?string $value): ?string - { - if (!$value) return null; - - if (preg_match('/(\d{14})/', $value, $m)) { - return Carbon::createFromFormat( - 'YmdHis', - $m[1] - )->format('Y-m-d H:i:s'); - } - - return null; - } - protected function splitBDAstmMessages(string $raw): array - { - // bersihkan karakter kontrol - $clean = preg_replace('/[\x02\x03\x04\x17]/', '', $raw); - - $lines = preg_split("/\r\n|\n|\r/", $clean); - - $messages = []; - $current = []; - - foreach ($lines as $line) { - - // Ketemu H baru → simpan pesan sebelumnya - if (str_starts_with($line, 'H|') && !empty($current)) { - $messages[] = implode("\n", $current); - $current = []; - } - - $current[] = $line; - - // Akhir pesan - if (str_starts_with($line, 'L|')) { - $messages[] = implode("\n", $current); - $current = []; - } - } - - // sisa buffer - if (!empty($current)) { - $messages[] = implode("\n", $current); - } - - return $messages; - } - protected function reassembleAstmFrames(string $raw): string - { - $buffer = ''; - $frames = explode("\x02", $raw); - - foreach ($frames as $frame) { - if ($frame === '') { - continue; - } - - $content = $frame; - $etxPos = strpos($content, "\x03"); - $etbPos = strpos($content, "\x17"); - - if ($etxPos !== false && ($etbPos === false || $etxPos < $etbPos)) { - $content = substr($content, 0, $etxPos); - } elseif ($etbPos !== false) { - $content = substr($content, 0, $etbPos); - } - - if ($content !== '' && ctype_digit($content[0])) { - $content = substr($content, 1); - } - - $buffer .= $content; - } - - return trim($buffer) !== '' ? trim($buffer) : trim($raw); - } - protected function normalizeBDResult(?string $value): ?string - { - $value = trim((string) $value); - if ($value === '') { - return null; - } - - if (stripos($value, 'NEGATIVE') !== false) { - return 'NEGATIVE'; - } - - if (stripos($value, 'POSITIVE') !== false) { - return 'POSITIVE'; - } - - return explode('^', $value)[0] ?: null; - } - protected function processBDAstmResponse(string $raw, $data): bool - { - try { - // ========================= - // PREPARE - // ========================= - $accnumber= ''; - $resultDateTime = null; - $bdResult = null; - $rawClean = preg_replace('/[\x02\x03\x04\x17]/', '', $this->reassembleAstmFrames($raw)); - $lines = preg_split("/\r\n|\n|\r/", $rawClean); - - $parsed = [ - 'header' => [], - 'patient'=> [], - 'order' => [], - 'result' => [], - 'instrument' => [] - ]; - - // ========================= - // PARSE LOOP - // ========================= - foreach ($lines as $line) { - - if (str_starts_with($line, 'H|')) { - $f = explode('|', $line); - $resultDateTime = $this->astmToDateTime($f[13] ?? null); - - $parsed['header'] = [ - 'sender_name' => $data->alat ?? ($f[4] ?? 'BD Instrument'), - 'version_number' => $f[12] ?? 'V1.0', - 'message_datetime' => $this->astmToDateTime($f[13] ?? null) - ]; - } elseif (str_starts_with($line, 'P|')) { - $f = explode('|', $line); - $name = explode(' ', $f[5] ?? ''); - - $parsed['patient'] = [ - 'patient_id' => $f[3] ?? null, - 'patient_name_last' => $name[0] ?? null, - 'patient_name_first'=> $name[1] ?? null, - 'patient_dob' => $this->astmToDateTime($f[7] ?? null), - 'patient_sex' => $f[8] ?? null, - 'patient_phone' => $f[13] ?? null, - 'address_street' => $f[11] ?? null, - 'hospital_client' => $f[29] ?? null, - ]; - } elseif (str_starts_with($line, 'O|')) { - $f = explode('|', $line); - $test = explode('^', $f[4] ?? ''); - $accnumber = $f[2] ?? null; - $parsed['order'] = [ - 'accession_number' => $f[2] ?? null, - 'test_id' => $test[3] ?? null, - 'specimen_type' => $f[15] ?? null, - 'body_site' => $f[16] ?? null, - 'test_start_datetime'=> $this->astmToDateTime($f[7] ?? null), - ]; - } elseif (str_starts_with($line, 'R|')) { - $f = explode('|', $line); - $test = explode('^', $f[2] ?? ''); - $bdResult = $this->normalizeBDResult($f[3] ?? null) ?? $bdResult; - $resultDateTime = $this->astmToDateTime($f[12] ?? null) - ?? $this->astmToDateTime($f[11] ?? null) - ?? $resultDateTime; - - // Instrument detail di akhir baris - preg_match('/(MGIT960|BACTECFX)[^|]*/', $line, $inst); - - $parsed['result'] = [ - 'organism' => $test[2] ?? null, - 'test_status' => $bdResult ?? explode('^', $f[3] ?? '')[0], - 'result_status_datetime' => $resultDateTime, - ]; - - $parsed['instrument'] = [ - 'instrument_type' => str_contains($line, 'MGIT960') ? 'MGIT960' : 'BACTECFX', - 'instrument_number' => $inst[0] ?? null, - ]; - } else { - $cekline = explode('|F|', $line); - if (isset($cekline[1])){ - $f = explode('|', $cekline[1]); - $resultDateTime2 = $this->astmToDateTime($f[3] ?? null); - if ($resultDateTime2){ - $parsed['result'] = array_merge($parsed['result'], [ - 'result_status_datetime' => $resultDateTime2, - ]); - $parsed['instrument'] = array_merge($parsed['instrument'], [ - 'instrument_type' => $f[4] ?? null, - ]); - } - - } - } - } - - // ========================= - // SAVE TO DATABASE - // ========================= - $resultSample = new ResultSample(); - $resultSample->fill(array_merge( - $parsed['header'], - $parsed['patient'], - $parsed['order'], - $parsed['result'], - $parsed['instrument'] - )); - - $resultSample->additional_result = [ - 'raw_astm' => $raw, - 'source' => 'BD' - ]; - - $resultSample->save(); - if ($accnumber != '' AND isset($resultDateTime)){ - KomponenJawaban::updateOrCreate( - [ - 'accnumber' => $accnumber, - 'komponen' => 'bd_result_date', - 'isidata' => $resultDateTime, - ], - [ - 'template' => 'Kultur', - 'created_by' => 'BD' - ] - ); - } - if ($accnumber != '' && $bdResult){ - KomponenJawaban::updateOrCreate( - [ - 'accnumber' => $accnumber, - 'komponen' => 'bd_result', - 'isidata' => $bdResult, - ], - [ - 'template' => 'Kultur', - 'created_by' => 'BD' - ] - ); - } - //Log::info("Data BD ASTM Berhasil di Parse dan di simpan ", $resultSample->toArray()); - - - return true; - - } catch (\Throwable $e) { - Log::error('BD ASTM parse error', [ - 'msg' => $e->getMessage() - ]); - return false; - } - } - protected function splitHl7LikeMessages(string $raw): array - { - $normalized = str_replace(["\r\n", "\r"], "\n", $raw); - $normalized = preg_replace('/[\x00-\x08\x0B-\x1F\x7F]/', '', $normalized); - $normalized = preg_replace('/(?=MSH\|)/', "\n", $normalized); - $parts = preg_split('/\n\s*\n+/', trim($normalized)) ?: []; - - $messages = []; - foreach ($parts as $part) { - $part = trim($part); - if ($part === '' || !str_contains($part, 'MSH|')) { - continue; - } - - $start = strpos($part, 'MSH|'); - if ($start !== false) { - $part = substr($part, $start); - } - - $messages[] = trim($part); - } - - return $messages; - } - protected function formatLocalInstrumentText(array $parsed, string $alat, ?string $accessionNumber, ?string $messageDateTime): string - { - $msh = $parsed['MSH'][0] ?? []; - $pid = $parsed['PID'][0] ?? []; - $obr = $parsed['OBR'][0] ?? []; - $obx = $parsed['OBX'][0] ?? []; - $nte = $parsed['NTE'][0] ?? []; - $qpd = $parsed['QPD'][0] ?? []; - $qid = $parsed['QID'][0] ?? []; - $spm = $parsed['SPM'][0] ?? []; - - $messageType = $msh[8] ?? '-'; - $patientName = $pid[3] ?? $pid[5] ?? '-'; - $patientName = trim((string) $patientName, "\" \t\n\r\0\x0B"); - $testId = $obr[4] ?? ($qpd[1] ?? '-'); - $receivedAt = $messageDateTime ?? '-'; - $escape = static fn ($value) => e($value ?: '-'); - - if (str_starts_with($alat, 'MYLA')) { - $organism = $obx[5] ?? '-'; - $abnormalFlag = $obx[8] ?? '-'; - $confidence = $obx[9] ?? '-'; - $status = $obx[11] ?? '-'; - $operator = $obx[16] ?? '-'; - $resultAt = $this->astmToDateTime($obx[14] ?? null) ?? '-'; - $comment = $nte[3] ?? '-'; - $specimen = $spm[3] ?? '-'; - - return - '
'. - '
HASIL ALAT MYLA
'. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - '
No. Foto: '.$escape($accessionNumber).'
No. Spesimen: '.$escape($specimen).'
Pasien: '.$escape($patientName).'
Jenis Pesan: '.$escape($messageType).'
Test: '.$escape($testId).'
Hasil: '.$escape($organism).'
Abnormal Flag: '.$escape($abnormalFlag).'
Confidence: '.$escape($confidence).'
Status: '.$escape($status).'
Operator: '.$escape($operator).'
Komentar: '.$escape($comment).'
Tanggal Hasil: '.$escape($resultAt).'
Tanggal Diterima LIS: '.$escape($receivedAt).'
'. - '
'; - } - - $orderAt = $this->astmToDateTime($parsed['ORC'][0][8] ?? null) ?? '-'; - $queryId = $qid[1] ?? ($msh[9] ?? '-'); - - return - '
'. - '
HASIL ALAT GENEXPERT
'. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - ''. - '
No. Foto: '.$escape($accessionNumber).'
Pasien: '.$escape($patientName).'
Jenis Pesan: '.$escape($messageType).'
Test/Assay: '.$escape($testId).'
Order/Run ID: '.$escape($queryId).'
Tanggal Order: '.$escape($orderAt).'
Tanggal Diterima LIS: '.$escape($receivedAt).'
Catatan: Payload alat disimpan sebagai raw untuk ditinjau analis.
'. - '
'; - } - protected function saveInstrumentRawResult(string $raw, string $alat): bool - { - try { - $segments = preg_split("/\r\n|\n|\r/", trim($raw)) ?: []; - $parsed = []; - - foreach ($segments as $segment) { - $segment = trim($segment); - if ($segment === '' || !str_contains($segment, '|')) { - continue; - } - - $fields = explode('|', $segment); - $segmentType = strtoupper(trim($fields[0] ?? '')); - if ($segmentType !== '') { - $parsed[$segmentType][] = $fields; - } - } - - $msh = $parsed['MSH'][0] ?? []; - $pid = $parsed['PID'][0] ?? []; - $obr = $parsed['OBR'][0] ?? []; - $obx = $parsed['OBX'][0] ?? []; - $spm = $parsed['SPM'][0] ?? []; - $nte = $parsed['NTE'][0] ?? []; - $qpd = $parsed['QPD'][0] ?? []; - $qid = $parsed['QID'][0] ?? []; - - $messageDateTime = $this->astmToDateTime($msh[6] ?? null); - $accessionNumber = $spm[3] ?? $pid[2] ?? $pid[3] ?? $qpd[3] ?? $qid[1] ?? null; - if ($accessionNumber) { - $accessionNumber = trim((string) $accessionNumber, "\"@ \t\n\r\0\x0B"); - } - - $patientName = $pid[3] ?? $pid[5] ?? null; - $patientName = $patientName ? trim((string) $patientName, "\" \t\n\r\0\x0B") : null; - $organism = $obx[5] ?? null; - $resultSample = new ResultSample(); - $resultSample->sender_name = $alat; - $resultSample->version_number = $msh[11] ?? null; - $resultSample->message_datetime = $messageDateTime; - $resultSample->patient_id = $accessionNumber; - $resultSample->patient_name_last = $patientName; - $resultSample->accession_number = $accessionNumber; - $resultSample->test_id = $obr[4] ?? ($qpd[1] ?? null); - $resultSample->result_type_code = $obx[3] ?? ($msh[8] ?? null); - $resultSample->organism = $organism; - $resultSample->test_status = $obx[11] ?? null; - $resultSample->result_status_datetime = $this->astmToDateTime($obx[14] ?? null); - $resultSample->instrument_type = $alat; - $resultSample->protocol_name = $obr[4] ?? null; - $resultSample->result_data = $obx ? json_encode($obx) : null; - $resultSample->additional_result = [ - 'source' => 'local_instrument_fallback', - 'raw_astm' => $raw, - 'segments' => array_keys($parsed), - 'nte' => $nte, - 'qpd' => $qpd, - 'qid' => $qid, - ]; - $resultSample->save(); - - if ($accessionNumber) { - $teksHasil = $this->formatLocalInstrumentText($parsed, $alat, $accessionNumber, $messageDateTime); - if ($organism !== '') { - $getnama = explode('^', $organism); - $organism = $getnama[1] ?? $organism; - $sudah = Organisms::where('name', $organism)->count(); - if ($sudah == 0) { - Organisms::create([ - 'kelompok' => 'biakankultur', - 'name' => $organism, - 'category' => $alat, - ]); - } - KomponenJawaban::updateOrCreate( - [ - 'accnumber' => $accessionNumber, - 'komponen' => 'id_bakteri01', - 'isidata' => $organism, - ], - [ - 'template' => 'all', - 'created_by' => $alat - ] - ); - if ($alat == 'MYLA-10.10.120.89') { - $status = 'Data Malditof di Terima'; - } else { - $status = 'Data Genexpert di Terima'; - } - Periksa::where('nofoto', $accessionNumber)->whereNotIn('status', ['Selesai', 'Arsip', 'Dibatalkan (Arsip)', 'Batal'])->update([ - 'status' => $status, - ]); - } - - Riwayat::create([ - 'nofoto' => $accessionNumber, - 'jawaban' => $teksHasil, - 'inputor' => $alat, - 'keterangan' => $messageDateTime ?: date('Y-m-d H:i:s'), - 'verifikasi' => '', - ]); - } - - return true; - } catch (\Throwable $e) { - Log::error('Local instrument fallback parse error', [ - 'alat' => $alat, - 'msg' => $e->getMessage(), - ]); - - return false; - } - } public function processAstmMessagesLokal($dataListener) { foreach ($dataListener as $data) { try { @@ -1128,15 +1238,16 @@ class AstmMessageService 'created_by' => 'BD' ] ); - } else if ( - $data->alat == 'MYLA-10.10.120.89' - OR $data->alat == 'GeneXpert-10.10.120.75' - OR $data->alat == 'GeneXpert-10.10.120.74' - OR $data->alat == 'GeneXpert-10.10.120.73' - OR $data->alat == '10.10.120.75' - OR $data->alat == '10.10.120.74' - OR $data->alat == '10.10.120.73' - ){ + } else if ( $data->alat == 'GeneXpert-10.10.120.75' OR $data->alat == 'GeneXpert-10.10.120.74' OR $data->alat == 'GeneXpert-10.10.120.73' OR $data->alat == 'GeneXpert-10.10.120.13' OR $data->alat == '10.10.120.75' OR $data->alat == '10.10.120.74' OR $data->alat == '10.10.120.73' OR $data->alat == '10.10.120.13') { + $result = $this->processAstmMessagesgenExpert($data->rawdt, $data->alat); + if ($result) { + DB::table('lis_phoenix')->where('id', $data->id)->update([ + 'processed' => 1 + ]); + } else { + Log::debug($result); + } + } else if ( $data->alat == 'MYLA-10.10.120.89' ){ $messages = $this->splitHl7LikeMessages($data->rawdt); $ok = false;