iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0

摘要
Day 20 已經把合作方政策從 Java enum 拆成可載入的 contract JSON。Day 21 修正 contract JSON 使其更貼近真實合作方交換契約;不能在 MVP 內合理完成的部分,明確列成 production gap。

這和一般 FHIR Validator 有什麼不同?

FHIR / TW Core validator 可以回答:

這份 Resource 或 Bundle 是否符合 FHIR 結構、Profile、binding 與 IG?

但真實交換前還會遇到另一個問題:

這份資料在指定合作方、指定版本、指定交換情境下,可不可以送出?

所以 Day 21 之後,本專案的定位更精準:

FHIR/TW Core validator = Profile validation
TW Lab Contract Gate = Profile validation + partner exchange contract gate

它概念上類似 validator.dicom.tw/app/ 這種 FHIR validation 入口,但多了一層可載入的合作方交換契約。
也就是說,它不是取代 validator,而是在 validator 結果之外再加上:

合作方 policy assertions
契約 lifecycle
SHALL / SHOULD / MAY blocking policy
LOINC / UCUM terminology policy snapshot
版本比較與 upgrade blocker evidence

今天的實作範圍

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

  • src/main/java/com/twlab/qualitygate/validation/ExchangeContract.java
  • src/main/java/com/twlab/qualitygate/validation/ExchangeContractService.java
  • src/main/java/com/twlab/qualitygate/validation/BundleParseService.java
  • 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
  • src/main/resources/templates/index.html
  • src/test/java/com/twlab/qualitygate/validation/BundleParseServiceTests.java
  • src/test/java/com/twlab/qualitygate/validation/ExchangeContractServiceTests.java
  • src/test/java/com/twlab/qualitygate/validation/TestExchangeContracts.java
  • src/test/java/com/twlab/qualitygate/web/ParseControllerTests.java
  • docs/references/fhir-validation-and-exchange-contracts.md
  • README.md

Day 21 做六件事:

把外部 contract JSON 從 rule-code list 改成 policyAssertions。
替 contract 補 lifecycle metadata。
替 policy assertion 補 obligation / severity。
讓 SHALL error/fatal failure 才 block,SHOULD/MAY failure 只進 warning。
替 LOINC / UCUM 補 terminology policy metadata。
把仍不符合 production FHIR exchange 的部分明確列成 gap。

官方依據與邊界

HL7 FHIR validation 官方文件本來就把 validation 和 business rule 分開看。
FHIR validation 可以檢查:

  • Structure
  • Cardinality
  • Value domains
  • Coding / CodeableConcept bindings
  • Invariants
  • Profiles
  • Questionnaires
  • Business rules

其中 business rules 是規格之外制定的規則,例如 duplicate check、reference resolution、authorization 等。
所以本專案的 exchange contract layer 不是亂加一層,而是把「標準之外的合作方政策」變成可驗證證據。

但也要講清楚:

本專案的 contract JSON 不是 FHIR 官方 resource。
它不是 ImplementationGuide package。
它不是 CapabilityStatement。
它也不是 terminology server。

它目前比較接近:

companion guide / interface specification / trading partner agreement
在 MVP 裡的可執行投影。

最新 validator reference

截至 2026-08-22,單筆 Resource / Profile validation 最應該引用的是:

HL7 FHIR Validator
https://validator.fhir.org/

Inferno 仍然重要,但定位不同。
Inferno 更適合 FHIR Server / Client conformance testing、API interaction、test kit。
而 Inferno Resource Validator 在 2026-07-13 的公告中提到,individual FHIR Resource validator service 預計最快 2026-08 停用,並建議使用 HL7 validator.fhir.org 這類替代服務。

所以本專案和現有工具的區別為:

工具 回答的問題
HL7 FHIR Validator / $validate Resource 是否符合 FHIR / Profile / IG
TW Core official validation guide 如何用 tw.gov.mohw.twcore#1.0.0 驗證 TW Core
Inferno / Touchstone FHIR API / Server / Client 行為是否符合測試情境
TW Lab Contract Gate 同一份 Bundle 在指定合作方契約下能不能送出

Contract JSON 現在長什麼樣?

Day 20 的 contract 還比較像:

這些 rule code 要不要跑?
允許哪些 LOINC / UCUM?

Day 21 改成比較接近 companion guide 條文:

{
  "id": "demo-lab-hospital-a",
  "name": "Demo Lab to Hospital A Exchange Contract",
  "version": "1.1",
  "status": "active",
  "publisher": "Demo Lab Integration Office",
  "jurisdiction": "TW",
  "effectiveDate": "2026-08-21",
  "retireDate": null,
  "fhirVersion": "4.0.1",
  "policyAssertions": [
    {
      "id": "LAB-UNIT-002",
      "source": "demo-lab-hospital-a companion guide section 3.3",
      "requirement": "Observation.valueQuantity.system SHALL be UCUM and code SHALL be allowed by this exchange scenario.",
      "obligation": "SHALL",
      "severity": "error",
      "enabled": true
    }
  ],
  "terminologyPolicy": {
    "loincSystem": "http://loinc.org",
    "loincVersion": "2.78",
    "loincValueSetCanonical": "https://example.org/fhir/ValueSet/demo-lab-hospital-a-lab-codes|1.1",
    "ucumSystem": "http://unitsofmeasure.org",
    "ucumVersion": "2.1",
    "ucumValueSetCanonical": "https://example.org/fhir/ValueSet/demo-lab-hospital-a-lab-units|1.1",
    "expansionTimestamp": "2026-08-21T00:00:00+08:00",
    "scope": "Demo lab exchange subset; runtime uses allowed code arrays as a local expansion snapshot."
  },
  "allowedLoincCodes": ["2345-7", "718-7"],
  "allowedUcumCodes": ["mg/dL", "mmol/L"]
}

