diff --git a/docs/discussions/2026-05-04-iot-fhir-platform-ecosystem-brainstorm.md b/docs/discussions/2026-05-04-iot-fhir-platform-ecosystem-brainstorm.md new file mode 100644 index 0000000..c56645e --- /dev/null +++ b/docs/discussions/2026-05-04-iot-fhir-platform-ecosystem-brainstorm.md @@ -0,0 +1,66 @@ +# IoT 设备采集 + FHIR 开放平台生态 — 发散式探讨 + +> 日期: 2026-05-04 | 参与者: 用户 + Claude + +## 背景 + +HMS 项目已进入功能基本完整阶段(575 次提交,B+ 架构评分)。Q2 路线图聚焦技术债清理和 AI 前端补全。用户希望探讨更长远的新功能方向:实时体征 + IoT 设备采集 与 平台生态扩展的融合。 + +## 讨论要点 + +### 1. 平台定位 + +- HMS 作为"健康数据枢纽" — 设备数据流入,通过标准接口流出 +- 选择**渐进演进**策略:先修炼 IoT 采集内功,再开放 API 给外部系统 +- 设备范围:**全场景覆盖**(穿戴 + 居家医疗 + 专业设备),但分阶段实施 + +### 2. 架构方案论证 + +比较了三种设备集成架构: + +| 方案 | 优点 | 缺点 | +|------|------|------| +| A: 统一适配器 | 上层逻辑一致 | 穿戴和医疗设备差异太大,抽象变"最低公分母" | +| B: 分通道 | 各通道独立优化 | 校验/存储逻辑重复,代码分散 | +| **C: 混合(采纳)** | BLE 统一适配器 + 网关扩展,平衡 MVP 速度和扩展性 | 两套模式需维护 | + +### 3. 数据输出标准 + +- 选择 **HL7 FHIR R4** — 国际医疗互操作标准,合作方无需学习私有接口 +- 输出方式:**拉取 + 推送分层** — V1 API 拉取,V2 Webhook 推送 +- 接入策略:**合作伙伴专属** — 审核制,多层隔离 + +### 4. Spec Review 发现 + +代码库中已存在大量设计为"新增"的功能: + +| 已存在 | 位置 | +|--------|------| +| device_readings 表 + 批量摄入 | erp-health entity + service | +| vital_signs_hourly 小时聚合 | erp-health entity | +| alert_rules + alert_engine(3 种规则) | erp-health entity + service | +| BLE 适配器框架 + 3 个适配器(TypeScript) | miniprogram services/ble/ | +| SSE 推送(vital_update + alert) | erp-message handler | +| EventBus 事件 + 消费者 | erp-health event.rs | + +真正新增的只有:FHIR API 层、OAuth2 合作伙伴认证、日聚合表、设备网关 trait(V2) + +### 5. 设计决策 + +- 保持现有 `device_type`(8种)+ JSONB `raw_value` 模式,不引入 ReadingType 拆分 +- BLE 适配器继续用 TypeScript(小程序端),不在 Rust 后端定义设备交互 trait +- FHIR 路由使用 `/fhir/R4/` 前缀(合规性要求),豁免于 `/api/v1/` 约定 +- client_secret 使用 Argon2 哈希(与现有密码策略一致) + +## 结论 + +1. **架构方案 C(混合)确认** — BLE 统一适配器 + 设备网关扩展点 +2. **渐进演进:V1(12周)→ V2a → V2b → V3** +3. **大量基础设施已存在** — V1 的核心工作量在 FHIR API 层和合作伙伴认证,而非设备采集 +4. **Q2 路线图协同** — 建议顺延至 Q3 启动 IoT V1,Q2 专注现有技术债 + +## 关联文档 + +- 设计规格:`docs/superpowers/specs/2026-05-04-iot-fhir-platform-ecosystem-design.md` +- 已有实时体征设计:`docs/superpowers/specs/2026-04-26-realtime-vital-signs-pipeline-design.md` +- Q2 路线图:`docs/superpowers/specs/2026-05-03-q2-roadmap-design.md` diff --git a/docs/superpowers/specs/2026-05-04-iot-fhir-platform-ecosystem-design.md b/docs/superpowers/specs/2026-05-04-iot-fhir-platform-ecosystem-design.md new file mode 100644 index 0000000..465c3a8 --- /dev/null +++ b/docs/superpowers/specs/2026-05-04-iot-fhir-platform-ecosystem-design.md @@ -0,0 +1,955 @@ +# 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 trait(V2 实现) +- 后端设备数据摄入 API(批量提交 + 降采样) +- 告警规则引擎(单次阈值 + 连续超标 + 趋势恶化) +- SSE 推送扩展(体征聚合更新 + 告警通知) +- FHIR R4 只读 API — 10 个资源端点 +- OAuth2 Client Credentials 合作伙伴认证 +- 数据分区与降采样策略 + +**不做(本设计不覆盖):** + +- ICU 级实时监控(亚秒级,需要专用系统) +- AI 驱动的异常检测(erp-ai 模块职责,本设计只提供数据输入) +- FHIR 写入 API(V2a 范围) +- Webhook 事件推送(V2b 范围) +- 设备厂商自助接入 SDK(V2c 范围) +- 多实例部署的事件总线演进(单实例足够) + +--- + +## 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; + 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 Trait(V2 扩展点,新增) + +专业医疗设备的接入抽象,运行在**后端**(Rust): + +```rust +/// 网关接收的标准化医疗数据 +struct GatewayObservation { + device_id: String, + patient_id: Option, + observation_type: String, // HL7 OBX-3 或 DICOM tag + value: serde_json::Value, + unit: Option, + observed_at: DateTime, + performer: Option, + 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>; +} + +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 + +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 handler(POST /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-10:FHIR 只读 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 前端,Q3(7-9 月)启动 IoT V1 | 不影响 Q2 计划,IoT 交付推迟到 Q3 末 | +| **并行** | Q2 W5-8 开始 IoT 基础设施(表迁移 + 摄入 API),Q3 做前端和 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 |