Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no card
FHIR을 이용한 원격 환자 모니터링
가정 혈압 측정부터 알림까지: 기기 관찰(Observations), 서명된 웹훅, 멱등성 규칙, 데이터 계층 외부 유지 사항. 테스트된 코드.
원격 환자 모니터링(RPM)은 환자가 집에서 측정하는 혈압, 체중, 혈당, 산소 포화도 등의 데이터를 수집하여 의료팀이 방문 사이에 해당 데이터를 확인하고 조치할 수 있도록 합니다. 데이터 부분은 네 가지 단계로 구성됩니다: 각 측정값을 코드로 지정되고 기기 속성이 부여된 관찰(Observation)로 환자의 기록에 저장; 새로운 측정값이 도착했을 때 반응; 명확한 규칙에 따라 주의가 필요한지 결정; 그리고 의료팀이 수행한 조치에 대한 기록 유지. 이 페이지에서는 Anpheros 웹훅과 TypeScript SDK를 사용하여 HL7 FHIR R4 기반으로 이러한 파이프라인을 구축하며, 플랫폼에서 실행된 코드를 제공하고 데이터 계층 외부에서 처리되는 항목들을 나열합니다.
파이프라인
cuff, scale, glucose meter, pulse oximeter
│ (Bluetooth / vendor app / Apple Health / Health Connect)
▼
patient's phone app ──► your backend ──► FHIR Observation (author: device) in the patient's record
│ webhook: resource.created (signed)
▼
your monitoring service: rule → alert → care team
│
note, call, CarePlan change, next appointment
실제로 작동하는지 여부를 결정하는 세 가지 선택 사항이 있습니다.
- 하나의 읽기, 하나의 관찰. 각 커프 측정값은 LOINC 85354-9와 두 개의 구성 요소(8480-6 수축기, 8462-4 이완기), 측정 시간 및
author_type: device를 포함하는 Observation입니다. 일일 웨어러블 요약과 달리 RPM 측정값은 임상의가 하나씩 살펴볼 수 있는 개별 측정값입니다. - 타이머가 아닌 이벤트에 반응. 새 Observation마다 웹훅을 사용하면 측정값이 도착한 후 몇 초 후에 규칙이 실행됩니다.
- 여러 읽기에 걸친 규칙. 단일 높은 읽기는 종종 노이즈입니다. 대부분의 프로토콜은 일련의 읽기를 살펴봅니다. 아래 규칙은 140/90 이상인 세 번의 연속 읽기를 요청합니다. 해당 임계값은 임상 조언이 아니라 예시입니다. 케어 팀은 각 환자에 대해 규칙을 설정합니다.
기기에서 읽은 값 작성하기
휴대폰 앱(또는 백엔드를 통한 기기 공급업체의 클라우드)은 각 읽은 값을 다음과 같이 작성합니다:
await anpheros.observations.create(patientId, {
code: '85354-9', category: 'vital-signs', effective_at: '2026-10-13T07:15:00-04:00', author_type: 'device',
components: [{ code: '8480-6', value: 152, unit: 'mm[Hg]' }, { code: '8462-4', value: 95, unit: 'mm[Hg]' }],
}, { source_system: 'bp-cuff-app' });
읽은 값이 기기에서 직접 오는 것이 아니라 Apple Health 또는 Health Connect를 통해 오는 경우, FHIR 형식의 웨어러블 데이터에 설명된 대로 휴대폰에서 읽으십시오. 단, 개별 혈압 읽은 값은 일일 평균 대신 개별 Observation으로 유지하십시오.
새로운 측정값에 대한 반응
웹훅 엔드포인트를 한 번 등록합니다 (POST /v1/webhooks, events: ["resource.created"] 사용). 응답에는 서명 비밀 키가 포함됩니다. 그런 다음 각 전달을 확인하고 규칙을 평가하는 작은 서비스를 실행합니다.
import { createServer } from 'node:http';
import { Anpheros, apiKey, verifyWebhookSignature } from '@anpheros/sdk';
const anpheros = new Anpheros({ auth: apiKey(process.env.ANPHEROS_KEY!) });
const SECRET = process.env.WEBHOOK_SECRET!;
const seen = new Set<string>(); // deliveries are at-least-once: de-duplicate by event id
const alerted = new Set<string>(); // one alert per newest reading, however many events arrive
// Rule: the three most recent home readings all at or above 140/90 → tell the care team once
async function checkBloodPressure(patientId: string, observationId: string) {
const obs = await anpheros.observations.get(observationId);
if (obs.code !== '85354-9') return; // only blood pressure readings trigger the rule
const { data } = await anpheros.observations.list(patientId, { code: '85354-9', limit: 3 }); // newest first
const value = (o: typeof obs, code: string) => o.components.find(c => c.code === code)?.value ?? 0;
const allHigh = data.length === 3 && data.every(o => value(o, '8480-6') >= 140 || value(o, '8462-4') >= 90);
if (!allHigh || alerted.has(data[0].id)) return;
alerted.add(data[0].id);
console.log(`ALERT patient ${patientId}: last 3 readings ≥ 140/90 →`,
data.map(o => `${value(o, '8480-6')}/${value(o, '8462-4')}`).join(', ')); // in production: notify the care team
}
createServer(async (req, res) => {
const chunks: Buffer[] = [];
for await (const c of req) chunks.push(c as Buffer);
const body = Buffer.concat(chunks).toString('utf8');
const ok = await verifyWebhookSignature({ secret: SECRET, header: req.headers['anpheros-signature'] as string, body });
if (!ok) { res.writeHead(400).end(); return; } // not from Anpheros, or too old
res.writeHead(204).end(); // answer fast; do the work after
const event = JSON.parse(body);
if (seen.has(event.id)) return;
seen.add(event.id);
const d = event.data;
if (event.type === 'resource.created' && d.resource_type === 'Observation') await checkBloodPressure(d.patient, d.id);
}).listen(8799);
테스트 방법: 네 번의 아침 측정값 (136/86, 144/92, 149/94, 152/95)을 로컬 플랫폼 인스턴스의 합성 환자에게 기록했으며, 플랫폼은 측정값당 하나의 서명된 resource.created 이벤트를 생성했습니다. 플랫폼 자체 서명 코드로 서명된 해당 이벤트는 두 개의 중복 항목 및 잘못된 비밀 키로 서명된 요청과 함께 위 서비스로 전송되었습니다. 결과: 잘못된 서명은 400 처리되었고, 중복 항목은 무시되었으며, 서비스는 이벤트당 하나가 아닌 정확히 하나의 알림 — 152/95, 149/94, 144/92 — 을 발생시켰습니다.
운영 환경에서는 재시작 시 알림이 반복되지 않도록 seen 및 alerted을 메모리가 아닌 데이터베이스에 보관하십시오.
개발 중 웹훅 수신. Anpheros는 공개 HTTPS 엔드포인트만 수락하고 전달합니다. 등록 시 (400, 필드 url) localhost 또는 개인 주소는 거부되며, 샌드박스에서도 마찬가지입니다. 또한 모든 전달은 주소를 다시 확인합니다. 노트북에서 이벤트를 수신하려면 공개 HTTPS 터널을 통해 서비스를 노출하고 터널 주소를 등록하십시오. 웹훅을 참조하십시오.
이후 레코드에 표시되어야 하는 내용
알림 자체는 모니터링 서비스에 존재하지만, 레코드에는 알림의 결과가 표시되어야 다음 임상의와 환자가 전체 내용을 볼 수 있습니다.
- 측정값, 각 측정값은 소스 시스템(provenance)으로 표시된 장치 데이터로 표시됩니다.
- 모니터링 계획(측정 대상, 빈도, 목표)을 포함하는
CarePlan. - 팀이 환자에게 전화할 때
Encounter(원격 연락의 경우VR클래스) 및MedicationStatement으로 표시되는 모든 약물 변경 사항.
Anpheros는 이러한 리소스를 저장합니다. 자체적인 알림 또는 작업 리소스는 없습니다(Flag, Task 및 Communication은 해당 리소스 유형에 포함되지 않으므로 알림 큐 및 당직 라우팅은 서비스에 속합니다).
청구, 규제 및 안전
RPM 프로그램은 종종 환급 규칙에 따라 달라집니다. 미국에서는 Medicare가 특정 CPT 코드에 따라 원격 생리 모니터링에 대해 최소 데이터 보유 일수 및 의료팀 근무 시간을 기준으로 비용을 지불합니다. 이러한 규칙은 변경될 수 있으며 데이터 플랫폼이 보증할 수 있는 사항이 아닙니다. 현재 CMS 규칙을 확인하고 증거(판독값 및 팀 근무 시간)를 자체 시스템에 보관하십시오. 판독값을 해석하여 진단 또는 치료를 유도하는 소프트웨어는 의료 기기로 규제될 수 있습니다. 임계값은 임상의의 판단에 맡기고 모든 경고 및 응답을 기록하십시오.
Anpheros의 역할
Anpheros는 기록(provenance가 포함된 FHIR R4 Observations, CarePlan, Encounter), 재시도 기능이 있는 서명된 웹훅, 다른 조직의 앱이 판독값을 읽어야 하는 경우를 위한 동의, 환자가 볼 수 있는 액세스 로그를 제공합니다. Anpheros는 장치 공급업체에 연결하거나, 비정상적인 것을 결정하거나, 경고를 보내거나, 청구를 처리하지 않습니다.
자주 묻는 질문
가정 혈압 측정값을 저장하는 FHIR 리소스는 무엇인가요?
Observation 리소스에 LOINC 85354-9 (혈압 패널)와 두 개의 구성 요소인 8480-6 (수축기) 및 8462-4 (이완기)가 포함되며, 각 구성 요소는 valueQuantity in mm[Hg]이며, 측정 시간과 장치가 작성자로 기록됩니다.
RPM 데이터는 일별 평균이어야 하나요, 아니면 개별 측정값이어야 하나요?
혈압, 체중 또는 포도당과 같은 임상 측정값의 경우 개별 측정값을 사용해야 합니다. 이는 임상의가 이를 검토하고 집계하기 때문입니다. 일별 요약은 심박수 또는 걸음 수와 같은 고주파 웨어러블 데이터에 적합합니다.
새 측정값이 도착했을 때 알림을 트리거하려면 어떻게 해야 하나요?
resource.created 웹훅에 구독하고, 서명을 확인하고, 새 Observation을 가져와 최근 시리즈에 대한 규칙을 평가합니다. 전달은 반복될 수 있으므로 이벤트 ID로 중복을 제거하고 알림을 멱등성 있게 만드십시오.
Anpheros가 임상의에게 알림을 보낼 수 있나요?
아니요. Anpheros는 웹훅을 통해 귀하의 서비스에 알림을 보냅니다. 누가 알림을 받고, 어떻게, 언제 알림을 받을지는 귀하의 시스템에서 결정됩니다.
관련 자료
Try it free in the sandbox · first FHIR call in 5 minutesFree · 30 synthetic patients · no card