# 一起 on FHIR!
## 前言:為什麼要介接FHIR?
* ~~公司走向國際~~,可以拿來宣傳
* 衛福部推動[電子病歷](https://emr.mohw.gov.tw/myemr/fhir.html)
* 未來與其他FHIR系統介接時,更為方便
## FHIR的文件精選
> [官方文件](http://hl7.org/fhir/R4B/index.html)
* 文件怎麼讀Owen講過了,沒看過的去看:[用FHIR開發App:基礎與實作介紹
](https://dev-internal.jubo.health/docs/blog/FHIR/)
以下會省略Owen之前講過的部分,進到若是要介接FHIR時的注意事項~~血淚史~~
在介接之前,還是建議閱讀以下✨✨✨精挑細選✨✨✨出來的的FHIR官方文章
~~如果你認為你工作上完全不會碰到,或是不想睡著的話,那可以先左轉離開ㄌ~~
### ✨✨✨精挑細選✨✨✨:了解FHIR API的設計
* [HTTP](http://hl7.org/fhir/R4B/http.html): 非常詳細的講解了 FHIR Restful API的設計
* [Search](http://hl7.org/fhir/R4B/search.html):列出所有可以拿來 search FHIR 資源的 parameters
## 一步一步 toward to FHIR
以下會用實際範例告訴你怎麼去從頭開始介接FHIR

以設計稿上的實際案例來看,如上圖,介接SpO2(%)/Flow(L/min) 血氧含量、供氧。
要先判斷這要放到什麼樣的FHIR Resource,到官方看了一下眼花撩亂的首頁

看起來似乎是 **Diagnostics** 的類別下比較適合,再一個個點進去看,首先看 [**Observation**](http://hl7.org/fhir/R4B/observation.html)

> Measurements and simple assertions made about a patient, device or other subject
很幸運一次就找到似乎可以用的Resource了

往下滑,看到下面的Scope and Usage的舉例更加確定他可以使用在Vital signs等量測項目上
但好像沒看到血氧欸,請教Google大神,找到[美國版的IG](https://build.fhir.org/ig/HL7/US-Core/Observation-satO2-fiO2.json.html)也是把他塞到**Observation**裡面,那似乎是可以隨便塞了

接下來很簡單,照抄就行了...嗎?
以下是美國IG的列的範例的全貌
```JSON
{
"resourceType" : "Observation",
"id" : "satO2-fiO2",
"meta" : {
"extension" : [
{
"url" : "http://hl7.org/fhir/StructureDefinition/instance-name",
"valueString" : "Observation SatO2 FiO2 Example"
},
{
"url" : "http://hl7.org/fhir/StructureDefinition/instance-description",
"valueMarkdown" : "This is a observation satO2 fiO2 example for the *US Core Pulse Oximetry Profile* representing a spO2 value with a for a patient on 6 l/min of O2 suppplemental oxygen."
}
],
"profile" : [
"http://hl7.org/fhir/us/core/StructureDefinition/us-core-pulse-oximetry"
]
},
"text" : {
"status" : "generated",
"div" : "<div xmlns=\"http://www.w3.org/1999/xhtml\"><p><b>Generated Narrative: Observation</b><a name=\"satO2-fiO2\"> </a></p><div style=\"display: inline-block; background-color: #d9e0e7; padding: 6px; margin: 4px; border: 1px solid #8da1b4; border-radius: 5px; line-height: 60%\"><p style=\"margin-bottom: 0px\">Resource Observation "satO2-fiO2" </p><p style=\"margin-bottom: 0px\">Profile: <a href=\"StructureDefinition-us-core-pulse-oximetry.html\">US Core Pulse Oximetry Profile</a></p></div><p><b>identifier</b>: id:\u00a0o1223435-10</p><p><b>status</b>: final</p><p><b>category</b>: Vital Signs <span style=\"background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki\"> (<a href=\"http://terminology.hl7.org/5.0.0/CodeSystem-observation-category.html\">Observation Category Codes</a>#vital-signs)</span></p><p><b>code</b>: Oxygen saturation in Arterial blood <span style=\"background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki\"> (<a href=\"https://loinc.org/\">LOINC</a>#2708-6; <a href=\"https://loinc.org/\">LOINC</a>#59408-5 "Oxygen saturation in Arterial blood by Pulse oximetry"; <a href=\"http://terminology.hl7.org/5.0.0/CodeSystem-v3-mdc.html\">ISO 11073-10101 Health informatics - Point-of-care</a>#150456 "MDC_PULS_OXIM_SAT_O2")</span></p><p><b>subject</b>: <a href=\"Patient-example.html\">Patient/example</a> " SHAW"</p><p><b>effective</b>: 2014-12-05 09:30:10+0100</p><p><b>value</b>: 95 %<span style=\"background: LightGoldenRodYellow\"> (Details: UCUM code % = '%')</span></p><p><b>interpretation</b>: Normal (applies to non-numeric results) <span style=\"background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki\"> (<a href=\"http://terminology.hl7.org/5.0.0/CodeSystem-v3-ObservationInterpretation.html\">ObservationInterpretation</a>#N "Normal")</span></p><p><b>device</b>: <span>: Acme Pulse Oximeter 2000</span></p><h3>ReferenceRanges</h3><table class=\"grid\"><tr><td style=\"display: none\">-</td><td><b>Low</b></td><td><b>High</b></td></tr><tr><td style=\"display: none\">*</td><td>90 %<span style=\"background: LightGoldenRodYellow\"> (Details: UCUM code % = '%')</span></td><td>99 %<span style=\"background: LightGoldenRodYellow\"> (Details: UCUM code % = '%')</span></td></tr></table><h3>Components</h3><table class=\"grid\"><tr><td style=\"display: none\">-</td><td><b>Code</b></td><td><b>Value[x]</b></td></tr><tr><td style=\"display: none\">*</td><td>Inhaled oxygen flow rate <span style=\"background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki\"> (<a href=\"https://loinc.org/\">LOINC</a>#3151-8)</span></td><td>6 liters/min<span style=\"background: LightGoldenRodYellow\"> (Details: UCUM code L/min = 'L/min')</span></td></tr></table></div>"
},
"identifier" : [
{
"system" : "http://goodcare.org/observation/id",
"value" : "o1223435-10"
}
],
"status" : "final",
"category" : [
{
"coding" : [
{
"system" : "http://terminology.hl7.org/CodeSystem/observation-category",
"code" : "vital-signs",
"display" : "Vital Signs"
}
],
"text" : "Vital Signs"
}
],
"code" : {
"coding" : [
{
"system" : "http://loinc.org",
"code" : "2708-6",
"display" : "Oxygen saturation in Arterial blood"
},
{
"system" : "http://loinc.org",
"code" : "59408-5",
"display" : "Oxygen saturation in Arterial blood by Pulse oximetry"
},
{
"system" : "urn:iso:std:iso:11073:10101",
"code" : "150456",
"display" : "MDC_PULS_OXIM_SAT_O2"
}
]
},
"subject" : {
"reference" : "Patient/example"
},
"effectiveDateTime" : "2014-12-05T09:30:10+01:00",
"valueQuantity" : {
"value" : 95,
"unit" : "%",
"system" : "http://unitsofmeasure.org",
"code" : "%"
},
"interpretation" : [
{
"coding" : [
{
"system" : "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
"code" : "N",
"display" : "Normal"
}
],
"text" : "Normal (applies to non-numeric results)"
}
],
"device" : {
"display" : "Acme Pulse Oximeter 2000"
},
"referenceRange" : [
{
"low" : {
"value" : 90,
"unit" : "%",
"system" : "http://unitsofmeasure.org",
"code" : "%"
},
"high" : {
"value" : 99,
"unit" : "%",
"system" : "http://unitsofmeasure.org",
"code" : "%"
}
}
],
"component" : [
{
"code" : {
"coding" : [
{
"system" : "http://loinc.org",
"code" : "3151-8",
"display" : "Inhaled oxygen flow rate"
}
],
"text" : "Inhaled oxygen flow rate"
},
"valueQuantity" : {
"value" : 6,
"unit" : "liters/min",
"system" : "http://unitsofmeasure.org",
"code" : "L/min"
}
}
]
}
```
看完馬上想:啊設計稿不是才兩個的數值,一個字串,怎麼欄位多到靠杯

這邊為大家整理一下重點:
### 必填有哪些欄位?
* status
* code
### [status](http://hl7.org/fhir/R4B/observation-definitions.html#Observation.status): 紀錄的狀態
因為紀錄可能不是最終版本,可以看到文件上有規範要使用 [**ObservationStatus**](http://hl7.org/fhir/R4B/valueset-observation-status.html)的值

點**ObservationStatus**進去後可以直接看各個值的定義

思考一下我們的使用情境,似乎是 **final** 就可以滿足了,因為表單輸入值量測後就是完成的狀態了
### code: 描述被觀察了什麼

是 [**CodeableConcept**](http://hl7.org/fhir/R4B/datatypes.html#CodeableConcept) 的結構,文件上有建議可以使用 [LOINC Codes](http://hl7.org/fhir/R4B/valueset-observation-codes.html)


想到剛剛美國IG上的範例:
```json
"code" : {
"coding" : [
{
"system" : "http://loinc.org", // 定義醫療專業術語的系統
"code" : "2708-6", // 系統上的定義碼
"display" : "Oxygen saturation in Arterial blood" // 系統定義的表示形式
},
{
"system" : "http://loinc.org",
"code" : "59408-5",
"display" : "Oxygen saturation in Arterial blood by Pulse oximetry"
},
{
"system" : "urn:iso:std:iso:11073:10101",
"code" : "150456",
"display" : "MDC_PULS_OXIM_SAT_O2"
}
]
}
```
這邊目前無法確定是否能照用,所以先填一樣的,畢竟我不是醫療專業人員,怕用錯的話,需要整理所有的欄位對應表格,並與FHIR專家諮詢確認使用情境,錯的話再改就行了
那到底要怎麼讀這些系統上的定義碼詳細的內容呢?
其實只要把網址組起來就好了:http://loinc.org/2708-6

可以看到網站列出了詳細的醫療術語定義,這就是為什麼FHIR可以作為一個公定的醫療資訊交換規範了,因為只需要知道code,與定義的系統網址,就可以直接查詢到結果,而不會有要交換資料的雙方,對於定義不一致的問題。
以下是目前我介接時常用的兩個網站,都可以註冊帳號或直接免費使用:
* LONIC
* SNOMED
#### [LONIC search](https://loinc.org/search/)
註冊免費帳號,可以進 [LONIC search](https://loinc.org/search) 查詢專業術語,這邊查詢的是 [oxygen saturation](https://loinc.org/search/?t=1&s=oxygen+saturation)

列表上列出了一大堆

LONIC 上只要有列出 `LHC-Forms` 的tag

都可以進入[form頁面](https://forms.loinc.org/43146-0)去確認欄位到底可以填什麼

#### [SNOMED](https://browser.ihtsdotools.org/?perspective=full&conceptId1=74262004&edition=MAIN/2023-07-31&release=&languages=en&latestRedirect=false)
通常在 LONIC 查不到的術語,可以到[SNOMED](http://hl7.org/fhir/R4B/terminologies-systems.html)找
> 如果以上兩個網站都沒有的話,hl7官方也有很好心的[列出其他可以查詢的系統](http://hl7.org/fhir/R4B/terminologies-systems.html)
以上終於把這筆關於血氧、供氧要填入什麼到 Observation 的兩個欄位搞清楚了
接著是對於資料關聯的必要資訊:
* subject:誰的量測資料是使用
* performer:誰執行量測
#### subject: 量測的目標
因為我們目前的介面是針對患者的量測,因此我們會關聯到 [Patient](http://hl7.org/fhir/R4B/patient.html)

JSON範例如下:
```json
"subject" : {
"reference" : "Patient/id-on-fhir-system" // 格式一定要填 ResourceType/id
},
```
#### performer: 由誰量測
量測者則是使用 [Practitioner(從業者)](http://hl7.org/fhir/R4B/practitioner.html)

```json
"performer" : [{
"reference" : "Practitioner/id-on-fhir-system" // 格式一定要填 ResourceType/id
}],
```
接著是量測相關的資料
* effectiveDateTime: 量測時間
* valueQuantity:量測數值
* component:組合結果
* method: 量測方式
#### effective[x]: 臨床進行觀察的相關時間/時間週期

根據需求,可以選擇自己需要的格式,我們這邊是使用 effectiveDateTime
```json
"effectiveDateTime" : "2014-12-05T09:30:10+01:00",
```
而針對不同的時間規範,官方文件有列出相對應的[格式驗證準則](http://hl7.org/fhir/R4B/datatypes.html#dateTime)
#### value[x]: 實際的結果

同樣也有不同格式,為了不同的量測資料的相容性,選擇 value[Quantity](http://hl7.org/fhir/R4B/datatypes.html#Quantity)
```json
"valueQuantity" : {
"value" : 95,
"unit" : "%",
"system" : "http://unitsofmeasure.org",
"code" : "%"
},
```

這邊又看到了裡面有 `system`、 `unit`、`code`要填寫,就會知道要去 http://unitsofmeasure.org 瞭解了,查詢的頁面是在 https://ucum.org/ucum

#### component: 組合結果

可以放多個量測結果的欄位,範例中放了供氧的資訊
```json
"component" : [
{
"code" : {
"coding" : [
{
"system" : "http://loinc.org",
"code" : "3151-8",
"display" : "Inhaled oxygen flow rate"
}
],
"text" : "Inhaled oxygen flow rate"
},
"valueQuantity" : {
"value" : 6,
"unit" : "liters/min",
"system" : "http://unitsofmeasure.org",
"code" : "L/min"
}
}
]
```
#### method: 量測方式

[官方文件](http://hl7.org/fhir/R4B/valueset-observation-methods.html#definition)推薦在 Snomed 查詢

```json
"method": {
"coding": [
{
"system": "http://snomed.info/sct",
"code": "722742002",
"display": "Breathing room air" //供氧方式為空氣
}
]
},
```
講到這邊大家看得快睡著,但真的把這一小個欄位的血氧、供氧的量測資料介接FHIR講完了

但我們設計稿其實長這樣,按照上面講的方法去對就好了


你以為已經會介接FHIR了,會這麼順利嗎?我還是有遇到一些介接上的問題
## 遇到的問題
以下會ㄧㄧ提出與解決我當初遇到的所有問題
### 問題1: Google FHIR 的支援度
* 不是所有官方文件上列出來的,都在 Google FHIR store都有支援
* 官方文件
* [search parameter](http://hl7.org/fhir/R4B/search.html)
* [Observation serach parameters](http://hl7.org/fhir/R4B/observation.html#search)
* Google 文件
* [Searching for FHIR resources](https://cloud.google.com/healthcare-api/docs/how-tos/fhir-search)
* [Advanced FHIR search features](https://cloud.google.com/healthcare-api/docs/how-tos/fhir-advanced-search)
* 如果要知道Google healthcare FHIR store有支援哪些,最準的還是去用 [FHIR viewer](https://cloud.google.com/healthcare-api/docs/how-tos/fhir-search#using_the_fhir_viewer) 去點點看作查詢
* 因為支援度不是那麼完全,所以在儲存FHIR資源時,都要先思考好要怎麼取資料出來,才不會導致介接好但是沒有可以用來取資料的search parameter

甚至還有search templates可以儲存使用

順帶列出可以用哪些花式操作:
#### 範例一
`Observation?_include=%2A&performer=97ba9869-a80b-47f5-bb4d-6b241e0fddc5&code:text=vitalSigns&date=ge2023-06-01T16:00:00Z&date=le2023-06-14T16:00:00Z`
#### `_include:%2A`:取得指定的關聯資料
* 如 performer(Practitioner), subject(Patient),看你的資料有根哪些資源關聯
* %2A: ASCII Encoding Reference的`*`,代表在該筆資料內所有的references都會被取出
* 使用時要注意,有可能會因為reference的資料太大,而導致timeout
* 因此怕取出資料量太多的話,可以指定要`_include`的欄位,例如:`_include=DocumentReference:author`
> 有另一個參數是 `_revinclude`,可以取出所有引用用該筆資源的資料,不建議使用,有限制他可以找到至多100個資源
#### `subject:id-of-subject`: 搜尋資料紀錄的對象
* 直接給定id就好
#### `performer:id-of-performer1`:搜尋紀錄資料執行者
* 同上
#### `code:text`
* 搜尋在 `code` 裡面的 `text`
#### `date=ge2023-06-01T16:00:00Z&date=le2023-06-14T16:00:00Z`: 時間區間
* 在observation內是`effective[x]`
* 可以加上`ge`,`le`去搜尋時間區間內的資料
建議所有操作都先在 Google FHIR viewer上先操作過,確定取得的結果真的是自己需要的資料
### 問題2: 設計稿上定義的其他部位要放哪!?
看完了上面的FHIR介接,應該會了解到,醫療術語上,只有明確的定義去講每個專有名詞,並沒有所謂的「其他」
但設計稿上,出現了「其他」部位,要怎麼辦?


怎麼找,都在術語定義網站LONIC,SNOMED上找不到相關的code
當然r,因為哪有一個身體部位叫「其他」
因為FHIR的介接規範,只需要列好指定的system以及code,因此我們可以定義好jubo自己的靜態網站(例如[台灣IG](https://twcore.mohw.gov.tw/ig/twcore/CodeSystem-medication-path-tw.html),去放這些定義
```go
BodySite = &fhir.CodeableConcept{
Coding: []fhir.Coding{
{
System: fhirenum.System_CustomJuboHealth.String(),
Code: "other-body-site", // TODO: not yet defined the code system
Display: "Other Position",
},
},
Text: pointer.TypePointer(fhirenum.BodyPosition_Other.String()),
}
```
## 結語
* 雖然介接FHIR比起一般API介接較複雜,但了解去哪裡找定義後,就可以駕輕就熟,~~或是由jubo link統一跟FHIR service介接~~
* **蛤!?既然你都說比較複雜,那還用FHIR幹嘛?**
* Google 官方有寫 [Importance of FHIR in healthcare information technology](https://cloud.google.com/healthcare-api/docs/concepts/fhir#importance_of_fhir_in_healthcare_information_technology)

> 簡而言之:FHIR 的目的時期是為了讓醫療系統之間的資訊交換更為容易,而不會有「你說你的我說我的」,因為定義都統一寫好了
* 多寫測試
* 省下未來的自己去修bug的時間
* 省下PM、QA、同事找你的時間
* 省下溝通成本
* 有機會重來的話:或許可以試試看grpc?
* 優點
* Service內部溝通不需要另外寫struct去parse JSON
* Google 有 package ([googl/fhir](https://github.com/google/fhir))
* 缺點
* proto buff 定義的 Ineterface 跟FHIR文件不太一樣