摘要
Day 5 已確認tw.gov.mohw.twcore#1.0.0package 可載入,且三個 MVP Profile canonical 可讀。Day 6 接著實測 HAPI validator support chain 能不能真的拿這些 Profile 去驗證 Bundle 內的 Patient、Observation、DiagnosticReport。
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 |
FilesystemPackageCacheManager。PASSED 只能在 Profile validation 已執行且沒有 error/fatal 時出現。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
TwCoreProfileValidator
public interface TwCoreProfileValidator {
TwCoreProfileValidationResult validate(Bundle bundle);
}
抽成介面的原因和 Day 5 類似:單元測試要能控制 Profile validation 成功、失敗或降級,不應該讓一般測試依賴本機 package cache 或外部 package server。
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 仍不穩 |
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 層宣稱通過或失敗。
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。
第一次實測時遇到:
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 可以實際執行。
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。

這代表:
valid-minimal-lab-bundle.json 只是 Day 1~Day 5 的最小測試資料,不保證符合 TW Core Profile。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 結果 |

這張圖代表:
Day 6 已經不是只顯示 TW Core package metadata,而是能把正式 Profile validation 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 |
|---|---|---|
| 主要問題 | package 能不能載入 | Profile validation 能不能執行 |
| 新增核心 | TwCorePackageProbe、HapiTwCorePackageProbe |
TwCoreProfileValidator、HapiTwCoreProfileValidator |
| TW Core 狀態 | package 成功仍是 NOT_EVALUATED |
validation 執行後可為 PASSED 或 FAILED |
| 是否讀到 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

Day 6 測試重點:
NOT_EVALUATED。FAILED。NOT_EVALUATED。Day 6 的實測剛好證明這是錯的。
同一份 valid-minimal-lab-bundle.json 可以是:
FHIR R4 validation: PASSED
TW Core validation: FAILED
缺少 hapi-fhir-caching-caffeine 時,validator support chain 無法穩定執行。
這應該歸類為 validation support 設定問題,而不是說輸入資料不符合 TW Core。
TW Core Profile validation 和 LAB-REF-001 是不同層。
Day 6 即使看到 TW Core FAILED,也不能說交換契約 Reference rule 已經完成。
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,不代表合作方一定能接受。
交換時還會有更具體的情境規則,例如:
這也是為什麼本專案要做分層資料品質閘門:
JSON / FHIR R4 / TW Core Profile / Exchange Contract
每一層回答的問題不同,不能互相冒充。
Day 6 先把 TW Core Profile validation 接起來,下一步才有基礎去做 Reference、LOINC、UCUM 這些交換契約規則。
TwCoreProfileValidator 介面。TwCoreProfileValidationResult。HapiTwCoreProfileValidator。hapi-fhir-caching-caffeine,讓 HAPI validation support chain 可以執行。TwCoreValidationService 從 package loading probe 前進到 Profile validation。TwCoreValidationResult 可攜帶 TW Core Profile issues。TW Core Profile issues 區塊。Day 6 尚未處理:
valid-minimal-lab-bundle.json 成為真正 TW Core Profile 正例。LAB-REF-001、LAB-REF-002、LAB-REF-003。後續分層預計會變成:
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