iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0

摘要
Day 10 已完成第四條交換契約規則 LAB-CODE-001,確認 Observation.code 是否包含合作方允許的 LOINC coding。Day 11 接著實作第五條規則 LAB-UNIT-001:當 Observation.value[x]Quantity 時,valueQuantity.unit 必須存在,讓檢驗數值不只機器可解析,也能被合作方系統與人工流程直接閱讀。

為什麼 unit 規則要放在 UCUM 規則之前?

Day 10 的 LOINC 規則回答的是:

這筆 Observation 是不是合作方允許接收的檢驗項目?

例如:

Observation.code.coding = http://loinc.org|2345-7

但就算檢驗項目是允許的,檢驗結果本身仍然可能缺少必要的單位資訊。

例如一筆血糖結果可能長這樣:

"valueQuantity": {
  "value": 95,
  "system": "http://unitsofmeasure.org",
  "code": "mg/dL"
}

這筆資料有 value,也有 UCUM 相關的 systemcode

但缺少人類可讀的:

valueQuantity.unit

如果合作方的畫面或報表直接顯示檢驗結果,缺少 unit 會讓結果變成:

95

而不是:

95 mg/dL

FHIR parser 可能可以解析這筆資料。

TW Core Profile 也不一定會把這個合作方顯示需求視為交換阻擋條件。

但從交換情境來看,合作方會問的是:

這筆 Quantity 檢驗結果是否具有可直接閱讀的單位?

這就是 LAB-UNIT-001 要處理的問題。

它不是完整 UCUM validation。

它是 Quantity 可讀單位存在性檢查。

今天的實作範圍

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

  • LabUnit001ObservationQuantityUnitRule
  • LabUnit001ObservationQuantityUnitRuleTests
  • 2 份 LAB-UNIT-001 專用 fixture
  • Day 11 journal

先把第五條規則的行為固定下來,會比提早擴張 UI 或契約版本功能更重要。

今天完成後,六條核心交換規則的進度變成:

Observation.subject → Patient
DiagnosticReport.result → Observation
DiagnosticReport.subject 與 Observation.subject → 同一 Patient
Observation.code → 契約允許 LOINC
Observation.valueQuantity.unit → 可讀單位存在

剩下最後一條:

Observation.valueQuantity.system/code → 契約允許 UCUM 單位集合

為什麼今天不直接做 UCUM?

Quantity 裡常見的單位欄位有三個:

valueQuantity.unit
valueQuantity.system
valueQuantity.code

它們處理的是不同層次。

unit 比較接近顯示與人工閱讀需求:

"unit": "mg/dL"

systemcode 比較接近機器判讀與標準化交換需求:

"system": "http://unitsofmeasure.org",
"code": "mg/dL"

Day 11 先做 LAB-UNIT-001,只確認:

當 value[x] 是 Quantity 時,unit 必須存在

Day 12 再做 LAB-UNIT-002,確認:

system = http://unitsofmeasure.org
code   in 合作契約允許的 UCUM 單位集合

這樣拆開的好處是,錯誤訊息會比較清楚。

缺少可讀單位時,使用者看到的是:

請補上合作方可讀的檢驗單位,例如 mg/dL。

UCUM system 或 code 不符合契約時,Day 12 再回另一個更精準的錯誤。

LAB-UNIT-001 要處理什麼?

LAB-UNIT-001 只看:

Observation.value[x]
Observation.valueQuantity.unit

規則判斷條件如下:

value[x] is Quantity
unit exists and is not blank

只要 Observation.value[x]Quantity,而且 valueQuantity.unit 有值,就回 PASS

如果 Observation.value[x]Quantity,但 unit 缺少或空白,就回 FAIL

如果 Bundle 沒有 Observation,或 Observation 的 value 不是 Quantity,就回 NOT_APPLICABLE

目標是展示「交換契約可以補上 Profile 驗證之外的合作方顯示需求」。

Day 11 的結果分類

LAB-UNIT-001 的分類順序如下:

Bundle 沒有 Observation → NOT_APPLICABLE
Observation 沒有 value[x] → NOT_APPLICABLE
Observation.value[x] 不是 Quantity → NOT_APPLICABLE
Observation.valueQuantity.unit 缺少或空白 → FAIL
Observation.valueQuantity.unit 有值 → PASS

這裡有三個容易混淆的地方。

第一,沒有 Observation 時不是 FAIL

因為這條規則的適用對象不存在,所以回:

NOT_APPLICABLE

第二,Observation.value[x] 不是 Quantity 時也不是 FAIL

例如:

"valueString": "positive"

這筆資料不一定錯。

