diff --git a/listener/app.py b/listener/app.py index f6a6c439..6dcdaec0 100644 --- a/listener/app.py +++ b/listener/app.py @@ -35,59 +35,6 @@ logging.basicConfig(level=logging.INFO, handlers=[log_handler]) THREAD_LOG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "thread_logs") thread_log_lock = threading.Lock() -def _sanitize_thread_log_name(thread_name): - safe_name = re.sub(r"[^A-Za-z0-9_.-]+", "_", str(thread_name or "main").strip()) - return safe_name or "main" - -def _write_thread_log(message): - try: - os.makedirs(THREAD_LOG_DIR, exist_ok=True) - thread_name = threading.current_thread().name - safe_thread_name = _sanitize_thread_log_name(thread_name) - log_path = os.path.join(THREAD_LOG_DIR, f"{safe_thread_name}.log") - timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - with thread_log_lock: - with open(log_path, "a", encoding="utf-8") as fh: - fh.write(f"{timestamp} | {message}\n") - except Exception: - pass - -def print(*args, **kwargs): - sep = kwargs.get("sep", " ") - end = kwargs.get("end", "\n") - message = sep.join(str(arg) for arg in args) - _write_thread_log(message) - return builtins.print(*args, **kwargs) - -def _visible_bytes(data: bytes) -> str: - mapping = { - 0x02: "", - 0x03: "", - 0x04: "", - 0x05: "", - 0x06: "", - 0x0D: "", - 0x0A: "", - 0x17: "", - 0x15: "", - 0x1C: "", - 0x0B: "", - } - parts = [] - for b in data: - if b in mapping: - parts.append(mapping[b]) - elif 32 <= b <= 126: - parts.append(chr(b)) - else: - parts.append(f"<0x{b:02X}>") - return "".join(parts) - -def _hex_bytes(data: bytes, limit: int = 160) -> str: - clipped = data[:limit] - text = clipped.hex().upper() - return text + ("..." if len(data) > limit else "") - # ========================================== # 1. KONFIGURASI SISTEM # ========================================== @@ -132,17 +79,17 @@ GENEXPERT_HOST_APPLICATION_BY_IP = { # Kanan: 'Host Test Code' dari Dokumen Word Anda GENEXPERT_TEST_MAPPING = { # Mapping untuk IP 10.10.120.75 (Multi-Assay) - "HIV": "HIV-1_VL", # Xpert HIV-1 Viral Load XC Version 3 + "HIV": "HIV1-VL", # Xpert HIV-1 Viral Load XC Version 3 "TCM TB": "MTBRIF", # Xpert MTBRIF Assay G4 Version 6 - "TCM TB ULTRA": "MTBRIF_ULTRA2", # Xpert MTBRIF Ultra Version 4 + "TCM TB ULTRA": "MTBRIF", # Xpert MTBRIF Ultra Version 4 "TCM TB XDR": "MTB-XDR", # Xpert MTB-XDR Version 1 "HCV VL": "HCV", # Xpert HCV Viral Load Version 1 - "COVID-19": "COV-2 2", # Xpert Xpress SARS-CoV-2 Version 2 - "17.3.1 TCM COVID-19": "COV-2 2", # Xpert Xpress SARS-CoV-2 Version 2 - "17.3.2 PCR COVID-19": "COV-2 2", # Xpert Xpress SARS-CoV-2 Version 2 + "COVID-19": "SARSCOV2FLURSV", # Xpert Xpress SARS-CoV-2 Version 2 + "17.3.1 TCM COVID-19": "SARSCOV2FLURSV", # Xpert Xpress SARS-CoV-2 Version 2 + "17.3.2 PCR COVID-19": "SARSCOV2FLURSV", # Xpert Xpress SARS-CoV-2 Version 2 "E.2.5 HCV TCM": "HCV", "18.1.1 TCM HCV": "HCV", - "18.1.2 TCM HIV VIRAL LOAD": "HIV-1_VL", + "18.1.2 TCM HIV VIRAL LOAD": "HIV1-VL", "18.1.4 TCM HPV": "HCV", "7.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTBRIF", "5.3.8 KULTUR TBC MGIT (AUTOMATIC)": "MTBRIF", @@ -180,7 +127,7 @@ GENEXPERT_TEST_MAPPING = { } GENEXPERT_IP_CAPABILITIES = { - "10.10.120.75": ["MTBRIF", "MTBRIF_ULTRA2", "MTB-XDR", "HIV-1_VL", "COV-2 2"], + "10.10.120.75": ["MTBRIF", "HBVVL", "HIV1-VL", "MTB-XDR", "HCV VL", "SARSCOV2FLURSV"], "10.10.120.13": ["HCV", "HBV"], "10.10.120.73": ["MTBRIF"] } @@ -308,6 +255,59 @@ Base.metadata.create_all(bind=engine) # ========================================== # 3. HL7 HELPER FUNCTIONS # ========================================== +def _sanitize_thread_log_name(thread_name): + safe_name = re.sub(r"[^A-Za-z0-9_.-]+", "_", str(thread_name or "main").strip()) + return safe_name or "main" + +def _write_thread_log(message): + try: + os.makedirs(THREAD_LOG_DIR, exist_ok=True) + thread_name = threading.current_thread().name + safe_thread_name = _sanitize_thread_log_name(thread_name) + log_path = os.path.join(THREAD_LOG_DIR, f"{safe_thread_name}.log") + timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + with thread_log_lock: + with open(log_path, "a", encoding="utf-8") as fh: + fh.write(f"{timestamp} | {message}\n") + except Exception: + pass + +def print(*args, **kwargs): + sep = kwargs.get("sep", " ") + end = kwargs.get("end", "\n") + message = sep.join(str(arg) for arg in args) + _write_thread_log(message) + return builtins.print(*args, **kwargs) + +def _visible_bytes(data: bytes) -> str: + mapping = { + 0x02: "", + 0x03: "", + 0x04: "", + 0x05: "", + 0x06: "", + 0x0D: "", + 0x0A: "", + 0x17: "", + 0x15: "", + 0x1C: "", + 0x0B: "", + } + parts = [] + for b in data: + if b in mapping: + parts.append(mapping[b]) + elif 32 <= b <= 126: + parts.append(chr(b)) + else: + parts.append(f"<0x{b:02X}>") + return "".join(parts) + +def _hex_bytes(data: bytes, limit: int = 160) -> str: + clipped = data[:limit] + text = clipped.hex().upper() + return text + ("..." if len(data) > limit else "") + def get_flag_by_device(ip_addr): for flag, ip in TARGET_MAPPING.items(): if ip == ip_addr: @@ -546,173 +546,76 @@ def send_all_orders_astm(conn, ip_addr, astm_msg, response_framing="astm"): session.close() def process_genexpert_hl7_message(conn, ip_addr, clean_hl7, response_framing): - # ========================================================== - # 1. BLOK PENANGANAN ASTM (Karena tidak diawali "MSH|") - # ========================================================== - if not str(clean_hl7 or "").startswith("MSH|"): - records = parse_astm_records(clean_hl7) - record_types = [rec.split("|", 1)[0] for rec in records if rec] - print(f"[GENEXPERT-ASTM] ip={ip_addr}, record_types={record_types}") + records = parse_astm_records(clean_hl7) + record_types = [rec.split("|", 1)[0] for rec in records if rec] + print(f"[GENEXPERT-ASTM] ip={ip_addr}, record_types={record_types}") - # A. Cek Jika Alat Meminta Order (Query) - if any(rec.startswith("Q|") for rec in records): - print(f"[GENEXPERT-ASTM] Alat meminta ORDER (Q Record)") - send_all_orders_astm(conn, ip_addr, clean_hl7, response_framing="astm") - return + # A. Cek Jika Alat Meminta Order (Query) + if any(rec.startswith("Q|") for rec in records): + print(f"[GENEXPERT-ASTM] Alat meminta ORDER (Q Record)") + send_all_orders_astm(conn, ip_addr, clean_hl7, response_framing="astm") + return - # B. Cek Jika Alat Mengirim Hasil Lab (Result) - if any(rec.startswith("R|") for rec in records): - print(f"[RESULT] Menerima Hasil Lab ASTM dari {ip_addr}.") - # Memanggil fungsi parser Anda untuk menyimpan hasil ke DB - parse_genexpert_astm_records(clean_hl7, device_name=f"GeneXpert-{ip_addr}") - return + # B. Cek Jika Alat Mengirim Hasil Lab (Result) + if any(rec.startswith("R|") for rec in records): + print(f"[RESULT] Menerima Hasil Lab ASTM dari {ip_addr}.") + # Memanggil fungsi parser Anda untuk menyimpan hasil ke DB + parse_genexpert_astm_records(clean_hl7, device_name=f"GeneXpert-{ip_addr}") + return - # C. [PERBAIKAN] Cek Jika Alat Mengirim Komentar/Penolakan (Comment) - if any(rec.startswith("C|") for rec in records): - # 1. Ekstrak teks komentar untuk ditampilkan di log - comments = [rec for rec in records if rec.startswith("C|")] - for c in comments: - parts = c.split('|') - comment_text = parts[3] if len(parts) > 3 else c - print(f"[GENEXPERT-ASTM-INFO] Komentar dari Alat: {comment_text}") + # C. [PERBAIKAN] Cek Jika Alat Mengirim Komentar/Penolakan (Comment) + if any(rec.startswith("C|") for rec in records): + # 1. Ekstrak teks komentar untuk ditampilkan di log + comments = [rec for rec in records if rec.startswith("C|")] + for c in comments: + parts = c.split('|') + comment_text = parts[3] if len(parts) > 3 else c + print(f"[GENEXPERT-ASTM-INFO] Komentar dari Alat: {comment_text}") + + # 2. Ekstrak NoReg (Nomor Order) dari record 'O' + rnoreg = None + for rec in records: + if rec.startswith("O|"): + o_parts = rec.split('|') + if len(o_parts) > 2: + rnoreg = o_parts[2].strip() + break + + # 3. Update Database PaslabOrder + if rnoreg: + # Cari kolom flag yang cocok dengan IP yang sedang terkoneksi + target_flag_col = None + for flag_col, mapped_ip in TARGET_MAPPING.items(): + if mapped_ip == ip_addr: + target_flag_col = flag_col + break - # 2. Ekstrak NoReg (Nomor Order) dari record 'O' - rnoreg = None - for rec in records: - if rec.startswith("O|"): - o_parts = rec.split('|') - if len(o_parts) > 2: - rnoreg = o_parts[2].strip() - break - - # 3. Update Database PaslabOrder - if rnoreg: - # Cari kolom flag yang cocok dengan IP yang sedang terkoneksi - target_flag_col = None - for flag_col, mapped_ip in TARGET_MAPPING.items(): - if mapped_ip == ip_addr: - target_flag_col = flag_col - break - - if target_flag_col: - try: - # Buka sesi database dan update - with SessionLocal() as session: - order = session.query(PaslabOrder).filter(PaslabOrder.rnoreg == rnoreg).first() - if order: - # Set flag mesin tersebut menjadi True agar tidak dikirim ulang - setattr(order, target_flag_col, True) - session.commit() - print(f"[GENEXPERT-DB] Order {rnoreg} ditolak alat. Flag {target_flag_col} di-set True (Selesai).") - else: - print(f"[GENEXPERT-DB] Order {rnoreg} tidak ditemukan di database saat memproses penolakan.") - except Exception as e: - print(f"[GENEXPERT-DB-ERROR] Gagal update order duplikat {rnoreg}: {e}") + if target_flag_col: + try: + # Buka sesi database dan update + with SessionLocal() as session: + order = session.query(PaslabOrder).filter(PaslabOrder.rnoreg == rnoreg).first() + if order: + # Set flag mesin tersebut menjadi True agar tidak dikirim ulang + setattr(order, target_flag_col, True) + session.commit() + print(f"[GENEXPERT-DB] Order {rnoreg} ditolak alat. Flag {target_flag_col} di-set True (Selesai).") + else: + print(f"[GENEXPERT-DB] Order {rnoreg} tidak ditemukan di database saat memproses penolakan.") + except Exception as e: + print(f"[GENEXPERT-DB-ERROR] Gagal update order duplikat {rnoreg}: {e}") - print(f"[GENEXPERT-ASTM] Transaksi penolakan order selesai diproses.") - return - - # D. Jika hanya berisi H dan L tanpa ada transaksi berarti (Status Echo) - if set(record_types).issubset({'H', 'L'}): - print(f"[GENEXPERT-ASTM] Menerima Heartbeat / Sesi Kosong dari alat.") - return - - print(f"[GENEXPERT-ASTM] Pesan ASTM tidak dikenali dari {ip_addr}. Isi: {clean_hl7[:50]}") + print(f"[GENEXPERT-ASTM] Transaksi penolakan order selesai diproses.") return - # ========================================================== - # 2. BLOK PENANGANAN HL7 (Fallback / Cadangan) - # ========================================================== - log_genexpert_hl7("IN", ip_addr, clean_hl7) - lines = clean_hl7.split('\r') - msh_fields = lines[0].split('|') - incoming_control_id = msh_fields[9] if len(msh_fields) > 9 else "UNKNOWN" - msg_id = incoming_control_id - - if "QRY^" in clean_hl7 or "QRY|" in clean_hl7: - print( - f"[GENEXPERT] Legacy QRY diterima dari {ip_addr} tetapi diabaikan. " - "Host hanya mendukung QBP^Z01/QBP^Z03 untuk order query." - ) + # D. Jika hanya berisi H dan L tanpa ada transaksi berarti (Status Echo) + if set(record_types).issubset({'H', 'L'}): + print(f"[GENEXPERT-ASTM] Menerima Heartbeat / Sesi Kosong dari alat.") return - if "ORU^" in clean_hl7: - # --- [PATCH] CEK APAKAH INI PESAN ERROR / PENOLAKAN --- - if "|Error^" in clean_hl7 or "|X\r" in clean_hl7 or "\rTE|" in clean_hl7: - print(f"[GENEXPERT-ERROR] Mesin {ip_addr} menolak tes (Test Unknown/Disabled).") - - # Coba ekstrak NoReg dari segmen SPM agar kita bisa mengunci ordernya (set Flag = True) - rnoreg_error = None - for line in clean_hl7.split('\r'): - if line.startswith('SPM|'): - parts = line.split('|') - if len(parts) > 2: - rnoreg_error = parts[2].replace('^', '').strip() - break - - if rnoreg_error: - # Cari kolom flag yang cocok dengan IP yang sedang terkoneksi - target_flag_col = None - for flag_col, mapped_ip in TARGET_MAPPING.items(): - if mapped_ip == ip_addr: - target_flag_col = flag_col - break - - if target_flag_col: - try: - # Buka sesi database dan update - with SessionLocal() as session: - order = session.query(PaslabOrder).filter(PaslabOrder.rnoreg == rnoreg_error).first() - if order: - # Set flag mesin tersebut menjadi True agar tidak dikirim ulang - setattr(order, target_flag_col, True) - session.commit() - print(f"[GENEXPERT-DB] Order {rnoreg_error} ditolak alat. Flag {target_flag_col} di-set True (Selesai).") - else: - print(f"[GENEXPERT-DB] Order {rnoreg_error} tidak ditemukan di database saat memproses penolakan.") - except Exception as e: - print(f"[GENEXPERT-DB-ERROR] Gagal update order duplikat {rnoreg_error}: {e}") - - # Kirim ACK agar alat berhenti mengirim error - ack_msg = create_genexpert_ack_r01_response(clean_hl7, ip_addr=ip_addr) - send_genexpert_response(conn, ip_addr, ack_msg, response_framing, label="oru-ack") - return # BERHENTI DI SINI. Jangan lanjut ke parse_hl7_result! - # ----------------------------------------------------- + print(f"[GENEXPERT-ASTM] Pesan ASTM tidak dikenali dari {ip_addr}. Isi: {clean_hl7[:50]}") + return - print(f"[RESULT] Menerima Hasil Lab.") - parse_hl7_result(conn, msg_id, clean_hl7, device_name=f"GeneXpert-{ip_addr}") - ack_msg = create_genexpert_ack_r01_response(clean_hl7, ip_addr=ip_addr) - send_genexpert_response(conn, ip_addr, ack_msg, response_framing, label="oru-ack") - print(f"[ACK SENT] Untuk hasil ID {incoming_control_id}") - return - - if "QBP^Z01" in clean_hl7 or "QBP^Z03" in clean_hl7: - print("[GENEXPERT] Alat meminta ORDER") - msg_id = extract_msg_control_id(clean_hl7) - send_all_orders(conn, ip_addr, clean_hl7, msg_id, response_framing=response_framing) - return - - if "QCN^J01" in clean_hl7: - clear_genexpert_inflight_for_ip(ip_addr, reason="query-confirmation") - ack_msg = create_genexpert_ack_j01_response(clean_hl7, ip_addr=ip_addr) - send_genexpert_response(conn, ip_addr, ack_msg, response_framing, label="qcn-ack") - print(f"[GENEXPERT] Menerima konfirmasi query dari {ip_addr}.") - return - - print(f"[GenExpert_TCP] Pesan Lengkap Diterima: {clean_hl7[:50]}...") - parse_hl7_result(conn, msg_id, clean_hl7, device_name=f"GeneXpert-{ip_addr}, ") - - try: - 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|^~\\&|MyLIS|LAB|GeneXpert|Cepheid|{ack_time}||ACK|{msg_control_id}|P|2.5\rMSA|AA|{msg_control_id}\r" - full_ack = f"\x0b{ack_msg}\x1c\r" - log_genexpert_hl7("OUT", ip_addr, ack_msg, label="generic-ack") - 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}") def handle_genexpert_client(conn, addr): print(f"[GenExpert_TCP] Koneksi baru dari {addr}") @@ -1486,9 +1389,8 @@ def debug_genexpert_order_message(hl7_message, ip_addr=None): def build_genexpert_result_query(accnumber, msg_control_id): ts = datetime.datetime.now().strftime('%Y%m%d%H%M%S') - # Query hasil berbasis accession number di QRD-8. query_msg = ( - f"MSH|^~\\&|LIS|LAB|GeneXpert|Cepheid|{ts}||QRY^Q02|{msg_control_id}|P|2.5\r" + f"MSH|^~\\&|MyLIS|Mikrobiology|GeneXpert|Cepheid|{ts}||QRY^Q02|{msg_control_id}|P|2.5\r" f"QRD|{ts}|R|I|{msg_control_id}|||1^RD|{accnumber}|OTH|||T\r" ) return query_msg @@ -1506,9 +1408,6 @@ def clear_genexpert_inflight_for_ip(ip_addr, reason="cleared"): state = genexpert_query_inflight_by_ip.pop(ip_addr, None) if state: - logging.info( - f"[GENEXPERT-QUERY] Clear inflight ip={ip_addr}, reason={reason}, accnumber={state.get('accnumber')}" - ) print( f"[GENEXPERT-QUERY] Clear inflight ip={ip_addr}, reason={reason}, accnumber={state.get('accnumber')}" ) @@ -1544,168 +1443,6 @@ def api_query_genexpert_result(): "message": "Mode GeneXpert pasif aktif. Host hanya menjawab request dari alat.", }), 409 -def parse_hl7_result(conn, msg_id, hl7_message, device_name="GeneXpert"): - session = SessionLocal() - clean_hl7 = hl7_message - 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 - sample_id = "" # no_id - patient_id = "" # seq_no - patient_name = "" # rnmpas - result_date = None # tgl_data - results_list = [] # untuk organisme - message_type = "" - - # Waktu default jika tidak ada di pesan - result_date = datetime.datetime.now() - - # 2. Loop setiap segmen - for segment in segments: - fields = segment.split('|') - if not fields: continue - - seg_type = fields[0] - - # --- MSH (Header) --- - if seg_type == 'MSH': - if len(fields) > 8 and fields[8]: - message_type = fields[8].strip() - # Ambil tanggal pesan (Field 7) Format: YYYYMMDDHHMMSS - if len(fields) > 6 and fields[6]: - try: - raw_date = fields[6][:14] # Ambil 14 digit pertama - result_date = datetime.datetime.strptime(raw_date, "%Y%m%d%H%M%S") - except ValueError: - pass # Gunakan default datetime.now() jika format salah - - # --- PID (Patient ID) --- - elif seg_type == 'PID': - # PID|3 = Patient ID (seq_no) - if len(fields) > 3: - patient_id = fields[3].replace('^', '') - - # PID|5 = Patient Name (rnmpas) - if len(fields) > 5: - # Ganti caret ^ dengan spasi (Family^Name -> Family Name) - patient_name = fields[5].replace('^', ' ').strip() - - # --- OBR (Observation Request - Info Sample) --- - elif seg_type == 'OBR': - # OBR|2 atau OBR|3 biasanya berisi Sample ID / Accession No - # Kita coba ambil field 3 (Filler Order Number) dulu, kalau kosong field 2 - if len(fields) > 3 and fields[3]: - sample_id = fields[3].replace('^', '') - elif len(fields) > 2 and fields[2]: - sample_id = fields[2].replace('^', '') - - # --- OBX (Observation Result - Hasil Tes) --- - elif seg_type == 'OBX': - # OBX|3 = Test Name/Code (Misal: MTB, RIF) - # OBX|5 = Result Value (Misal: DETECTED, NOT DETECTED) - if len(fields) > 5: - test_name = fields[3].split('^')[1] if '^' in fields[3] else fields[3] - test_val = fields[5].replace('^', ' ') - - # Gabungkan nama tes dan hasil - # Contoh: "MTB: DETECTED" - results_list.append(f"{test_name}: {test_val}") - - # 3. Gabungkan semua hasil OBX menjadi satu string - if not results_list: - final_result = "No Result Found" - else: - final_result = "; ".join(results_list) - - # 4. Validasi Data Penting - if not sample_id: - if str(message_type).startswith("ORU^"): - source_ip = "" - try: - source_ip = str(conn.getpeername()[0]).strip() - except Exception: - source_ip = str(device_name).replace("GeneXpert-", "").replace(",", " ").strip() - - if source_ip: - logging.info(f"[HL7 Parser] ORU tanpa sample_id dari {source_ip}. Anggap sebagai request order.") - print(f"[HL7 Parser] ORU tanpa sample_id dari {source_ip}. Anggap sebagai request order.") - send_all_orders(conn, source_ip, clean_hl7, msg_id) - else: - logging.warning("[HL7 Parser] ORU tanpa sample_id diterima, tetapi IP sumber tidak dapat diidentifikasi.") - print("[HL7 Parser] ORU tanpa sample_id diterima, tetapi IP sumber tidak dapat diidentifikasi.") - else: - logging.info(f"[HL7 Parser] Pesan {message_type or 'UNKNOWN'} tanpa sample_id diabaikan.") - print(f"[HL7 Parser] Pesan {message_type or 'UNKNOWN'} tanpa sample_id diabaikan.") - return - - mapped_no_id = sample_id - mapped_seq_no = patient_id - mapped_alat = device_name - pending_event = None - - with pending_query_lock: - pending = pending_result_queries.get(sample_id) - if pending: - source_ip = str(device_name).replace("GeneXpert-", "").strip() - mapped_no_id = pending.get("register_no") or sample_id - mapped_seq_no = sample_id - mapped_alat = f"GeneXpert-{source_ip}" if source_ip else device_name - pending["status"] = "found" - pending["source_ip"] = source_ip - pending["response_at"] = datetime.datetime.now() - pending_event = pending.get("event") - clear_genexpert_inflight_for_ip(source_ip, reason="result-received") - - source_ip = "" - try: - source_ip = str(conn.getpeername()[0]).strip() - except Exception: - source_ip = str(device_name).replace("GeneXpert-", "").replace(",", " ").strip() - - if source_ip: - flag_name = get_flag_by_device(source_ip) - if flag_name: - order = session.query(PaslabOrder).filter(PaslabOrder.rnoreg == sample_id).first() - if order: - setattr(order, flag_name, True) - print(f"[GENEXPERT] Hasil diterima, set {flag_name}=TRUE untuk {sample_id}") - - print(f"[HL7 Parser] Menyimpan hasil untuk Sample: {sample_id}") - - # 5. Simpan ke Database - new_data = LisPhoenix( - no_id=mapped_no_id, - seq_no=mapped_seq_no, - rnmpas=patient_name, - tgl_data=result_date, - rawdt=hl7_message, - organisme=final_result, - alat=mapped_alat - ) - - session.add(new_data) - session.commit() - print(f"[DB] Berhasil simpan ke LisPhoenix: {sample_id}") - stop_scheduled_result_query(sample_id, reason="result-received") - if pending_event: - pending_event.set() - - except Exception as e: - logging.error(f"[HL7 Parser] Error menyimpan data: {e}") - print(f"[HL7 Parser] Error menyimpan data: {e}") - session.rollback() - finally: - session.close() def create_hl7_dsr_response(order, msg_control_id, qrd_segment): """ diff --git a/listener/geneexpert.py b/listener/geneexpert.py index 07eeed2e..2de33aa9 100644 --- a/listener/geneexpert.py +++ b/listener/geneexpert.py @@ -22,7 +22,7 @@ from sqlalchemy.orm import declarative_base, sessionmaker # type: ignore # Logging Setup # Konfigurasi logging per hari log_handler = TimedRotatingFileHandler( - filename="app.log", + filename="geneexpert.log", when="midnight", interval=1, backupCount=7, @@ -116,9 +116,9 @@ GENEXPERT_RESPONSE_MODE_BY_IP = { } GENEXPERT_HOST_APPLICATION_DEFAULT = "DE002" GENEXPERT_HOST_APPLICATION_BY_IP = { - "10.10.120.75": "GE01", - "10.10.120.13": "DE002", - "10.10.120.73": "GE01", + "10.10.120.75": "GE01", # GenExpert Kecil + "10.10.120.13": "DE002", # GenExpert Tempat Lama + "10.10.120.73": "GE01", # GenExpert Besa } # Mapping Flag ke IP Address GeneXpert # Pastikan IP ini SESUAI dengan settingan "Server IP" di masing-masing alat (Client Mode) @@ -135,17 +135,17 @@ TARGET_MAPPING = { # Kanan: 'Host Test Code' dari Dokumen Word Anda GENEXPERT_TEST_MAPPING = { # Mapping untuk IP 10.10.120.75 (Multi-Assay) - "HIV": "HIV-1_VL", # Xpert HIV-1 Viral Load XC Version 3 + "HIV": "HIV1-VL", # Xpert HIV-1 Viral Load XC Version 3 "TCM TB": "MTBRIF", # Xpert MTBRIF Assay G4 Version 6 - "TCM TB ULTRA": "MTBRIF_ULTRA2", # Xpert MTBRIF Ultra Version 4 + "TCM TB ULTRA": "MTBRIF", # Xpert MTBRIF Ultra Version 4 "TCM TB XDR": "MTB-XDR", # Xpert MTB-XDR Version 1 "HCV VL": "HCV", # Xpert HCV Viral Load Version 1 - "COVID-19": "COV-2 2", # Xpert Xpress SARS-CoV-2 Version 2 - "17.3.1 TCM COVID-19": "COV-2 2", # Xpert Xpress SARS-CoV-2 Version 2 - "17.3.2 PCR COVID-19": "COV-2 2", # Xpert Xpress SARS-CoV-2 Version 2 + "COVID-19": "SARSCOV2FLURSV", # Xpert Xpress SARS-CoV-2 Version 2 + "17.3.1 TCM COVID-19": "SARSCOV2FLURSV", # Xpert Xpress SARS-CoV-2 Version 2 + "17.3.2 PCR COVID-19": "SARSCOV2FLURSV", # Xpert Xpress SARS-CoV-2 Version 2 "E.2.5 HCV TCM": "HCV", "18.1.1 TCM HCV": "HCV", - "18.1.2 TCM HIV VIRAL LOAD": "HIV-1_VL", + "18.1.2 TCM HIV VIRAL LOAD": "HIV1-VL", "18.1.4 TCM HPV": "HCV", "7.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTBRIF", "5.3.8 KULTUR TBC MGIT (AUTOMATIC)": "MTBRIF", @@ -183,7 +183,7 @@ GENEXPERT_TEST_MAPPING = { } GENEXPERT_IP_CAPABILITIES = { - "10.10.120.75": ["MTBRIF", "MTBRIF_ULTRA2", "MTB-XDR", "HIV-1_VL", "COV-2 2"], + "10.10.120.75": ["MTBRIF", "HBVVL", "HIV1-VL", "MTB-XDR", "HCV VL", "SARSCOV2FLURSV"], "10.10.120.13": ["HCV", "HBV"], "10.10.120.73": ["MTBRIF"] } @@ -455,7 +455,7 @@ def get_genexpert_query_orders(ip_addr, hl7_msg): base_orders = session.query(PaslabOrder).filter( (flag_attr == False) | (flag_attr == None) - ).order_by(PaslabOrder.rtglast.desc().nullslast(), PaslabOrder.urut.desc()).all() + ).order_by(PaslabOrder.urut.asc()).all() requested_sample_id = (param_2 or param_1).strip() if (param_2 or param_1) else "" if requested_sample_id and requested_sample_id.upper() != "ALL": @@ -611,7 +611,7 @@ def create_genexpert_astm_order_message(orders, ip_addr=None, query_tag=""): f"assay_code={assay_code}, assay_source={assay_source}, capability_match={capability_match}" ) - records.append(f"P|{index}|{patient_id}||{patient_id}|{patient_name}|||{sex}") + records.append(f"P|{index}|{patient_name}||{patient_id}|{patient_name}|||{sex}") records.append(f"O|1|{sample_id}||^^^{assay_code}|R|{order_ts}|||||||||ORH||||||||||A") records.append("L|1|N") @@ -672,7 +672,7 @@ def send_all_orders_astm(conn, ip_addr, astm_msg, response_framing="astm"): base_orders = session.query(PaslabOrder).filter( (flag_attr == False) | (flag_attr == None) - ).order_by(PaslabOrder.rtglast.desc().nullslast(), PaslabOrder.urut.desc()).all() + ).order_by(PaslabOrder.urut.asc()).all() selected_orders = [] if requested_sample_id and requested_sample_id.upper() != "ALL": @@ -2460,18 +2460,12 @@ def parse_genexpert_astm_records(astm_string, device_name): alat=device_name, ) session.add(new_result) - source_ip = str(device_name or "").replace("GeneXpert-", "").strip() - flag_name = get_flag_by_device(source_ip) - if flag_name: - order = session.query(PaslabOrder).filter(PaslabOrder.rnoreg == seq_no_safe).first() - if order: - setattr(order, flag_name, True) - print(f"[GENEXPERT] Hasil ASTM diterima, set {flag_name}=TRUE untuk {seq_no_safe}") session.commit() print(f"[GENEXPERT-DB-SUCCESS] Hasil lab untuk Order {seq_no_safe} berhasil disimpan ke LisPhoenix!") except Exception as e: print(f"[GENEXPERT-PARSER-ERROR] Gagal memparsing/menyimpan hasil: {e}") + import traceback traceback.print_exc() def manage_tcp_server():