สรุป
HL7 FHIR (Fast Healthcare Interoperability Resources) เป็นมาตรฐานที่ทันสมัยสำหรับการแลกเปลี่ยนข้อมูลด้านการดูแลสุขภาพ โดยใช้ RESTful API พร้อมการตอบกลับแบบ JSON/XML มันมีทรัพยากรที่ได้มาตรฐานสำหรับผู้ป่วย ข้อมูลสังเกตการณ์ ยา และอื่นๆ พร้อมการยืนยันตัวตนด้วย OAuth 2.0 และ SMART on FHIR สำหรับการผสานรวมแอปพลิเคชัน คู่มือนี้ครอบคลุมสถาปัตยกรรม FHIR ประเภททรัพยากร พารามิเตอร์การค้นหา การยืนยันตัวตน และกลยุทธ์การนำไปใช้งานจริง
บทนำ
การแตกแยกของข้อมูลด้านการดูแลสุขภาพทำให้ระบบดูแลสุขภาพของสหรัฐอเมริกาต้องเสียค่าใช้จ่าย 30 พันล้านดอลลาร์ต่อปี สำหรับนักพัฒนาที่สร้างแอปพลิเคชันด้านการดูแลสุขภาพ การผสานรวม HL7 FHIR API ไม่ใช่ทางเลือก—แต่เป็นมาตรฐานอุตสาหกรรมที่ CMS กำหนดและนำไปใช้โดย Epic, Cerner และผู้จำหน่าย EHR รายใหญ่ทั้งหมด
นี่คือความจริง: ผู้ให้บริการที่ใช้แอปที่รองรับ FHIR จะลดเวลาในการประสานงานการดูแลลง 40% และกำจัดการร้องขอเวชระเบียนที่ใช้แฟกซ์ถึง 85% การผสานรวม FHIR API ที่แข็งแกร่งช่วยให้การแลกเปลี่ยนข้อมูลเป็นไปอย่างราบรื่นระหว่าง EHRs, พอร์ทัลผู้ป่วย และแพลตฟอร์มการประสานงานการดูแล
คู่มือนี้จะอธิบายกระบวนการผสานรวม 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 รายการ ทรัพยากรหลักได้แก่:
| ทรัพยากร (Resource) | วัตถุประสงค์ (Purpose) | กรณีการใช้งานทั่วไป (Common Use Cases) |
|---|---|---|
| Patient | ข้อมูลประชากร | การค้นหาผู้ป่วย, การลงทะเบียน |
| Practitioner | ข้อมูลผู้ให้บริการ | ไดเรกทอรี, การจัดตารางเวลา |
| Encounter | การเข้าพบ/การรับเข้า | ตอนการดูแล, การเรียกเก็บเงิน |
| Observation | ข้อมูลทางคลินิก | สัญญาณชีพ, ผลตรวจทางห้องปฏิบัติการ, การประเมิน |
| Condition | ปัญหา/การวินิจฉัย | รายการปัญหา, การวางแผนการดูแล |
| MedicationRequest | ใบสั่งยา | การสั่งยาทางอิเล็กทรอนิกส์, ประวัติการใช้ยา |
| AllergyIntolerance | อาการแพ้ | การตรวจสอบความปลอดภัย, การแจ้งเตือน |
| Immunization | การฉีดวัคซีน | ประวัติการฉีดวัคซีน |
| DiagnosticReport | รายงานห้องปฏิบัติการ/ภาพวินิจฉัย | การส่งผลลัพธ์ |
| DocumentReference | เอกสารทางคลินิก | CCD, สรุปการจำหน่าย |
สถาปัตยกรรม FHIR API
FHIR ใช้โครงสร้าง RESTful API:
https://fhir-server.com/fhir/{resourceType}/{id}
เปรียบเทียบเวอร์ชัน FHIR
| เวอร์ชัน (Version) | สถานะ (Status) | กรณีการใช้งาน (Use Case) |
|---|---|---|
| R4 (4.0.1) | STU ปัจจุบัน | การนำไปใช้งานจริง |
| R4B (4.3) | Trial Implementation | ผู้ใช้เริ่มต้น |
| R5 (5.0.0) | Draft STU | การนำไปใช้งานในอนาคต |
| DSTU2 | เลิกใช้แล้ว | เฉพาะระบบเดิมเท่านั้น |
CMS กำหนดให้ EHRs ที่ได้รับการรับรองต้องรองรับ FHIR R4 สำหรับ Patient Access และ Provider Access APIs
เริ่มต้นใช้งาน: การเข้าถึงเซิร์ฟเวอร์ FHIR
ขั้นตอนที่ 1: เลือกเซิร์ฟเวอร์ FHIR ของคุณ
ตัวเลือกสำหรับการปรับใช้เซิร์ฟเวอร์ FHIR:
| เซิร์ฟเวอร์ (Server) | ประเภท (Type) | ค่าใช้จ่าย (Cost) | เหมาะที่สุดสำหรับ (Best For) |
|---|---|---|---|
| Azure API for FHIR | Managed | จ่ายตามการใช้งาน | ระดับองค์กร, ลูกค้า Azure |
| AWS HealthLake | Managed | จ่ายตามการใช้งาน | สภาพแวดล้อม AWS |
| Google Cloud Healthcare API | Managed | จ่ายตามการใช้งาน | สภาพแวดล้อม GCP |
| HAPI FHIR | โอเพนซอร์ส | โฮสต์เอง | การปรับใช้แบบกำหนดเอง |
| Epic FHIR Server | เชิงพาณิชย์ | ลูกค้า Epic | การผสานรวม Epic EHR |
| Cerner Ignite FHIR | เชิงพาณิชย์ | ลูกค้า Cerner | การผสานรวม Cerner EHR |
ขั้นตอนที่ 2: รับข้อมูลรับรองเซิร์ฟเวอร์
สำหรับบริการ FHIR บนคลาวด์:
# Azure API for FHIR
# 1. สร้างบริการ FHIR ใน Azure Portal
# 2. กำหนดค่าการตรวจสอบสิทธิ์ (OAuth 2.0 หรือ AAD)
# 3. รับ FHIR endpoint: https://{service-name}.azurehealthcareapis.com
# 4. ลงทะเบียนแอปไคลเอนต์สำหรับ OAuth
# AWS HealthLake
# 1. สร้าง Data Store ใน AWS Console
# 2. กำหนดค่าบทบาท IAM
# 3. รับ endpoint: https://healthlake.{region}.amazonaws.com
ขั้นตอนที่ 3: ทำความเข้าใจการดำเนินการ RESTful ของ FHIR
FHIR รองรับเมธอด HTTP มาตรฐาน:
| การดำเนินการ (Operation) | เมธอด HTTP (HTTP Method) | Endpoint | คำอธิบาย (Description) |
|---|---|---|---|
| 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.name[0].given[0]} ${patient.name[0].family}`);
console.log(`วันเกิด: ${patient.birthDate}`);
console.log(`เพศ: ${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(`พบผู้ป่วย ${results.total} ราย`);
results.entry.forEach(entry => {
const patient = entry.resource;
console.log(`${patient.name[0].family}, ${patient.name[0].given[0]}`);
});
พารามิเตอร์การค้นหาที่พบบ่อย
| ทรัพยากร (Resource) | พารามิเตอร์การค้นหา (Search Parameters) | ตัวอย่าง (Example) |
|---|---|---|
| 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 |
ตัวปรับแต่งการค้นหา
| ตัวปรับแต่ง (Modifier) | คำอธิบาย (Description) | ตัวอย่าง (Example) |
|---|---|---|
:exact |
ตรงกันทุกประการ | name:exact=Smith |
:contains |
มีส่วนประกอบ | name:contains=smi |
:missing |
มี/ไม่มีค่า | phone:missing=true |
: (คำนำหน้า) |
ตัวดำเนินการคำนำหน้า | birthdate=ge1980-01-01 |
คำนำหน้าการค้นหาสำหรับวันที่และตัวเลข
| คำนำหน้า (Prefix) | ความหมาย (Meaning) | ตัวอย่าง (Example) |
|---|---|---|
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' สำหรับ Systolic BP
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: 'ความดันโลหิตซิสโตลิก',
value: 120,
unit: 'mmHg',
ucumCode: 'mm[Hg]',
performerId: '67890'
});
console.log(`Observation สร้างแล้ว: ${systolicBP.id}`);
รหัส LOINC ที่พบบ่อย
| รหัส (Code) | การแสดงผล (Display) | หมวดหมู่ (Category) |
|---|---|---|
| 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: 'โรคเบาหวานชนิดที่ 2',
status: 'active',
category: 'problem-list-item',
onsetDate: '2024-01-15'
});
รหัส SNOMED CT ที่พบบ่อย
| รหัส (Code) | การแสดงผล (Display) | หมวดหมู่ (Category) |
|---|---|---|
| 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(` ขนาดยา: ${med.dosageInstruction[0]?.text}`);
console.log(` สถานะ: ${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(`วันที่: ${obs.effectiveDateTime}`);
});
OAuth 2.0 และ SMART on FHIR
ทำความเข้าใจการยืนยันตัวตน FHIR
เซิร์ฟเวอร์ FHIR ใช้ OAuth 2.0 ร่วมกับ OpenID Connect:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ ไคลเอนต์ │───▶│ เซิร์ฟเวอร์ │───▶│ เซิร์ฟเวอร์ │
│ (แอป) │ │ ตรวจสอบสิทธิ์ │ │ 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; // URL เซิร์ฟเวอร์ FHIR
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(`เปลี่ยนเส้นทางไปที่: ${authUrl}`);
ขอบเขต SMART ที่จำเป็น
| ขอบเขต (Scope) | สิทธิ์ (Permission) | กรณีการใช้งาน (Use Case) |
|---|---|---|
openid |
การตรวจสอบสิทธิ์ OIDC | จำเป็นสำหรับทุกแอป |
profile |
ข้อมูลโปรไฟล์ผู้ใช้ | ไดเรกทอรีผู้ให้บริการ |
patient/Patient.read |
อ่านข้อมูลประชากรของผู้ป่วย | การค้นหาผู้ป่วย |
patient/Observation.read |
อ่านข้อมูลสังเกตการณ์ | สัญญาณชีพ, ผลตรวจทางห้องปฏิบัติการ |
patient/Condition.read |
อ่าน Condition | รายการปัญหา |
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 callback
const fhirClient = new FHIRClient(tokens.accessToken, 'https://fhir.epic.com');
const patient = await fhirClient.getPatient(tokens.patientId);
การดำเนินการแบบ Batch และ Transaction
คำขอแบบ Batch
ดำเนินการคำขอหลายรายการที่เป็นอิสระจากกัน:
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(`การตอบกลับ ${index}: ${entry.response.status}`);
console.log(entry.resource);
});
คำขอแบบ Transaction
ดำเนินการคำขอหลายรายการเป็นหน่วยอะตอมมิก:
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: 'ติดตามผลการตรวจทางห้องปฏิบัติการของผู้ป่วย',
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('ไม่ได้รับอนุญาต');
}
// ประมวลผลการแจ้งเตือน
if (notification.event?.resourceType === 'DiagnosticReport') {
const reportId = notification.event.resourceId;
const report = await fhirRequest(`/DiagnosticReport/${reportId}`);
// ประมวลผลผลการตรวจทางห้องปฏิบัติการใหม่
await processLabResult(report);
}
res.status(200).send('ตกลง');
});
การแก้ไขปัญหาทั่วไป
ปัญหา: 401 Unauthorized (ไม่ได้รับอนุญาต)
อาการ: ได้รับข้อผิดพลาด “Unauthorized” หรือ “Invalid token”
วิธีแก้ปัญหา:
- ตรวจสอบว่าโทเค็นยังไม่หมดอายุ
- ตรวจสอบว่าขอบเขตโทเค็นครอบคลุมทรัพยากรที่ร้องขอ
- ตรวจสอบให้แน่ใจว่ามีส่วนหัว
Authorization: Bearer {token} - ตรวจสอบว่า URL เซิร์ฟเวอร์ FHIR ตรงกับ audience ของโทเค็น
ปัญหา: 403 Forbidden (ต้องห้าม)
อาการ: โทเค็นถูกต้อง แต่ถูกปฏิเสธการเข้าถึง
วิธีแก้ปัญหา:
- ตรวจสอบว่าผู้ใช้มีสิทธิ์สำหรับทรัพยากรที่ร้องขอ
- ตรวจสอบว่าบริบทผู้ป่วยตรงกัน (สำหรับโทเค็นที่กำหนดขอบเขตผู้ป่วย)
- ตรวจสอบว่า SMART scopes ครอบคลุมการดำเนินการที่ร้องขอ
- ตรวจสอบการควบคุมการเข้าถึงระดับทรัพยากร
ปัญหา: 404 Not Found (ไม่พบ)
อาการ: ทรัพยากรไม่มีอยู่จริง หรือ endpoint ไม่ถูกต้อง
วิธีแก้ปัญหา:
- ตรวจสอบว่า ID ทรัพยากรถูกต้อง
- ตรวจสอบว่า Base URL ของ FHIR ถูกต้อง
- ตรวจสอบว่าประเภททรัพยากรได้รับการสนับสนุนโดยเซิร์ฟเวอร์
- ตรวจสอบ endpoint เฉพาะเวอร์ชัน (R4 เทียบกับ R4B)
ปัญหา: 422 Unprocessable Entity (เอนทิตีไม่สามารถประมวลผลได้)
อาการ: ข้อผิดพลาดในการตรวจสอบความถูกต้องเมื่อสร้าง/อัปเดต
วิธีแก้ปัญหา:
// แยกวิเคราะห์ข้อผิดพลาดในการตรวจสอบความถูกต้อง
const error = await response.json();
error.issue?.forEach(issue => {
console.log(`ความรุนแรง: ${issue.severity}`);
console.log(`ตำแหน่ง: ${issue.expression?.join('.')}`);
console.log(`ข้อความ: ${issue.details?.text}`);
});
สาเหตุทั่วไป:
- ฟิลด์ที่จำเป็นขาดหายไป
- ค่าระบบรหัสไม่ถูกต้อง
- รูปแบบการอ้างอิงไม่ถูกต้อง
- ปัญหาเรื่องรูปแบบวันที่
รายการตรวจสอบสำหรับการนำไปใช้งานจริง
ก่อนนำไปใช้งานจริง:
- [ ] กำหนดค่า OAuth 2.0 ด้วย SMART on FHIR
- [ ] ใช้งานตรรกะการรีเฟรชโทเค็น
- [ ] ตั้งค่าการจัดการข้อผิดพลาดที่เหมาะสม
- [ ] เพิ่มการบันทึกที่ครอบคลุม (ไม่มีข้อมูล PHI ในบันทึก)
- [ ] ใช้งานการจำกัดอัตรา (Rate Limiting)
- [ ] กำหนดค่าตรรกะการลองใหม่ด้วย exponential backoff
- [ ] ทดสอบกับผู้จำหน่าย EHR หลายราย
- [ ] ตรวจสอบกับ FHIR validator
- [ ] จัดทำเอกสารการดำเนินการ FHIR ทั้งหมด
- [ ] ตั้งค่าการตรวจสอบและการแจ้งเตือน
- [ ] สร้าง runbook สำหรับปัญหาทั่วไป
การตรวจสอบความถูกต้องของ 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(`ข้อผิดพลาดในการตรวจสอบความถูกต้อง: ${issue.message}`);
console.error(`ตำแหน่ง: ${issue.path}`);
});
throw new Error('การตรวจสอบความถูกต้องของทรัพยากรล้มเหลว');
}
return true;
};
// การใช้งานก่อนสร้าง/อัปเดต
await validateResource(patientResource);
กรณีการใช้งานจริง
การบูรณาการ Patient Portal
ระบบสุขภาพสร้าง Patient Portal:
- ความท้าทาย: ผู้ป่วยไม่สามารถเข้าถึงเวชระเบียนจากผู้ให้บริการหลายราย
- วิธีแก้ปัญหา: แอป SMART on FHIR ที่ผสานรวมกับ Epic และ Cerner
- ผลลัพธ์: ผู้ป่วยรับไปใช้งาน 80%, ลดการร้องขอเวชระเบียน 50%
การนำไปใช้งานหลัก:
- การเปิดแอป SMART ที่หันหน้าเข้าหาผู้ป่วย
- การเข้าถึง Patient, Observation, Condition, MedicationRequest แบบอ่านอย่างเดียว
- รีเฟรชโทเค็นสำหรับเซสชันที่ใช้งานได้นาน
- UI ที่ตอบสนองต่อมือถือ
การสนับสนุนการตัดสินใจทางคลินิก
แพลตฟอร์มการจัดการการดูแลเพิ่ม CDS:
- ความท้าทาย: ผู้ให้บริการพลาดโอกาสในการดูแลเชิงป้องกัน
- วิธีแก้ปัญหา: การสอบถาม FHIR แบบเรียลไทม์สำหรับช่องว่างในการดูแล
- ผลลัพธ์: คะแนน HEDIS ดีขึ้น 25%
การนำไปใช้งานหลัก:
- แอป SMART ที่หันหน้าเข้าหาผู้ให้บริการ
- สอบถาม Patient, Condition, Observation, Immunization
- คำนวณช่องว่างในการดูแลตามแนวทาง
- คำแนะนำในกระบวนการทำงานของ EHR
การวิเคราะห์สุขภาพประชากร
ผู้ชำระเงินสร้างแดชบอร์ดสุขภาพประชากร:
- ความท้าทาย: ข้อมูลไม่สมบูรณ์ในเครือข่ายผู้ให้บริการ
- วิธีแก้ปัญหา: การส่งออกข้อมูล FHIR แบบ bulk สำหรับการวิเคราะห์
- ผลลัพธ์: มุมมองผู้ป่วยแบบ 360 องศา, ลดต้นทุน PMPM
การนำไปใช้งานหลัก:
- การเข้าถึงข้อมูล FHIR แบบ Bulk ($export)
- การส่งออกข้อมูลไปยัง data warehouse ทุกคืน
- โมเดลการแบ่งชั้นความเสี่ยง
- การแจ้งเตือนสำหรับผู้จัดการการดูแล
บทสรุป
HL7 FHIR เป็นรากฐานสำหรับการทำงานร่วมกันด้านการดูแลสุขภาพในยุคปัจจุบัน ประเด็นสำคัญ:
- FHIR R4 เป็นมาตรฐานปัจจุบันสำหรับการผสานรวม API ด้านการดูแลสุขภาพ
- SMART on FHIR ช่วยให้การยืนยันตัวตน OAuth 2.0 ปลอดภัย
- ประเภททรัพยากรกำหนดมาตรฐานข้อมูลผู้ป่วย ข้อมูลสังเกตการณ์ Condition และยา
- พารามิเตอร์การค้นหาช่วยให้การสืบค้นมีความยืดหยุ่น
- การดำเนินการแบบ Batch และ Transaction รองรับกระบวนการทำงานที่ซับซ้อน
- Apidog ช่วยปรับปรุงการทดสอบและจัดทำเอกสาร FHIR API
ส่วนคำถามที่พบบ่อย
HL7 FHIR ใช้สำหรับอะไร?
FHIR ช่วยให้การแลกเปลี่ยนข้อมูลด้านการดูแลสุขภาพเป็นมาตรฐานระหว่าง EHRs, Patient Portal, แอปพลิเคชันมือถือ และระบบ IT ด้านสุขภาพอื่นๆ กรณีการใช้งานทั่วไปรวมถึงแอปพลิเคชันสำหรับผู้ป่วยในการเข้าถึงข้อมูล, การสนับสนุนการตัดสินใจทางคลินิก, สุขภาพประชากร และการประสานงานการดูแล
ฉันจะเริ่มต้นใช้งาน FHIR ได้อย่างไร?
เริ่มต้นด้วยการเข้าถึงเซิร์ฟเวอร์ FHIR สาธารณะ (เช่น HAPI FHIR test server) หรือตั้งค่าบริการ FHIR บนคลาวด์ (Azure API for FHIR, AWS HealthLake) ฝึกฝนการอ่านทรัพยากรและการใช้พารามิเตอร์การค้นหา
HL7 v2 กับ FHIR แตกต่างกันอย่างไร?
HL7 v2 ใช้ข้อความที่คั่นด้วยสัญลักษณ์ไปป์ (ADT, ORM, ORU) สำหรับการแลกเปลี่ยนข้อมูลแบบขับเคลื่อนด้วยเหตุการณ์ (event-driven) ส่วน FHIR ใช้ RESTful API กับ JSON/XML สำหรับการเข้าถึงแบบทรัพยากร (resource-based) FHIR ง่ายต่อการนำไปใช้งานและเหมาะสมกับแอปพลิเคชันเว็บ/มือถือที่ทันสมัยกว่า
FHIR ปฏิบัติตาม HIPAA หรือไม่?
FHIR เป็นมาตรฐานรูปแบบข้อมูล การปฏิบัติตาม HIPAA ขึ้นอยู่กับการนำไปใช้งาน: การเข้ารหัส, การยืนยันตัวตน, การควบคุมการเข้าถึง และการบันทึกการตรวจสอบ ใช้ OAuth 2.0 กับ SMART on FHIR เพื่อการเข้าถึงที่ปลอดภัย
SMART scopes คืออะไร?
SMART scopes กำหนดสิทธิ์การเข้าถึงแบบละเอียดสำหรับทรัพยากร FHIR (เช่น patient/Observation.read, user/*.read) ร้องขอเฉพาะ scopes ที่แอปของคุณต้องการเท่านั้น
ฉันจะค้นหาทรัพยากรใน 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) และใช้ endpoint เฉพาะเวอร์ชัน ตรวจสอบ CapabilityStatement สำหรับเวอร์ชันและทรัพยากรที่รองรับ
ฉันสามารถขยาย FHIR ด้วยฟิลด์ที่กำหนดเองได้หรือไม่?
ได้ คุณสามารถใช้ส่วนขยาย FHIR (FHIR extensions) เพื่อเพิ่มองค์ประกอบข้อมูลที่กำหนดเอง กำหนดส่วนขยายใน Implementation Guide ของคุณและลงทะเบียนกับ HL7 หากต้องการแบ่งปันอย่างกว้างขวาง
มีเครื่องมือใดบ้างที่ช่วยในการพัฒนา FHIR?
เครื่องมือยอดนิยมได้แก่ HAPI FHIR (เซิร์ฟเวอร์โอเพนซอร์ส), FHIR validator, Postman collections และ Apidog สำหรับการทดสอบและจัดทำเอกสาร API
