iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0

摘要
Day 22 已經替 live FHIR /metadata 入口補上 allowlist、redirect blocking、endpoint safety evidence,也讓 contract lifecycle 會影響 Quality Gate。Day 23 把 contract 裡已經宣告的 versioned ValueSet canonical URL 從「文件化 metadata」推進到 terminology server $expand / $validate-code evidence。

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

假設某個檢驗 Bundle 裡的 Observation.code2345-7。它存在於目前 contract 內建的 allowedLoincCodes snapshot,所以本地規則會通過。

但 partner contract 同時宣告了 versioned ValueSet canonical:

https://example.org/fhir/ValueSet/demo-lab-hospital-a-lab-codes|1.1

如果 terminology server 無法展開這份 ValueSet,或 $validate-code 回覆這個 code 不屬於該 ValueSet,那就不能只說「本地清單通過,所以可以交換」。

Day 22 先處理 live endpoint safety 和 contract lifecycle。Day 23 往交換語意再推一層:把 local snapshot rule 的結果,和 terminology server 對 $expand / $validate-code 的實際回覆分開呈現。

也就是說,Day 22 是 live endpoint safety gate;Day 23 是 terminology evidence gate。

目前專案的 contract 已經有:

"terminologyPolicy": {
  "loincValueSetCanonical": "https://example.org/fhir/ValueSet/demo-lab-hospital-a-lab-codes|1.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": ["2345-7", "718-7"],
"allowedUcumCodes": ["mg/dL", "mmol/L"]

Day 23 要處理的就是這個 gap:

ValueSet canonical 不應只停留在 report metadata。
它應該能產生 terminology server 查詢證據。

今天的實作範圍

Day 23 的 request / report flow 改成這樣:

使用者上傳 Bundle
   │
   ├─ 選擇 partner exchange contract
   │     └─ 讀取 terminologyPolicy
   │          ├─ loincValueSetCanonical
   │          └─ ucumValueSetCanonical
   │
   ├─ 執行原本的 local snapshot rules
   │     ├─ LAB-CODE-001:allowedLoincCodes
   │     └─ LAB-UNIT-002:allowedUcumCodes
   │
   └─ 如果有填 terminology server base URL
         │
         ├─ endpoint guard
         │     ├─ allowlist match
         │     ├─ reject userinfo / query / fragment
         │     ├─ reject localhost / private / link-local / metadata IP
         │     └─ redirect 不 follow
         │
         ├─ ValueSet/$expand
         │     ├─ LOINC ValueSet canonical
         │     └─ UCUM ValueSet canonical
         │
         └─ ValueSet/$validate-code
               ├─ Observation.code.coding
               └─ Observation.valueQuantity.system/code

最後 Quality Test Report 會變成三層 evidence:

Quality Gate blocking result
   ├─ FHIR / TW Core / contract local rules
   ├─ Contract lifecycle evidence
   └─ Terminology server evidence
        ├─ endpoint safety evidence
        ├─ ValueSet expansion evidence
        ├─ code validation evidence
        └─ local snapshot fallback evidence

對使用者來說,Day 23 做的是五件事:

新增 optional terminology server base URL / allowlist config。
用 contract terminologyPolicy 呼叫 ValueSet/$expand。
用 Bundle 裡實際 LOINC / UCUM 呼叫 ValueSet/$validate-code。
保留 local allowed code snapshot fallback。
在 Quality Test Report 顯示 terminology server evidence。

實作上主要落在三個位置:configuration 負責 allowlist,validation service 負責 $expand / $validate-code evidence,首頁 report 負責把 server evidence 和 local snapshot fallback 分開呈現。

為什麼先做 terminology server?

目前 LAB-CODE-001LAB-UNIT-002 仍然是:

Observation.code 是否在 allowedLoincCodes 裡?
Observation.valueQuantity.code 是否在 allowedUcumCodes 裡?

這是好的 MVP fallback,但不是 terminology server validation。

Day 23 的正確定位是:

local snapshot = fallback / comparison evidence
terminology server = external terminology evidence

這比直接做 live Patient / Observation / DiagnosticReport lookup 更安全,因為 terminology check 不需要讀取 PHI resource endpoint,也不需要先設計完整 patient workflow。

Terminology server endpoint safety

Day 23 不把 Day 22 的 endpoint safety 重做一套低標準版本。
Terminology server base URL 也應該有類似設定:

qualitygate.terminology.server.allowed-base-urls=
qualitygate.terminology.server.allow-private-network=false

預設空 allowlist 表示:

terminology server evidence = NOT_EVALUATED
HTTP call = not called
local snapshot fallback = still available

Terminology endpoint guard 應拒絕:

  • http / https
  • userinfo
  • query string
  • fragment
  • 非 allowlist host / port / path
  • localhost
  • private network
  • link-local network
  • cloud metadata IP such as 169.254.169.254
  • redirect target

這和 Day 22 的 metadata endpoint hardening 保持同一個安全模型。

$expand 要回答什麼?

$expand 的用途是確認 contract 宣告的 ValueSet 可以被 terminology server 展開。
Day 23 會針對 contract 裡的兩個 canonical URL 建立 expansion evidence:

Contract field Operation
terminologyPolicy.loincValueSetCanonical GET {TERMINOLOGY_BASE}/ValueSet/$expand?url={canonical}
terminologyPolicy.ucumValueSetCanonical GET {TERMINOLOGY_BASE}/ValueSet/$expand?url={canonical}

成功時 report 應顯示:

  • ValueSet canonical
  • operation:$expand
  • terminology server base URL
  • expansion status
  • expansion code count
  • server response evidence

如果 terminology server 回 OperationOutcome,report 不應只顯示 raw JSON。
應該萃取 severity / code / diagnostics,並把該 expansion 標成:

NOT_EVALUATED

原因是 terminology server 沒有成功提供可用 expansion。

$validate-code 要回答什麼?

$validate-code 的用途是確認 Bundle 裡實際出現的 code 是否屬於 contract 宣告的 ValueSet。

Day 23 先只針對現有 rule 已經處理的範圍:

Bundle source Code system ValueSet
Observation.code.coding http://loinc.org loincValueSetCanonical
Observation.valueQuantity.system/code http://unitsofmeasure.org ucumValueSetCanonical

每個實際 code 產生一筆 evidence:

  • path
  • system
  • code
  • ValueSet canonical
  • operation:$validate-code
  • outcome:PASS / FAIL / NOT_EVALUATED
  • server diagnostics
  • local snapshot fallback result

例如:

Observation/obs-1.code.coding[0]
system = http://loinc.org
code = 2345-7
ValueSet = https://example.org/fhir/ValueSet/demo-lab-hospital-a-lab-codes|1.1
server result = PASS
local snapshot = PASS

UCUM 範例:

Observation/obs-1.valueQuantity
system = http://unitsofmeasure.org
code = mg/dL
ValueSet = https://example.org/fhir/ValueSet/demo-lab-hospital-a-lab-units|1.1
server result = PASS
local snapshot = PASS

fallback policy 要怎麼處理?

Day 23 不應該在 terminology server timeout 時把原本可驗證的 Bundle 直接擋掉。

原因是目前 contract 的 allowedLoincCodes / allowedUcumCodes 仍然是本專案可重現的 local expansion snapshot。
所以 fallback policy 應該是:

Terminology server state Quality Gate behavior
未設定 terminology server base URL terminology layer NOT_EVALUATED,local snapshot rules 照跑
base URL 不在 allowlist terminology layer NOT_EVALUATED,local snapshot rules 照跑
$expand timeout / parse failed terminology layer NOT_EVALUATED,local snapshot rules 照跑
$validate-code timeout / parse failed 該 code evidence NOT_EVALUATED,local snapshot rules 照跑
server 明確回 code 不在 ValueSet terminology evidence FAIL,但是否 blocking 仍由 contract SHALL/error policy 與 local rule 結果共同決定

Day 23 的保守做法:

terminology server evidence 本身先不直接取代 LAB-CODE-001 / LAB-UNIT-002。
它先成為獨立 report layer。
blocking 仍由現有 contract rules 決定。

這樣可以避免第一版 terminology integration 因外部 server 差異導致 gate 行為大幅波動。

首頁新增哪些 Report 區塊?

Day 23 首頁新增:

  • Optional terminology server base URL
  • Terminology server evidence
  • ValueSet expansion evidence
  • Code validation evidence
  • Local snapshot fallback evidence

Quality Test Report 的 layer summary 新增一層:

Layer Blocking? 說明
Terminology server evidence No for Day 23 呼叫 $expand / $validate-code,但第一版不取代 local snapshot blocking rules

Terminology report 應該清楚區分:

server evidence = terminology server 實際回覆
local snapshot evidence = contract bundled allowed code arrays

這可以避免使用者誤解:

看到 allowedLoincCodes 通過,不等於 terminology server 已經通過。

自動化驗證

本機 Maven 測試:

./mvnw test

結果:

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

Day 23 新增測試覆蓋:

  • terminology server base URL 未提供時,terminology layer 回 NOT_EVALUATED,且 HTTP client 不被呼叫。
  • base URL 不在 allowlist 時,terminology layer 回 NOT_EVALUATED,且 HTTP client 不被呼叫。
  • LOINC ValueSet $expand 成功時產生 expansion evidence。
  • UCUM ValueSet $expand 成功時產生 expansion evidence。
  • $expandOperationOutcome 時轉成 clear diagnostics。
  • LOINC $validate-code 成功時產生 PASS evidence。
  • UCUM $validate-code 成功時產生 PASS evidence。
  • $validate-code 明確回 false 時產生 FAIL evidence。
  • timeout / invalid JSON / non-FHIR response 時產生 NOT_EVALUATED evidence。
  • local snapshot fallback 在 terminology server 不可用時仍顯示。
  • Controller 首頁顯示 terminology server input 與 report section。

測試 fixture 應該避免打真實 terminology server。
使用 fake TerminologyServerHttpClient 回傳固定 FHIR R4 JSON。

今天完成了什麼

  • contract terminologyPolicy 不再只是 report metadata。
  • LOINC ValueSet canonical 可以產生 $expand evidence。
  • UCUM ValueSet canonical 可以產生 $expand evidence。
  • Bundle 裡的 Observation LOINC code 可以產生 $validate-code evidence。
  • Bundle 裡的 Observation UCUM code 可以產生 $validate-code evidence。
  • terminology server unavailable 時,不會假裝通過,也不會直接破壞 MVP local snapshot gate。
  • 首頁 report 能同時呈現 server terminology evidence 與 local snapshot fallback。
  • README 更新 Day 23 terminology server evidence 的 scope 與 remaining gaps。
  • ./mvnw test 通過。

Day 23 未處理:

  • Formal JSON Schema file。
  • Formal FHIR IG package。
  • Full UCUM algebra / parser。
  • Clinical plausibility checks。
  • Live Patient / Observation / DiagnosticReport lookup。
  • SMART / OAuth / mTLS。
  • Consent / Provenance / AuditEvent production workflow。
  • PHI masking / retention / persistent history。

Day 23 也不應把 terminology server 當成完整臨床判斷。

它只能回答:

這個 code 是否被 terminology server 判定屬於 contract 宣告的 ValueSet?

它不能回答:

這個檢驗值是否臨床合理?
這個單位和數值是否符合病人情境?
這個 code 是否應用在所有檢驗場景?

目前的 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                       完成
│  ├─ lifecycle runtime gate                   完成
│  ├─ terminologyPolicy metadata               完成
│  ├─ allowedLoincCodes local snapshot          完成
│  ├─ allowedUcumCodes local snapshot           完成
│  ├─ optional uploaded contract                完成
│  └─ application-level schema validation       完成
├─ Exchange contract rules
│  ├─ LAB-REF-001                              完成並由 contract 啟用
│  ├─ LAB-REF-002                              完成並由 contract 啟用
│  ├─ LAB-REF-003                              完成並由 contract 啟用
│  ├─ LAB-CODE-001                             完成,允許值由 local snapshot 提供
│  ├─ LAB-UNIT-001                             完成並由 contract 啟用
│  └─ LAB-UNIT-002                             完成,允許值由 local snapshot 提供
├─ Quality Gate
│  ├─ SHALL error/fatal blocking                完成
│  ├─ SHOULD/MAY warning behavior               完成
│  └─ contract lifecycle blocking               完成
├─ Contract comparison
│  ├─ comparison service test                   完成
│  ├─ homepage comparison display               完成
│  ├─ compare checkbox                          完成
│  ├─ uploaded 2+ version comparison             完成
│  ├─ upgrade blocker evidence display          完成
│  ├─ compatibility classification              完成
│  └─ contract difference summary               完成
├─ Unit normalization evidence
│  ├─ glucose mg/dL -> mmol/L                   完成
│  ├─ glucose mmol/L -> mg/dL                   完成
│  └─ unsupported code/unit NOT_EVALUATED        完成
├─ Live FHIR API metadata
│  ├─ optional FHIR base URL                    完成
│  ├─ endpoint allowlist                         完成
│  ├─ endpoint safety evidence                   完成
│  ├─ GET /metadata only                         完成
│  ├─ CapabilityStatement parse                  完成
│  ├─ fhirVersion alignment                      完成
│  ├─ JSON format evidence                       完成
│  └─ IG / profile declaration evidence          完成
├─ Terminology server evidence
│  ├─ optional terminology server base URL       完成
│  ├─ terminology endpoint allowlist             完成
│  ├─ ValueSet/$expand evidence                  完成
│  ├─ ValueSet/$validate-code evidence           完成
│  └─ local snapshot fallback evidence           完成
├─ 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                         完成最小版

下一步預計處理:

live exchange sandbox auth and non-PHI resource interaction

Repository:twcore-data-quality-gate


上一篇
Day22 - 把 Contract Gate 從契約語境推進到升版與 Metadata Evidence
系列文
醫療資料通過標準驗證,就真的能交換嗎?——30 天打造 TW Core 資料品質閘門23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言