# FHIR を用いた遠隔患者モニタリング

> 自宅での血圧測定からアラートまで：デバイスの Observation、署名付き Webhook、冪等なルール、そしてデータレイヤーの外に残るもの。テスト済みのコード。

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

**遠隔患者モニタリング (RPM) は、患者が自宅で測定した血圧、体重、血糖値、酸素飽和度などの測定値を収集し、ケアチームが診察の合間にそれらを確認して対応できるようにします。** データ部分は 4 つのステップで構成されます。各測定値を、コード化され、デバイス属性が付与された 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
```

実際に機能するかどうかは、以下の3つの選択肢によって決まります。

- **1回の測定、1つのObservation。** 各カフ測定値は、LOINC 85354-9 を持つ Observation となり、2つのコンポーネント（8480-6 収縮期、8462-4 拡張期）、測定時間、および `author_type: device` を含みます。日々のウェアラブルデバイスのサマリーとは異なり、RPMの測定値は、臨床医が個別に確認できる個々の測定値です。
- **タイマーではなく、イベントに反応する。** 新しい Observation ごとのWebhookにより、測定値が到着した数秒後にルールが実行されます。
- **複数の測定値にわたるルール。** 単一の高い測定値はノイズであることが多いため、ほとんどのプロトコルでは一連の測定値を確認します。以下のルールは、140/90以上が3回連続で測定された場合に要求します。このしきい値はあくまで例であり、臨床的なアドバイスではありません。ケアチームが各患者に対してルールを設定します。

## デバイスからの読み取り値の書き込み

電話アプリ（またはデバイスベンダーのクラウド経由で、バックエンドを介して）は、各読み取り値を書き込みます。

```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/ja/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);
```

テスト方法: 4 回の朝の測定値 (136/86、144/92、149/94、152/95) を、ローカルインスタンスのプラットフォーム上の合成患者に記録しました。プラットフォームは測定値ごとに 1 つの署名付き `resource.created` イベントを生成しました。プラットフォーム自身の署名コードで署名されたこれらのイベントは、2 つの重複と間違ったシークレットで署名された 1 つのリクエストと共に上記のサービスに送信されました。結果: 間違った署名は `400` となり、重複は無視され、サービスは正確に 1 つのアラート (`152/95, 149/94, 144/92`) を発生させました。イベントごとに 1 つではありません。

本番環境では、アラートの繰り返しを避けるために、`seen` と `alerted` をメモリではなくデータベースに保存してください。

**開発中に Webhook を受信する。** Anpheros は公開 HTTPS エンドポイントのみを受け入れ、配信します。登録時に `localhost` またはプライベートアドレスは拒否されます (`400`、フィールド `url`)。サンドボックスでも同様であり、各配信でアドレスが再度チェックされます。ラップトップでイベントを受信するには、サービスを公開 HTTPS トンネル経由で公開し、トンネルのアドレスを登録してください。詳細は [Webhook](https://developers.anpheros.com/ja/guides/webhooks) を参照してください。

## レコードに表示されるべき内容

アラート自体は監視サービス内に存在しますが、レコードにはその結果が表示されるべきです。これにより、次の担当者や患者が全体像を把握できます。

- 測定値。各測定値は、そのソースシステム ([provenance](https://developers.anpheros.com/guides/medical-data-provenance)) を持つデバイスデータとしてマークされます。
- 監視計画 (測定内容、頻度、目標) を示す `CarePlan`。
- チームが患者に電話した際の `Encounter` (リモートコンタクトの場合はクラス `VR`) および、薬の変更があった場合は `MedicationStatement`。

Anpheros はこれらのリソースを保存します。Anpheros 自体にはアラートリソースやタスクリソースはありません (`Flag`、`Task`、`Communication` はリソースタイプに含まれていません)。そのため、アラートキューやオンコールルーティングは、お客様のサービスに属します。

## 請求、規制、および安全性

RPMプログラムは、しばしば償還規則に依存します。米国では、Medicareは特定のCPTコードで遠隔生理学的モニタリングに支払いを行っており、データの日数とケアチームの滞在時間には最低要件があります。これらの規則は変更される可能性があり、データプラットフォームが証明できるものではありません。最新のCMS規則を確認し、証拠（測定値とチームの滞在時間）はご自身のシステムで保管してください。測定値を解釈して診断や治療を推進するソフトウェアは、医療機器として規制される場合があります。しきい値は臨床医の管理下に置き、すべての警告と対応を記録してください。

## Anpheros の位置づけ

Anpheros は、記録（プロビナンス付き FHIR R4 Observations、CarePlan、Encounter）、再試行機能付き署名済み Webhook、他の組織のアプリが読み取りを必要とする場合の同意、および患者が確認できるアクセスログを提供します。デバイスベンダーへの接続、異常の判断、アラートの送信、請求処理は行いません。

## よくある質問

### 家庭血圧測定値はどのFHIRリソースに格納されますか？
Observationリソースに格納されます。LOINCコードは85354-9（血圧パネル）で、2つのコンポーネント、8480-6（収縮期）と8462-4（拡張期）を持ちます。それぞれが`valueQuantity` in `mm[Hg]`であり、測定時刻とデバイスが作成者となります。

### RPMデータは日次平均値と個別測定値のどちらを使用すべきですか？
血圧、体重、血糖値などの臨床測定値については、臨床医がレビューしてカウントするため、個別測定値を使用してください。日次サマリーは、心拍数や歩数などの高頻度ウェアラブルデータに適しています。

### 新しい測定値が到着したときにアラートをトリガーするにはどうすればよいですか？
`resource.created` Webhookにサブスクライブし、署名を検証し、新しいObservationを取得して、最近のシリーズに対してルールを評価してください。配信は繰り返される可能性があるため、イベントIDで重複排除し、アラートを冪等にしてください。

### Anpherosは臨床医にアラートを送信できますか？
いいえ。AnpherosはWebhookを通じてお客様のサービスに通知します。誰が、どのように、いつアラートを受け取るかは、お客様のシステムで決定されます。

## 関連情報

- [FHIRにおけるウェアラブルデータ](https://developers.anpheros.com/ja/guides/wearable-data-fhir)
- [バーチャルケアとAIドクターに必要なデータ](https://developers.anpheros.com/ja/guides/virtual-care-ai-data)
- [Webhook](https://developers.anpheros.com/ja/guides/webhooks)
- [医療データプロベナンスの説明](https://developers.anpheros.com/guides/medical-data-provenance)
- [ヘルスケア連携](https://developers.anpheros.com/ja/guides/healthcare-integrations)