這個格式仍然不是 FHIR-native。
但它比單純 rule-code list 更接近真實交換契約,因為它保留了:

  • 誰發布的契約。
  • 哪個 jurisdiction。
  • 哪一天生效。
  • 對應哪個 FHIR version。
  • 哪些條文是 SHALL
  • 哪些條文只是 SHOULDMAY
  • terminology policy 用哪個 ValueSet canonical 與 expansion timestamp 表示。

policyAssertions 和 Java rule 的分工

今天把 contract 與 rule implementation 的分工重新收斂。

Java rule class 仍然負責:

  • 到 Bundle 裡找 ObservationDiagnosticReportPatient
  • 判斷 Bundle-local reference 是否能解析。
  • 判斷 Observation.code.coding 是否含有允許的 LOINC。
  • 判斷 Observation.valueQuantity.system/code 是否符合 UCUM policy。
  • 產生 RuleResult

Contract file 則負責:

  • 哪些 policyAssertions 在這個版本啟用。
  • 每個 assertion 來源於哪個 companion guide section。
  • 每個 assertion 是 SHALLSHOULD 還是 MAY
  • 每個 assertion 的 severity。
  • LOINC / UCUM policy 的版本與 local expansion snapshot。
  • contract lifecycle metadata。

這樣 LAB-UNIT-002 的意思變成:

層次 負責內容
Java rule 會檢查 Observation.valueQuantity.system/code
policy assertion 說這條要求來自哪個合作方條文、是否啟用、是否阻擋
terminology policy 說這次允許值是哪個 ValueSet snapshot 的本機投影

SHALL / SHOULD / MAY 如何影響 Quality Gate?

Day 20 只要 rule fail,就會 block。
這對 demo 很直覺,但不夠像真實 companion guide。

Day 21 改成:

obligation severity Rule failed 時的 Gate
SHALL error / fatal BLOCKED
SHOULD warning PASS_WITH_WARNINGS
MAY information / warning PASS_WITH_WARNINGS 或不阻擋

也就是說,現在 failure 不只看 rule 本身,也看契約條文的強度。

新增測試確認:

同一份 UCUM 錯誤資料
如果 contract 把 LAB-UNIT-002 定義成 SHALL/error -> BLOCKED
如果 contract 把 LAB-UNIT-002 定義成 SHOULD/warning -> PASS_WITH_WARNINGS

這比「所有規則失敗都阻擋」更接近真實交換契約。

terminologyPolicy 做到哪裡?

真實情境通常不會只寫:

"allowedUcumCodes": ["mg/dL", "mmol/L"]

比較合理的是引用 ValueSet canonical、版本與 expansion。
所以 Day 21 補上:

"terminologyPolicy": {
  "loincSystem": "http://loinc.org",
  "loincVersion": "2.78",
  "loincValueSetCanonical": "https://example.org/fhir/ValueSet/demo-lab-hospital-a-lab-codes|1.1",
  "ucumSystem": "http://unitsofmeasure.org",
  "ucumVersion": "2.1",
  "ucumValueSetCanonical": "https://example.org/fhir/ValueSet/demo-lab-hospital-a-lab-units|1.1",
  "expansionTimestamp": "2026-08-21T00:00:00+08:00"
}

但 runtime 仍然使用:

allowedLoincCodes
allowedUcumCodes

原因是目前 MVP 沒有 terminology server。
所以這裡必須誠實稱為:

local expansion snapshot

而不是正式 terminology validation。

首頁顯示更多 Contract metadata

Day 20 首頁只顯示:

Default validation: demo-lab-hospital-a#1.1
Current validation: demo-lab-hospital-a#1.1

Day 21 補上:

Contract lifecycle: active from 2026-08-21
Publisher: Demo Lab Integration Office
FHIR version: 4.0.1
Terminology snapshot: 2026-08-21T00:00:00+08:00

這讓使用者知道本次 Quality Gate 不是只套一個抽象版本號,而是套用一份有狀態、生效日與 terminology snapshot 的 contract。

目前的 Production gap?

已落地:

問題 Day 21 處理方式
自定 rule-code list 不像真實契約 改成 policyAssertions
沒有 lifecycle statuspublisherjurisdictioneffectiveDateretireDatefhirVersion
沒有 SHALL / SHOULD / MAY obligation,並影響 Gate
severity 不可由契約控制 severity,rule result 會套用 contract severity
terminology 太 flat terminologyPolicy 與 local expansion snapshot 說明

