This commit is contained in:
Dwi Swandhana
2026-05-19 17:51:09 +07:00
parent 930cf48cfe
commit a9c6cf12ca
2 changed files with 136 additions and 405 deletions
+121 -384
View File
@@ -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: "<STX>",
0x03: "<ETX>",
0x04: "<EOT>",
0x05: "<ENQ>",
0x06: "<ACK>",
0x0D: "<CR>",
0x0A: "<LF>",
0x17: "<ETB>",
0x15: "<NAK>",
0x1C: "<FS>",
0x0B: "<VT>",
}
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: "<STX>",
0x03: "<ETX>",
0x04: "<EOT>",
0x05: "<ENQ>",
0x06: "<ACK>",
0x0D: "<CR>",
0x0A: "<LF>",
0x17: "<ETB>",
0x15: "<NAK>",
0x1C: "<FS>",
0x0B: "<VT>",
}
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):
"""
+15 -21
View File
@@ -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():