This commit is contained in:
Dwi Swandhana
2026-01-30 21:07:15 +07:00
parent 083de5fb96
commit d8ff19c9b2
12 changed files with 131 additions and 119 deletions
@@ -237,7 +237,11 @@ class ListController extends Controller
$tcmtb,
$urine
);
$batasWaktu = Carbon::now()->subHours(24);
Periksa::whereNull('status')->where('mulai', '<', $batasWaktu)->update([
'status' => 'Dibatalkan (Arsip)',
'updated_at' => now()
]);
$arraylist = Periksa::select(
'id', 'mulai', 'akhir', 'orderid', 'noloket', 'nofoto', 'noregister', 'asalpasien', 'nmrs', 'pasien_id', 'nmpasien',
'jkpasien', 'tgllahirpasien', 'tlppasien', 'alamatpasien', 'reques', 'usia', 'berat', 'ktp', 'bpjs', 'ruangan_id',
+4 -5
View File
@@ -285,7 +285,7 @@ class AstmMessageService
Periksa::where('nofoto', $accession_number)->update([
'status' => 'Data Vitek di Terima',
]);
Log::info("Data berhasil disimpan:", $resultSample->toArray());
//Log::info("Data berhasil disimpan:", $resultSample->toArray());
$resultSample->save();
}
return response()->json(['message' => 'Data berhasil diproses dan disimpan.']);
@@ -467,7 +467,7 @@ class AstmMessageService
'status' => 'Data Vitek di Terima',
]);
}
Log::info("Data MTRL Berhasil di Parse dan di simpan ", $resultSample->toArray());
//Log::info("Data MTRL Berhasil di Parse dan di simpan ", $resultSample->toArray());
return response()->json(['message' => 'Data berhasil diproses dan disimpan.']);
} else {
$headerData = explode("|", $astmData);
@@ -628,7 +628,7 @@ class AstmMessageService
$resultSample->additional_result = json_encode($resultData);
if ($noregister){
$resultSample->save();
Log::info("Data ASTM Berhasil di Parse dan di simpan ", $resultSample->toArray());
//Log::info("Data ASTM Berhasil di Parse dan di simpan ", $resultSample->toArray());
return response()->json(['message' => 'Data berhasil diproses dan disimpan.']);
}
} else {
@@ -823,7 +823,7 @@ class AstmMessageService
]
);
}
Log::info("Data BD ASTM Berhasil di Parse dan di simpan ", $resultSample->toArray());
//Log::info("Data BD ASTM Berhasil di Parse dan di simpan ", $resultSample->toArray());
return true;
@@ -838,7 +838,6 @@ class AstmMessageService
public function processAstmMessagesLokal($dataListener) {
foreach ($dataListener as $data) {
try {
// Ambil pesan ASTM dari kolom 'message' atau yang relevan
$response = $data->rawdt;
if ($data->alat == 'MGIT' OR $data->alat == 'COM5' OR $data->alat == 'BACTEC'){
$assembled = $this->reassembleAstmFrames($data->rawdt);
@@ -70,9 +70,9 @@
<td valign="top">Tanggal Kirim Sample</td>
<td valign="top">:</td>
<td valign="top">{{ $periksa->daftar }}</td>
<td valign="top">&nbsp;</td>
<td valign="top">&nbsp;</td>
<td valign="top" colspan="2">&nbsp;</td>
<td valign="top">Spesimen</td>
<td valign="top">:</td>
<td valign="top" colspan="2">{!! $periksa->nm_spesimen !!}</td>
</tr>
<tr>
@@ -69,9 +69,9 @@
<td valign="top">Tanggal Kirim Sample</td>
<td valign="top">:</td>
<td valign="top">{{ $periksa->daftar }}</td>
<td valign="top">&nbsp;</td>
<td valign="top">&nbsp;</td>
<td valign="top" colspan="2">&nbsp;</td>
<td valign="top">Spesimen</td>
<td valign="top">:</td>
<td valign="top" colspan="2">{!! $periksa->nm_spesimen !!}</td>
</tr>
<tr>
@@ -87,9 +87,9 @@
<td valign="top">Tanggal Kirim Sample</td>
<td valign="top">:</td>
<td valign="top">{{ $periksa->daftar }}</td>
<td valign="top">&nbsp;</td>
<td valign="top">&nbsp;</td>
<td valign="top" colspan="2">&nbsp;</td>
<td valign="top">Spesimen</td>
<td valign="top">:</td>
<td valign="top" colspan="2">{!! $periksa->nm_spesimen !!}</td>
</tr>
<tr>
@@ -69,9 +69,9 @@
<td valign="top">Tanggal Kirim Sample</td>
<td valign="top">:</td>
<td valign="top">{{ $periksa->daftar }}</td>
<td valign="top">&nbsp;</td>
<td valign="top">&nbsp;</td>
<td valign="top" colspan="2">&nbsp;</td>
<td valign="top">Spesimen</td>
<td valign="top">:</td>
<td valign="top" colspan="2">{!! $periksa->nm_spesimen !!}</td>
</tr>
<tr>
@@ -85,9 +85,9 @@
<td valign="top">Tanggal Kirim Sample</td>
<td valign="top">:</td>
<td valign="top">{{ $periksa->daftar }}</td>
<td valign="top">&nbsp;</td>
<td valign="top">&nbsp;</td>
<td valign="top" colspan="2">&nbsp;</td>
<td valign="top">Spesimen</td>
<td valign="top">:</td>
<td valign="top" colspan="2">{!! $periksa->nm_spesimen !!}</td>
</tr>
<tr>
@@ -70,9 +70,9 @@
<td valign="top">Tanggal Kirim Sample</td>
<td valign="top">:</td>
<td valign="top">{{ $periksa->daftar }}</td>
<td valign="top">&nbsp;</td>
<td valign="top">&nbsp;</td>
<td valign="top" colspan="2">&nbsp;</td>
<td valign="top">Spesimen</td>
<td valign="top">:</td>
<td valign="top" colspan="2">{!! $periksa->nm_spesimen !!}</td>
</tr>
<tr>
@@ -1555,8 +1555,8 @@
<div class="form-group col-lg-4">
<select class="form-control ekspertiseseletc" id="hasilpemeriksaanttcmmtbxdrxpert_pagimtb" name="hasilpemeriksaanttcmmtbxdrxpert_pagimtb">
<option value=""></option>
<option value="D">MTB Detected (D)</option>
<option value="ND">MTB Not Detected (ND)</option>
<option value="Detected">MTB Detected (D)</option>
<option value="Not Detected">MTB Not Detected (ND)</option>
</select>
</div>
</div>
@@ -1707,8 +1707,8 @@
<div class="form-group col-lg-4">
<select class="form-control ekspertiseseletc" id="hasilpemeriksaanttcmmtbxdrxpert_sewaktumtb" name="hasilpemeriksaanttcmmtbxdrxpert_sewaktumtb">
<option value=""></option>
<option value="D">MTB Detected (D)</option>
<option value="ND">MTB Not Detected (ND)</option>
<option value="Detected">MTB Detected (D)</option>
<option value="Not Detected">MTB Not Detected (ND)</option>
</select>
</div>
</div>
+4 -4
View File
@@ -1610,8 +1610,8 @@
<div class="form-group col-lg-4">
<select class="form-control ekspertiseseletc" id="hasilpemeriksaanttcmmtbxdrxpert_pagimtb" name="hasilpemeriksaanttcmmtbxdrxpert_pagimtb">
<option value=""></option>
<option value="D">MTB Detected (D)</option>
<option value="ND">MTB Not Detected (ND)</option>
<option value="Detected">MTB Detected (D)</option>
<option value="Not Detected">MTB Not Detected (ND)</option>
</select>
</div>
</div>
@@ -1762,8 +1762,8 @@
<div class="form-group col-lg-4">
<select class="form-control ekspertiseseletc" id="hasilpemeriksaanttcmmtbxdrxpert_sewaktumtb" name="hasilpemeriksaanttcmmtbxdrxpert_sewaktumtb">
<option value=""></option>
<option value="D">MTB Detected (D)</option>
<option value="ND">MTB Not Detected (ND)</option>
<option value="Detected">MTB Detected (D)</option>
<option value="Not Detected">MTB Not Detected (ND)</option>
</select>
</div>
</div>
@@ -1610,8 +1610,8 @@
<div class="form-group col-lg-4">
<select class="form-control ekspertiseseletc" id="hasilpemeriksaanttcmmtbxdrxpert_pagimtb" name="hasilpemeriksaanttcmmtbxdrxpert_pagimtb">
<option value=""></option>
<option value="D">MTB Detected (D)</option>
<option value="ND">MTB Not Detected (ND)</option>
<option value="Detected">MTB Detected (D)</option>
<option value="Not Detected">MTB Not Detected (ND)</option>
</select>
</div>
</div>
@@ -1762,8 +1762,8 @@
<div class="form-group col-lg-4">
<select class="form-control ekspertiseseletc" id="hasilpemeriksaanttcmmtbxdrxpert_sewaktumtb" name="hasilpemeriksaanttcmmtbxdrxpert_sewaktumtb">
<option value=""></option>
<option value="D">MTB Detected (D)</option>
<option value="ND">MTB Not Detected (ND)</option>
<option value="Detected">MTB Detected (D)</option>
<option value="Not Detected">MTB Not Detected (ND)</option>
</select>
</div>
</div>
+92 -83
View File
@@ -75,7 +75,7 @@ active_genexpert_connections = {}
connection_lock = threading.Lock()
DEVICE_CONFIGS = [
{
'port': 'COM6', 'baud_rate': 19200, 'device_type': 'vitek', 'alat_name': 'Vitek 1',
'port': 'COM6', 'baud_rate': 9600, 'device_type': 'vitek', 'alat_name': 'Vitek 1',
'protocol': 'serial', 'flag_column': 'flg_vitek1'
},
{
@@ -133,7 +133,7 @@ class LisPhoenix(Base):
__tablename__ = 'lis_phoenix'
id = Column(Integer, primary_key=True)
no_id = Column(String(50)) # Patient ID
seq_no = Column(String(50), unique=True) # Isolate ID / Sample ID
seq_no = Column(String(50)) # Isolate ID / Sample ID
rnmpas = Column(String(100)) # Patient Name
tgl_data = Column(SqDate)
rawdt = Column(Text)
@@ -145,7 +145,7 @@ class LisPhoenix(Base):
class LisPhoenixDtl(Base):
__tablename__ = 'lis_phoenix_dtl'
id = Column(Integer, primary_key=True)
seq_no = Column(String(50), index=True) # Isolate ID / Sample ID
seq_no = Column(String(50)) # Isolate ID / Sample ID
kd_antibiotik = Column(String(50))
nm_antibiotik = Column(String(100))
keterangan = Column(String(50)) # MIC Value (e.g., <=0.5)
@@ -557,111 +557,121 @@ def calculate_vitek_checksum(data_str):
def parse_and_save_vitek_result(raw_data, port_name="VITEK"):
session = SessionLocal()
try:
# --- LANGKAH 1: PEMBERSIHAN DATA ---
# Vitek sering mengirim karakter framing \x1e (RS), \x1d (GS), \x03 (ETX)
# Hapus framing characters dan newline
clean_data = raw_data.replace('\x02', '').replace('\x03', '').replace('\x1e', '').replace('\r', '').replace('\n', '')
# --- 1. CLEANING DATA ---
# Hapus karakter kontrol STX(02), ETX(03), RS(1e/30), GS(1d/29), CR, LF
# Perhatikan: Log Anda menunjukkan RS () muncul di tengah kata, jadi harus dihapus total.
clean_data = raw_data.replace('\x02', '').replace('\x03', '').replace('\x1e', '').replace('\x1d', '').replace('\r', '').replace('\n', '')
# Pisahkan jika ada checksum di akhir (biasanya setelah \x1d)
if '\x1d' in clean_data:
clean_data = clean_data.split('\x1d')[0]
# --- LANGKAH 2: PARSING FIELD BERDASARKAN TAG ---
# Format Vitek: tag|value|tag|value...
# Pisahkan field berdasarkan pipa '|'
fields = clean_data.split('|')
msg_type = fields[0] # mtmpr (Order) atau mtrsl (Result)
# Cek apakah ini pesan result (mtrsl)
if not fields or fields[0] != 'mtrsl':
return # Abaikan jika bukan result
# Inisialisasi variabel
sample_id = None # ci (Accession Number)
patient_id = "" # pi
# --- 2. VARIABLE INIT ---
sample_id = None # ci (No Lab / No Container)
patient_id = "" # pi (No RM)
patient_name = "" # pn
organism_name = "" # o2
antibiotics = [] # List untuk menyimpan hasil AB
card_barcode = "" # is
antibiotics = [] # List penampung hasil AB
result_date = datetime.datetime.now()
# Penanda apakah kita sedang membaca blok antibiotik
current_ab_name = ""
current_ab_mic = ""
current_ab_int = ""
# Variabel sementara untuk looping antibiotik
curr_ab_name = ""
curr_ab_mic = ""
curr_ab_int = ""
# Loop setiap field untuk mencari Tag
# --- 3. PARSING LOOP ---
for field in fields:
if not field: continue
# --- HEADER INFO ---
if field.startswith("pi") and len(field) > 2: # Patient ID
patient_id = field[2:].strip()
elif field.startswith("pn") and len(field) > 2: # Patient Name
patient_name = field[2:].strip()
elif field.startswith("ci") and len(field) > 2: # Case ID / No Reg (KUNCI UTAMA)
# Format Vitek kadang 25-035007, kita ambil angkanya saja atau sesuai format LIS
if field.startswith("ci") and len(field) > 2:
sample_id = field[2:].strip()
# --- ORGANISME (Bakteri) ---
elif field.startswith("pi") and len(field) > 2:
patient_id = field[2:].strip()
elif field.startswith("pn") and len(field) > 2:
patient_name = field[2:].strip()
elif field.startswith("is") and len(field) > 2:
card_barcode = field[2:].strip()
# --- ORGANISME ---
elif field.startswith("o2") and len(field) > 2:
organism_name = field[2:].strip()
# --- ANTIBIOTIK (Looping Block) ---
# Penanda blok antibiotik baru dimulai dengan tag 'ra'
# --- ANTIBIOTIK BLOCK (Mulai Pesan Kedua) ---
# Tag 'ra' adalah pemisah antar obat
elif field == "ra":
# Simpan antibiotik sebelumnya jika ada
if current_ab_name:
ab_str = f"{current_ab_name} {current_ab_mic} ({current_ab_int})"
# Jika ada data obat sebelumnya di memori, simpan dulu
if curr_ab_name:
ab_str = f"{curr_ab_name} {curr_ab_mic} ({curr_ab_int})"
antibiotics.append(ab_str)
# Reset temp vars
current_ab_name = ""
current_ab_mic = ""
current_ab_int = ""
# Reset untuk obat berikutnya
curr_ab_name = ""; curr_ab_mic = ""; curr_ab_int = ""
# Detail Antibiotik
elif field.startswith("a2") and len(field) > 2: # Nama Antibiotik
current_ab_name = field[2:].strip()
elif field.startswith("a3") and len(field) > 2: # Nilai MIC
current_ab_mic = field[2:].strip()
elif field.startswith("a4") and len(field) > 2: # Interpretasi (S/I/R)
current_ab_int = field[2:].strip()
# Detail Obat
elif field.startswith("a2") and len(field) > 2: # Nama Obat (mis: Cefoxitin)
curr_ab_name = field[2:].strip()
elif field.startswith("a3") and len(field) > 2: # MIC (mis: >=4)
curr_ab_mic = field[2:].strip()
elif field.startswith("a4") and len(field) > 2: # Interpretasi (R/S/I)
curr_ab_int = field[2:].strip()
elif field.startswith("an") and len(field) > 2: # Interpretasi Alternatif
if not current_ab_int: current_ab_int = field[2:].strip()
if not curr_ab_int: curr_ab_int = field[2:].strip()
# Jangan lupa simpan antibiotik terakhir setelah loop selesai
if current_ab_name:
ab_str = f"{current_ab_name} {current_ab_mic} ({current_ab_int})"
antibiotics.append(ab_str)
# Jangan lupa simpan obat terakhir yang tersisa di buffer
if curr_ab_name:
ab_str = f"{curr_ab_name} {curr_ab_mic} ({curr_ab_int})"
antibiotics.append(ab_str)
# --- LANGKAH 3: FORMAT FINAL & SAVE ---
# Kita hanya memproses jika tipe pesan adalah 'mtrsl' (Result) dan ada Sample ID
if msg_type == 'mtrsl' and sample_id:
# --- 4. FORMAT FINAL STRING ---
# Format: "Staphylococcus hominis | Cefoxitin >=4 (R), Gentamicin 4 (S)..."
final_res_string = organism_name
#if antibiotics:
# final_res_string += " | " + ", ".join(antibiotics)
# Fallback jika negatif (biasanya tidak ada o2, tapi ada teks neg)
#if not final_res_string and "neg" in raw_data.lower():
# final_res_string = "NEGATIVE / NO GROWTH"
# --- 5. LOGIKA DATABASE (UPSERT: UPDATE or INSERT) ---
if sample_id:
# Cari data berdasarkan No Lab (Sample ID)
final_seq_no = card_barcode if card_barcode else patient_id
existing_data = session.query(LisPhoenix).filter(
LisPhoenix.seq_no == final_seq_no
).first()
if existing_data:
# === SKENARIO PESAN KEDUA (UPDATE) ===
logging.info(f"[{port_name}] UPDATE Data (Tahap 2) -> ID: {sample_id}, Pasien: {patient_name}")
print(f"[{port_name}] UPDATE Data -> ID: {sample_id} (Hasil Lengkap)")
existing_data.rnmpas = patient_name
existing_data.organisme = final_res_string
existing_data.rawdt = raw_data
existing_data.tgl_data = result_date
existing_data.processed = None
else:
# === SKENARIO PESAN PERTAMA (INSERT) ===
logging.info(f"[{port_name}] INSERT Data Baru (Tahap 1) -> ID: {sample_id}, Pasien: {patient_name}")
print(f"[{port_name}] INSERT Data -> ID: {sample_id} (Identifikasi Awal)")
new_entry = LisPhoenix(
no_id=sample_id,
seq_no=final_seq_no,
rnmpas=patient_name,
tgl_data=result_date,
rawdt=raw_data,
organisme=final_res_string,
alat=port_name
)
session.add(new_entry)
# Gabungkan hasil: Organisme | AB1, AB2, AB3...
final_res_string = organism_name
if antibiotics:
final_res_string += " | " + ", ".join(antibiotics)
# Jika tidak ada organisme tapi tipe result, mungkin Negative?
if not final_res_string and "neg" in raw_data.lower():
final_res_string = "NEGATIVE / NO GROWTH"
logging.info(f"[{port_name}] Save DB -> ID: {sample_id}, Pasien: {patient_name}, Bakteri: {organism_name}")
print(f"[{port_name}] Save DB -> ID: {sample_id}, Pasien: {patient_name}, Bakteri: {organism_name}")
new_entry = LisPhoenix(
no_id=sample_id, # Menggunakan tag 'ci'
seq_no=patient_id, # Menggunakan tag 'pi'
rnmpas=patient_name, # Menggunakan tag 'pn'
tgl_data=result_date,
rawdt=raw_data,
organisme=final_res_string, # Hasil gabungan
alat=port_name
)
session.add(new_entry)
session.commit()
else:
if msg_type != 'mtrsl':
logging.info(f"[{port_name}] Mengabaikan pesan tipe {msg_type}")
print(f"[{port_name}] Mengabaikan pesan tipe {msg_type}")
else:
logging.warning(f"[{port_name}] Data tidak lengkap (No Sample ID). Raw: {raw_data[:50]}...")
print(f"[{port_name}] Data tidak lengkap (No Sample ID). Raw: {raw_data[:50]}...")
logging.warning(f"[{port_name}] Pesan diabaikan (Tanpa Sample ID): {clean_data[:30]}...")
except Exception as e:
logging.error(f"Error Parsing Vitek: {e}")
@@ -669,7 +679,6 @@ def parse_and_save_vitek_result(raw_data, port_name="VITEK"):
session.rollback()
finally:
session.close()
def create_vitek_order_message(order):
"""
Membuat Frame Order Vitek sesuai Manual Ref 514937.