未實作:

Production gap 為什麼不放進今天 MVP
正式 FHIR IG package 需要 IG publisher、profiles、ValueSet、examples 與 package build pipeline
Full terminology server validation 需要 terminology server、ValueSet expansion、code system version、inactive code policy
CapabilityStatement validation 需要 live FHIR endpoint,不是單份 Bundle JSON
External FHIR Server reference lookup 需要 endpoint、auth、timeout、retry、錯誤分類與 PHI 傳輸控管
OAuth / SMART / mTLS 這是正式交換平台 security layer
Consent / Provenance / AuditEvent / PHI handling 牽涉隱私、稽核、保存與權限設計
Persistent history 需要資料庫、retention policy、刪除策略與 access control
完整 lab domain coverage 需要明確合作方規格,例如 specimen、performer、organization、referenceRange、interpretation

自動化驗證

本機 Maven 測試:

./mvnw test

結果:

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

Day 21 新增或調整的測試確認:

  • bundled v1.0 / v1.1 contract 可以載入新的 lifecycle metadata。
  • policyAssertions 可以控制 rule 是否啟用。
  • policyAssertions 可以判斷 blocking policy。
  • terminologyPolicy metadata 可以從 contract 載入。
  • SHALL/error 的 UCUM failure 會 BLOCKED
  • SHOULD/warning 的 UCUM failure 會 PASS_WITH_WARNINGS
  • Controller 上傳的 custom contract 必須提供 lifecycle、policy assertion、terminology policy。
  • Controller 上傳 contract 引用未知 rule code 時仍會顯示錯誤。

今天完成了什麼

  • ExchangeContract 新增 lifecycle 欄位。
  • ExchangeContract 新增 nested PolicyAssertion
  • ExchangeContract 新增 nested TerminologyPolicy
  • ExchangeContractService 加強 contract validation。
  • BundleParseService 改成用 contract obligation / severity 判斷 Gate。
  • demo-lab-v1.0.json 改成 policy assertion format。
  • demo-lab-v1.1.json 改成 policy assertion format。
  • 新增 demo-lab-hospital-a-v1.2-reference.json 作為 reference-only specimen。
  • 首頁顯示 contract lifecycle / publisher / FHIR version / terminology snapshot。
  • README 補上 FHIR validator、TW Core、partner contract 的分工。
  • 新增 docs/references/fhir-validation-and-exchange-contracts.md
  • ./mvnw test 通過,測試數 66。

Day 21 尚未處理:

  • Contract JSON Schema validation。
  • Contract diff report / export。
  • 正式 FHIR IG package。
  • Terminology server $expand / $validate-code
  • CapabilityStatement validator。
  • External FHIR Server reference lookup。
  • OAuth / SMART / mTLS。
  • Consent / Provenance / AuditEvent。
  • PHI masking 與 persistent history。
  • 更完整 lab domain rule pack。

目前的 MVP 進度:

validation-flow
├─ JSON parse                                  完成
├─ FHIR R4 parse                               完成
├─ FHIR R4 validation                          完成
├─ TW Core validation / safe NOT_EVALUATED      完成
├─ Partner exchange contract loading
│  ├─ demo-lab-v1.0.json                       完成
│  ├─ demo-lab-v1.1.json                       完成
│  ├─ policyAssertions                         完成
│  ├─ obligation / severity                    完成
│  ├─ lifecycle metadata                       完成
│  ├─ terminologyPolicy metadata               完成
│  ├─ allowedLoincCodes local snapshot          完成
│  ├─ allowedUcumCodes local snapshot           完成
│  └─ optional uploaded contract                完成最小版
├─ Exchange contract rules
│  ├─ LAB-REF-001                              完成並由 contract 啟用
│  ├─ LAB-REF-002                              完成並由 contract 啟用
│  ├─ LAB-REF-003                              完成並由 contract 啟用
│  ├─ LAB-CODE-001                             完成,允許值由 contract 提供
│  ├─ LAB-UNIT-001                             完成並由 contract 啟用
│  └─ LAB-UNIT-002                             完成,允許值由 contract 提供
├─ Quality Gate
│  ├─ SHALL error/fatal blocking                完成
│  └─ SHOULD/MAY warning behavior               完成
├─ Contract comparison
│  ├─ comparison service test                   完成
│  ├─ homepage comparison display               完成
│  ├─ compare checkbox                          完成
│  ├─ uploaded 2+ version comparison             完成
│  └─ upgrade blocker evidence display          完成
├─ Scenario test pack
│  ├─ v1.0 / v1.1 representative cases          完成 4 例
│  └─ SHOULD warning behavior                   完成 1 例
├─ Homepage Quality Test Report                 完成最小版
└─ Reproducible delivery
   ├─ Dockerfile                                完成最小版
   ├─ Docker Compose                            完成最小版並驗證啟動
   └─ GitHub Actions CI                         完成最小版

Repository:twcore-data-quality-gate


上一篇
Day20 - 將合作方交換要求抽成可載入 Contract
系列文
醫療資料通過標準驗證,就真的能交換嗎?——30 天打造 TW Core 資料品質閘門21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言