Patient這個Resource是FHIR應用中最常碰到的(另一個是Encounter)。他的結構夠複雜,可以涵蓋許多面向。
電子病歷實作技術
2024年11月10日 星期日
FHIR SDK(DotNet)- Resource Type - Complex (Patient)
FHIR SDK(DotNet)- Resource Type - Simple (Basic)
Basic這個Resouce是神奇的存在著。他沒什麼用途,但每次開發系統,一定會先拿他來測試。這裡當然不例外,可惜他真的太單純,無法涵蓋所有面向。
透露一下,這裡面的程式碼,都是用另一個專案產生的。這又是另一個技術門檻。
2024年11月9日 星期六
FHIR SDK(DotNet)- FHIR Type Framework - Resource
Resource系列雖然可視為Complex Type,但仍須獨立視之,否則到處都是地雷。在其他的Resource,例如Bundle,其元素entry/resource的資料型態就是Resource。但Resource是抽象型態(Abstract Class),在C#中是不能產生實例。這塊就是得想辦法克服之處。
Resource直接繼承了Base,其所含的元素,乍看之下沒有什麼特殊性。比較麻煩的是meta,他的資料型態是Meta。Meta繼承DataType,所以,他沒有Extension機制。那Extension機制呢?哈~怎麼可能遺漏呢?原來是放在了DomainResource,而且一次給妳兩個,extension與modifierExtension。(這兩者差異不在本系列範疇)。contained的資料型態是Resource,是他的父類別。這個問題不大,這個類別沒有特別要處理的。
至於其子類別CanonicalResource與在下一個子類別MetadataResource,這兩個定義是介面,表示某些類別需要實做這兩個。例如ActiveDefinition在其結構描述中就有指定要實做MetadataResource。在這兩個介面描述就會說明實做資源相對應條件要求。這塊SDK並沒有實做。
===============
Resource類別的設計與Complex Type類似,只是OnPropertyChange與SetupPropertyValue需要另外處理。(因為Resource直接繼承了base,而這些功能不能寫在Base,因為Datatype那邊有個Element,處理方式略有不同。)
DomainResource更簡單,就是單純的ComplexType。
之前也說過,Resource 是抽象類別,無法直接產生實例。所以,透過可實做類別來實現之。多一個ResourceType類別,硬是把所有Resource都變成Complex Type一樣的操作方式。
2024年11月2日 星期六
FHIR SDK(DotNet)- FHIR Type Framework - BackboneType, BackboneElement
在Base Type的Element這邊系列中,長了兩個很奇怪的類別BackboneElement繼承了Element,而BackboneType繼承了DataType,兩個類別的欄位內容卻一模一樣。這到底是怎麼一回事呢?
原來BackboneType其實就是為了幾個Complex Type擁有modifierExtension的機制。有哪幾個呢?依據文件,目前(是的,只能說目前,未來還是有可能增加)Timing、Dosage與ElementDefinition這三個DataType。注意了,我拿Address與Timing來比對。
文件中,兩者都是繼承了Element。所有的Complex Type都是寫繼承Element,沒有DataType。僅能從描述中得知Timing繼承了一個modifierExtension的屬性。這個程式碼單純,就是ComplexType的一種。就不多做解釋了。
原來他也是一種DataType,但是,他僅是抽象類別,實際的內容還是要看此Elmenet之下的宣告。有感應到了嗎?原則上所有的資料型態都會有extension,但是這類BackboneElement的類別,還會多了modifierExtension。(與BackboneType給的邏輯不太相同)。
而分散在Resource裡的BackboneElement,其結構每一個都不一樣。所以,得為每一個不同的BackboneElement設計共用父類別,而這個父類別必須走Reflect架構,這樣子才能夠因應不同的BackboneElement。這個類別的程式非常複雜,也不好解釋。有緣再說了。
2024年10月27日 星期日
FHIR SDK(DotNet)- FHIR Type Framework - Choice Type
Choice Type是個美麗又邪惡的小女孩,她的彈性是種讓你無法承受的愛。想在密密麻麻的文件中尋得他的蹤跡,是件苦差事。當你揭開她的神秘面紗,你一定會深深的愛上他。
上一篇已經提到Choice Type 是該面對他的時候。
想要深入瞭解他,得從Extension這個資料型態的文件去瞭解他。
這個value[x] ,尤其是Type欄位的*,她帶著什麼樣的神秘事件呢?
他的意思是說,Extension有欄位叫value,他可以任意一種資料型態。能有多任意呢?以下的都可以。
- valueBase64Binary: base64Binary
- valueBoolean: boolean
- valueCanonical: canonical
- valueCode: code (only if the extension definition provides a fixed binding to a suitable set of codes)
- valueDate: date
- valueDateTime: dateTime
- valueDecimal: decimal
- valueId: id
- valueInstant: instant
- valueInteger: integer
- valueInteger64: integer64
- valueMarkdown: markdown
- valueOid: oid
- valuePositiveInt: positiveInt
- valueString: string
- valueTime: time
- valueUnsignedInt: unsignedInt
- valueUri: uri
- valueUrl: url
- valueUuid: uuid
- valueAddress: Address
- valueAge: Age
- valueAnnotation: Annotation
- valueAttachment: Attachment
- valueCodeableConcept: CodeableConcept
- valueCodeableReference: CodeableReference
- valueCoding: Coding
- valueContactPoint: ContactPoint
- valueCount: Count
- valueDistance: Distance
- valueDuration: Duration
- valueHumanName: HumanName
- valueIdentifier: Identifier
- valueMoney: Money
- valuePeriod: Period
- valueQuantity: Quantity
- valueRange: Range
- valueRatio: Ratio
- valueRatioRange: RatioRange
- valueReference: Reference - a reference to another resource
- valueSampledData: SampledData
- valueSignature: Signature
- valueTiming: Timing
- valueContactDetail: ContactDetail
- valueDataRequirement: DataRequirement
- valueExpression: Expression
- valueParameterDefinition: ParameterDefinition
- valueRelatedArtifact: RelatedArtifact
- valueTriggerDefinition: TriggerDefinition
- valueUsageContext: UsageContext
- valueAvailability: Availability
- valueExtendedContactDetail: ExtendedContactDetail
- valueDosage: Dosage
- valueMeta: Meta
有注意到了嗎?在JSON中,他的Tag Name(key) 是(欄位名稱+資料型態名稱)而資料型態名稱還得首字大寫。
其他資料型態也有此Choice Type的有Timing的repeat(這是個詭異結構)與Annotation。
程式解法很單純,提供一個ChoiceType當父類別,來處理一切雜事。提供各種情境的建構元。
接著就是複寫來自DataType的函數與實做介面。
這時候,74: IsChoiceType()就得設成true了。
其他最重要的就是88:與89:這兩行。89:宣告一個抽象函數,讓子類別來告訴父類別,目前這個Choice Type限定哪些資料型態。
88:然後有了GetSupportDataType()這個函數讓建構元來使用,確保抓到屬於這個Choice Type該有的Tag。
這就是子類別的樣子,其實差異只在於SupportType而已。
FHIR SDK(DotNet)- FHIR Type Framework - Complex Type - 3
也許應該要感謝DotNet平台提供了Reflection機制,讓程式結構可以做到簡化,但要能發揮他的功效,需要抽象概念轉換,吃了不少苦頭。
=====SetupPropertyValue=====
56-143:就是分別對這個欄位進行資料轉換作業。因為欄位的性質不同,處理的方式也就要不同。
58-60:先把來源資料與欄位關係確立好。
63:順便把可能是Primitie Element那塊的json資料Key準備好。
64-103:如果這個欄位定義為[0..*]時;在類別宣告上,當為多時都用List<T>。
66:得先知道這個多的欄位他的資訊型態是什麼?
68-84:如果是Choice Type,那就走Choice Type流程。
85-102:如果是Complex或Primitive的話,就走這邊。奇怪,這邊怎們不分開處理?因為是多,逐一執行時,還是會回到單一一筆的流程,那邊就會分了。
106-141:如果欄位宣告是[0..1]時。就依據資料型態個別處理吧。
=====SetupJsonObject=====
就根據欄位產生JsonObject,其中SetupPropertyValue函數也是用到Reflection,這邊就不贅述了。
FHIR SDK(DotNet)- FHIR Type Framework - Complex Type - 2
雖然Complex Type有很多,但他們的行為模式都是一樣,設計一個父類別來處理。等等,不是有最討厭的Element嗎?這邊可以放心,因為Complex對應回JSON就是一個物件結構,id與extension就如同其他欄位一樣的自然存在。
宣告ComplexType<T>,用到泛型,T就是哪些有宣告欄位的Complex Type們。建構元也很簡單,就是把傳進來的值進行解析,然後去更新所有的欄位內容值。在此是呼叫SetupPropertyValue()這個函數。(有點複雜,容後說明)
接著複寫來自DataType的函數。應該可以理解,目前IsCompelx()就得設成true了。另外,取得JsonString的部份,用了C# Extension Method機制,將JsonNode,多了產生JSON String 的方法。(為何多此一舉?唉~那就要問微軟了,他的DotNet的問題)
再來實做來自IComplexType的介面要求,而且是非常重要的函數服務GetJsonObject()。因為SDK其實是用Key-Value來存放JSON內容。Value的部份實際上是用JsonNode。身為Complex Type他一定是在Value這端,所以,這個函數很重要。把Key-Value轉成JsonNode出來。
因為Complex的欄位,也有可能是JsonValue、JsonObject或JsonArray,還好他們有共同父類別是JsonNode。只是要注意,這套程式庫,會咬死Json Path,不能輕易把一段JSON掛到某一個Element上,所以要用DeepClone()這個方法,來取得乾淨的JsonNode結構。
另外,還有一種情境,這個Complex是用new() {}然後設值的方式建構的,也就是說沒有經過建構元,那就不會建立啟始的_Properties內容,此時,就得透過SetupJsonObject()方式,來取得各欄位內容值,再轉換成JsonNode。
==========
SetupPropertyValue()是支援建構元,將JsonNode資料轉換成Complex 物件。
SetupJsonObject()是將Complex物件的內容轉換成JsonNode。
這兩個東西是整個SDK第二困難與複雜的點。就留待下篇來說明。