iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0

摘要
Day 15 已經把契約 v1.0 / v1.1 的代表案例整理成最小情境測試包,證明 Expected / Actual 結果可以穩定重跑。Day 16 把既有的契約版本比較能力接回首頁,讓使用者貼上同一份 Bundle 後,可以直接看到 v1.0 與 v1.1 的 Gate 差異。

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

Day 15 已經可以在測試裡回答:

同一份 Bundle 在契約 v1.0 與 v1.1 下,Expected / Actual 結果是否穩定?

但畫面仍然只顯示單一版本驗證結果。
今天要補上的問題是:

使用者貼上同一份 Bundle 後,能不能直接看出 v1.0 與 v1.1 的交換結果差在哪裡?

這和交換很有關係。
因為契約升級時,資料可能不是「突然壞掉」,而是「新契約多檢查了一條以前沒檢查的條件」。
如果頁面只顯示最後的 BLOCKED,使用者很難知道:

這是原本就不能交換?
還是升級到 v1.1 後才被擋?

所以 Day 16 的重點是把 Day 14 / Day 15 做好的比較結果,變成可展示的畫面。

今天的實作範圍

今天新增或修改的範圍有:

  • ParseController
  • index.html
  • ParseControllerTests
  • 20260817.md

成果集中在畫面展示層與 controller 測試。

Controller 接上 ContractComparisonService

Day 14 已經建立:

ContractComparisonService

它的責任很單純:

public ContractComparisonResult compare(String bundleJson) {
  return new ContractComparisonResult(
      bundleParseService.parse(bundleJson, ContractVersion.V1_0),
      bundleParseService.parse(bundleJson, ContractVersion.V1_1)
  );
}

也就是同一份 Bundle 跑兩次:

v1.0 → 啟用舊版規則
v1.1 → 啟用新版規則

今天在 ParseController 裡新增 ContractComparisonService

private final BundleParseService bundleParseService;
private final ContractComparisonService contractComparisonService;

POST /parse/validate 原本只把單版結果放進 model:

model.addAttribute("result", bundleParseService.parse(input));

今天改成同時放入比較結果:

model.addAttribute("result", bundleParseService.parse(input));
model.addAttribute("comparison", contractComparisonService.compare(input));

這裡沒有改掉原本的單版驗證結果。
原因是目前首頁仍然需要顯示完整的四層驗證:

JSON parse
FHIR R4 parse
FHIR R4 validation
TW Core validation
Exchange contract rule results

新增的 comparison 只補一個視角:

同一份資料在不同契約版本下的 Gate 與失敗規則。

畫面新增最小比較表

畫面新增一個區塊:

Contract comparison

目前只顯示三欄:

Contract version Quality Gate Failed rule codes
v1.0 依輸入計算 v1.0 失敗規則
v1.1 依輸入計算 v1.1 失敗規則

這個表格刻意保持很小,讓版本比較可以被使用者看見。

UCUM system 測試案例調整與結果

Day 14 / Day 15 原本使用的 UCUM system 錯誤案例是:

observation-quantity-wrong-ucum-system.json

這份 fixture 很適合用在單元測試,因為測試裡的 TW Core validation 是 stub 成通過。
在那個情境下,v1.0 與 v1.1 的差異只會來自交換契約規則:

v1.0 → 沒有啟用 LAB-UNIT-002
v1.1 → 啟用 LAB-UNIT-002

但在實際畫面中,系統會執行真的 TW Core Profile validation。
原本那份 fixture 沒有補齊 Patient identifier、gender、birthDate,以及 Observation category、effective[x] 等 TW Core Profile 需要的最小欄位。

結果會變成:

v1.0 → TW Core validation FAILED → BLOCKED
v1.1 → TW Core validation FAILED + LAB-UNIT-002 FAILED → BLOCKED

這樣畫面看起來就會是兩版都 BLOCKED
它不是契約版本比較壞掉,而是 TW Core 層錯誤先把兩版都擋下來,掩蓋了 v1.1 新增 UCUM 規則造成的差異。

所以 Day 16 新增:

twcore-valid-wrong-ucum-system.json

這份 fixture 先確保 Patient、Observation 與 DiagnosticReport 能通過目前的 TW Core Profile validation,再只把 valueQuantity.system 改成錯誤值。
這樣畫面才能乾淨展示:

v1.0 → PASSED
v1.1 → BLOCKED by LAB-UNIT-002

最重要的展示案例因此改成:

twcore-valid-wrong-ucum-system.json

這份資料的 reference 鏈、LOINC code 與可讀 unit 都是乾淨的。
Patient、Observation 與 DiagnosticReport 也補齊目前 TW Core Profile validation 需要的最小欄位。
唯一問題是 valueQuantity.system 不是 UCUM 官方 URI:

"valueQuantity": {
  "value": 95,
  "unit": "mg/dL",
  "system": "http://example.org/local-units",
  "code": "mg/dL"
}

所以畫面應該呈現:

契約版本 Gate 結果 失敗規則
v1.0 PASSED None
v1.1 BLOCKED LAB-UNIT-002

https://ithelp.ithome.com.tw/upload/images/20260817/20177913KAJJvrazbj.png

這個結果說明:

同一份 Bundle 不是在所有版本都壞掉。
它是在 v1.1 新增 UCUM system/code 要求後才被阻擋。

