接著就是分別呼叫對應的CardService。
2024年12月21日 星期六
CDS Hooks - 程式設計 - CDS Service端
接著就是分別呼叫對應的CardService。
2024年12月20日 星期五
CDS Hooks - 程式設計 - 實做抉擇
討論到現在,前端服務架構已逐漸成形。 現在的問題是要收來自EHR端的POST資料,更重要是FHIR Resource與FHIR Server的相關資訊。然後翠取出CDS Serevice所需的資料內容,送給他處理,並且等待結果轉化成Card類別,回覆給EHR端。
現在要面臨深思的事,這個平台到底是要處理一般化,還是要特殊化。
特殊化很單純,目前這個專案就是處理兩個CDS,BMI與Hear Rate。而且他們要的資料都很明確就是要Observation然後各自對應LOINC碼,把Value值抓出來丟給CDS。兩隻CDS收到資料後,開始進行邏輯處理(這個對資訊人員來說,就是最困難的部份)。處理好後,再把結果組合成Card後,送回給EHR。這個叫暴力實做法。
而一般化,就是說這個平台只要匯入MLM的話,就自動產生的一個CDS 服務,這是我的理想。而這個目標也是可行的。Arden Sytax已經開始支援FHIR,只是應用上還不很明確。當資料源(Data:)確定資料,那邏輯(Logic:)的部份就是Parser程式的問題。再明確一點,就是有一個MLM的編輯器,把文件寫好後,就可以產生CDS Hook要的設定檔。
可惜,這些年來我的工作一直跟FHIR無緣,只能利用下班時間研究,能量還是有限。再者也沒什麼深厚背景,可以搞新創,搞基金。總之一切隨緣。
==========
雖然說是暴力實做法,但是,跟FHIR Server互動這塊,還是會提供程式碼參考。雖然,目前挑的這兩個案例,只會用到Observation,但實務上,不會這麼簡單。(哈~要找到漂亮複雜的CDS,那就超出我能力範圍)。只是Access Token的部份,我還是會保留。畢竟我也不是專搞慈善事業的。
2024年12月19日 星期四
CDS Hooks - 程式開發 - Call Service
當EHR叫用cds-services得知目前CDS Server支援了哪些CDS項目。接著就依據Id名稱,正式叫用服務。但,這時候已經把兩邊搞的天翻地覆了。
EHR要依據CDS Hooks端的要求,把所有資料準備好,接著用POST方式叫用服務。
CDS Hooks端收到資料後,就得開始找出真正提供CDS服務者。這個過程有點複雜,因為使用的Hooks不同,FHIR Resource會出現在不同的地方。另外,會有Reference Datatype的問題,不過這個可以在一開始Prefetch時,把查詢的URL先搞定。
這篇只說明呼叫時的運作,還沒有到CDS Service。
程式
這邊使用到一個CardService,他是用來與CDS溝通,並產生Card類別提供給EHR端。目前先用他的一個產生空白Card的函數。
測試
CDS Hooks - 程式開發 - Discovery
整個遊戲的一開端,就是EHR端要先知道CDS Hooks端提供了哪些CDS服務。這個階段稱之為Discovery。不過,有些平台例如EPIC,他就會省略這一段。
程式
根據CDS Hooks的規範書可知,CDS Hooks端要提供一個endpoint 叫cds-services。
(這不是C# DotNet教學,細節就不多提)
這個Endpoint內容很直覺。就設計一個Hooks類別提供所有Hooks清單。
===== Hooks =====
Hooks類別,主要目的是去讀取HookSettings.json檔案。這個檔案就是描述著會有哪些CDS Hooks服務。
讀進來後,就可以提供GetHooksList函數了。
其中ServiceModel就是依照CDS Hooks規範之要求所設計的。
===== ServicesModel, HookItem =====
這兩個類別應該不需要多做解釋。那個UsageRequirements抱歉,我也還沒搞清楚實際用意為何。
===== HookSettings.json =====
回到這個設定檔。
透露一下,這個設定檔是產生自另一個介面,其內容都是來自MLM(當然,您不需要如此)。唯一要注意的是Prefetch這段。這是要求EHR端,要使用這個CDS服務時,需要準備什麼樣的FHIR Resource。這塊自動化的程度,仍有待思考。
==========
測試
2024年12月15日 星期日
CDS Hooks - 程式開發 - 簡易說明
開發CDS Hooks,其實不難,難的是你要提供什麼服務。
在此架構中,EHR是需求端,CDS Hooks是服務(回應)端。之間只有資料層面的交換,並沒有UI(網頁)內容呈現。(若有,那請走Smart on FHIR架構)
本次系統開發採用C# DotNet 9,用Web API專案範本。(若Smart on FHIR就需MVC或Blazor)
依據CDS Hooks規範,需要提供下列服務(Endpoint)
- Discovery:讓EHR端知道CDS端提供了哪些CDS項目。 CDS端要告知每一項服務會走什麼Hook(這個會決定Context內容),其id編碼是什麼(如Patient Id, Encounter Id)。然後可以順便告知,要使用這項服務的話,需先準備哪些FHIR Resource。上述內容CDS端會回傳services這個json檔。
- CDS Service:有了上述服務清單,EHR端的使用者就要決定採用哪一項服務。EHR決定要呼叫那個服務時,就得先準備好幾項東西,例如Fhir Server在哪(若還要再撈其他資料,例如遇到Reference型態),fhirAuthorization相關資訊(若需要的話),還有Conttext所要求的Id內容,另外,當初在Discovery時,CDS端告知需要事先準備的FHIR Resource。CDS端收到資料後,經過處理,然後回傳card這個json檔。EHR端要負責解析呈現這個card json檔內容。
- Feeback::上一階段回傳的card內容,其實包含了很多按鈕宣告。而CDS端則需要接收EHR使用者到底按了那個按鈕的訊息,然後進行下一個步驟。例如刪除Resource或者新增Resource。
- Prefetch的宣告:重點就是如何宣告一個Resource的查詢URL,而能夠效率最佳化。(不用一來一往,重複存取)
- Hooks的選用:目前這塊也不是很穩定。向CDS Hooks Sandbox他也只支援patient-view與order-select。這塊會影響到Perfetch能產生多複雜的查詢語法。
- Authorization設計:CDS Hooks與Smart on FHIR架構差距此為重要一點。CDS Hooks是請EHR端準備好Access Token相關資料,請一併送過來。而Smart on FHIR則是需要一段一段交握中取回。
- Card的設計:這個是最困難的部份,因為要配合CDS端的設計,畢竟Card能夠處理的是有限的。CDS設計了太複雜的回應結果,是很難用Card來完整呈現。再來,Card是有EHR負責Render,所以,呈現的結果可能不會是CDS端所想要的。
- Feeback設計:EHR端的回饋,其實是由CDS端決定的。也就是把按鈕設計在Card中。問題就是,能設計到多複雜,要細分到什麼程度?後續的處理模式刪除或新增,這個還得問問FHIR Server是否有開放支援。
CDS HOOKS - 選擇測試平台(CDS Hooks Sandbox)
2024年12月14日 星期六
CDS HOOKS - 確認測試資料源
這個步驟在實際上線時,可有可無。若個案沒有所需資料,當然此項CDS HOOKS就不成立。但為了說明實做,總不能最後結果都是個案無資料。
目前各家可公開測試的FHIR Server都有些差異性,而且,查詢特定資料的方法也可以多樣性。所以,這篇的目的就是想先確認,哪一個FHIR Server可以提供穩定資料來源方便測試。
使用工具就是之前開發的FHIR Query Builder工具。有興趣可以參考這篇。
預計檢視的FHIR Server有:(為降低實做複雜度,僅採用Open)
- Firely Server: https://server.fire.ly/
- HAPI FHIR Reference Server: http://hapi.fhir.org/baseR5
- Epic's Sandbox: https://open.epic.com/Interface/FHIR
- Cerner's Sandbox: https://fhir-open.cerner.com/r4/ec2458f2-1e24-41c8-b71b-0e701af7583d
- BMI Calculation
- bmi: numeric (input)
- weight: numeric (option)
- height: numeric (option)
- bmi_category: text (output)
- Blood Glucose Monitoring
- blood_glucose: numeric (input)
- recommendation: text (output)
- Heart Rate Monitoring
- heart_rate: numeric (input)
- recommendation: text (output)
- BMI Calculation: Resource Profile: US Core BMI Profile
- Blood Glucose Monitoring: Resource Profile: Observation - Laboratory Blood Glucose (保留)
- Heart Rate Monitoring: Resource Profile: US Core Heart Rate Profile
- code/Coding/system = http://loinc.org
- code/Coding/code = 39156-5
- code/Coding/system = http://loinc.org
- code/Coding/code = 8867-4
2024年12月12日 星期四
CDS HOOKS - 思維再論
其實講定說要使用CDS HOOKS時,資料來源就已經固定是FHIR了。
但就操作流程來看,使用者(醫師)一定正在使用某個系統,而這個系統已經有個案的基本資料。就看你要怎麼稱呼這個「系統」,你就當他是EHR吧。接著的問題是,這個EHR的資料從哪裡來?
- 從這個EHR所依附的資料庫?
- 從FHIR來?
- 從CDA R2來。
2024年12月11日 星期三
CDS HOOKS - 資料來源: 從Arden Syntax (MLM) 看FHIR與CDA R2
Arden Syntax簡單的說就是把醫師的臨床決策思維,用結構化、電子化方式表達出來。既然可行,此決策所需的臨床資料當然是數位化。
若是直接從資料庫的話,那也就沒什麼好討論的。此篇是想用一個MLM來看看,要怎麼從FHIR或CDA R2中取得資料。或許我想表達的是臨床資料是時間性的。
===== MLM =====
title: "Heart Rate Rhythm Monitoring"mlmname: "HeartRateRhythmMonitoring"
version: "1.0"
institution: "Example Hospital"
author: "Dr. Jane Smith"
specialist: "Cardiology"
date: "2024-12-10"
validation: testing
purpose: "To monitor the heart rate rhythm of a patient and provide recommendations"
explanation: "This MLM checks the patient's heart rate and rhythm, and provides recommendations based on the results."
knowledge:
type: data-driven
data:
heart_rate: numeric
heart_rhythm: text
recommendation: text
logic:
evoke:
if exists(heart_rate) and exists(heart_rhythm)
data:
heart_rate := getField("heart_rate")
heart_rhythm := getField("heart_rhythm")
action:
if heart_rate < 60 then
recommendation := "Bradycardia detected. Consider further evaluation and possible intervention."
else if heart_rate >= 60 and heart_rate <= 100 then
recommendation := "Heart rate is within the normal range. Continue regular monitoring."
else if heart_rate > 100 then
recommendation := "Tachycardia detected. Consider further evaluation and possible intervention."
if heart_rhythm == "irregular" then
recommendation := recommendation + " Irregular heart rhythm detected. Consider further evaluation and possible intervention."
message:
"The patient's heart rate is {heart_rate} and the rhythm is {heart_rhythm}. Recommendation: {recommendation}."
<templateId root="2.16.840.1.113883.10.20.22.2.4.1"/>
<templateId root="2.16.840.1.113883.10.20.22.2.4.1" extension="2015-08-01"/>
<code code="8716-3" codeSystem="2.16.840.1.113883.6.1" codeSystemName="LOINC"
displayName="Vital signs"/>
<title>Vital Signs (Last Filed)</title>
<text>
<table>
<thead>
<tr>
<th>Date</th>
<th>Pulse</th>
<th>Heart Rhythm</th>
<!-- other vitals signs could exist here-->
</tr>
</thead>
<tbody>
<tr>
<td>05/20/2014 7:36pm</td>
<td ID="Pulse_1">80 /min</td>
<td ID="Pulsequalifier_1">Heart irregularly irregular</td>
<!-- other vitals sign values could exist here-->
</tr>
</tbody>
</table>
</text>
<entry typeCode="DRIV">
<!-- When a set of vital signs are recorded together, include them in single clustered organizer-->
<organizer classCode="CLUSTER" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.22.4.26"/>
<templateId root="2.16.840.1.113883.10.20.22.4.26" extension="2015-08-01"/>
<id extension="21688133041015158234"
root="2.16.840.1.113883.3.42.126.100001.19"/>
<code code="46680005" displayName="Vital Signs" codeSystem="2.16.840.1.113883.6.96"
codeSystemName="SNOMED CT">
<translation code="74728-7"
displayName="Vital signs, weight, height, head circumference, oximetry, BMI, and BSA panel "
codeSystem="2.16.840.1.113883.6.1" codeSystemName="LOINC"/>
</code>
<statusCode code="completed"/>
<effectiveTime value="20140520193605-0600"/>
<!-- Each vital sign should be its own component. -->
<component>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.22.4.27"/>
<templateId root="2.16.840.1.113883.10.20.22.4.27" extension="2014-06-09"/>
<id extension="216881330410151584"
root="2.16.840.1.113883.3.42.126.100001.19"/>
<code code="8867-4" codeSystem="2.16.840.1.113883.6.1" codeSystemName="LOINC"
displayName="Heart rate"/>
<text>
<reference value="#Pulse_1"/>
</text>
<statusCode code="completed"/>
<effectiveTime value="20140520193605-0600"/>
<value xsi:type="PQ" value="80" unit="/min"/>
</observation>
</component>
<component>
<observation classCode="OBS" moodCode="EVN">
<templateId root="2.16.840.1.113883.10.20.22.4.2"/>
<templateId root="2.16.840.1.113883.10.20.22.4.2" extension="2015-08-01"/>
<id extension="216881330410151"
root="2.16.840.1.113883.3.42.126.100001.19"/>
<code xsi:type="CE" codeSystem="2.16.840.1.113883.6.1"
codeSystemName="LOINC"
code="8884-9" displayName="Heart rate rhythm"/>
<text>
<reference value="#Pulsequalifier_1"/>
</text>
<statusCode code="completed" />
<effectiveTime value="20140520193605-0600" />
<value xsi:type="CE" codeSystem="2.16.840.1.113883.6.96"
codeSystemName="SNOMED CT" code="248651005"
displayName="Heart irregularly irregular (finding)"/>
</observation>
</component>
</organizer>
</entry>
</section>
這時候情境就要確認:
(1)醫師當時是要處理什麼事情。
有可能就是正要寫Progress Note。然後有收集了一些Vital Signs (也許是從FHIR來),然後一股腦地都丟給MLM(當然不是本篇所示,可以更複雜),然後得回一堆的建議,之後複製到病歷中,產生一份Progress Note。從Progress Note的建議內容來說,是可以有Vital Signs區段。這時候,所有的建議是匯集在同一份臨床文件上。
不管用誰,都得搞定實做。CDS HOOK要怎麼基於MLM來實做呢?這是接著要克服的東西。