TL;DR
HL7 FHIR(Fast Healthcare Interoperability Resources)は、RESTful APIとJSON/XMLレスポンスを使用する、医療データ交換のための最新標準です。患者、観察結果、薬剤などに関する標準化されたリソースを提供し、OAuth 2.0認証とアプリ統合のためのSMART on FHIRを備えています。このガイドでは、FHIRのアーキテクチャ、リソースタイプ、検索パラメーター、認証、および本番環境での実装戦略について説明します。
はじめに
医療データの断片化により、米国の医療システムは年間300億ドルのコストを負担しています。医療アプリケーションを構築する開発者にとって、HL7 FHIR APIの統合は選択肢ではなく、CMSによって義務付けられ、Epic、Cerner、およびすべての主要な電子カルテ(EHR)ベンダーに採用されている業界標準です。
現実を見てみましょう。FHIR対応アプリを使用する医療提供者は、ケア連携時間を40%短縮し、FAXベースの記録要求を85%削減しています。堅牢なFHIR API統合により、EHR、患者ポータル、ケア連携プラットフォーム間でシームレスなデータ交換が可能になります。
このガイドでは、HL7 FHIR APIの統合プロセス全体を説明します。FHIRのアーキテクチャ、リソースタイプ、検索パラメーター、OAuth 2.0認証、SMART on FHIR統合、および本番デプロイ戦略を学びます。最終的には、本番環境に対応したFHIR統合が完了するでしょう。
HL7 FHIRとは?
FHIR (Fast Healthcare Interoperability Resources) は、医療情報を電子的に交換するための標準フレームワークです。Health Level Seven International (HL7) によって開発されたFHIRは、RESTful API、JSON、XML、OAuth 2.0などの最新のウェブ技術を使用しています。

