把精密儀器產出的報表檔,經過來源端品質閘後寫進資料庫的小型服務。
In brief — A gateway that ingests analytical-instrument reports through a source-side quality gate before they reach the database. Pluggable transport layer (file drop / TCP-IP / serial / vendor SDK), ten quality rules encoding methodological preconditions rather than generic validation, and a three-way verdict instead of pass/fail. C# / .NET 10.
| 你想看 | 去這裡 |
|---|---|
| 十條品質規則怎麼寫(每條背後的儀器學依據) | src/Instrument.Core/QualityGate.cs |
| 通訊層抽象與四種接法(含 TCP 黏包切分) | src/Instrument.Core/Sources/ |
| 測試怎麼測反向案例(31 項中只有 2 項測乾淨資料) | tests/Instrument.Tests/QualityGateTests.cs |
| 跨廠商欄位別名對映 | src/Instrument.Core/ReportParser.cs |
| schema 設計與 UPSERT 冪等 | src/Instrument.Core/MeasurementStore.cs |
監看資料夾 → 解析多廠商格式 → 品質閘判定 → SQLite(含 UPSERT 冪等)→ 異常清單供人工複核。
通訊層(可抽換) 共用下游
┌──────────────────────────┐
│ 檔案投放 FileDropSource │──┐
│ TCP 監聽 TcpSource │──┤
│ 串列埠 SerialPortSource│──┼──> 解析(廠商別名對映)
│ 廠商 SDK VendorSdkSource │──┤ │
│ OPC UA OpcUaSource ※ │──┘ ▼
└──────────────────────────┘ 來源端品質閘(10 條規則)
│
┌───────────────┼───────────────┐
▼ ▼ ▼
Pass Conditional Reject
│ │ │
▼ ▼ ▼
analysis_ready 入庫但帶限制 入庫但不供分析
(下游只查這個) 條件 +異常清單
※ OPC UA 為契約定義,未實作(見下方誠實邊界)。
現場的儀器接法差異極大——老機台只剩 RS-232、新機台走 TCP 或 OPC UA、 有些封閉機台只能靠廠商 SDK 回呼,還有一大票只肯把報表丟到共用資料夾。 但接進來之後的事情完全一樣:解析 → 品質閘 → 入庫。
所以 IInstrumentSource 只回傳「一段原始文字 + 來源識別」,不回傳解析後的物件:
通訊層只負責把位元組收完整,判讀是別人的責任。換來源時下游一行都不用改,
不同機台走不同路時,多開幾條管線指向同一個資料庫即可。
| 接法 | 現場真正卡住的地方 |
|---|---|
| 檔案投放 | 不是監看,而是檔案還沒寫完就被讀到。儀器軟體是開檔→逐段寫→關檔,Created 事件在開檔當下就觸發。這裡用「檔案大小連續兩次相同」判定寫入完成,比固定 sleep 可靠。 |
| TCP | TCP 是位元組串流,不是訊息佇列。一次讀取可能收到半份報表,也可能一次收到兩份。必須與工作站端約定訊息邊界(結束標記+長度上限),否則資料一定黏在一起或被截斷。 |
| 串列埠 | 埠號、鮑率、資料位元、同位、停止位元、流量控制六項要與儀器端逐一對齊,錯一項就是亂碼。這正是與網管、設備商對整合參數時實際在核對的東西。 |
| 廠商 SDK | 難處不在技術而在授權與版本:SDK 綁授權碼、只支援特定 Windows 版本、儀器軟體一升級 API 就變。這些要在採購階段談進合約。 |
| OPC UA | 最常卡在憑證互信——雙方要把對方憑證加進信任清單,沒做時客戶端往往只回一句 BadSecurityChecksFailed,完全不指向真正原因。 |
進了資料庫再清是清不出來的。舉三個這支程式實際在擋的例子:
| 陷阱 | 為什麼下游看不出來 |
|---|---|
| GPC 校正曲線過期 | 資料庫裡就是一組分子量數字,看起來完全正常。但 GPC 量的是相對聚苯乙烯標準品的相對值,管柱老化後曲線漂移,跨批次比較就會得到假的差異。 |
| DSC 未註明升溫速率 | Tg 依升溫速率而變(越快量到越高)。少了速率欄位,這個 Tg 沒辦法跟任何其他數據比——這正是不同實驗室 Tg 對不起來的主因之一。 |
| 報表 PDI 與 Mw/Mn 不一致 | 兩個都是「合理」的數字,但兜不起來,通常代表匯出時取了不同積分區間或欄位對應錯位。只有同時拿到三個欄位才驗得出來。 |
這些都不是通用的資料驗證(非空、型別、範圍),而是方法學前提——每條規則背後都是一個「這台儀器的數字在什麼條件下才有意義」的事實。
大部分驗證框架只有「過/不過」。儀器數據需要第三種:
- Pass — 通過全部規則,進
analysis_ready檢視供下游使用 - Conditional — 數值可用但有限制條件(例:校正曲線過期,僅限同曲線內比較)。入庫、標註限制、不進分析檢視。直接丟掉會損失可用資料,直接放行會製造假結論。
- Reject — 違反物理必然性或方法前提(例:Mw < Mn、Tg ≥ Tm),不得進入分析。
原始值一律不刪除、不修正,只在旁邊標註判定與原因。任何「清理」都必須可回溯。
同一個物理量,各廠商叫不同名字。Agilent 寫 Calibration ID、Waters 寫 Calib. File、舊機台寫 CalID。解析邏輯本身很單純,別名對映表才是這個元件的核心資產——它就是「來源端資料規格」的具體形式。
未對映到的欄位會被列出來(未對映欄位:Atmosphere、Operator),不靜默吞掉——因為今天沒用到的欄位,可能就是明天要追的線索。
已產生 40 份儀器報表至 data/incoming(其中 6 份含刻意缺陷,seed=42)
解析成功 40 份、失敗 0 份
未對映欄位(需擴充別名表):Atmosphere、Operator
── 品質閘結果 ──
Pass 27 (67.5%)
Conditional 9 (22.5%)
Reject 4 (10.0%)
可供分析(analysis_ready 檢視):27 筆
模擬器植入的 6 個缺陷全數被攔下,另外揪出 3 筆過期校正曲線造成的 Conditional。
dotnet test # 31 個測試
# 產生模擬報表,批次擷取
dotnet run --project src/Instrument.Simulator -- data/incoming 40 42
dotnet run --project src/Instrument.Gateway -- --source file --dir data/incoming
# 常駐監看資料夾
dotnet run --project src/Instrument.Gateway -- --source file --dir data/incoming --watch
# TCP 監聽,等工作站推送
dotnet run --project src/Instrument.Gateway -- --source tcp --port 5560
# 串列埠(需實機或虛擬串列埠)
dotnet run --project src/Instrument.Gateway -- --source serial --serial /dev/ttyUSB0 --baud 9600批次模式為預設,方便 CI 與重現;--watch 與 --source tcp|serial 為常駐串流模式,
對應實際部署的背景服務(Windows 上即 Windows Service)。
推送三份報表,前兩份刻意黏在同一次寫入送出:
✓ 127.0.0.1:49364 → Pass
✗ 127.0.0.1:49364 → Reject|Mw(20000) < Mn(42000),違反物理定義…; PDI 0.476 < 1…
✗ 127.0.0.1:49364 → Reject|無校正曲線識別碼;GPC 分子量是相對值,無曲線即無意義
黏包被正確切成三筆,來源識別記錄為連線端點,方便追回是哪台工作站送的。
- UPSERT 而非 INSERT:以
(source_file, sample_id, measured_at)為唯一鍵。儀器工作站常重複匯出同一份報表,重跑閘道器不該產生重複列——整條管線可安全重放(已實測:重放後筆數不變)。 - 直接寫 SQL 而非 ORM:schema 的意圖(唯一鍵、索引、
analysis_ready檢視)在程式碼裡看得見。 - 品質閘參數外顯(
QualityGateOptions):校正有效天數、PDI 容許差、DSC 標準速率與樣品重量區間,依實驗室 SOP 調整不必改程式。 - 測試測反向案例:31 個測試裡,只有 2 個測「乾淨資料會通過」,其餘都在測髒資料會不會被擋下來、且擋在正確的等級。
src/Instrument.Core/
Sources/ 通訊層:檔案投放/TCP/串列埠/廠商 SDK/OPC UA 契約
Models.cs 量測模型與三級判定
ReportParser.cs 跨廠商欄位別名對映
QualityGate.cs 10 條來源端品質規則
MeasurementStore.cs SQLite persistence(UPSERT 冪等、analysis_ready 檢視)
IngestPipeline.cs 來源 → 解析 → 品質閘 → 入庫
src/Instrument.Gateway/ 閘道器主程式(批次/串流,來源可由參數選擇)
src/Instrument.Simulator/ 儀器工作站模擬器(含刻意缺陷)
tests/Instrument.Tests/ xUnit 31 項:品質閘 18、解析器 7、通訊來源 6
各通訊來源的完成度不同,逐條列清楚:
| 來源 | 狀態 |
|---|---|
| 檔案投放 | ✅ 已實作並實測(含寫入完成判定、鎖定重試) |
| TCP | ✅ 已實作並實測(含黏包切分、超長防護、連線中斷處理) |
| 廠商 SDK | ✅ 整合點以委派注入,已用測試替身驗證;實際 SDK 為廠商私有無法散布 |
| 串列埠 | |
| OPC UA | ❌ 僅定義契約與連線參數,未實作。需 OPC Foundation 函式庫、憑證信任鏈與實機端點才能驗證;寫一個跑得動但沒對過實機的版本只會製造「已完成」的假象 |
其他限制:
- 報表格式參考實際儀器軟體的匯出樣式撰寫,但不是任何廠商的真實檔案。
- SQLite 為單機情境;產線環境會換成 SQL Server/PostgreSQL,並需要重試佇列、背壓與斷線續傳。
- 未做:認證授權、Web UI、告警通知、歷史趨勢圖、OPC UA 斷線重連與 Session 重建。
資料工程師寫得出這支程式,但編不出裡面的資料陷阱——要知道校正曲線會過期、Tg 依速率而變、PDI 該和 Mw/Mn 對得起來,得先在儀器前面站過幾年。