Enable GenExpert auto polling and flag eligible TCM orders

This commit is contained in:
Dwi Swandhana
2026-04-10 19:45:42 +07:00
parent 6e9951adf6
commit e1a3598472
2 changed files with 250 additions and 27 deletions
@@ -1255,7 +1255,13 @@ class FrontpageController extends Controller
'nmpendaftar' => $clientIp,
'orderid' => $notransaksi,
]);
$pesan = $nofoto;
$pesan = $nofoto;
$daftaridtcm = [79,80,81,82,83,84,203, 14,28,55,62,94,104,105,124,138,146];
if (in_array($poli_id, $daftaridtcm)){
$genexpert = true;
} else {
$genexpert = false;
}
Paslab::updateOrCreate(
[
'rnoreg' => $nofoto,
@@ -1278,9 +1284,9 @@ class FrontpageController extends Controller
'flg_vitek2' => false,
'flg_bd1' => false,
'flg_bd2' => false,
'flg_gxp1' => false,
'flg_gxp2' => false,
'flg_gxp3' => false,
'flg_gxp1' => $genexpert,
'flg_gxp2' => $genexpert,
'flg_gxp3' => $genexpert,
'flg_vitek3' => false,
]
);
@@ -1478,6 +1484,13 @@ class FrontpageController extends Controller
'orderid' => $notransaksi,
]);
$pesan = $nofoto;
$daftaridtcm = [79,80,81,82,83,84,203, 14,28,55,62,94,104,105,124,138,146];
if (in_array($poli_id, $daftaridtcm)){
$genexpert = true;
} else {
$genexpert = false;
}
Paslab::updateOrCreate(
[
'rnoreg' => $nofoto,
@@ -1500,9 +1513,9 @@ class FrontpageController extends Controller
'flg_vitek2' => false,
'flg_bd1' => false,
'flg_bd2' => false,
'flg_gxp1' => false,
'flg_gxp2' => false,
'flg_gxp3' => false,
'flg_gxp1' => $genexpert,
'flg_gxp2' => $genexpert,
'flg_gxp3' => $genexpert,
'flg_vitek3' => false,
]
);
@@ -1729,6 +1742,13 @@ class FrontpageController extends Controller
'pendaftar' => Session('previlage'),
'nmpendaftar' => Session('nama'),
]);
$daftaridtcm = [79,80,81,82,83,84,203, 14,28,55,62,94,104,105,124,138,146];
if (in_array($poli_id, $daftaridtcm)){
$genexpert = true;
} else {
$genexpert = false;
}
Paslab::updateOrCreate(
[
'rnoreg' => $nofoto,
@@ -1751,9 +1771,9 @@ class FrontpageController extends Controller
'flg_vitek2' => false,
'flg_bd1' => false,
'flg_bd2' => false,
'flg_gxp1' => false,
'flg_gxp2' => false,
'flg_gxp3' => false,
'flg_gxp1' => $genexpert,
'flg_gxp2' => $genexpert,
'flg_gxp3' => $genexpert,
'flg_vitek3' => false,
]
);
@@ -1876,9 +1896,9 @@ class FrontpageController extends Controller
'flg_vitek2' => false,
'flg_bd1' => false,
'flg_bd2' => false,
'flg_gxp1' => false,
'flg_gxp2' => false,
'flg_gxp3' => false,
'flg_gxp1' => $genexpert,
'flg_gxp2' => $genexpert,
'flg_gxp3' => $genexpert,
'flg_vitek3' => false,
]
);
@@ -1945,9 +1965,9 @@ class FrontpageController extends Controller
'flg_vitek2' => false,
'flg_bd1' => false,
'flg_bd2' => false,
'flg_gxp1' => false,
'flg_gxp2' => false,
'flg_gxp3' => false,
'flg_gxp1' => $genexpert,
'flg_gxp2' => $genexpert,
'flg_gxp3' => $genexpert,
'flg_vitek3' => false,
]
);
+214 -11
View File
@@ -39,10 +39,15 @@ active_genexpert_connections = {}
connection_lock = threading.Lock()
pending_result_queries = {}
pending_query_lock = threading.Lock()
scheduled_result_queries = {}
scheduled_result_query_lock = threading.Lock()
# Network Configuration
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
HTTP_API_PORT = 6002 # Endpoint trigger dari Laravel -> Python
GENEXPERT_RESULT_QUERY_INITIAL_DELAY_SECONDS = 60
GENEXPERT_RESULT_QUERY_INTERVAL_SECONDS = 120
GENEXPERT_RESULT_QUERY_MAX_DURATION_SECONDS = 21600
# Mapping Flag ke IP Address GeneXpert
# Pastikan IP ini SESUAI dengan settingan "Server IP" di masing-masing alat (Client Mode)
TARGET_MAPPING = {
@@ -220,6 +225,7 @@ def get_pending_orders(ip_addr):
def send_all_orders(conn, ip_addr, hl7_msg, msg_id):
orders = get_pending_orders(ip_addr)
session = SessionLocal()
scheduled_orders = []
try:
if not orders:
print(f"[GENEXPERT] Tidak ada order pending untuk {ip_addr}")
@@ -242,8 +248,19 @@ def send_all_orders(conn, ip_addr, hl7_msg, msg_id):
flag = get_flag_by_device(ip_addr)
setattr(order, flag, True)
session.add(order)
scheduled_orders.append({
"accnumber": str(order.rnoreg or "").strip(),
"register_no": str(order.rnoreg or "").strip(),
"target_ip": ip_addr,
})
session.commit()
for scheduled_order in scheduled_orders:
schedule_result_query_for_order(
accnumber=scheduled_order["accnumber"],
register_no=scheduled_order["register_no"],
target_ip=scheduled_order["target_ip"],
)
finally:
session.close()
@@ -257,6 +274,16 @@ def extract_msg_control_id(hl7_message):
except:
return None
def extract_message_type(hl7_message):
try:
segments = hl7_message.split('\r')
msh = segments[0].split('|')
if len(msh) > 8:
return msh[8].strip()
return ""
except:
return ""
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.
@@ -270,6 +297,164 @@ def get_active_genexpert_ips():
with connection_lock:
return list(active_genexpert_connections.keys())
def stop_scheduled_result_query(accnumber, reason="completed"):
accnumber = str(accnumber or "").strip()
if not accnumber:
return False
with scheduled_result_query_lock:
state = scheduled_result_queries.get(accnumber)
if not state:
return False
state["status"] = reason
stop_event = state.get("stop_event")
if stop_event:
stop_event.set()
scheduled_result_queries.pop(accnumber, None)
logging.info(f"[GENEXPERT-SCHEDULER] Stop accnumber={accnumber}, reason={reason}")
print(f"[GENEXPERT-SCHEDULER] Stop accnumber={accnumber}, reason={reason}")
return True
def scheduled_result_query_worker(accnumber):
while True:
with scheduled_result_query_lock:
state = scheduled_result_queries.get(accnumber)
if not state:
return
stop_event = state["stop_event"]
target_ip = state.get("target_ip")
register_no = state.get("register_no") or accnumber
attempt = int(state.get("attempt", 0))
created_at = state.get("created_at") or datetime.datetime.now()
max_duration_seconds = max(
int(state.get("max_duration_seconds", GENEXPERT_RESULT_QUERY_MAX_DURATION_SECONDS)),
1
)
age_seconds = max(int((datetime.datetime.now() - created_at).total_seconds()), 0)
if age_seconds >= max_duration_seconds:
state["status"] = "expired"
state["expired_at"] = datetime.datetime.now()
state["age_seconds"] = age_seconds
stop_event.set()
scheduled_result_queries.pop(accnumber, None)
logging.info(
f"[GENEXPERT-SCHEDULER] Expired accnumber={accnumber}, "
f"age={age_seconds}s, max_duration={max_duration_seconds}s"
)
print(
f"[GENEXPERT-SCHEDULER] Expired accnumber={accnumber}, "
f"age={age_seconds}s, max_duration={max_duration_seconds}s"
)
return
next_delay = max(
int(
state.get(
"initial_delay_seconds" if attempt == 0 else "interval_seconds",
GENEXPERT_RESULT_QUERY_INITIAL_DELAY_SECONDS if attempt == 0 else GENEXPERT_RESULT_QUERY_INTERVAL_SECONDS
)
),
1
)
if stop_event.wait(next_delay):
return
with scheduled_result_query_lock:
state = scheduled_result_queries.get(accnumber)
if not state:
return
state["attempt"] = int(state.get("attempt", 0)) + 1
state["last_requested_at"] = datetime.datetime.now()
attempt_no = state["attempt"]
logging.info(
f"[GENEXPERT-SCHEDULER] Trigger query hasil accnumber={accnumber}, "
f"register_no={register_no}, target_ip={target_ip}, attempt={attempt_no}"
)
print(
f"[GENEXPERT-SCHEDULER] Trigger query hasil accnumber={accnumber}, "
f"register_no={register_no}, target_ip={target_ip}, attempt={attempt_no}"
)
result = trigger_result_query_to_genexpert(
accnumber=accnumber,
register_no=register_no,
target_ip=target_ip,
wait_seconds=0,
)
with scheduled_result_query_lock:
state = scheduled_result_queries.get(accnumber)
if not state:
return
state["last_result"] = result
def schedule_result_query_for_order(
accnumber,
register_no,
target_ip=None,
initial_delay_seconds=GENEXPERT_RESULT_QUERY_INITIAL_DELAY_SECONDS,
interval_seconds=GENEXPERT_RESULT_QUERY_INTERVAL_SECONDS,
max_duration_seconds=GENEXPERT_RESULT_QUERY_MAX_DURATION_SECONDS,
):
accnumber = str(accnumber or "").strip()
register_no = str(register_no or accnumber).strip()
target_ip = str(target_ip or "").strip() or None
if not accnumber:
logging.warning("[GENEXPERT-SCHEDULER] Jadwal query hasil dilewati karena accnumber kosong.")
print("[GENEXPERT-SCHEDULER] Jadwal query hasil dilewati karena accnumber kosong.")
return False
with scheduled_result_query_lock:
existing = scheduled_result_queries.get(accnumber)
if existing:
existing["register_no"] = register_no
existing["target_ip"] = target_ip
existing["initial_delay_seconds"] = initial_delay_seconds
existing["interval_seconds"] = interval_seconds
existing["max_duration_seconds"] = max_duration_seconds
existing["status"] = "active"
logging.info(f"[GENEXPERT-SCHEDULER] Jadwal sudah aktif untuk accnumber={accnumber}")
print(f"[GENEXPERT-SCHEDULER] Jadwal sudah aktif untuk accnumber={accnumber}")
return True
stop_event = threading.Event()
worker = threading.Thread(
target=scheduled_result_query_worker,
args=(accnumber,),
name=f"GeneXpertResultQuery-{accnumber}",
daemon=True,
)
scheduled_result_queries[accnumber] = {
"register_no": register_no,
"target_ip": target_ip,
"initial_delay_seconds": initial_delay_seconds,
"interval_seconds": interval_seconds,
"max_duration_seconds": max_duration_seconds,
"attempt": 0,
"status": "active",
"created_at": datetime.datetime.now(),
"stop_event": stop_event,
"thread_name": worker.name,
}
worker.start()
logging.info(
f"[GENEXPERT-SCHEDULER] Jadwal dibuat accnumber={accnumber}, "
f"register_no={register_no}, target_ip={target_ip}, "
f"initial_delay={initial_delay_seconds}s, interval={interval_seconds}s, "
f"max_duration={max_duration_seconds}s"
)
print(
f"[GENEXPERT-SCHEDULER] Jadwal dibuat accnumber={accnumber}, "
f"register_no={register_no}, target_ip={target_ip}, "
f"initial_delay={initial_delay_seconds}s, interval={interval_seconds}s, "
f"max_duration={max_duration_seconds}s"
)
return True
def trigger_result_query_to_genexpert(accnumber, register_no, target_ip=None, wait_seconds=20):
active_ips = get_active_genexpert_ips()
if not active_ips:
@@ -395,6 +580,7 @@ def parse_hl7_result(conn, msg_id, hl7_message, device_name="GeneXpert"):
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()
@@ -408,6 +594,8 @@ def parse_hl7_result(conn, msg_id, hl7_message, device_name="GeneXpert"):
# --- 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:
@@ -456,19 +644,23 @@ def parse_hl7_result(conn, msg_id, hl7_message, device_name="GeneXpert"):
# 4. Validasi Data Penting
if not sample_id:
source_ip = ""
try:
source_ip = str(conn.getpeername()[0]).strip()
except Exception:
source_ip = str(device_name).replace("GeneXpert-", "").replace(",", " ").strip()
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)
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.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.")
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
@@ -506,6 +698,7 @@ def parse_hl7_result(conn, msg_id, hl7_message, device_name="GeneXpert"):
session.commit()
logging.info(f"[DB] Berhasil simpan ke LisPhoenix: {sample_id}")
print(f"[DB] Berhasil simpan ke LisPhoenix: {sample_id}")
stop_scheduled_result_query(sample_id, reason="result-received")
if pending_event:
pending_event.set()
@@ -1577,6 +1770,11 @@ def handle_genexpert_client(conn, addr):
session.commit()
logging.info(f"[UPDATE] {target_flag_col} diset TRUE untuk {search_sample_id}")
print(f"[UPDATE] {target_flag_col} diset TRUE untuk {search_sample_id}")
schedule_result_query_for_order(
accnumber=str(order.rnoreg or "").strip(),
register_no=str(order.rnoreg or "").strip(),
target_ip=client_ip,
)
else:
logging.warning(f"[NOT FOUND/ALREADY SENT] Tidak ada order baru untuk {search_sample_id} di kolom {target_flag_col}")
@@ -1621,6 +1819,11 @@ def handle_genexpert_client(conn, addr):
send_all_orders(conn, addr[0], clean_hl7, msg_id)
continue
elif "QCN^J01" in clean_hl7:
logging.info(f"[GENEXPERT] Menerima konfirmasi query dari {addr[0]}.")
print(f"[GENEXPERT] Menerima konfirmasi query dari {addr[0]}.")
continue
# Logging sample data (50 karakter)
print(f"[GenExpert_TCP] Pesan Lengkap Diterima: {clean_hl7[:50]}...")