iT邦幫忙

2026 iThome 鐵人賽

DAY 4
0

摘要
Day 3 已經能盤點 Bundle 裡有哪些 Resource。Day 4 建立 TW Core 驗證層的狀態模型、固定 package metadata 與降級處理訊息,確保尚未驗證的資料不會被誤顯示成通過。

為何 TW Core 層需要先建立降級處理 (Graceful Degradation)?

Day 3 的頁面已經能顯示:

  • JSON parse
  • FHIR R4 parse
  • Resource Type Gate
  • FHIR R4 validation
  • Resource inventory

但還有一層很重要的驗證沒有真正接上:

TW Core Profile validation

在這系列中,TW Core 使用的套件為:

Package ID: tw.gov.mohw.twcore
Package version: 1.0.0
Package coordinate: tw.gov.mohw.twcore#1.0.0
FHIR version: R4 4.0.1

理想上,系統後面會針對 Bundle 內三種 Resource 分別套用 TW Core Profile:

Resource TW Core Profile
Patient TW Core Patient
Observation TW Core Simple Observation
DiagnosticReport TW Core DiagnosticReport

但 Day 4 還沒有要完成正式 package loading。
原因是 TW Core package 載入、HAPI validation support、terminology 設定與 profile canonical 對應都需要實測。
如果還沒有穩定載入 package,就把 TW Core validation 顯示成 PASSED,會讓使用者誤以為資料已經符合 TW Core Profile。

所以 Day 4 先建立降級處理:

尚未穩定載入 tw.gov.mohw.twcore#1.0.0,因此 TW Core validation 顯示 NOT_EVALUATED

資料品質閘門不只要知道什麼通過,也要清楚標示什麼還沒有被檢查。

Day 4 的 NOT_EVALUATED 狀態代表什麼?

Day 3 TW Core 的 NOT_EVALUATED 是頁面硬編碼;Day 4 則改由 TwCoreValidationService 回傳資料化結果,讓 TW Core package 不穩定時,優先降級處理顯示 NOT_EVALUATED。

TW Core validation result 暫時先建立這四個欄位,顯示狀態與 package metadata(中繼資料):

欄位 說明
status PASSEDFAILEDNOT_EVALUATED
packageId 固定 tw.gov.mohw.twcore
packageVersion 固定 1.0.0
message 成功、失敗或安全降級原因

因此 Day 4 目前只會回傳:

status: NOT_EVALUATED
packageId: tw.gov.mohw.twcore
packageVersion: 1.0.0
message: 尚未穩定載入 tw.gov.mohw.twcore#1.0.0,不冒充 Profile 通過。

而這裡的 NOT_EVALUATED 有兩種情境:

第一種是合法 Bundle 已經通過前面的 gate,但 TW Core package 尚未穩定載入:

TW Core validation = NOT_EVALUATED
原因:尚未穩定載入 tw.gov.mohw.twcore#1.0.0,不冒充 Profile 通過。

第二種是前面的 JSON、FHIR R4 parse 或 Bundle gate 還沒通過,因此 TW Core 層根本不應該執行:

TW Core validation = NOT_EVALUATED
原因:JSON、FHIR R4 parse 或 Bundle gate 尚未通過。

這樣可以避免在非法 JSON 或非 Bundle 輸入時,硬跑後續 Profile validation,造成錯誤層級混亂。

學習重點

  • 分層驗證:TW Core validation 必須獨立於 FHIR R4 base validation 顯示。
  • 降級處理:package 尚未穩定載入時,明確顯示 NOT_EVALUATED
  • Metadata:即使尚未執行正式 Profile validation,也先固定顯示 package ID 與 version。
  • 前置條件:只有 JSON、FHIR R4 parse 與 Bundle gate 通過後,TW Core 層才有意義。
  • 誠實狀態:NOT_EVALUATED 不等於錯誤,也不等於通過。

核心流程

Day 4 的流程如下:

使用者貼上或上傳 JSON
        ↓
Jackson 檢查 JSON grammar
        ↓
JSON 合法?
  ├─ 否 → JSON parse FAILED,TW Core validation NOT_EVALUATED
  └─ 是
        ↓
HAPI FHIR R4 parser 解析 Resource
        ↓
FHIR Resource 可解析?
  ├─ 否 → FHIR R4 parse FAILED,TW Core validation NOT_EVALUATED
  └─ 是
        ↓
是 Bundle?
  ├─ 否 → Resource Type Gate FAILED,TW Core validation NOT_EVALUATED
  └─ 是
        ↓
HAPI FHIR R4 Validator
        ↓
整理 OperationOutcome issues
        ↓
盤點 Bundle.entry
        ↓
進入 TW Core validation shell
        ↓
