This commit is contained in:
Dwi Swandhana
2026-02-05 05:59:37 +07:00
parent 6f5f9c179e
commit 539d0a8b50
2 changed files with 184 additions and 25 deletions
@@ -126,8 +126,6 @@ class JsonTransferController extends Controller
return back()->with('success', 'Import berhasil!');
}
public function pull(Request $request){
if (session('previlage') !== 'developer') {
abort(403, 'Akses ditolak. Anda tidak memiliki izin untuk menjalankan git pull.');
+184 -23
View File
@@ -52,7 +52,11 @@ GENEXPERT_TEST_MAPPING = {
# Pastikan ini benar, atau sesuaikan jika itu typo di dokumen.
"HBV VL": "HCV",
}
GENEXPERT_IP_CAPABILITIES = {
"10.10.120.75": ["MTB-RIF", "MTB-RIF_ULTRA2", "MTB-XDR", "HIV-1_VL", "COV-2 2"],
"10.10.120.74": ["HCV", "HBV"],
"10.10.120.73": [] # Belum ada yang aktif
}
# Default code jika nama tes di database tidak dikenali
DEFAULT_GXP_CODE = "MTB-RIF"
@@ -458,7 +462,65 @@ def send_order_response(conn, hl7_msg, msg_id=None):
conn.sendall(mllp.encode('utf-8'))
print(f"[GENEXPERT] >> RSP^Z03 terkirim untuk ORDER {order.urut}")
def create_hl7_dsr_response(order, msg_control_id, qrd_segment):
"""
Membuat pesan balasan DSR^Q03 (Data Response) untuk GeneXpert.
"""
timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
# --- 1. HEADER (MSH) ---
# Field 9 (Message Type) adalah DSR^Q03
# Field 10 (Control ID) kita generate baru
# Field 6 (Receiving Fac) harusnya GeneXpert
resp_control_id = f"RESP{timestamp}"
msh = f"MSH|^~\\&|LIS|LAB|GeneXpert|Cepheid|{timestamp}||DSR^Q03|{resp_control_id}|P|2.5"
# --- 2. ACKNOWLEDGEMENT (MSA) ---
# AA = Application Accept (Kita mengerti pertanyaannya)
# msg_control_id = ID dari pesan QRY yang dikirim GeneXpert (Supaya dia tahu ini jawaban untuk pertanyaan yg mana)
msa = f"MSA|AA|{msg_control_id}"
# --- 3. QUERY DEFINITION (QRD) ---
# Kita kembalikan segmen QRD yang dikirim alat (Echo back)
# qrd_segment harus string raw dari pesan masuk
qrd = qrd_segment
# --- 4. QUERY RESPONSE STATUS (QRF) ---
# Opsional, tapi baik untuk konfirmasi
qrf = f"QRF|LIS|{timestamp}||||"
# --- 5. DATA PASIEN & ORDER (Jika Order Ditemukan) ---
if order:
# Mapping Data
sample_id = str(order.rnoreg)
pid_norm = order.norm if order.norm else ""
pid_nama = order.nama if order.nama else "No Name"
# Gender
raw_gender = str(order.rjenis).upper()
pid_gender = 'M' if 'LAKI' in raw_gender or raw_gender == 'L' else 'F'
# Test Code (Pakai Mapping yang tadi kita buat)
nama_tes_db = str(order.tes).strip() if order.tes else ""
test_code = GENEXPERT_TEST_MAPPING.get(nama_tes_db, DEFAULT_GXP_CODE) # Default: MTB-RIF
# Segmen Data
# DSP/PID/ORC/OBR tergantung setting alat.
# GeneXpert standar biasanya terima format ORM di dalam DSR atau sequence PID-ORC-OBR
pid = f"PID|1||{pid_norm}||{pid_nama}|||{pid_gender}"
orc = f"ORC|NW|{sample_id}"
obr = f"OBR|1|{sample_id}||{test_code}^{nama_tes_db}^L|||{timestamp}"
# Gabungkan
return f"{msh}\r{msa}\r{qrd}\r{qrf}\r{pid}\r{orc}\r{obr}\r"
else:
# Jika TIDAK ADA ORDER (Not Found)
# Kita kirim QAK (Query Acknowledge) dengan status NF (Not Found) di QRF/QAK
# Atau cukup kirim MSA|AA tapi tanpa segmen Order
return f"{msh}\r{msa}\r{qrd}\r{qrf}\r"
# ==========================================
# 4. NETWORK & COMMUNICATION LOGIC
# ==========================================
@@ -528,11 +590,6 @@ def manage_tcp_server():
logging.critical(f"[TCP-SERVER] Gagal Start: {e}")
print(f"[TCP-SERVER] Gagal Start: {e}")
def run_flask():
"""Wrapper untuk menjalankan Flask di Thread"""
# use_reloader=False WAJIB agar tidak membuat duplikat proses
app.run(host='0.0.0.0', port=5000, debug=False, use_reloader=False)
def send_mllp_message(sock, hl7_msg):
"""Membungkus pesan HL7 dengan MLLP (Minimal Lower Layer Protocol)"""
# Start Block: 0x0B (<VT>)
@@ -606,7 +663,127 @@ def handle_genexpert_client(conn, addr):
if "MSH|" in temp_str:
msh_index = temp_str.find("MSH|")
clean_hl7 = temp_str[msh_index:]
if "QBP^Z03" in 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"
# ==========================================
# SKENARIO 1: ALAT BERTANYA (QUERY / QRY)
# ==========================================
if "QRY^" in clean_hl7 or "QRY|" in clean_hl7:
client_ip = addr[0]
logging.info(f"[QUERY] Request dari IP: {client_ip} (Control ID: {incoming_control_id})")
print(f"[QUERY] Request dari IP: {client_ip} (Control ID: {incoming_control_id})")
# 1. Tentukan Kolom Flag mana yang harus dicek berdasarkan IP
# Kita balik mappingnya: IP -> Nama Kolom
target_flag_col = None
for col_name, ip_addr in TARGET_MAPPING.items():
if ip_addr == client_ip:
target_flag_col = col_name
break
if not target_flag_col:
logging.warning(f"[DENIED] IP {client_ip} tidak terdaftar di TARGET_MAPPING. Abaikan.")
print(f"[DENIED] IP {client_ip} tidak terdaftar di TARGET_MAPPING. Abaikan.")
# Kirim jawaban kosong agar alat tidak hang
reply_msg = create_hl7_dsr_response(None, incoming_control_id, "")
conn.sendall(f"\x0b{reply_msg}\x1c\r".encode('utf-8'))
continue # Skip proses selanjutnya
logging.info(f"[TARGET] IP {client_ip} akan mengecek kolom: {target_flag_col}")
print(f"[TARGET] IP {client_ip} akan mengecek kolom: {target_flag_col}")
# 2. Cari Sample ID di pesan QRY
search_sample_id = None
qrd_line = ""
for line in lines:
if line.startswith("QRD|"):
qrd_line = line
fields = line.split('|')
if len(fields) > 8:
search_sample_id = fields[8]
break
if search_sample_id:
session = SessionLocal()
try:
# 3. Query Database Dinamis (Pakai getattr)
# Kita cari RN yang cocok DAN (Flag Kolom Tersebut False ATAU Null)
# Ambil atribut kolom Paslab berdasarkan nama string (misal: Paslab.flg_gxp3)
flag_attr = getattr(PaslabOrder, target_flag_col, None)
if flag_attr is None:
logging.error(f"Kolom '{target_flag_col}' tidak ditemukan di Model Paslab!")
print(f"Kolom '{target_flag_col}' tidak ditemukan di Model Paslab!")
raise Exception("Invalid Column Name")
logging.info(f"[DB LOOKUP] Mencari {search_sample_id} dimana {target_flag_col} == False")
print(f"[DB LOOKUP] Mencari {search_sample_id} dimana {target_flag_col} == False")
order = session.query(PaslabOrder).filter(
PaslabOrder.rnoreg == search_sample_id,
(flag_attr == False) | (flag_attr == None)
).first()
# Variabel kontrol kirim
send_order = False
if order:
# (Opsional) Validasi Kapabilitas Mesin di sini jika perlu
# ...
send_order = True
# 4. Kirim Respon
if send_order and order:
logging.info(f"[FOUND] Order ditemukan: {order.nama}. Mengirim ke {client_ip}...")
print(f"[FOUND] Order ditemukan: {order.nama}. Mengirim ke {client_ip}...")
reply_msg = create_hl7_dsr_response(order, incoming_control_id, qrd_line)
conn.sendall(f"\x0b{reply_msg}\x1c\r".encode('utf-8'))
# 5. UPDATE FLAG DINAMIS (PENTING)
# Set kolom yang sesuai (misal flg_gxp3) menjadi True
setattr(order, target_flag_col, True)
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}")
else:
logging.warning(f"[NOT FOUND/ALREADY SENT] Tidak ada order baru untuk {search_sample_id} di kolom {target_flag_col}")
print(f"[NOT FOUND/ALREADY SENT] Tidak ada order baru untuk {search_sample_id} di kolom {target_flag_col}")
# Kirim DSR Kosong (NF)
reply_msg = create_hl7_dsr_response(None, incoming_control_id, qrd_line)
conn.sendall(f"\x0b{reply_msg}\x1c\r".encode('utf-8'))
except Exception as db_err:
logging.error(f"Database Error: {db_err}")
print(f"Database Error: {db_err}")
session.rollback()
finally:
session.close()
# ==========================================
# SKENARIO 2: ALAT KIRIM HASIL (RESULT / ORU)
# ==========================================
elif "ORU^" in clean_hl7:
logging.info(f"[RESULT] Menerima Hasil Lab.")
# 1. Parse dan Simpan Hasil (Panggil fungsi parser Anda)
parse_hl7_result(clean_hl7, msg_id, clean_hl7, device_name=f"GeneXpert-{addr[0]}")
# 2. Kirim ACK (Terima Kasih)
ack_time = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
ack_msg = f"MSH|^~\\&|LIS|LAB|GeneXpert|Cepheid|{ack_time}||ACK|{incoming_control_id}|P|2.5\rMSA|AA|{incoming_control_id}\r"
full_ack = f"\x0b{ack_msg}\x1c\r"
conn.sendall(full_ack.encode('utf-8'))
logging.info(f"[ACK SENT] Untuk hasil ID {incoming_control_id}")
print(f"[ACK SENT] Untuk hasil ID {incoming_control_id}")
# ==========================================
# SKENARIO 3: Handle Pesan Apa Adanya
# ==========================================
elif "QBP^Z03" in clean_hl7:
print("[GENEXPERT] Alat meminta ORDER")
msg_id = extract_msg_control_id(clean_hl7)
send_all_orders(conn, addr[0], clean_hl7, msg_id)
@@ -1599,16 +1776,6 @@ def manage_serial_port(config):
else:
logging.error(f"Tipe alat tidak diketahui: '{device_type}' untuk port {config.get('port')}. Thread dihentikan.")
print(f"Tipe alat tidak diketahui: '{device_type}' untuk port {config.get('port')}. Thread dihentikan.")
# ==========================================
# 6. FLASK API (Opsional)
# ==========================================
@app.route('/status', methods=['GET'])
def status():
with connection_lock:
return jsonify({
"active_connections": list(active_genexpert_connections.keys()),
"total_connected": len(active_genexpert_connections)
})
# ==========================================
# 7. MAIN EXECUTION
@@ -1641,12 +1808,6 @@ if __name__ == "__main__":
t_tcp.start()
all_threads.append(t_tcp)
# 4. Start Thread Flask Web Server (API)
# PENTING: Flask juga harus di thread agar tidak memblokir monitoring
t_flask = threading.Thread(target=run_flask, name="WebServer-Flask", daemon=True)
t_flask.start()
all_threads.append(t_flask)
# 5. LOOP UTAMA (Keep-Alive & Monitoring)
# Ini sekarang bisa berjalan karena Flask sudah dipindah ke thread
try: