iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0

摘要
Day 5 已確認 tw.gov.mohw.twcore#1.0.0 package 可載入,且三個 MVP Profile canonical 可讀。Day 6 接著實測 HAPI validator support chain 能不能真的拿這些 Profile 去驗證 Bundle 內的 Patient、Observation、DiagnosticReport。

為什麼 package loading 成功後,還要再做 Profile validation 實測?

Day 5 已經回答:

HAPI 能不能在本機拿到 TW Core IG v1.0.0 的 package 與 Profile 定義?

Day 6 要回答的是下一個問題:

拿得到 StructureDefinition 之後,HAPI 能不能真的用它們驗證 Resource?

這兩件事仍然不同。

package loading 成功只代表 Profile 定義可取得。

Profile validation 成功執行才代表 validator support chain 可以解析 Profile、產生 snapshot、套用 constraints,並回傳 OperationOutcome issue。

所以 Day 6 不急著宣稱資料符合 TW Core,而是先做一個小範圍實測,確認正式驗證鏈能不能跑起來。

今天的實測範圍

Day 6 仍固定使用這個 package:

Package ID: tw.gov.mohw.twcore
Package version: 1.0.0
Package coordinate: tw.gov.mohw.twcore#1.0.0
Canonical base: https://twcore.mohw.gov.tw/ig/twcore
FHIR version: R4 4.0.1

驗證範圍只限 Bundle entry 裡的三種 MVP Resource:

Resource Canonical
Patient https://twcore.mohw.gov.tw/ig/twcore/StructureDefinition/Patient-twcore
Observation https://twcore.mohw.gov.tw/ig/twcore/StructureDefinition/Observation-simple-twcore
DiagnosticReport https://twcore.mohw.gov.tw/ig/twcore/StructureDefinition/DiagnosticReport-twcore

學習重點

  • package loading 成功不等於 Profile validation 成功。
  • HAPI Profile validation 需要 validation support chain,不只是 FilesystemPackageCacheManager
  • TW Core Profile issue 要和 FHIR R4 base OperationOutcome 分開顯示。
  • PASSED 只能在 Profile validation 已執行且沒有 error/fatal 時出現。
  • validator support chain 不穩時,要回 NOT_EVALUATED,不能把設定問題誤報成資料通過或資料錯誤。

核心流程

Day 6 的流程在合法 Bundle 通過前置 gate 之後:

Bundle 已通過 JSON / FHIR R4 parse / Resource Type Gate
        ↓
TwCoreValidationService
        ↓
TwCorePackageProbe
        ↓
確認 tw.gov.mohw.twcore#1.0.0 可載入
        ↓
HapiTwCoreProfileValidator
        ↓
建立 TW Core validation support chain
        ↓
對 Patient / Observation / DiagnosticReport 分別套用固定 Profile
        ↓
收集 TW Core Profile OperationOutcome issue
        ↓
回報 PASSED / FAILED / NOT_EVALUATED

重點是 Day 6 開始真的執行 Profile validation。

因此 TW Core validation 不再固定是 NOT_EVALUATED

只要正式 Profile validation 回傳 error 或 fatal,TW Core validation 就應該是 FAILED

實作

Day 6 新增三個小型類別:

src/main/java/com/twlab/qualitygate/validation
├─ TwCoreProfileValidator.java
├─ TwCoreProfileValidationResult.java
└─ HapiTwCoreProfileValidator.java
  1. 建立 TwCoreProfileValidator
public interface TwCoreProfileValidator {

  TwCoreProfileValidationResult validate(Bundle bundle);
}

抽成介面的原因和 Day 5 類似:單元測試要能控制 Profile validation 成功、失敗或降級,不應該讓一般測試依賴本機 package cache 或外部 package server。

  1. 建立 TwCoreProfileValidationResult

Profile validation result 回報三個資訊:

public record TwCoreProfileValidationResult(
    ParseStatus status,
    String message,
    List<OperationOutcomeIssue> operationOutcomeIssues
) {}

其中 status 的意義如下:

status 意義
PASSED Profile validation 已執行,沒有 error/fatal
FAILED Profile validation 已執行,存在 error/fatal
NOT_EVALUATED validator support chain、package、canonical 或 terminology 仍不穩
  1. 建立 HapiTwCoreProfileValidator

Day 6 的 validator 只處理三種 Resource:

private static final Map<String, String> PROFILES_BY_RESOURCE_TYPE = Map.of(
    "Patient", CANONICAL_BASE + "/StructureDefinition/Patient-twcore",
    "Observation", CANONICAL_BASE + "/StructureDefinition/Observation-simple-twcore",
    "DiagnosticReport", CANONICAL_BASE + "/StructureDefinition/DiagnosticReport-twcore"
);

驗證時逐一讀取 Bundle entry。

如果 resourceType 不在 MVP 範圍內,就先略過,不在 TW Core 層宣稱通過或失敗。

  1. 建立 validation support chain

Day 6 使用 PrePopulatedValidationSupport 載入 TW Core package 裡的 conformance resources,並搭配 HAPI 內建 R4 profile support 與 snapshot generator:

ValidationSupportChain supportChain = new ValidationSupportChain(
    new DefaultProfileValidationSupport(fhirContext),
    twCoreSupport,
    new SnapshotGeneratingValidationSupport(fhirContext)
);

這裡保留 setNoTerminologyChecks(true)

原因是 Day 6 目標是 Profile validation 實測,不是完整 terminology validation。

  1. 補上 HAPI cache provider

第一次實測時遇到:

HAPI-2200: No Cache Service Providers found.
Choose between hapi-fhir-caching-caffeine and hapi-fhir-caching-guava.

這不是資料錯誤,也不是 TW Core canonical 錯誤,而是 HAPI validation support chain 的 dependency 不完整。

因此補上:

<dependency>
  <groupId>ca.uhn.hapi.fhir</groupId>
  <artifactId>hapi-fhir-caching-caffeine</artifactId>
  <version>${hapi-fhir.version}</version>
</dependency>

補上後,最小 Profile validation 可以實際執行。

  1. 接回 TwCoreValidationService

TwCoreValidationService 現在會先跑 package probe。

只有 package probe 成功後,才會執行 Profile validation:

if (probeResult.loaded()) {
  TwCoreProfileValidationResult profileResult = profileValidator.validate(bundle);
  String message = probeResult.message() + " " + profileResult.message();

  if (profileResult.status() == ParseStatus.PASSED) {
    return TwCoreValidationResult.passed(message, profileResult.operationOutcomeIssues());
  }
  if (profileResult.status() == ParseStatus.FAILED) {
    return TwCoreValidationResult.failed(message, profileResult.operationOutcomeIssues());
  }
  return TwCoreValidationResult.notEvaluated(message, profileResult.operationOutcomeIssues());
}

這樣可以維持 Day 5 的安全前提:

package 不穩時,不執行 Profile validation,也不冒充通過。

實測結果

使用 valid-minimal-lab-bundle.json 驗證時,前四層仍然通過:

JSON parse: PASSED
FHIR R4 parse: PASSED
Resource Type Gate: PASSED
FHIR R4 validation: PASSED

但 TW Core 層變成:

TW Core validation: FAILED
tw.gov.mohw.twcore#1.0.0
TW Core package loading probe 成功:tw.gov.mohw.twcore#1.0.0...
Day 6 已執行 Patient、Observation-simple、DiagnosticReport 的最小 TW Core Profile validation;存在 error/fatal issue,因此 TW Core validation 標示 FAILED。

https://ithelp.ithome.com.tw/upload/images/20260807/20177913yORcjF9kPP.png

這代表:

  • package loading 成功。
  • HAPI validator support chain 已經能執行 TW Core Profile validation。
  • valid-minimal-lab-bundle.json 只是 Day 1~Day 5 的最小測試資料,不保證符合 TW Core Profile。
  • 系統沒有把「FHIR R4 validation passed」誤解成「TW Core validation passed」。

TW Core Profile issues

Day 6 頁面新增 TW Core Profile issues 區塊。

這個區塊顯示 Profile validation 回傳的:

欄位 意義
Severity HAPI 回傳的 issue 嚴重度
Location 對應的 Resource 與 HAPI location
Diagnostics Profile validation 診斷文字

FHIR R4 validation 的 OperationOutcome 與 TW Core Profile issues 分開顯示。

這樣畫面上可以清楚區分:

區塊 代表
OperationOutcome issues FHIR R4 base validation 結果
TW Core Profile issues TW Core Profile validation 結果

https://ithelp.ithome.com.tw/upload/images/20260807/201779130yOzLwjtQm.png

這張圖代表:

Day 6 已經不是只顯示 TW Core package metadata,而是能把正式 Profile validation issue 顯示在頁面上。

這些 TW Core issue 代表什麼?

這次 valid-minimal-lab-bundle.json 會在 TW Core 層失敗,不是因為 JSON 壞掉,也不是因為它不是 FHIR Resource。

前面幾層已經證明它可以被解析:

JSON parse: PASSED
FHIR R4 parse: PASSED
FHIR R4 validation: PASSED

真正的差異是 TW Core Profile 對 Patient、Observation 與 DiagnosticReport 有更細的約束。

例如 base FHIR 允許一份很精簡的 Patient:

{
  "resourceType": "Patient",
  "id": "patient-1"
}

這種資料對 Day 1~Day 5 很適合,因為它可以用來驗證 parser、Bundle inventory、FHIR R4 base validation 與 package loading。

但到了 TW Core Profile validation,系統開始檢查台灣核心規範裡對 Patient、Observation-simple、DiagnosticReport 的 Profile 要求。這時候,原本的最小測試資料就不一定足夠。

所以 Day 6 的 FAILED 不是壞消息。

它反而證明:

系統已經能分辨「FHIR R4 base validation passed」和「TW Core Profile validation passed」不是同一件事。

如果這裡仍然顯示 PASSED,才比較危險,因為使用者可能會誤以為這份最小 Bundle 已經符合 TW Core。

今天先不逐條解釋每一個 TW Core issue 的官方 element path。

原因是 Day 6 的目標是確認 validation chain 能跑,完整 issue 對照與修正資料會需要回查 TW Core IG 的 Profile 定義、constraint、binding 與官方範例,這會是後續建立真正 TW Core 正例時要做的工作。

Day 5 與 Day 6 的差異

項目 Day 5 Day 6
主要問題 package 能不能載入 Profile validation 能不能執行
新增核心 TwCorePackageProbeHapiTwCorePackageProbe TwCoreProfileValidatorHapiTwCoreProfileValidator
TW Core 狀態 package 成功仍是 NOT_EVALUATED validation 執行後可為 PASSEDFAILED
是否讀到 canonical 是,並用 canonical 驗證 Resource
是否轉換 issue 是,顯示 TW Core Profile issues
是否做 Reference rule

真正的差異是:

Day 5 證明 Profile 定義可取得;Day 6 證明 Profile validation chain 可以實際執行,並能把錯誤顯示出來。

自動化驗證

指令:

./mvnw test

測試結果:

Tests run: 18, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

https://ithelp.ithome.com.tw/upload/images/20260807/20177913wHBkNRkouL.png

Day 6 測試重點:

  • package probe 失敗時,TW Core result 仍然是 NOT_EVALUATED
  • package probe 成功後,Profile validator 才會被呼叫。
  • Profile validator 回傳 error/fatal 時,TW Core result 是 FAILED
  • Profile validator 無法穩定執行時,TW Core result 是 NOT_EVALUATED
  • 非法 JSON 與非 Bundle 不會觸發 TW Core validation。

常見錯誤 & 排查

  1. 把 FHIR R4 validation passed 當成 TW Core passed

