05 · 錯誤、安全與稽核
5.1 OperationOutcome
所有非 2xx 回應的 body 一律是 OperationOutcome,且每個 issue 必須齊備四項:
| 元素 | 必填 | 內容 |
|---|---|---|
severity |
✓ | fatal / error / warning / information |
code |
✓ | R4 標準 issue-type(business-rule、conflict、required、value、forbidden、invariant…) |
details.coding |
✓ | 本院錯誤碼,system = http://icu.emr.local/CodeSystem/api-error |
diagnostics |
✓ | 給臨床使用者看的繁中訊息,可直接顯示在畫面 |
expression |
條件 | 欄位層級錯誤時必填,FHIRPath 路徑 |
diagnostics 是使用者可見文字,不是給工程師的堆疊訊息。原型的訊息文字(例如「需逐項人工確認五對(模擬),不能由單次掃碼推定」)可直接沿用,去掉「(模擬)」字樣。
{
"resourceType": "OperationOutcome",
"issue": [{
"severity": "error",
"code": "business-rule",
"details": {
"coding": [{
"system": "http://icu.emr.local/CodeSystem/api-error",
"code": "FIVE-RIGHTS-INCOMPLETE"
}]
},
"diagnostics": "需逐項人工確認五對,不能由單次掃碼推定。",
"expression": ["MedicationAdministration.extension('…/verificationChecklist')"]
}]
}
5.2 錯誤碼表
| 錯誤碼 | HTTP | issue.code |
規則 |
|---|---|---|---|
UNKNOWN-ACTOR |
401 | login |
BR-AUTH-001 |
ROLE-DOCTOR-ONLY |
403 | forbidden |
BR-AUTH-002 |
ROLE-NURSE-ONLY |
403 | forbidden |
BR-AUTH-004 |
CONSULT-READ-ONLY |
403 | forbidden |
BR-AUTH-003 |
ACTOR-NOT-ASSIGNABLE |
422 | business-rule |
BR-AUTH-006 |
PATIENT-REQUIRED |
422 | required |
BR-CTX-001 |
PATIENT-OUT-OF-SCOPE |
403 | forbidden |
BR-CTX-002 |
ENCOUNTER-MISMATCH |
422 | invariant |
BR-CTX-003 |
ROSTER-NOT-CONFIRMED |
409 | business-rule |
BR-CTX-004 |
SOURCE-PATIENT-MISMATCH |
422 | invariant |
BR-CTX-005 |
READ-ONLY-SOURCE |
422 | business-rule |
§1.2.1, BR-RPT-001 |
PROGRESS-NOTE-REQUIRED |
422 | required |
BR-ORD-001 |
SIGN-OWN-DRAFT-ONLY |
403 | forbidden |
BR-ORD-002, BR-DOC-004 |
SUBMIT-STATE-INVALID |
409 | conflict |
BR-ORD-004 |
HIS-RESPONSE-INVALID |
422 | value |
BR-ORD-005 |
ORDER-IMMUTABLE-AFTER-SUBMIT |
409 | conflict |
BR-ORD-006 |
RESEND-BLOCKED-UNKNOWN |
409 | business-rule |
BR-ORD-007 |
CODE-REQUIRED |
422 | required |
BR-MED-001 |
ORDER-NOT-EXECUTABLE |
409 | business-rule |
BR-MED-002 |
FIVE-RIGHTS-INCOMPLETE |
422 | business-rule |
BR-MED-003 |
VARIANCE-REASON-REQUIRED |
422 | required |
BR-MED-004 |
DOSE-ALREADY-ADMINISTERED |
200 | duplicate |
BR-MED-005 |
TIME-OUT-OF-ORDER-PERIOD |
422 | invariant |
BR-MED-006 |
ACTUAL-VALUE-REQUIRED |
422 | required |
BR-MED-007 |
REVIEW-NOTE-REQUIRED |
422 | required |
BR-MED-008, BR-RPT-003 |
VALUE-INVALID |
422 | value |
BR-OBS-001 |
BP-COMPONENTS-REQUIRED |
422 | required |
BR-OBS-002 |
ZERO-AS-MISSING |
422 | business-rule |
BR-OBS-003 |
INFUSION-SOURCE-REQUIRED |
422 | required |
BR-OBS-004 |
REPORT-NOT-AVAILABLE |
409 | business-rule |
BR-RPT-002 |
SECTION-REQUIRED |
422 | required |
BR-DOC-001~003 |
SIGNED-IMMUTABLE |
409 | conflict |
BR-DOC-005 |
AMEND-REASON-REQUIRED |
422 | required |
BR-DOC-005, BR-HO-002 |
NO-COPYABLE-PROGRESS |
409 | business-rule |
BR-DOC-006 |
CATALOG-INACTIVE |
422 | business-rule |
BR-CAT-001, BR-DOC-009 |
CATALOG-CHANGED |
409 | conflict |
BR-CAT-003 |
TASK-NOT-OWNER |
403 | forbidden |
BR-TASK-001 |
EVIDENCE-REQUIRED |
422 | required |
BR-TASK-002 |
SOURCE-NOT-PROCESSED |
409 | business-rule |
BR-TASK-002 |
SUMMARY-REQUIRED |
422 | required |
BR-HO-001 |
HANDOVER-SUPERSEDED |
409 | conflict |
BR-HO-002, BR-HO-005 |
HANDOVER-ROLE-MISMATCH |
403 | forbidden |
BR-HO-003 |
HANDOVER-ALREADY-RECEIVED |
409 | duplicate |
BR-HO-006 |
ASSIGNEE-INVALID |
422 | value |
BR-CONS-001 |
CONSULT-NOT-ASSIGNEE |
403 | forbidden |
BR-CONS-002 |
CONSULT-ALREADY-ANSWERED |
409 | conflict |
BR-CONS-003 |
RECOMMENDATION-REQUIRED |
422 | required |
BR-CONS-004 |
TIMEZONE-REQUIRED |
422 | value |
BR-TIME-001 |
TIME-INVALID |
422 | value |
BR-TIME-002 |
HIS-UNAVAILABLE |
502 | transient |
見 06 |
HIS-TIMEOUT |
504 | timeout |
見 06 |
5.3 HTTP 狀態碼使用
| 碼 | 用於 |
|---|---|
200 |
讀取成功、update 成功、條件式建立命中既有資源 |
201 |
create 成功。必帶 Location 與 ETag |
304 |
If-None-Match 命中 |
400 |
語法錯誤(JSON 壞掉、參數不合法) |
401 |
未認證或權杖失效 |
403 |
已認證但無權(角色、病人範圍) |
404 |
資源不存在。不因無權而回 404,權限問題一律 403 |
409 |
版本衝突、狀態不允許 |
412 |
缺 If-Match |
422 |
資源結構合法但違反業務規則 |
429 |
節流 |
502 / 504 |
HIS 不可用/逾時 |
5.4 保存失敗的語義(與原型的重要差異)
原型的 store 在持久化失敗時仍把交易寫進記憶體,只顯示警告——那是 localStorage 原型的權宜做法。
正式 API 不得如此。 未持久化就不能回 2xx:
| 情況 | 原型 | 正式 API |
|---|---|---|
| 寫入儲存失敗 | 保留在記憶體 + 警示 | 500,不回 201,資源不存在 |
| 並行版本衝突 | 保留畫面內容 + 警示 | 409 + 目前版本,客戶端合併後重送 |
| HIS 逾時 | unknown 狀態 |
504,本地醫囑仍為 sent+hisTransmissionStatus=unknown(本地已持久化) |
客戶端保留使用者輸入是 UI 的責任,不是靠伺服器回假成功。
scenario(保存失敗/來源缺漏/HIS 逾時)是原型的情境切換開關,正式 API 沒有這個端點。
5.5 認證與授權
5.5.1 SMART on FHIR
採 OAuth 2.0 + SMART on FHIR user/ scopes(後端服務用 system/)。
存取權杖必須帶:
| Claim | 內容 |
|---|---|
sub |
使用者識別 |
fhirUser |
PractitionerRole/{id} — 決定角色與科別 |
shift |
班別代碼(決定「本班」時間窗與交班對象) |
scope |
見下表 |
5.5.2 Scope 對照
| 角色 | Scope |
|---|---|
| 主責醫師 | user/Patient.rs user/Encounter.rs user/MedicationRequest.cru user/ServiceRequest.cru user/Composition.cru user/Condition.cru user/Goal.cru user/CarePlan.cs user/Task.cru user/Provenance.cs user/Communication.cs user/DiagnosticReport.rs user/Observation.rs |
| 護理師 | user/Patient.rs user/Encounter.rs user/MedicationRequest.rs user/ServiceRequest.rs user/MedicationAdministration.cs user/Procedure.cs user/Observation.crs user/Composition.cru user/Task.cru user/Communication.cs user/DiagnosticReport.rs |
| 照會醫師 | user/Patient.rs user/Encounter.rs user/DiagnosticReport.rs user/Observation.rs user/Composition.rs user/ServiceRequest.rs + 僅限已指派照會的病人 |
| 整合服務 | system/MedicationRequest.u system/ServiceRequest.u system/DiagnosticReport.cu system/Observation.cu system/Specimen.cu system/Patient.cu system/Encounter.cu |
(c=create、r=read、u=update、d=delete、s=search)
Scope 是必要條件,不是充分條件。 通過 scope 後仍要跑 04 的全部規則——例如護理師有 MedicationAdministration.c,但 BR-MED-002/BR-MED-003 仍可能擋下。
5.5.3 病人範圍
除了 scope,伺服器對每個請求做 compartment 檢查:
- 從
fhirUser取得作用中的CareTeam參與關係。 - 照會醫師另外檢查是否有指派給他、且
status=active的照會ServiceRequest。 - 目標資源的
subject/patient不在集合內 →403 PATIENT-OUT-OF-SCOPE。
搜尋一律隱含加上病人範圍過濾,不是回 403 而是回較少的結果——避免透過錯誤碼探測病人是否存在。單一資源 read 才回 403。
5.6 稽核
AuditEvent vs Provenance
兩者都要,用途不同:
AuditEvent |
Provenance |
|
|---|---|---|
| 目的 | 誰在何時存取/異動了什麼(安全稽核) | 這筆資料怎麼來的(臨床溯源) |
| 產生者 | 伺服器自動,每個請求一筆 | 業務動作明確產生 |
| 臨床端可見 | 否(僅稽核人員) | 是(畫面顯示作者、時間、引用來源) |
| 對應原型 | state.audit[] |
病歷的引用來源、已閱/判讀、簽章 |
AuditEvent 必填
{
"resourceType": "AuditEvent",
"type": { "system": "http://terminology.hl7.org/CodeSystem/audit-event-type", "code": "rest" },
"subtype": [{ "system": "http://hl7.org/fhir/restful-interaction", "code": "create" }],
"action": "C",
"recorded": "2026-08-31T09:12:04+07:00",
"outcome": "0",
"agent": [{
"who": { "reference": "PractitionerRole/d1" },
"requestor": true,
"network": { "address": "10.2.3.4", "type": "2" }
}],
"source": { "observer": { "reference": "Device/emr-api" } },
"entity": [{
"what": { "reference": "MedicationAdministration/9a2" },
"type": { "code": "2" }
}]
}
- 讀取也要記錄。 病歷閱覽(
action=R)必須留軌跡。 agent.network.address對應規格ServiceRequestCancelled的ipAddress/computerName(取得=C,不可手打冒充)——由伺服器從連線取得,客戶端送的值忽略。- 失敗的請求同樣要記錄(
outcome非0),尤其是 403。
異動理由
audit_event.change_reason(S06 標為 M)→ 寫入時以 X-Change-Reason 標頭帶入,伺服器落到 AuditEvent.extension[changeReason]。診斷管理等需要理由的操作缺此標頭時回 422。
5.6.1 電子簽章驗證
Provenance.signature.data 的驗證結果(signature_hash 與驗證服務輸出,取得=C)由伺服器產生,透過 Provenance 的 extension[signatureValidation] 回傳:valid / invalid / unverified(憑證服務不可用時用 unverified,不預設 valid)。
5.7 傳輸與記錄
- 僅接受 TLS 1.2 以上。
- 存取權杖不得出現在 URL。
- 應用程式日誌不得記錄請求/回應 body(含臨床內容);只記錄方法、路徑樣板、狀態碼、
AuditEvent.id。 - 搜尋參數會含病人識別碼,日誌中的 query string 必須遮蔽。