# 使用 FHIR 进行远程患者监护

> 从家庭血压读数到警报：设备 Observations、签名 Webhook、幂等规则以及数据层之外的内容。已测试代码。

Source: https://developers.anpheros.com/zh/guides/remote-patient-monitoring-fhir

**远程患者监护 (RPM) 收集患者在家中进行的测量——血压、体重、血糖、血氧饱和度——并让护理团队在就诊之间查看和处理这些测量。** 数据部分包括四个步骤：将每个读数作为编码的、归属设备的 Observation 录入患者记录；在新读数到达时做出反应；通过明确的规则决定是否需要关注；并记录团队所做的操作。本页面使用 Anpheros Webhook 和 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`。与每日可穿戴设备摘要不同，RPM 读数是临床医生可能逐一查看的单个测量值。
- **响应事件，而非计时器。** 每个新观察值的 Webhook 允许规则在读数到达后几秒钟内运行。
- **跨多个读数的规则。** 单个高读数通常是噪声；大多数协议会查看一系列读数。下面的规则要求连续三个读数达到或超过 140/90。该阈值是一个示例，而非临床建议：护理团队为每位患者设置规则。

## 从设备写入读数

手机应用（或通过您的后端连接的设备供应商云）会写入每个读数：

```ts
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 中的可穿戴设备数据](https://developers.anpheros.com/zh/guides/wearable-data-fhir) 中的描述在手机上读取它们，但将单个血压读数保留为单独的 Observation，而不是每日平均值。

## 响应新读数

注册一个 webhook 端点一次（`POST /v1/webhooks` 和 `events: ["resource.created"]`）；响应包含签名密钥。然后运行一个小型服务，该服务验证每次传递并评估规则：

```ts
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` 保存在数据库中，而不是内存中，这样重启就不会重复发送警报。

**开发过程中接收 webhooks。** Anpheros 只接受并传递到公共 HTTPS 端点：注册时会拒绝 `localhost` 或私有地址（`400`，字段 `url`），在沙盒中也是如此，并且每次传递都会再次检查地址。要在您的笔记本电脑上接收事件，请通过公共 HTTPS 隧道公开您的服务并注册该隧道的地址。请参阅 [Webhooks](https://developers.anpheros.com/zh/guides/webhooks)。

## 记录应显示的内容

警报本身存在于您的监控服务中，但记录应显示警报的结果，以便下一位临床医生和患者看到完整的故事：

- 读数，每条都标记为具有其源系统的设备数据（[provenance](https://developers.anpheros.com/zh/guides/medical-data-provenance)）；
- 一个 `CarePlan`，包含监控计划（测量内容、频率、目标）；
- 一个 `Encounter`（远程联系的类别为 `VR`），当团队致电患者时，以及任何药物变更，作为 `MedicationStatement`。

Anpheros 存储这些资源；它没有自己的警报或任务资源（`Flag`、`Task` 和 `Communication` 不在其资源类型之列），因此警报队列和值班路由属于您的服务。

## 计费、法规和安全

RPM 项目通常依赖于报销规则——在美国，Medicare 会根据特定的 CPT 代码支付远程生理监测费用，这些代码要求有最低天数的数据和医护团队花费的时间。这些规则会发生变化，数据平台无法对其进行认证；请查阅当前的 CMS 规则，并在您自己的系统中保留证据（读数和团队花费的时间）。解释读数以驱动诊断或治疗的软件可能会被视为医疗设备进行监管。将阈值保留在临床医生手中，并记录每一次警报和响应。

## Anpheros 的定位

Anpheros 提供记录（包含来源信息的 FHIR R4 Observations、CarePlan、Encounter）、带重试机制的签名 Webhook、当其他组织的应用程序需要读取读数时的同意机制，以及患者可以查看的访问日志。它不连接设备供应商，不决定异常情况，不发送警报，也不处理计费。

## 常见问题解答

### 哪个 FHIR 资源存储家庭血压读数？
一个 Observation 资源，其 LOINC 编码为 85354-9（血压面板），包含两个组件：8462-4（收缩压）和 8480-6（舒张压），每个组件都是 `valueQuantity` 中的 `mm[Hg]`，测量时间和设备作为作者。

### RPM 数据应该是每日平均值还是单独的读数？
对于血压、体重或血糖等临床测量值，应使用单独的读数，因为临床医生会审查并计算它们。每日摘要适用于心率或步数等高频可穿戴设备数据。

### 如何在新读数到达时触发警报？
订阅 `resource.created` Webhook，验证签名，获取新的 Observation，并根据最近的系列评估您的规则。交付可能会重复，因此请按事件 ID 去重，并使警报幂等。

### Anpheros 可以向临床医生发送警报吗？
不可以。它通过 Webhook 通知您的服务；谁、如何以及何时发出警报由您的系统决定。

## 相关资源

- [FHIR 中的可穿戴设备数据](https://developers.anpheros.com/zh/guides/wearable-data-fhir)
- [虚拟护理和 AI 医生所需的数据](https://developers.anpheros.com/zh/guides/virtual-care-ai-data)
- [Webhooks](https://developers.anpheros.com/zh/guides/webhooks)
- [医疗数据溯源详解](https://developers.anpheros.com/zh/guides/medical-data-provenance)
- [医疗保健集成](https://developers.anpheros.com/zh/guides/healthcare-integrations)