Day 6 的實測剛好證明這是錯的。

同一份 valid-minimal-lab-bundle.json 可以是:

FHIR R4 validation: PASSED
TW Core validation: FAILED
  1. 把 Profile validation dependency 問題當成資料錯誤

缺少 hapi-fhir-caching-caffeine 時,validator support chain 無法穩定執行。

這應該歸類為 validation support 設定問題,而不是說輸入資料不符合 TW Core。

  1. 把 TW Core Profile issue 混進交換契約 rule

TW Core Profile validation 和 LAB-REF-001 是不同層。

Day 6 即使看到 TW Core FAILED,也不能說交換契約 Reference rule 已經完成。

  1. 驗證整個 collection Bundle

MVP 的 collection Bundle 是專案聚合封裝。Day 6 只對 Bundle entry 裡的 Patient、Observation、DiagnosticReport 套用 TW Core Profile,不驗證 Bundle 本身是正式交換文件或 transaction。

這和「能不能交換」有什麼關係?

Day 6 的結果剛好回到系列標題:

醫療資料通過標準驗證,就真的能交換嗎?

今天看到的是第一個反例:

FHIR R4 validation: PASSED
TW Core validation: FAILED

也就是說,一份資料可以符合 base FHIR 的基本結構,卻還沒有符合 TW Core Profile。

但這還不是終點。

就算未來修到 TW Core validation passed,也只能代表它符合指定 Profile,不代表合作方一定能接受。

交換時還會有更具體的情境規則,例如:

  • Observation.subject 必須指向 Bundle 裡存在的 Patient。
  • DiagnosticReport.result 必須指向 Bundle 裡存在的 Observation。
  • DiagnosticReport 和 Observation 必須對應同一個 Patient。
  • Observation.code 必須在合作方允許的 LOINC 集合裡。
  • Quantity 單位必須符合合作方允許的 UCUM 條件。

這也是為什麼本專案要做分層資料品質閘門:

JSON / FHIR R4 / TW Core Profile / Exchange Contract

每一層回答的問題不同,不能互相冒充。

Day 6 先把 TW Core Profile validation 接起來,下一步才有基礎去做 Reference、LOINC、UCUM 這些交換契約規則。

今天完成了什麼

  • 新增 TwCoreProfileValidator 介面。
  • 新增 TwCoreProfileValidationResult
  • 新增 HapiTwCoreProfileValidator
  • 建立最小 TW Core validation support chain。
  • 對 Patient、Observation-simple、DiagnosticReport 套用固定 Profile canonical。
  • 補上 hapi-fhir-caching-caffeine,讓 HAPI validation support chain 可以執行。
  • TwCoreValidationService 從 package loading probe 前進到 Profile validation。
  • TwCoreValidationResult 可攜帶 TW Core Profile issues。
  • 頁面新增 TW Core Profile issues 區塊。
  • 測試補上 Profile validation failed、Profile validation not evaluated 與 web 文案。

Day 6 尚未處理:

  • valid-minimal-lab-bundle.json 成為真正 TW Core Profile 正例。
  • 完整分析每一個 TW Core Profile issue 的官方 element path。
  • LAB-REF-001LAB-REF-002LAB-REF-003
  • LOINC/UCUM 契約允許集合。
  • Quality Gate 的 Passed / Warning / Blocked 判定。
  • 契約 v1.0/v1.1 比較。

後續分層預計會變成:

quality-gate
├─ parser
├─ fhir-r4-validator
├─ resource-inventory
├─ tw-core-package-probe
├─ tw-core-validator
├─ reference-checker
├─ contract-rule
└─ report

Repository:twcore-data-quality-gate


上一篇
Day5 - 實測 TW Core Package Loading
下一篇
Day7 - 建立第一條交換契約 Reference 規則入口
系列文
醫療資料通過標準驗證,就真的能交換嗎?——30 天打造 TW Core 資料品質閘門10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言