尚未穩定載入 tw.gov.mohw.twcore#1.0.0
        ↓
TW Core validation NOT_EVALUATED

這裡刻意把 TW Core validation shell 放在 Resource Type Gate 後面。因為 TW Core Profile 是套用在 Bundle 內的 Patient、Observation、DiagnosticReport 上,如果輸入根本不是 Bundle,後續層就不應該執行。

實作

今天的架構比 Day 3 多了一個 TW Core validation shell:

             Browser
                |
          Multipart JSON
                |
        Spring Controller
                |
       BundleParseService
      /       |        \
 Jackson   HAPI      FHIR R4
 Parser    Parser    Validator
      \       |        /
       Resource Inventory
                |
      TwCoreValidationService
                |
       ValidationResult
                |
          Thymeleaf UI

Day 4 TW Core 先獨立成 TwCoreValidationService,但 parser、FHIR R4 validation 與 inventory 的流程仍由 BundleParseService 串起來。等之後真正載入 package 後,再視複雜度拆出更完整的 validation support 設定。

專案目錄新增或調整如下:

src/main/java/com/twlab/qualitygate
├─ validation
│  ├─ BundleEntrySummary.java
│  ├─ BundleParseService.java
│  ├─ OperationOutcomeIssue.java
│  ├─ ParseStatus.java
│  ├─ ResourceSummary.java
│  ├─ TwCoreValidationResult.java
│  ├─ TwCoreValidationService.java
│  └─ ValidationResult.java
└─ web
   └─ ParseController.java

src/main/resources/templates
└─ index.html
  1. 建立 TwCoreValidationResult

TwCoreValidationResult 是頁面顯示用資料傳輸物件 (DTO)。Day 4 先固定 package ID 與 package version,讓結果頁可以清楚顯示目前瞄準的 TW Core 版本。

public record TwCoreValidationResult(
    ParseStatus status,
    String packageId,
    String packageVersion,
    String message
) {
  public static final String PACKAGE_ID = "tw.gov.mohw.twcore";
  public static final String PACKAGE_VERSION = "1.0.0";

  public static TwCoreValidationResult notEvaluated(String message) {
    return new TwCoreValidationResult(
        ParseStatus.NOT_EVALUATED,
        PACKAGE_ID,
        PACKAGE_VERSION,
        message
    );
  }
}

這裡重用既有的 ParseStatus,因為目前狀態集合剛好皆是 PASSEDFAILEDNOT_EVALUATED。如果後續需要再另外建立規則層的狀態模型。

  1. 建立 TwCoreValidationService

Day 4 的 service 只負責回傳降級處理結果,不做真正 Profile validation。

@Service
public class TwCoreValidationService {

  public TwCoreValidationResult validate(Bundle bundle) {
    return TwCoreValidationResult.notEvaluated(
        "尚未穩定載入 tw.gov.mohw.twcore#1.0.0,不冒充 Profile 通過。"
    );
  }

  public TwCoreValidationResult notEvaluatedBeforeBundleGate() {
    return TwCoreValidationResult.notEvaluated(
        "TW Core validation 未執行:JSON、FHIR R4 parse 或 Bundle gate 尚未通過。"
    );
  }
}

validate(Bundle bundle) 先保留 Bundle 參數,是為了讓後續正式實作時可以直接從 Bundle 內取出 Patient、Observation 與 DiagnosticReport,再逐一套用 TW Core Profile。

  1. 擴充 ValidationResult

Day 3 的 ValidationResult 已經有 parse status、FHIR validation status、OperationOutcome issues 與 resource inventory。Day 4 新增 twCoreValidationResult

public record ValidationResult(
    ParseStatus jsonStatus,
    ParseStatus fhirR4Status,
    ParseStatus resourceTypeStatus,
    ParseStatus fhirValidationStatus,
    TwCoreValidationResult twCoreValidationResult,
    List<OperationOutcomeIssue> operationOutcomeIssues,
    ResourceSummary resourceSummary,
    List<BundleEntrySummary> bundleEntrySummaries,
    Integer resourceCount,
    String resourceType,
    String errorMessage
) {}

這樣頁面不用硬編碼 TW Core 狀態,也不用猜測是否有執行 Profile validation。所有狀態都從同一個結果物件讀取。

  1. 在 Bundle gate 通過後呼叫 TW Core 層

BundleParseService 只有在確定輸入是 Bundle 後,才會呼叫 TW Core validation shell:

List<BundleEntrySummary> bundleEntrySummaries = bundle.getEntry().stream()
    .map(BundleEntrySummary::fromEntry)
    .toList();
TwCoreValidationResult twCoreValidationResult = twCoreValidationService.validate(bundle);

