update
This commit is contained in:
@@ -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
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user