在撰写仪器连线程式与检验系统 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。
介接程式所在的连线电脑主要负责以下两件事情
定期询问 LIS 是否有检体需要测定,产生医令档案在 Order 资料夹。定期监听 Result 是否有档案产生,分析档案数据写入 LIS 的资料库。不论是在 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|20230926132149P|1|11209268033|||11209268033|||U|||||00^00||||||||||||00^00OBR|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|||AUTOVERIFYOBX|2|NM|RBC^RBC||4.70|10^6 /uL|3.70-6.20||||F|20230926132140|D-XN10|||AUTOVERIFYOBX|3|NM|HGB^HGB||13.4|g /dL|11.3-18.0||||F|20230926132140|D-XN10|||AUTOVERIFYOBX|4|NM|HCT^HCT||43.1|%|33.0-53.0||||F|20230926132140|D-XN10|||AUTOVERIFYOBX|5|NM|MCV^MCV||91.7|fl|79.0-99.0||||F|20230926132140|D-XN10|||AUTOVERIFYOBX|6|NM|MCH^MCH||28.5|pg|26.0-34.0||||F|20230926132140|D-XN10|||AUTOVERIFYOBX|7|NM|MCHC^MCHC||31.1|g /dL|30.0-36.0||||F|20230926132140|D-XN10|||AUTOVERIFYOBX|8|NM|PLT^PLT||198|10^3 /uL|150-400||||F|20230926132140|D-XN10|||AUTOVERIFYOBX|9|NM|NEUT^SEG/NEUT||55.6|%|40.0-75.0||||F|20230926132140|D-XN10|||AUTOVERIFYOBX|10|NM|LYMPH^LYMPH||35.4|%|20.0-45.0||||F|20230926132140|D-XN10|||AUTOVERIFYOBX|11|NM|MONO^MONO||5.7|%|2.0-10.0||||F|20230926132140|D-XN10|||AUTOVERIFYOBX|12|NM|EO^EO||2.8|%|1.0-6.0||||F|20230926132140|D-XN10|||AUTOVERIFYOBX|13|NM|BASO^BASO||0.5|%|0.0-1.0||||F|20230926132140|D-XN10|||AUTOVERIFYL|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||FUK1209268033L|RBC^RBC|4.70|10^6 /uL|3.70-6.20||FUK1209268033L|HGB^HGB|13.4|g /dL|11.3-18.0||FUK1209268033L|HCT^HCT|43.1|%|33.0-53.0||FUK1209268033L|MCV^MCV|91.7|fl|79.0-99.0||FUK1209268033L|MCH^MCH|28.5|pg|26.0-34.0||FUK1209268033L|MCHC^MCHC|31.1|g /dL|30.0-36.0||FUK1209268033L|PLT^PLT|198|10^3 /uL|150-400||FUK1209268033L|NEUT^SEG/NEUT|55.6|%|40.0-75.0||FUK1209268033L|LYMPH^LYMPH|35.4|%|20.0-45.0||FUK1209268033L|MONO^MONO|5.7|%|2.0-10.0||FUK1209268033L|EO^EO|2.8|%|1.0-6.0||FUK1209268033L|BASO^BASO|0.5|%|0.0-1.0||F
接下来就是把这些转换过的档案推送到 Loki,若对 Loki 不熟的朋友可以参考一下之前的文章喔。
撰写 promtail-local-config.yaml
server: http_listen_port: 9080 grpc_listen_port: 0positions: filename: ./positions.yamlclients: - url: http://your_loki_server:3100/loki/api/v1/pushscrape_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 为检验项目的医令与出现次数Unit 为检验项目的单位与出现次数,Value 代表空白。Abnormal 为检验项目是否异常与出现次数,Value 代表空白。Result Status 为检验项目的状态与出现次数我们可以在 Test 搜寻栏位输入 DFTO 快速地找到该笔的资讯与条码编号
再透过 Barcode 搜寻栏位输入条码编号来找到该检体的所有项目与数据。
如此一来我们就可以在 24,106 个检验项目中快速分析出现过的医令有哪些,非常的耐斯。