return new ValidationResult(
    ParseStatus.PASSED,
    ParseStatus.PASSED,
    ParseStatus.PASSED,
    hasErrors(validationResult.getMessages()) ? ParseStatus.FAILED : ParseStatus.PASSED,
    twCoreValidationResult,
    issues,
    ResourceSummary.fromEntries(bundleEntrySummaries),
    bundleEntrySummaries,
    bundle.getEntry().size(),
    "Bundle",
    null
);

非法 JSON、FHIR parse 失敗或不是 Bundle 時,則回傳:

twCoreValidationService.notEvaluatedBeforeBundleGate()

讓 TW Core 層的狀態與前置條件保持一致。

  1. 更新頁面

Day 3 的頁面原本把 TW Core validation 寫死成 NOT_EVALUATED。Day 4 改成讀取 result.twCoreValidationResult

<dt>TW Core validation</dt>
<dd>
  <span class="status"
        th:classappend="${result.twCoreValidationResult.status.name() == 'PASSED'} ? ' passed' : (${result.twCoreValidationResult.status.name() == 'FAILED'} ? ' failed' : ' not-evaluated')"
        th:text="${result.twCoreValidationResult.status}">NOT_EVALUATED</span>
  <span class="muted"
        th:text="${result.twCoreValidationResult.packageId + '#' + result.twCoreValidationResult.packageVersion}">
    tw.gov.mohw.twcore#1.0.0
  </span>
  <br>
  <span class="muted" th:text="${result.twCoreValidationResult.message}">
    尚未穩定載入 tw.gov.mohw.twcore#1.0.0,不冒充 Profile 通過。
  </span>
</dd>

畫面上仍然會看到 NOT_EVALUATED,但意義已經不同。Day 3 是硬編碼文字;Day 4 是正式資料模型回傳的驗證層狀態。

測試資料

Day 4 沿用 Day 3 的測試資料。

  1. valid-minimal-lab-bundle.json

這份資料通過 JSON parse、FHIR R4 parse、Resource Type Gate 與 FHIR R4 validation。Day 4 用它確認 TW Core validation shell 會顯示:

TW Core validation: NOT_EVALUATED
Package: tw.gov.mohw.twcore#1.0.0
Message: 尚未穩定載入 tw.gov.mohw.twcore#1.0.0,不冒充 Profile 通過。
  1. 非法 JSON

貼上:

