03 · CRUD 與搜尋規格
3.1 基本約定
| 項目 | 值 |
|---|---|
| Base URL | https://{host}/fhir/r4 |
Content-Type / Accept |
application/fhir+json |
| 能力宣告 | GET /metadata → capabilitystatement.json |
| 字元編碼 | UTF-8 |
未帶 Accept |
一律回 application/fhir+json(不做內容協商猜測) |
3.2 互動矩陣
C=create、R=read、V=vread、U=update、P=patch、D=delete、H=history、S=search。
| 資源 | C | R | V | U | P | D | H | S | 備註 |
|---|---|---|---|---|---|---|---|---|---|
Patient |
✓ | ✓ | ✓ | ✓ | HIS 唯讀 | ||||
Encounter |
✓ | ✓ | ✓ | ✓ | HIS 唯讀 | ||||
EpisodeOfCare |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||
Coverage |
✓ | ✓ | HIS 唯讀 | ||||||
Practitioner / PractitionerRole |
✓ | ✓ | HIS 唯讀 | ||||||
RelatedPerson |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 同意書用 | ||
CareTeam |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||
Condition |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||
Goal |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||
CarePlan |
✓ | ✓ | ✓ | ✓ | ✓ | 更新=新建+replaces |
|||
AllergyIntolerance |
✓ | ✓ | HIS 唯讀 | ||||||
MedicationRequest |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 簽署後限縮,見 3.4 | |
MedicationAdministration |
✓ | ✓ | ✓ | ✓ | ✓ | 建立後不可改 | |||
ServiceRequest |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
Procedure |
✓ | ✓ | ✓ | ✓ | ✓ | 建立後不可改 | |||
Specimen |
✓ | ✓ | HIS 唯讀 | ||||||
DiagnosticReport |
✓ | ✓ | ✓ | ✓ | HIS 唯讀 | ||||
Observation |
✓ | ✓ | ✓ | ✓ | ✓ | 建立後不可改 | |||
Composition |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 已簽後限縮,見 3.4 | ||
Task |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ||
Communication |
✓ | ✓ | ✓ | ✓ | ✓ | 建立後不可改 | |||
Consent |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | |||
Provenance |
✓ | ✓ | ✓ | 僅新增,永不修改 | |||||
AuditEvent |
✓ | ✓ | 伺服器產生 |
3.2.1 沒有 delete
任何資源都不提供 DELETE。 誤建的資料以 status = entered-in-error 標記:
PATCH /MedicationRequest/8f3a HTTP/1.1
Content-Type: application/json-patch+json
If-Match: W/"3"
[{ "op": "replace", "path": "/status", "value": "entered-in-error" }]
伺服器同時自動產生 AuditEvent(action=D)。標為 entered-in-error 的資源仍可 read,但預設不出現在 search 結果(除非帶 status=entered-in-error)。
3.2.2 建立後不可改的資源
MedicationAdministration、Procedure、Observation、Communication 一旦建立就是已發生事實的紀錄,只能標 entered-in-error 後重建,不能 update。這對應原型「執行紀錄不可回頭編修」的行為。
3.3 版本並行控制
所有可寫資源強制樂觀鎖。
GET /Composition/7c21
→ 200 OK
ETag: W/"4"
Last-Modified: Sun, 31 Aug 2026 09:12:04 GMT
PUT /Composition/7c21
If-Match: W/"4"
| 狀況 | 回應 |
|---|---|
| 版本相符 | 200 OK,ETag: W/"5" |
| 版本落後 | 409 Conflict + OperationOutcome(code=conflict) |
缺 If-Match |
412 Precondition Failed |
原型的 store 已有等價檢查(「其他分頁已有較新版本;請保留畫面內容並重新整理」)。API 層的要求更嚴:沒帶 If-Match 就直接拒絕,不允許盲目覆寫。
409 的 OperationOutcome 必須帶目前版本,讓客戶端能就地合併:
{
"resourceType": "OperationOutcome",
"issue": [{
"severity": "error", "code": "conflict",
"diagnostics": "資源已被其他工作階段更新至 versionId=6。請重新讀取後合併,內容未被覆寫。",
"details": { "text": "currentVersionId=6" }
}]
}
3.4 狀態相依的可寫性
update 是否合法取決於資源當下狀態,不只取決於權限。
MedicationRequest
| 狀態 | 可改 | 不可改 |
|---|---|---|
draft(未簽) |
全部欄位 | — |
| 已簽、未送出 | status(→cancelled) |
藥品、劑量、途徑、頻次 |
| 已送出 HIS | 無 | 全部。改動須走取消+重新開立 |
active(HIS 已接受) |
status(→stopped) |
其餘 |
已送出的醫囑要改內容,必須取消再重開,不能就地 update——因為 HIS 端的 hisPrescriptionNumber 已經產生,就地改會讓兩端不一致(BR-ORD-006)。
Composition
| 狀態 | 可改 |
|---|---|
preliminary(草稿) |
全部欄位 |
final(已簽) |
無。更正一律新建版本 + relatesTo[replaces] |
amended |
無 |
3.5 條件式互動
| 互動 | 支援 | 用途 |
|---|---|---|
條件式建立(If-None-Exist) |
✓ | 劑次唯一性、HIS 事件冪等 |
條件式更新(PUT /X?params) |
✗ | 語義模糊,一律禁用 |
| 條件式刪除 | ✗ | 沒有 delete |
劑次唯一性的實作:
POST /MedicationAdministration
If-None-Exist: identifier=http://icu.emr.local/occurrence-id|M1-V1-D1&status=completed
已存在同劑次的完成紀錄時回 200 OK(回傳既有資源)而非 201,客戶端據此得知「本劑次已有完成紀錄,不可重複給藥」。這比先查再寫安全:不會有兩個護理師同時通過檢查的競態。
3.6 搜尋
3.6.1 共用參數
_id、_lastUpdated、_sort、_count(預設 20,上限 200)、_include、_revinclude、_summary、_elements、_total=accurate。
分頁走標準 Bundle.link(self / next / previous / first / last)。客戶端不得自行拼 offset,一律跟隨 next 的 URL。
3.6.2 各資源搜尋參數
| 資源 | 支援參數 |
|---|---|
Patient |
identifier, name, birthdate, _has:CareTeam:participant:participant |
Encounter |
patient, status, date, identifier, episode-of-care |
EpisodeOfCare |
patient, status, date |
CareTeam |
patient, participant, status, encounter |
Condition |
patient, encounter, code, verification-status, recorded-date |
Goal |
patient, lifecycle-status, target-date |
CarePlan |
patient, status, date, replaces |
MedicationRequest |
patient, encounter, status, intent, authoredon, requester, identifier, code, _has:MedicationAdministration:request:status |
MedicationAdministration |
patient, encounter, request, status, effective-time, performer, identifier |
ServiceRequest |
patient, encounter, status, category, code, authored, requester, performer, identifier, priority |
Procedure |
patient, encounter, based-on, status, date, performer |
DiagnosticReport |
patient, encounter, status, category, code, issued, based-on, specimen, identifier |
Observation |
patient, encounter, code, date, status, category, component-code, part-of, derived-from, performer |
Composition |
patient, encounter, type, status, date, author, related-id(relatesTo), section-code(本地) |
Task |
patient, encounter, status, owner, focus, business-status, authored-on |
Communication |
patient, encounter, category, sent, sender, recipient |
Provenance |
target, agent, recorded, activity(本地), entity |
Consent |
patient, status, category, date |
AuditEvent |
patient, agent, date, action, entity, outcome |
3.6.3 時間窗
原型的三種時間窗直接對映到日期區間搜尋:
| 時間窗 | 查詢 |
|---|---|
| 24 小時 | date=ge2026-08-30T19:00:00+07:00&date=le2026-08-31T19:00:00+07:00 |
| 本班起訖 | 由客戶端依 PractitionerRole 的班別起訖換算後帶入相同格式 |
| 全部 | 不帶 date |
伺服器不提供 window=shift 這種語意參數——班別起訖屬於排班資料,換算責任在客戶端,避免伺服器對「本班」有隱含定義。
3.6.4 一次載入病人工作區
原型的病人詳細頁需要多種資源。用 _include / _revinclude 減少往返:
GET /MedicationRequest?patient=Patient/abc&status=active
&_revinclude=MedicationAdministration:request
&_include=MedicationRequest:requester
&_count=100
GET /DiagnosticReport?patient=Patient/abc&date=ge2026-08-30
&_include=DiagnosticReport:result
&_include=DiagnosticReport:specimen
&_revinclude=Provenance:target
第二個查詢一次帶回報告、逐項數值、檢體、以及已閱/判讀的 Provenance——正好是 S13 檢驗結果頁的完整資料需求。
3.6.5 搜尋不做的事
- 不做模糊比對以外的推論。
code查不到就是空集合,不回「相近項目」。 - 不因為
interpretation未填就把結果歸入normal。要查正常值必須明寫interpretation=N。 - B 目錄搜尋(藥品/服務/ICD,共 31,146 筆)走
ValueSet/$expand,不走資源搜尋:
GET /ValueSet/his-medication/$expand?filter=paracetamol&count=20&offset=0
3.7 Bundle
transaction(原子)
以下情境必須用 type=transaction,全成功或全失敗:
| 情境 | 內容 |
|---|---|
| 一次開立多項醫囑 | N × MedicationRequest/ServiceRequest,共用同一 extension[orderSet] |
| 簽署文件 | Composition 更新 + Provenance 建立 |
| 接收交班 | Provenance 建立 + N × Task.owner 更新 |
| 病歷更正 | 新 Composition 建立 + 舊版 status 更新 |
原型的 catalogOrder 一次可建多筆醫囑,但每筆之後仍各自獨立簽署、送出、執行、回報。transaction 只保證「建立」這一步的原子性,不代表後續生命週期綁在一起。
batch(非原子)
只用於純讀取的批次載入。任何寫入不得用 batch。
3.8 自訂操作
只在「多資源副作用必須原子」時才定義操作,其餘一律用標準互動。
| 操作 | 方法 | 說明 |
|---|---|---|
GET /Patient/$roster |
GET | 回傳目前登入者的照護名單(Bundle,含 Patient+CareTeam+作用中醫囑計數)。取代客戶端自行拼 _has 查詢 |
POST /MedicationRequest/{id}/$sign |
POST | 簽署醫囑:驗證病程來源存在 → 寫入 progressSnapshot → 建立 Provenance.signature |
POST /MedicationRequest/{id}/$submit |
POST | 送 HIS:產生 outbound_request、設 hisTransmissionStatus=sent。冪等,見 06 |
POST /Composition/{id}/$sign |
POST | 文件簽署,同上簽章流程 |
POST /Composition/{id}/$receive |
POST | 交班接收:建立 Provenance + 轉移 Task.owner |
$roster 回應
{
"resourceType": "Bundle", "type": "searchset",
"entry": [{
"resource": { "resourceType": "Patient", "id": "abc" },
"search": { "mode": "match" }
}, {
"resource": { "resourceType": "CareTeam", "id": "ct-1" },
"search": { "mode": "include" }
}]
}
作用中醫囑筆數不放進 Patient(那不是 Patient 的屬性),改由客戶端對 MedicationRequest?patient=…&status=active&_summary=count 取得,或由 $roster 在 Bundle.entry.extension[activeOrderCount] 帶出。
3.9 快取
- 所有
read回應帶ETag與Last-Modified,支援If-None-Match→304。 - B 目錄的
ValueSet/$expand回應帶Cache-Control: max-age=3600,並在ValueSet.version帶目錄版本(原型的catalogVersion,例:20260831-b1)。 - 病人臨床資料一律
Cache-Control: no-store。