FHIRリソースタイプ
FHIRは140以上のリソースタイプを定義しています。コアリソースは以下の通りです。
| リソース | 目的 | 一般的なユースケース |
|---|---|---|
| Patient | デモグラフィック情報 | 患者検索、登録 |
| Practitioner | 医療従事者情報 | ディレクトリ、スケジューリング |
| Encounter | 受診/入院 | ケアエピソード、請求 |
| Observation | 臨床データ | バイタル、検査結果、評価 |
| Condition | 問題/診断 | 問題リスト、ケアプランニング |
| MedicationRequest | 処方箋 | e-処方、薬剤履歴 |
| AllergyIntolerance | アレルギー | 安全確認、アラート |
| Immunization | 予防接種 | 予防接種記録 |
| DiagnosticReport | 検査/画像診断レポート | 結果配信 |
| DocumentReference | 臨床文書 | CCD、退院サマリー |
FHIR APIアーキテクチャ
FHIRはRESTful API構造を使用します。
https://fhir-server.com/fhir/{resourceType}/{id}
FHIRバージョン比較
| バージョン | ステータス | ユースケース |
|---|---|---|
| R4 (4.0.1) | 現在のSTU | 本番環境での実装 |
| R4B (4.3) | 試用実装 | 早期導入者 |
| R5 (5.0.0) | ドラフトSTU | 将来の実装 |
| DSTU2 | 非推奨 | レガシーシステムのみ |
CMSは、認定EHRに対し、患者アクセスおよび医療提供者アクセスAPIでFHIR R4をサポートすることを要求しています。
開始する:FHIRサーバーへのアクセス
ステップ1:FHIRサーバーの選択
FHIRサーバーデプロイのオプション:
| サーバー | タイプ | コスト | 最適な用途 |
|---|---|---|---|
| Azure API for FHIR | マネージド | 従量課金制 | エンタープライズ、Azureユーザー |
| AWS HealthLake | マネージド | 従量課金制 | AWS環境 |
| Google Cloud Healthcare API | マネージド | 従量課金制 | GCP環境 |
| HAPI FHIR | オープンソース | セルフホスト | カスタムデプロイ |
| Epic FHIR Server | 商用 | Epic顧客 | Epic EHRとの統合 |
| Cerner Ignite FHIR | 商用 | Cerner顧客 | Cerner EHRとの統合 |
ステップ2:サーバー認証情報の取得
クラウドFHIRサービスの場合:
# Azure API for FHIR
# 1. Azure PortalでFHIRサービスを作成
# 2. 認証を構成 (OAuth 2.0 または AAD)
# 3. FHIRエンドポイントを取得: https://{service-name}.azurehealthcareapis.com
# 4. OAuth用のクライアントアプリを登録
# AWS HealthLake
# 1. AWSコンソールでデータストアを作成
# 2. IAMロールを構成
# 3. エンドポイントを取得: https://healthlake.{region}.amazonaws.com
ステップ3:FHIR RESTful操作の理解
FHIRは標準的なHTTPメソッドをサポートしています。
| 操作 | HTTPメソッド | エンドポイント | 説明 |
|---|---|---|---|
| Read | GET | /{resourceType}/{id} |
特定のリソースを取得 |
| Search | GET | /{resourceType}?param=value |
リソースを検索 |
| Create | POST | /{resourceType} |
新しいリソースを作成 |
| Update | PUT | /{resourceType}/{id} |
リソースを置き換え |
| Patch | PATCH | /{resourceType}/{id} |
部分的な更新 |
| Delete | DELETE | /{resourceType}/{id} |
リソースを削除 |
| History | GET | /{resourceType}/{id}/_history |
リソースのバージョン履歴 |
ステップ4:最初のFHIR呼び出しを行う
接続をテストします。
curl -X GET "https://fhir-server.com/fhir/metadata" \
-H "Accept: application/fhir+json" \
-H "Authorization: Bearer {token}"
期待されるレスポンス:
{
"resourceType": "CapabilityStatement",
"status": "active",
"date": "2026-03-25",
"fhirVersion": "4.0.1",
"rest": [{
"mode": "server",
"resource": [
{ "type": "Patient" },
{ "type": "Observation" },
{ "type": "Condition" }
]
}]
}
コアFHIR操作
患者リソースの読み取り
IDで患者をフェッチします。
const FHIR_BASE_URL = process.env.FHIR_BASE_URL;
const FHIR_TOKEN = process.env.FHIR_TOKEN;
const fhirRequest = async (endpoint, options = {}) => {
const response = await fetch(`${FHIR_BASE_URL}/fhir${endpoint}`, {
...options,
headers: {
'Accept': 'application/fhir+json',
'Authorization': `Bearer ${FHIR_TOKEN}`,
...options.headers
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(`FHIR Error: ${error.issue?.[0]?.details?.text || response.statusText}`);
}
return response.json();
};
// IDで患者を読み込む
const getPatient = async (patientId) => {
const patient = await fhirRequest(`/Patient/${patientId}`);
return patient;
};
// 使用例
const patient = await getPatient('12345');
console.log(`Patient: ${patient.name[0].given[0]} ${patient.name[0].family}`);
console.log(`DOB: ${patient.birthDate}`);
console.log(`Gender: ${patient.gender}`);
患者リソースの構造
{
"resourceType": "Patient",
"id": "12345",
"identifier": [
{
"use": "usual",
"type": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/v2-0203",
"code": "MR"
}]
},
"system": "http://hospital.example.org",
"value": "MRN123456"
}
],
"name": [
{
"use": "official",
"family": "Smith",
"given": ["John", "Michael"]
}
],
"telecom": [
{
"system": "phone",
"value": "555-123-4567",
"use": "home"
},
{
"system": "email",
"value": "john.smith@email.com"
}
],
"gender": "male",
"birthDate": "1985-06-15",
"address": [
{
"use": "home",
"line": ["123 Main Street"],
"city": "Springfield",
"state": "IL",
"postalCode": "62701"
}
]
}
リソースの検索
名前で患者を検索します。
const searchPatients = async (searchParams) => {
const query = new URLSearchParams();
// 検索パラメーターを追加
if (searchParams.name) {
query.append('name', searchParams.name);
}
if (searchParams.birthDate) {
query.append('birthdate', searchParams.birthDate);
}
if (searchParams.identifier) {
query.append('identifier', searchParams.identifier);
}
if (searchParams.gender) {
query.append('gender', searchParams.gender);
}
const response = await fhirRequest(`/Patient?${query.toString()}`);
return response;
};
// 使用例
const results = await searchPatients({ name: 'Smith', birthDate: '1985-06-15' });
console.log(`Found ${results.total} patients`);
results.entry.forEach(entry => {
const patient = entry.resource;
console.log(`${patient.name[0].family}, ${patient.name[0].given[0]}`);
});
共通の検索パラメーター
| リソース | 検索パラメーター | 例 |
|---|---|---|
| Patient | name, birthdate, identifier, gender, phone, email | ?name=Smith&birthdate=1985-06-15 |
| Observation | patient, code, date, category, status | ?patient=123&code=8480-6&date=ge2026-01-01 |
| Condition | patient, clinical-status, category, onset-date | ?patient=123&clinical-status=active |
| MedicationRequest | patient, status, intent, medication | ?patient=123&status=active |
| Encounter | patient, date, status, class | ?patient=123&date=ge2026-01-01 |
| DiagnosticReport | patient, category, date, status | ?patient=123&category=laboratory |
検索修飾子
| 修飾子 | 説明 | 例 |
|---|---|---|
:exact |
完全一致 | name:exact=Smith |
:contains |
部分一致 | name:contains=smi |
:missing |
値の有無 | phone:missing=true |
: (プレフィックス) |
プレフィックス演算子 | birthdate=ge1980-01-01 |
日付と数値の検索プレフィックス
| プレフィックス | 意味 | 例 |
|---|---|---|
eq |
等しい | birthdate=eq1985-06-15 |
ne |
等しくない | birthdate=ne1985-06-15 |
gt |
より大きい | birthdate=gt1980-01-01 |
lt |
より小さい | birthdate=lt1990-01-01 |
ge |
以上 | birthdate=ge1980-01-01 |
le |
以下 | birthdate=le1990-01-01 |
sa |
より後 | date=sa2026-01-01 |
eb |
より前 | date=eb2026-12-31 |
臨床データの操作
Observation(バイタルサイン)の作成
バイタルサインを記録します。
const createObservation = async (observationData) => {
const observation = {
resourceType: 'Observation',
status: 'final',
category: [
{
coding: [{
system: 'http://terminology.hl7.org/CodeSystem/observation-category',
code: 'vital-signs'
}]
}
],
code: {
coding: [{
system: 'http://loinc.org',
code: observationData.code, // 例: 収縮期血圧の場合は '8480-6'
display: observationData.display
}]
},
subject: {
reference: `Patient/${observationData.patientId}`
},
effectiveDateTime: observationData.effectiveDate || new Date().toISOString(),
valueQuantity: {
value: observationData.value,
unit: observationData.unit,
system: 'http://unitsofmeasure.org',
code: observationData.ucumCode
},
performer: [
{
reference: `Practitioner/${observationData.performerId}`
}
]
};
const response = await fhirRequest('/Observation', {
method: 'POST',
body: JSON.stringify(observation)
});
return response;
};
// 使用例 - 血圧の記録
const systolicBP = await createObservation({
patientId: '12345',
code: '8480-6',
display: 'Systolic blood pressure',
value: 120,
unit: 'mmHg',
ucumCode: 'mm[Hg]',
performerId: '67890'
});
console.log(`Observation created: ${systolicBP.id}`);
一般的なLOINCコード
| コード | 表示 | カテゴリ |
|---|---|---|
| 8480-6 | 収縮期血圧 | バイタルサイン |
| 8462-4 | 拡張期血圧 | バイタルサイン |
| 8867-4 | 心拍数 | バイタルサイン |
| 8310-5 | 体温 | バイタルサイン |
| 8302-2 | 身長 | バイタルサイン |
| 29463-7 | 体重 | バイタルサイン |
| 8871-5 | 呼吸数 | バイタルサイン |
| 2339-0 | 血糖 [質量/容積] | 検査 |
| 4548-4 | ヘモグロビンA1c | 検査 |
| 2093-3 | コレステロール [質量/容積] | 検査 |
Condition(問題リスト項目)の作成
問題リストに診断を追加します。
const createCondition = async (conditionData) => {
const condition = {
resourceType: 'Condition',
clinicalStatus: {
coding: [{
system: 'http://terminology.hl7.org/CodeSystem/condition-clinical',
code: conditionData.status || 'active'
}]
},
verificationStatus: {
coding: [{
system: 'http://terminology.hl7.org/CodeSystem/condition-ver-status',
code: 'confirmed'
}]
},
category: [
{
coding: [{
system: 'http://terminology.hl7.org/CodeSystem/condition-category',
code: conditionData.category || 'problem-list-item'
}]
}
],
code: {
coding: [{
system: 'http://snomed.info/sct',
code: conditionData.sctCode,
display: conditionData.display
}]
},
subject: {
reference: `Patient/${conditionData.patientId}`
},
onsetDateTime: conditionData.onsetDate,
recordedDate: new Date().toISOString()
};
const response = await fhirRequest('/Condition', {
method: 'POST',
body: JSON.stringify(condition)
});
return response;
};
// 使用例 - 問題リストに糖尿病を追加
const diabetes = await createCondition({
patientId: '12345',
sctCode: '44054006',
display: 'Type 2 Diabetes Mellitus',
status: 'active',
category: 'problem-list-item',
onsetDate: '2024-01-15'
});
一般的なSNOMED CTコード
| コード | 表示 | カテゴリ |
|---|---|---|
| 44054006 | 2型糖尿病 | 問題 |
| 38341003 | 高血圧 | 問題 |
| 195967001 | 喘息 | 問題 |
| 13645005 | 慢性閉塞性肺疾患 | 問題 |
| 35489007 | うつ病性障害 | 問題 |
| 22298006 | 心筋梗塞 | 問題 |
| 26929004 | アルツハイマー病 | 問題 |
| 396275006 | 変形性関節症 | 問題 |
患者の薬剤情報の取得
アクティブな薬剤依頼を取得します。
const getPatientMedications = async (patientId) => {
const response = await fhirRequest(
`/MedicationRequest?patient=${patientId}&status=active`
);
return response;
};
// 使用例
const medications = await getPatientMedications('12345');
medications.entry?.forEach(entry => {
const med = entry.resource;
console.log(`${med.medicationCodeableConcept.coding[0].display}`);
console.log(` Dose: ${med.dosageInstruction[0]?.text}`);
console.log(` Status: ${med.status}`);
});
検査結果の取得
診断レポートと観察結果を取得します。
const getPatientLabResults = async (patientId, options = {}) => {
const params = new URLSearchParams({
patient: patientId,
category: options.category || 'laboratory'
});
if (options.dateFrom) {
params.append('date', `ge${options.dateFrom}`);
}
const response = await fhirRequest(`/DiagnosticReport?${params.toString()}`);
return response;
};
// 特定の検査値(例: HbA1c)を取得
const getLabValue = async (patientId, loincCode) => {
const params = new URLSearchParams({
patient: patientId,
code: loincCode
});
const response = await fhirRequest(`/Observation?${params.toString()}`);
return response;
};
// 使用例 - HbA1cの結果を取得
const hba1c = await getLabValue('12345', '4548-4');
hba1c.entry?.forEach(entry => {
const obs = entry.resource;
console.log(`HbA1c: ${obs.valueQuantity.value} ${obs.valueQuantity.unit}`);
console.log(`Date: ${obs.effectiveDateTime}`);
});
OAuth 2.0とSMART on FHIR
FHIR認証の理解
FHIRサーバーはOpenID Connect付きのOAuth 2.0を使用します。
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ クライアント │───▶│ 認証 │───▶│ FHIR │
│ (アプリ) │ │ サーバー │ │ サーバー │
└─────────────┘ └─────────────┘ └─────────────┘
│ │ │
│ 1. 認証リクエスト │ │
│───────────────────▶│ │
│ │ │
│ 2. ユーザーログイン │ │
│◀───────────────────│ │
│ │ │
│ 3. 認証コード │ │
│───────────────────▶│ │
│ │ │
│ 4. トークンリクエスト │ │
│───────────────────▶│ │
│ │ 5. トークン + ID │
│◀───────────────────│ │
│ │ │
│ 6. APIリクエスト │ │
│────────────────────────────────────────▶│
│ │ │
│ 7. FHIRデータ │ │
│◀────────────────────────────────────────│
SMART on FHIRアプリの起動
SMARTアプリの起動を実装します。
const crypto = require('crypto');
class SMARTClient {
constructor(config) {
this.clientId = config.clientId;
this.redirectUri = config.redirectUri;
this.issuer = config.issuer; // FHIRサーバーのURL
this.scopes = config.scopes;
}
generatePKCE() {
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto
.createHash('sha256')
.update(codeVerifier)
.digest('base64url');
return { codeVerifier, codeChallenge };
}
buildAuthUrl(state, patientId = null) {
const { codeVerifier, codeChallenge } = this.generatePKCE();
// トークン交換のためにcodeVerifierを保存
this.codeVerifier = codeVerifier;
const params = new URLSearchParams({
response_type: 'code',
client_id: this.clientId,
redirect_uri: this.redirectUri,
scope: this.scopes.join(' '),
state: state,
code_challenge: codeChallenge,
code_challenge_method: 'S256',
aud: this.issuer,
launch: patientId ? `patient-${patientId}` : null
});
return `${this.issuer}/authorize?${params.toString()}`;
}
async exchangeCodeForToken(code) {
const response = await fetch(`${this.issuer}/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
redirect_uri: this.redirectUri,
client_id: this.clientId,
code_verifier: this.codeVerifier
})
});
const data = await response.json();
return {
accessToken: data.access_token,
refreshToken: data.refresh_token,
expiresIn: data.expires_in,
patientId: data.patient,
encounterId: data.encounter
};
}
}
// 使用例
const smartClient = new SMARTClient({
clientId: 'my-app-client-id',
redirectUri: 'https://myapp.com/callback',
issuer: 'https://fhir.epic.com',
scopes: [
'openid',
'profile',
'patient/Patient.read',
'patient/Observation.read',
'patient/Condition.read',
'patient/MedicationRequest.read',
'offline_access'
]
});
// ユーザーを認証URLにリダイレクト
const state = crypto.randomBytes(16).toString('hex');
const authUrl = smartClient.buildAuthUrl(state);
console.log(`Redirect to: ${authUrl}`);
必須SMARTスコープ
| スコープ | パーミッション | ユースケース |
|---|---|---|
openid |
OIDC認証 | すべてのアプリに必須 |
profile |
ユーザープロファイル情報 | 医療従事者ディレクトリ |
patient/Patient.read |
患者デモグラフィック情報の読み取り | 患者検索 |
patient/Observation.read |
観察結果の読み取り | バイタル、検査 |
patient/Condition.read |
状態の読み取り | 問題リスト |
patient/MedicationRequest.read |
薬剤情報の読み取り | 薬剤履歴 |
patient/*.read |
すべての患者リソースの読み取り | 患者データの全体像 |
user/*.read |
アクセス可能なすべてのリソースの読み取り | 医療従事者ビュー |
offline_access |
リフレッシュトークン | 長時間セッション |
認証済みFHIRリクエストの作成
class FHIRClient {
constructor(accessToken, fhirBaseUrl) {
this.accessToken = accessToken;
this.baseUrl = fhirBaseUrl;
}
async request(endpoint, options = {}) {
const response = await fetch(`${this.baseUrl}/fhir${endpoint}`, {
...options,
headers: {
'Accept': 'application/fhir+json',
'Authorization': `Bearer ${this.accessToken}`,
...options.headers
}
});
if (!response.ok) {
const error = await response.json();
throw new Error(`FHIR Error: ${error.issue?.[0]?.details?.text}`);
}
return response.json();
}
async getPatient(patientId) {
return this.request(`/Patient/${patientId}`);
}
async searchPatients(params) {
const query = new URLSearchParams(params);
return this.request(`/Patient?${query.toString()}`);
}
}
// OAuthコールバック後の使用例
const fhirClient = new FHIRClient(tokens.accessToken, 'https://fhir.epic.com');
const patient = await fhirClient.getPatient(tokens.patientId);
バッチおよびトランザクション操作
バッチリクエスト
複数の独立したリクエストを実行します。
const batchRequest = async (requests) => {
const bundle = {
resourceType: 'Bundle',
type: 'batch',
entry: requests.map(req => ({
resource: req.resource,
request: {
method: req.method,
url: req.url
}
}))
};
const response = await fhirRequest('', {
method: 'POST',
body: JSON.stringify(bundle)
});
return response;
};
// 使用例 - 複数のリソースをフェッチ
const bundle = await batchRequest([
{ method: 'GET', url: 'Patient/12345' },
{ method: 'GET', url: 'Patient/12345/Observation?category=vital-signs' },
{ method: 'GET', url: 'Patient/12345/Condition?clinical-status=active' }
]);
bundle.entry.forEach((entry, index) => {
console.log(`Response ${index}: ${entry.response.status}`);
console.log(entry.resource);
});
トランザクションリクエスト
複数のリクエストをアトミックな単位として実行します。
const transactionRequest = async (requests) => {
const bundle = {
resourceType: 'Bundle',
type: 'transaction',
entry: requests.map(req => ({
resource: req.resource,
request: {
method: req.method,
url: req.url
}
}))
};
const response = await fhirRequest('', {
method: 'POST',
body: JSON.stringify(bundle)
});
return response;
};
// 使用例 - 患者と関連リソースの作成
const transaction = await transactionRequest([
{
method: 'POST',
url: 'Patient',
resource: {
resourceType: 'Patient',
name: [{ family: 'Doe', given: ['Jane'] }],
gender: 'female',
birthDate: '1990-01-01'
}
},
{
method: 'POST',
url: 'Condition',
resource: {
resourceType: 'Condition',
clinicalStatus: { coding: [{ code: 'active' }] },
code: { coding: [{ system: 'http://snomed.info/sct', code: '38341003' }] },
subject: { reference: 'Patient/-1' } // 最初のリソースへの参照
}
}
]);
サブスクリプションとWebhook
FHIRサブスクリプション (R4B+)
リソースの変更をサブスクライブします。
const createSubscription = async (subscriptionData) => {
const subscription = {
resourceType: 'Subscription',
status: 'requested',
criteria: subscriptionData.criteria,
reason: subscriptionData.reason,
channel: {
type: 'rest-hook',
endpoint: subscriptionData.endpoint,
payload: 'application/fhir+json'
}
};
const response = await fhirRequest('/Subscription', {
method: 'POST',
body: JSON.stringify(subscription)
});
return response;
};
// 使用例 - 新しい検査結果をサブスクライブ
const subscription = await createSubscription({
criteria: 'DiagnosticReport?category=laboratory&patient=12345',
reason: 'Monitor patient lab results',
endpoint: 'https://myapp.com/webhooks/fhir'
});
FHIR Webhookの処理
const express = require('express');
const app = express();
app.post('/webhooks/fhir', express.json({ type: 'application/fhir+json' }), async (req, res) => {
const notification = req.body;
// サブスクリプション参照を検証
if (notification.subscription !== expectedSubscription) {
return res.status(401).send('Unauthorized');
}
// 通知を処理
if (notification.event?.resourceType === 'DiagnosticReport') {
const reportId = notification.event.resourceId;
const report = await fhirRequest(`/DiagnosticReport/${reportId}`);
// 新しい検査結果を処理
await processLabResult(report);
}
res.status(200).send('OK');
});
一般的な問題のトラブルシューティング
問題: 401 Unauthorized
症状: 「Unauthorized」または「Invalid token」エラーが発生する。
解決策:
- トークンの有効期限が切れていないか確認する
- トークンのスコープが要求されたリソースを含んでいるか確認する
Authorization: Bearer {token}ヘッダーが存在することを確認する- FHIRサーバーのURLがトークンのオーディエンスと一致しているか確認する
問題: 403 Forbidden
症状: トークンは有効だがアクセスが拒否される。
解決策:
- ユーザーが要求されたリソースに対する権限を持っているか確認する
- 患者コンテキストが一致しているか確認する(患者スコープのトークンの場合)
- SMARTスコープが要求された操作を含んでいるか確認する
- リソースレベルのアクセス制御を確認する
問題: 404 Not Found
症状: リソースが存在しない、またはエンドポイントが間違っている。
解決策:
- リソースIDが正しいか確認する
- FHIRベースURLが正しいか確認する
- リソースタイプがサーバーによってサポートされているか確認する
- バージョン固有のエンドポイント(R4とR4Bなど)を確認する
問題: 422 Unprocessable Entity
症状: 作成/更新時に検証エラーが発生する。
解決策:
// 検証エラーを解析
const error = await response.json();
error.issue?.forEach(issue => {
console.error(`Severity: ${issue.severity}`);
console.error(`Location: ${issue.expression?.join('.')}`);
console.error(`Message: ${issue.details?.text}`);
});
一般的な原因:
- 必須フィールドの欠落
- 無効なコードシステム値
- 不正確な参照形式
- 日付形式の問題
本番デプロイチェックリスト
本番稼働前に:
- [ ] SMART on FHIRを使用したOAuth 2.0を構成する
- [ ] トークンリフレッシュロジックを実装する
- [ ] 適切なエラー処理を設定する
- [ ] 包括的なロギングを追加する(ログにPHIを含めない)
- [ ] レート制限を実装する
- [ ] 指数関数的バックオフを用いたリトライロジックを構成する
- [ ] 複数のEHRベンダーでテストする
- [ ] FHIRバリデーターで検証する
- [ ] すべてのFHIR操作を文書化する
- [ ] 監視とアラートを設定する
- [ ] 一般的な問題に対するランブックを作成する
FHIR検証
const { FhirValidator } = require('fhir-validator');
const validator = new FhirValidator('4.0.1');
const validateResource = async (resource) => {
const validationResult = await validator.validate(resource);
if (!validationResult.valid) {
validationResult.issues.forEach(issue => {
console.error(`Validation Error: ${issue.message}`);
console.error(`Location: ${issue.path}`);
});
throw new Error('Resource validation failed');
}
return true;
};
// 作成/更新前の使用例
await validateResource(patientResource);
実世界のユースケース
患者ポータル統合
医療システムが患者ポータルを構築しました。
- 課題: 患者が複数の医療提供者からの記録にアクセスできなかった
- 解決策: EpicとCerner統合を備えたSMART on FHIRアプリ
- 結果: 患者利用率80%、記録要求50%削減
主要な実装:
- 患者向けのSMARTアプリ起動
- Patient、Observation、Condition、MedicationRequestへの読み取り専用アクセス
- 永続的なセッションのためのリフレッシュトークン
- モバイル対応UI
臨床意思決定支援
ケア管理プラットフォームがCDSを追加しました。
- 課題: 医療提供者が予防ケアの機会を見逃していた
- 解決策: ケアギャップを検出するためのリアルタイムFHIRクエリ
- 結果: HEDISスコアが25%改善
主要な実装:
- 医療従事者向けのSMARTアプリ
- Patient、Condition、Observation、Immunizationをクエリ
- ガイドラインに基づいたケアギャップの計算
- EHRワークフロー内のインライン推奨事項
集団健康分析
保険会社が集団健康ダッシュボードを構築しました。
- 課題: 医療提供者ネットワーク全体のデータが不完全だった
- 解決策: 分析のためのFHIRバルクデータエクスポート
- 結果: 360度の患者ビュー、PMPMコストの削減
主要な実装:
- FHIRバルクデータアクセス($export)
- データウェアハウスへの毎晩のエクスポート
- リスク層別化モデル
- ケアマネージャーへのアラート
結論
HL7 FHIRは、現代の医療相互運用性の基盤を提供します。主なポイント:
- FHIR R4は、医療API統合の現在の標準です
- SMART on FHIRは、セキュアなOAuth 2.0認証を可能にします
- リソースタイプは、患者、観察結果、状態、薬剤データを標準化します
- 検索パラメーターは、柔軟なクエリを可能にします
- バッチおよびトランザクション操作は、複雑なワークフローをサポートします
- Apidogは、FHIR APIのテストとドキュメント化を効率化します
FAQ
HL7 FHIRは何のために使われますか?
FHIRは、電子カルテ、患者ポータル、モバイルアプリ、その他のヘルスITシステム間で医療データを標準化された方法で交換することを可能にします。一般的なユースケースには、患者アクセスアプリ、臨床意思決定支援、集団健康、ケア連携などがあります。
FHIRを始めるにはどうすればよいですか?
まず、公開されているFHIRサーバー(HAPI FHIRテストサーバーなど)にアクセスするか、クラウドFHIRサービス(Azure API for FHIR、AWS HealthLakeなど)を設定します。リソースの読み取りや検索パラメーターの使用を練習してください。
HL7 v2とFHIRの違いは何ですか?
HL7 v2は、イベント駆動型のデータ交換のためにパイプ区切りのメッセージ(ADT、ORM、ORU)を使用します。FHIRは、リソースベースのアクセスにJSON/XMLを使用したRESTful APIを使用します。FHIRは実装が容易で、最新のWeb/モバイルアプリにより適しています。
FHIRはHIPAAに準拠していますか?
FHIR自体はデータ形式の標準です。HIPAA準拠は、実装に依存します。暗号化、認証、アクセス制御、監査ログなどが必要です。セキュアなアクセスには、SMART on FHIRを使用したOAuth 2.0を使用してください。
SMARTスコープとは何ですか?
SMARTスコープは、FHIRリソースに対するきめ細やかなアクセス許可(例:patient/Observation.read、user/*.read)を定義します。アプリが必要とするスコープのみを要求してください。
FHIRでリソースを検索するにはどうすればよいですか?
クエリパラメーターを使ったGETリクエストを使用します。例:/Patient?name=Smith&birthdate=ge1980-01-01。FHIRは修飾子(:exact、:contains)やプレフィックス(gt、lt、ge、le)をサポートしています。
Bulk FHIRとは何ですか?
Bulk FHIR($export)は、NDJSON形式で大規模なデータセットを非同期でエクスポートすることを可能にします。集団健康、分析、データウェアハウジングなどで使用されます。
FHIRのバージョン管理はどのように行いますか?
特定のFHIRバージョン(R4推奨)をターゲットにし、バージョン固有のエンドポイントを使用します。サポートされているバージョンとリソースについては、CapabilityStatementを確認してください。
FHIRをカスタムフィールドで拡張できますか?
はい、FHIR拡張機能を使用してカスタムデータ要素を追加できます。拡張機能をImplementation Guideで定義し、広く共有する場合はHL7に登録してください。
FHIR開発に役立つツールは何ですか?
一般的なツールには、HAPI FHIR(オープンソースサーバー)、FHIRバリデーター、Postmanコレクション、APIテストとドキュメント化のためのApidogがあります。
