在上一篇文章中,我們認識了FHIR RESTful API的read互動。
如果已經知道Patient的Resource id,就可以用以下形式讀取資料:
GET [base]/Patient/[id]
例如:
GET https://hospital.example.org/fhir/Patient/patient-001
但是,現實情況中不一定會事先知道FHIR Server替Patient分配的id。
使用者可能只知道:
這時就需要使用FHIR的search互動,透過搜尋參數找出可能符合條件的Patient。
今天不進行實際API操作,而是透過Request及Response範例,認識FHIR Patient搜尋的基本概念。
本文使用的姓名、病歷號、電話及網址均為虛構教學資料。
FHIR搜尋的基本形式是:
GET [base]/[Resource Type]?[parameter]=[value]
如果要搜尋Patient:
GET [base]/Patient?[parameter]=[value]
例如,按照姓名搜尋:
GET https://hospital.example.org/fhir/Patient?name=王小明
可以拆成:
| 部分 | 用途 |
|---|---|
[base] |
FHIR Server的Base URL |
Patient |
要搜尋的Resource類型 |
? |
後方開始放入搜尋參數 |
name |
搜尋參數名稱 |
王小明 |
搜尋值 |
= |
連接參數名稱及搜尋值 |
這個Request的意思是:
請搜尋姓名符合王小明的Patient。
read與search都可能使用GET,但兩者的使用情境不同。
GET /Patient/patient-001
表示:
請讀取id為patient-001的Patient。
GET /Patient?name=王小明
表示:
請搜尋姓名符合王小明的Patient。
兩者可以整理成:
| 比較項目 | read | search |
|---|---|---|
| 是否需要Resource id | 需要 | 不需要 |
| 查詢依據 | id | 姓名、生日或病歷號等條件 |
| 結果數量 | 一筆 | 零筆、一筆或多筆 |
| 成功Response | Patient | Bundle |
| 找不到資料時 | 通常為404 | 通常回傳結果為0的Bundle |
搜尋可能找到:
因此,FHIR使用Bundle包裝搜尋結果。
例如:
{
"resourceType": "Bundle",
"type": "searchset",
"total": 1,
"entry": [
{
"fullUrl": "https://hospital.example.org/fhir/Patient/patient-001",
"resource": {
"resourceType": "Patient",
"id": "patient-001",
"name": [
{
"text": "王小明"
}
]
}
}
]
}
其中:
resourceType是Bundle
type是searchset
total表示符合條件的結果數量entry放入搜尋結果entry.resource是實際的Patient即使只找到一筆Patient,搜尋Response仍然通常是Bundle,而不是直接回傳Patient。
FHIR R4的Patient Resource定義了多種搜尋參數,今天先介紹較常見的幾種。
_id用來依照Resource的邏輯id搜尋。
GET /Patient?_id=patient-001
這和read看起來很相似,但Response不同。
GET /Patient/patient-001
成功時直接回傳Patient。
_idGET /Patient?_id=patient-001
成功時回傳搜尋結果Bundle。
可以比較如下:
| Request | Response |
|---|---|
GET /Patient/patient-001 |
Patient |
GET /Patient?_id=patient-001 |
Bundle |
Patient的identifier可能用來記錄病歷號。
假設Patient包含:
"identifier": [
{
"system": "https://hospital.example.org/mrn",
"value": "MRN0001"
}
]
搜尋概念可以寫成:
GET /Patient?identifier=MRN0001
不過,只提供value可能不夠精確,因為不同識別系統可能出現相同編號。
更明確的方式是同時提供system及value:
GET /Patient?identifier=https://hospital.example.org/mrn|MRN0001
其中使用直線符號|分隔:
system|value
也就是:
https://hospital.example.org/mrn|MRN0001
表示:
搜尋病歷號系統為指定URI,而且病歷號為MRN0001的Patient。
GET /Patient?name=王小明
name會針對Patient的姓名相關內容進行搜尋。
Patient姓名使用HumanName資料型別,可能包含:
text
family
given
prefix
suffix
不同FHIR Server對字串比對、語言及索引的實作可能不同,因此搜尋姓名時不一定只會找到完全相同的文字。
如果只知道姓氏,可以使用:
GET /Patient?family=王
family對應HumanName中的姓氏或家族名稱。
Patient資料可能包含:
"name": [
{
"family": "王",
"given": [
"小明"
]
}
]
這時family=王可能找到這筆Patient。
GET /Patient?given=小明
given用來依照名字搜尋。
如果Patient為:
"name": [
{
"family": "王",
"given": [
"小明"
]
}
]
那麼:
family是王given是小明name則可能搜尋整體姓名相關內容不同文化對姓名的拆分方式可能不同,實際使用時需要依照Profile及在地實作規則處理。
GET /Patient?birthdate=2000-01-01
birthdate屬於date類型的搜尋參數。
它對應Patient中的:
"birthDate": "2000-01-01"
日期格式通常使用:
YYYY-MM-DD
也就是:
年-月-日
如果日期格式不符合規範,Server可能無法理解搜尋條件。
GET /Patient?gender=male
Patient的gender使用AdministrativeGender代碼,常見值包括:
male
female
other
unknown
因此,不應自行寫成:
gender=男
或:
gender=M
而要使用FHIR規範允許的代碼。
GET /Patient?active=true
active用來搜尋目前被視為有效使用中的Patient紀錄。
也可以搜尋:
GET /Patient?active=false
不過,active=false不代表病人已死亡,只表示該Patient紀錄目前不再被積極使用。
GET /Patient?telecom=0900-000-001
telecom可能搜尋:
如果只搜尋一串數字,可能找到使用該內容的不同ContactPoint。
實際系統也可能因電話格式不同而影響搜尋,例如:
0900000001
0900-000-001
+886-900-000-001
這些格式是否會被視為相同,取決於Server的標準化及搜尋實作。
GET /Patient?address=桃園
address可能針對Address中的多個欄位進行搜尋,例如:
text
line
city
district
state
postalCode
country
FHIR也定義較具體的地址搜尋參數,例如:
GET /Patient?address-city=中壢區
GET /Patient?address-postalcode=320
不同國家的地址結構差異很大,臺灣地址的實際搜尋方式仍需要參考TW Core IG及Server實作。
Patient可以透過managingOrganization連結管理該病人紀錄的Organization。
例如:
"managingOrganization": {
"reference": "Organization/hospital-001"
}
搜尋概念可以表示為:
GET /Patient?organization=Organization/hospital-001
或在特定情境中使用Resource id:
GET /Patient?organization=hospital-001
這類參數屬於Reference搜尋。實際接受的Reference表示方式需依FHIR規範及Server能力判斷。
FHIR搜尋參數不是全部以相同方式比對。
常見搜尋參數型別包括:
| 類型 | 常見用途 | Patient範例 |
|---|---|---|
string |
文字 | name、family、given |
token |
代碼或Identifier | identifier、gender、active |
date |
日期 | birthdate |
reference |
Resource連結 | organization |
uri |
URI | 部分標準識別資料 |
number |
數值 | 其他Resource可能使用 |
quantity |
數值加單位 | Observation常見 |
不同型別具有不同的搜尋規則及修飾方式。
例如:
name=王小明
是string搜尋。
gender=male
是token搜尋。
birthdate=2000-01-01
是date搜尋。
如果想同時使用姓名及出生日期,可以使用&連接參數:
GET /Patient?name=王小明&birthdate=2000-01-01
通常可以理解為:
姓名符合王小明,而且出生日期為2000年1月1日。
也就是AND關係。
再加入性別:
GET /Patient?name=王小明&birthdate=2000-01-01&gender=male
表示同時符合:
多個條件可以縮小結果範圍,但仍不代表一定只會找到一位病人。
可能有兩位病人同時:
因此,即使加入多個條件,搜尋結果仍可能包含多筆Patient。
系統不能因為搜尋結果第一筆看起來很像,就直接認定它是正確病人。
病人比對可能還需要考慮:
錯誤連結病人資料可能影響病人安全,因此需要特別謹慎。
FHIR搜尋可以使用逗號表示多個可能值,也就是OR概念。
例如:
GET /Patient?gender=male,female
可以理解為:
搜尋gender為male或female的Patient。
概念上:
male OR female
不過,並非每一個搜尋參數或FHIR Server都一定支援所有多值搜尋形式,因此仍要查看Server的CapabilityStatement及實作說明。
FHIR搜尋也可能重複使用相同參數,例如:
GET /Patient?address=桃園&address=中壢
概念上希望搜尋地址同時符合桃園及中壢的Patient。
也就是:
桃園 AND 中壢
但是,各搜尋參數對重複值的支援及比對方式可能有所限制。正式使用時需要確認FHIR規範及Server實作,不能假設所有參數都具有完全相同的AND或OR行為。
FHIR允許部分搜尋參數使用Modifier,進一步說明比對方式。
Modifier會放在搜尋參數名稱後面,例如:
name:exact
GET /Patient?name:exact=王小明
:exact表示進行較精確的字串比對。
與一般string搜尋相比,它通常會更重視完整內容、大小寫及空白等差異。
GET /Patient?name:contains=小明
:contains表示搜尋字串中包含指定內容的資料。
例如,「王小明」可能包含「小明」。
不過,Server是否支援:contains,仍應查看CapabilityStatement及實作規則。
date搜尋可以搭配前綴,表示日期之間的比較關係。
常見前綴包括:
| 前綴 | 意義 |
|---|---|
eq |
等於 |
ne |
不等於 |
gt |
大於或晚於 |
lt |
小於或早於 |
ge |
大於等於 |
le |
小於等於 |
sa |
開始於指定時間之後 |
eb |
結束於指定時間之前 |
ap |
約等於 |
例如:
GET /Patient?birthdate=ge2000-01-01
可以理解為:
搜尋出生日期大於或等於2000年1月1日的Patient。
GET /Patient?birthdate=lt2010-01-01
表示:
搜尋出生日期早於2010年1月1日的Patient。
前綴應直接放在日期值前面,中間沒有空格。
gender、identifier等欄位常使用token搜尋。
如果只提供Value:
GET /Patient?identifier=MRN0001
表示搜尋識別碼值為MRN0001的Patient。
如果同時指定System:
GET /Patient?identifier=https://hospital.example.org/mrn|MRN0001
表示同時比對:
代碼搜尋也可能使用相似形式:
system|code
這通常比只提供Value或Code更加明確。
FHIR提供_count參數,用來要求每一頁回傳的結果數量。
例如:
GET /Patient?_count=10
表示Client希望每一頁最多回傳10筆Patient。
需要注意:
_count不是限制所有符合條件的總數。next連結取得。如果沒有找到符合條件的Patient,FHIR Server通常不會因為「沒有搜尋結果」就回傳404。
Response仍可能是:
200 OK
Body則是結果為0的Bundle:
{
"resourceType": "Bundle",
"type": "searchset",
"total": 0
}
可以比較:
| 情境 | 常見Response |
|---|---|
| read指定id,但Resource不存在 | 404 Not Found |
| search沒有找到符合條件的資料 | 200 OK及空的搜尋Bundle |
這是read與search的重要差異。
一份搜尋Bundle可能包含:
{
"resourceType": "Bundle",
"type": "searchset",
"total": 2,
"link": [
{
"relation": "self",
"url": "https://hospital.example.org/fhir/Patient?name=王小明"
}
],
"entry": [
{
"fullUrl": "https://hospital.example.org/fhir/Patient/patient-001",
"search": {
"mode": "match"
},
"resource": {
"resourceType": "Patient",
"id": "patient-001"
}
},
{
"fullUrl": "https://hospital.example.org/fhir/Patient/patient-002",
"search": {
"mode": "match"
},
"resource": {
"resourceType": "Patient",
"id": "patient-002"
}
}
]
}
其中:
total為2,表示有兩筆符合條件。entry包含兩筆搜尋結果。fullUrl是Resource的完整位置。search.mode為match,表示該Resource符合搜尋條件。entry.resource是實際的Patient。Bundle會在下一篇文章中進一步介紹。
FHIR R4規範定義了許多Patient搜尋參數,但個別FHIR Server不一定全部支援。
Server可能只支援:
_id
identifier
name
birthdate
卻不支援:
address
telecom
organization
因此,需要查看CapabilityStatement中的:
rest → resource → searchParam
確認Patient實際支援哪些搜尋參數。
如果使用不支援的參數,Server可能:
Client不能假設所有Server對未知搜尋參數的反應完全相同。
搜尋Patient最大的風險之一,是將搜尋結果誤認為正確病人。
例如,搜尋:
GET /Patient?name=王小明
可能找到多位同名病人。
只比對姓名可能受到以下因素影響:
因此,Patient搜尋只是「找出可能符合的候選資料」,不一定能直接完成身分確認。
醫療機構仍需要依照病人比對政策,使用足夠且合法的資訊確認病人身分。
Patient搜尋可能接觸:
這些都屬於敏感資料。
正式FHIR Server通常需要控制:
如果允許未授權使用者反覆搜尋姓名及生日,可能造成病人資料外洩。
所以FHIR定義搜尋方法,不代表所有人都具有搜尋權限。
| 需求 | 搜尋概念 |
|---|---|
| 依FHIR Resource id | Patient?_id=patient-001 |
| 依病歷號 | Patient?identifier=system|value |
| 依姓名 | Patient?name=王小明 |
| 依姓氏 | Patient?family=王 |
| 依名字 | Patient?given=小明 |
| 依出生日期 | Patient?birthdate=2000-01-01 |
| 依性別 | Patient?gender=male |
| 依有效狀態 | Patient?active=true |
| 依聯絡方式 | Patient?telecom=... |
| 依地址 | Patient?address=桃園 |
| 依管理機構 | Patient?organization=... |
| 多條件AND | 使用&連接 |
| 多值OR | 使用逗號分隔 |
| 精確字串 | 使用:exact |
| 包含字串 | 使用:contains |
| 日期比較 | 使用ge、lt等前綴 |
實際可用參數及修飾詞,仍然要以FHIR Server的CapabilityStatement及實作說明為準。
今天認識了FHIR Patient的search互動。
當Client不知道Patient id,但知道姓名、出生日期、病歷號或其他條件時,可以透過搜尋參數尋找可能符合的Patient。
常見參數包括:
_id
identifier
name
family
given
birthdate
gender
active
telecom
address
organization
搜尋結果通常使用type為searchset的Bundle包裝。即使沒有找到資料,也可能回傳200 OK及結果為0的Bundle,而不是404。
今天最重要的觀念是:
搜尋找到的Patient只是候選結果,不代表已經完成病人身分確認。
正式醫療系統需要使用適當的識別資料、權限及病人比對規則,避免將資料連結到錯誤病人。
下一篇將專門介紹FHIR Bundle,進一步拆解total、link、entry、fullUrl及搜尋分頁等內容。
Day 19|搜尋結果為什麼變成Bundle?
HL7 FHIR R4:Search
https://hl7.org/fhir/R4/search.html
HL7 FHIR R4:Patient Search Parameters
https://hl7.org/fhir/R4/patient.html#search
HL7 FHIR R4:SearchParameter
https://hl7.org/fhir/R4/searchparameter.html
HL7 FHIR R4:Bundle
https://hl7.org/fhir/R4/bundle.html
HL7 FHIR R4:Patient Matching
https://hl7.org/fhir/R4/patient.html#match