合法 Bundle 的比較結果

除了版本差異案例,也需要確認正常資料不會因為新增比較表而看起來像壞掉。
使用:

valid-twcore-contract-bundle.json

預期結果是:

契約版本 Gate 結果 失敗規則
v1.0 PASSED None
v1.1 PASSED None

https://ithelp.ithome.com.tw/upload/images/20260817/20177913tku4prDykh.png

這個案例的用途是保留一個對照組:

不是所有資料升級到 v1.1 都會被阻擋。
只有踩到 v1.1 新增規則的資料才會改變 Gate 結果。

Controller 測試補什麼?

Day 15 的 ContractScenarioCaseTests 已經證明比較服務本身是穩定的。
Day 16 補的是 controller 測試,確認比較結果真的回到畫面:

src/test/java/com/twlab/qualitygate/web/ParseControllerTests.java

新增的第一個測試使用合法 Bundle:

mockMvc.perform(post("/parse")
    .param("bundleJson", fixture("valid-twcore-contract-bundle.json")))
  .andExpect(status().isOk())
  .andExpect(content().string(containsString("Contract comparison")))
  .andExpect(content().string(containsString("v1.0")))
  .andExpect(content().string(containsString("v1.1")))
  .andExpect(content().string(containsString("None")));

它確認頁面有顯示:

Contract comparison
v1.0
v1.1
None

新增的第二個測試使用 UCUM system 錯誤案例:

mockMvc.perform(post("/parse")
    .param("bundleJson", fixture("twcore-valid-wrong-ucum-system.json")))
  .andExpect(status().isOk())
  .andExpect(content().string(containsString("Contract comparison")))
  .andExpect(content().string(containsString("v1.0")))
  .andExpect(content().string(containsString("v1.1")))
  .andExpect(content().string(containsString("LAB-UNIT-002")));

它確認使用者在頁面上看得到 v1.1 的失敗規則。

自動化驗證

指令:

./mvnw test

測試結果:

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

https://ithelp.ithome.com.tw/upload/images/20260817/201779132IOwm9965J.png

Day 16 從 57 個測試增加到 59 個測試。

新增的 2 個測試集中在展示入口:

  • 合法 Bundle 顯示 v1.0 / v1.1 都通過,且 failed rule codes 為 None
  • UCUM system 錯誤案例顯示 v1.1 failed rule codes 包含 LAB-UNIT-002

常見錯誤 & 排查

  1. 只顯示 Gate,不顯示 failed rule codes

如果比較表只顯示:

v1.0 → PASSED
v1.1 → BLOCKED

仍然不夠。
因為讀者不知道 v1.1 為什麼阻擋。

所以今天的表格一定要顯示 failed rule codes:

LAB-UNIT-002

這樣才能看出差異來自 UCUM system/code 規則,而不是 reference 或 LOINC 錯誤。

今天完成了什麼

  • /parse/validate 的 POST flow 接上 ContractComparisonService
  • 畫面新增 Contract comparison 區塊。
  • 顯示 v1.0 / v1.1 的 Quality Gate
  • 顯示 v1.0 / v1.1 的 failed rule codes。
  • 補上合法 Bundle 的 controller 顯示測試。
  • 補上 UCUM system 錯誤案例的 controller 顯示測試。
  • ./mvnw test 通過,測試數從 57 增加到 59。

Day 16 尚未處理:

  • 契約 YAML / JSON 載入。
  • 契約啟用 / 停用規則的外部設定。
  • Change Manifest。
  • COMPATIBLE / EXPECTED_BREAKING_CHANGE / UNEXPECTED_REGRESSION 三分類。
  • JSON Diff。
  • History。
  • Docker Compose。
  • GitHub Actions。

目前的 MVP 進度:

validation-flow
├─ JSON parse                                  完成
├─ FHIR R4 parse                               完成
├─ FHIR R4 validation                          完成
├─ TW Core validation / safe NOT_EVALUATED      完成
├─ Exchange contract rules
│  ├─ LAB-REF-001                              完成並接回畫面
│  ├─ LAB-REF-002                              完成並接回畫面
│  ├─ LAB-REF-003                              完成並接回畫面
│  ├─ LAB-CODE-001                             完成並接回畫面
│  ├─ LAB-UNIT-001                             完成並接回畫面
│  └─ LAB-UNIT-002                             完成並接回畫面
├─ Quality Gate                                完成最小版
├─ Contract comparison
│  ├─ ContractVersion                          完成最小版
│  ├─ v1.0 / v1.1 rule selection                完成最小版
│  ├─ comparison service test                   完成最小版
│  └─ homepage comparison display               完成最小版
└─ Scenario test pack
   ├─ minimal expected / actual table           完成最小版
   ├─ v1.0 / v1.1 representative cases          完成 4 例
   └─ NOT_APPLICABLE scenario fixture           完成 1 例

下一步預計處理:

Expected / Actual 結果整理
補足更多情境案例的覆蓋矩陣
Docker Compose 與 CI quality gate

Repository:twcore-data-quality-gate


上一篇
Day15 - 情境測試包最小化
系列文
醫療資料通過標準驗證,就真的能交換嗎?——30 天打造 TW Core 資料品質閘門16
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言