它只是超出 LAB-UNIT-001 的檢查範圍。

所以回:

NOT_APPLICABLE

第三,Day 11 沒有檢查 systemcode

例如:

"valueQuantity": {
  "value": 95,
  "unit": "mg/dL"
}

LAB-UNIT-001 會先通過。

至於是否具有:

system = http://unitsofmeasure.org
code   = mg/dL

那是 LAB-UNIT-002 的責任。

測試資料

Day 11 新增或複用以下 fixture:

fixture 用途 LAB-UNIT-001 預期
valid-minimal-lab-bundle.json valueQuantity.unit = mg/dL PASS
observation-quantity-without-unit.json valueQuantity,但缺少 unit FAIL
observation-value-string.json value[x]valueString,不是 Quantity NOT_APPLICABLE
unsupported-resource-in-bundle.json Bundle 沒有 Observation NOT_APPLICABLE

這些 fixture 的目的不是追求案例數量。

它們的目的,是先把 LAB-UNIT-001 的最小狀態分類固定下來:

PASS
FAIL
NOT_APPLICABLE

後續加入 LAB-UNIT-002 時,才有穩定的 Quantity unit 行為可以比對。

實作

Day 11 新增:

src/main/java/com/twlab/qualitygate/validation
└─ LabUnit001ObservationQuantityUnitRule.java

規則仍然實作同一個介面:

public class LabUnit001ObservationQuantityUnitRule implements ContractRule {

  public static final String RULE_CODE = "LAB-UNIT-001";

  @Override
  public String ruleCode() {
    return RULE_CODE;
  }

  @Override
  public List<RuleResult> validate(Bundle bundle) {
    // ...
  }
}

代表 Day 7 建立的 ContractRule 介面仍然可以繼續使用。

第五條規則也不需要新的資料模型。

核心邏輯分成三步:

找出 Bundle 內所有 Observation
        ↓
確認 Observation.value[x] 是否為 Quantity
        ↓
確認 valueQuantity.unit 是否存在且不是空白

判斷邏輯如下:

private boolean hasReadableUnit(Quantity quantity) {
  return quantity.hasUnit() && !quantity.getUnit().isBlank();
}

實際顯示值則收斂成:

private String actualUnit(Quantity quantity) {
  return hasReadableUnit(quantity) ? quantity.getUnit() : "N/A";
}

這樣缺少 unit 時,RuleResult.actual 會是:

N/A

而不是空字串或 null

四種測試案例

Day 11 新增:

src/test/java/com/twlab/qualitygate/validation/LabUnit001ObservationQuantityUnitRuleTests.java

測試一:Quantity 有可讀 unit

assertThat(results.get(0).ruleCode()).isEqualTo("LAB-UNIT-001");
assertThat(results.get(0).outcome()).isEqualTo(RuleOutcome.PASS);
assertThat(results.get(0).path()).isEqualTo("Observation/obs-1.valueQuantity.unit");
assertThat(results.get(0).actual()).isEqualTo("mg/dL");

這代表最基本的 Quantity 檢驗結果可以通過。

測試二:Quantity 缺少 unit

assertThat(results.get(0).outcome()).isEqualTo(RuleOutcome.FAIL);
assertThat(results.get(0).severity()).isEqualTo("error");
assertThat(results.get(0).path()).isEqualTo("Observation/obs-quantity-no-unit.valueQuantity.unit");
assertThat(results.get(0).actual()).isEqualTo("N/A");

這代表有檢驗數值但缺少可讀單位時,會被交換契約擋下。

測試三:Observation.value[x] 不是 Quantity

assertThat(results.get(0).outcome()).isEqualTo(RuleOutcome.NOT_APPLICABLE);
assertThat(results.get(0).path()).isEqualTo("Observation/obs-value-string.value[x]");
assertThat(results.get(0).actual()).isEqualTo("string");

這代表非 Quantity 類型不會被這條規則誤判成錯誤。

測試四:Bundle 沒有 Observation

assertThat(results.get(0).outcome()).isEqualTo(RuleOutcome.NOT_APPLICABLE);
assertThat(results.get(0).severity()).isEqualTo("information");
assertThat(results.get(0).path()).isEqualTo("Bundle.entry");

這代表規則沒有適用對象時,不會誤判成資料錯誤。

這四段 assertion 就是 Day 11 測試的核心。

自動化驗證

指令:

./mvnw test

測試結果:

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

https://ithelp.ithome.com.tw/upload/images/20260812/20177913KouayJxqnM.png

Day 11 從 Day 10 的 40 個測試增加到 44 個測試。

