Slicing & Discriminator === FHIR資料中,Slicing一直是一個不容易理解的題目,最近在重構程式,順便整理一下相關概念。 主要參考:[Profiling FHIR](https://hl7.org/fhir/R4/profiling.html) Slicing --- 通常Slicing的使用情境為資料為清單形式,且清單內的資料包含不同的類型。以血壓量測為例: ![slicing-1](https://hackmd.io/_uploads/BJQ0TSDbbl.png) 血壓的LOINC碼是55284-4,血壓量測通常會包含收縮壓(8480-6)與舒張壓(8462-4),各有其量測值。如果以Slicing的方式表示將如上圖所示。 以TW Core 0.3.2為例,資料如下: ``` { "resourceType" : "Observation", ... "code" : { "coding" : [ { "system" : "http://loinc.org", "code" : "85354-9", "display" : "Blood pressure panel with all children optional" } ], "text" : "Blood pressure panel with all children optional" }, ... "component" : [ { "code" : { "coding" : [ { "system" : "http://loinc.org", "code" : "8480-6", "display" : "Systolic blood pressure" } ] }, "valueQuantity" : { "value" : 110, "unit" : "mmHg", "system" : "http://unitsofmeasure.org", "code" : "mm[Hg]" } }, { "code" : { "coding" : [ { "system" : "http://loinc.org", "code" : "8462-4", "display" : "Diastolic blood pressure" } ] }, "valueQuantity" : { "value" : 56, "unit" : "mmHg", "system" : "http://unitsofmeasure.org", "code" : "mm[Hg]" } } ] } ``` 如何區別slice欄位名稱?目前有看到兩種情形:FHIRPath與Slice Name。以上面資料說明,要明確標示Diastolic blood pressure可以使用以下方式: * FHIRPath:`component.where(code.coding.code=8462-4)` * Slice Name:component:Diastolic 這兩種方式都可以協助程式開發人員正確辨識相關欄位。基本上,各有各的用途,如果針對資料本身,自然是以FHIRPath較為直接,但若是直接解析StructureDefinition時,則是Slice Name較為方便。 舉個實務案例: 在TW NGS基因檢測資訊的範例資料:[Lung adenocarcinoma 基因檢測資訊](https://nhicore.nhi.gov.tw/ngs/Observation-obs-lung-min.html),如果我們想取得基因如下圖紅框資訊 ![slicing-2](https://hackmd.io/_uploads/SJE9JiF-bl.png) 我們可以使用以下程式碼取得相關訊息: ``` string examplePath = @"C:\Data\ig\ngs\package\example\Observation-obs-lung-min.json"; string exampleContent = File.ReadAllText(examplePath); FhirJsonParser parser = new FhirJsonParser(); Observation obs = parser.Parse<Observation>(exampleContent); List<Observation.ComponentComponent> components = obs.Select("component.where(code.coding.code='48018-6')").Cast<Observation.ComponentComponent>().ToList(); foreach (Observation.ComponentComponent c in components){ Console.WriteLine((c.Value as CodeableConcept)?.Coding?.FirstOrDefault()?.Display); } ``` 相關輸出如下: ``` BCORL1 FGFR2 U2AF1 EGFR TP53 EPHB4 FLT1 CREBBP CIC ``` 按理說,規定只有一個,換句話說,表現方法也應該只有一種。接下來,我們要證明這兩種表示方法事實上是相同的。這也是Discriminator的作用。 Discriminator --- Discriminator顧名思義就是拿來判別資料的依據。同樣以NGS為例,以下程式可取得所有Discriminator的內容。 ``` foreach (var profile in profiles){ string profileName = profile.Split("/").Last(); StructureDefinition sd = await resolver.ResolveByUriAsync("StructureDefinition/" + profileName) as StructureDefinition; List<ElementDefinition?> ed = sd.Differential.Element.Where(e => e.Slicing != null).ToList(); foreach (var e in ed){ Console.WriteLine($"{e.Path}:{e.SliceName} (Path: {e.Slicing?.Discriminator.FirstOrDefault()?.Path} , Type: {e.Slicing?.Discriminator.FirstOrDefault()?.Type} ) | {e.Slicing?.Rules}"); } } ``` 程式執行結果如下: ``` Composition.section.entry: (Path: $this.resolve() , Type: Profile ) | Open Condition.code.coding: (Path: system , Type: Value ) | Open DiagnosticReport.extension: (Path: url , Type: Value ) | Open DiagnosticReport.effective[x]: (Path: $this , Type: Type ) | Open Observation.component: (Path: code , Type: Value ) | Open ``` 每一個Discriminator包含兩個property:type和path。其中type表示discriminator如何使用,共有五種type: | 種類 | 說明 | | -------- | -------- | |value|The slices have different values in the nominated element.(slice在指定元素有不同的值)| |exists|The slices are differentiated by the presence or absence of the nominated element.(slice是根據指定元素是否存在來區分。)| |pattern|The slices have different values in the nominated element, as determined by testing them against the applicable ElementDefinition.pattern[x].(slice在指定元素中具有不同的值,這是透過與適用的 ElementDefinition.pattern[x] 測試來決定的。)| |type|The slices are differentiated by type of the nominated element.(slice根據指定元素類型區分。)| |profile|The slices are differentiated by conformance of the nominated element to a specified profile. Note that if the path specifies .resolve() then the profile is the target profile on the reference. In this case, validation by the possible profiles is required to differentiate the slices.(slice依據指定元素是否符合特定配置來區分。請注意,如果路徑指定為 .resolve()則該設定檔即為參考的目標設定檔。此時需透過驗證區分slice。)| 坦白說,這些說明文字(無論中英文)都不是那麼平易近人。我們還是以實際的例子來說明。 以前面基因檢測資料的範例,從上面結果得知其(type,path)=(value,code)也就是說,指定的element是component.code。從IG的規範(如下圖)可知component.code的資料格式是CodeableConcept,也就是說它必須包含coding.system、coding.code與coding.Display等三個元件。其中coding.code的值為Fixed value:48018-6。如果使用FHIRPath就可以表示為`component.where(code.coding.code='48018-6')`,另外,它的slice Name則為gene-list。也就是說,在IG規範中,清楚表示FHIRPath與slice Name是同一件事。 ![slicing-3](https://hackmd.io/_uploads/r1a4cecbWe.png) 以下舉幾個在實務上會遇到的真實案例: | IG | Profile | Element | Slicing Path|Slicing Type| Slicing Rule| | -------- | -------- | -----|--- |-----|---| | pas | Patient-twpas| identifier| type.coding.code|value|closed| | pas | Claim-twpas| supportingInfo| category|value|closed| | pas | Claim-twpas| extension| url|value|open| # FHIR Slicing:ordered 與 unordered 的差異 本文件根據 FHIR 官方規範(Profiling - FHIR v6.0.0-ballot3)整理 slicing 中 **ordered = true** 與 **ordered = false** 的語意差異與實務影響。 --- ## 🔹 ordered = true(有序 slicing) 表示切片之間的順序具有語意意義: - 切片順序本身是規範的一部分 - Instance 中的元素順序必須與 slice 定義順序一致 - Validator 會依照 slice 順序比對 - 適用於順序本身具有語意的資料 - 例如:Practitioner.name 要求第一個 name 是 usual name(FHIR 官方示例) --- ## 🔹 ordered = false(無序 slicing) 表示切片之間的順序沒有語意意義: - Instance 中的元素順序不影響驗證 - Validator 只依 discriminator 判斷每個元素屬於哪個 slice - 為 FHIR slicing 預設行為 - 適用於大部分 list 型元素 - 例如:Observation.component(血壓 systolic/diastolic 順序不重要) --- ## 🔸 差異比較表 | 特性 | ordered = true | ordered = false | |------|----------------|-----------------| | 切片順序是否有語意 | ✔️ 是 | ❌ 否 | | Instance 是否需依 slice 順序 | ✔️ 是 | ❌ 否 | | Validator 判斷方式 | 順序 + discriminator | 只依 discriminator | | 常見用途 | 結構化文件、固定順序資料 | 大部分 FHIR list | | FHIR 預設 | 否 | ✔️ 是 | --- ## 🧠 一句話總結 **ordered = true:順序是規範的一部分。 ordered = false:順序不重要,由 discriminator 決定切片。** # FHIR Slicing:Open 與 Closed 的差異 以下內容根據 FHIR Profiling 規範整理,說明 slicing 中 **open** 與 **closed** 的語意差異、驗證行為與實務使用情境。 --- ## 🔹 Open Slicing(開放式切片) 表示切片集合是「開放的」,允許未定義的額外項目。 ### ✦ 行為特性 - Instance 中 **可以出現未被定義的 slice** - Validator 會先嘗試依 discriminator 判斷是否屬於已定義的 slice - 若不屬於任何 slice,也 **不會視為錯誤** - 適用於資料來源可能提供額外欄位或彈性內容的情境 ### ✦ 常見用途 - Observation.component(可能出現額外 component) - Extension slicing(允許額外 extension) - Identifier、Address 等彈性高的元素 --- ## 🔹 Closed Slicing(封閉式切片) 表示切片集合是「封閉的」,不允許額外項目。 ### ✦ 行為特性 - Instance 中 **不得出現未定義的 slice** - 若出現未被定義的 slice → **驗證錯誤** - Validator 僅接受 slicing 定義中列出的 slice - 適用於資料結構必須嚴格控制的情境 ### ✦ 常見用途 - 嚴格規範的醫療文件格式 - 固定欄位的資料模型 - 不允許系統自行擴充的元素 --- ## 🔸 差異比較表 | 特性 | Open Slicing | Closed Slicing | |------|--------------|----------------| | 是否允許未定義 slice | ✔️ 是 | ❌ 否 | | Instance 出現額外項目 | ✔️ 可接受 | ❌ 視為錯誤 | | Validator 行為 | 未匹配 slice 也不報錯 | 未匹配 slice → 錯誤 | | 適用情境 | 彈性資料、可擴充欄位 | 嚴格結構、固定欄位 | | FHIR 常用預設 | ✔️ 多為 open | ❌ 較少使用 | --- ## 🧠 一句話總結 **Open slicing:允許額外項目,彈性高。** **Closed slicing:不允許額外項目,結構嚴格。** ---