update
This commit is contained in:
No files matched your search
@@ -1274,7 +1274,7 @@ class FrontpageController extends Controller
|
|||||||
'orderid' => $notransaksi,
|
'orderid' => $notransaksi,
|
||||||
]);
|
]);
|
||||||
$pesan = $nofoto;
|
$pesan = $nofoto;
|
||||||
$daftaridtcm = [79,80,81,82,83,84,203, 14,28,55,62,94,104,105,124,138,146];
|
$daftaridtcm = [77,78,79,80,81,82,83,84,203, 14,28,55,62,94,104,105,124,138,146];
|
||||||
if (in_array($poli_id, $daftaridtcm)){
|
if (in_array($poli_id, $daftaridtcm)){
|
||||||
$genexpert = false;
|
$genexpert = false;
|
||||||
} else {
|
} else {
|
||||||
@@ -1504,7 +1504,7 @@ class FrontpageController extends Controller
|
|||||||
'orderid' => $notransaksi,
|
'orderid' => $notransaksi,
|
||||||
]);
|
]);
|
||||||
$pesan = $nofoto;
|
$pesan = $nofoto;
|
||||||
$daftaridtcm = [79,80,81,82,83,84,203, 14,28,55,62,94,104,105,124,138,146];
|
$daftaridtcm = [77,78,79,80,81,82,83,84,203, 14,28,55,62,94,104,105,124,138,146];
|
||||||
if (in_array($poli_id, $daftaridtcm)){
|
if (in_array($poli_id, $daftaridtcm)){
|
||||||
$genexpert = false;
|
$genexpert = false;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+253
-148
@@ -14,7 +14,6 @@ import datetime
|
|||||||
import traceback
|
import traceback
|
||||||
import serial # type: ignore
|
import serial # type: ignore
|
||||||
|
|
||||||
from flask import Flask, jsonify, request # type: ignore
|
|
||||||
from sqlalchemy import create_engine, Column, Integer, String, Boolean, Text # type: ignore
|
from sqlalchemy import create_engine, Column, Integer, String, Boolean, Text # type: ignore
|
||||||
from sqlalchemy import DateTime as SqDateTime # type: ignore
|
from sqlalchemy import DateTime as SqDateTime # type: ignore
|
||||||
from sqlalchemy import Date as SqDate # type: ignore
|
from sqlalchemy import Date as SqDate # type: ignore
|
||||||
@@ -54,11 +53,8 @@ ERROR_LOG_KEYWORDS = (
|
|||||||
# 1. KONFIGURASI SISTEM
|
# 1. KONFIGURASI SISTEM
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# Global Variables
|
# Global Variables
|
||||||
app = Flask(__name__)
|
|
||||||
active_genexpert_connections = {}
|
active_genexpert_connections = {}
|
||||||
connection_lock = threading.Lock()
|
connection_lock = threading.Lock()
|
||||||
pending_result_queries = {}
|
|
||||||
pending_query_lock = threading.Lock()
|
|
||||||
scheduled_result_queries = {}
|
scheduled_result_queries = {}
|
||||||
scheduled_result_query_lock = threading.Lock()
|
scheduled_result_query_lock = threading.Lock()
|
||||||
genexpert_query_inflight_by_ip = {}
|
genexpert_query_inflight_by_ip = {}
|
||||||
@@ -66,26 +62,23 @@ genexpert_query_inflight_lock = threading.Lock()
|
|||||||
# Network Configuration
|
# Network Configuration
|
||||||
TCP_LISTENER_PORT = 6001 # PC GeneXpert set ke mode Client, konek ke IP:PORT ini
|
TCP_LISTENER_PORT = 6001 # PC GeneXpert set ke mode Client, konek ke IP:PORT ini
|
||||||
SERVER_HOST = '0.0.0.0' # Listen di semua interface
|
SERVER_HOST = '0.0.0.0' # Listen di semua interface
|
||||||
HTTP_API_PORT = 6002 # Endpoint trigger dari Laravel -> Python
|
|
||||||
|
|
||||||
GENEXPERT_RESPONSE_MODE_DEFAULT = "astm_active"
|
GENEXPERT_RESPONSE_MODE_DEFAULT = "astm_active"
|
||||||
GENEXPERT_RESPONSE_MODE_BY_IP = {
|
GENEXPERT_RESPONSE_MODE_BY_IP = {
|
||||||
# "10.10.120.73": "astm_active",
|
|
||||||
# "10.10.120.75": "hl7_passive",
|
# "10.10.120.75": "hl7_passive",
|
||||||
}
|
}
|
||||||
|
GENEXPERT_HOST_APPLICATION_DEFAULT = "DE002"
|
||||||
|
GENEXPERT_HOST_APPLICATION_BY_IP = {
|
||||||
|
"10.10.120.75": "GE01", # GenExpert Kecil
|
||||||
|
"10.10.120.108": "DE002", # GenExpert Tempat Lama
|
||||||
|
"10.10.120.73": "GE01", # GenExpert Besa
|
||||||
|
}
|
||||||
# Mapping Flag ke IP Address GeneXpert
|
# Mapping Flag ke IP Address GeneXpert
|
||||||
# Pastikan IP ini SESUAI dengan settingan "Server IP" di masing-masing alat (Client Mode)
|
# Pastikan IP ini SESUAI dengan settingan "Server IP" di masing-masing alat (Client Mode)
|
||||||
TARGET_MAPPING = {
|
TARGET_MAPPING = {
|
||||||
'flg_gxp1': '10.10.120.73',
|
'flg_gxp1': '10.10.120.73',
|
||||||
'flg_gxp2': '10.10.120.13',
|
'flg_gxp2': '10.10.120.108',
|
||||||
'flg_gxp3': '10.10.120.75'
|
'flg_gxp3': '10.10.120.75'
|
||||||
}
|
}
|
||||||
GENEXPERT_HOST_APPLICATION_DEFAULT = "GE01"
|
|
||||||
GENEXPERT_HOST_APPLICATION_BY_IP = {
|
|
||||||
"10.10.120.75": "GE01",
|
|
||||||
"10.10.120.13": "DE002",
|
|
||||||
"10.10.120.73": "GE01",
|
|
||||||
}
|
|
||||||
# GeneXpert Configuration
|
# GeneXpert Configuration
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# KONFIGURASI MAPPING TES (DATABASE -> GENEXPERT)
|
# KONFIGURASI MAPPING TES (DATABASE -> GENEXPERT)
|
||||||
@@ -94,43 +87,45 @@ GENEXPERT_HOST_APPLICATION_BY_IP = {
|
|||||||
# Kanan: 'Host Test Code' dari Dokumen Word Anda
|
# Kanan: 'Host Test Code' dari Dokumen Word Anda
|
||||||
GENEXPERT_TEST_MAPPING = {
|
GENEXPERT_TEST_MAPPING = {
|
||||||
# Mapping untuk IP 10.10.120.75 (Multi-Assay)
|
# Mapping untuk IP 10.10.120.75 (Multi-Assay)
|
||||||
"HIV": "HIV1-VL", # Xpert HIV-1 Viral Load XC Version 3
|
"HIV": "HIV1-VL",
|
||||||
"TCM TB": "MTBRIF", # Xpert MTBRIF Assay G4 Version 6
|
"HBV": "HBVVL",
|
||||||
"TCM TB ULTRA": "MTBRIF", # Xpert MTBRIF Ultra Version 4
|
"TCM TB": "MTBRIF",
|
||||||
"TCM TB XDR": "MTB-XDR", # Xpert MTB-XDR Version 1
|
"TCM TB ULTRA": "MTBRIF",
|
||||||
"HCV VL": "HCV", # Xpert HCV Viral Load Version 1
|
"TCM TB XDR": "MTB-XDR 2",
|
||||||
"COVID-19": "SARSCOV2FLURSV", # Xpert Xpress SARS-CoV-2 Version 2
|
"HCV VL": "HCV",
|
||||||
"17.3.1 TCM COVID-19": "SARSCOV2FLURSV", # Xpert Xpress SARS-CoV-2 Version 2
|
"COVID-19": "SARSCOV2FLURSV",
|
||||||
"17.3.2 PCR COVID-19": "SARSCOV2FLURSV", # Xpert Xpress SARS-CoV-2 Version 2
|
"17.3.1 TCM COVID-19": "SARSCOV2FLURSV",
|
||||||
|
"17.3.2 PCR COVID-19": "SARSCOV2FLURSV",
|
||||||
"E.2.5 HCV TCM": "HCV",
|
"E.2.5 HCV TCM": "HCV",
|
||||||
"18.1.1 TCM HCV": "HCV",
|
"18.1.1 TCM HCV": "HCV",
|
||||||
"18.1.2 TCM HIV VIRAL LOAD": "HIV1-VL",
|
"18.1.2 TCM HIV VIRAL LOAD": "HIV1-VL",
|
||||||
"18.1.4 TCM HPV": "HCV",
|
"18.1.4 TCM HPV": "HCV",
|
||||||
"7.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTBRIF",
|
"7.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTBRIF",
|
||||||
"5.3.8 KULTUR TBC MGIT (AUTOMATIC)": "MTBRIF",
|
"5.3.8 KULTUR TBC MGIT (AUTOMATIC)": "MTBRIF",
|
||||||
"5.3.7 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
|
"5.3.7 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
|
||||||
"3.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL) ": "MTB-XDR",
|
"3.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL) ": "MTB-XDR 2",
|
||||||
"2.3.7 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
|
"2.3.7 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
|
||||||
"1.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
|
"1.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
|
||||||
"1.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR",
|
"1.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR 2",
|
||||||
"15.2.1 KULTUR TB MEDIA LJ": "MTB-XDR",
|
"15.2.1 KULTUR TB MEDIA LJ": "MTB-XDR 2",
|
||||||
"8.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR",
|
"8.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR 2",
|
||||||
"9.3.5 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
|
"9.3.5 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
|
||||||
"9.3.6 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR",
|
"9.3.6 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR 2",
|
||||||
"12.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR",
|
"12.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR 2",
|
||||||
"H.2.5 PEMERIKSAAN KULTUR MYCROBACTERIUM TBC": "MTB-XDR",
|
"H.2.5 PEMERIKSAAN KULTUR MYCROBACTERIUM TBC": "MTB-XDR 2",
|
||||||
"12.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
|
"12.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
|
||||||
"11.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
|
"11.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
|
||||||
"10.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR",
|
"10.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR 2",
|
||||||
"10.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
|
"10.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
|
||||||
"3.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR",
|
"3.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR 2",
|
||||||
"15.2.2 KULTUR TB MEDIA MGIT (AUTOMATIC)": "MTB-XDR",
|
"15.2.2 KULTUR TB MEDIA MGIT (AUTOMATIC)": "MTB-XDR 2",
|
||||||
"11.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR",
|
"11.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTB-XDR 2",
|
||||||
"8.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
|
"8.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
|
||||||
"7.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR",
|
"7.3.6 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
|
||||||
"2.3.10 TCM CLAMIDIA TRACHOMATIS / NEISSERIA GONORRHOE": "MTBRIF",
|
"2.3.10 TCM CLAMIDIA TRACHOMATIS / NEISSERIA GONORRHOE": "MTBRIF",
|
||||||
"12.3.8 TCM TB (GENE EXPERT)": "MTBRIF",
|
"12.3.8 TCM TB (GENE EXPERT)": "MTBRIF",
|
||||||
"15.2.3 TCM TB (GENE EXPERT)": "MTBRIF",
|
"15.2.3 TCM TB (GENE EXPERT)": "MTBRIF",
|
||||||
|
"15.2.3 TCM TB (GENE EXPERT) GENE EXPERT": "MTBRIF",
|
||||||
"2.3.9 TCM GENE EXPERT": "MTBRIF",
|
"2.3.9 TCM GENE EXPERT": "MTBRIF",
|
||||||
"3.3.8 TCM GENE EXPERT": "MTBRIF",
|
"3.3.8 TCM GENE EXPERT": "MTBRIF",
|
||||||
"3.3.9 TCM MYCOBACTERIUM TUBERCULOSIS": "MTBRIF",
|
"3.3.9 TCM MYCOBACTERIUM TUBERCULOSIS": "MTBRIF",
|
||||||
@@ -142,11 +137,10 @@ GENEXPERT_TEST_MAPPING = {
|
|||||||
|
|
||||||
}
|
}
|
||||||
GENEXPERT_IP_CAPABILITIES = {
|
GENEXPERT_IP_CAPABILITIES = {
|
||||||
"10.10.120.75": ["MTBRIF", "HBVVL", "HIV1-VL", "MTB-XDR", "HCV VL", "SARSCOV2FLURSV"],
|
"10.10.120.75": ["MTBRIF", "HBVVL", "HIV1-VL", "MTB-XDR 2", "HCV", "SARSCOV2FLURSV"],
|
||||||
"10.10.120.13": ["HCV", "HBV"],
|
"10.10.120.73": ["MTBRIF", "HBVVL", "HIV1-VL", "HCV", "SARSCOV2FLURSV"],
|
||||||
"10.10.120.73": ["MTBRIF"]
|
"10.10.120.108": ["MTBRIF", "HCV", "HIV1-VL", "HBVVL", "SARSCOV2FLURSV"],
|
||||||
}
|
}
|
||||||
# Default code jika nama tes di database tidak dikenali
|
|
||||||
DEFAULT_GXP_CODE = "MTBRIF"
|
DEFAULT_GXP_CODE = "MTBRIF"
|
||||||
|
|
||||||
DEVICE_CONFIGS = [
|
DEVICE_CONFIGS = [
|
||||||
@@ -452,10 +446,6 @@ def manage_tcp_server():
|
|||||||
logging.critical(f"[TCP-SERVER] Gagal Start: {e}")
|
logging.critical(f"[TCP-SERVER] Gagal Start: {e}")
|
||||||
print(f"[TCP-SERVER] Gagal Start: {e}")
|
print(f"[TCP-SERVER] Gagal Start: {e}")
|
||||||
|
|
||||||
def run_http_api_server():
|
|
||||||
print(f"[HTTP-API] Listening di port {HTTP_API_PORT}...")
|
|
||||||
app.run(host=SERVER_HOST, port=HTTP_API_PORT, debug=False, use_reloader=False, threaded=True)
|
|
||||||
|
|
||||||
def get_genexpert_host_application(ip_addr):
|
def get_genexpert_host_application(ip_addr):
|
||||||
ip_addr = str(ip_addr or "").strip()
|
ip_addr = str(ip_addr or "").strip()
|
||||||
host_app = GENEXPERT_HOST_APPLICATION_BY_IP.get(ip_addr, GENEXPERT_HOST_APPLICATION_DEFAULT)
|
host_app = GENEXPERT_HOST_APPLICATION_BY_IP.get(ip_addr, GENEXPERT_HOST_APPLICATION_DEFAULT)
|
||||||
@@ -490,6 +480,28 @@ def parse_genexpert_astm_query(message_text):
|
|||||||
query["query_sample_id"] = fields[2] if len(fields) > 2 else ""
|
query["query_sample_id"] = fields[2] if len(fields) > 2 else ""
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
def summarize_genexpert_astm_orders(records):
|
||||||
|
summaries = []
|
||||||
|
for rec in records:
|
||||||
|
if not rec.startswith("O|"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
fields = rec.split("|")
|
||||||
|
sample_id = fields[2].strip() if len(fields) > 2 else ""
|
||||||
|
container_id = fields[3].strip() if len(fields) > 3 else ""
|
||||||
|
assay_code = ""
|
||||||
|
if len(fields) > 4:
|
||||||
|
assay_parts = [part.strip() for part in fields[4].split("^") if part.strip()]
|
||||||
|
assay_code = assay_parts[-1] if assay_parts else fields[4].strip()
|
||||||
|
priority = fields[5].strip() if len(fields) > 5 else ""
|
||||||
|
action_code = fields[11].strip() if len(fields) > 11 else ""
|
||||||
|
|
||||||
|
summaries.append(
|
||||||
|
f"sample_id={sample_id or '-'}, container_id={container_id or '-'}, "
|
||||||
|
f"assay={assay_code or '-'}, priority={priority or '-'}, action={action_code or '-'}"
|
||||||
|
)
|
||||||
|
return summaries
|
||||||
|
|
||||||
def create_genexpert_astm_order_message(orders, ip_addr=None, query_tag=""):
|
def create_genexpert_astm_order_message(orders, ip_addr=None, query_tag=""):
|
||||||
timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
|
timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
|
||||||
records = [
|
records = [
|
||||||
@@ -521,7 +533,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}"
|
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(f"O|1|{sample_id}||^^^{assay_code}|R|{order_ts}|||||||||ORH||||||||||A")
|
||||||
|
|
||||||
records.append("L|1|N")
|
records.append("L|1|N")
|
||||||
@@ -583,76 +595,166 @@ def send_all_orders_astm(conn, ip_addr, astm_msg, response_framing="astm"):
|
|||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
def process_genexpert_hl7_message(conn, ip_addr, clean_hl7, response_framing):
|
def process_genexpert_hl7_message(conn, ip_addr, clean_hl7, response_framing):
|
||||||
records = parse_astm_records(clean_hl7)
|
# ==========================================================
|
||||||
record_types = [rec.split("|", 1)[0] for rec in records if rec]
|
# 1. BLOK PENANGANAN ASTM (Karena tidak diawali "MSH|")
|
||||||
print(f"[GENEXPERT-ASTM] ip={ip_addr}, record_types={record_types}")
|
# ==========================================================
|
||||||
|
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}")
|
||||||
|
|
||||||
# A. Cek Jika Alat Meminta Order (Query)
|
# A. Cek Jika Alat Meminta Order (Query)
|
||||||
if any(rec.startswith("Q|") for rec in records):
|
if any(rec.startswith("Q|") for rec in records):
|
||||||
print(f"[GENEXPERT-ASTM] Alat meminta ORDER (Q Record)")
|
print(f"[GENEXPERT-ASTM] Alat meminta ORDER (Q Record)")
|
||||||
send_all_orders_astm(conn, ip_addr, clean_hl7, response_framing="astm")
|
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
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# E. GeneXpert dapat mengirim status/echo order ASTM berisi H/P/O/L.
|
||||||
|
# Ini bukan hasil lab karena tidak ada R record, dan bukan query karena tidak ada Q record.
|
||||||
|
if "O" in record_types and set(record_types).issubset({'H', 'P', 'O', 'L'}):
|
||||||
|
order_summaries = summarize_genexpert_astm_orders(records)
|
||||||
|
print(
|
||||||
|
f"[GENEXPERT-ASTM] Menerima status/echo order tanpa hasil dari {ip_addr}. "
|
||||||
|
f"orders={order_summaries}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
log_genexpert_hl7("GENEXPERT-ASTM", ip_addr, clean_hl7, label="pesantidakdikenali")
|
||||||
|
print(f"[GENEXPERT-ASTM] Pesan ASTM tidak dikenali dari {ip_addr}. Isi: {clean_hl7[:50]}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# B. Cek Jika Alat Mengirim Hasil Lab (Result)
|
# ==========================================================
|
||||||
if any(rec.startswith("R|") for rec in records):
|
# 2. BLOK PENANGANAN HL7 (Fallback / Cadangan)
|
||||||
print(f"[RESULT] Menerima Hasil Lab ASTM dari {ip_addr}.")
|
# ==========================================================
|
||||||
# Memanggil fungsi parser Anda untuk menyimpan hasil ke DB
|
log_genexpert_hl7("IN", ip_addr, clean_hl7)
|
||||||
parse_genexpert_astm_records(clean_hl7, device_name=f"GeneXpert-{ip_addr}")
|
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."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
# C. [PERBAIKAN] Cek Jika Alat Mengirim Komentar/Penolakan (Comment)
|
if "ORU^" in clean_hl7:
|
||||||
if any(rec.startswith("C|") for rec in records):
|
# --- [PATCH] CEK APAKAH INI PESAN ERROR / PENOLAKAN ---
|
||||||
# 1. Ekstrak teks komentar untuk ditampilkan di log
|
if "|Error^" in clean_hl7 or "|X\r" in clean_hl7 or "\rTE|" in clean_hl7:
|
||||||
comments = [rec for rec in records if rec.startswith("C|")]
|
print(f"[GENEXPERT-ERROR] Mesin {ip_addr} menolak tes (Test Unknown/Disabled).")
|
||||||
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'
|
# Coba ekstrak NoReg dari segmen SPM agar kita bisa mengunci ordernya (set Flag = True)
|
||||||
rnoreg = None
|
rnoreg_error = None
|
||||||
for rec in records:
|
for line in clean_hl7.split('\r'):
|
||||||
if rec.startswith("O|"):
|
if line.startswith('SPM|'):
|
||||||
o_parts = rec.split('|')
|
parts = line.split('|')
|
||||||
if len(o_parts) > 2:
|
if len(parts) > 2:
|
||||||
rnoreg = o_parts[2].strip()
|
rnoreg_error = parts[2].replace('^', '').strip()
|
||||||
break
|
break
|
||||||
|
|
||||||
# 3. Update Database PaslabOrder
|
if rnoreg_error:
|
||||||
if rnoreg:
|
print(f"[GENEXPERT-ERROR] Mengunci/Membatalkan Order {rnoreg_error} agar tidak terjadi Infinite Loop.")
|
||||||
# Cari kolom flag yang cocok dengan IP yang sedang terkoneksi
|
# (OPSIONAL: Jalankan fungsi update ke DB untuk mengubah flag PaslabOrder menjadi True di sini)
|
||||||
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:
|
# Kirim ACK agar alat berhenti mengirim error
|
||||||
try:
|
ack_msg = create_genexpert_ack_r01_response(clean_hl7, ip_addr=ip_addr)
|
||||||
# Buka sesi database dan update
|
send_genexpert_response(conn, ip_addr, ack_msg, response_framing, label="oru-ack")
|
||||||
with SessionLocal() as session:
|
return # BERHENTI DI SINI. Jangan lanjut ke parse_hl7_result!
|
||||||
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.")
|
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
|
return
|
||||||
|
|
||||||
# D. Jika hanya berisi H dan L tanpa ada transaksi berarti (Status Echo)
|
if "QBP^Z01" in clean_hl7 or "QBP^Z03" in clean_hl7:
|
||||||
if set(record_types).issubset({'H', 'L'}):
|
print("[GENEXPERT] Alat meminta ORDER")
|
||||||
print(f"[GENEXPERT-ASTM] Menerima Heartbeat / Sesi Kosong dari alat.")
|
msg_id = extract_msg_control_id(clean_hl7)
|
||||||
|
send_all_orders(conn, ip_addr, clean_hl7, msg_id, response_framing=response_framing)
|
||||||
return
|
return
|
||||||
|
|
||||||
print(f"[GENEXPERT-ASTM] Pesan ASTM tidak dikenali dari {ip_addr}. Isi: {clean_hl7[:50]}")
|
if "QCN^J01" in clean_hl7:
|
||||||
return
|
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|^~\\&|LIS|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):
|
def handle_genexpert_client(conn, addr):
|
||||||
print(f"[GenExpert_TCP] Koneksi baru dari {addr}")
|
print(f"[GenExpert_TCP] Koneksi baru dari {addr}")
|
||||||
@@ -693,13 +795,15 @@ def handle_genexpert_client(conn, addr):
|
|||||||
# --- 1. HANDLE HANDSHAKE (ENQ) ---
|
# --- 1. HANDLE HANDSHAKE (ENQ) ---
|
||||||
# Jika alat kirim ENQ (\x05/♣), langsung balas ACK (\x06)
|
# Jika alat kirim ENQ (\x05/♣), langsung balas ACK (\x06)
|
||||||
if b'\x05' in buffer:
|
if b'\x05' in buffer:
|
||||||
# logging.info(f"[TCP] Terima ENQ dari {addr}, kirim ACK.")
|
|
||||||
log_genexpert_handshake(addr[0], "ENQ-RX", detail=f"buffer_len={len(buffer)}")
|
log_genexpert_handshake(addr[0], "ENQ-RX", detail=f"buffer_len={len(buffer)}")
|
||||||
conn.sendall(b'\x06')
|
conn.sendall(b'\x06')
|
||||||
log_genexpert_handshake(addr[0], "ACK-TX", detail="reason=enq")
|
log_genexpert_handshake(addr[0], "ACK-TX", detail="reason=enq")
|
||||||
# Hapus ENQ dari buffer agar tidak mengganggu
|
|
||||||
buffer = buffer.replace(b'\x05', b'')
|
|
||||||
|
|
||||||
|
# [PERBAIKAN KURSIS 2]: KOSONGKAN TOTAL BUFFER SAAT ENQ!
|
||||||
|
# Alat meminta sesi baru, pastikan tidak ada sisa pesan lama yang nyangkut
|
||||||
|
buffer = b""
|
||||||
|
pending_astm_hl7 = ""
|
||||||
|
continue # Langsung lanjut ke recv() berikutnya
|
||||||
if b'\x15' in buffer:
|
if b'\x15' in buffer:
|
||||||
log_genexpert_handshake(addr[0], "NAK-BUFFER-CLEAR", detail=f"buffer_len={len(buffer)}")
|
log_genexpert_handshake(addr[0], "NAK-BUFFER-CLEAR", detail=f"buffer_len={len(buffer)}")
|
||||||
buffer = buffer.replace(b'\x15', b'')
|
buffer = buffer.replace(b'\x15', b'')
|
||||||
@@ -719,13 +823,23 @@ def handle_genexpert_client(conn, addr):
|
|||||||
if b'\x1c' in buffer: # Pola MLLP Standard
|
if b'\x1c' in buffer: # Pola MLLP Standard
|
||||||
end_marker_pos = buffer.find(b'\x1c')
|
end_marker_pos = buffer.find(b'\x1c')
|
||||||
msg_complete = True
|
msg_complete = True
|
||||||
elif b'\x03' in buffer: # Pola ASTM (Ada Checksum setelahnya)
|
elif b'\x03' in buffer or b'\x17' in buffer:
|
||||||
# ASTM: <STX>...<ETX>CS<CR><LF>
|
# Cari di index mana letak ETX atau ETB
|
||||||
# Kita cari \x03, lalu ambil ETX + 2 checksum + CRLF secara penuh.
|
pos_etx = buffer.find(b'\x03')
|
||||||
pos = buffer.find(b'\x03')
|
pos_etb = buffer.find(b'\x17')
|
||||||
if len(buffer) >= pos + 5:
|
|
||||||
|
# Tentukan mana yang muncul lebih dulu di buffer
|
||||||
|
pos = -1
|
||||||
|
if pos_etx != -1 and pos_etb != -1:
|
||||||
|
pos = min(pos_etx, pos_etb)
|
||||||
|
else:
|
||||||
|
pos = max(pos_etx, pos_etb)
|
||||||
|
|
||||||
|
# Pastikan kita menerima 5 bytes penuh (ETB/ETX + C1 + C2 + CR + LF)
|
||||||
|
if pos != -1 and len(buffer) >= pos + 5:
|
||||||
end_marker_pos = pos + 5
|
end_marker_pos = pos + 5
|
||||||
msg_complete = True
|
msg_complete = True
|
||||||
|
|
||||||
elif b'\x04' in buffer: # Pola EOT (Putus Koneksi/Selesai)
|
elif b'\x04' in buffer: # Pola EOT (Putus Koneksi/Selesai)
|
||||||
end_marker_pos = buffer.find(b'\x04')
|
end_marker_pos = buffer.find(b'\x04')
|
||||||
msg_complete = True
|
msg_complete = True
|
||||||
@@ -748,6 +862,8 @@ def handle_genexpert_client(conn, addr):
|
|||||||
|
|
||||||
full_message_bytes = buffer[:end_marker_pos]
|
full_message_bytes = buffer[:end_marker_pos]
|
||||||
response_framing = detect_genexpert_message_framing(full_message_bytes)
|
response_framing = detect_genexpert_message_framing(full_message_bytes)
|
||||||
|
if response_framing == "astm":
|
||||||
|
debug_genexpert_astm_frame(addr[0], full_message_bytes, direction="RX")
|
||||||
log_genexpert_handshake(
|
log_genexpert_handshake(
|
||||||
addr[0],
|
addr[0],
|
||||||
"FRAME-COMPLETE",
|
"FRAME-COMPLETE",
|
||||||
@@ -770,22 +886,27 @@ def handle_genexpert_client(conn, addr):
|
|||||||
|
|
||||||
# Decode ke string
|
# Decode ke string
|
||||||
temp_str = full_message_bytes.decode('latin-1', errors='ignore')
|
temp_str = full_message_bytes.decode('latin-1', errors='ignore')
|
||||||
|
astm_text = extract_astm_frame_text(full_message_bytes) if response_framing == "astm" else ""
|
||||||
|
|
||||||
|
# --- SANITIZING (PEMBERSIHAN) ---
|
||||||
|
# Cari MSH pertama
|
||||||
if "MSH|" in temp_str:
|
if "MSH|" in temp_str:
|
||||||
msh_index = temp_str.find("MSH|")
|
msh_index = temp_str.find("MSH|")
|
||||||
clean_hl7 = temp_str[msh_index:]
|
clean_hl7 = temp_str[msh_index:]
|
||||||
elif response_framing == "astm":
|
|
||||||
clean_hl7 = extract_astm_frame_text(full_message_bytes)
|
|
||||||
else:
|
|
||||||
clean_hl7 = temp_str.strip("\x0b\x1c\r\n")
|
|
||||||
|
|
||||||
if clean_hl7:
|
|
||||||
if response_framing == "astm":
|
if response_framing == "astm":
|
||||||
pending_astm_hl7 = clean_hl7
|
pending_astm_hl7 = clean_hl7
|
||||||
pending_astm_framing = response_framing
|
pending_astm_framing = response_framing
|
||||||
log_genexpert_handshake(addr[0], "ASTM-MSG-STORED", detail=f"len={len(clean_hl7)}")
|
log_genexpert_handshake(addr[0], "ASTM-MSG-STORED", detail=f"len={len(clean_hl7)}")
|
||||||
continue
|
continue
|
||||||
process_genexpert_hl7_message(conn, addr[0], clean_hl7, response_framing)
|
process_genexpert_hl7_message(conn, addr[0], clean_hl7, response_framing)
|
||||||
|
elif response_framing == "astm" and astm_text:
|
||||||
|
pending_astm_hl7 = (pending_astm_hl7 or "") + astm_text
|
||||||
|
pending_astm_framing = response_framing
|
||||||
|
log_genexpert_handshake(addr[0], "ASTM-MSG-STORED", detail=f"len={len(pending_astm_hl7)}, mode=records")
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# Jika pesan lengkap tapi tidak ada MSH (misal cuma EOT doang)
|
||||||
|
pass
|
||||||
else:
|
else:
|
||||||
if buffer:
|
if buffer:
|
||||||
head_hex = buffer[:12].hex()
|
head_hex = buffer[:12].hex()
|
||||||
@@ -1297,15 +1418,16 @@ def get_genexpert_response_mode(ip_addr):
|
|||||||
def send_genexpert_response(conn, ip_addr, hl7_message, framing, label=""):
|
def send_genexpert_response(conn, ip_addr, hl7_message, framing, label=""):
|
||||||
log_genexpert_hl7("OUT", ip_addr, hl7_message, label=label)
|
log_genexpert_hl7("OUT", ip_addr, hl7_message, label=label)
|
||||||
log_genexpert_hl7_full("OUT", ip_addr, hl7_message, label=label)
|
log_genexpert_hl7_full("OUT", ip_addr, hl7_message, label=label)
|
||||||
payload = frame_genexpert_response(hl7_message, framing)
|
|
||||||
response_mode = get_genexpert_response_mode(ip_addr)
|
response_mode = get_genexpert_response_mode(ip_addr)
|
||||||
print(
|
|
||||||
f"[GENEXPERT-DEBUG] Send response ip={ip_addr}, framing={framing}, "
|
|
||||||
f"response_mode={response_mode}, label={label}, bytes={len(payload)}"
|
|
||||||
)
|
|
||||||
if framing == "astm" and response_mode == "astm_active":
|
if framing == "astm" and response_mode == "astm_active":
|
||||||
|
print(
|
||||||
|
f"[GENEXPERT-DEBUG] Send response ip={ip_addr}, framing={framing}, "
|
||||||
|
f"response_mode={response_mode}, label={label}, bytes=multi-frame"
|
||||||
|
)
|
||||||
send_genexpert_astm_frame(conn, ip_addr, hl7_message, label=label)
|
send_genexpert_astm_frame(conn, ip_addr, hl7_message, label=label)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
payload = frame_genexpert_response(hl7_message, framing)
|
||||||
if framing == "astm":
|
if framing == "astm":
|
||||||
debug_genexpert_astm_frame(ip_addr, payload, direction="TX", label=label)
|
debug_genexpert_astm_frame(ip_addr, payload, direction="TX", label=label)
|
||||||
else:
|
else:
|
||||||
@@ -1313,6 +1435,10 @@ def send_genexpert_response(conn, ip_addr, hl7_message, framing, label=""):
|
|||||||
f"[GENEXPERT-FRAME-TX] ip={ip_addr}, label={label}, framing={framing}, "
|
f"[GENEXPERT-FRAME-TX] ip={ip_addr}, label={label}, framing={framing}, "
|
||||||
f"hex={_hex_bytes(payload)}, visible={_visible_bytes(payload)}"
|
f"hex={_hex_bytes(payload)}, visible={_visible_bytes(payload)}"
|
||||||
)
|
)
|
||||||
|
print(
|
||||||
|
f"[GENEXPERT-DEBUG] Send response ip={ip_addr}, framing={framing}, "
|
||||||
|
f"response_mode={response_mode}, label={label}, bytes={len(payload)}"
|
||||||
|
)
|
||||||
conn.sendall(payload)
|
conn.sendall(payload)
|
||||||
|
|
||||||
def send_genexpert_transport_ack(conn, ip_addr, framing, reason="frame-received"):
|
def send_genexpert_transport_ack(conn, ip_addr, framing, reason="frame-received"):
|
||||||
@@ -1424,14 +1550,6 @@ def debug_genexpert_order_message(hl7_message, ip_addr=None):
|
|||||||
f"patient_name='{current_patient_name}', raw='{segment}'"
|
f"patient_name='{current_patient_name}', raw='{segment}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
def build_genexpert_result_query(accnumber, msg_control_id):
|
|
||||||
ts = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
|
|
||||||
query_msg = (
|
|
||||||
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
|
|
||||||
|
|
||||||
def get_active_genexpert_ips():
|
def get_active_genexpert_ips():
|
||||||
with connection_lock:
|
with connection_lock:
|
||||||
return list(active_genexpert_connections.keys())
|
return list(active_genexpert_connections.keys())
|
||||||
@@ -1473,14 +1591,6 @@ def schedule_result_query_for_order(accnumber, register_no, target_ip=None, **kw
|
|||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@app.route("/api/genexpert/query-result", methods=["POST"])
|
|
||||||
def api_query_genexpert_result():
|
|
||||||
return jsonify({
|
|
||||||
"ok": False,
|
|
||||||
"message": "Mode GeneXpert pasif aktif. Host hanya menjawab request dari alat.",
|
|
||||||
}), 409
|
|
||||||
|
|
||||||
|
|
||||||
def create_hl7_dsr_response(order, msg_control_id, qrd_segment):
|
def create_hl7_dsr_response(order, msg_control_id, qrd_segment):
|
||||||
"""
|
"""
|
||||||
Membuat pesan balasan DSR^Q03 (Data Response) untuk GeneXpert.
|
Membuat pesan balasan DSR^Q03 (Data Response) untuk GeneXpert.
|
||||||
@@ -3240,12 +3350,7 @@ if __name__ == "__main__":
|
|||||||
myla_inbound_thread.start()
|
myla_inbound_thread.start()
|
||||||
all_threads.append(myla_inbound_thread)
|
all_threads.append(myla_inbound_thread)
|
||||||
|
|
||||||
# 4. Start Thread HTTP API (Trigger dari Laravel)
|
# 4. LOOP UTAMA (Keep-Alive & Monitoring)
|
||||||
t_http = threading.Thread(target=run_http_api_server, name="Manager-HTTP-API", daemon=True)
|
|
||||||
t_http.start()
|
|
||||||
all_threads.append(t_http)
|
|
||||||
|
|
||||||
# 5. LOOP UTAMA (Keep-Alive & Monitoring)
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
print(f"--- Monitoring {len(all_threads)} Threads ---")
|
print(f"--- Monitoring {len(all_threads)} Threads ---")
|
||||||
|
|||||||
+37
-2
@@ -136,6 +136,7 @@ TARGET_MAPPING = {
|
|||||||
GENEXPERT_TEST_MAPPING = {
|
GENEXPERT_TEST_MAPPING = {
|
||||||
# Mapping untuk IP 10.10.120.75 (Multi-Assay)
|
# Mapping untuk IP 10.10.120.75 (Multi-Assay)
|
||||||
"HIV": "HIV1-VL",
|
"HIV": "HIV1-VL",
|
||||||
|
"HBV": "HBVVL",
|
||||||
"TCM TB": "MTBRIF",
|
"TCM TB": "MTBRIF",
|
||||||
"TCM TB ULTRA": "MTBRIF",
|
"TCM TB ULTRA": "MTBRIF",
|
||||||
"TCM TB XDR": "MTB-XDR 2",
|
"TCM TB XDR": "MTB-XDR 2",
|
||||||
@@ -146,7 +147,7 @@ GENEXPERT_TEST_MAPPING = {
|
|||||||
"E.2.5 HCV TCM": "HCV",
|
"E.2.5 HCV TCM": "HCV",
|
||||||
"18.1.1 TCM HCV": "HCV",
|
"18.1.1 TCM HCV": "HCV",
|
||||||
"18.1.2 TCM HIV VIRAL LOAD": "HIV1-VL",
|
"18.1.2 TCM HIV VIRAL LOAD": "HIV1-VL",
|
||||||
"18.1.4 TCM HPV": "HCV",
|
"18.1.4 TCM HPV": "HPV",
|
||||||
"7.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTBRIF",
|
"7.3.7 KULTUR TBC MGIT (AUTOMATIC)": "MTBRIF",
|
||||||
"5.3.8 KULTUR TBC MGIT (AUTOMATIC)": "MTBRIF",
|
"5.3.8 KULTUR TBC MGIT (AUTOMATIC)": "MTBRIF",
|
||||||
"5.3.7 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
|
"5.3.7 KULTUR TBC MEDIA LJ (KONVENSIONAL)": "MTB-XDR 2",
|
||||||
@@ -172,6 +173,7 @@ GENEXPERT_TEST_MAPPING = {
|
|||||||
"2.3.10 TCM CLAMIDIA TRACHOMATIS / NEISSERIA GONORRHOE": "MTBRIF",
|
"2.3.10 TCM CLAMIDIA TRACHOMATIS / NEISSERIA GONORRHOE": "MTBRIF",
|
||||||
"12.3.8 TCM TB (GENE EXPERT)": "MTBRIF",
|
"12.3.8 TCM TB (GENE EXPERT)": "MTBRIF",
|
||||||
"15.2.3 TCM TB (GENE EXPERT)": "MTBRIF",
|
"15.2.3 TCM TB (GENE EXPERT)": "MTBRIF",
|
||||||
|
"15.2.3 TCM TB (GENE EXPERT) GENE EXPERT": "MTBRIF",
|
||||||
"2.3.9 TCM GENE EXPERT": "MTBRIF",
|
"2.3.9 TCM GENE EXPERT": "MTBRIF",
|
||||||
"3.3.8 TCM GENE EXPERT": "MTBRIF",
|
"3.3.8 TCM GENE EXPERT": "MTBRIF",
|
||||||
"3.3.9 TCM MYCOBACTERIUM TUBERCULOSIS": "MTBRIF",
|
"3.3.9 TCM MYCOBACTERIUM TUBERCULOSIS": "MTBRIF",
|
||||||
@@ -185,7 +187,7 @@ GENEXPERT_TEST_MAPPING = {
|
|||||||
GENEXPERT_IP_CAPABILITIES = {
|
GENEXPERT_IP_CAPABILITIES = {
|
||||||
"10.10.120.75": ["MTBRIF", "HBVVL", "HIV1-VL", "MTB-XDR 2", "HCV", "SARSCOV2FLURSV"],
|
"10.10.120.75": ["MTBRIF", "HBVVL", "HIV1-VL", "MTB-XDR 2", "HCV", "SARSCOV2FLURSV"],
|
||||||
"10.10.120.73": ["MTBRIF", "HBVVL", "HIV1-VL", "HCV", "SARSCOV2FLURSV"],
|
"10.10.120.73": ["MTBRIF", "HBVVL", "HIV1-VL", "HCV", "SARSCOV2FLURSV"],
|
||||||
"10.10.120.108": ["HCV", "HIV1-VL", "HBVVL"],
|
"10.10.120.108": ["MTBRIF", "HCV", "HIV1-VL", "HBVVL", "SARSCOV2FLURSV"],
|
||||||
}
|
}
|
||||||
DEFAULT_GXP_CODE = "MTBRIF"
|
DEFAULT_GXP_CODE = "MTBRIF"
|
||||||
|
|
||||||
@@ -379,6 +381,28 @@ def parse_genexpert_astm_query(message_text):
|
|||||||
query["query_sample_id"] = fields[2] if len(fields) > 2 else ""
|
query["query_sample_id"] = fields[2] if len(fields) > 2 else ""
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
def summarize_genexpert_astm_orders(records):
|
||||||
|
summaries = []
|
||||||
|
for rec in records:
|
||||||
|
if not rec.startswith("O|"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
fields = rec.split("|")
|
||||||
|
sample_id = fields[2].strip() if len(fields) > 2 else ""
|
||||||
|
container_id = fields[3].strip() if len(fields) > 3 else ""
|
||||||
|
assay_code = ""
|
||||||
|
if len(fields) > 4:
|
||||||
|
assay_parts = [part.strip() for part in fields[4].split("^") if part.strip()]
|
||||||
|
assay_code = assay_parts[-1] if assay_parts else fields[4].strip()
|
||||||
|
priority = fields[5].strip() if len(fields) > 5 else ""
|
||||||
|
action_code = fields[11].strip() if len(fields) > 11 else ""
|
||||||
|
|
||||||
|
summaries.append(
|
||||||
|
f"sample_id={sample_id or '-'}, container_id={container_id or '-'}, "
|
||||||
|
f"assay={assay_code or '-'}, priority={priority or '-'}, action={action_code or '-'}"
|
||||||
|
)
|
||||||
|
return summaries
|
||||||
|
|
||||||
def detect_genexpert_message_framing(message_bytes):
|
def detect_genexpert_message_framing(message_bytes):
|
||||||
raw = message_bytes or b""
|
raw = message_bytes or b""
|
||||||
if b"\x1c" in raw or raw.startswith(b"\x0b"):
|
if b"\x1c" in raw or raw.startswith(b"\x0b"):
|
||||||
@@ -2569,6 +2593,17 @@ def process_genexpert_hl7_message(conn, ip_addr, clean_hl7, response_framing):
|
|||||||
print(f"[GENEXPERT-ASTM] Menerima Heartbeat / Sesi Kosong dari alat.")
|
print(f"[GENEXPERT-ASTM] Menerima Heartbeat / Sesi Kosong dari alat.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# E. GeneXpert dapat mengirim status/echo order ASTM berisi H/P/O/L.
|
||||||
|
# Ini bukan hasil lab karena tidak ada R record, dan bukan query karena tidak ada Q record.
|
||||||
|
if "O" in record_types and set(record_types).issubset({'H', 'P', 'O', 'L'}):
|
||||||
|
order_summaries = summarize_genexpert_astm_orders(records)
|
||||||
|
print(
|
||||||
|
f"[GENEXPERT-ASTM] Menerima status/echo order tanpa hasil dari {ip_addr}. "
|
||||||
|
f"orders={order_summaries}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
log_genexpert_hl7("GENEXPERT-ASTM", ip_addr, clean_hl7, label="pesantidakdikenali")
|
||||||
print(f"[GENEXPERT-ASTM] Pesan ASTM tidak dikenali dari {ip_addr}. Isi: {clean_hl7[:50]}")
|
print(f"[GENEXPERT-ASTM] Pesan ASTM tidak dikenali dari {ip_addr}. Isi: {clean_hl7[:50]}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user