This commit is contained in:
Dwi Swandhana
2026-08-19 14:27:05 +07:00
parent 5b284fd0d3
commit 97de187bc8
+237 -320
View File
@@ -19,22 +19,64 @@ from sqlalchemy import DateTime as SqDateTime # type: ignore
from sqlalchemy import Date as SqDate # type: ignore
from sqlalchemy.orm import declarative_base, sessionmaker # type: ignore
# Logging Setup
# Konfigurasi logging per hari
def get_app_dir():
"""
Mengembalikan direktori aplikasi.
Python:
folder tempat app.py berada
PyInstaller EXE:
folder tempat app.exe berada
"""
if getattr(sys, "frozen", False):
return os.path.dirname(os.path.abspath(sys.executable))
return os.path.dirname(os.path.abspath(__file__))
APP_DIR = get_app_dir()
THREAD_LOG_DIR = os.path.join(APP_DIR, "thread_logs")
APP_LOG_FILE = os.path.join(APP_DIR, "app.log")
thread_log_lock = threading.Lock()
# Buat folder sejak startup
try:
os.makedirs(THREAD_LOG_DIR, exist_ok=True)
except Exception as exc:
builtins.print(
f"[LOG-INIT-ERROR] Tidak bisa membuat "
f"{THREAD_LOG_DIR}: {exc}"
)
# Logging Setup
log_handler = TimedRotatingFileHandler(
filename="app.log",
filename=APP_LOG_FILE,
when="midnight",
interval=1,
backupCount=7,
encoding="utf-8"
)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(threadName)s - %(message)s')
formatter = logging.Formatter(
'%(asctime)s - %(levelname)s - %(threadName)s - %(message)s'
)
log_handler.setFormatter(formatter)
log_handler.setLevel(logging.ERROR)
logging.basicConfig(level=logging.ERROR, handlers=[log_handler])
logging.basicConfig(
level=logging.ERROR,
handlers=[log_handler]
)
logging.getLogger().setLevel(logging.ERROR)
logging.getLogger("werkzeug").setLevel(logging.ERROR)
THREAD_LOG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "thread_logs")
thread_log_lock = threading.Lock()
ERROR_LOG_KEYWORDS = (
"critical",
@@ -66,7 +108,7 @@ 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
"10.10.120.73": "GE01", # GenExpert Besar
}
# Mapping Flag ke IP Address GeneXpert
# Pastikan IP ini SESUAI dengan settingan "Server IP" di masing-masing alat (Client Mode)
@@ -75,14 +117,14 @@ TARGET_MAPPING = {
'flg_gxp2': '10.10.120.108',
'flg_gxp3': '10.10.120.75'
}
GENEXPERT_ORDER_LOCKS = {
"10.10.120.73": threading.Lock(),
"10.10.120.108": threading.Lock(),
"10.10.120.75": threading.Lock(),
}
# GeneXpert Configuration
# ==========================================
# KONFIGURASI MAPPING TES (DATABASE -> GENEXPERT)
# ==========================================
# Kiri: Nama di kolom 'tes' database Anda
# Kanan: 'Host Test Code' dari Dokumen Word Anda
GENEXPERT_TEST_MAPPING = {
# Mapping untuk IP 10.10.120.75 (Multi-Assay)
"HIV": "HIV1-VL",
"HBV": "HBVVL",
"TCM TB": "MTBRIF",
@@ -130,7 +172,6 @@ GENEXPERT_TEST_MAPPING = {
"8.3.8 TCM TBC (GENE EXPERT)": "MTBRIF",
"10.3.8 TCM TBC (GENE EXPERT)": "MTBRIF",
"11.3.9 TCM TB (GENE EXPERT)": "MTBRIF",
}
GENEXPERT_IP_CAPABILITIES = {
"10.10.120.75": ["MTBRIF", "HBVVL", "HIV1-VL", "MTB-XDR 2", "HCV", "SARSCOV2FLURSV"],
@@ -177,13 +218,16 @@ active_serial_ports = {}
order_queues = {config['port']: Queue() for config in DEVICE_CONFIGS if config['protocol'] == 'serial'}
# ==========================================
# 2. DATABASE MODEL
# 2. DATABASE CONNECTION
# ==========================================
DATABASE_URL = "postgresql://lismikro:[email protected]:5002/lismikro"
engine = create_engine(DATABASE_URL, pool_recycle=3600)
SessionLocal = sessionmaker(bind=engine)
Base = declarative_base()
# ==========================================
# 2. DATABASE MODEL
# ==========================================
class Sample(Base):
__tablename__ = 'samples'
id = Column(Integer, primary_key=True)
@@ -278,8 +322,12 @@ def _write_thread_log(message):
with thread_log_lock:
with open(log_path, "a", encoding="utf-8") as fh:
fh.write(f"{timestamp} | {message}\n")
except Exception:
pass
except Exception as exc:
builtins.print(
f"[THREAD-LOG-ERROR] "
f"Gagal menulis thread log ke "
f"{THREAD_LOG_DIR}: {exc}"
)
def print(*args, **kwargs):
sep = kwargs.get("sep", " ")
@@ -323,18 +371,6 @@ def get_flag_by_device(ip_addr):
return flag
return None
def get_pending_orders(ip_addr):
flag = get_flag_by_device(ip_addr)
if not flag:
return []
session = SessionLocal()
try:
q = session.query(PaslabOrder).filter(getattr(PaslabOrder, flag) == False)
return q.all()
finally:
session.close()
def mark_genexpert_order_flag(rnoreg, ip_addr, reason="processed"):
rnoreg = str(rnoreg or "").strip()
flag_name = get_flag_by_device(str(ip_addr or "").strip())
@@ -424,16 +460,6 @@ def build_hl7_segment(segment_name, fields):
values = [str(fields.get(index, "")) for index in range(1, max_field + 1)]
return f"{segment_name}|" + "|".join(values)
def map_hl7_sex(value):
text = str(value or "").strip().upper()
if not text:
return ""
if text.startswith("L") or "LAKI" in text or text == "M":
return "M"
if text.startswith("P") or "PEREM" in text or text == "F":
return "F"
return ""
def extract_msg_control_id(hl7_message):
try:
segments = hl7_message.split('\r')
@@ -588,60 +614,164 @@ def create_genexpert_astm_order_message(orders, ip_addr=None, query_tag=""):
return message
def send_all_orders_astm(conn, ip_addr, astm_msg, response_framing="astm"):
query = parse_genexpert_astm_query(astm_msg)
requested_sample_id = str(query.get("query_sample_id") or "").strip()
query_tag = str(query.get("query_tag") or "").strip()
flag = get_flag_by_device(ip_addr)
if not flag:
print(f"[GENEXPERT] ASTM query diabaikan, flag untuk {ip_addr} tidak ditemukan.")
return
lock = GENEXPERT_ORDER_LOCKS.setdefault(ip_addr, threading.Lock())
session = SessionLocal()
try:
flag_attr = getattr(PaslabOrder, flag, None)
if flag_attr is None:
print(f"[GENEXPERT] ASTM query diabaikan, atribut flag {flag} tidak ada.")
print(f"[GENEXPERT-LOCK] ip={ip_addr}, waiting")
with lock:
print(f"[GENEXPERT-LOCK] ip={ip_addr}, acquired")
query = parse_genexpert_astm_query(astm_msg)
requested_sample_id = str(query.get("query_sample_id") or "").strip()
query_tag = str(query.get("query_tag") or "").strip()
flag = get_flag_by_device(ip_addr)
if not flag:
print(
f"[GENEXPERT] ASTM query diabaikan, "
f"flag untuk {ip_addr} tidak ditemukan."
)
return
base_orders = session.query(PaslabOrder).filter(
(flag_attr == False) | (flag_attr == None)
).order_by(PaslabOrder.rtglast.desc().nullslast(), PaslabOrder.urut.desc()).all()
selected_orders = []
if requested_sample_id and requested_sample_id.upper() != "ALL":
for order in base_orders:
if str(order.rnoreg or "").strip() != requested_sample_id:
continue
assay_code, _, _ = resolve_genexpert_assay(order, ip_addr)
if assay_code:
selected_orders = [order]
break
else:
for order in base_orders:
assay_code, _, _ = resolve_genexpert_assay(order, ip_addr)
if assay_code:
selected_orders = [order]
break
print(
f"[GENEXPERT-ASTM-QUERY] ip={ip_addr}, query_tag={query_tag}, requested_sample_id='{requested_sample_id}', "
f"selected_rnoreg={[str(order.rnoreg or '').strip() for order in selected_orders]}"
f"[GENEXPERT-ORDER-SELECT] "
f"ip={ip_addr}, flag={flag}, "
f"requested_sample_id='{requested_sample_id}'"
)
if not selected_orders:
reply = f"H|\\^&|||{sanitize_astm_field(get_genexpert_host_application(ip_addr), max_len=20)}|||||GeneXpert Host||P|1394-97|{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}\rL|1|N\r"
send_genexpert_response(conn, ip_addr, reply, response_framing, label="astm-q-empty")
return
session = SessionLocal()
reply = create_genexpert_astm_order_message(selected_orders, ip_addr=ip_addr, query_tag=query_tag)
sent_ok = send_genexpert_response(conn, ip_addr, reply, response_framing, label=f"astm-q-order:{selected_orders[0].rnoreg}")
for order in selected_orders:
rnoreg = str(order.rnoreg or "").strip()
print(f"[GENEXPERT] Order ASTM ditawarkan ke {ip_addr}: {rnoreg}, sent_ok={sent_ok}")
if sent_ok:
mark_genexpert_order_flag(rnoreg, ip_addr, reason="astm-order-sent")
finally:
session.close()
try:
flag_attr = getattr(PaslabOrder, flag, None)
if flag_attr is None:
print(
f"[GENEXPERT] ASTM query diabaikan, "
f"atribut flag {flag} tidak ada."
)
return
base_orders = (
session.query(PaslabOrder)
.filter(
(flag_attr == False) | (flag_attr == None)
)
.order_by(
PaslabOrder.rtglast.desc().nullslast(),
PaslabOrder.urut.desc()
)
.all()
)
selected_orders = []
if requested_sample_id and requested_sample_id.upper() != "ALL":
for order in base_orders:
if str(order.rnoreg or "").strip() != requested_sample_id:
continue
assay_code, _, _ = resolve_genexpert_assay(
order,
ip_addr
)
if assay_code:
selected_orders = [order]
break
else:
for order in base_orders:
assay_code, _, _ = resolve_genexpert_assay(
order,
ip_addr
)
if assay_code:
selected_orders = [order]
break
print(
f"[GENEXPERT-ASTM-QUERY] "
f"ip={ip_addr}, "
f"flag={flag}, "
f"query_tag={query_tag}, "
f"requested_sample_id='{requested_sample_id}', "
f"selected_rnoreg="
f"{[str(order.rnoreg or '').strip() for order in selected_orders]}"
)
if not selected_orders:
reply = (
f"H|\\^&|||"
f"{sanitize_astm_field(get_genexpert_host_application(ip_addr), max_len=20)}"
f"|||||GeneXpert Host||P|1394-97|"
f"{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}"
f"\rL|1|N\r"
)
send_genexpert_response(
conn,
ip_addr,
reply,
response_framing,
label="astm-q-empty"
)
return
reply = create_genexpert_astm_order_message(
selected_orders,
ip_addr=ip_addr,
query_tag=query_tag
)
rnoreg_first = str(
selected_orders[0].rnoreg or ""
).strip()
sent_ok = send_genexpert_response(
conn,
ip_addr,
reply,
response_framing,
label=f"astm-q-order:{rnoreg_first}"
)
for order in selected_orders:
rnoreg = str(order.rnoreg or "").strip()
print(
f"[GENEXPERT] Order ASTM ditawarkan ke "
f"{ip_addr}: {rnoreg}, sent_ok={sent_ok}"
)
if sent_ok:
flag_ok = mark_genexpert_order_flag(
rnoreg,
ip_addr,
reason="astm-order-sent"
)
print(
f"[GENEXPERT-ORDER-COMMIT] "
f"ip={ip_addr}, "
f"rnoreg={rnoreg}, "
f"flag={flag}, "
f"success={flag_ok}"
)
finally:
session.close()
print(f"[GENEXPERT-LOCK] ip={ip_addr}, released")
def process_genexpert_hl7_message(conn, ip_addr, clean_hl7, response_framing):
# ==========================================================
@@ -971,40 +1101,6 @@ def handle_genexpert_client(conn, addr):
pass
logging.info(f"[GenExpert_TCP] Koneksi {addr} ditutup.")
def send_order_via_active_connection(target_ip, hl7_message):
conn = None
with connection_lock:
conn = active_genexpert_connections.get(target_ip)
if not conn:
logging.warning(f"Gagal kirim Order: GeneXpert dengan IP {target_ip} BELUM TERKONEKSI ke Listener.")
print(f"Gagal kirim Order: GeneXpert dengan IP {target_ip} BELUM TERKONEKSI ke Listener.")
return False
try:
# Bungkus pesan dengan MLLP (Minimal Lower Layer Protocol) standard HL7
# Format: <VT> message <FS><CR>
mllp_msg = f"\x0b{hl7_message}\x1c\r"
logging.info(f"Mengirim Order ke {target_ip}...")
print(f"Mengirim Order ke {target_ip}...")
conn.sendall(mllp_msg.encode('utf-8'))
# Opsi: Jika ingin menunggu ACK balasan untuk Order
# Namun hati-hati ini bisa blocking jika alat lambat
ack = conn.recv(1024)
logging.info(f"Dapat ACK Order dari {target_ip}: {ack}")
print(f"Dapat ACK Order dari {target_ip}: {ack}")
return True
except Exception as e:
logging.error(f"Error mengirim ke {target_ip}: {e}")
print(f"Error mengirim ke {target_ip}: {e}")
# Jika error saat kirim, anggap koneksi rusak
with connection_lock:
if target_ip in active_genexpert_connections:
del active_genexpert_connections[target_ip]
return False
def parse_genexpert_astm_records(astm_string, device_name):
"""
Parser khusus untuk membaca hasil ASTM dari instrumen GeneXpert
@@ -1100,28 +1196,35 @@ def parse_genexpert_qpd(qpd_segment):
def resolve_genexpert_assay(order, ip_addr=None):
assay_name = str(getattr(order, "tes", "") or "").strip()
specimen_code = str(getattr(order, "kd_spesimen", "") or "").strip()
assay_code = GENEXPERT_TEST_MAPPING.get(assay_name)
assay_source = "mapping:tes"
if not assay_code and specimen_code:
assay_code = specimen_code
assay_source = "fallback:kd_spesimen"
supported_codes = GENEXPERT_IP_CAPABILITIES.get(str(ip_addr or "").strip(), []) if ip_addr else []
capability_match = True if not supported_codes else assay_code in supported_codes
if not assay_code:
print(
f"[GENEXPERT-DEBUG] rnoreg={getattr(order, 'rnoreg', '')}, ip={ip_addr}, "
f"tes='{assay_name}', kd_spesimen='{specimen_code}', assay_code=EMPTY"
)
return None, "none", capability_match
assay_code = DEFAULT_GXP_CODE
assay_source = "default"
supported_codes = (
GENEXPERT_IP_CAPABILITIES.get(str(ip_addr or "").strip(), [])
if ip_addr else []
)
capability_match = (
True if not supported_codes
else assay_code in supported_codes
)
print(
f"[GENEXPERT-DEBUG] rnoreg={getattr(order, 'rnoreg', '')}, ip={ip_addr}, "
f"tes='{assay_name}', kd_spesimen='{specimen_code}', assay_code='{assay_code}', "
f"assay_source={assay_source}, capability_match={capability_match}"
f"[GENEXPERT-DEBUG] "
f"rnoreg={getattr(order, 'rnoreg', '')}, "
f"ip={ip_addr}, "
f"tes='{assay_name}', "
f"kd_spesimen='{specimen_code}', "
f"assay_code='{assay_code}', "
f"assay_source={assay_source}, "
f"capability_match={capability_match}"
)
return assay_code, assay_source, capability_match
def get_genexpert_query_orders(ip_addr, hl7_msg):
@@ -1563,73 +1666,6 @@ def debug_genexpert_order_message(hl7_message, ip_addr=None):
f"patient_name='{current_patient_name}', raw='{segment}'"
)
def get_active_genexpert_ips():
with connection_lock:
return list(active_genexpert_connections.keys())
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 ""
first_name, last_name = split_patient_name(order.nama)
pid_nama = f"{last_name}^{first_name}"
room = str(getattr(order, "ruangan", "") or "RSSA MALANG").strip().upper()
room = room.replace("|", " ").replace("^", " ")[:30]
# 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: MTBRIF
# 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}"
pv1 = f"PV1|1|I|{room}"
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{pv1}\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"
# ==========================================
# bioMérieux MYLA TCP Server Handler
# ==========================================
@@ -1753,65 +1789,6 @@ def mark_myla_order_sent(order_id):
finally:
session.close()
def create_myla_astm_order_message(order):
"""
ASTM E1394/LIS2-A2 style:
H, P, C, O, R, L
"""
pid = sanitize_astm_field(order.norm, max_len=32)
sid = sanitize_astm_field(order.rnoreg, max_len=32)
first_name, last_name = split_patient_name(sanitize_astm_field(order.nama, max_len=80))
first_name = sanitize_astm_field(first_name, uppercase=True, max_len=20)
last_name = sanitize_astm_field(last_name, uppercase=True, max_len=20)
p_name = f"{last_name}^{first_name}"
sex_raw = sanitize_astm_field(order.rjenis, uppercase=True, max_len=10)
sex = "M" if sex_raw.startswith("L") else "F"
raw_location = sanitize_astm_field(getattr(order, 'ruangan', "UT"), uppercase=True, max_len=40)
location = re.sub(r"[^A-Z0-9]", "", raw_location)[:10] or "UT"
diagnosis = sanitize_astm_field(getattr(order, 'diagnosa', "Unspecified"), max_len=60) or "Unspecified"
specimen_type = sanitize_astm_field(order.kd_spesimen, uppercase=True, max_len=20) if order.kd_spesimen else "BLOOD"
specimen_field = f"{specimen_type}^VENA^^BAIK"
test_code = sanitize_astm_field(order.tes, uppercase=True, max_len=40) or "GENERAL"
head = r"H|\^&|||LIS|||||||||P|1"
p_rec = [""] * 36
p_rec[0] = "P"
p_rec[1] = "1"
p_rec[2] = pid
p_rec[3] = pid
p_rec[5] = p_name
p_rec[8] = sex
p_rec[25] = location
pat_str = "|".join(p_rec[:36])
com_str = f"C|1|L|{diagnosis}|G"
o_rec = [""] * 32
o_rec[0] = "O"
o_rec[1] = "1"
o_rec[2] = sid
o_rec[4] = f"^^^{test_code}"
o_rec[5] = "R"
o_rec[11] = "A"
o_rec[15] = specimen_field
ord_str = "|".join(o_rec[:32])
# Placeholder R record agar struktur record lengkap H,P,C,O,R,L.
rcd_str = "R|1|^^^ORDER_STATUS|PENDING|||N|||||F"
term = "L|1|N"
message_content = f"{head}\r{pat_str}\r{com_str}\r{ord_str}\r{rcd_str}\r{term}\r"
seq = "1"
frame_body = f"{seq}{message_content}\x03"
chk = calculate_astm_checksum(frame_body)
full_frame = f"\x02{frame_body}{chk}\r\n"
return [full_frame.encode("latin-1")]
def parse_myla_astm_records(raw_message, device_name="MYLA"):
session = SessionLocal()
try:
@@ -1939,59 +1916,6 @@ def receive_myla_astm_transmission(conn, peer_ip, first_control=ENQ):
raw_message = "".join(assembled)
parse_myla_astm_records(raw_message, device_name=f"MYLA-{peer_ip}")
def wait_for_astm_control(conn, expected_controls, peer_ip, timeout_seconds=MYLA_CONTROL_TIMEOUT_SECONDS):
deadline = time.time() + timeout_seconds
while time.time() < deadline:
try:
conn.settimeout(max(0.1, deadline - time.time()))
b = conn.recv(1)
if not b:
return None
if b in expected_controls:
return b
if b == ENQ:
receive_myla_astm_transmission(conn, peer_ip, first_control=ENQ)
elif b == STX:
receive_myla_astm_transmission(conn, peer_ip, first_control=STX)
except socket.timeout:
continue
except Exception as e:
logging.error(f"[MYLA-ASTM] Error wait control: {e}")
print(f"[MYLA-ASTM] Error wait control: {e}")
return None
return None
def send_order_to_myla_astm(conn, order, peer_ip):
frames = create_myla_astm_order_message(order)
conn.sendall(ENQ)
hs = wait_for_astm_control(conn, {ACK}, peer_ip, timeout_seconds=MYLA_CONTROL_TIMEOUT_SECONDS)
if hs != ACK:
logging.warning(f"[MYLA-ASTM] Handshake gagal untuk rnoreg={order.rnoreg}, respon={hs}")
print(f"[MYLA-ASTM] Handshake gagal untuk rnoreg={order.rnoreg}, respon={hs}")
return False
for i, frame in enumerate(frames, start=1):
frame_ok = False
for attempt in range(1, 4):
conn.sendall(frame)
resp = wait_for_astm_control(conn, {ACK, NAK}, peer_ip, timeout_seconds=MYLA_CONTROL_TIMEOUT_SECONDS)
if resp == ACK:
frame_ok = True
break
if resp == NAK:
logging.warning(f"[MYLA-ASTM] Frame {i} NAK rnoreg={order.rnoreg}, retry={attempt}")
print(f"[MYLA-ASTM] Frame {i} NAK rnoreg={order.rnoreg}, retry={attempt}")
time.sleep(0.5)
continue
logging.warning(f"[MYLA-ASTM] Frame {i} timeout rnoreg={order.rnoreg}, retry={attempt}")
print(f"[MYLA-ASTM] Frame {i} timeout rnoreg={order.rnoreg}, retry={attempt}")
if not frame_ok:
conn.sendall(EOT)
return False
conn.sendall(EOT)
return True
def create_myla_hl7_order_message(order, msg_control_id):
timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
order_timestamp = format_hl7_datetime(getattr(order, "rtglast", None)) or timestamp
@@ -2389,13 +2313,6 @@ def start_myla_inbound_server(host, port):
# VITEK PARSER
# ==========================================
def calculate_vitek_checksum(data_str):
"""
Menghitung Checksum Vitek (Sum of bytes % 256).
"""
total = sum(ord(c) for c in data_str)
return f"{total % 256:02X}"
def parse_and_save_vitek_result(raw_data, port_name="VITEK"):
session = SessionLocal()
try: