摘要
走到 Day 30,我們完成了TW Lab Contract Gate:一個以 Spring Boot + HAPI FHIR R4 建立的 TW Core lab Bundle exchange quality gate。從最初的 Bundle JSON parsing、FHIR R4 validation、TW Core profile validation,到合作方交換契約規則、契約版本比較、terminology evidence、sandbox readiness、SMART/OAuth token boundary、live sandbox read/search evidence、PHI masking、AuditEvent / Provenance preview,這 30 天把一個單純的驗證工具推進成可展示、可測試、可說明安全邊界的 pre-exchange quality gate。
Observation.subject、DiagnosticReport.result、report / observation patient consistency、LOINC、UCUM、Quantity unit。/metadata endpoint safety。ValueSet/$expand 與 $validate-code evidence。AuditEvent / Provenance preview。read / search-type execution evidence。使用者可以貼上或上傳 FHIR Bundle JSON,也可以選擇 built-in synthetic sandbox lab fixture。系統先切出基礎驗證層:

Bundle,並要求 Bundle.type = collection。專案內建 demo lab contracts:
src/main/resources/contracts/demo-lab-v1.0.json
src/main/resources/contracts/demo-lab-v1.1.json
src/main/resources/contracts/demo-lab-hospital-a-v1.2-reference.json
預設 contract 是:
demo-lab-hospital-a#1.1
Quality Gate 會根據 contract 執行六條 MVP lab exchange rules:
LAB-REF-001:Observation.subject 必須指向 Bundle 內的 Patient。LAB-REF-002:DiagnosticReport.result 必須指向 Bundle 內的 Observation。LAB-REF-003:DiagnosticReport 與 Observation 必須指向同一個 Patient。LAB-CODE-001:Observation LOINC code 必須符合 contract allowed set。LAB-UNIT-001:Quantity observation 必須有可讀單位。LAB-UNIT-002:UCUM system / code 必須符合 contract allowed set。SHALL failure 會成為 blocking issue,SHOULD / MAY failure 則以 warning 呈現。這讓 report 不只回答「FHIR 合不合法」,也回答:
這份 lab Bundle 是否符合合作方交換契約?

完成後的首頁會產生 English-first Quality Test Report,主要 evidence layer 包含:

其中 terminology、metadata、sandbox、privacy、audit / provenance 都是 report evidence layer,不會取代本地 contract gate。最終 blocking outcome 仍然以 FHIR / TW Core failure、contract lifecycle blocker,以及 blocking contract rule failure 為主。

Day 29 後,專案可以在所有 safety gate 通過時產生 request-scoped sandbox read/search evidence,但範圍刻意很窄:
GET {sandboxBase}/Patient/{id}
GET {sandboxBase}/Patient?_id={id}
GET {sandboxBase}/Observation/{id}
GET {sandboxBase}/Observation?_id={id}
GET {sandboxBase}/DiagnosticReport/{id}
GET {sandboxBase}/DiagnosticReport?_id={id}
執行條件包含:
read / search-type。report 只顯示 endpoint called、HTTP status、masked response summary、expected id match 與 retention policy。它不顯示 raw server response,不保存 validation history,不提交 Bundle,也不執行 production endpoint、create、update、delete 或 Bundle submit。

專案最後補上的 privacy 與 audit layer,是為了讓 quality gate 不只「會驗證」,也能說明資料怎麼被處理。
目前 report 明確呈現:
AuditEvent JSON preview。Provenance JSON preview。這不是正式 production audit platform,但已經建立未來接 persistent audit log 或外部 FHIR repository 前所需的最小 resource shape。

這個專案最後不是一般 FHIR validator,也不是正式醫療交換平台。它比較像一個介於兩者之間的 pre-exchange quality gate:
TW Lab Contract Gate
├─ Parser:資料能不能被讀懂
├─ Validator:資料是否符合 FHIR / TW Core
├─ Contract Gate:資料是否符合合作方交換契約
├─ Evidence Report:terminology / metadata / sandbox / privacy / audit evidence
└─ Safety Boundary:只做 request-scoped, allowlisted, non-PHI sandbox evidence
它想回答的是這個系列第一天提出的問題:
If a healthcare Bundle passes standard validation, is it really ready to exchange?
經過 30 天後,答案變得更完整:
不只要看 FHIR validation。
還要看 TW Core profile、合作方 contract、reference completeness、terminology evidence、endpoint safety、sandbox readiness、PHI masking、retention policy,以及 audit / provenance evidence。
雖說醫資系上有相關課程,不過剛開始我對 FHIR 的理解都停留在基礎理論上,甚至 TW Core 是從零開始接觸。過去比較熟悉的是一般 Web 開發或後端 API 流程,但醫療交換不是單純把 JSON 傳出去就結束,而是牽涉標準、Profile、Reference、Terminology、合作方契約與安全邊界。
因此這 30 天比較像是邊做邊補領域知識。從 Day 1 先讓 Bundle 能被解析,到後面慢慢理解為什麼 Observation、DiagnosticReport、Patient 之間的 reference 會影響交換品質,也讓我感受到醫療資訊系統的嚴謹程度與一般練習專案的不同之處。
如果只是做一個表單或 CRUD 系統,雖然也能練習技術,但比較難看出資料品質、交換契約和驗證流程之間的關係。
而醫療資料交換剛好是一個很適合練習工程思維的題目,在衛福部近年全面推動 FHIR(國際醫療資料交換標準)的情況下,相關需求會明顯增加,尤其是與合作方進行契約交換時如何更嚴謹的統一格式。
它不能只追求畫面完成,也不能只看單一 API 成功。每一步都要問:
這份資料為什麼可以交換?
如果不能交換,原因要怎麼被看懂、測試、重現?
這讓我在每天實作時,不只是完成一個功能,而是在累積一套可以說明的 quality gate。
主題一開始鎖定在 TW Core lab Bundle,是因為檢驗資料很適合展示跨 Resource 的交換問題。Patient、Observation、DiagnosticReport 都是常見 Resource,而且 lab code、unit、report result reference 都能對應到實際交換時會遇到的資料品質問題。
使用者動機設計
這個工具的使用者,不是一般消費者,而是需要檢查醫療資料交換品質的開發者、系統整合者或資料交換測試人員。因此畫面和流程的重點不是華麗互動,而是讓 report 可以快速回答:
哪一層通過?
哪一層失敗?
失敗是 blocking 還是 warning?
這個 evidence 有沒有真的呼叫外部 endpoint?
有沒有保存或顯示 PHI?
所以專案採用 Quality Test Report 的形式,把 JSON、FHIR、TW Core、contract rules、terminology、metadata、sandbox、privacy、AuditEvent / Provenance 分層呈現,讓使用者可以逐層檢查交換 readiness。
功能設計
功能設計上,我刻意把專案分成三個層次:
Parser:資料能不能被讀懂
Validator:資料是否符合 FHIR / TW Core
Quality Gate:資料是否符合合作方交換契約與安全邊界
前半段先把 parsing、validation、contract rules 做穩,後半段再補 terminology server evidence、sandbox readiness、OAuth token boundary、live read/search evidence、PHI masking 和 AuditEvent / Provenance preview。
簡單來說,TW Lab Contract Gate 就是希望讓醫療資料交換前,不只知道格式有沒有錯,也能知道這份資料是否已經具備進入下一步交換的證據。
領域知識陌生
FHIR 和 TW Core 一開始最難的地方,是很多錯誤不是程式語法錯,而是交換語意不完整。例如 DiagnosticReport.result 沒有指到 Bundle 內的 Observation,JSON 本身可能完全合法,但交換時就會出問題。
這讓我花了很多時間理解 Resource 之間的關係、Profile validation 和 partner contract rule 的差異,也更理解為什麼單靠標準 validator 還不夠。
Debug 卡關
實作過程中也遇到很多技術卡關,例如 TW Core package loading、OperationOutcome issue 整理、contract version diff、terminology server response parsing、sandbox endpoint allowlist、OAuth token masking,以及 sandbox read/search response summary。
其中最需要小心的是後半段的安全邊界。sandbox live evidence 很容易一不小心就變成「可以讀取任意 Patient / Observation」的工具;OAuth token evidence 也可能不小心把 access token 或 client secret 顯示在 report 裡;AuditEvent / Provenance 如果放入 raw Bundle,又會造成 PHI 風險。
所以後面每天都在反覆確認:
這個 HTTP call 有沒有 allowlist?
這個 evidence 是 request-scoped 嗎?
這個 report 會不會顯示 PHI?
這個結果應該 blocking,還是只作為 evidence?
這些問題雖然讓開發速度變慢,但也讓專案從功能堆疊變成更有邊界感的工程練習。
目前專案可以:
不包括:
AuditEvent / Provenance 外部提交。簡單來說,TW Lab Contract Gate 希望證明一件事:
醫療資料交換不能只問「格式對不對」。
更重要的是,要能說明「為什麼這份資料現在可以,或不可以,安全地進入下一步交換」。