diff --git a/listener/app.py b/listener/app.py index 806ac5ce..be75d59a 100644 --- a/listener/app.py +++ b/listener/app.py @@ -227,6 +227,13 @@ def parse_hl7_result(hl7_message, device_name="GeneXpert"): try: # 1. Bersihkan dan Split Pesan # HL7 dipisahkan oleh \r (Carriage Return) + if "MSH|" not in hl7_message: + logging.warning("[HL7] Data tidak mengandung segmen MSH valid.") + print("[HL7] Data tidak mengandung segmen MSH valid.") + return + start_idx = hl7_message.find("MSH|") + hl7_message = hl7_message[start_idx:] + segments = hl7_message.strip().split('\r') # Variabel penampung @@ -294,23 +301,28 @@ def parse_hl7_result(hl7_message, device_name="GeneXpert"): else: final_result = "; ".join(results_list) - # 4. Validasi Data Penting + # 4. Validasi Data Penting (DIMODIFIKASI) if not sample_id: - logging.warning("[HL7 Parser] Sample ID tidak ditemukan. Data tidak disimpan.") - print("[HL7 Parser] Sample ID tidak ditemukan. Data tidak disimpan.") - return + # JANGAN RETURN, tapi buat ID palsu agar tetap bisa masuk database + timestamp_err = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + sample_id = f"ERR_{timestamp_err}" + logging.warning(f"[HL7 Parser] Sample ID kosong. Disimpan sebagai {sample_id} untuk audit.") + print(f"[HL7 Parser] Sample ID kosong. Disimpan sebagai {sample_id} untuk audit.") + # Tambahkan penanda di hasil agar Anda tahu ini error + final_result = f"[PARSE FAIL] {final_result}" - logging.info(f"[HL7 Parser] Menyimpan hasil untuk Sample: {sample_id} - {patient_name}") - print(f"[HL7 Parser] Menyimpan hasil untuk Sample: {sample_id} - {patient_name}") - # 5. Simpan ke Database (Tabel LisPhoenix) + logging.info(f"[HL7 Parser] Menyimpan hasil untuk Sample: {sample_id}") + print(f"[HL7 Parser] Menyimpan hasil untuk Sample: {sample_id}") + + # 5. Simpan ke Database new_data = LisPhoenix( - no_id=sample_id, # OBR-3 - seq_no=patient_id, # PID-3 - rnmpas=patient_name, # PID-5 - tgl_data=result_date, # MSH-7 - rawdt=hl7_message, # Pesan Asli - organisme=final_result, # Gabungan OBX-5 - alat=device_name # Parameter fungsi + no_id=sample_id, + seq_no=patient_id, + rnmpas=patient_name, + tgl_data=result_date, + rawdt=hl7_message, + organisme=final_result, + alat=device_name ) session.add(new_data) @@ -407,61 +419,107 @@ def send_mllp_message(sock, hl7_msg): sock.sendall(mllp_msg) def handle_genexpert_client(conn, addr): - ip_sender = addr[0] - logging.info(f"[CONNECT] GeneXpert terkoneksi dari IP: {ip_sender}") - print(f"[CONNECT] GeneXpert terkoneksi dari IP: {ip_sender}") - with connection_lock: - active_genexpert_connections[ip_sender] = conn - + print(f"[TCP] Koneksi baru dari {addr}") buffer = b"" + try: while True: - data = conn.recv(4096) - if not data: - break - - buffer += data - - # Cek apakah paket MLLP lengkap (diawali \x0b dan diakhiri \x1c\r) - # Logic sederhana: cari penutup \x1c\r - while b'\x1c\r' in buffer: - # Ekstrak pesan - start_marker = buffer.find(b'\x0b') - end_marker = buffer.find(b'\x1c\r') - - if start_marker != -1 and end_marker != -1: - raw_msg = buffer[start_marker+1 : end_marker] - hl7_str = raw_msg.decode('utf-8') - - logging.info(f"[RECV] Dari {ip_sender}: {hl7_str[:50]}...") # Log 50 karakter awal - print(f"[RECV] Dari {ip_sender}: {hl7_str[:50]}...") # Print 50 karakter awal - # Cek Tipe Pesan (MSH field 9) - if "ORU^R01" in hl7_str: - # Ini adalah Hasil - hasil = parse_hl7_result(hl7_str, device_name=addr[0] if addr else "GeneXpert") - logging.info(f"[RESULT] {hasil}") - print(f"[RESULT] {hasil}") - # TODO: Simpan 'hasil' ke database berdasarkan Sample ID di HL7 - - # Kirim ACK (Terima Kasih) ke Alat - # ACK dinamis - msg_control_id = hl7_str.split('|')[9] # Ambil ID pesan asli - ack_msg = f"MSH|^~\\&|LIS|LAB|GeneXpert|Cepheid|{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}||ACK|{msg_control_id}|P|2.3\rMSA|AA|{msg_control_id}\r" - send_mllp_message(conn, ack_msg) - - # Hapus pesan yang sudah diproses dari buffer - buffer = buffer[end_marker+2:] - else: + try: + data = conn.recv(4096) + if not data: break + + buffer += data + + # --- 1. HANDLE HANDSHAKE (ENQ) --- + # Jika alat kirim ENQ (\x05/♣), langsung balas ACK (\x06) + if b'\x05' in buffer: + # logging.info(f"[TCP] Terima ENQ dari {addr}, kirim ACK.") + conn.sendall(b'\x06') + # Hapus ENQ dari buffer agar tidak mengganggu + buffer = buffer.replace(b'\x05', b'') + + # --- 2. CEK APAKAH PESAN SUDAH LENGKAP? --- + # Kita cari tanda akhir pesan umum: + # - \x1c (End Block MLLP) + # - \x03 (ETX - End Text ASTM) + # - \x04 (EOT - End Transmission ASTM) + + msg_complete = False + end_marker_pos = -1 + + if b'\x1c' in buffer: # Pola MLLP Standard + end_marker_pos = buffer.find(b'\x1c') + msg_complete = True + elif b'\x03' in buffer: # Pola ASTM (Ada Checksum setelahnya) + # ASTM: ...CS + # Kita cari \x03, lalu tambah 4 byte (CS + CRLF) untuk aman + pos = buffer.find(b'\x03') + if len(buffer) >= pos + 4: + end_marker_pos = pos + 4 + msg_complete = True + elif b'\x04' in buffer: # Pola EOT (Putus Koneksi/Selesai) + end_marker_pos = buffer.find(b'\x04') + msg_complete = True + + # --- 3. PROSES JIKA LENGKAP --- + if msg_complete: + # Ambil pesan dari awal sampai marker + # (Gunakan slice sampai end_marker_pos+1 agar karakter penutup ikut terambil/dibuang) + if end_marker_pos == -1: end_marker_pos = len(buffer) + + full_message_bytes = buffer[:end_marker_pos] + + # Sisa buffer (jika ada paket nempel di belakangnya) disimpan untuk loop berikutnya + buffer = buffer[end_marker_pos:] + + # Bersihkan buffer dari sisa marker di awal (jika loop sebelumnya menyisakan sampah) + buffer = buffer.lstrip(b'\x04').lstrip(b'\r').lstrip(b'\n') + + # Decode ke string + temp_str = full_message_bytes.decode('latin-1', errors='ignore') + + # --- SANITIZING (PEMBERSIHAN) --- + # Cari MSH pertama + if "MSH|" in temp_str: + msh_index = temp_str.find("MSH|") + clean_hl7 = temp_str[msh_index:] + + # Logging sample data (50 karakter) + print(f"[TCP] Pesan Lengkap Diterima: {clean_hl7[:50]}...") + + # Panggil Parser + parse_hl7_result(clean_hl7, device_name=f"GeneXpert-{addr[0]}") + + # --- KIRIM ACK --- + try: + lines = clean_hl7.split('\r') + msh_fields = lines[0].split('|') + if len(msh_fields) > 9: + msg_control_id = msh_fields[9] + ack_time = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + ack_msg = f"MSH|^~\\&|LIS|LAB|GeneXpert|Cepheid|{ack_time}||ACK|{msg_control_id}|P|2.5\rMSA|AA|{msg_control_id}\r" + + # Kirim ACK format MLLP + full_ack = f"\x0b{ack_msg}\x1c\r" + conn.sendall(full_ack.encode('utf-8')) + print(f"[ACK] Terkirim untuk ID {msg_control_id}") + except Exception as e: + print(f"Gagal kirim ACK: {e}") + else: + # Jika pesan lengkap tapi tidak ada MSH (misal cuma EOT doang) + pass + + except Exception as e: + print(f"[Loop Error] {e}") + # Jangan break, lanjut terima data + time.sleep(0.1) except Exception as e: - logging.error(f"[ERROR] Koneksi {ip_sender} terputus: {e}") + logging.error(f"[TCP Error] Koneksi {addr} terputus: {e}") finally: - with connection_lock: - if ip_sender in active_genexpert_connections: - del active_genexpert_connections[ip_sender] conn.close() - logging.info(f"[DISCONNECT] Koneksi dengan {ip_sender} ditutup.") + logging.info(f"[TCP] Koneksi {addr} ditutup.") def run_tcp_server(): """Loop utama TCP Server"""