iT邦幫忙

0

如何使用 Loki 分析 Sysmex HCLAB 訊息格式

  • 分享至 

  • xImage
  •  

在撰寫儀器連線程式與檢驗系統 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|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 為檢驗項目的醫令與出現次數
  • Unit 為檢驗項目的單位與出現次數,Value 代表空白。
  • Abnormal 為檢驗項目是否異常與出現次數,Value 代表空白。
  • Result Status 為檢驗項目的狀態與出現次數

我們可以在 Test 搜尋欄位輸入 DFTO 快速地找到該筆的資訊與條碼編號

再透過 Barcode 搜尋欄位輸入條碼編號來找到該檢體的所有項目與數據。

如此一來我們就可以在 24,106 個檢驗項目中快速分析出現過的醫令有哪些,非常的耐斯。


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言