update
This commit is contained in:
No files matched your search
@@ -126,8 +126,6 @@ class JsonTransferController extends Controller
|
|||||||
|
|
||||||
return back()->with('success', 'Import berhasil!');
|
return back()->with('success', 'Import berhasil!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function pull(Request $request){
|
public function pull(Request $request){
|
||||||
if (session('previlage') !== 'developer') {
|
if (session('previlage') !== 'developer') {
|
||||||
abort(403, 'Akses ditolak. Anda tidak memiliki izin untuk menjalankan git pull.');
|
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.
|
# Pastikan ini benar, atau sesuaikan jika itu typo di dokumen.
|
||||||
"HBV VL": "HCV",
|
"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 code jika nama tes di database tidak dikenali
|
||||||
DEFAULT_GXP_CODE = "MTB-RIF"
|
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'))
|
conn.sendall(mllp.encode('utf-8'))
|
||||||
print(f"[GENEXPERT] >> RSP^Z03 terkirim untuk ORDER {order.urut}")
|
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
|
# 4. NETWORK & COMMUNICATION LOGIC
|
||||||
# ==========================================
|
# ==========================================
|
||||||
@@ -528,11 +590,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_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):
|
def send_mllp_message(sock, hl7_msg):
|
||||||
"""Membungkus pesan HL7 dengan MLLP (Minimal Lower Layer Protocol)"""
|
"""Membungkus pesan HL7 dengan MLLP (Minimal Lower Layer Protocol)"""
|
||||||
# Start Block: 0x0B (<VT>)
|
# Start Block: 0x0B (<VT>)
|
||||||
@@ -606,7 +663,127 @@ def handle_genexpert_client(conn, addr):
|
|||||||
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:]
|
||||||
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")
|
print("[GENEXPERT] Alat meminta ORDER")
|
||||||
msg_id = extract_msg_control_id(clean_hl7)
|
msg_id = extract_msg_control_id(clean_hl7)
|
||||||
send_all_orders(conn, addr[0], clean_hl7, msg_id)
|
send_all_orders(conn, addr[0], clean_hl7, msg_id)
|
||||||
@@ -1599,16 +1776,6 @@ def manage_serial_port(config):
|
|||||||
else:
|
else:
|
||||||
logging.error(f"Tipe alat tidak diketahui: '{device_type}' untuk port {config.get('port')}. Thread dihentikan.")
|
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.")
|
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
|
# 7. MAIN EXECUTION
|
||||||
@@ -1641,12 +1808,6 @@ if __name__ == "__main__":
|
|||||||
t_tcp.start()
|
t_tcp.start()
|
||||||
all_threads.append(t_tcp)
|
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)
|
# 5. LOOP UTAMA (Keep-Alive & Monitoring)
|
||||||
# Ini sekarang bisa berjalan karena Flask sudah dipindah ke thread
|
# Ini sekarang bisa berjalan karena Flask sudah dipindah ke thread
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user