Files
hms/docs/superpowers/specs/2026-05-04-iot-fhir-platform-ecosystem-design.md
iven 2afe3a8848 docs: IoT 设备采集 + FHIR 开放平台生态设计规格
发散式探讨产出:BLE 适配器 + 设备网关混合架构,HL7 FHIR R4 输出,
OAuth2 合作伙伴认证,渐进演进 V1-V3 路线图。

Spec review 发现大量已有基础设施(device_readings/alert_engine/SSE/BLE),
设计已据此修正为"增强现有 + 新增 FHIR 层"策略。
2026-05-04 01:08:01 +08:00

956 lines
45 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# HMS IoT 设备采集 + FHIR 开放平台设计规格
> 日期: 2026-05-04 | 类型: 设计规格 | 状态: Draft
>
> **如何使用本文档:** §1-2 是全景架构所有人应读。§3-6 是各层详细设计按需阅读。§7-8 是实施路线和变更清单,执行时参考。
---
## 1. 背景与目标
### 1.1 业务背景
HMS 平台当前已实现体征数据的手动录入和危急值阈值告警。但慢病患者(高血压、糖尿病、肾病)的健康数据采集严重依赖患者主动录入,依从性差、数据稀疏、无法捕捉短时波动。
健康手环(小米手环、华为手环)的普及率为 **24 小时被动采集**提供了硬件基础。心率、血氧、步数、睡眠等数据可连续产生,通过小程序 BLE 同步到 HMS。居家医疗设备蓝牙血压计、血糖仪进一步覆盖慢病管理的核心指标。体检中心专业设备生化分析仪、心电图机则提供最权威的医疗级数据。
HMS 从"数据采集平台"升级为"健康数据枢纽":设备数据流入,通过 FHIR 标准接口流出第三方系统HIS/LIS/体检系统)可消费标准化数据。
### 1.2 目标用户与场景
| 用户 | 场景 | 价值 |
|------|------|------|
| **慢病患者** | 佩戴手环/使用蓝牙设备,小程序自动同步数据 | 免去手动录入,持续监测 |
| **主管医生** | Web 端实时看板查看患者体征趋势和告警 | 及时发现异常,干预更早 |
| **护士站** | 接收高危告警推送 | 快速响应危急情况 |
| **体检中心** | 出具报告时参考连续监测数据 + 专业设备直连 | 报告更全面,数据自动入库 |
| **合作方系统** | 通过 FHIR API 拉取/推送标准化健康数据 | 无需适配 HMS 私有接口 |
### 1.3 成功指标
| 指标 | 基线 | V1 目标 | V2 目标 |
|------|------|---------|---------|
| 体征数据日均采集量 | ~10 条/患者(手动) | > 500 条/患者(设备) | > 1000 条/患者 |
| 设备接入数 | 0 | 2 款(手环 + 血压计) | 6+ 款(含网关) |
| 告警延迟(采集→通知) | 分钟级(轮询) | < 30 秒SSE | < 10 秒 |
| FHIR API 响应时间 | N/A | < 200ms (P95) | < 150ms (P95) |
| 合作方接入 | 0 | 1 家(验证) | 3-5 家 |
### 1.4 范围边界
**做:**
- BLE 设备采集(穿戴 + 居家医疗)— DeviceAdapter 统一抽象
- 专业设备接入扩展点 — DeviceGateway traitV2 实现)
- 后端设备数据摄入 API批量提交 + 降采样)
- 告警规则引擎(单次阈值 + 连续超标 + 趋势恶化)
- SSE 推送扩展(体征聚合更新 + 告警通知)
- FHIR R4 只读 API — 10 个资源端点
- OAuth2 Client Credentials 合作伙伴认证
- 数据分区与降采样策略
**不做(本设计不覆盖):**
- ICU 级实时监控(亚秒级,需要专用系统)
- AI 驱动的异常检测erp-ai 模块职责,本设计只提供数据输入)
- FHIR 写入 APIV2a 范围)
- Webhook 事件推送V2b 范围)
- 设备厂商自助接入 SDKV2c 范围)
- 多实例部署的事件总线演进(单实例足够)
---
## 2. 整体架构与演进路线
### 2.1 全景数据流
```
┌─────────────────────────────────────────────────────────────────┐
│ 设备层 (Device Layer) │
│ │
│ 消费穿戴 居家医疗 专业设备 │
│ ┌───────┐ ┌──────────┐ ┌──────────────┐ │
│ │小米手环│ │蓝牙血压计 │ │生化分析仪 │ │
│ │华为手环│ │蓝牙血糖仪 │ │心电图机 │ │
│ │Apple │ │蓝牙体温计 │ │超声设备 │ │
│ │Watch │ │智能体脂秤 │ │DICOM 设备 │ │
│ └───┬────┘ └────┬─────┘ └──────┬───────┘ │
│ │ │ │ │
│ ────┴──── BLE GATT ───┴──── │ HL7/DICOM/TCP │
│ │ │ │ │
│ ┌───┴──────────────────┴──┐ ┌─────────┴──────────┐ │
│ │ DeviceAdapter trait │ │ DeviceGateway trait │ │
│ │ (V1 实现, 小程序端) │ │ (V2, 插件/网关) │ │
│ └───────────┬─────────────┘ └──────────┬───────────┘ │
│ │ │ │
├──────────────┼───────────────────────────────┼────────────────────┤
│ │ 摄入层 (Ingestion) │ │
│ │
│ ┌───────────┴───────────────────────────────┴───────────┐ │
│ │ 统一摄入 API │ │
│ │ POST /api/v1/health/device/readings (批量) │ │
│ │ POST /api/v1/health/device/gateway/{type} (网关) │ │
│ │ │ │
│ │ 校验 → 降采样 → 存储 → 事件发布 │ │
│ └───────────────────────────┬────────────────────────────┘ │
│ │ │
├──────────────────────────────┼────────────────────────────────────┤
│ │ 数据层 (Storage) │
│ │
│ ┌───────────┐ ┌──────────┐ ┌───────────┐ ┌───────────────┐ │
│ │device_ │ │device_ │ │vital_ │ │health_ │ │
│ │readings │ │gateways │ │signs │ │alerts │ │
│ │(高频原始) │ │(网关注册)│ │(手动+降采样)│ │(规则触发) │ │
│ └───────────┘ └──────────┘ └───────────┘ └───────────────┘ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ 输出层 (Output) │
│ │
│ V1: API 拉取 V2: 事件推送 │
│ ┌─────────────────┐ ┌──────────────────┐ │
│ │ FHIR API │ │ Webhook 推送 │ │
│ │ /fhir/Patient │ │ 事件订阅管理 │ │
│ │ /fhir/Observation│ │ 幂等投递+重试 │ │
│ │ /fhir/Device │ │ Dead letter 处理 │ │
│ │ /fhir/Diagnostic │ │ │ │
│ └────────┬────────┘ └────────┬──────────┘ │
│ │ │ │
│ ─────────┴───── 合作伙伴 API ─────┴───────── │
│ │ │ │
│ ┌────────┴──────┐ ┌────────┴──────────┐ │
│ │HIS/LIS 系统 │ │体检中心系统 │ │
│ │电子病历 │ │健康管理机构 │ │
│ └───────────────┘ └───────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### 2.2 现有功能基线
以下功能**已存在于代码库中**,本 spec 不重复设计,只标注需要的增强:
| 已有功能 | 位置 | 状态 |
|----------|------|------|
| device_readings 表 + Entity | `erp-health/entity/device_readings.rs` | 生产就绪,含分区 |
| 批量摄入 API + 服务 | `erp-health/service/device_reading_service.rs` | 生产就绪,含去重/校验/小时聚合 |
| patient_devices 设备绑定 | `erp-health/entity/patient_devices.rs` | 基础功能完成,缺少状态追踪 |
| vital_signs_hourly 小时聚合 | `erp-health/entity/vital_signs_hourly.rs` | 生产就绪 |
| alert_rules 告警规则 | `erp-health/entity/alert_rules.rs` | 生产就绪 |
| alert_engine 告警引擎 | `erp-health/service/alert_engine.rs` | 3 种规则类型已实现 |
| EventBus 事件 | `erp-health/event.rs` | `device.readings.synced` + `alert.triggered` 已发布 |
| SSE 推送 | `erp-message/handler/sse_handler.rs` | 已推送 vital_update + alert 事件 |
| BLE 适配器框架 | 小程序 `services/ble/` | TypeScript DeviceAdapter 接口 + 3 个适配器 |
| 设备类型校验 | `erp-health/service/validation.rs` | 8 种 device_type |
### 2.3 模块划分与职责
| 模块 | 位置 | 职责 | 状态 |
|------|------|------|------|
| **BLE 采集层** | 小程序 `services/ble/` | 设备发现、连接、数据读取 | 已存在 |
| **DeviceAdapter** | 小程序 `services/ble/adapters/` | 统一接口,屏蔽 BLE 差异 | 已存在TypeScript |
| **摄入 API** | `erp-health` handler | 批量接收、校验、存储 | 已存在 |
| **降采样 Pipeline** | `erp-health` service | 小时聚合已实现 | 已存在,**需新增日聚合** |
| **告警引擎** | `erp-health` service | 3 种规则评估 | 已存在 |
| **SSE 推送** | `erp-message` handler | vital_update + alert 推送 | 已存在,**需增强重连** |
| **FHIR API** | `erp-health` handler (新路由组) | FHIR R4 资源查询 | **新增** |
| **合作伙伴认证** | `erp-auth` | OAuth2 Client Credentials | **新增** |
| **DeviceGateway** | `erp-health` trait | 非 BLE 设备接入扩展点 | **新增V2** |
### 2.3 演进阶段
| 阶段 | 时间 | 内容 | 里程碑 |
|------|------|------|--------|
| **V1** | 8-12 周 | BLE 适配器 + 摄入 API + 降采样 + 告警引擎 + FHIR 只读 API + OAuth2 | 手环数据全链路跑通1 家合作方验证 |
| **V2a** | 4-6 周 | 居家医疗设备适配 ×3 + FHIR 写入 API | 血压/血糖/体温数据自动采集 |
| **V2b** | 6-8 周 | DeviceGateway + HL7/DICOM 适配 + Webhook 推送 | 体检中心设备接入 |
| **V3** | 持续 | 设备厂商自助接入(插件 SDK+ 数据市场 + AI 分析增强 | 平台生态自运转 |
### 2.5 与现有 HMS 架构的集成点
| 集成点 | 现有 | 本设计扩展 |
|--------|------|-----------|
| EventBus | `device.readings.synced` + `alert.triggered` 已实现 | 不需要新增事件类型 |
| SSE Handler | 已推送 `vital_update` + `alert` + `message` | 增强Last-Event-ID 重连 + 心跳 |
| device_readings 表 | 已有分区表 + 批量摄入 + 去重 | 不变更,复用现有 |
| vital_signs_hourly | 已有小时聚合 | 不变更,复用现有 |
| vital_signs_daily | 不存在 | **新增**:日聚合表 + background task |
| patient_devices | 基础设备绑定 | **增强**status/firmware/metadata 列 |
| alert_engine | 3 种规则类型 + cooldown | **增强**:患者级升级 + 系统级聚合降噪 |
| 认证系统 | JWT + RBAC | **新增**OAuth2 Client Credentials合作伙伴专用 |
| WASM 插件 | erp-plugin 运行时 | V2 复用为设备网关扩展点 |
### 2.5 架构决策原则
**为什么 BLE 适配器 + 设备网关混合而非统一抽象?**
BLE 穿戴和居家医疗设备本质上是同一协议族(蓝牙 GATT统一 DeviceAdapter 自然合理。专业设备的数据模型HL7 OBX/PID/OBR与体征读数时间序列根本不同强行统一会变成"最低公分母"。分开处理是正确的关注点分离。HMS 已有 WASM 插件系统,设备网关可复用插件架构作为扩展点。
**为什么 FHIR 而非自定义 JSON API**
FHIR 是国际医疗互操作标准HIS/LIS/EMR 系统广泛支持。选择 FHIR 意味着 HMS 从一开始就为与现有医疗系统对接做好了准备,合作方不需要学习 HMS 私有接口。V1 只做 6 个核心资源的只读 API复杂度可控。
**为什么合作伙伴专属而非完全开放?**
医疗数据的敏感性要求严格的访问控制。合作伙伴模式确保只有经过审核的机构才能接入,配合多层隔离(租户/资源/患者范围/频率限制)降低数据泄露风险。
---
## 3. 数据模型与 FHIR 映射
### 3.1 现有表(不修改)
以下表已存在于代码库中,本设计不做结构性变更:
| 表名 | 关键列 | 说明 |
|------|--------|------|
| `device_readings` | id, tenant_id, patient_id, device_id(VARCHAR), device_type, metric, raw_value(JSON), measured_at | 高频原始数据,含分区 |
| `patient_devices` | id, tenant_id, patient_id, device_id(VARCHAR), device_model, device_type, bound_at, last_sync_at | 设备绑定 |
| `vital_signs_hourly` | id, tenant_id, patient_id, device_type, hour_start, min_val, max_val, avg_val, sample_count | 小时聚合 |
| `alert_rules` | id, tenant_id, name, device_type, condition_type, condition_params(JSON), severity, is_active, cooldown_minutes | 告警规则 |
| `alerts` | id, tenant_id, patient_id, rule_id, device_type, severity, status, ... | 告警实例 |
### 3.2 需增强的表
**patient_devices — 增加设备状态追踪**
新增列(迁移):
| 列名 | 类型 | 说明 |
|------|------|------|
| status | VARCHAR(20) DEFAULT 'active' | active / inactive / disconnected |
| firmware_version | VARCHAR(100) | 固件版本 |
| manufacturer | VARCHAR(200) | 厂商 |
| connection_type | VARCHAR(50) DEFAULT 'ble' | ble / ble_gateway / serial / tcp / hl7 |
| metadata | JSONB | 设备特有配置 |
### 3.3 新增表
#### vital_signs_daily — 日聚合(新增)
| 列名 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | UUID v7 | PK | |
| tenant_id | UUID | NOT NULL | |
| patient_id | UUID | FK → patients, NOT NULL | |
| device_type | VARCHAR(50) | NOT NULL | 与现有 device_type 一致 |
| date_bucket | DATE | NOT NULL | 日期 |
| min_val | NUMERIC(12,4) | | 最小值 |
| max_val | NUMERIC(12,4) | | 最大值 |
| avg_val | NUMERIC(12,4) | NOT NULL | 平均值 |
| sample_count | INTEGER | NOT NULL | 样本数 |
| percentile_95 | NUMERIC(12,4) | | 95 百分位 |
| created_at | TIMESTAMPTZ | NOT NULL | |
| updated_at | TIMESTAMPTZ | NOT NULL | |
| version | INTEGER | NOT NULL DEFAULT 1 | |
UNIQUE: `(tenant_id, patient_id, device_type, date_bucket)`
#### api_clients — 合作方 API 客户端(新增)
| 列名 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | UUID v7 | PK | |
| tenant_id | UUID | NOT NULL | 所属租户 |
| client_id | VARCHAR(128) | UNIQUE NOT NULL | OAuth2 client_id |
| client_secret_hash | VARCHAR(256) | NOT NULL | Argon2 哈希 |
| client_name | VARCHAR(200) | NOT NULL | 合作方名称 |
| scopes | JSONB | NOT NULL | 允许的 FHIR 资源范围 |
| allowed_patient_ids | JSONB | | 可访问的患者 ID 白名单null = 全部) |
| rate_limit_per_minute | INTEGER | NOT NULL DEFAULT 60 | 限流 |
| is_active | BOOLEAN | NOT NULL DEFAULT true | |
| token_lifetime_seconds | INTEGER | NOT NULL DEFAULT 3600 | token 有效期 |
| created_at | TIMESTAMPTZ | NOT NULL | |
| updated_at | TIMESTAMPTZ | NOT NULL | |
| created_by | UUID | | |
| updated_by | UUID | | |
| deleted_at | TIMESTAMPTZ | | |
| version | INTEGER | NOT NULL DEFAULT 1 | |
### 3.2 FHIR Resource 映射
| HMS 实体 | FHIR R4 Resource | 说明 |
|----------|-----------------|------|
| Patient | Patient | 患者基本信息 |
| Doctor | Practitioner | 医护人员 |
| Department | Organization | 科室/组织 |
| Appointment | Appointment | 预约 |
| VitalSign | Observation | 体征数据(手动录入) |
| DeviceReading | Observation | 设备采集数据 |
| Device | Device | 设备注册信息 |
| HealthAlert | Flag | 告警 |
| LabReport | DiagnosticReport | 化验报告 |
| Consultation | Encounter | 咨询记录 |
| FollowUpTask | Task | 随访任务 |
### 3.3 LOINC 编码映射
| ReadingType | LOINC Code | Display |
|-------------|-----------|---------|
| HeartRate | 8867-4 | Heart rate |
| SpO2 | 2708-6 | Oxygen saturation in Arterial blood |
| BloodPressureSystolic | 8480-6 | Systolic blood pressure |
| BloodPressureDiastolic | 8462-4 | Diastolic blood pressure |
| BloodGlucose | 2339-0 | Glucose in Blood |
| Temperature | 8310-5 | Body temperature |
| Weight | 29463-7 | Body weight |
| BodyFat | 41982-0 | Body fat percentage |
| Steps | 55423-8 | Number of steps in 24 hours |
| RespiratoryRate | 9279-1 | Respiratory rate |
### 3.4 降采样策略
| 数据年龄 | 存储策略 | 查询来源 |
|----------|---------|---------|
| 0-24h | 原始数据 `device_readings` | 实时看板、详情页 |
| 1-30 天 | 小时聚合 `device_reading_hourly` | 趋势图、日报 |
| 30+ 天 | 日聚合 `device_reading_daily` | 月报、长期趋势 |
| 保留原始数据 | 可配置(默认 90 天后归档到冷存储) | 审计、科研需要时恢复 |
**数据量估算**1000 活跃患者,手环每 5 分钟采集 4 种指标):
| 指标 | 日数据量 | 月数据量 |
|------|---------|---------|
| 原始读数 | ~115 万条 | ~3,450 万条 |
| 小时聚合 | ~2,880 条 | ~8.6 万条 |
| 日聚合 | ~4,000 条 | ~12 万条 |
降采样后存储压力降低 **400 倍**,查询长期趋势从扫描千万行降到扫描几百行。
---
## 4. DeviceAdapter 与 DeviceGateway 接口设计
### 4.1 现有 BLE 适配器架构(已实现)
BLE 设备的适配器模式运行在**小程序端TypeScript**,已有完整实现:
```typescript
// 已有接口 — services/ble/types.ts
interface DeviceAdapter {
name: string;
supportedModels: string[];
serviceUUIDs: string[];
notifyCharacteristics: BLECharacteristic[];
readCharacteristics: BLECharacteristic[];
parseNotification(data: ArrayBuffer): NormalizedReading[];
parseReadResponse(data: ArrayBuffer): NormalizedReading[];
}
type DeviceType = 'heart_rate' | 'blood_oxygen' | 'steps' | 'sleep'
| 'temperature' | 'stress' | 'blood_pressure' | 'blood_glucose';
interface NormalizedReading {
device_type: DeviceType;
values: Record<string, number | string>;
metric?: string;
measured_at: string;
}
```
**已实现的适配器:**
- `XiaomiBandAdapter.ts` — 小米手环(心率/血氧/步数/睡眠)
- `BloodPressureAdapter.ts` — 蓝牙血压计8 个型号,含欧姆龙/AND/iHealth
- `GlucoseMeterAdapter.ts` — 蓝牙血糖仪
**已有的 BLEManager 功能:**
- 适配器注册 + 按设备名自动匹配
- BLE 扫描 + 连接管理
- 数据同步 + 本地缓冲(最大 2000 条)
- 可配置:扫描超时、每次同步最大条数、重试次数
### 4.2 BLE 适配器扩展V1 增强)
V1 需要新增的适配器和功能:
| 新增内容 | 说明 |
|----------|------|
| `HuaweiBandAdapter.ts` | 华为手环Band 7/8/9 |
| `GenericBleAdapter.ts` | 通用 BLE 适配器(标准 Health Thermometer Profile |
| `DataSyncScheduler.ts` | 定时同步调度(每天至少 1 次自动同步) |
| BLEManager 离线增强 | DataBuffer 持久化到 Storage网络恢复后自动提交 |
### 4.3 现有 device_type 与 FHIR 映射
现有系统使用 `device_type` 字段8 种),与 FHIR Observation 的 LOINC 编码映射:
| device_type现有 | FHIR Observation code | LOINC |
|---------------------|----------------------|-------|
| heart_rate | Heart rate | 8867-4 |
| blood_oxygen | Oxygen saturation | 2708-6 |
| blood_pressure | Blood pressure panel | 85354-9含收缩压 8480-6 + 舒张压 8462-4 |
| blood_glucose | Glucose in Blood | 2339-0 |
| temperature | Body temperature | 8310-5 |
| steps | Steps | 55423-8 |
| sleep | Sleep duration | 93832-4 |
| stress | Stress level | 自定义(无标准 LOINC |
**设计决策:保持现有 `device_type` 而非引入 `ReadingType`**
现有系统用一个 `device_type`(如 `blood_pressure`+ JSONB `raw_value`(含收缩压/舒张压)的模式存储多值数据。这比拆分为多行(每行一个 reading_type + 标量 value更高效。FHIR 转换层负责将 `blood_pressure` 的 JSONB 拆分为独立的 Systolic/Diastolic Observation。不在数据层做拆分。
### 4.4 同步流程(已实现,不需变更)
```
1. 用户打开小程序 → BLEManager 检查已配对设备
2. 扫描 → AdapterRegistry.matchAdapter() 匹配适配器
3. 适配器连接设备 → parseNotification/parseReadResponse
4. NormalizedReading[] → 本地缓冲ble_pending_readings
5. 批量提交 → POST /api/v1/health/device/readings
6. 后端 batch_create_readings() → 去重 → 存储 → 小时聚合 → 事件发布
7. EventBus → alert_engine.evaluate_rules() → 告警触发 → SSE 推送
8. 同步完成 → 更新 patient_devices.last_sync_at
```
### 4.3 同步流程
```
1. 用户打开小程序 → DataSyncScheduler 检查同步计划
2. BleManager 扫描已配对设备 → AdapterRegistry 匹配适配器
3. 适配器连接设备 → read_history(since_last_sync)
4. 批量读取的 RawDeviceReading[] → DataBuffer 暂存
5. 网络可用时批量提交 → POST /api/v1/health/device/readings
6. 后端校验 → 存储 → 降采样触发 → 告警评估 → 事件发布
7. 同步完成 → 更新 last_synced_at → 显示同步结果给用户
```
### 4.5 DeviceGateway TraitV2 扩展点,新增)
专业医疗设备的接入抽象,运行在**后端**Rust
```rust
/// 网关接收的标准化医疗数据
struct GatewayObservation {
device_id: String,
patient_id: Option<String>,
observation_type: String, // HL7 OBX-3 或 DICOM tag
value: serde_json::Value,
unit: Option<String>,
observed_at: DateTime<Utc>,
performer: Option<String>,
metadata: serde_json::Value,
}
/// 设备网关 trait — 处理非 BLE 协议
#[async_trait]
trait DeviceGateway: Send + Sync {
fn gateway_id(&self) -> &str;
fn protocol(&self) -> GatewayProtocol;
async fn start_listening(&self, config: GatewayConfig) -> Result<()>;
async fn stop_listening(&self) -> Result<()>;
async fn parse_message(&self, raw: &[u8]) -> Result<Vec<GatewayObservation>>;
}
enum GatewayProtocol {
Hl7V2,
Hl7Fhir,
Dicom,
SerialRs232,
TcpRaw,
}
```
### 4.6 网关插件化接入流程
```
第三方设备厂商接入流程:
1. 实现 DeviceGateway trait或使用通用 HL7/DICOM 网关)
2. 打包为 WASM 插件(复用 erp-plugin 基础设施)
3. 租户管理员在设备管理页面启用该网关插件
4. 配置连接参数TCP 端口 / serial 设备路径 / FHIR endpoint
5. 网关开始接收数据 → 自动解析 → 写入 device_readings
```
---
## 5. FHIR API 输出层
### 5.1 API 路由设计
FHIR API 独立于 HMS 内部 API使用独立的认证中间件和路由前缀。
> **注意:** FHIR 使用 `/fhir/R4/` 路由前缀而非 HMS 标准的 `/api/v1/`。这是 FHIR 合规性要求 — FHIR 标准规定服务器使用标准 URL 路径,合作方系统期望标准的 FHIR 端点。此豁免仅适用于 FHIR 路由组,不影响内部 API。
```
/fhir/R4/ — FHIR R4 标准 endpoint
├── /metadata — CapabilityStatement服务器能力声明
├── /Patient — 患者资源 CRUD
│ ├── GET / — 搜索_id, name, identifier, birthdate
│ ├── GET /{id} — 读取单个
│ └── GET /{id}/$everything — 患者全景数据
├── /Observation — 观测数据(体征 + 设备读数)
│ ├── GET / — 搜索patient, category, code, date, device
│ ├── GET /{id} — 读取单个
│ └── GET /$lastn — 患者最近 N 次观测
├── /Device — 设备资源
│ ├── GET / — 搜索
│ ├── GET /{id} — 读取
│ └── POST / — 注册新设备V2a
├── /DiagnosticReport — 化验/诊断报告
│ ├── GET / — 搜索
│ └── GET /{id} — 读取
├── /Encounter — 咨询/就诊记录
│ ├── GET / — 搜索
│ └── GET /{id} — 读取
├── /Appointment — 预约
│ ├── GET / — 搜索
│ ├── POST / — 创建V2a
│ └── PATCH /{id} — 更新V2a
├── /Practitioner — 医护人员
│ ├── GET / — 搜索
│ └── GET /{id} — 读取
└── /Task — 随访任务
├── GET / — 搜索
└── GET /{id} — 读取
```
### 5.2 HMS → FHIR Observation 转换示例
以设备采集的心率数据为例:
```json
// HMS device_readings 记录
{
"id": "01901abc-def0-7000-8000-000000000001",
"tenant_id": "t001",
"device_id": "dev-xiaomi-8",
"patient_id": "p-12345",
"reading_type": "heart_rate",
"value": 78.0,
"unit": "bpm",
"measured_at": "2026-05-04T14:30:00Z",
"quality_indicator": "raw",
"source": "ble_sync"
}
// ↓ 转换为 FHIR Observation ↓
{
"resourceType": "Observation",
"id": "01901abc-def0-7000-8000-000000000001",
"status": "final",
"category": [{
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/observation-category",
"code": "vital-signs",
"display": "Vital Signs"
}]
}],
"code": {
"coding": [{
"system": "http://loinc.org",
"code": "8867-4",
"display": "Heart rate"
}]
},
"subject": {
"reference": "Patient/p-12345"
},
"device": {
"reference": "Device/dev-xiaomi-8"
},
"effectiveDateTime": "2026-05-04T14:30:00Z",
"valueQuantity": {
"value": 78.0,
"unit": "beats/minute",
"system": "http://unitsofmeasure.org",
"code": "/min"
},
"meta": {
"source": "hms-device-ble",
"lastUpdated": "2026-05-04T14:30:01Z"
}
}
```
### 5.3 合作伙伴认证OAuth2 Client Credentials
```
认证流程:
1. 合作方在 HMS 管理后台注册
→ 获得 client_id + client_secret
→ 配置允许的 FHIR 资源范围和患者数据范围
2. 合作方请求令牌
POST /oauth/token
Body: grant_type=client_credentials&client_id=xxx&client_secret=xxx
&scope=fhir/Patient.read+Observation.read
→ 返回 access_token (JWT, 1小时有效)
3. 合作方调用 FHIR API
GET /fhir/R4/Observation?patient=p-12345&category=vital-signs&date=gt2026-05-01
Header: Authorization: Bearer <access_token>
4. HMS 验证
→ JWT 签名验证
→ tenant_id 匹配(合作方只能访问所属租户数据)
→ scope 验证(只允许已授权的资源操作)
→ 患者 ID 范围验证(合作方只能访问授权的患者列表)
```
### 5.4 数据隔离矩阵
| 维度 | 隔离方式 | 说明 |
|------|---------|------|
| 租户 | JWT `tenant_id` | 合作方只能访问所属租户数据 |
| 资源类型 | OAuth scope | `Patient.read` / `Observation.read` 等 |
| 患者范围 | 白名单/黑名单 | 合作方可访问的患者 ID 列表 |
| 时间窗口 | 可选限制 | 数据只开放最近 N 天 |
| 频率 | 限流 | 每合作方每分钟最大请求数 |
### 5.5 $everything 操作
患者全景数据一次请求获取,用于 HIS 系统拉取完整健康档案、体检中心出报告参考、转诊数据导出:
```
GET /fhir/R4/Patient/{id}/$everything
返回 Bundle包含
├── Patient 本身
├── 所有 Observation体征 + 设备读数,支持 _count 分页)
├── 所有 DiagnosticReport化验报告
├── 所有 Encounter就诊记录
├── 所有 Appointment预约
├── 所有 Task随访任务
└── 关联的 Device 列表
```
---
## 6. 告警引擎与实时监控
### 6.1 现有告警引擎(已实现)
`alert_engine.rs` 已实现完整的规则评估流程:
- 加载租户+设备类型的活跃规则
- 批量查询近期告警实现 cooldown
- 批量查询 `vital_signs_hourly` 近 168 小时数据
- 3 种评估器:`single_threshold`(阈值)、`consecutive`(连续超标)、`trend`(趋势恶化)
- 血压/血糖特殊处理(从 JSONB raw_value 提取收缩压/舒张压)
- 告警创建 + `alert.triggered` 事件发布
- 事件消费者发送站内通知
**本设计不做重复实现。** 以下 §6.2-6.5 仅描述**增强内容**。
### 6.2 告警降噪增强
现有引擎已有 cooldown 机制。V1 增加两级降噪:
| 层级 | 现有 | 增强 |
|------|------|------|
| 规则级 | cooldown_minutes 已实现 | 无需变更 |
| 患者级 | 无 | 新增:同一患者连续低级别告警 → 自动升级为高级别 |
| 系统级 | 无 | 新增5 分钟内同一设备的多个告警合并为一条通知 |
```
device_readings 写入
规则加载器(从 device_alert_rules 缓存)
┌─────────────────────────────────────────┐
│ 规则评估器 │
│ │
│ ┌─────────┐ ┌──────────┐ ┌─────────┐ │
│ │单次阈值 │ │连续超标 │ │趋势恶化 │ │
│ │评估器 │ │评估器 │ │评估器 │ │
│ └────┬────┘ └─────┬────┘ └────┬────┘ │
│ │ │ │ │
│ └────────────┴───────────┘ │
│ │ │
│ 告警触发? │
│ Yes ↓ │
└──────────────────┼─────────────────────┘
生成 HealthAlert
EventBus 发布
device.alert.triggered
┌──────────┼──────────┐
▼ ▼ ▼
SSE 推送 消息通知 行动收件箱
(医生Web) (护士站) (待办聚合)
```
### 6.2 规则类型
**类型 1单次阈值Threshold**
```json
{
"rule_name": "心动过速预警",
"reading_type": "heart_rate",
"rule_type": "threshold",
"condition": {
"operator": "gt",
"value": 120,
"unit": "bpm"
},
"severity": "high",
"cooldown_minutes": 30
}
```
**类型 2连续超标Consecutive**
```json
{
"rule_name": "高血压持续预警",
"reading_type": "blood_pressure_systolic",
"rule_type": "consecutive",
"condition": {
"operator": "gt",
"value": 140,
"consecutive_count": 3,
"within_minutes": 60
},
"severity": "critical",
"cooldown_minutes": 120
}
```
**类型 3趋势恶化Trend**
```json
{
"rule_name": "血糖恶化趋势",
"reading_type": "blood_glucose",
"rule_type": "trend",
"condition": {
"baseline_window_days": 7,
"change_threshold_percent": 20,
"direction": "up",
"min_samples": 5
},
"severity": "medium",
"cooldown_minutes": 1440
}
```
### 6.3 实时看板(医生 Web 端)
```
┌─────────────────────────────────────────────────────────────────┐
│ 实时体征监控台 [自动刷新] │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 告警摘要栏 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 危急 2 │ │ 高危 5 │ │ 中等 12 │ │ 低危 3 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ 患者列表(按告警优先级排序) │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 🔴 张三 | 心率 135bpm ↑ | 血压 158/95 | 最近同步 2分钟前 │ │
│ │ 🟠 李四 | 血糖 285mg/dL | 趋势: 7天+35% | 15分钟前 │ │
│ │ 🟡 王五 | SpO2 93% ↓ | 心率 98bpm | 30分钟前 │ │
│ │ 赵六 | 心率 72bpm 正常 | 步数 3,200 | 1小时前 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ 患者详情(点击展开) │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 张三 | 男 58岁 | 高血压+糖尿病 | 设备: 小米手环8 │ │
│ │ │ │
│ │ 心率趋势 (24h): 血压趋势 (7天): │ │
│ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │
│ │ │ ╱╲ ╱╲ ╱╲╱╲ │ │ ╱╲ │ │ │
│ │ │ ╲╱╱ ╲ ╲ │ │╱ ╲╱╱ ╱╲╱╱ ╱╲ │ │ │
│ │ │╱ ╲╱ ╲│ │ ╲╱ ╲ │ │ │
│ │ └─────────────────────┘ └─────────────────────┘ │ │
│ │ │ │
│ │ 最近告警: │ │
│ │ 14:32 心率 135bpm → 触发心动过速预警 (高危) │ │
│ │ 14:15 收缩压 158mmHg → 连续第3次超标 (危急) │ │
│ │ │ │
│ │ [查看完整数据] [AI 分析] [发起咨询] [调整告警阈值] │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### 6.4 SSE 推送(已实现,需增强)
现有 `sse_handler.rs` 已实现三种事件推送:
| SSE 事件 | 触发源 | 过滤方式 |
|----------|--------|---------|
| `message` | `message.sent` | 按收件人 user_id |
| `alert` | `alert.triggered` | 按医患关系 |
| `vital_update` | `device.readings.synced` | 按医患关系 |
V1 需增强的内容:
| 增强 | 说明 |
|------|------|
| `Last-Event-ID` 支持 | SSE 重连时从断点续传 |
| 心跳机制 | 定期 ping 保持连接活跃 |
| 订阅范围 | 医生可选择性订阅特定患者的更新 |
### 6.5 告警降噪机制
高频设备数据容易产生告警疲劳,三级降噪:
| 层级 | 机制 | 说明 |
|------|------|------|
| **规则级** | cooldown 窗口 | 同一患者同一规则在冷却期内不重复触发 |
| **患者级** | 告警升级 | 同一患者连续低级别告警 → 自动升级为高级别 |
| **系统级** | 告警聚合 | 5 分钟内同一设备的多个告警合并为一条通知 |
```
示例:
10:00 心率 > 100 → 中等告警(单独通知)
10:05 心率 > 105 → 中等告警(冷却期内,不通知)
10:10 心率 > 110 → 连续3次超标 → 升级为高危告警(通知)
10:15 SpO2 < 95% → 中等告警 → 与心率告警聚合为 1 条通知
```
---
## 7. 演进路线与风险分析
### 7.1 V1 详细周计划12 周)
**W1-2基础设施 + 数据层**
- devices / device_readings / device_reading_hourly / device_reading_daily 表迁移
- ReadingType 枚举 + LOINC 映射常量
- 摄入 API handlerPOST /api/v1/health/device/readings
- 降采样 background task小时/日聚合)
- EventBus 新增事件类型
**W3-4小程序 BLE 采集层**
- BleManager + AdapterRegistry
- XiaomiBandAdapter第一个适配器
- DataSyncScheduler + DataBuffer
- 设备管理页面(绑定/解绑/同步状态)
- E2E 测试:手环 → 小程序 → 后端 → 数据库
**W5-6告警引擎**
- device_alert_rules 表 + CRUD API
- 三种规则评估器threshold / consecutive / trend
- 告警降噪cooldown + 升级 + 聚合)
- SSE 推送扩展
- 规则引擎单元测试 + 集成测试
**W7-8告警看板 + 第二个适配器**
- 实时体征监控台页面Web
- OmronBpAdapter血压计验证适配器扩展性
- 患者体征趋势图(复用 ECharts
- 看板页面测试
**W9-10FHIR 只读 API**
- /fhir/R4/ 路由组 + 独立认证中间件
- HMS → FHIR 转换层Patient / Observation / Device
- 搜索参数支持patient, category, code, date
- $everything 操作
- FHIR 一致性测试
**W11-12合作伙伴认证 + 集成测试**
- OAuth2 Client Credentials 实现
- 数据隔离矩阵(租户/资源/患者范围/频率)
- API 限流 + 审计日志
- 合作方管理页面(注册/密钥/权限配置)
- 全链路集成测试 + 性能测试
### 7.2 V2 扩展路线
| 阶段 | 时间 | 内容 | 前置条件 |
|------|------|------|---------|
| **V2a** | 4-6 周 | 居家设备适配器 ×3 + FHIR 写入 API | V1 完成 |
| **V2b** | 6-8 周 | DeviceGateway + HL7 适配 + Webhook 推送 | V2a 完成 |
| **V2c** | 4-6 周 | 合作方自助接入 + 设备厂商插件 SDK | V2b 完成 |
### 7.3 与 Q2 路线图的协同
当前 Q2 路线图W1-8已排满技术债清理和 AI 前端补全。建议策略:
| 策略 | 说明 | 影响 |
|------|------|------|
| **顺延(推荐)** | Q2 专注技术债 + AI 前端Q37-9 月)启动 IoT V1 | 不影响 Q2 计划IoT 交付推迟到 Q3 末 |
| **并行** | Q2 W5-8 开始 IoT 基础设施(表迁移 + 摄入 APIQ3 做前端和 FHIR | 前后端分离交付,风险可控 |
| **替换** | 用 IoT V1 替换 Q2 的实时体征管线计划(两者本质相同) | 最紧凑,但需重新评估测试覆盖目标 |
### 7.4 风险矩阵
| 风险 | 概率 | 影响 | 缓解措施 |
|------|:----:|:----:|---------|
| **BLE 协议碎片化** — 不同厂商 BLE Service UUID 不同 | 高 | 中 | V1 只支持 2 款设备AdapterRegistry 抽象层隔离差异 |
| **FHIR 合规性** — 自实现 FHIR server 可能不完全符合规范 | 中 | 高 | V1 只做 read-only 6 个资源,后续评估引入 fhir-sdk crate |
| **高频数据写入性能** — 日增 115 万条原始读数 | 中 | 高 | 降采样 + 分区索引 + 批量写入PostgreSQL 单表可承受千万级 |
| **小程序 BLE 兼容性** — 微信 BLE API 在不同手机行为不一致 | 高 | 中 | 真机测试矩阵DataBuffer 离线兜底 |
| **合作伙伴数据安全** — 医疗数据通过 API 暴露给第三方 | 低 | 极高 | 多层隔离(租户+scope+患者白名单+限流+审计日志) |
| **告警疲劳** — 高频数据导致过多告警 | 高 | 中 | 三级降噪 + cooldown + 告警升级 + 可配置阈值 |
---
## 8. 与现有模块的变更清单
### 8.1 erp-health 变更
| 变更 | 类型 | 状态 | 说明 |
|------|------|------|------|
| device_readings Entity + 摄入服务 | 已有 | — | 完整的批量摄入+去重+校验 |
| vital_signs_hourly 聚合 | 已有 | — | 小时聚合完整 |
| alert_rules + alert_engine | 已有 | — | 3 种规则类型完整 |
| EventBus 事件发布+消费 | 已有 | — | device.readings.synced + alert.triggered |
| patient_devices 增强迁移 | 迁移 | 增强 | 增加 status/firmware_version/metadata 列 |
| vital_signs_daily Entity + 迁移 | 迁移 | **新增** | 日聚合表 |
| `fhir` 子模块 | 模块 | **新增** | FHIR R4 路由组 + HMS→FHIR 转换层 |
| 日聚合 background task | 代码 | **新增** | 从小时数据聚合为日数据 |
| 告警降噪增强 | 代码 | 增强 | 患者级升级 + 系统级聚合 |
### 8.2 erp-core 变更
| 变更 | 类型 | 状态 | 说明 |
|------|------|------|------|
| 事件类型 | 已有 | — | device.readings.synced + alert.triggered 已定义 |
| 无新增事件 | — | — | 现有事件足够 |
### 8.3 erp-auth 变更
| 变更 | 类型 | 状态 | 说明 |
|------|------|------|------|
| api_clients 表 + 迁移 | 迁移 | **新增** | 合作方 client_id/secret_hash/scope |
| OAuth2 Client Credentials 流程 | 功能 | **新增** | 合作伙伴专用认证,与现有 JWT 中间件并行 |
| FHIR 路由认证中间件 | 代码 | **新增** | 从 Bearer token 提取 api_client 上下文 |
| client_secret 使用 Argon2 哈希 | 安全 | **新增** | 与现有用户密码哈希策略一致 |
### 8.4 erp-server 变更
| 变更 | 类型 | 状态 | 说明 |
|------|------|------|------|
| `/fhir/R4/*` 路由组 | 代码 | **新增** | 挂载 FHIR API使用独立认证中间件 |
| `/oauth/token` endpoint | 代码 | **新增** | OAuth2 token 签发 |
| 日聚合 background task 注册 | 代码 | **新增** | 复用现有 tasks 框架 |
### 8.5 erp-message 变更
| 变更 | 类型 | 状态 | 说明 |
|------|------|------|------|
| SSE handler | 已有 | — | 已推送 vital_update + alert 事件 |
| Last-Event-ID 重连支持 | 增强 | 增强 | SSE 断线续传 |
| 心跳机制 | 增强 | 增强 | 保持连接活跃 |
### 8.6 小程序变更
| 变更 | 类型 | 状态 | 说明 |
|------|------|------|------|
| BLE 采集框架 + 3 个适配器 | 已有 | — | XiaomiBand/BloodPressure/GlucoseMeter |
| HuaweiBandAdapter.ts | 适配器 | **新增** | 华为手环支持 |
| GenericBleAdapter.ts | 适配器 | **新增** | 通用 BLE 设备 |
| DataSyncScheduler.ts | 服务 | **新增** | 定时自动同步调度 |
| DataBuffer 持久化增强 | 增强 | 增强 | 离线缓冲持久化到 Storage |
| 设备管理页面 | 页面 | 增强 | 增加设备状态/同步历史展示 |
### 8.7 Web 前端变更
| 变更 | 类型 | 状态 | 说明 |
|------|------|------|------|
| 实时体征监控台页面 | 页面 | **新增** | 告警摘要 + 患者列表 + 趋势图 |
| 设备管理页面 | 页面 | **新增** | 设备列表/状态/同步历史 |
| 告警规则配置页面 | 页面 | **新增** | 规则 CRUD + 启用/禁用 |
| 合作方管理页面(管理后台) | 页面 | **新增** | 注册/密钥/权限/审计 |
| SSE 事件监听增强 | 代码 | 增强 | vital_update 事件已在监听,增加 Last-Event-ID |