{ not-json

這個案例用來確認 TW Core 層不會在 JSON parse 失敗時執行。

  1. 非 Bundle FHIR Resource

貼上:

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

這個案例可以被 HAPI FHIR parser 解析,但不符合目前入口只接受 Bundle 的規則,所以 Resource Type Gate 會失敗,TW Core 層也會維持 NOT_EVALUATED

執行結果

Day 4 最重要的展示,是 TW Core validation 不再只是頁面硬編碼,而是有自己的狀態、package metadata 與降級原因。

  1. 合法 minimal lab Bundle

使用首頁預設 sample,或貼上 valid-minimal-lab-bundle.json,按下 Parse Bundle

結果會看到:

JSON parse: PASSED
FHIR R4 parse: PASSED
Resource Type Gate: PASSED
FHIR R4 validation: PASSED
TW Core validation: NOT_EVALUATED
tw.gov.mohw.twcore#1.0.0
尚未穩定載入 tw.gov.mohw.twcore#1.0.0,不冒充 Profile 通過。

https://ithelp.ithome.com.tw/upload/images/20260804/201779137J4PmtcLNq.png

這代表前面幾層已經通過,但 TW Core Profile 尚未正式驗證。系統沒有把這一層偽裝成 Passed。

Resource summary 與 Bundle entry table 仍然維持 Day 3 行為:

Patient: 1
Observation: 1
DiagnosticReport: 1
Not evaluated: 0

https://ithelp.ithome.com.tw/upload/images/20260804/20177913yQ3l5OB60N.png

  1. 非法 JSON

貼上:

{ not-json

結果停在 JSON parse layer:

JSON parse: FAILED
FHIR R4 parse: FAILED
Resource Type Gate: FAILED
FHIR R4 validation: NOT_EVALUATED
TW Core validation: NOT_EVALUATED

TW Core message 會說明:

TW Core validation 未執行:JSON、FHIR R4 parse 或 Bundle gate 尚未通過。

https://ithelp.ithome.com.tw/upload/images/20260804/20177913LI35jzqjPT.png

這表示錯誤資料不會繼續進入後面的 Profile validation shell。

  1. 非 Bundle FHIR Resource

貼上 Patient Resource:

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

結果會看到:

JSON parse: PASSED
FHIR R4 parse: PASSED
Resource Type Gate: FAILED
FHIR R4 validation: NOT_EVALUATED
TW Core validation: NOT_EVALUATED

錯誤訊息會保留:

FHIR R4 parse succeeded, but resourceType is not Bundle.

https://ithelp.ithome.com.tw/upload/images/20260804/20177913YrVL4Og2ZL.png

這個案例用來確認 TW Core 層不會在非 Bundle 輸入上執行,避免錯誤層級混在一起。

Day 3 與 Day 4 的差異

Day 3 的 TW Core validation 還只是頁面上固定顯示的 NOT_EVALUATED

Day 4 新增 TW Core validation result 後,變成:

情境 FHIR R4 Validation Resource Inventory TW Core Validation
minimal lab Bundle PASS Patient 1、Observation 1、DiagnosticReport 1 NOT_EVALUATED,顯示 package metadata
缺少 Bundle.type FAIL 仍可盤點 Bundle entry NOT_EVALUATED,尚未正式載入 TW Core
unsupported Resource PASS 或 warning 非 MVP Resource 標示 NOT_EVALUATED NOT_EVALUATED,尚未正式載入 TW Core
JSON 壞掉 NOT_EVALUATED 不執行 NOT_EVALUATED,前置 gate 未通過
Patient Resource NOT_EVALUATED 不執行 NOT_EVALUATED,前置 gate 未通過

重點是:

TW Core NOT_EVALUATED 現在有資料來源與原因,不再只是 UI 上的一行固定文字。

自動化驗證

指令:

./mvnw test

正常測試結果:

Tests run: 13, Failures: 0, Errors: 0, Skipped: 0

Day 4 測試重點:

  • 合法 Bundle 會產生 TW Core NOT_EVALUATED
  • TW Core result 會顯示 tw.gov.mohw.twcore#1.0.0
  • 合法 Bundle 的降級訊息包含「不冒充 Profile 通過」。
  • 非法 JSON 的 TW Core result 是 NOT_EVALUATED
  • 非 Bundle FHIR Resource 的 TW Core result 是 NOT_EVALUATED
  • Day 3 的 Resource inventory 測試維持通過。

常見錯誤 & 排查

  1. 把 TW Core NOT_EVALUATED 當成通過

Day 4 的 NOT_EVALUATED 只代表尚未完成正式 TW Core Profile validation。它不能被寫成 Passed,也不能在 README 或文章裡描述成「已符合 TW Core」。

  1. 只在頁面硬編碼 package 版本

Package ID 與 version 應該從 TwCoreValidationResult 顯示。這樣後面真正接上 package loading 時,結果頁不用重寫。

  1. 在非法 JSON 時執行 TW Core validation

TW Core 層必須放在 JSON parse、FHIR R4 parse 與 Bundle gate 後面。前面任一層失敗時,TW Core 只能顯示 NOT_EVALUATED

  1. 忘記保留 Day 3 inventory

Day 4 只是新增 TW Core validation shell,不應該改變 PatientObservationDiagnosticReport 的 inventory 顯示,也不應該開始判斷 Reference 正確性。

  1. 太早處理交換契約規則

LAB-REF-001LAB-REF-002LAB-REF-003 是第 2 週的工作。Day 4 只建立 TW Core 層的可觀測狀態。

今天完成了什麼

  • 新增 TwCoreValidationResult DTO。
  • 新增 TwCoreValidationService
  • 固定記錄 tw.gov.mohw.twcore#1.0.0
  • 擴充 ValidationResult,加入 TW Core validation result。
  • BundleParseService 中接入 TW Core validation shell。
  • 只有 JSON、FHIR R4 parse 與 Bundle gate 通過後才執行 TW Core shell。
  • 非法 JSON、FHIR parse 失敗、非 Bundle 與檔案讀取失敗都明確顯示 TW Core NOT_EVALUATED
  • 更新結果頁,顯示 TW Core status、package 與降級原因。
  • 補上合法 Bundle、非法 JSON 與非 Bundle 的測試。
  • 確認 Day 3 resource inventory 行為維持不變。

Day 4 尚未處理:

  • 正式載入 tw.gov.mohw.twcore#1.0.0 package。
  • TW Core Patient Profile validation。
  • TW Core Simple Observation Profile validation。
  • TW Core DiagnosticReport Profile validation。
  • TW Core OperationOutcome issue 轉換。
  • Reference integrity 規則。
  • LOINC/UCUM 契約允許集合。
  • Quality Gate 的 Passed / Warning / Blocked 判定。

後續分層預計會變成:

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

Repository:twcore-data-quality-gate


上一篇
Day3 - 盤點 Bundle 裡有哪些 Resource
系列文
醫療資料通過標準驗證,就真的能交換嗎?——30 天打造 TW Core 資料品質閘門4
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言