新增的 4 個測試都集中在 LAB-UNIT-001

  • Quantity 有可讀 unit:PASS
  • Quantity 缺少 unit:FAIL
  • Observation.value[x] 不是 Quantity:NOT_APPLICABLE
  • Bundle 沒有 Observation:NOT_APPLICABLE

常見錯誤 & 排查

  1. LAB-UNIT-001 誤當成完整 UCUM validation

LAB-UNIT-001 不是完整 UCUM validation。

它沒有檢查:

system = http://unitsofmeasure.org
code 是否在允許集合

它只檢查:

valueQuantity.unit 是否存在

所以文章裡應該稱為「可讀單位存在性檢查」,不要稱為完整 UCUM 驗證。

  1. 把非 Quantity value 判成失敗

Observation.value[x] 有很多型別。

如果資料是:

"valueString": "positive"

它不是 LAB-UNIT-001 的適用對象。

這時應該回:

NOT_APPLICABLE

而不是 FAIL

  1. 只檢查 system/code,忘記 unit

Day 12 會處理 system/code

但 Day 11 的重點是可讀單位:

valueQuantity.unit

如果只看 system/code,就無法展示「機器標準化」與「人工可讀」是兩個不同層次。

  1. 缺少 unit 時回傳空字串或 null

規則結果裡的 actual 應該穩定顯示:

N/A

這樣測試、畫面與文章說明都比較一致。

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

前四條規則目前回答的是:

資料鏈是否接得起來?
檢驗項目是否屬於合作方允許集合?

Day 11 的 LAB-UNIT-001 回答的是:

檢驗數值是否具有合作方可直接閱讀的單位?

也就是說,就算資料長這樣:

DiagnosticReport.result → Observation/obs-1
Observation.subject     → Patient/patient-1
Observation.code        → http://loinc.org|2345-7

還是可能因為:

Observation.valueQuantity.unit = N/A

而被交換契約擋下。

這不是 JSON 格式錯誤。

也不一定是 TW Core Profile 錯誤。

它比較適合被歸類為:

Exchange Contract: FAILED

這也是資料品質閘門要補上的地方:

標準驗證告訴我們 Resource 結構是否合理。
交換契約規則告訴我們這份資料是否符合合作方接收與閱讀條件。

今天完成了什麼

  • 新增 LabUnit001ObservationQuantityUnitRule
  • 新增 LabUnit001ObservationQuantityUnitRuleTests
  • LAB-UNIT-001 對 Quantity 有 unitPASS
  • LAB-UNIT-001 對 Quantity 缺少 unitFAIL
  • LAB-UNIT-001 對非 Quantity value 回 NOT_APPLICABLE
  • LAB-UNIT-001 對沒有 Observation 的 Bundle 回 NOT_APPLICABLE
  • 新增 2 份 LAB-UNIT-001 專用 fixture。
  • 複用既有 valid-minimal-lab-bundle.jsonunsupported-resource-in-bundle.json
  • 新增 4 個單元測試。
  • ./mvnw test 通過,測試數從 40 增加到 44。

Day 11 尚未處理:

  • LAB-UNIT-002 UCUM system/code 契約允許集合。
  • 將交換契約規則接回 BundleParseService
  • 在首頁顯示 RuleResult
  • 契約 YAML/JSON 載入。
  • 契約啟用/停用規則。
  • Quality Gate 的 Passed / Warning / Blocked 判定。
  • 契約 v1.0/v1.1 比較。

目前的規則進度:

contract-rule
├─ LAB-REF-001  Observation.subject → Patient            完成
├─ LAB-REF-002  DiagnosticReport.result → Observation    完成
├─ LAB-REF-003  Report 與 Observation 對應同一 Patient    完成
├─ LAB-CODE-001 Observation.code 契約允許 LOINC           完成
├─ LAB-UNIT-001 Quantity 必須具有可讀 unit                 完成
└─ LAB-UNIT-002 UCUM system / code 契約允許集合             未開始

下一步預計進入 LAB-UNIT-002

原因是目前已經能證明:

Reference 鏈正確
Observation.code 包含合作方允許 LOINC
Observation.valueQuantity.unit 具有可讀單位

但還不能判斷 Quantity 類型的檢驗單位是否使用合作契約允許的 UCUM system 與 code。

完成 LAB-UNIT-002 後,六條核心交換規則就會先具備完整骨架。

Repository:twcore-data-quality-gate


上一篇
Day10 - 完成 Reference 共用整理與 LOINC 契約允許集合規則
下一篇
Day12 - 完成 UCUM system/code 契約允許集合規則
系列文
醫療資料通過標準驗證,就真的能交換嗎?——30 天打造 TW Core 資料品質閘門17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言