在撰寫儀器連線程式與檢驗系統 LIS 介接時,醫檢師通常只會知道檢驗系統所訂定的醫令代碼 LIS Code,但對我們來說需要的則是能夠驅動儀器運作的醫令代碼 Test ID。
雖然 Host Interface Specifications 也會提供 Test ID,但常常也讓人摸不著頭緒,例如嗜中性白血球寫 NEUT% 實際上卻是 NEUT^SEG/NEUT。
所以今天就來教大家如何將使用 Loki 分析 Sysmex HLCAB 訊息格式,我們直接從儀器回傳的報告數據進行分析。
Sysmex HCLAB
一般的儀器通常都是透過 RS232 實作 ASTM 協定來與介接程式進行通訊,Sysmex HCLAB 則是會在 HCLWAN 的電腦主機透過網路芳鄰 CIFS 來共享 ASTM Order 與 Result。
介接程式所在的連線電腦主要負責以下兩件事情
不論是在 Order 或者 Result 資料夾,HCLAB 與介接程式大約會在 2 秒內把檔案讀取並刪除,若想要複製大量的檔案來進行分析,我們必須先撰寫程式來瞬間複製檔案。
private void StartCopyResult(CancellationToken token)
{
string _path = Directory.GetCurrentDirectory();
string sourceFolderPath = @"Y:\"; // Result 資料夾路徑
string targetFolderPath = $@"{_path}\ASTM\Result\{DateTime.Now.ToString("yyyy-MM-dd")}\";
if (!Directory.Exists(targetFolderPath))
Directory.CreateDirectory(targetFolderPath);
while (token.IsCancellationRequested == false)
{
var sourceFiles = Directory.GetFiles(sourceFolderPath);
var targetFiles = Directory.GetFiles(targetFolderPath);
// 使用 LINQ 查詢比對尚未複製的檔案
var filesToCopy = sourceFiles
.Where(sourceFilePath =>
!targetFiles.Contains(Path.Combine(targetFolderPath, Path.GetFileName(sourceFilePath))))
.ToList();
foreach (var sourceFilePath in filesToCopy)
{
string fileName = Path.GetFileName(sourceFilePath);
string targetFilePath = Path.Combine(targetFolderPath, fileName);
try
{
// 複製檔案到目標資料夾
File.Copy(sourceFilePath, targetFilePath);
Console.WriteLine($"複製檔案: {fileName}");
}
catch (Exception ex)
{
Console.WriteLine($"複製發生錯誤: {ex.Message}");
}
}
Thread.Sleep(100);
}
}
可以看到大概收到了快兩千個檢體的檔案,假設每個檢體都做 CBC 全套血液檢查 13 項,那大概會有 24,908 個醫令需要確認。
看一下 Sysmex HCLAB 產出的 ASTM 格式如下
H|^~\&||||||||||P|A.2|20230926132149
P|1|11209268033|||11209268033|||U|||||00^00||||||||||||00^00
OBR|1|^000025^01|UK1209268033L|WBC^WBC~RBC^RBC~HGB^HGB~HCT^HCT~NEUT^SEG/NEUT~LYMPH^LYMPH~MONO^MONO~BASO^BASO~EO^EO~PLT^PLT~MCH^MCH~MCV^MCV~MCHC^MCHC|||20230926121300||||||^|20230926121300|^|||||||20230926132140||CTRLAB^CTRLAB|||
OBX|1|NM|WBC^WBC||6.10|10^3 /uL|4.00-11.00||||F|20230926132140|D-XN10|||AUTOVERIFY
OBX|2|NM|RBC^RBC||4.70|10^6 /uL|3.70-6.20||||F|20230926132140|D-XN10|||AUTOVERIFY
OBX|3|NM|HGB^HGB||13.4|g /dL|11.3-18.0||||F|20230926132140|D-XN10|||AUTOVERIFY
OBX|4|NM|HCT^HCT||43.1|%|33.0-53.0||||F|20230926132140|D-XN10|||AUTOVERIFY
OBX|5|NM|MCV^MCV||91.7|fl|79.0-99.0||||F|20230926132140|D-XN10|||AUTOVERIFY
OBX|6|NM|MCH^MCH||28.5|pg|26.0-34.0||||F|20230926132140|D-XN10|||AUTOVERIFY
OBX|7|NM|MCHC^MCHC||31.1|g /dL|30.0-36.0||||F|20230926132140|D-XN10|||AUTOVERIFY
OBX|8|NM|PLT^PLT||198|10^3 /uL|150-400||||F|20230926132140|D-XN10|||AUTOVERIFY
OBX|9|NM|NEUT^SEG/NEUT||55.6|%|40.0-75.0||||F|20230926132140|D-XN10|||AUTOVERIFY
OBX|10|NM|LYMPH^LYMPH||35.4|%|20.0-45.0||||F|20230926132140|D-XN10|||AUTOVERIFY
OBX|11|NM|MONO^MONO||5.7|%|2.0-10.0||||F|20230926132140|D-XN10|||AUTOVERIFY
OBX|12|NM|EO^EO||2.8|%|1.0-6.0||||F|20230926132140|D-XN10|||AUTOVERIFY
OBX|13|NM|BASO^BASO||0.5|%|0.0-1.0||||F|20230926132140|D-XN10|||AUTOVERIFY
L|1||1|16
我們需要將資料轉換 Loki 方便處理的格式
private void PaserResult()
{
string resultPath = @"C:\Users\ivan_cheng\Desktop\ASTM\Result\2023-09-27";
string outputPath = @"C:\Users\ivan_cheng\Downloads\Sysmex_Result\";
string line;
string barcode;
List<string> results = new();
var files = Directory.GetFiles(resultPath);
if (!Directory.Exists(outputPath))
Directory.CreateDirectory(outputPath);
foreach (var file in files)
{
results.Clear();
line = string.Empty;
barcode = string.Empty;
StreamReader sr = new(file);
//Read the first line of text
line = sr.ReadLine();
//Continue to read until you reach end of file
while (line != null)
{
if (line.StartsWith("OBR"))
{
string[] fileds = line.Split('|');
barcode = fileds[3];
}
if (line.StartsWith("OBX"))
{
string[] fileds = line.Split('|');
string test = fileds[3];
string result = fileds[5];
string unit = fileds[6];
string reference = fileds[7];
string abnormal = fileds[8];
string resultStatus = fileds[11];
string record = $"{barcode}|{test}|{result}|{unit}|{reference}|{abnormal}|{resultStatus}";
results.Add(record);
}
//Read the next line
line = sr.ReadLine();
}
//close the file
sr.Close();
if(results.Any())
File.WriteAllLines(@$"{outputPath}{barcode}.txt", results);
}
}
轉換過的格式如下
UK1209268033L|WBC^WBC|6.10|10^3 /uL|4.00-11.00||F
UK1209268033L|RBC^RBC|4.70|10^6 /uL|3.70-6.20||F
UK1209268033L|HGB^HGB|13.4|g /dL|11.3-18.0||F
UK1209268033L|HCT^HCT|43.1|%|33.0-53.0||F
UK1209268033L|MCV^MCV|91.7|fl|79.0-99.0||F
UK1209268033L|MCH^MCH|28.5|pg|26.0-34.0||F
UK1209268033L|MCHC^MCHC|31.1|g /dL|30.0-36.0||F
UK1209268033L|PLT^PLT|198|10^3 /uL|150-400||F
UK1209268033L|NEUT^SEG/NEUT|55.6|%|40.0-75.0||F
UK1209268033L|LYMPH^LYMPH|35.4|%|20.0-45.0||F
UK1209268033L|MONO^MONO|5.7|%|2.0-10.0||F
UK1209268033L|EO^EO|2.8|%|1.0-6.0||F
UK1209268033L|BASO^BASO|0.5|%|0.0-1.0||F
接下來就是把這些轉換過的檔案推送到 Loki,若對 Loki 不熟的朋友可以參考一下之前的文章喔。
撰寫 promtail-local-config.yaml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: ./positions.yaml
clients:
- url: http://your_loki_server:3100/loki/api/v1/push
scrape_configs:
- job_name: SysmexResult
static_configs:
- targets:
- localhost
labels:
job: SysmexResult
__path__: C:/Users/ivan_cheng/Downloads/Sysmex_Result/*
接下來就可以到 Grafana 繪製分析的儀表板
Result 使用到的 LogQL 語法如下
{job="SysmexResult"}
|~ "((?i)$Barcode|(?i)$Test)"
| pattern "<barcode>|<test>|<result>|<unit>|<reference>|<abnormal>|<resultStatus>"
若想要分析 Test ID 數量的 LogQL 語法如下
sum by (test) (
count_over_time({job="SysmexResult"}
| pattern "<barcode>|<test>|<result>|<unit>|<reference>|<abnormal>|<resultStatus>"
[$__interval])
)
完成的儀表板如下,我們總共分析了 2,863 個檢體與 24,106 個檢驗項目。
我們可以在 Test 搜尋欄位輸入 DFTO 快速地找到該筆的資訊與條碼編號
再透過 Barcode 搜尋欄位輸入條碼編號來找到該檢體的所有項目與數據。
如此一來我們就可以在 24,106 個檢驗項目中快速分析出現過的醫令有哪些,非常的耐斯。