From d8ff19c9b29229204e444801dcd4b8914bfe745a Mon Sep 17 00:00:00 2001 From: Dwi Swandhana Date: Fri, 30 Jan 2026 21:07:15 +0700 Subject: [PATCH] update --- .../app/Http/Controllers/ListController.php | 6 +- htdocs/app/Services/AstmMessageService.php | 9 +- .../views/cetak/ekspertisecci.blade.php | 6 +- .../views/cetak/ekspertisedefault.blade.php | 6 +- .../views/cetak/ekspertisekultur.blade.php | 6 +- .../views/cetak/ekspertisepl.blade.php | 6 +- .../views/cetak/ekspertisevl.blade.php | 6 +- .../views/cetak/igmiggletospira.blade.php | 6 +- .../views/dokter/pemeriksaan.blade.php | 8 +- htdocs/resources/views/dokter/ppds.blade.php | 8 +- .../views/dokter/ppdsdeveloper.blade.php | 8 +- listener/app.py | 175 +++++++++--------- 12 files changed, 131 insertions(+), 119 deletions(-) diff --git a/htdocs/app/Http/Controllers/ListController.php b/htdocs/app/Http/Controllers/ListController.php index 71763c01..a71168e7 100644 --- a/htdocs/app/Http/Controllers/ListController.php +++ b/htdocs/app/Http/Controllers/ListController.php @@ -237,7 +237,11 @@ class ListController extends Controller $tcmtb, $urine ); - + $batasWaktu = Carbon::now()->subHours(24); + Periksa::whereNull('status')->where('mulai', '<', $batasWaktu)->update([ + 'status' => 'Dibatalkan (Arsip)', + 'updated_at' => now() + ]); $arraylist = Periksa::select( 'id', 'mulai', 'akhir', 'orderid', 'noloket', 'nofoto', 'noregister', 'asalpasien', 'nmrs', 'pasien_id', 'nmpasien', 'jkpasien', 'tgllahirpasien', 'tlppasien', 'alamatpasien', 'reques', 'usia', 'berat', 'ktp', 'bpjs', 'ruangan_id', diff --git a/htdocs/app/Services/AstmMessageService.php b/htdocs/app/Services/AstmMessageService.php index d58c8341..2f47d590 100644 --- a/htdocs/app/Services/AstmMessageService.php +++ b/htdocs/app/Services/AstmMessageService.php @@ -285,7 +285,7 @@ class AstmMessageService Periksa::where('nofoto', $accession_number)->update([ 'status' => 'Data Vitek di Terima', ]); - Log::info("Data berhasil disimpan:", $resultSample->toArray()); + //Log::info("Data berhasil disimpan:", $resultSample->toArray()); $resultSample->save(); } return response()->json(['message' => 'Data berhasil diproses dan disimpan.']); @@ -467,7 +467,7 @@ class AstmMessageService 'status' => 'Data Vitek di Terima', ]); } - Log::info("Data MTRL Berhasil di Parse dan di simpan ", $resultSample->toArray()); + //Log::info("Data MTRL Berhasil di Parse dan di simpan ", $resultSample->toArray()); return response()->json(['message' => 'Data berhasil diproses dan disimpan.']); } else { $headerData = explode("|", $astmData); @@ -628,7 +628,7 @@ class AstmMessageService $resultSample->additional_result = json_encode($resultData); if ($noregister){ $resultSample->save(); - Log::info("Data ASTM Berhasil di Parse dan di simpan ", $resultSample->toArray()); + //Log::info("Data ASTM Berhasil di Parse dan di simpan ", $resultSample->toArray()); return response()->json(['message' => 'Data berhasil diproses dan disimpan.']); } } else { @@ -823,7 +823,7 @@ class AstmMessageService ] ); } - Log::info("Data BD ASTM Berhasil di Parse dan di simpan ", $resultSample->toArray()); + //Log::info("Data BD ASTM Berhasil di Parse dan di simpan ", $resultSample->toArray()); return true; @@ -838,7 +838,6 @@ class AstmMessageService public function processAstmMessagesLokal($dataListener) { foreach ($dataListener as $data) { try { - // Ambil pesan ASTM dari kolom 'message' atau yang relevan $response = $data->rawdt; if ($data->alat == 'MGIT' OR $data->alat == 'COM5' OR $data->alat == 'BACTEC'){ $assembled = $this->reassembleAstmFrames($data->rawdt); diff --git a/htdocs/resources/views/cetak/ekspertisecci.blade.php b/htdocs/resources/views/cetak/ekspertisecci.blade.php index fae1b4d4..97aee6f6 100644 --- a/htdocs/resources/views/cetak/ekspertisecci.blade.php +++ b/htdocs/resources/views/cetak/ekspertisecci.blade.php @@ -70,9 +70,9 @@ Tanggal Kirim Sample : {{ $periksa->daftar }} -   -   -   + Spesimen + : + {!! $periksa->nm_spesimen !!} diff --git a/htdocs/resources/views/cetak/ekspertisedefault.blade.php b/htdocs/resources/views/cetak/ekspertisedefault.blade.php index 5bd77108..79aa72df 100644 --- a/htdocs/resources/views/cetak/ekspertisedefault.blade.php +++ b/htdocs/resources/views/cetak/ekspertisedefault.blade.php @@ -69,9 +69,9 @@ Tanggal Kirim Sample : {{ $periksa->daftar }} -   -   -   + Spesimen + : + {!! $periksa->nm_spesimen !!} diff --git a/htdocs/resources/views/cetak/ekspertisekultur.blade.php b/htdocs/resources/views/cetak/ekspertisekultur.blade.php index ad2fd365..63565771 100644 --- a/htdocs/resources/views/cetak/ekspertisekultur.blade.php +++ b/htdocs/resources/views/cetak/ekspertisekultur.blade.php @@ -87,9 +87,9 @@ Tanggal Kirim Sample : {{ $periksa->daftar }} -   -   -   + Spesimen + : + {!! $periksa->nm_spesimen !!} diff --git a/htdocs/resources/views/cetak/ekspertisepl.blade.php b/htdocs/resources/views/cetak/ekspertisepl.blade.php index d6612aae..e4eb90c3 100644 --- a/htdocs/resources/views/cetak/ekspertisepl.blade.php +++ b/htdocs/resources/views/cetak/ekspertisepl.blade.php @@ -69,9 +69,9 @@ Tanggal Kirim Sample : {{ $periksa->daftar }} -   -   -   + Spesimen + : + {!! $periksa->nm_spesimen !!} diff --git a/htdocs/resources/views/cetak/ekspertisevl.blade.php b/htdocs/resources/views/cetak/ekspertisevl.blade.php index a5322394..5339136e 100644 --- a/htdocs/resources/views/cetak/ekspertisevl.blade.php +++ b/htdocs/resources/views/cetak/ekspertisevl.blade.php @@ -85,9 +85,9 @@ Tanggal Kirim Sample : {{ $periksa->daftar }} -   -   -   + Spesimen + : + {!! $periksa->nm_spesimen !!} diff --git a/htdocs/resources/views/cetak/igmiggletospira.blade.php b/htdocs/resources/views/cetak/igmiggletospira.blade.php index 0e937934..bd0c4831 100644 --- a/htdocs/resources/views/cetak/igmiggletospira.blade.php +++ b/htdocs/resources/views/cetak/igmiggletospira.blade.php @@ -70,9 +70,9 @@ Tanggal Kirim Sample : {{ $periksa->daftar }} -   -   -   + Spesimen + : + {!! $periksa->nm_spesimen !!} diff --git a/htdocs/resources/views/dokter/pemeriksaan.blade.php b/htdocs/resources/views/dokter/pemeriksaan.blade.php index f412edd1..acaaa40e 100644 --- a/htdocs/resources/views/dokter/pemeriksaan.blade.php +++ b/htdocs/resources/views/dokter/pemeriksaan.blade.php @@ -1555,8 +1555,8 @@
@@ -1707,8 +1707,8 @@
diff --git a/htdocs/resources/views/dokter/ppds.blade.php b/htdocs/resources/views/dokter/ppds.blade.php index d94a6f75..5787b05f 100644 --- a/htdocs/resources/views/dokter/ppds.blade.php +++ b/htdocs/resources/views/dokter/ppds.blade.php @@ -1610,8 +1610,8 @@
@@ -1762,8 +1762,8 @@
diff --git a/htdocs/resources/views/dokter/ppdsdeveloper.blade.php b/htdocs/resources/views/dokter/ppdsdeveloper.blade.php index 32d090ff..a3da3ac1 100644 --- a/htdocs/resources/views/dokter/ppdsdeveloper.blade.php +++ b/htdocs/resources/views/dokter/ppdsdeveloper.blade.php @@ -1610,8 +1610,8 @@
@@ -1762,8 +1762,8 @@
diff --git a/listener/app.py b/listener/app.py index 17bf1339..7034ee82 100644 --- a/listener/app.py +++ b/listener/app.py @@ -75,7 +75,7 @@ active_genexpert_connections = {} connection_lock = threading.Lock() DEVICE_CONFIGS = [ { - 'port': 'COM6', 'baud_rate': 19200, 'device_type': 'vitek', 'alat_name': 'Vitek 1', + 'port': 'COM6', 'baud_rate': 9600, 'device_type': 'vitek', 'alat_name': 'Vitek 1', 'protocol': 'serial', 'flag_column': 'flg_vitek1' }, { @@ -133,7 +133,7 @@ class LisPhoenix(Base): __tablename__ = 'lis_phoenix' id = Column(Integer, primary_key=True) no_id = Column(String(50)) # Patient ID - seq_no = Column(String(50), unique=True) # Isolate ID / Sample ID + seq_no = Column(String(50)) # Isolate ID / Sample ID rnmpas = Column(String(100)) # Patient Name tgl_data = Column(SqDate) rawdt = Column(Text) @@ -145,7 +145,7 @@ class LisPhoenix(Base): class LisPhoenixDtl(Base): __tablename__ = 'lis_phoenix_dtl' id = Column(Integer, primary_key=True) - seq_no = Column(String(50), index=True) # Isolate ID / Sample ID + seq_no = Column(String(50)) # Isolate ID / Sample ID kd_antibiotik = Column(String(50)) nm_antibiotik = Column(String(100)) keterangan = Column(String(50)) # MIC Value (e.g., <=0.5) @@ -557,111 +557,121 @@ def calculate_vitek_checksum(data_str): def parse_and_save_vitek_result(raw_data, port_name="VITEK"): session = SessionLocal() try: - # --- LANGKAH 1: PEMBERSIHAN DATA --- - # Vitek sering mengirim karakter framing \x1e (RS), \x1d (GS), \x03 (ETX) - # Hapus framing characters dan newline - clean_data = raw_data.replace('\x02', '').replace('\x03', '').replace('\x1e', '').replace('\r', '').replace('\n', '') + # --- 1. CLEANING DATA --- + # Hapus karakter kontrol STX(02), ETX(03), RS(1e/30), GS(1d/29), CR, LF + # Perhatikan: Log Anda menunjukkan RS () muncul di tengah kata, jadi harus dihapus total. + clean_data = raw_data.replace('\x02', '').replace('\x03', '').replace('\x1e', '').replace('\x1d', '').replace('\r', '').replace('\n', '') - # Pisahkan jika ada checksum di akhir (biasanya setelah \x1d) - if '\x1d' in clean_data: - clean_data = clean_data.split('\x1d')[0] - - # --- LANGKAH 2: PARSING FIELD BERDASARKAN TAG --- - # Format Vitek: tag|value|tag|value... + # Pisahkan field berdasarkan pipa '|' fields = clean_data.split('|') - msg_type = fields[0] # mtmpr (Order) atau mtrsl (Result) + # Cek apakah ini pesan result (mtrsl) + if not fields or fields[0] != 'mtrsl': + return # Abaikan jika bukan result - # Inisialisasi variabel - sample_id = None # ci (Accession Number) - patient_id = "" # pi + # --- 2. VARIABLE INIT --- + sample_id = None # ci (No Lab / No Container) + patient_id = "" # pi (No RM) patient_name = "" # pn organism_name = "" # o2 - antibiotics = [] # List untuk menyimpan hasil AB + card_barcode = "" # is + antibiotics = [] # List penampung hasil AB result_date = datetime.datetime.now() - # Penanda apakah kita sedang membaca blok antibiotik - current_ab_name = "" - current_ab_mic = "" - current_ab_int = "" + # Variabel sementara untuk looping antibiotik + curr_ab_name = "" + curr_ab_mic = "" + curr_ab_int = "" - # Loop setiap field untuk mencari Tag + # --- 3. PARSING LOOP --- for field in fields: if not field: continue # --- HEADER INFO --- - if field.startswith("pi") and len(field) > 2: # Patient ID - patient_id = field[2:].strip() - elif field.startswith("pn") and len(field) > 2: # Patient Name - patient_name = field[2:].strip() - elif field.startswith("ci") and len(field) > 2: # Case ID / No Reg (KUNCI UTAMA) - # Format Vitek kadang 25-035007, kita ambil angkanya saja atau sesuai format LIS + if field.startswith("ci") and len(field) > 2: sample_id = field[2:].strip() - - # --- ORGANISME (Bakteri) --- + elif field.startswith("pi") and len(field) > 2: + patient_id = field[2:].strip() + elif field.startswith("pn") and len(field) > 2: + patient_name = field[2:].strip() + elif field.startswith("is") and len(field) > 2: + card_barcode = field[2:].strip() + # --- ORGANISME --- elif field.startswith("o2") and len(field) > 2: organism_name = field[2:].strip() - # --- ANTIBIOTIK (Looping Block) --- - # Penanda blok antibiotik baru dimulai dengan tag 'ra' + # --- ANTIBIOTIK BLOCK (Mulai Pesan Kedua) --- + # Tag 'ra' adalah pemisah antar obat elif field == "ra": - # Simpan antibiotik sebelumnya jika ada - if current_ab_name: - ab_str = f"{current_ab_name} {current_ab_mic} ({current_ab_int})" + # Jika ada data obat sebelumnya di memori, simpan dulu + if curr_ab_name: + ab_str = f"{curr_ab_name} {curr_ab_mic} ({curr_ab_int})" antibiotics.append(ab_str) - # Reset temp vars - current_ab_name = "" - current_ab_mic = "" - current_ab_int = "" + # Reset untuk obat berikutnya + curr_ab_name = ""; curr_ab_mic = ""; curr_ab_int = "" - # Detail Antibiotik - elif field.startswith("a2") and len(field) > 2: # Nama Antibiotik - current_ab_name = field[2:].strip() - elif field.startswith("a3") and len(field) > 2: # Nilai MIC - current_ab_mic = field[2:].strip() - elif field.startswith("a4") and len(field) > 2: # Interpretasi (S/I/R) - current_ab_int = field[2:].strip() + # Detail Obat + elif field.startswith("a2") and len(field) > 2: # Nama Obat (mis: Cefoxitin) + curr_ab_name = field[2:].strip() + elif field.startswith("a3") and len(field) > 2: # MIC (mis: >=4) + curr_ab_mic = field[2:].strip() + elif field.startswith("a4") and len(field) > 2: # Interpretasi (R/S/I) + curr_ab_int = field[2:].strip() elif field.startswith("an") and len(field) > 2: # Interpretasi Alternatif - if not current_ab_int: current_ab_int = field[2:].strip() + if not curr_ab_int: curr_ab_int = field[2:].strip() - # Jangan lupa simpan antibiotik terakhir setelah loop selesai - if current_ab_name: - ab_str = f"{current_ab_name} {current_ab_mic} ({current_ab_int})" - antibiotics.append(ab_str) + # Jangan lupa simpan obat terakhir yang tersisa di buffer + if curr_ab_name: + ab_str = f"{curr_ab_name} {curr_ab_mic} ({curr_ab_int})" + antibiotics.append(ab_str) - # --- LANGKAH 3: FORMAT FINAL & SAVE --- - # Kita hanya memproses jika tipe pesan adalah 'mtrsl' (Result) dan ada Sample ID - if msg_type == 'mtrsl' and sample_id: + # --- 4. FORMAT FINAL STRING --- + # Format: "Staphylococcus hominis | Cefoxitin >=4 (R), Gentamicin 4 (S)..." + final_res_string = organism_name + #if antibiotics: + # final_res_string += " | " + ", ".join(antibiotics) + + # Fallback jika negatif (biasanya tidak ada o2, tapi ada teks neg) + #if not final_res_string and "neg" in raw_data.lower(): + # final_res_string = "NEGATIVE / NO GROWTH" + + # --- 5. LOGIKA DATABASE (UPSERT: UPDATE or INSERT) --- + if sample_id: + # Cari data berdasarkan No Lab (Sample ID) + final_seq_no = card_barcode if card_barcode else patient_id + existing_data = session.query(LisPhoenix).filter( + LisPhoenix.seq_no == final_seq_no + ).first() + + if existing_data: + # === SKENARIO PESAN KEDUA (UPDATE) === + logging.info(f"[{port_name}] UPDATE Data (Tahap 2) -> ID: {sample_id}, Pasien: {patient_name}") + print(f"[{port_name}] UPDATE Data -> ID: {sample_id} (Hasil Lengkap)") + existing_data.rnmpas = patient_name + existing_data.organisme = final_res_string + existing_data.rawdt = raw_data + existing_data.tgl_data = result_date + existing_data.processed = None + + else: + # === SKENARIO PESAN PERTAMA (INSERT) === + logging.info(f"[{port_name}] INSERT Data Baru (Tahap 1) -> ID: {sample_id}, Pasien: {patient_name}") + print(f"[{port_name}] INSERT Data -> ID: {sample_id} (Identifikasi Awal)") + + new_entry = LisPhoenix( + no_id=sample_id, + seq_no=final_seq_no, + rnmpas=patient_name, + tgl_data=result_date, + rawdt=raw_data, + organisme=final_res_string, + alat=port_name + ) + session.add(new_entry) - # Gabungkan hasil: Organisme | AB1, AB2, AB3... - final_res_string = organism_name - if antibiotics: - final_res_string += " | " + ", ".join(antibiotics) - - # Jika tidak ada organisme tapi tipe result, mungkin Negative? - if not final_res_string and "neg" in raw_data.lower(): - final_res_string = "NEGATIVE / NO GROWTH" - - logging.info(f"[{port_name}] Save DB -> ID: {sample_id}, Pasien: {patient_name}, Bakteri: {organism_name}") - print(f"[{port_name}] Save DB -> ID: {sample_id}, Pasien: {patient_name}, Bakteri: {organism_name}") - new_entry = LisPhoenix( - no_id=sample_id, # Menggunakan tag 'ci' - seq_no=patient_id, # Menggunakan tag 'pi' - rnmpas=patient_name, # Menggunakan tag 'pn' - tgl_data=result_date, - rawdt=raw_data, - organisme=final_res_string, # Hasil gabungan - alat=port_name - ) - session.add(new_entry) session.commit() else: - if msg_type != 'mtrsl': - logging.info(f"[{port_name}] Mengabaikan pesan tipe {msg_type}") - print(f"[{port_name}] Mengabaikan pesan tipe {msg_type}") - else: - logging.warning(f"[{port_name}] Data tidak lengkap (No Sample ID). Raw: {raw_data[:50]}...") - print(f"[{port_name}] Data tidak lengkap (No Sample ID). Raw: {raw_data[:50]}...") + logging.warning(f"[{port_name}] Pesan diabaikan (Tanpa Sample ID): {clean_data[:30]}...") except Exception as e: logging.error(f"Error Parsing Vitek: {e}") @@ -669,7 +679,6 @@ def parse_and_save_vitek_result(raw_data, port_name="VITEK"): session.rollback() finally: session.close() - def create_vitek_order_message(order): """ Membuat Frame Order Vitek sesuai Manual Ref 514937.