---
# System prepended metadata

title: 一起 on FHIR!

---

# 一起 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

![](https://hackmd.io/_uploads/SkC9pDPp3.png)


以設計稿上的實際案例來看，如上圖，介接SpO2(%)/Flow(L/min) 血氧含量、供氧。

要先判斷這要放到什麼樣的FHIR Resource，到官方看了一下眼花撩亂的首頁

![](https://hackmd.io/_uploads/SJIkldPTh.png)

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

![](https://hackmd.io/_uploads/S1AXlOwp2.png)

> Measurements and simple assertions made about a patient, device or other subject

很幸運一次就找到似乎可以用的Resource了

![](https://hackmd.io/_uploads/rJzgW_Da3.png)

往下滑，看到下面的Scope and Usage的舉例更加確定他可以使用在Vital signs等量測項目上

但好像沒看到血氧欸，請教Google大神，找到[美國版的IG](https://build.fhir.org/ig/HL7/US-Core/Observation-satO2-fiO2.json.html)也是把他塞到**Observation**裡面，那似乎是可以隨便塞了

![](https://hackmd.io/_uploads/BJNRbODTh.png)


接下來很簡單，照抄就行了...嗎?

以下是美國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 &quot;satO2-fiO2&quot; </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 &quot;Oxygen saturation in Arterial blood by Pulse oximetry&quot;; <a href=\"http://terminology.hl7.org/5.0.0/CodeSystem-v3-mdc.html\">ISO 11073-10101 Health informatics - Point-of-care</a>#150456 &quot;MDC_PULS_OXIM_SAT_O2&quot;)</span></p><p><b>subject</b>: <a href=\"Patient-example.html\">Patient/example</a> &quot; SHAW&quot;</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 &quot;Normal&quot;)</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"
      }
    }
  ]
}
```

看完馬上想：啊設計稿不是才兩個的數值，一個字串，怎麼欄位多到靠杯

![設計稿](https://hackmd.io/_uploads/SkC9pDPp3.png)


這邊為大家整理一下重點：
### 必填有哪些欄位？
* status
* code

### [status](http://hl7.org/fhir/R4B/observation-definitions.html#Observation.status): 紀錄的狀態
因為紀錄可能不是最終版本，可以看到文件上有規範要使用 [**ObservationStatus**](http://hl7.org/fhir/R4B/valueset-observation-status.html)的值
![](https://hackmd.io/_uploads/r1--Iuv6n.png)

點**ObservationStatus**進去後可以直接看各個值的定義
![](https://hackmd.io/_uploads/HkDo8uvT3.png)

思考一下我們的使用情境，似乎是 **final** 就可以滿足了，因為表單輸入值量測後就是完成的狀態了


### code: 描述被觀察了什麼

![](https://hackmd.io/_uploads/ry9dDdwTn.png)

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

![CodeableConcept](https://hackmd.io/_uploads/H141_dvan.png)

![Coding](https://hackmd.io/_uploads/HySgtuD62.png)


想到剛剛美國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

![](https://hackmd.io/_uploads/ry5ZndPT3.png)

可以看到網站列出了詳細的醫療術語定義，這就是為什麼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)

![](https://hackmd.io/_uploads/BkTIAOPph.png)

列表上列出了一大堆

![](https://hackmd.io/_uploads/BJN3CODan.png)

LONIC 上只要有列出 `LHC-Forms` 的tag
![](https://hackmd.io/_uploads/BkBWJFD6n.png)


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

![](https://hackmd.io/_uploads/rkQYyKPan.png)


#### [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)
![](https://hackmd.io/_uploads/r1m1mKvp2.png)

JSON範例如下:

```json
 "subject" : {
    "reference" : "Patient/id-on-fhir-system" // 格式一定要填 ResourceType/id
  },
```

#### performer: 由誰量測
量測者則是使用 [Practitioner(從業者)](http://hl7.org/fhir/R4B/practitioner.html)

![](https://hackmd.io/_uploads/BJjSNtv6h.png)

```json
"performer" : [{
    "reference" : "Practitioner/id-on-fhir-system" // 格式一定要填 ResourceType/id
  }],
```

接著是量測相關的資料
* effectiveDateTime: 量測時間
* valueQuantity：量測數值
* component：組合結果
* method: 量測方式

#### effective[x]: 臨床進行觀察的相關時間/時間週期

![](https://hackmd.io/_uploads/SJfyBFPpn.png)

根據需求，可以選擇自己需要的格式，我們這邊是使用 effectiveDateTime


```json
 "effectiveDateTime" : "2014-12-05T09:30:10+01:00",
```

而針對不同的時間規範，官方文件有列出相對應的[格式驗證準則](http://hl7.org/fhir/R4B/datatypes.html#dateTime)

#### value[x]: 實際的結果
![](https://hackmd.io/_uploads/S1QPUtvph.png)
同樣也有不同格式，為了不同的量測資料的相容性，選擇 value[Quantity](http://hl7.org/fhir/R4B/datatypes.html#Quantity)

```json
  "valueQuantity" : {
    "value" : 95,
    "unit" : "%",
    "system" : "http://unitsofmeasure.org",
    "code" : "%"
  },
```

![Quantity](https://hackmd.io/_uploads/BJpoCo6Th.png)


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

![ucum.org](https://hackmd.io/_uploads/HkqPnsap2.png)



#### component: 組合結果

![](https://hackmd.io/_uploads/HJDxFFvah.png)

可以放多個量測結果的欄位，範例中放了供氧的資訊


```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: 量測方式


![](https://hackmd.io/_uploads/rJn5qtvp2.png)

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

![snomed room air](https://hackmd.io/_uploads/r1z5ynT63.png)


```json
"method": {
    "coding": [
      {
        "system": "http://snomed.info/sct",
        "code": "722742002",
        "display": "Breathing room air" //供氧方式為空氣
      }
    ]
  },
```

講到這邊大家看得快睡著，但真的把這一小個欄位的血氧、供氧的量測資料介接FHIR講完了

![](https://hackmd.io/_uploads/SkC9pDPp3.png)

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

![](https://hackmd.io/_uploads/Hyhg3Kwph.png)

![](https://hackmd.io/_uploads/ByMfhKwa2.png)


你以為已經會介接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

![Googoe FHIR query builder](https://hackmd.io/_uploads/ByapEjS6h.png)

甚至還有search templates可以儲存使用
![](https://hackmd.io/_uploads/H1UBb1ta3.png)




順帶列出可以用哪些花式操作：
#### 範例一
`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介接，應該會了解到，醫療術語上，只有明確的定義去講每個專有名詞，並沒有所謂的「其他」
但設計稿上，出現了「其他」部位，要怎麼辦？
![](https://hackmd.io/_uploads/rkkQaJYTh.png)
![](https://hackmd.io/_uploads/rJyN61Yah.png)

怎麼找，都在術語定義網站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)

![](https://hackmd.io/_uploads/Sy_rPt66h.png)
> 簡而言之：FHIR 的目的時期是為了讓醫療系統之間的資訊交換更為容易，而不會有「你說你的我說我的」，因為定義都統一寫好了


* 多寫測試
    * 省下未來的自己去修bug的時間
    * 省下PM、QA、同事找你的時間
    * 省下溝通成本
* 有機會重來的話：或許可以試試看grpc？
    * 優點
        * Service內部溝通不需要另外寫struct去parse JSON
        * Google 有 package ([googl/fhir](https://github.com/google/fhir))
    * 缺點
        * proto buff 定義的 Ineterface 跟FHIR文件不太一樣