fhir-developer-skill について
fhir-developer-skill は、FHIR R4 API を実装する開発者向けのリファレンスです。リソース検証、REST 操作、レスポンス規約を扱い、FastAPI のプロジェクト生成スクリプトも含みます。
主な機能
- 01FHIR R4 の必須フィールドと列挙値
- 02REST のステータスとレスポンス規約
- 03Coding と CodeableConcept の例
- 04FastAPI プロジェクト生成スクリプト
活用シーン
- 01リソース項目と列挙値の検証確認
- 02FHIR エラーレスポンスの実装
- 03FastAPI FHIR プロジェクトの生成
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ anthropics/ healthcare --skill fhir-developer-skillインストール後、Claude Code で/fhir-developer-skillを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/bundles.mdpagination.mdresource-examples.mdsmart-auth.mdscripts/setup_fhir_project.py詳細
FHIR 開発者向けスキル
クイックリファレンス
HTTP ステータスコード
| コード | 使用するタイミング |
|---|---|
200 OK | 読み取り、更新、検索が成功した場合 |
201 Created | 作成が成功した場合(Location ヘッダーを含めます) |
204 No Content | 削除が成功した場合 |
400 Bad Request | JSON の形式が不正、または resourceType が誤っている場合 |
401 Unauthorized | トークンがない、期限切れ、失効、または形式が不正な場合(RFC 6750) |
403 Forbidden | トークンは有効だが、スコープが不十分な場合 |
404 Not Found | リソースが存在しない場合 |
412 Precondition Failed | If-Match ETag が一致しない場合(400 ではありません!) |
422 Unprocessable Entity | 必須フィールドがない、無効な列挙値、ビジネスルール違反の場合 |
リソースごとの必須フィールド(FHIR R4)
| リソース | 必須フィールド | その他すべて |
|---|---|---|
| Patient | (なし) | すべて任意 |
| Observation | status、code | 任意 |
| Encounter | status、class | 任意(subject、period を含む) |
| Condition | subject | 任意(code、clinicalStatus を含む) |
| MedicationRequest | status、intent、medication[x]、subject | 任意 |
| Medication | (なし) | すべて任意 |
| Bundle | type | 任意 |
必須フィールドと任意フィールド(重要)
カーディナリティが「1」で始まるフィールドのみを必須として検証してください。
| カーディナリティ | 必須? |
|---|---|
0..1、0..* | いいえ |
1..1、1..* | はい |
よくある誤り:Encounter の subject または period を必須にすること。これらは 0..1(任意)です。
値セット(列挙値)
無効な列挙値には 422 Unprocessable Entity を返す必要があります。
Patient.gender
male | female | other | unknown
Observation.status
registered | preliminary | final | amended | corrected | cancelled | entered-in-error | unknown
Encounter.status
planned | arrived | triaged | in-progress | onleave | finished | cancelled | entered-in-error | unknown
Encounter.class(一般的なコード)
| コード | 表示名 | 用途 |
|---|---|---|
AMB | 外来診療 | 外来受診 |
IMP | 入院患者との診療 | 入院 |
EMER | 救急 | 救急部門 |
VR | バーチャル | 遠隔医療 |
Condition.clinicalStatus
active | recurrence | relapse | inactive | remission | resolved
Condition.verificationStatus
unconfirmed | provisional | differential | confirmed | refuted | entered-in-error
MedicationRequest.status
active | on-hold | cancelled | completed | entered-in-error | stopped | draft | unknown
MedicationRequest.intent
proposal | plan | order | original-order | reflex-order | filler-order | instance-order | option
Bundle.type
document | message | transaction | transaction-response | batch | batch-response | history | searchset | collection
検証パターン
Python/FastAPI:
from fastapi import FastAPI
from fastapi.responses import JSONResponse
app = FastAPI()
def operation_outcome(severity: str, code: str, diagnostics: str):
return {
"resourceType": "OperationOutcome",
"issue": [{"severity": severity, "code": code, "diagnostics": diagnostics}]
}
VALID_OBS_STATUS = {"registered", "preliminary", "final", "amended",
"corrected", "cancelled", "entered-in-error", "unknown"}
@app.post("/Observation", status_code=201)
async def create_observation(data: dict):
if not data.get("status"):
return JSONResponse(status_code=422, content=operation_outcome(
"error", "required", "Observation.status is required"
), media_type="application/fhir+json")
if data["status"] not in VALID_OBS_STATUS:
return JSONResponse(status_code=422, content=operation_outcome(
"error", "value", f"Invalid status '{data['status']}'"
), media_type="application/fhir+json")
# ... create resource
TypeScript/Express:
const VALID_OBS_STATUS = new Set(['registered', 'preliminary', 'final', 'amended',
'corrected', 'cancelled', 'entered-in-error', 'unknown']);
app.post('/Observation', (req, res) => {
if (!req.body.status) {
return res.status(422).contentType('application/fhir+json')
.json(operationOutcome('error', 'required', 'Observation.status is required'));
}
if (!VALID_OBS_STATUS.has(req.body.status)) {
return res.status(422).contentType('application/fhir+json')
.json(operationOutcome('error', 'value', `Invalid status '${req.body.status}'`));
}
// ... create resource
});
Pydantic v2 モデル(const=True ではなく Literal を使用):
from typing import Literal
from pydantic import BaseModel
class Patient(BaseModel):
resourceType: Literal["Patient"] = "Patient"
id: str | None = None
gender: Literal["male", "female", "other", "unknown"] | None = None
コーディングシステム(URL)
| システム | URL |
|---|---|
| LOINC | http://loinc.org |
| SNOMED CT | http://snomed.info/sct |
| RxNorm | http://www.nlm.nih.gov/research/umls/rxnorm |
| ICD-10 | http://hl7.org/fhir/sid/icd-10 |
| v3-ActCode | http://terminology.hl7.org/CodeSystem/v3-ActCode |
| Observation Category | http://terminology.hl7.org/CodeSystem/observation-category |
| Condition Clinical | http://terminology.hl7.org/CodeSystem/condition-clinical |
| Condition Ver Status | http://terminology.hl7.org/CodeSystem/condition-ver-status |
一般的な LOINC コード(バイタルサイン)
| コード | 説明 |
|---|---|
8867-4 | 心拍数 |
8480-6 | 収縮期血圧 |
8462-4 | 拡張期血圧 |
8310-5 | 体温 |
2708-6 | 酸素飽和度(SpO2) |
データ型のパターン
Coding(直接)と CodeableConcept(ラップ形式)
Coding - Encounter.class で使用:
{"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", "code": "AMB"}
CodeableConcept - Observation.code、Condition.code で使用:
{"coding": [{"system": "http://loinc.org", "code": "8480-6"}], "text": "Systolic BP"}
参考
{"reference": "Patient/123", "display": "John Smith"}
識別子
{"system": "http://hospital.example.org/mrn", "value": "12345"}
よくある間違い
| 間違い | 正しい方法 |
|---|---|
Encounter で subject または period を必須にする | どちらも 0..1(任意)です。必須なのは status と class のみです |
Encounter.class に CodeableConcept を使用する | class は Coding を直接使用します:{"system": "...", "code": "AMB"} |
| ETag の不一致に対して 400 を返す | If-Match の失敗には 412 Precondition Failed を使用します |
| 無効な enum 値に対して 400 を返す | 検証エラーには 422 Unprocessable Entity を使用します |
| Content-Type ヘッダーを忘れる | 常に Content-Type: application/fhir+json を設定します |
| 作成時に Location ヘッダーがない | 201 Created とともに Location: /Patient/{id} を返します |
リソース構造
すべてのリソースの完全な JSON の例については、references/resource-examples.md を参照してください。
エラーレスポンスのクイックリファレンス:
{
"resourceType": "OperationOutcome",
"issue": [{"severity": "error", "code": "not-found", "diagnostics": "Patient/123 not found"}]
}
RESTful エンドポイント
POST /[ResourceType] # Create (returns 201 + Location header)
GET /[ResourceType]/[id] # Read
PUT /[ResourceType]/[id] # Update
DELETE /[ResourceType]/[id] # Delete (returns 204)
GET /[ResourceType]?param=value # Search (returns Bundle)
GET /metadata # CapabilityStatement
POST / # Bundle transaction/batch
条件付き操作
If-Match(楽観的ロック):
- クライアントが送信:
If-Match: W/"1" - 不一致の場合は
412 Precondition Failedを返します
If-None-Exist(条件付き作成):
- クライアントが送信:
If-None-Exist: identifier=http://mrn|12345 - 一致するものが存在する場合:既存のものを返します(200)
- 一致するものがない場合:新規作成します(201)
参考ファイル
詳しいガイダンスについては、以下を参照してください。
- リソースの例:Patient、Observation、Encounter、Condition、MedicationRequest、OperationOutcome、CapabilityStatement の完全な JSON 構造
- SMART on FHIR 認可:OAuth フロー、スコープ構文(v1/v2)、バックエンドサービス、スコープの適用
- ページネーション:検索結果のページネーション、
_count/_offsetパラメーター、リンク関係 - Bundle 操作:トランザクションとバッチのセマンティクス、アトミック性、処理順序
実装チェックリスト
- すべてのレスポンスに
Content-Type: application/fhir+jsonを設定します - リソースに
meta.versionIdとmeta.lastUpdatedを返します - 作成時に
Locationヘッダーを返します:/Patient/{id} ETagヘッダーを返します:W/"{versionId}"- すべてのエラーレスポンスに OperationOutcome を使用します
- 必須フィールドを検証します → 欠落している場合は 422
- enum 値を検証します → 無効な場合は 422
- 検索では
type: "searchset"を含む Bundle を返します
クイックスタートスクリプト
正しい Pydantic v2 のパターンを使用して、新しい FHIR API プロジェクトをスキャフォールディングするには:
python scripts/setup_fhir_project.py my_fhir_api
正しいモデル、OperationOutcome ヘルパー、Patient CRUD エンドポイントを備えた FastAPI プロジェクトを作成します。