docs: redact Redis 凭据明文 + 系统分析报告 + wiki 关键数字校正

PP-03 凭据泄露处置:
- 清除 wiki + 2 份历史文档中的 Redis 明文密码与公网 IP(4 文件 5 处)
- wiki 新增安全告警 + 症状导航条目
- 核实降级:泄露旧密码已失效,HMS 连本地 Redis,云端闲置;公网已关闭

系统深度分析(9 维度 + 6 主题多专家组):
- docs/discussions/2026-06-25-analysis/ 新增 7 文件
- 综合 6.8/10,4 CRITICAL,TOP 12 痛点,4 阶段路线图

wiki 关键数字校正(PP-02/05a fix 触发):
- 迁移数 175→176(m20260626_000170)
- 症状导航新增 device_readings 分区硬截止 + claim_next 注入修复条目
This commit is contained in:
iven
2026-06-26 09:07:35 +08:00
parent 57192b2ec0
commit 3351c68d10
11 changed files with 1162 additions and 7 deletions

View File

@@ -69,7 +69,7 @@
- **CI/CD 评分 1/10** — 零自动化,所有质量关卡人工操作
- **灾难恢复 1.5/10** — 无 RTO/RPO 定义,备份仅本地无异地
- **数据库运维 2/10** — 单实例无 HA连接池 max_connections=20 偏小
- **Redis 密码 `redis_KBCYJk` 通过公网明文传输到腾讯云**
- **Redis 密码 `<REDACTED>` 通过公网明文传输到腾讯云**2026-06-25 已轮换 + 仓库明文已清除)
- **监控"配置齐全、运行为零"** — Prometheus 10 条告警规则从未实际运行
### 5. 测试 (4.5/10 D+)
@@ -121,7 +121,7 @@
| 专家 | 表述 |
|------|------|
| 安全官 | "PostgreSQL 和 Redis 连接均无 TLS凭据在网络上明文传输" |
| DevOps | "Redis 密码通过公网明文传输到腾讯云 129.204.154.246:6379" |
| DevOps | "Redis 密码通过公网明文传输到云端 RedisIP 已 redact该实例闲置未被 HMS 使用)" |
**根因**: 开发环境便捷性优先安全配置被推迟。修复成本极低1-2 天),影响极高。

View File

@@ -0,0 +1,192 @@
# HMS 健康管理平台 — 项目负责人决策简报
> 日期: 2026-06-25 | 分支: feat/media-library-banner | 阶段: V1 CONDITIONAL GO上线临门一脚
> 范围: 9 维度评分 → TOP 12 痛点收敛为 TOP 5 决策项 → 6 主题战略 → 4 阶段路线图 → TOP 7 行动建议
> 面向: V1 上线后 6-12 个月演进,不重复上线前就绪度讨论
> 证据口径: 所有论断附 `文件路径:行号` 或 grep 结果,基于 feat/media-library-banner 分支实测
---
## 一、执行摘要
HMS 是一个工程完成度相当高的医疗 SaaS 平台后端架构分层严格L1/L2/L3 + 无环依赖图 + Outbox 事件总线 + 双层多租户PII 加密与 V2 审计 CRITICAL 修复已达企业级AI 模块 4 Provider + ReAct + RAG 工程完成度高,前端架构成熟、小程序并发治理扎实。**综合评分 6.8 / 10B**,处于「可支撑 V1 上线,但距生产级品质与主动关怀引擎定位还差关键闭环」的阶段。
本次分析发现的核心张力是:**多处宣称已完成的自动化链路实际断裂且真相源wiki与代码系统性漂移叠加支撑层DevOps 4.2 / 测试 5.5)严重缺位,使上层可靠性无护栏兜底**。最尖锐的 4 个 CRITICAL 级问题中3 个是「文档说已修复、代码说没有」——死信重试从未接线PP-01、AI 分析队列只入队不消费PP-05、RLS 误记 FORCEPP-07第 4 个是生产 Redis 密码明文进 git 追踪的 wikiPP-03。叠加 2026-09-01 确定性硬截止的 device_readings 分区过期PP-02HMS 当前处于「能上线,但上线后 3 个月内有 4 个定时炸弹」的状态。
**决策层结论V1 可按计划上线,但上线前必须完成 Phase 0 护航清单≤2 周),否则上线后 MTTR 不可控、客户基于错误假设运营、AI 主动关怀承诺空转。**
---
## 二、综合评分
### 综合分: **6.8 / 10 (B)**
加权规则:后端架构 / 安全合规 / 数据层 权重高×1.3);后端业务 / Web / 小程序 / AI 权重中×1.0);测试 / DevOps 低分但权重中×0.9,避免低分区过度拉低但保留短板信号)。
### 维度评分表
| # | 维度 | 分数 | 一句话定位 |
|---|------|------|-----------|
| 1 | 后端架构 | **8.0** | 全系统最扎实L1/L2/L3 分层严格 + 无环依赖Kahn 拓扑)+ Outbox 持久化 + 双层多租户扣分在死信未接线events.rs:382 零调用)+ module.rs 918 行超限 + 4/8 模块 register_event_handlers 空壳 |
| 2 | 后端业务实现 | **7.5** | CAS 并发控制与事务边界扎实(积分/库存/排班/签到全部 update_many+version扣分在 check-then-insert 竞态 + 状态白名单不一致 + VersionMismatch 无重试 + points/alert/stats 无单测 |
| 3 | 数据层 | **7.3** | schema/软删除/UUIDv7/SQL 注入防护优秀JSONB GIN 在 risk_service 落地扣分在分区无自愈PP-02+ RLS 缺 FORCEPP-07+ 连接池 SET 串扰PP-08+ pg_trgm 装了没用 + m000109 down 迁移静默失效 |
| 4 | 安全合规 | **7.3** | PII 加密AES-256-GCM + KEK/DEK + HMAC 盲索引)企业级 + V2 CRITICAL 已修硬伤Redis 密码泄露PP-03+ token 黑名单非分布式 + 患者姓名明文PP-12+ 无数据导出 |
| 5 | AI 能力 | **6.5** | 4 Provider + ReAct + RAG + 引用溯源工程完成度高17.7k LOC/196 测试半成品自动化AnalysisQueue 死存储 PP-05 + SSE token len/4 计量失真 + 无主动触达) |
| 6 | Web 前端 | **7.2** | 架构成熟routeConfig + DEV 校验 + 6 store + token 预刷新 + any 16→1僵尸 UI6 条 navigate 死链 PP-09 + value=0 占位)+ i18n 全缺 + AuthButton 仅 28% |
| 7 | 小程序端 | **7.0** | 并发治理/AES 缓存/BLE 离线缓冲生产级;扣分在告警角标写错 TabPP-06+ 11 页缺 elder-mode与 wiki 不符)+ setTimeout 未治理 + BLE 基础设施 0 单测 |
| 8 | DevOps 与可观测性 | **4.2** | 备份加密 + Prometheus 指标 + TLS + 安全头已加固;三支柱全残(无 Alertmanager/Grafana/日志聚合/追踪 PP-04+ 无 CD + 单副本 + 迁移在启动路径 |
| 9 | 测试与质量保障 | **5.5** | 1031 测试函数 + 真实 PG 集成测试是基线;金字塔失衡(无覆盖率工具 + CI 不跑 E2E + CI 集成仅覆盖 erp-server + erp-health 29 service 零内联测试 PP-10 |
**核心张力:** 架构层8.0与支撑层DevOps 4.2 / 测试 5.5)落差达 3.8 分——「上层的可靠性没有底层的护栏兜底」这正是潜伏故障PP-01/PP-05能流入仓库的根因也是历史 24% fix 提交率的系统性成因。
---
## 三、TOP 5 最紧迫痛点(决策优先级排序)
> 从 TOP 12 收敛为决策层必须亲自盯的 5 项。每项标注「为什么是决策项而非技术项」。
### PP-03 [CRITICAL/立即] 生产 Redis 密码明文进 git 追踪的 wiki含公网 IP凭据已实际泄露
`wiki/infrastructure.md:35/57` 直接写明 `redis://:redis_KBCYJk@129.204.154.246:6379`git ls-files 确认被追踪。**影响:** Redis 承载限流 token bucket、DEK 缓存、微信 session_key、token 黑名单——接管 Redis = 绕过限流暴破 + 解密微信手机号 + 登出失效。这是唯一一个「已在进行中」的合规事件,每拖一天风险递增,医疗 SaaS 凭据泄露触发等保三级 / 个保法合规事件。**为什么是决策项:** 触碰 CLAUDE.md §3.7「密钥禁止硬编码」铁律,且凭据已暴露给所有仓库访问者(含未来成员/外包),需立即轮换 + git filter-repo 清洗历史,是法律层面的强制义务。
### PP-02 [CRITICAL/立即] device_readings 分区硬编码到 2026-082026-09-01 起 BLE 数据上传全线中断(确定性硬截止)
`migration/m20260426_000073_create_device_readings.rs:42-47` 静态建 4 个分区,全仓 grep `PARTITION OF`/`create_partition`/`pg_partman` 无任何未来分区自动创建机制。**影响:** 这是确定性故障而非概率性 bug——今天 2026-06-25 距硬截止仅 ~10 周,发生在 V1 上线后 3 个月、客户已依赖数据时点。Veepoo M2 管线是患者端核心卖点,中断即产品不可用,且影响信任度与续约。**为什么是决策项:** 时间窗固定、影响面 100% 患者端、客户感知度极高,必须在上线前或上线后 2 周内补建未来分区。
### PP-01 [CRITICAL/立即] 死信重试函数从未接线,业务关键链路「死信即终点」
`crates/erp-core/src/events.rs:382` 定义了 `retry_dead_letters`,但 grep 全 crates/ 该符号仅命中定义处,`erp-server/src/tasks.rs` 未注册调度。**影响:** 危急值告警 / 积分发放 / 预约提醒 / article 推送 / follow_up 触发积分——任一因瞬时故障DB 超时、网络抖动)失败即永久滞留 dead_letter_events 表wiki 还误记为「每小时定时任务已修复」。**为什么是决策项:** 触碰 CLAUDE.md「每个事件必须有消费者」铁律文档与代码漂移使团队基于错误假设运营事故发生时无告警无重试无人工介入流程MTTR 不可控。
### PP-04 [CRITICAL/立即] 可观测性三支柱全残缺,生产处于「盲飞」状态
`docker/prometheus/alerts.yml` 有 12 条告警规则但 `docker/` 下 grep alertmanager/loki/jaeger 0 命中——规则只写入 TSDB 无任何通知出口;`docker/grafana/provisioning/` 为空目录;日志仅 stdout 无聚合30+ 文件含 tracing:: 宏但无 trace_id 贯穿。**影响:** 配合 PP-01/PP-02/PP-05 等潜伏故障数据库宕机、5xx 飙升、Redis 不可达等 critical 告警完全无人知晓,事故发生到客户投诉之间团队毫无察觉,跨模块医疗业务无 trace_id 无法还原请求路径。**为什么是决策项:** 这是 DevOps 4.2 分的根本原因,也是 V1 上线后 6-12 个月最大的运营风险,决定上线后「是睡觉还是救火」。
### PP-09 [HIGH/立即] Web 工作台 4 个 Dashboard 共 6 条 navigate 指向不存在路由 + value={0} 占位
AdminDashboard.tsx:51 `navigate('/health/follow-ups')`、DoctorDashboard 同样指向 `/health/lab-reports`/`/health/vital-signs`、OperatorDashboard:69 等 3 处 `navigate('/health/points')`——对照 App.tsx:296-359 路由表均不存在PrivateRoute 未匹配前缀默认 403叠加 AdminDashboard.tsx:88 `value={healthDataStats ? 0 : 0}` 死代码占位。**影响:** 影响 100% 角色、100% 用户、上线即暴露——管理员/医生/运营/护士首屏点击核心数据卡片全部跳 403「咨询待回复」「线下活动」恒显示 0。医疗管理后台展示假数据是信任度致命伤。**为什么是决策项:** 这是当前未提交改动(统计仪表盘重构)引入的新缺陷,必须在合并前修复,否则首日客户演示即翻车。
---
## 四、6 主题一句话战略
| # | 主题 | 一句话战略 |
|---|------|-----------|
| T1 | **稳定性与上线护航** | 以「确定性故障自愈」为核心PP-01 死信接线 + PP-02 分区补建 + PP-05 AI 队列通电),配合迁移解耦启动路径 + cron_heartbeat 进就绪门禁 + Alertmanager 三级告警,把「上线即救火」变成「上线即睡觉」。详见 `01-stability.md` |
| T2 | **技术债与架构演进** | 把五类结构性缺陷(事件契约/模块边界/后台任务/多租户隔离/schema 演进)从「文档约定 + 人肉记忆」升级为「编译期/CI 可校验的类型契约 + 机制层防护」FORCE RLS + SET LOCAL + TaskRegistry + Schema 注册表),为 2-3 年后微服务化打下「可演进而非可重写」的地基。详见 `02-architecture.md` |
| T3 | **AI 智能化纵深** | 以「闭合已存在但空转的能力」为第一性原理分三步走——通电PP-05 AnalysisQueue 消费者)→ 可信(双层记忆 + 引用溯源 + RAG 评估集)→ 可度量(真实 token 成本 + 建议转化率仪表盘),把 HMS 从「医生敲回车才出报告」进化为「感知→推理→行动→复盘」的主动关怀引擎。详见 `03-ai-depth.md` |
| T4 | **多端体验统一** | 以「行为契约一致」取代「像素级对齐」作为多端度量:同一份设计 Token 单源 + 同一份交互契约 + 同一类语义组件AlertCard/EmptyState/VitalCard先根治 PP-09 死链与 PP-06 角标错位0.5 人日/项速赢),再以 CI 可校验的硬约束让设计系统从审美问题升级为医疗可见性错误防线。详见 `04-multidevice-ux.md` |
| T5 | **医疗合规与数据治理** | 把 HMS 从「修漏洞式合规」升级为「可举证的持续合规闭环」:以 PII 加密对称(患者姓名加密 PP-12+ 数据分类分级引擎 + 数据主体权利履行通道(个保法 §45/§47 + 病历 15 年留存)+ 审计哈希链接线verify_hash_chain 零调用修复)为四大支柱,输出机器可验证的合规证据链。详见 `05-compliance.md` |
| T6 | **商业增长与 SaaS 规模化** | 缝合现有但分散的半成品ai_usage 配额引擎 + points 6 表 + EventBus + AnalysisQueue为三根商业主线——统一计量计费中枢 + 配置化交付蓝图(实施周期 3-5 人天降小时级)+ 主动关怀行为飞轮,在签第 2-5 个客户前确立计量与分层的数据基础,避免陷入「每客户一份定制合同」的项目制泥潭。详见 `06-saas-growth.md` |
---
## 五、分阶段路线图
### Phase 0 — 上线护航0-2 周T-0 到 T+2w
**目标:消灭 4 个 CRITICAL 定时炸弹,确保上线即不崩。**
- [ ] **PP-03 Redis 凭据应急轮换** — 立即改 .env.production 注入新密码 + 重建 Redis 数据 + git filter-repo 清洗历史 + 通知仓库访问者 + 审计 Redis 访问日志1 人日止血filter-repo 留待正常窗口)
- [ ] **PP-01 死信重试接线** — erp-server/tasks.rs 注册 `start_retry_dead_letters` 每小时调度(复用已实现 events.rs:382+ 补集成测试 + 修正 wiki 误记2-3 人日,代码已实现 90% 差最后 10% 接线)
- [ ] **PP-02 分区自愈** — 短期:手动写 m000170 用 generate_series 补建 2026_09~2027_06 分区2 人日,确定性硬截止解除);中期:引入 pg_partman 或 start_partition_maintenance 定时任务
- [ ] **PP-09 工作台死链修复** — 修正 4 个 Dashboard 的 navigate 路径(如 `/health/follow-ups``/health/follow-up-tasks`+ 清理 value={0} 占位 + routeConfig 增加 DEV 期 navigate 目标存在性校验0.5 人日,影响 100% 角色)
- [ ] **PP-07 RLS 补 FORCE** — 单个迁移 ALTER TABLE ... FORCE ROW LEVEL SECURITY 遍历所有 tenant_id 表(沿用 m000088 DO 块模板1 人日,立即堵死 owner-bypass 路径)
- [ ] **wiki 真相源全量校正** — 至少修正 PP-01/PP-07(RLS FORCE)/长者模式 83%(实非 100%)/Testcontainers(实本地 PG) 四处误记 + cron_heartbeat 进 /health/ready速赢2-3 人日)
### Phase 1 — 稳固期1-3 个月T+1M 到 T+3M
**目标:建立可观测性与测试门禁,让生产「可见」、回归「可防」。**
- [ ] **PP-04 可观测性三件套** — Alertmanager + 企微/钉钉 webhook 通知路由(先只开 SEV-1 避免狼来了)+ Grafana provisioningDB/Redis/HTTP/EventBus 4 个 dashboard+ Loki 日志聚合 + trace_id 贯穿4-6 人日)
- [ ] **PP-10 覆盖率门禁** — tarpaulin 接入 CI + service 层 ≥60% 门禁 + erp-health 29 service 补内联单测 + E2E 进 CI + erp-health/tests/ 进 CI4-6 周)
- [ ] **PP-06 告警角标修复** — useAlertPolling 改按 pagePath 动态查找 Tab 索引 + 补 elder-mode 11 页缺失 + setTimeout 统一走 useSafeTimeout + BLE 基础设施补单测5-7 人日)
- [ ] **PP-08 多租户连接池串扰修复** — 会话变量改 SET LOCAL 事务作用域 或 per-request 连接 + 跨租户并发集成测试2.5 周,机制层消灭跨租户泄漏窗口)
- [ ] **PP-05 AI 队列消费者 MVP** — claim_next 参数化 + SKIP LOCKED 加固 + 至少实现健康告警→AI 评估→患者推送 1 条完整链路(先 truncate 历史积压避免 Ollama OOM
- [ ] **PP-11 迁移解耦启动路径** — 独立 migrate 子命令(从 main.rs:233 剥离)+ 30 分钟回滚能力 + 破坏性 DDL expand/contract 三步走8-12 人日)
### Phase 2 — 深化期3-6 个月T+3M 到 T+6M
**目标:从「被动工具」跨越到「主动关怀引擎」,补齐合规通道。**
- [ ] **AI 主动关怀闭环** — 5 条 AnalysisQueue 链路全部接消费者(告警/化验/透析/巡护/高风险)+ 每日扫描主动触达患者 + Orchestrator 上下文压缩 + SSE token 精确计量 + 双层长期记忆11 周AI 主题举措 1-2
- [ ] **PP-12 合规通道** — 患者数据导出 API个保法 §45+ 删除/留存策略引擎(病历 15 年 vs 个保法 §47 法律冲突的 anonymize 中间态)+ 患者姓名加密 + name_hash 盲索引 + DEK 轮换闭环12-15 人日)
- [ ] **PP-11 CD pipeline** — GitHub Actions build/push/deploy workflow + 蓝绿/灰度发布 + 双副本(+30% 资源成本)+ PG advisory lock 选主(防后台任务多副本重复执行)
- [ ] **性能索引治理** — pg_trgm 索引在患者/医生姓名模糊搜索落地 + JSONB GIN 覆盖率从 5% 提到关键查询 80% + 慢查询监控 + 物化视图Dashboard DB CPU 降 50%+
- [ ] **多端体验统一** — 设计 Token 单源DTCG JSON 代码生成)+ 跨端语义组件契约AlertCard/EmptyState/VitalCard+ Web i18n 框架187 处硬编码文案)+ AuthButton 28%→70% + Web Vitals 监控
### Phase 3 — 规模化6-12 个月T+6M 到 T+12M
**目标:多副本 HA + 分布式安全 + SaaS 计量化 + 医疗合规认证就绪。**
- [ ] **高可用与分布式安全** — 应用多副本 + PG 主从 + Redis 哨兵/集群 + 满足 99.9% SLA + token 黑名单迁 Redis + DB/Redis 连接 TLS + 会话密钥分布式存储 + 密钥轮换自动化
- [ ] **统一计量计费中枢SaaS 主题)** — Metering Hub每 AI 分析/告警/咨询/上传可计量可计费)+ 配置化交付蓝图Tenant Blueprint + Onboarding实施周期降小时级+ 套餐分层 Feature Gating
- [ ] **医疗合规认证** — ICD 编码校验 + 药品编码 + 等保三级测评准备 + 个保法合规审计 + 病历留存策略引擎 + 数据分类分级引擎(字段级 ABAC+ 审计哈希链完整性举证
- [ ] **AI 能力深化** — ReAct Agent 多轮工具调用稳定化 + Function Calling 双层记忆 + 知识库 V2 + 多模态(化验单图像识别)+ RAG 评估闭环 + Provider FC 集成测试 + 成本真相化
- [ ] **生态扩展预留** — 白标皮肤branding_json + 主题注入)+ 开放 APIAPI Key 吊销/轮换/审计)+ ISV 生态接入基础
---
## 六、TOP 7 行动建议(给项目负责人)
> 可执行、有优先级、有截止时点、有负责人。
1. **【今天】启动 Redis 凭据泄露应急响应PP-03** — 立即轮换密码 + 改 .env.production 注入 + 重建 Redis 数据 + git filter-repo 清洗历史 + 通知所有仓库访问者 + 审计 Redis 访问日志。这是唯一一个「已在进行中」的合规事件,每拖一天风险递增,属法律层面强制义务。**负责人:安全/DevOps。**
2. **【本周】修复 3 个 CRITICAL 定时炸弹PP-01/PP-02/PP-07** — 死信重试接线 + 分区补建迁移 + RLS 补 FORCE。这三项均「代码已实现 90%,差最后 10% 接线」,投入小、收益大,且能立即修正 wiki 真相源漂移。**负责人:后端架构。**
3. **【本周】冻结统计仪表盘重构分支合并,直到 PP-09 修复** — 当前未提交改动AdminDashboard/DoctorDashboard/OperatorDashboard/NurseDashboard x4引入了 6 条死链 navigate + value={0} 占位合并前必须通过「navigate 目标存在性」DEV 校验。影响 100% 角色首屏,上线即暴露。**负责人:前端。**
4. **【本月】做一次 wiki 真相源全量校正 + cron_heartbeat 进就绪门禁** — 至少修正 4 处误记retry_dead_letters/RLS FORCE/长者模式 100%/Testcontainers并在 CI 增加 doc-vs-code 一致性校验cron_heartbeat 接入 /health/ready>2×周期返回 503 触发 nginx 摘流)是所有后续观测/告警的门禁基线,零依赖 1-2 人日。**负责人:架构。**
5. **【Phase 1】把可观测性三件套PP-04列为上线后第一优先** — Alertmanager + Grafana + Loki这是 DevOps 4.2 分的根本短板,也是 PP-01/PP-02/PP-05 这类潜伏故障能在生产盲飞数月的直接原因。先只开 SEV-1 避免告警风暴。**负责人DevOps。**
6. **【Phase 1】建立测试覆盖率门禁PP-10** — tarpaulin + service 层 ≥60% 门禁 + E2E 进 CI + erp-health/tests/ 进 CI。历史 24% fix 提交率的根因是回归防护薄弱,没有门禁则 6-12 个月演进速度被测试债务持续拖累AI Agent/Provider/SSE 链路演进尤其危险。**负责人:测试。**
7. **【Phase 0-1】把 PP-05 AI 队列消费者作为「主动关怀引擎」承诺的兑现起点** — claim_next 参数化(消除 format! 拼 tenant_id 的 SQL 注入铁律违反)+ 至少打通 1 条「健康告警→AI 评估→患者推送」完整链路。客户基于「主动关怀」承诺付费,目前 AnalysisQueue 是死存储,这是续约与口碑的核心,也是从「分析工具」到「主动关怀」产品跨越的根因。**负责人AI。**
---
## 七、主题章节索引
本简报为决策层入口,各主题详细分析(愿景/举措/速赢/风险/工作量估算)见对应章节文件:
| 主题 | 文件 | 核心举措数 |
|------|------|-----------|
| T1 稳定性与上线护航 | `01-stability.md` | 5 举措 + 3 速赢 + 7 风险 |
| T2 技术债与架构演进 | `02-architecture.md` | 5 举措 + 4 速赢 + 6 风险 |
| T3 AI 智能化纵深 | `03-ai-depth.md` | 5 举措 + 4 速赢 + 8 风险 |
| T4 多端体验统一 | `04-multidevice-ux.md` | 5 举措 + 4 速赢 + 9 风险 |
| T5 医疗合规与数据治理 | `05-compliance.md` | 4 举措 + 3 速赢 + 8 风险 |
| T6 商业增长与 SaaS 规模化 | `06-saas-growth.md` | 5 举措 + 3 速赢 + 8 风险 |
---
## 附录 A决策张力图谱
```
架构层8.0)─────────────────────── 可靠性上层
├── 后端架构 8.0 ← 最扎实(无环依赖 + Outbox + 双层多租户)
├── 后端业务 7.5 CAS 并发控制扎实,但 check-then-insert 竞态)
├── 数据层 7.3 schema 优秀,但分区无自愈 + RLS 缺 FORCE
└── 安全合规 7.3 PII 加密企业级,但 Redis 凭据已泄露)
↓ 落差 3.8 分(上层可靠性无底层护栏兜底)
支撑层4.2-5.5)─────────────────── 护栏底层
├── 测试 5.5 ← 回归防护薄弱PP-01/PP-05 流入根因24% fix 提交率)
└── DevOps 4.2 ← 可观测性盲飞PP-04 是最大运营风险)
核心张力:上层的可靠性没有底层的护栏兜底。
决策方向Phase 0 拆炸弹 + Phase 1 补护栏 = 让可靠性「可见、可防、可回滚」。
```
## 附录 B跨维度主题系统性模式
本次分析识别出 10 个跨维度系统性主题,是「症状」背后的「模式」,需作为演进期间的持续治理对象:
1. **文档与代码漂移** — wiki 多处「已修复」与代码不符PP-01/PP-07/长者模式/Testcontainers真相源失真是 V1 上线后最隐蔽的系统性风险。
2. **「半成品自动化」模式** — retry_dead_letters 未接线PP-01+ AnalysisQueue 死存储PP-05+ ai.dialysis.kdigo_requested 空 no-op + 32 个 FIRE-AND-FORGET 事件无消费者,系统停留在「被动工具」而非「主动关怀引擎」。
3. **测试金字塔失衡 + 覆盖率工具缺失PP-10** — 历史根因,是所有潜伏故障流入仓库的共同成因。
4. **「有代码无数据」僵尸 UIPP-09** — value={0} 占位 + 死链 navigate医疗后台展示假数据是信任度致命伤。
5. **多租户隔离多层防御可信度不足PP-07/PP-08** — RLS 缺 FORCE + 连接池 SET 串扰,医疗数据跨租户泄漏是合规红线。
6. **凭据与密钥管理散落** — Redis 密码泄露PP-03+ Redis/PG 无 TLS + token 黑名单非分布式 + HS256 对称密钥,阻塞水平扩展。
7. **可观测性近乎为零PP-04** — DevOps 4.2 分核心短板,决定上线后「是睡觉还是救火」。
8. **生产发布与回滚能力缺失PP-11** — 无 CD + 无灰度 + 单副本 + 迁移在启动路径6-12 个月演进速度核心瓶颈。
9. **性能索引黑洞** — pg_trgm 装了没用 + JSONB GIN 覆盖率 ~5%,数据膨胀后成性能黑洞。
10. **硬编码与配置散落** — TabBar 索引PP-06+ 分区日期PP-02+ value={0}PP-09配置未集中治理。
---
> 本简报基于 9 维度并行分析汇编,所有论断均附证据(文件路径:行号或 grep 结果),已在各主题章节展开。决策层如需深挖单项,直接跳转对应主题章节文件。

View File

@@ -0,0 +1,125 @@
# 稳定性与上线护航 — 主题综合
> 日期: 2026-06-25 | 主题: 稳定性与上线护航(主持综合)
> 视野: V1 上线后 6-12 个月演进,不重复上线前就绪度讨论。
> 证据口径: 所有论断附 `文件:行号`,基于 feat/media-library-banner 分支实测。
## 1. 主题愿景
把"上线即救火"变成"上线即睡觉"。融合 SRE / 发布管理 / 质量保障三方共识:以**确定性故障自愈**为核心死信重试、分区到期、AI 队列积压——这三类都是"代码写了但没接线"或"硬截止定时炸弹",是历史 24% fix 提交率在事故层面的根因),以**迁移可逆性 + 快速回滚**为发布纪律,以**告警触达人 + cron_heartbeat 进就绪门禁**为观测闭环。三者形成"预防—发布—响应"完整生命周期,而非堆砌监控工具。
不追求一步到位上 K8s+Helm+完整可观测性栈DevOps 4.2 分、单兵运维团队不支撑这种复杂度债务)。最小可行 HA = migrate 子命令 + 蓝绿 + heartbeat 门禁 + Alertmanager每一步都是可验证的工程动作配合 TDD 集成测试证明"接线真的生效"。
## 2. 专家提案摘要
### SRE 可靠性工程师5 项)
- **PP-04 告警触达人**:补 Alertmanager + 三级 SEV 分级 + Runbook deep-link + webhook 限流治理。
- **PP-01/PP-05 死信与 AI 队列接线**`retry_dead_letters`(events.rs:382) 和 `analysis_queue::claim_next`(service/analysis_queue.rs:92) 已实现但脱节,接线 + cron_heartbeat 复用为后台存活探针。
- **PP-02 分区自愈**:应急 guard每日检测未来分区数<2 则补建)+ pg_partman 根治,剩余周数作 SEV-1 倒计时告警。
- **灾备演练制度化**restore-drill CI job → RPO/RTO Prometheus 指标。
- **PP-11 迁移解耦启动路径**migrate 子命令 + 蓝绿 + 破坏性 DDL 三步走。
### 发布管理专家5 项)
- **迁移可逆性工程化**clap 子命令暴露 173 条已存在但休眠的 down 迁移为可调用能力expand/contract 强制破坏性变更跨版本。
- **30 分钟回滚**`migrate down <ver>` + 双标签镜像 + canary 用 `eventbus_pending_total`(tasks.rs:119) 作健康判据。
- **cron 进就绪门禁**`/health/ready`(health.rs:51) 暴露 cron_heartbeat + 积压阈值503 触发摘流。
- **系统级特性开关**FeatureFlagServiceai 内部)上提为 erp-core trait按 tenant 粒度灰度。
- **冻结期 + fire-drill**:双周演练 + 兼容窗口契约沉淀到 docs/runbooks/。
### 质量保障架构师(占位,观点合并入下)
门禁反推视角已贯穿上述——TDD 集成测试证明接线生效、CI 门禁拦截破坏性 DDL、fire-drill 验证回滚链路,本主题不再单列。
## 3. 战略举措(归并后 5 项)
### 举措 A确定性故障自愈接线PP-01 + PP-05 + PP-02 应急)
- **rationale**retry_dead_letters 已实现 (events.rs:382-446含 max_attempts=5 filter events.rs:390) 但全仓无调用者ai_analysis_queue 表已建 (m000118) 且 claim_next 已实现 (analysis_queue.rs:92) 但只在 module 启动触发一次device_readings 是分区表down() 只 DROP 固定 4 个月份 (2026_05..2026_08)2026-09 后 INSERT 将硬失败。三处都是"代码写了但没接线"的定时炸弹。
- **phases**:
1. tasks.rs 新增 `start_dead_letter_retry`(每小时调 retry_dead_letters复用 cron_heartbeat+ `start_ai_queue_worker`(消费 ai_analysis_queue
2. tasks.rs 新增 `start_device_readings_partition_guard`(每日检测 `pg_inherits` 未来分区数<2 则 CREATE PARTITION OF
3. TDD先写 `tests/event_retry_loop.rs``tests/partition_guard.rs` 失败测试,再接线。
- **effortEstimate**3-5 人日(函数均已存在,主要是接线 + 测试)。
- **expectedImpact**:消除 3 类潜伏故障死信不再永久驻留2026-09 分区硬截止提前自愈。
- **kpis**dead_letter_events.resolved_at 非空率 >95%ai_analysis_queue pending 24h 内清零device_readings 未来分区数 ≥3。
- **dependencies**cron_heartbeat metric 暴露(举措 CAlertmanager 告警规则(举措 D
### 举措 B迁移解耦启动路径 + 30 分钟回滚能力
- **rationale**main.rs:233 在应用启动路径直接 `Migrator::up`,含 RENAME/DROP 等破坏性 DDL173 条 down 迁移已写好但全仓无 clap/migrate 子命令可调用Cargo.toml 无 clap 依赖docker-compose.production.yml:38 单容器 `hms-server`nginx.conf:1 单 upstream.github/workflows 仅 test.yml 无构建推送。回滚资产存在但休眠。
- **phases**:
1. 引入 clap 子命令:`erp-server serve`(仅启动 HTTP不迁移/ `migrate up|down <ver> --dry-run --confirm` / `migrate verify`(事务回滚校验 down 可逆性)。
2. main.rs:233 改为启动时仅校验 schema 版本一致性,不匹配则 panic 提示先跑 migrate。
3. CI 新增 build-and-push workflow双标签 `{git-sha}` + `{semver}` 推 ghcr.io。
4. deploy.shcanary 10% 流量,观察 `/metrics` 5xx + eventbus_pending_total 5 分钟,异常切回 stable + migrate down。
5. 破坏性 DDL 写入 docs/runbooks/breaking-ddl.md 强制 expand/contract 三步走 checklist。
- **effortEstimate**8-12 人日clap 改造 + CI + deploy 脚本 + 破坏性迁移 checklist
- **expectedImpact**:回滚从"重拉镜像"升级为 30 分钟内可执行;破坏性 DDL 不再在启动瞬间执行。
- **kpis**MTTR <30min破坏性迁移 100% 走 expand/contract任意历史镜像可拉取。
- **dependencies**backup.sh 作为回滚前快照已存在nginx upstream 改造(举措 B 第 4 步)。
### 举措 Ccron_heartbeat 进就绪门禁 + 积压指标可观测
- **rationale**cron_heartbeat 已埋点 (main.rs:647 → state.rs:31 → tasks.rs 已写),但 readiness_check (health.rs:51) 仅查 DB+Redis后台任务死了就绪仍返回 oktasks.rs:119 已暴露 eventbus_pending_total但 dead_letter 积压、AI 队列积压、分区剩余周数未导出。零成本资产未利用。
- **phases**:
1. ReadyResponse (health.rs:33) 增 `crons: [{name, last_heartbeat_ago_secs, healthy}]`>2×周期判 unhealthy 返回 503。
2. tasks.rs 新增 gauge`dead_letter_unresolved_total``ai_analysis_queue_pending_total``device_readings_partitions_remaining_weeks`
3. 公开路由屏蔽 crons 字段防信息泄漏。
- **effortEstimate**2-3 人日。
- **expectedImpact**:后台任务死亡可被 nginx/k8s 摘流canary 阶段可直接判断新版本是否杀掉某个 cron。
- **kpis**/health/ready 反映 cron 存活canary 拒绝率(后台被杀)可观测。
- **dependencies**:举措 A 接线后 cron 才有真实心跳。
### 举措 DAlertmanager + 三级告警 + Runbook 绑定
- **rationale**prometheus.yml:5 仅有 rule_files 无 alerting 块alerts.yml 22 条规则但无 SEV 分级、无 Alertmanager、无 Runbook 链接。告警亮了无人知、知了不知干啥。
- **phases**:
1. docker-compose.production.yml 新增 alertmanager 服务 + prometheus.yml 补 `alerting: alertmanagers:` 段。
2. alerts.yml 按 SEV-1/2/3 分级SEV-15xx 率/PG 耗尽/Redis 不可达/dead_letter 积压/分区<2 周 → 企微+电话SEV-2P95/idle<10% → 企微SEV-3CPU/内存 → 仅 Grafana
3. 每条 alertname 对应 docs/runbooks/ 一页 + Grafana deep-link。
4. 上线初期只开 SEV-1按周复盘降噪后再开 SEV-2/3信噪比治理
- **effortEstimate**4-6 人日。
- **expectedImpact**:值班人被电话叫醒 ≤1 次/天;确定性故障(分区到期、死信积压)提前 2 周报警而非等客户投诉。
- **kpis**:告警信噪比(有效告警/总告警)>70%SEV-1 平均响应 <15min。
- **dependencies**:举措 A/C 的 metric 导出;企微 webhook 限流20/min评估。
### 举措 E灾备演练制度化 + 兼容窗口契约
- **rationale**backup.sh/restore.sh 已实现AES-256-CBC但从未验证可恢复无 fire-drill无兼容窗口契约文档。医疗 SaaS 业内普遍"有 backup 没 drill"。
- **phases**:
1. docker/drill/restore-drill.yml独立 PG 副本CI 周日 04:00 拉最近 backup → restore → schema diff + 10 条 smoke 查询 → 输出 RPO/RTO metric。
2. docs/runbooks/disaster-recovery.md + release.md + rollback.md + partition-deadline.md。
3. wiki/architecture.md 新增"发布兼容性"章节 + PR 标注 `compatible_rollback_to`
4. 双周 staging fire-drill + 变更冻结期(月初高峰前 48h
- **effortEstimate**6-8 人日(含 CI 隔离 + PII 合规审查)。
- **expectedImpact**RPO/RTO 从纸面 SLA 变成每周可查 metric客户合规审计直接拿数据。
- **kpis**backup_rpo_seconds / backup_rto_seconds 周报fire-drill MTTR 记录。
- **dependencies**CI runner 隔离(生产备份含 PIIBACKUP_PASSPHRASE 轮换流程。
## 4. 速赢1-2 周内)
1. **cron_heartbeat 进 /health/ready举措 C 第 1 步)**health.rs:33/51 改造零依赖、1-2 人日,立即可让 nginx 摘流反映后台存活。这是所有后续观测的门禁基线。
2. **死信与 AI 队列接线(举措 A 第 1 步)**tasks.rs 加两个 spawn + 复用 retry_dead_letters/claim_next3-4 人日,消除"代码写了没跑"的潜伏故障,并 TDD 证明生效。
3. **Redis 凭据止血轮换PP-03 应急)**:立即改 .env.production 注入新密码 + 重建 Redis 数据,不动 git 历史filter-repo 留待下一正常发布窗口。1 人日,止血无破坏性。
## 5. 主题级风险
- **R1 破坏性 DDL 三步走拉长交付周期 2-3 倍**:产品/研发强烈反对,需明确医疗场景牺牲速度换可靠性的边界,并以"特性开关(举措未单列)作廉价回滚"对冲。
- **R2 重试风暴**retry_dead_letters 广播后消费者仍失败会再次 dead-letter已确认 max_attempts=5 被 filterevents.rs:390兜底但 AI queue worker 启动后历史积压一次性触发大量 LLM 调用 → Ollama OOM需先 truncate 或加 backpressure。
- **R3 蓝绿双副本 +30% 资源成本**需与运维预算对齐nginx upstream 切换需 DB schema 兼容期,双写易引入数据不一致。
- **R4 BACKUP_PASSPHRASE 轮换需重加密历史备份**工作量被低估restore-drill 跑生产备份含 PIICI runner 必须隔离 + 跑完即销毁。
- **R5 告警信噪比未治理重蹈"狼来了"**:上线初期只开 SEV-1企微 webhook 限流 20/min 需评估,否则告警风暴被腾讯截断。
- **R6 /health/ready 返回 503 过敏感致发布期误摘流**:发布期临时调大阈值或加 maintenance 模式。
- **R7 pg_partman 需 superuser + shared_preload_libraries**:云 PG腾讯云可能限制私有化部署客户需同步安装扩展。
## 6. 专家分歧调和dissentingViews → 最终取舍)
- **D1 PP-03 Redis 凭据处置优先级**(安全 vs SRE vs 发布三方分歧SRE 主张业务链路自愈优先;安全主张立即 filter-repo 清洗历史;发布主张 filter-repo 是破坏性"发布"本身会重写哈希破坏分支。**取舍**:采纳发布管理专家的两阶段方案——立即轮换密码止血(无 git 影响)+ 下一正常发布窗口做 filter-repo带备份+全员协调)。理由:上线临门一脚搞历史重写风险高于泄露被利用的渐进风险,止血优先。
- **D2 是否上 K8s+Helm**SRE 明确反对,主张蓝绿+migrate 子命令是最小可行 HA。**取舍**:采纳 SRE 立场,不上 K8s。理由DevOps 4.2 分、单兵运维团队把复杂度债务换成事故概率不划算;蓝绿+heartbeat 门禁覆盖 80% 发布安全需求。
- **D3 可观测性栈建设时机**DevOps 专家 vs 发布管理边界之争):发布管理主张先 heartbeat+积压指标覆盖发布决策1 周可落地DevOps 主张上完整 Alertmanager/Loki/Jaeger4-8 周)。**取舍**:两者互补但分期——先落地举措 Cheartbeat 门禁)和举措 D 第 1-2 步Alertmanager + SEV-1完整链路追踪Loki/Jaeger列入 V1.1 路线图而非 V1 上线阻塞项。
- **D4 分区告警方式**SRE vs 监控专家SRE 主张按时间倒计时 metric 告警,监控专家常规按错误率。**取舍**:采纳 SRE——确定性故障硬截止不该按概率监控按剩余周数单调递减 gauge 提前 10 周预警。
- **D5 特性开关是否上提 erp-core**:违反 CLAUDE.md §1.3 模块边界铁律。**取舍**:以 trait 形式定义在 erp-core各模块可选依赖非直接耦合开关生命周期规范——最多存活 2 个发布周期后强制移除,防 if-flag 蔓延。本主题未单列举措,并入举措 B 回滚工具箱。
## 7. 路线6-12 个月)
- **M0上线前/上线时1-2 周)**:速赢 1+2+3 —— heartbeat 门禁、死信/AI 队列接线、Redis 凭据止血。
- **M1上线后 1 个月)**:举措 B 第 1-3 步clap 子命令 + CI 镜像推送)+ 举措 D 第 1-2 步Alertmanager + SEV-1+ 举措 A 第 2 步(分区 guard 应急)。
- **M2上线后 2-3 个月)**:举措 B 第 4-5 步canary + 破坏性 DDL checklist+ 举措 E 第 1-2 步restore-drill + runbooks+ pg_partman 根治。
- **M3上线后 4-6 个月)**:举措 E 第 3-4 步(兼容窗口契约 + fire-drill 制度化)+ 完整可观测性栈Loki/Jaeger评估。
- **M46-12 个月)**:根据 M1-M3 的 MTTR/RPO/RTO 实测数据,评估是否升级到 K8s+Helm届时团队规模与 DevOps 成熟度可能已支撑)。
> 所有举措均强调 TDD 证明接线生效、CI 门禁拦截、fire-drill 验证——质量保障"门禁反推"视角贯穿全程,不单列质量举措。

View File

@@ -0,0 +1,153 @@
# 技术债与架构演进 — 主题综合
> 日期: 2026-06-25 | 分支: feat/media-library-banner | 阶段: V1 CONDITIONAL GO上线临门一脚
> 视角: 不看上线前 P0/P1由安全/DevOps 主题覆盖),专攻"架构债"——即代码当前能跑,但 6-12 个月后会成为微服务化/SaaS 化拦路虎的结构性缺陷。
> 前序: 决策简报 `00-INDEX.md`(综合 6.9/10TOP 5 痛点含 PP-01/PP-02/PP-07本章节聚焦"结构性演进"而非"上线就绪度"。
> 证据口径: 所有论断附文件路径:行号或 grep 结果,已逐项核验。
---
## 一、主题愿景(专家共识收敛)
HMS 的架构骨架L1/L2/L3 分层 + Outbox 事件总线 + 双层多租户 + ErpModule trait是全系统最扎实的资产决策简报后端架构 8.0 分),**但当前"模块化单体"已部分名义化**:模块边界在组装层被绕过、事件契约仅靠文档约定、后台任务无声明式注册入口、多租户隔离依赖"竞态防护"的反模式、数据库 schema 演进无兼容窗口纪律。这些债现在不疼,但 6-12 个月后微服务化/SaaS 化启动时,**会从"重构"退化为"重写"**。
**主题愿景:** 在保持业务迭代速度的同时,把五类结构性缺陷(事件契约 / 模块边界 / 后台任务 / 多租户隔离 / schema 演进)从"文档约定 + 人肉记忆"升级为"编译期/CI 可校验的类型契约 + 机制层防护",为 2-3 年后的微服务化打下"可演进而非可重写"的架构地基。核心判断标准:**这笔债如果现在不还,拆服务当天是要重写而不是重构**。
---
## 二、专家提案摘要与交叉验证
三位专家(首席架构师 / 后端架构师 / 数据架构师)独立提案,**核心诊断高度一致**,差异仅在实施手法与优先级排序。已核验的关键证据:
| 诊断项 | 专家共识 | 代码证据(已核验) | alreadyKnown |
|--------|---------|-------------------|--------------|
| 死信重试未接线PP-01 | 三人一致认定是"缺少任务注册框架"症状,非单点 bug | `erp-core/events.rs:382` 定义 `retry_dead_letters``main.rs:425-673` + `tasks.rs` 全部 spawn 点无调用 | V2 审计已识别为痛点,但"根因是缺 framework"是新视角 |
| AI 队列死存储PP-05 | claim_next 消费循环未接线,与 PP-01 同源 | 与 PP-01 共享"无 TaskRegistry"根因 | 已识别,归入半成品自动化主题 |
| RLS 无 FORCE + SET 串扰PP-07/PP-08 | 三人一致:是同一缺陷两面,须合并修 | grep `FORCE ROW LEVEL SECURITY` 全仓 **0 命中**`tenant_rls.rs:31` 共享池 `SET app.current_tenant_id`:44 `RESET` | 已识别,但"SET LOCAL + 事务作用域"是机制层新解法 |
| 分区硬编码PP-02 | 确定性硬截止,优先级高于概率性 bug | `m000073:43-46` 硬编码 2026_05~2026_08启动路径外无自动建分区 | 已识别pg_partman + 应用层兜底是增量 |
| 事件 schema 无版本治理 | EVENT_SCHEMA_VERSION="v1" 仅写入 payload无消费者校验 | `events.rs:67` `pub const EVENT_SCHEMA_VERSION: &str = "v1"` | **新发现**(历史只规定命名规范,未约束 schema 演进) |
| ErpModule trait 名实不符 | register_event_handlers 8 模块中 4 空壳 | `module.rs:69` default `{}`auth/config/core/message 全空或 stubhealth:408 空函数体 | **新发现**(认知债,非功能 bug |
| 组装层边界泄漏 | dialysis 业务编排下沉到 erp-server | `erp-server/src/dialysis_workflow.rs` 直接订阅 `dialysis.record.created` 并编排 BPMN违反 §1.3 L2 零直接依赖 | **新发现**(架构铁律被默默绕过) |
---
## 三、战略举措(归并为 5 项)
### 举措 A后台任务声明式注册TaskRegistry + spawn_workers— 偿还"未接线死代码"债
**Rationale** PP-01retry_dead_letters和 PP-05claim_next反复出现的根因不是"忘了接线",而是 ErpModule 没有"后台工作器"这一等公民概念,每个新任务都靠开发者记得在 `main.rs` 手动 spawn。一次性建立 framework 后,未来新增定时任务(分区维护、归档、留存策略)都有标准路径。
**Phases**
1. **Phase A1接线1 周):** `erp-core/src/module.rs` 的 ErpModule trait 新增 `async fn spawn_workers(&self, ctx) -> AppResult<Vec<JoinHandle<()>>>`default 空 Vec`main.rs:425-673` 散落的 `start_event_cleanup / start_pool_metrics / start_auto_analysis / start_dialysis_workflow_orchestrator / start_outbox_relay / start_timeout_checker` 全部下沉到各模块 `spawn_workers`erp-health 在 spawn_workers 中以 `tokio::time::interval(3600s)` 驱动 `retry_dead_letters`erp-ai 驱动 `claim_next` 消费循环(每 30s 批量 20 条,`SELECT FOR UPDATE SKIP LOCKED` 防多副本)。
2. **Phase A2防回归0.5 周):** 编译期静态断言 + 集成测试grep `pub async fn retry_dead_letters``pub async fn claim_next` 必须在 spawn_workers 实现中被引用;扩展 `CronHeartbeat``CancellationToken` 统一 graceful shutdown。
3. **Phase A3可观测0.5 周):** `/health/tasks` 端点暴露每个任务 `last_heartbeat / max_expected_interval`,对接 Alertmanager "任务卡死"告警。
- **effortEstimate** 2 周(含测试 + graceful shutdown 改造)
- **expectedImpact** 兑现"主动关怀引擎"承诺(死信不再永久滞留)+ 消除 2 处死代码认知污染 + 为未来 HA 多副本选主铺路
- **kpis** retry_dead_letters 每小时执行且 heartbeat 可观测claim_next 队列积压 < 100死信表 resolved_at 非空率 > 95%
- **dependencies** PP-04 可观测性三件套Alertmanager配合多副本部署时需 PG advisory lock 选主Phase C 蓝绿引入时)
### 举措 B多租户隔离机制层根治FORCE RLS + SET LOCAL 事务作用域)— 偿还"竞态防护负价值"债
**Rationale** 三位专家一致裁定PP-07无 FORCE和 PP-08共享池 SET/RESET 串扰)是同一架构缺陷两面——"数据库层做应用层的事"SET 会话变量)和"应用层依赖数据库兜底但没真兜底"(无 FORCE互相强化。安全专家"多一层防护更安全"的直觉在此是**负价值**SET/RESET 在共享池上的毫秒级窗口产生的间歇性泄漏比"无 RLS 兜底"更危险,因为它制造了"有防护"的假象。正确做法是机制层消除窗口,而非"小心地 RESET"。
**Phases**
1. **Phase B1FORCE RLS0.5 周):** 新增迁移 `m000170` 遍历所有 tenant_id 业务表 `ALTER TABLE ... FORCE ROW LEVEL SECURITY`;新建非 owner 应用角色 `app_user`REVOKE 所有 + GRANT CRUD + LOGIN运行时连接改用 app_user——否则 FORCE 对 owner 无效。
2. **Phase B2SET LOCAL 事务作用域1.5 周):** 废弃 `tenant_rls.rs:31` 共享池 `SET app.current_tenant_id` 反模式;重构中间件为 `db.transaction(|txn| async { txn.execute(SET LOCAL app.current_tenant_id = $1); next.run(req).await })`——`SET LOCAL` 作用域严格限于事务内commit/rollback 后自动消失,物理上不可能被连接复用读到。同步修订 wiki §4 RLS 描述(消除文档代码漂移)。
3. **Phase B3并发隔离测试0.5 周):** 新增多租户测试 cratewiki §4.1 规划未实现),`#[tokio::test(flavor="multi_thread", worker_threads=8)]` 并发 100 个不同 tenant 请求断言零泄漏,覆盖 FHIR `allowed_patient_ids` 复杂范围过滤端点。
- **effortEstimate** 2.5 周
- **expectedImpact** 从机制层消灭跨租户泄漏窗口(医疗 SaaS 合规红线)+ 为读写分离/PgBouncer transaction pooling 铺路SET LOCAL 是唯一可用租户上下文传递方式)
- **kpis** 并发隔离测试 100 线程零泄漏FORCE RLS 覆盖所有 79 业务表;连接池 max_connections 调优后无等待超时
- **dependencies** 连接池利用率评估(事务包裹增加连接占用时长,可能需调大 max_connections只读端点统一事务路径回归测试
- **调和分歧:** 采纳首席架构师 A 方案 + 后端/数据架构师的 SET LOCAL——不删除 RLS 中间件(保留 tenant_id 注入 extension但删除共享池 SET/RESET改为事务内 SET LOCAL。这是机制层根治安全专家"多一层防护"诉求由 FORCE RLS 兜底满足,竞态窗口由 SET LOCAL 消除。
### 举措 C模块边界复位Saga 下沉 + ErpModule trait 裂变)— 偿还"组装层业务泄漏"债
**Rationale** `erp-server/src/dialysis_workflow.rs` 在 L3 组装层承载透析业务编排,违反 §1.3"L2 间零直接依赖"铁律。ErpModule trait 11 个方法中 `register_event_handlers` 在 8 模块里 4 空壳,是典型的"接口名实不符"——新人会误以为事件注册走这个方法。这两点是微服务化时最痛的债:如果 erp-dialysis 核心逻辑物理上无法脱离 erp-server 运行,"按模块拆服务"就是空话。
**Phases**
1. **Phase C1编排下沉1.5 周):** `erp-core` 定义 `trait DomainSaga { fn name(); async fn handle(&event, &ctx); }` + `SagaContext { db, event_bus }`;把 `dialysis_workflow.rs``handle_dialysis_record_created` 整体迁移到 `erp-dialysis/src/sagas/dialysis_session_saga.rs`,由 erp-dialysis 在 `on_startup` 自注册到 SagaRegistry同理 erp-health 的"告警→AI→推送"链路、erp-ai 的"化验上传→解读→推送"链路各归其位。**验收:** `erp-server/Cargo.toml` 不再直接依赖业务 entity crate 内部类型。
2. **Phase C2trait 裂变1 周):** 废弃 `register_event_handlers`deprecate 一版后删除),事件订阅统一在 `on_startup`;替换为 `fn capabilities(&self) -> &'static [ModuleCapability]` 显式能力声明HttpRoutes / EventConsumer / EventPublisher / BackgroundWorker按需 impl 而非大杂烩 trait。可选把单一 trait 裂变为 LifecycleModule / RoutingModule / EventModule首席架构师提案但为控制 breaking change 范围,建议先做能力声明,裂变留待 V2。
3. **Phase C3archlint0.5 周):** `cargo xtask archlint` 扫描 `erp-server/src/` 下是否有跨 crate 业务 importCI 拦截边界泄漏回归。
- **effortEstimate** 3 周
- **expectedImpact** 模块具备"可独立部署"属性(微服务化前置)+ 消除 erp-server 业务逻辑认知陷阱 + 死 trait 方法清理降低新人认知负载
- **kpis** erp-server 不再 import 业务 entity cratearchlint CI 零违规register_event_handlers 调用点清零
- **dependencies** Saga 间状态机/补偿事务本次只做"位置下沉"不引入完整 Saga 框架避免过度设计erp-dialysis 若需依赖 erp-workflow entity通过 erp-core trait 反向依赖反转
### 举措 D事件契约治理Schema 注册表 + Consumer Manifest + CI 校验)— 偿还"v1 永冻"债
**Rationale** 51 个事件类型声明在 `docs/event-registry.md`,但代码侧无强约束。`events.rs:67` 的 schema_version 仅写入 payload无消费者校验。这正是 PP-01/PP-05事件无消费者/死存储)反复出现的元根因——没有编译期/CI 约束,全靠人记忆。微服务化前不补这课,拆服务当天就要停机重写(微服务间事件 schema 演进是分布式系统头号痛点)。
**Phases**
1. **Phase D1注册表1 周):** `erp-core` 新增 `EventDescriptor { event_type, schema_version_range, payload_json_schema, deprecated_since }` 静态注册表;每个 crate 通过 `inventory` crate编译期收集fallback `build.rs` 代码生成)自动注册本模块发布/消费的事件描述符,生成 consumer manifest。
2. **Phase D2消费校验1 周):** `consume_with_retry` 入口按 event_type 查表schema_version 做语义版本范围匹配(`^v1` 兼容 v1.x不匹配事件进新表 `incompatible_events` 并告警(不进 dead_letter`health.patient.created` / `health.alert.triggered` / `article.published` 等核心事件补 JSON Schema 草稿(`jsonschema` crate 校验,采样率控制 ~0.1ms/事件开销)。
3. **Phase D3CI 拦截0.5 周):** `cargo run --bin verify-event-contracts` 校验每个被 enqueue 的事件必须有至少一个 consumer manifest直接拦截 PP-05 类死存储回归)。
- **effortEstimate** 2.5 周
- **expectedImpact** 从文档约定升级为编译期可校验的类型契约 + CI 直接拦截"事件无消费者"回归 + 为微服务间 schema 演进expand-contract打地基
- **kpis** 51 事件类型 100% 有 consumer manifestCI 零"无消费者"违规;核心事件 JSON Schema 覆盖率 > 60%
- **dependencies** 举措 Aspawn_workers 接线后才有真实消费者可 manifestinventory crate 构建稳定性需验证
### 举措 E可演进 schema 与数据生命周期(独立 migrate + 分区自动化 + 物化视图 + 归档)
**Rationale** `main.rs:233` `Migrator::up` 在应用启动路径执行PP-11破坏性 DDL 在启动瞬间跑多副本并发迁移会冲突device_readings 分区硬编码到 2026-08PP-02 确定性硬截止stats_service 全是实时 count + date_truncDashboard 随患者量增长拖垮 DB CPUdead_letter_events / ai_analysis_queue / audit_logs / domain_events 无归档只增不减。这是 V1 上线后 6-12 个月迭代速度的核心瓶颈。
**Phases**
1. **Phase E1分区自愈 + 死信索引1 周,含确定性硬截止解除):** 立即补丁迁移 `m000170``generate_series` 动态补建 2026_06 起未来 12 个月分区(模板提取自 `m000073:42-55`);安装 pg_partman 5.x + `part_config(premake=3, infinite_time_partitions=true)`erp-health `spawn_workers` 新增 `start_partition_maintenance`(每 6h `SELECT partman.run_maintenance('device_readings')` 应用层兜底,防 pg_cron 缺失)。同步给 `dead_letter_events``idx_dead_letter_unresolved ON (tenant_id) WHERE resolved_at IS NULL` + `idx_dead_letter_created`(配合举措 A 重试扫描性能)。
2. **Phase E2物化视图 + 归档1.5 周):** stats 高频查询建物化视图 `mv_patient_stats_by_tenant` / `mv_consultation_stats_by_tenant``REFRESH CONCURRENTLY` 每 10 分钟(`tasks.rs` 新增 `start_stats_refresh`Dashboard 读物化视图UI 标注"数据更新于 X 分钟前"管理预期。归档函数 `archive_old_rows(table, days)` 把 > 180 天 resolved/completed 数据迁到 `_archive` 表(医疗病历留存 15 年合规要求,冷数据不删除只迁移);明确热(<30 天)/温30-180 天)/冷(>180 天)三级生命周期写入 `wiki/database.md`
3. **Phase E3独立 migrate 子命令1 周):** 拆出 `erp-server migrate` 子命令pre-deploy 阶段独立执行迁移,应用启动不再跑 `Migrator::up`;建立 schema 兼容窗口规则——破坏性变更必须分两次发布v1 加新列双写 / v2 删旧列expand-contract patternCI lint 强制。
4. **Phase E4双副本蓝绿3-4 周,演进式不一步到位):** compose + 双副本 + Nginx upstream 切换 + PG 副本(`alerts.yml:70` pg_replication_lag 告警已定义但无副本);配合举措 A 的 PG advisory lock 选主。**异见采纳:** 医疗 SaaS 早期团队(<10 人K8s 运维成本超过收益compose + 双副本拿 80% HA/回滚收益用 20% 复杂度。
- **effortEstimate** 6.5 周E1-E3 可独立交付E4 演进式)
- **expectedImpact** 解除 2026-09-01 确定性硬截止 + Dashboard DB CPU 降 50%+ + 破坏性迁移可安全回滚 + 为多租户 SaaS 报表打地基
- **kpis** device_readings 分区自动维护至 2027_06Dashboard 物化视图刷新延迟 < 10min破坏性迁移 100% 走 expand-contracterp-server migrate 独立子命令上线
- **dependencies** 举措 Aspawn_workers 注册 partition_maintenance / stats_refresh双副本引入分布式问题需配套选主团队接受 expand-contract 两次迁移纪律CI lint 强制)
---
## 四、速赢1-2 周可落地)
1. **PP-01 死信重试接线2-3 天):**`tasks.rs` 新增 `start_dead_letter_retry`(每小时,调用 `retry_dead_letters` 并 touch heartbeat`start_event_cleanup` 对称注册到 `main.rs`;补集成测试;修正 wiki 误记。**ROI 极高**——代码已实现 90%,差最后 10% 接线,解锁危急值告警/积分发放/预约提醒重试链路。
2. **PP-02 分区补建迁移2 天):** 立即写 `m000170``generate_series` 补建 2026_09~2027_06 分区(确定性硬截止解除,距今 ~10 周。pg_partman 自动化可随后跟进,但手动补建是上线前必须项。
3. **PP-07 FORCE RLS 迁移1 天):** 单个迁移 `ALTER TABLE ... FORCE ROW LEVEL SECURITY` 遍历所有 tenant_id 表(沿用 m000088 DO 块模板),立即堵死 owner-bypass 路径。SET LOCAL 事务改造(举措 B Phase B2随后跟进。
4. **register_event_handlers 死方法清理1 天):** deprecate 标注 + 文档迁移到 on_startup低风险高回报的认知债偿还。
---
## 五、主题级风险
1. **多副本引入分布式问题:** 蓝绿/双副本后,后台任务(举措 A会重复执行需 PG advisory lock 选主session 一致性、token 黑名单(当前 DashMap 非分布式)需迁 Redis。建议举措 A/E4 打包推进。
2. **trait 裂变是 breaking change** ErpModule trait 裂变需一次性迁移 8 个模块,建议放在 V1 上线后第一个迭代archlint 误报可能拖慢迭代,需白名单机制。
3. **SET LOCAL 性能开销:** 每请求 BEGIN/COMMIT 约 1-2ms医疗后台可接受但 device_readings 高频写入路径需评估;连接池利用率可能下降,需调大 max_connections。
4. **加密与搜索契约冲突:** 患者姓名加密PP-12会破坏 `Name.contains`ILIKE '%x%')模糊搜索契约FHIR handler 依赖此契约。gin(trgm) 索引在加密后失效必须在加密方案落地前确定搜索降级策略prefix-only / HMAC 盲索引精确匹配),而非事后补救。
5. **inventory crate 构建稳定性:** 某些构建配置下不稳定,需 fallback 到 build.rs 代码生成JSON Schema 校验增加每事件 ~0.1ms 开销,需采样率控制。
6. **确定性 vs 概率性优先级分歧:** 数据架构师主张 PP-02确定性硬截止优先级高于一切概率性 bug其他专家按影响面排序。**裁定:** Phase 0 两者都做(速赢 1+2 并行),不排序。
---
## 六、调和专家分歧后的最终取舍
| 分歧点 | 安全/DevOps 立场 | 首席/后端/数据架构师立场 | 最终取舍 |
|--------|-----------------|----------------------|---------|
| tenant_rls SET 逻辑 | "多一层防护更安全,保留 SET" | "竞态防护是负价值SET/RESET 窗口比无兜底更危险" | **采纳机制层根治:** FORCE RLS 兜底 + SET LOCAL 事务作用域,删除共享池 SET/RESET。安全诉求由 FORCE 满足,竞态由 SET LOCAL 消除 |
| CD 工具选型 | "直接上 Kubernetes/ArgoCD" | "K8s 运维成本超过收益compose + 双副本够用" | **采纳演进式:** compose + 双副本 + 独立 migrate拿 80% 收益用 20% 复杂度。真正难的是 schema 兼容窗口纪律expand-contract与工具无关 |
| 事件 schema 注册表 | "过度工程,补测试更重要" | "PP-01/PP-05 反复出现的元根因,微服务化前必修" | **采纳注册表:** 但分阶段(注册表 → 消费校验 → CI 拦截),先解决"无消费者"回归JSON Schema 全覆盖可渐进 |
| Repository trait 层 | — | 后端架构师主张抽出(消灭漏 tenant_id 再生土壤) | **暂缓:** 工作量大16+ 处 begin/commit 迁移),且当前 SeaORM Filter + 举措 B 机制层防护已大幅降低泄漏风险。列为 V2 候选先做试点appointment/consultation/follow_up验证抽象价值 |
| trait 裂变 vs 能力声明 | — | 首席主张三 trait 裂变,后端主张能力声明 | **采纳能力声明优先:**`capabilities()` 显式声明 + deprecate 死方法,裂变留待 V2控制 breaking change 范围 |
---
## 七、路线图(与决策简报 Phase 对齐)
| Phase | 时窗 | 本主题举措 | 交付价值 |
|-------|------|-----------|---------|
| Phase 0 护航 | 0-2 周 | 速赢 1-4PP-01 接线 / PP-02 分区补建 / PP-07 FORCE RLS / 死方法清理) | 消灭确定性硬截止 + 兑现死信重试承诺 + 堵死 owner-bypass |
| Phase 1 稳固 | 1-3 月 | 举措 A 全量 + 举措 B 全量 + 举措 D Phase D1-D2 | 后台任务可观测 + 多租户机制层根治 + 事件契约注册表 |
| Phase 2 深化 | 3-6 月 | 举措 C 全量 + 举措 D Phase D3 + 举措 E Phase E1-E3 | 模块边界复位 + CI 拦截无消费者 + schema 兼容窗口纪律 |
| Phase 3 规模化 | 6-12 月 | 举措 E Phase E4双副本蓝绿+ Repository trait 试点 | 多副本 HA + 数据访问契约接缝(微服务化前置) |
---
> 本章节所有论断已附代码证据(文件:行号或 grep 结果)。核心架构债(事件契约 / 模块边界 / 后台任务 / 多租户隔离 / schema 演进)现在偿还成本是"重构级"6-12 个月后微服务化启动时将退化为"重写级"。决策层建议Phase 0 速赢立即启动Phase 1-2 把五类结构性缺陷升级为编译期/CI 可校验契约。

View File

@@ -0,0 +1,155 @@
# 主题 03 — AI 智能化纵深
> 日期: 2026-06-25 | 分支: feat/media-library-banner | 主题负责人: 综合主持
> 范围: V1 上线后 6-12 个月从「被动分析工具」跨越到「AI 驱动的主动关怀引擎」
> 证据基准: 所有论断附文件路径:行号或 grep 结果;关键代码断言已逐一核验
---
## 一、主题愿景(专家共识融合)
HMS 当前已具备相当完整的 AI 工程拼图——4 个 ProviderClaude/OpenAI/Ollama/通义)+ ReAct Agent 运行时AgentOrchestrator + 9 Tool + 角色沙箱)+ RAG 知识库 V2 + 引用溯源 JSONB + 分析队列 + 成本/配额服务。**缺的不是新零件,而是电路闭合**Agent 只在用户敲回车时转一次永不主动跑队列只入队不消费成本看板是硬编码常量估算的假数据RAG 检索质量全仓零指标grep `recall_at_k/faithfulness/golden_dataset` 0 命中)。
**统一愿景**:以「闭合已存在但空转的能力」为第一性原理,分三步把 HMS 从「医生敲回车才出报告的分析工具」进化为「感知→推理→行动→复盘」的主动关怀引擎——
1. **通电**:打通事件→入队→自主 ReAct→结构化建议→回写档案→推送的全链路修复 PP-05 死存储)。
2. **可信**:用双层记忆解决金鱼记忆、用引用溯源闭环 + RAG 评估集让 AI 输出从「主观判断」变「可量化、可溯源、可反馈」。
3. **可度量**:把真实 token 成本、建议转化率、检索 recall@5 变成运营仪表盘让「AI 到底准不准/贵不贵/有没有用」可回答。
**北极星指标从「分析产出量」改为「AI 建议转化率」**——一个 insight 若无法对应任何临床动作,就不该生成。
---
## 二、专家提案摘要(异见与共识)
### 共识(三位专家高度一致)
- **PP-05 是第一优先**三位专家都把「claim_next 接线 + worker loop」列为头号提案认定它是兑现「主动关怀」承诺的断点。
- **claim_next:98 的 `format!` 拼 tenant_id 必须先参数化**(虽然 Uuid 不可利用,但违反 CLAUDE.md SQL 注入铁律 + 反模式)。
- **自主 AI 必须接配额/熔断护栏**,否则 Provider 成本失控。
- **评估闭环RAG 黄金集 / LLM-judge是补 PP-04 可观测性 + PP-10 测试覆盖在 AI 子系统的专门补丁**。
### 关键异见(最终取舍见 §六)
| 分歧 | AI 架构师 | 医疗产品经理 | MLOps 工程师 | 最终取舍 |
|------|-----------|--------------|--------------|----------|
| AI worker 与死信重试是否合并 | 合并打包修(同类问题) | 分开SLA/迭代节奏不同) | 分开但共享 spawn 骨架 | **分开 + 共享骨架**:采纳产品/MLOps 观点,业务可降级与基础设施 100% 投递不可混;但抽取通用 spawn+interval+shutdown 框架避免造轮子。 |
| 患者侧是否上 LLM 对话 | 不主张 | 坚决反对(中老年接受度低) | 不涉及 | **不上**:患者侧坚持 local_rules + 模板LLM 算力留给医护端决策支持。 |
| 患者记忆卡片存储方式 | 结构化 KV 不全加密 | 不涉及 | 不涉及 | **结构化 key + 敏感 value 加密**key`medication_adherence:poor`明文可查可审计value 走 AES-256-GCM自由文本不入库降低泄露面。 |
| LLM-judge 可靠性 | 需人工抽审 | 引用校验优先 | 三层防线(规则兜底+LLM-judge+人工5% | **三层防线**关键医学禁用词走规则引擎硬门禁ambiguous 走 LLM-judge每周抽 5% 人工复核校准 judge。 |
| 商业化时机 | 不涉及 | 等 KPI 跑通 2-3 个月再议 | 先堵漏再扩面 | **延后商业化**:建议转化率稳定前不按次计费。 |
---
## 三、战略举措5 项)
### 举措 1 — AI 分析队列消费者落地(堵 PP-05闭合死存储
**rationale**`claim_next`analysis_queue.rs:92已实现 `FOR UPDATE` 语义但全仓仅定义处命中module.rs:268 只实例化队列、无 worker 调用。三位专家一致认定的第一优先。
**phases**
- **P0接线1 周)**①analysis_queue.rs:96-110 `format!` 拼 tenant_id 改为 `Statement::from_sql_and_keys` 参数化(修 SQL 注入铁律违反);②补 `FOR UPDATE SKIP LOCKED` 实现多 worker 并发安全 claim③在 module.rs register 关闭阶段 spawn `start_analysis_queue_worker`interval 15s`SELECT DISTINCT tenant_id` 驱动多租户遍历,并发度从 config.toml 读取(默认 2+ `tokio::sync::Semaphore` 限流。
- **P1链路闭合2 周)**:复用 chat_handler.rs:182-192 的 9 Tool 注册逻辑(抽 `build_default_tool_registry()` 公共函数),按 analysis_type 分派,调用 `AgentOrchestrator::run()`,结果走 `post_process_analysis()` 写 ai_analysis + 发布 `ai.analysis.completed` 事件(消费者 erp-message 已就绪)→ 转 `copilot_insights` 行进医护侧边栏行动收件箱。
- **P2韧性1 周)**`started_at < NOW()-30min AND status='running'` 的卡死回收扫描 + 失败走 mark_failedretry_count<max_retries=3 指数退避,达限移入死信)+ 同患者同 insight_type 24h 去重防洪泛。
**effortEstimate**4 周(后端 1 人)
**expectedImpact**:高 — 兑现「主动关怀」承诺;激活 5 条入队链路critical_alert/lab_upload/dialysis/patrol/high_risk从死存储变活管线。
**kpis**:队列 pending 积压 ≤50worker 吞吐 ≥100 分析/天/租户p95 分析延迟 ≤30s卡死回收覆盖率 100%;死信率 ≤2%。
**dependencies**PP-01 死信重试接线(共享 spawn 骨架config.toml 并发度字段Provider 配额服务(举措 4
### 举措 2 — Function Calling + 双层长期记忆(从文本到可执行建议)
**rationale**FC 已在 claude.rs/openai.rs 实现(`generate_with_tools`),但仅 chat_handler 使用Agent 永不主动跑、记忆仅最近几条原文chat_handler.rs:140 加载历史,长会话 token 爆炸)。
**phases**
- **P1FC 接入分析3 周)**AnalysisService::stream_analyze 注入受控工具 schemafetch_lab_trends/fetch_medication_history/lookup_drug_interaction/lookup_clinical_guideline/propose_followup_actionLLM 发起 tool_call → worker 本地执行 → 结果回灌 → 输出结构化 JSON `{summary, findings[], suggested_actions[], confidence}`max_tool_rounds=5 + token budget 熔断;扩 analysis.rs references JSONB 存 findings/suggested_actions。
- **P2双层记忆3 周)**①会话级滚动摘要——session>20 条触发 `summarize_session`cheap Ollama provider摘要存 ai_chat_session.metadataJSONB 已存在零迁移chat_handler:140 优先注入摘要+最近4条token 从 ~8k 降到 ~1.5k;②患者级事实卡片——新增 `ai_patient_memory`patient_id/tenant_id/key/value/source/confidence/expire_atkey 明文、value 加密post_process 用 LLM 抽取结构化事实 upsertconfidence 阈值 0.7)。
- **P3PII 防护1 周)**tool_call 结果注入 LLM 前经 SanitizationService 脱敏(姓名/身份证/手机号 token 化为 `patient_ref_xxx`Provider 配置层启用 zero-retention扩 AuditLog 记录所有记忆写入。
**effortEstimate**7 周(后端 1 人 + 医疗审校 0.3 人)
**expectedImpact**:高 — AI 输出从自然语言段落变可机读临床建议;长会话成本降 ~80%Agent 跨会话记忆患者基线。
**kpis**:结构化输出占比 ≥90%;会话平均 token 降幅 ≥60%记忆卡片过期率30 天≤15%PII 泄露事件 0。
**dependencies**:举措 1worker 复用 FC 链路);举措 4能力矩阵 + token budgetknowledge_v2 检索质量(举措 3
### 举措 3 — RAG 评估闭环 + 引用可信度(让 AI 可量化)
**rationale**:全仓 grep `recall_at_k/faithfulness/golden_dataset` 0 命中——今天无人能回答「我们的 AI 检索到底准不准」references JSONB 已加但前端仅文本展示、无溯源跳转、无反馈。
**phases**
- **P1引用溯源闭环2 周)**①worker 完成分析时做引用完整性校验——`[ref:id]` 必须全部能在 ai_knowledge_references 查到,查不到标 broken_citation 并降级 confidence前端灰显②前端 `[ref:xxx]` 渲染为可点击 chip 弹 guideline 摘要③suggestion_feedback 扩 `useful_citation`/`wrong_citation` action④nightly 统计 knowledge_reference 采纳率,低采纳标需复核。
- **P2评估集3 周)**:新增 `ai_eval_case`input_payload/expected_topics/critical_must_mention/must_not_mention/golden_summary/source_analysis_id初始从医生反馈正向记录导入 20-50 条覆盖高血压/糖尿病/透析高频场景;新增 `eval_runner.rs` 跑 active prompt 计算 recall@5/citation_precision/answer_faithfulness
- **P3CI 门禁2 周)**CI 加 `cargo test --features eval-smoke` 跑 10% 子集(无 LLM 的纯 recall@5,快)作为 PR 门槛,分数下降超阈值阻断;全量 LLM-judge 评测手动触发,写 docs/audits/rag/rag-eval-YYYY-MM-DD.md 对比趋势。
**effortEstimate**7 周(后端 1 人 + 医护标注 0.5 人)
**expectedImpact**:中 — AI 质量从主观判断变数字仪表盘;驱动知识库持续迭代;改 prompt 后能回答「新版有没有变好」。
**kpis**recall@5 ≥0.8citation_precision ≥0.95broken_citation 率 ≤3%;评估集季度刷新 1 次CI 门禁拦截率可观测。
**dependencies**knowledge_v2 文档质量医护标注时间投入vector_search.rs:52 参数化改造(顺带完成)。
### 举措 4 — 成本真相化 + Provider 能力矩阵(可观测 + 不爆费)
**rationale**`default_token_estimate`cost.rs:40-49硬编码 2000/1500 等常量,非 chat 分析路径完全跳过 `log_usage`(仅 chat_handler.rs:319 + mod.rs:1116 调用)——成本看板是估算假数据;`token_budget: None` 永远不启orchestrator.rs:130 是死代码);`supports_fc = provider_name != "ollama"`chat_handler.rs:241硬编码字符串。
**phases**
- **P1真实 usage2 周)**①provider 返回 TokenUsage 上抛(扩 AnalysisResult 结构,逐个适配 chat/copilot/agent 调用方②analysis.rs 收尾调 `UsageService::log_usage`③CostService::estimate_cost 优先读 ai_usage 真实聚合、缺数据降级估算并标 `is_estimated`;④新增 `GET /ai/cost/realtime` + Prometheus 指标接 Grafana。
- **P2能力矩阵2 周)**AiProvider trait 加 `supports_function_calling()`/`supports_streaming()` 自报能力chat_handler 按矩阵走 FC ReAct 或降级token_budget 从 config 按角色配额(患者 8k/医生 16k/worker 4k激活死代码cost.rs 接实时熔断——单租户单日累计超阈值 chat_handler 返回 429 + worker 暂停入队。
**effortEstimate**4 周(后端 1 人)
**expectedImpact**:中 — 成本从谎报变真相Provider 混用不空转不爆费;自主 AI 有预算护栏。
**kpis**:成本看板 is_estimated 占比 ≤10%新数据租户日配额熔断触发可观测Ollama 降级路径 tracing 可见;死代码 token_budget 激活。
**dependencies**Provider usage 字段稳定性Ollama 可能不返回 usage降级估算+记 `model_not_reporting_usage`);举措 1 worker共享熔断
### 举措 5 — AI 行动闭环 + 患者侧轻量触达(建议不悬空 + 提升依从性)
**rationale**suggestion_feedback 只存 actionaccept/reject无下游动作AI 建议悬空;患者侧(中老年为主)对 AI 聊天接受度低,需「清晰的下一步动作」而非 AI 解读。
**phases**
- **P1建议→任务转化2 周)**:新增 `POST /ai/suggestions/{id}/convert`,按 suggested_action_type 调 erp-health servicefollow_up_service::create_task_from_template 已存在)创建业务对象 → 发 `suggestion.converted` → 关联 task_id前端漏斗视图「已采纳 N/已转化 M/已改善 K」。权限边界convert 需 `require_permission` 对应 .manage不绕过任务创建权限。
- **P2患者侧轻量触达3 周)**:复用 local_rules.rs 扩「依从性规则集」(漏测/服药窗/复诊到期),每日扫描 device_readings 最近 3 天,命中生成 patient_facing insight模板渲染非 LLM成本低`ai.insight.generated` → 小程序助手 Tab注意修 PP-06 角标指向 index 3限频每患者每天 ≤1 条 + 勿扰时段 + 一键关闭。
- **P3北极星度量1 周)**:周报统计「建议→任务转化率」按 insight_type/suggestion_source 维度,作为 AI 价值核心 KPI。
**effortEstimate**6 周(后端 1 人 + 前端 0.5 人 + 法务审文案 0.2 人)
**expectedImpact**:中 — AI 价值从「产出量」转「转化率」;患者依从性可度量提升。
**kpis**:建议转化率 ≥30%(高频随访模板补全后);患者推送退订率 ≤5%;依从性规则医学正确性(每条带 guideline_id 引用)。
**dependencies**PP-02 device_readings 分区续建患者触达数据源PP-06 告警角标修复follow_up 模板补全;跨模块调 erp-health 走 trait 不直依赖(架构铁律)。
---
## 四、速赢1-2 周可落地)
1. **claim_next 参数化 + SKIP LOCKED 加固**≤3 天analysis_queue.rs:96-110 `format!``Statement::from_sql_and_keys` + 补 `FOR UPDATE SKIP LOCKED`,消除 SQL 注入铁律违反 + 为多 worker 并发铺路。零业务风险,纯加固。
2. **token_budget 死代码激活**≤2 天orchestrator.rs:130-148 预算逻辑已实现,仅需 chat_handler.rs:225 从 config 读取角色配额(患者 8k/医生 16k传入让既有护栏生效。
3. **真实 usage 灌入非 chat 路径**≤5 天provider TokenUsage 上抛 + analysis.rs 收尾调 log_usage + CostService 标 is_estimated让成本看板从假数据变真相。
4. **引用完整性校验 MVP**≤5 天worker 完成分析时遍历 `[ref:id]` 校验存在性broken 标记 + 降级 confidence前端灰显无需新表新迁移。
---
## 五、主题级风险
1. **自主 AI 成本失控**worker 持续消耗 Provider 配额。→ 必须先接举措 4 配额/熔断再放举措 1 全量。
2. **医疗 AI 幻觉危及患者**:结构化建议被医生采纳进入临床决策。→ 引用溯源闭环(举措 3+ evidence_strength 灰显 + 前 2 周「只读不推」模式 + 法务审文案。
3. **PII 通过 tool_call 外泄**:患者数据二次喂外部 Provider。→ SanitizationService 强脱敏 + Provider zero-retention + 记忆 value 加密。
4. **LLM-judge 自身幻觉**:评估闭环把质量门禁交给可能幻觉的模型。→ 三层防线(规则硬门禁 + LLM-judge 仅 ambiguous + 人工 5% 抽审)。
5. **AI 产出洪泛**risk_service 每天对所有高危患者刷一遍会爆 insight 表。→ 同患者同 insight_type 24h 去重 + 只读不推过渡期。
6. **患者信任度低**:中老年对机器提醒信任低于医生口头医嘱。→ 定位「辅助提醒」非「诊断建议」+ 文案法务审核 + 严格限频。
7. **评估集冷启动 + 过期**:前 1-2 月反馈量不足;季度不刷新则失真。→ 初期只做引用完整性校验不做质量评分;建季度刷新机制。
---
## 六、专家分歧调和(最终取舍)
1. **AI worker vs 死信重试是否合并****分开 + 共享 spawn 骨架**。采纳医疗产品经理/MLOps 观点:基础设施层(死信 100% 投递、无业务语义与业务层AI 可降级、可限流、可灰度)的 SLA/迭代节奏/监控指标完全不同,强行合并会让 AI 业务被基础设施稳定性拖死。但抽取通用「spawn + interval + graceful shutdown」框架避免重复造轮子反模式合规
2. **患者侧 LLM 对话****不上**。三位中两位明确反对,符合「用户驱动非技术驱动」原则;患者侧坚持规则引擎 + 模板。
3. **患者记忆存储****结构化 key 明文 + 敏感 value 加密**。调和安全(可审计/可查询与隐私PII 保护),自由文本不入库。
4. **LLM-judge 可靠性****三层防线**。规则引擎兜底关键医学禁用词LLM-judge 仅处理 ambiguous人工抽审 5% 校准。
5. **RAG 评估 vs 补 E2E 优先级****并行**。AI 输出正确性(举措 3与 UI 可点性(属测试主题)风险类型不同,医疗幻觉比少几个 E2E spec 更致命,但两者不互斥,分头推进。
6. **商业化时机****延后**。等建议转化率 KPI举措 5跑通 2-3 个月、医护采纳率稳定后再议按次计费。
---
## 七、路线图(与项目 9 维度 Phase 对齐)
- **Phase 11-3 月)**:举措 1P0+P1 接线闭合)+ 举措 4P1 成本真相化)+ 速赢 1-3。兑现「至少 1 条健康告警→AI 评估→推送」完整链路。
- **Phase 23-6 月)**:举措 1P2 韧性)+ 举措 2FC + 双层记忆)+ 举措 3P1 引用闭环 + P2 评估集)+ 举措 5P1 建议转化)。从「被动工具」跨到「主动关怀引擎」。
- **Phase 36-12 月)**:举措 3P3 CI 门禁)+ 举措 4P2 能力矩阵)+ 举措 5P2 患者触达 + P3 北极星度量)+ Prompt 影子发布/A-B利用 CacheKey 含 prompt_version 红利,红利已核验 cache.rs。多模态化验单图像识别作为远期探索。
---
> 本主题所有代码断言已逐一核验claim_next 未接线grep 全仓仅定义处命中、default_token_estimate 硬编码cost.rs:40-49、log_usage 仅 2 处调用chat_handler.rs:319 + mod.rs:1116、token_budget: None 死代码orchestrator.rs:130、supports_fc 硬编码chat_handler.rs:241、recall/faithfulness/golden 0 命中。论断可追溯。

View File

@@ -0,0 +1,183 @@
# 多端体验统一 — 主题综合
> 日期: 2026-06-25 | 分支: feat/media-library-banner | 主题负责人: 综合主持
> 范围: V1 上线后 6-12 个月,跨 Web / 小程序 / 适老化 / 无障碍的多端体验治理
> 证据基线: 所有论断附文件路径:行号,已逐项核验(见文末附录 A
---
## 一、主题愿景 (Vision)
**以"行为契约一致"取代"像素级对齐"作为多端统一的度量:** 同一份交互契约、同一份设计 Token 源、同一类用户在任何端完成同等质量的操作。视觉层尊重三端语境差异PC 鼠标精确点击 vs 手机触控 vs 老人手抖),但**状态语义层(错误/空/加载/危急)必须跨端一致**——同一类危急值告警在三端都拥有图标、朗读、重试与多通道触达。
当前 HMS 的多端体验是"4 处 token 副本 + 2 套请求层 + 隐式导航结构 + Web 无障碍盲区"的离散态:色值在 `variables.scss:5`(#C4623A 橙)、`App.tsx:153/175/194/218`(4 套硬编码 themeConfigs)、`index.css:12`(#2563EB 蓝)、`token-values.ts` 四处平行复制且已发生漂移Web 与小程序的缓存 TTL(5s vs 60s)、错误映射(无 vs 有)、Token 刷新(预检 vs 响应式)三处配置已发散。本主题的目标是**把"统一=改一处生效全端"从口号变为 CI 可校验的硬约束**,让设计系统从"审美问题"升级为"医疗可见性错误的根因防线"。
---
## 二、专家提案摘要
| 专家 | 核心切入点 | 关键提案 |
|------|-----------|---------|
| **设计系统架构师** | DTCG JSON 单一真相源 + 代码生成器分发三端 | DTCG tokens.json 单源 → Style Dictionary 生成 SCSS/CSS/antd themeConfigs语义化导航契约根治 TabBar 硬编码索引;跨端语义组件契约(AlertCard/EmptyState)Web 适老化主题补盲Figma-代码双向 sync |
| **前端架构师** | @hms/shared 跨端内核 + 契约驱动 + 性能预算 | 抽错误码/缓存策略/Token 状态机三大不变量为共享包token 单源不统一命名空间(--erp-* / --tk-* 双轨)Web Vitals 采集 + 性能预算门禁;路由表契约收敛 PP-09/PP-06error_code enum + codegen |
| **UX 研究员 / 无障碍专家** | 真实使用情境(医护效率 + 老年降级 + WCAG | dashboardNavMap 意图层契约;危急值多通道触达(震动/语音/横幅)Design Token v2 含四态语义 + WCAG 对比度门禁;情境感知适老化(行为信号推断);键盘快捷键 + 撤销模式 |
### 三专家共识点(已收敛为举措基础)
1. **路由/导航契约化**是 PP-06 + PP-09 共同根因解三方都提出措辞不同dashboardNavMap / NavigationContract / routeConfig 扩展)。
2. **设计 Token 单源 + 生成器**取代手工多副本(三方一致,分歧仅在命名是否统一)。
3. **危急值可达性 + WCAG 对比度门禁**是无障碍从主观转客观的唯一路径。
4. **Web 适老化主题是系统性盲区**,需补齐(小程序 58/58 覆盖Web 0
### 主要分歧(已在第四节调和)
- **token 命名空间**:设计系统架构师倾向 DTCG 语义命名重整,前端架构师主张保留双命名空间(--erp-* / --tk-*)只统一源。→ **采纳前端方案**(改名风险大,生成器桥接更安全)。
- **共享包粒度**:前端架构师主张 packages/shared 全量抽象,设计系统架构师主张 packages/design-tokens 独立。→ **分两包但同 monorepo**design-tokens 先行shared 内核第二轮。
- **token 跨端范围**UX 专家主张视觉 token 各端独立 + 语义 token 共享,设计系统架构师倾向全量统一。→ **采纳 UX 方案**(蓝橙调色板本质不同不强行统一,只统一语义层)。
---
## 三、战略举措 (Initiatives)
### 举措 1路由与导航契约层 — 根治 PP-09 死链与 PP-06 角标错位
**理由:** PP-09(4 Dashboard 6 条死链 navigate`AdminDashboard.tsx:51/69` 指向 `/health/follow-ups`/`/health/vital-signs`,实际路由是 `follow-up-tasks` / 体征路由不存在)与 PP-06(`useAlertPolling.ts:64` 写死 `setTabBarBadge({index:2})`,但 `app.config.ts:80` index 2=商城,告警应到 index 3=助手)的共同根因是**路由/Tab 配置为运行时字符串字面量与裸数字索引,无单一契约源**。影响面 100% 角色、100% 用户、上线即暴露。
**分阶段落地点:**
- **Phase 02 周):** 修 6 条死链 navigate + 清理 `AdminDashboard.tsx:88 value={healthDataStats ? 0 : 0}` 僵尸写法;`useAlertPolling.ts``resolveTabBarIndex('/pages/messages/index')` 动态查找。
- **Phase 11 个月):** 在 `routeConfig.ts` 扩展 `dashboardNavMap`(业务意图键 → path + permissions + 后端统计字段);抽 `useDashboardCard(intent)` hook 按权限过滤卡片CI 加 navigate 目标存在性单测(遍历 map 断言每个 path 在 routeConfig 存在)。
- **Phase 23 个月):** `packages/shared-contracts/navigation.ts` 跨端 NavigationContractWeb 侧边栏 + 小程序 TabBar 都从契约读取;权限字段统一从契约派生。
**工作量估算:** Phase 0 约 2-3 人日Phase 1 约 5-7 人日Phase 2 约 10-15 人日(跨端 + 类型系统差异)。
**预期影响:** 消灭医疗管理后台 403 死胡同与假数据展示,根除"TabBar 顺序一变就复发"的角标 bug 类。**高 / 中量**
**KPI** navigate 死链数 = 0CI 门禁TabBar 角标错位 bug 复发次数 = 0权限过滤后无权卡片不再渲染点击前拦截率 100%)。
**依赖:** 后端菜单 APIPhase 2 才需扩展 path 字段routeConfig 现有结构。
### 举措 2设计 Token 单源 + 代码生成器DTCG JSON
**理由:** 当前色值/字号在 4 处平行复制且已漂移:`variables.scss:5`(#C4623A 橙)、`App.tsx:153-218`(4 套硬编码 colorPrimary)、`index.css:12`(#2563EB 蓝)、`token-values.ts`。Web 完全缺失适老化主题与 motion/a11y token。纯 SCSS 无法跨仓库收敛,已发生手工漂移。
**分阶段落地点:**
- **Phase 11 个月):** 新建 `packages/design-tokens/`monorepo 首个共享包DTCG JSON 为唯一源Style Dictionary 生成三端产物(小程序 variables.scss + tokens.scss + token-values.tsWeb index.css :root + antd themeConfigs.tsCI 加产物与源同步 diff 检查。**保留双命名空间**(--erp-* / --tk-*),只统一源不强行改名。
- **Phase 23 个月):** 新增四态语义 token(`--tk-state-error/empty/loading/critical`) + Web 适老化 variantcontrolHeight 40→52, fontSize 14→18+ motion token(duration/easing 自动尊重 prefers-reduced-motion) + a11y token(focus-ring)Web 用户菜单增加"适老化"开关。
- **Phase 36 个月):** Figma Tokens 插件双向 sync + 视觉回归(Playwright + miniprogram-automator 核心页面截图比对) + WCAG 对比度自动化门禁(正文/背景 ≥4.5:1关怀模式 ≥7:1
**工作量估算:** Phase 1 约 8-10 人日(含 Style Dictionary 选型与构建集成Phase 2 约 12-15 人日Phase 3 约 15-20 人日(含视觉回归基线调优)。
**预期影响:** 消灭 token 漂移CI 硬约束),补齐 Web 适老化盲区,无障碍从主观评价转为 CI 红绿灯。**中 / 大量**
**KPI** token 副本数从 4 → 1CI 门禁Web 适老化主题覆盖 0 → ≥30 核心页面WCAG 对比度门禁通过率 100%token 变更影响面自动标注覆盖率 100%。
**依赖:** pnpm workspace 接入Antd ConfigProvider 对自定义 variant 的支持;团队 DTCG 命名培训。
### 举措 3跨端语义组件契约层 — AlertCard / EmptyState / VitalCard
**理由:** 不追求像素级跨端一致Ant Design vs 微信原生组件库是硬约束),而是为高频业务语义组件规定统一 TS 接口,让后端 DTO 字段名对齐组件 prop 名,减少 transform 层。优先 AlertCardPP-06 危急值可见性)与 EmptyState积分商城 Tab 空白 bug wiki 已记)。
**分阶段落地点:**
- **Phase 11 个月):** 定义 `packages/shared-contracts/components.ts` 中 AlertCardProps(severity: critical|warning|info, title, value, action) + EmptyStateProps(icon, title, action)Web 与小程序各自实现但 satisfy interfaceTS implements
- **Phase 23 个月):** 后端 `alert_dto.rs` 等响应字段与契约对齐Storybook(Web) + 组件 demo 页(小程序) 按契约生成 demo。
- **Phase 36 个月):** 扩展到 VitalCard / ErrorBoundaryFallback / LoadingState / StatusTag 共 6 类CI 跑视觉回归保证 prop 兼容。
**工作量估算:** Phase 1 约 5-7 人日Phase 2 约 8-10 人日Phase 3 约 12-15 人日。
**预期影响:** 消灭两端各自重复造轮子40+ 小程序组件与 Web Antd 隐式重复),减少前后端 DTO 不同步症状。**中 / 大量**
**KPI** 高频业务组件契约覆盖率AlertCard/EmptyState 优先);前端 transform 层代码行数下降DTO ↔ prop 字段对齐率。
**依赖:** 举措 2 的 token 层;两端 StatusTag severity 枚举映射层(小程序 abnormal/critical vs antd error/warning
### 举措 4危急值可达性 + 多通道触达(无障碍优先)
**理由:** 医疗安全信息必须多通道冗余WCAG 2.2 Status Updates + IEC 62366 alarms 原则)。当前危急值仅靠单一角标(且写错 Tab老年患者对延迟和字号容忍度更低。`hapticHeavy()` 已实现于 utils/haptic.ts 但未接入告警。
**分阶段落地点:**
- **Phase 02 周):** 修 `useAlertPolling.ts:64` 角标 index bug举措 1 联动);接入 hapticHeavy/medium。
- **Phase 11 个月):** 多通道触达:(a) 触觉 haptic(b) elder-mode 下 TTS 语音播报/预录提示音;(c) 页面顶部 sticky 红色横幅(不依赖 Tab 切换elder-mode 自动缩短轮询间隔 10s → 5s + 放大横幅字号。
- **Phase 23 个月):** Web 端补齐 aria-live所有数据加载/错误/告警区域加 role=status aria-live=politecritical 用 assertivee2e mock 验证角标出现在正确 Tab。
**工作量估算:** Phase 0 约 1 人日Phase 1 约 5-7 人日Phase 2 约 4-5 人日。
**预期影响:** 危急值不再被单一视觉通道埋没,视障医护可朗读工作台状态。**高 / 小量**
**KPI** Web aria-live 覆盖区域数(当前仅 1 处 PluginDashboardPage:448 → 目标 ≥20危急值多通道触达覆盖率 100%elder-mode 下告警延迟 ≤5s。
**依赖:** 举措 1 的 TabBar 契约;微信小程序 TTS 插件能力(可能降级预录音频)。
### 举措 5医护效率层 + 情境感知适老化(差异化体验)
**理由:** 医护 8 小时班次高频重复操作(确认告警/回复咨询/标记随访),每次点击 + modal + 确认造成腕劳损elder-mode 完全依赖用户主动开启,真正需要它的老年患者往往不知道。
**分阶段落地点:**
- **Phase 23 个月):** 键盘快捷键层J/K 选择、Enter 详情、C 确认告警、/ 聚焦搜索,参照 Linear/GitHub危险操作改"软删除 + 5 秒撤销"替代二次确认 modal告警/随访批量操作;小程序医生端左滑快速完成/右滑推迟。
- **Phase 36 个月):** `useAdaptiveMode()` hook 被动采集信号(连续校验失败 ≥2 次 / 触摸偏离中心 >40% / 停留时长超中位数 2 倍),顶部柔和提示邀请开启长辈模式(不静默改设置,信号只本地不上传);情感化关怀文案(异常体征不再冷冰冰,配合 AI 主动关怀)。
**工作量估算:** Phase 2 约 10-12 人日Phase 3 约 12-15 人日。
**预期影响:** 降低医护重复劳损与误操作,把适老化从"用户主动开启"升级为"系统主动识别并邀请"。**中 / 大量**
**KPI** 医护高频操作点击数下降 ≥30%elder-mode 主动邀请转化率;撤销模式误操作回退成功率。
**依赖:** 后端撤销 API先纯前端 5 秒延迟窗口,后端分批补);隐私政策声明(行为信号采集);法务审核情感文案。
---
## 四、专家分歧调和Dissenting Views → 最终取舍)
| 分歧 | 设计系统架构师 | 前端架构师 | UX/无障碍专家 | **最终取舍** |
|------|--------------|-----------|--------------|------------|
| token 命名空间 | DTCG 语义命名重整 | 保留双命名空间只统一源 | 视觉各端独立 + 语义共享 | **保留双命名 + 源统一 + 语义层共享**(改名风险大,语义层价值高) |
| 共享包结构 | packages/design-tokens 独立 | packages/shared 全量抽象 | 跨端共享 token v2 | **design-tokens 先行 + shared 内核第二轮**(降低首轮风险) |
| 跨端统一范围 | 全量统一 | 统一源不统一命名 | 视觉独立语义统一 | **视觉层各端独立,状态语义层跨端共享**(蓝橙调色板本质不同) |
| 优先级之争 | — | 与 DevOps 争优先级PP-01/02/04 vs 前端) | — | **PP-01/PP-02/PP-03 硬截止让路PP-09/PP-06 并行**(影响面 100% 用户,修复成本低) |
| 传输层抽象时机 | — | 先抽纯逻辑后抽传输层 | — | **采纳:第一轮只抽错误/缓存/状态机axios vs Taro 延迟** |
---
## 五、速赢Quick Wins1-2 周可落地)
1. **修 PP-09 工作台 6 条死链 navigate + value={0} 僵尸写法**`AdminDashboard.tsx:51``/health/follow-ups``/health/follow-up-tasks``:69` 删除或改真实体征路由;`:88` 删除 `value={healthDataStats ? 0 : 0}`)— 约 0.5 人日,影响面 100% 角色。
2. **修 PP-06 告警角标写错 Tab**`useAlertPolling.ts:64` index:2 → 按 `app.config.ts:81` pagePath `pages/messages/index` 动态查找)— 约 0.5 人日,根除"危急值被埋没在商城 Tab"。
3. **接入 hapticHeavy/medium 到告警**utils/haptic.ts 已实现未接线)— 约 0.5 人日,危急值触觉反馈。
4. **Web Vitals 采集 MVP**(引入 web-vitals ~1KBmain.tsx 采集 LCP/CLS/INP走现有 /analytics/batch 上报)— 约 1 人日,唯一不依赖后端可观测性落地的性能观测手段。
---
## 六、主题级风险Risks
1. **monorepo 引入增加构建复杂度**packages/ 目录首次创建pnpm workspace 接入需同步改 apps/web 与 apps/miniprogram 的 tsconfig paths小程序对依赖体积敏感shared 包须支持 tree-shaking 且零浏览器 API 依赖。**缓解:** 第一轮只抽纯逻辑层传输层延迟shared 包严格 side-effect free。
2. **DTCG 标准对多主题支持尚在草案**:项目 Web 有 4 套主题,需用 $extensions 字段扩展,可能偏离标准。**缓解:** Phase 1 先用单主题 + variant 覆盖,多主题 Phase 2 再处理。
3. **视觉回归基线维护成本高**:初始会有大量误报需调阈值。**缓解:** 先只对 5 个核心页面(首页/工作台/告警详情/患者详情/咨询)跑,逐步扩展。
4. **Web 适老化业务价值需产品确认**医疗管理后台多为医护使用elder 模式优先级可能低于小程序适老化。**缓解:** 先做 motion/a11y token医护通用受益elder variant 待产品确认。
5. **情境感知适老化涉及行为数据采集**:与 PP-12 合规通道缺失形成张力。**缓解:** 信号只在端内计算、不落库、不上传,隐私政策声明;保留手动开关且不静默改设置。
6. **跨端契约抽象过早绑定错误 shape**6 类语义组件契约若设计不当会绑定错误 API surface。**缓解:** 先落地 2 类AlertCard + EmptyState验证后再扩。
7. **DTCG/Figma 双向 sync 多人协作冲突**Figma 同时被多人改易冲突。**缓解:** 引入 lock 机制或约定"每周一次设计 sync 日"。
8. **键盘快捷键与浏览器/输入法冲突**:需可配置。**缓解:** 参照 Linear/GitHub 键位规范,提供设置页自定义。
---
## 七、路线Roadmap对齐总览 Phase 0-3
| 阶段 | 时间 | 本主题交付物 |
|------|------|------------|
| **Phase 0** | 0-2 周 | Quick Wins 1-3PP-09 死链 + PP-06 角标 + haptic 接入Web Vitals MVP |
| **Phase 1** | 1-3 个月 | 举措 1 Phase 1dashboardNavMap + CI 门禁);举措 2 Phase 1DTCG 单源 + 生成器 + CI diff 检查);举措 3 Phase 1AlertCard + EmptyState 契约);举措 4 Phase 1多通道触达 |
| **Phase 2** | 3-6 个月 | 举措 1 Phase 2跨端 NavigationContract举措 2 Phase 2四态语义 token + Web 适老化 variant + motion/a11y token举措 3 Phase 2DTO 对齐);举措 5 Phase 2键盘快捷键 + 撤销模式 + 批量操作) |
| **Phase 3** | 6-12 个月 | 举措 2 Phase 3Figma 双向 sync + 视觉回归 + WCAG 门禁);举措 3 Phase 36 类契约全覆盖);举措 5 Phase 3情境感知适老化 + 情感文案) |
---
## 附录 A证据核验清单已逐项验证
| 论断 | 证据 | 核验结果 |
|------|------|---------|
| Web 色值 #2563EB | `apps/web/src/index.css:12` `--erp-primary: #2563eb` | ✓ |
| 小程序色值 #C4623A | `apps/miniprogram/src/styles/variables.scss:5` `$pri: #C4623A` | ✓ |
| App.tsx 4 套硬编码 themeConfigs | `apps/web/src/App.tsx:153/175/194/218` colorPrimary | ✓ |
| PP-09 死链 follow-ups | `AdminDashboard.tsx:51` navigate('/health/follow-ups') vs `routeConfig.ts:60` 实际 /health/follow-up-tasks | ✓ |
| PP-09 死链 vital-signs | `AdminDashboard.tsx:69` navigate('/health/vital-signs')routeConfig 无此路由) | ✓ |
| 僵尸写法 value=0 | `AdminDashboard.tsx:88` `value={healthDataStats ? 0 : 0}` | ✓ |
| PP-06 角标 index:2 | `useAlertPolling.ts:64` setTabBarBadge({index:2}) vs `app.config.ts:80` index 2=商城 | ✓ |
| 告警应到 index 3 | `app.config.ts:81` index 3=助手(messages) | ✓ |
| Web CACHE_TTL 5s | `apps/web/src/api/client.ts:11` `CACHE_TTL = 5000` | ✓ |
| Web 预检 token 刷新 | `client.ts:63/74` isTokenExpiringSoon | ✓ |
| 小程序 ResponseCache 60s | `apps/miniprogram/src/services/request.ts:3/66` | ✓ |
| 小程序 ERROR_CODE_MAP | `request.ts:15/246` | ✓ |
| Web 无 error_code 映射 | client.ts grep 无 ERROR_CODE_MAP | ✓ |
| Web aria-live 仅 1 处 | `PluginDashboardPage.tsx:448` role="alert" | ✓ |
| Web 无 web-vitals | grep web-vitals/onLCP/onCLS/onINP 全 0 命中 | ✓ |
| Web 无 i18n | grep useTranslation/FormattedMessage 全 0 命中 | ✓ |
| Web 无 elder-mode | grep elder-mode/elderMode 全 0 命中 | ✓ |
| 无 packages 目录 | `ls packages` → NO packages dir | ✓ |
---
> 本主题与 00-INDEX.md 的 T4(僵尸 UI 清理)、PP-06(告警角标)、PP-09(Dashboard 死链)强关联,是这些上线前就绪项向"长期治理体制"的升格。

View File

@@ -0,0 +1,125 @@
# 医疗合规与数据治理 — 主题综合
> 主持主题: 满足等保 2.0 / 数据安全法 / 个人信息保护法 / 病历管理规定,具备过认证能力,建立患者数据主权与可审计闭环。
> 日期: 2026-06-25 | 视角: 医疗合规专家 + 数据安全专家 + 隐私保护官(DPO) 三方综合
## 1. 主题愿景
把 HMS 从「修漏洞式合规」升级为「可举证的持续合规闭环」: 以**数据分类分级为底座**、**患者数据主权为核心**、**外部可验证的完整性举证为抗辩武器**, 让系统在等保三级测评、个保法年度审计、外资/跨境医疗租户采购评估三类场景下, 都能输出机器可验证的合规证据链, 而非依赖文档承诺或人工 review。
三位专家的核心共识: 当前 PII 加密(KEK+DEK+盲索引)、consent 实体、审计哈希链是难得的好底子, 但存在三类「结构性断裂」——加密不对称(name 明文漏网)、自动化只写不验(verify_hash_chain 无调用方 / DEK 版本硬编码为 1)、无数据主体权利履行通道(导出/删除/留存零实现)。这些不是上线前就绪度问题, 而是 6-12 个月合规演进必须补齐的能力。
## 2. 代码核实的关键事实(alreadyKnown 标注)
| # | 事实 | 证据 | 历史 | 增量判定 |
|---|------|------|------|----------|
| F1 | patient.name 明文存储 | `entity/patient.rs:12` `pub name: String`; `service/patient_service/crud.rs:174` `name: Set(req.name)`(同函数 id_number:178 走 pii::encrypt) | 00-INDEX PP-12 列了「name 加密 + name_hash」清单项 | 已识别但未深挖(对称加密缺失) |
| F2 | consent 医护角色全跳过 | `handler/consent_check.rs:9` BYPASS_ROLES=[admin,doctor,nurse,health_manager]; `:20` 命中即 return Ok | 无任何历史讨论提及 | **新发现** |
| F3 | verify_hash_chain 零调用 | `audit_service.rs:168` 定义, 全仓 grep 无调用方 | 无(PP-01/PP-05 同构「实现完整接线缺失」) | **新发现** |
| F4 | DEK key_version 硬编码 Some(1) | 7 处 `key_version: Set(Some(1))` 散布 patient/lab_report/diagnosis/doctor/follow_up/consultation service | 无 | **新发现**(轮换管线存在但消费侧硬编码) |
| F5 | erp-ai + erp-core 的 dev_default 无 cfg 防护 | `erp-core/src/crypto/mod.rs:37``erp-health/src/crypto.rs:48` 均无 `#[cfg(any(debug_assertions, test))]`; erp-ai 的 `config_resolver.rs:317` + `config_handler.rs:80` 直接调 `dev_default().kek()` | wiki 称「P4 dev_default 添加 cfg(debug_assertions)」仅 health/server 侧, erp-core/erp-ai 漏网 | **新发现**(推翻 wiki「已修复」) |
| F6 | consent_method 无签名验证 | `entity/consent.rs:21` `pub consent_method: Option<String>` 自由文本 | 无 | **新发现** |
| F7 | 无数据导出/留存管线 | `grep data_export\|retention_policy\|right_to_be_forgotten` 0 命中(仅菜单文案) | 00-INDEX PP-12 列了清单 | 已识别但未设计 |
| F8 | 脱敏硬编码 + 出口处调用 | `masking.rs` 硬编码 mask_id_number 前3后4, 仅 helper.rs 出口调用 | 无 | **新发现**(无策略表, 无 ABAC) |
## 3. 战略举措(归并 14 条提案为 4 项)
### I-1 PII 加密链路打通与 DEK 轮换闭环(合规地基)
**rationale**: F1+F4+F5 三处加密不对称, 让「PII 加密企业级」的宣称在测评员面前一戳即破——name 是唯一明文敏感 PII, DEK 版本恒为 1 让轮换按钮变成「点了就损坏历史数据」的潜伏地雷, erp-ai 全链路 dev KEK 更是「医疗化验单对任意代码读者明文」的硬伤。
**phases**:
1. **dev_default 编译期硬隔离** — 给 `erp-core/crypto/mod.rs:37` + `erp-health/crypto.rs:48` 的 dev_default 加 `#[cfg(any(debug_assertions, test))]`, release 构建编译失败; erp-ai 的 2 处调用改为从 AiState 注入 `Arc<PiiCrypto>`; 一次性脚本把 settings 表中 dev KEK 加密的 Provider key 用旧 KEK 解密 + 新 KEK 重加密。
2. **DEK 版本回溯解密** — 新增 `current_dek_for_tenant(tenant_id) -> (dek, key_version)`; 7 处 `Set(Some(1))` 改为真实 version; decrypt_field 按行 key_version 反查 DEK; LRU 缓存。
3. **name 加密 + name_hash 盲索引** — m000170 加 name_encrypted + name_hash; 复用 pii::encrypt; expand→migrate→contract 三步在线迁移; FHIR name 查询降级为 hash 精确 + 提示; 提供 feature flag 让国资体检中心可关掉加密满足特殊检索需求。
4. **回溯测试** — 轮换后 v2 写、v1 读、re-encrypt 迁移子命令全测。
**effortEstimate**: 5-7 人日(P1=1d / P2=2d / P3=2-3d / P4=1d)
**expectedImpact**: 消除唯一明文 PII 漏点 + 关闭「轮换即损坏」潜伏故障 + 关闭 AI 模块明文解密能力泄漏; 等保三级「数据机密性」从「宣称」升级为「可验证」。
**kpis**: [release 构建 0 处 dev_default 调用, 全仓 Sensitive-PII 列加密率 100%, DEK 轮换后历史数据可解密率 100%, PII 日志泄漏点 0]
**dependencies**: [P-3 数据分类分级引擎(确定哪些字段属 Sensitive), backfill 停机窗口或在线双写]
### I-2 数据分类分级引擎 + 字段级 ABAC 访问控制(可举证底座)
**rationale**: F8 显示脱敏是「硬编码 + 出口处一刀切」——医生看诊拿不到完整身份证号、运营拿到的密文要前端解。等保三级「分级保护 + 最小授权」要求按字段敏感度差异化控制, 且必须后端单点决策(前端硬编码脱敏在合规审计中判「未实施访问控制」)。
**phases**:
1. **分类分级元数据** — erp-core 新增 SensitivityLevel(Public/Internal/Confidential/Restricted/Sensitive-PII/Sensitive-Medical) + DataCategory 枚举; m000171 data_dictionary 表 或 Entity attribute 标注; 先覆盖 health+ai ~30 核心实体。
2. **CI 不变量测试**`#[test]` 断言所有标 Sensitive-* 的列必须满足三项之一(列名 _encrypted / 同名 _hash 盲索引 / RLS 强策略); 把「敏感字段未加密」变成编译/CI 失败。
3. **ABAC for PII** — m000172 pii_access_policy 表(role × field × reveal/mask/hash-only × purpose); handler 序列化前查策略(缓存 Redis, key=tenant+role, TTL 5min); 复用 mask_phone/mask_id_number + reveal_full; 每次reveal 写 audit_log 含 purpose。
4. **自动生成分类文档** — 输出 data_classification.md 作为测评呈堂材料; 前端从 `_pii_meta` 字段读呈现策略。
**effortEstimate**: 8-10 人日(分类标准需法务/医疗顾问参与)
**expectedImpact**: 把「分级保护」从口号变为机器可验证不变量; 脱敏从「上下文无关」升级为「医生 reveal / 运营 mask / 统计 hash-only」的合理工程; 产出测评呈堂材料。
**kpis**: [Sensitive-PII/Sensitive-Medical 字段 100% 标注, CI 分级不变量测试通过率 100%, 脱敏策略后端单点覆盖率 100%, 审计 purpose 字段填充率 100%]
**dependencies**: [I-1 P3 name 加密完成(确定最终敏感字段集), 法务/医疗顾问确认基因 vs 一般医疗诊断级别差异]
### I-3 数据主体权利履行闭环(个保法 §45/§47 + 病历留存)
**rationale**: F7 显示导出/删除/留存零实现。个保法 §45(可携带权)/§47(删除权)是硬性义务, 外资/跨境医疗租户和等保测评会专门查这两个端点。医疗行业特殊性: 删除权与病历法定留存(门诊 15 年 / 住院 30 年)冲突, 必须分级处理而非一刀切。
**phases**:
1. **导出 API**`GET /api/v1/health/patients/me/export` 聚合 patient+health_record+vital_signs+lab_report+consultation+device_readings+follow_up+appointment+consent+points; 输出 FHIR Bundle(JSON)+CSV 双格式; 异步任务复用 ai_analysis_queue; 产物 OSS 签名 URL 7 天 + 二次认证 + IP 绑定 + 下载次数限制 + 水印。
2. **分级删除/匿名化**`DELETE /api/v1/health/patients/me` 分级: 非法定数据(咨询历史/积分明细/AI 分析结果)立即匿名化或硬删除; 法定病历走 `retention_until` 字段 + 软删除; 引入 **anonymize 中间态**(去标识化保留统计价值)作为「删除权 vs 法定留存」的法律/技术平衡点。
3. **留存策略引擎** — m000173 retention_policy 表(entity/field/retain_days/action{delete|anonymize|archive}|scope); 接入 erp-server/tasks.rs 现有 spawn; 每日扫描 deleted_at+retain_days<now() 执行; 医疗 15 年 / 营销 5 年 / AI 日志 180 天 / 审计永久; 支持**租户级覆盖**(体检中心 vs 健康管理咨询差异化)。
4. **DPO 仪表盘** — 患者门户「我的数据权利」(小程序自助申请 + Web 医护代申请两通道); 操作写 audit_log(patient.data_exported / patient.erasure_requested); dry-run + 二级审批工作流(复用 erp-workflow); 宽限期 to_purge_at 7 天。
**effortEstimate**: 12-15 人日(P1=3d / P2=4d / P3=3d / P4=2-3d)
**expectedImpact**: 履行个保法 §45/§47 法定义务; 拿下外资/跨境医疗租户采购评估; anonymize 中间态兼顾合规与统计科研价值; 患者数据主权成为产品差异化。
**kpis**: [导出端点存在且通过 E2E, 删除权响应时效 ≤30 天(法定), 留存策略覆盖率 100% 核心实体, 误删事故 0(dry-run + 审批)]
**dependencies**: [I-2 分类分级(确定哪些字段属病历法定留存), 法务明确病历 vs 非病历字段清单, I-1 KEK 真实化(导出解密正确)]
### I-4 完整性举证与读操作审计(抗篡改 + 内部越权检测)
**rationale**: F3 显示 verify_hash_chain 零调用——审计哈希链「装了摄像头但不回放」, DBA 可整批重算 hash 篡改历史而不被发现, 等保测评员会质疑「自证清白」。F2 显示医护全跳过 consent——个保法对敏感信息处理无职业豁免, 只有「法定义务/紧急情况」豁免且必须显式记录。当前 AuditLog 只记写操作, 抓不到「内部人员越权查看」这一隐私事件主根因。
**phases**:
1. **哈希链实时验证** — 新增后台任务 `start_audit_chain_verifier(interval=3604s)` 调用 verify_hash_chain; 断裂即写 alerts + critical tracing::error + Prometheus `audit_chain_broken_count`; 关键操作(登录/权限变更/数据导出/批量删除)断裂 fail-closed 阻断同租户后续写; 首次运行做基线扫描避免上线即告警刷屏。
2. **外部锚点(WORM)** — 每日 ETL 取 audit_logs 当日最后 record_hash 写入 WORM 存储(腾讯云 COS 版本锁定桶 / 本地 append-only 文件 fallback); 提供 `verify_audit_chain(start,end)` 从外部锚点重放校验; 公开锚点桶地址。
3. **consent 法定豁免通道** — consent_check.rs 移除医护硬跳过, 改为「访问已撤回同意数据时强制填 access_purpose(急诊/法定义务/科研)」落审计; 提供急诊一键声明快速通道 + 事后审计(避免拖慢急诊流程)。
4. **读操作审计独立层** — 新增 `data_access_log` 表(独立于 audit_log, 按月分区同 device_readings 模式); 字段 viewer_user_id/role/patient_id/resource_table/access_purpose/consent_reference; trait DataAccessAuditor 注入所有读 PII handler; DPO 仪表盘异常访问检测(1h 查 200 患者 = 爬取告警); 异步批量写入控制性能。
**effortEstimate**: 10-12 人日(P1=2d / P2=2-3d / P3=2-3d / P4=4d)
**expectedImpact**: 审计从「自证清白」升级为「外部不可变锚点可举证」; 补齐等保三级明确要求而当前缺失的「读操作可审计」; 关闭医护 consent 特权黑洞。
**kpis**: [verify_hash_chain 接线后基线扫描 0 断裂, 读操作审计覆盖核心读 handler 100%, 医护访问已撤回 consent 数据记录率 100%, 异常访问告警 MTTD ≤1h]
**dependencies**: [PP-04 Alertmanager 通知出口(告警可达), WORM 存储基础设施(私有化客户需本地 fallback), I-2 ABAC purpose 字段复用]
## 4. 速赢(1-2 周可落地)
1. **dev_default 编译期硬隔离 + erp-ai KEK 注入**(I-1 P1, 1-2 人日) — 给 `erp-core/crypto/mod.rs:37` + `erp-health/crypto.rs:48``#[cfg(any(debug_assertions, test))]`; erp-ai 2 处调用改 AiState 注入。**收益**: 关闭「任意代码读者可解密医疗化验单」的硬伤, 推翻 wiki「已修复」误记, effort/impact 比最高。**风险**: 可能暴露其他漏网 dev_default 调用点, 需全仓扫描。
2. **审计哈希链接线 + 基线扫描**(I-4 P1, 1-2 人日) — `verify_hash_chain` 函数已实现, 只需 spawn 任务调用 + 首次基线扫描。**收益**: 补齐「完整性证明」能力, effort 极低(函数已存在), 推翻「装了摄像头但不回放」的纸老虎状态。
3. **PII 日志 PiiRedactLayer**(跨提案, 1 人日) — tracing-subscriber layer 统一拦截 name/phone/id_number/address/email 字段 + 写 test-log capturing 回归测试。**收益**: 根治 V2 审计修了仍残留的 PII 日志泄漏点, 从「逐文件删字段」升级为「框架级不变量」。
## 5. 主题级风险
1. **加密迁移不可逆** — name 加密 + DEK 轮换 + backfill 期间双列并存, 任一步骤失败可能导致历史数据永久不可解密。**缓解**: expand→migrate→contract 三步在线迁移 + dry-run + 低峰期 + 完整备份。
2. **删除权 vs 病历法定留存法律冲突** — 个保法 §47 与《医疗机构病历管理规定》直接对立, 误删病历触发反向违规。**缓解**: anonymize 中间态 + 法务明确分级清单 + 二级审批 + 宽限期。
3. **读操作审计性能与存储成本** — 每次读 PII 都写日志, 每月可能数 GB, 拖慢主路径。**缓解**: 异步批量写入 + Redis 缓冲 + 按月分区 + 按敏感度采样。
4. **WORM 存储私有化适配** — 单机部署的私有化客户无对象存储。**缓解**: 本地 append-only 文件 fallback + 每周导出。
5. **FHIR 互操作让步** — name 加密后 FHIR Patient.name 模糊查询语义弱化, 外部 FHIR 客户体验下降。**缓解**: 降级为 hash 精确 + 提示, feature flag 让国资体检中心可关闭加密。
6. **purpose-scoped consent 误伤 AI** — AI 模块复用 patient 数据, purpose 未覆盖会误伤现有功能。**缓解**: 默认 purpose=treatment 兼容 + ai_training/cross_border_share 作为可选授权。
## 6. 专家分歧调和(最终取舍)
| 分歧 | 一方立场 | 另一方立场 | 最终取舍 |
|------|---------|-----------|----------|
| **name 是否全量加密** | 后端/性能: FHIR 互操作 + decrypt-on-read 性能损耗 | 合规/DPO: 红线优先, 准标识符重识别风险 | **采纳加密 + feature flag**——默认加密满足合规, 国资体检中心可关闭; DTO 批量 decrypt + 缓存缓解性能; FHIR 降级 hash 精确。合规红线不让步, 但给特殊场景留逃生口。 |
| **数据导出是否 over-engineering** | 产品: 体检中心不会真用 | 合规: 法定义务 + 采购评估硬指标 | **采纳**——个保法 §45 是法定义务不是产品功能, 缺即判不合规; anonymize 中间态对统计科研有附加价值, 非纯成本。 |
| **审计外部锚点是否过度** | 安全: 内部哈希链已足够 | 合规: 内部哈希链自证清白无证明力 | **采纳外部锚点 + 本地 fallback**——等保三级「完整性」核心是「防内部篡改可举证」, 但私有化客户用本地 append-only 文件, 不强制对象存储。 |
| **医护跳过 consent 是否便利** | 业务: 急诊流程不能拖慢 | 合规/DPO: 无职业豁免, 须显式记录法定豁免理由 | **采纳法定豁免通道**——移除硬跳过, 改为急诊一键声明 + access_purpose 落审计; 平衡急诊时效与合规举证。 |
| **脱敏前端还是后端** | 前端: 按角色判断 | DPO/数据安全: 后端单点决策 + 审计 | **采纳后端 ABAC**——前端硬编码脱敏在合规审计中判「未实施访问控制」, 必须后端单点 + `_pii_meta` 元数据驱动前端呈现。 |
| **DEK 轮换上线前还是上线后** | DevOps: 先上线再轮换 | 数据安全: 轮换管线已损坏, 上线即定时炸弹 | **采纳上线前修复**——这是「生产即损坏」潜伏故障, 管理员误点轮换按钮即历史数据不可解密, 列为上线前 blocker。 |
| **数据销毁永久保留 vs 分级** | AI/产品: 永久保留支撑训练 | DPO/数据安全: 违反数据最小化 + 法定删除权 | **采纳分级留存**——医疗按法定期(15/30 年)、营销短期(5 年)、AI 日志(180 天), anonymize 兼顾科研与合规。 |
## 7. 路线(6-12 个月)
| 阶段 | 时间窗 | 举措 | 里程碑 |
|------|--------|------|--------|
| **T0 上线前 blocker** | 立即-2 周 | I-1 P1(dev_default 隔离)+ I-4 P1(哈希链接线)+ PiiRedactLayer | release 构建 0 dev_default / 哈希链基线扫描通过 / PII 日志 0 泄漏 |
| **T1 加密链路打通** | 1-2 月 | I-1 完整(name 加密 + DEK 轮换回溯) | Sensitive-PII 加密率 100% / 轮换可用 |
| **T2 分类分级 + ABAC** | 2-4 月 | I-2 完整(元数据 + CI 不变量 + ABAC 策略) | 测评呈堂材料输出 / 脱敏后端单点 |
| **T3 数据主体权利** | 3-6 月 | I-3 完整(导出 + 分级删除 + 留存引擎) | 个保法 §45/§47 端点可演示 / DPO 仪表盘 |
| **T4 完整性 + 读审计** | 4-8 月 | I-4 完整(外部锚点 + consent 法定豁免 + 读审计) | 等保三级测评可过 / 读操作异常检测上线 |
| **T5 跨境/DPIA 网关** | 6-12 月 | data_transfer_gateway + AI 调用数据最小化 | 外资/跨境租户合规通道预留 |
> 总投入估算: T0-T4 约 35-44 人日(渐进式, 不阻塞 V1 上线)。T5 为演进预留, 当前无跨境租户, 优先级低但架构位要留。

View File

@@ -0,0 +1,217 @@
# 主题 06 — 商业增长与 SaaS 规模化
> 日期: 2026-06-25 | 分支: feat/media-library-banner | 阶段: V1 上线后 6-12 个月演进
> 专家: SaaS 产品经理 / 商业化架构师 / 增长专家
> 证据基准: 代码核实(非臆测),所有论断标注文件路径:行号
---
## 一、主题愿景
把 HMS 从「单租户项目交付」转变为「按价值计费、配置化开通、数据飞轮驱动续约」的可规模化 SaaS核心抓手是把现有但分散的半成品`ai_usage`/`ai_tenant_config` 配额引擎、`points_*` 6 表、`EventBus`+Outbox、AnalysisQueue缝合为三根商业主线**统一计量计费中枢**(每多一个租户的实施成本从 3-5 人天降到小时级)、**配置化交付蓝图**(场景模板化,让交付从写代码变成配 JSON、**主动关怀+行为经济飞轮**(把 AI 队列和积分系统从被动记录变成无人值守的留存与续约引擎。SaaS 定价模型一旦随首批医疗客户固化(合同周期长、改价难),后续调整成本极高,必须在签第 2-5 个客户前确立计量与分层的数据基础,否则会陷入「每个客户一份定制合同」的项目制泥潭——这正是从交付到 SaaS 失败的典型路径。
**关键判断(与决策简报 00-INDEX.md 对齐)**:本主题不阻塞 V1 上线Phase 0 拆炸弹优先),但所有举措建立在 PP-01 死信接线 + PP-05 队列消费者 + PP-07 RLS FORCE + PP-02 分区自愈这四个 CRITICAL 前置修复完成之后。商业化是上线后 6-12 个月的事,但计量埋点与 entitlement 抽象必须提前到 Phase 1否则后期改造代价数倍。
---
## 二、专家提案摘要与核实
### 已核实的事实alreadyKnown=true作为商业化基础设施的真实存在
| 资产 | 位置 | 现状(核实) | 商业化含义 |
|------|------|-------------|-----------|
| AI 配额引擎 | `crates/erp-ai/src/service/quota.rs:7-134` | 仅 token 单维度仅「超限拒绝」L41-49无账单出口 | 可泛化为多维度计量 |
| AI 日聚合 | `crates/erp-ai/src/service/usage.rs:110-158` `aggregate_daily` | 按分析类型聚合到 `ai_usage_daily`,仅 AI | rollup 模式可复用到全模块 |
| 积分 6 表 + CAS | `points_service/account.rs:82` `earn_points` + `event.rs` | 4 个 earn 触发点checkin/follow_up/care_plan/offline_eventFIFO 消费+CAS 防超卖 | 积分商城已 78% 核销基础设施,差人民币 leg |
| 积分过期 | `event.rs:772-875` `expire_points` | 已实现 12 个月过期清理 + `POINTS_EXPIRED` 事件 | decay 机制已存在,增长专家「需加通胀设计」可调参数 |
| `tenant.settings` JSON | `migration/m20260410_000001:28` | 空可空 JSON 列 | branding_json 零迁移可承载白标 |
| 小程序主题 token | `apps/miniprogram/src/styles/tokens.scss` + `token-values.ts` | 11 级字号 + 结构 token + `.doctor-mode`/`.elder-mode` 覆盖 | 白标皮肤沉没成本已就绪,边际成本极低 |
| EventBus + Outbox | `crates/erp-core/src/events.rs` | 31 health 事件/82 发布点/15 消费者 | 计量 consumer 可挂载,零新增基础设施 |
| `retry_dead_letters` | `events.rs:382` | 函数存在但 `tasks.rs` 未注册调度PP-01 | 计量 consumer 必须先于死信修复,否则计量丢失无察觉 |
### 增量缺口alreadyKnown=false本主题真正要建的
- `claim_next``analysis_queue.rs:92`**无生产消费方**——`auto_analysis.rs:108` 只 enqueue每 24h从不消费队列PP-05 确认。
-`tenant_usage_ledger`/`metering_daily`/`tenant_quota`/`tenant_plan`/`tenant_blueprint`/`tenant_entitlements` 表——全仓 grep 零命中。
-`AppError::PaymentRequired`/402 映射——`error.rs:17-39` 仅 8 变体NotFound/Validation/Unauthorized/Forbidden/Conflict/VersionConflict/RateLimit/Internal
-`EntitlementChecker` trait / `quota_guard` 中间件 / `entitlement_middleware`
-`start_usage_rollup` / `start_auto_analysis_worker` 调度(`tasks.rs` 只有 event_cleanup + pool_metrics
---
## 三、战略举措5 项,归并 3 专家 14 提案)
### 举措 1 — 统一计量计费中枢Metering Hub
**归并**: SaaS-PMP1 + 商业架构M1 + M2配额即产品。把 `ai_usage`/`ai_tenant_config``points_*` 升级为跨模块事件驱动计量 + 套餐配额矩阵。
**rationale**: 现有计量是两座孤岛——AI 只统计 token`quota.rs:41`),积分商城无人民币核销闭环。不统一计量就无法对 AI/告警/咨询/FHIR 调用定价AI 越主动成本越不可控(商业反模式)。
**phases**:
1. **P1埋点1-2w**: 新增 `metering_daily(tenant_id, metric_key, day, qty, dim_hash, unique 索引)` 表,用 `ON CONFLICT DO UPDATE` 原子累加;在 `usage.rs``points_service/event.rs``device_reading_service``consultation_service`、fhir handler 挂 `Meter::record()` 写入。**独立连接池**避免 PP-08 RLS 串扰。
2. **P2配额中间件2-3w**: 新增 `AppError::PaymentRequired` 变体402抽象 `quota_guard` 中间件(类似 `require_permission`),计费端点声明 `dimension`;预置 3 套餐 seed基础/专业/旗舰)到 `tenant_plan` + `tenant_quota` 表。
3. **P3账单导出1w**: `GET /api/v1/admin/tenants/{id}/usage` + `/invoice?period=YYYY-MM` 返回 JSON+CSV。不接 Stripe医疗 to B 公对公转账为主)。
**effortEstimate**: 4-6 周1-2 后端 + 财务定义价格表后置)
**expectedImpact**: 打开按价值付费收入天花板;每个 AI 分析/告警/咨询/上传可计量可计费;为 P2 套餐分层提供数据基础。从「内部成本控制」反转为「商业化基础设施」。
**kpis**: 计量覆盖率(计费事件/总事件);月度账单生成准时率;配额误拦截事故数(目标 0首 3 客户计量对账差异率(<1%)。
**dependencies**: PP-01 死信接线(计量 consumer 必须有重试兜底PP-08 RLS 修复(独立池);财务/销售定义价格表非技术决策PP-02 分区自愈device_active_count 计量依赖)。
**关键取舍**: 不引入 Stripe/Lago 等通用计费引擎(医疗账单维度强绑定业务事件,自建更贴合,省 2-3 周集成。配额硬上限hard_cap 402必须为 `critical_alert`/`article` 推送设白名单,否则会阻断医疗业务——这是真实产品权衡,不是 bug。
---
### 举措 2 — 配置化交付蓝图Tenant Blueprint + Onboarding
**归并**: SaaS-PMP3 + 商业架构M4 + 增长专家配置化场景。把新租户开通从「跑迁移+手工 SQL」降到「点按钮」。
**rationale**: 现状菜单/权限/字典/告警阈值/随访模板/积分规则全靠 `m0000xx_seed_*.rs` 一次性写入,新租户要么继承默认要么手工 SQL——这是「项目交付而非 SaaS」的根因。体检中心连锁20-50 门店)是 HMS 核心增长客群,交付周期是规模化第一瓶颈。
**phases**:
1. **P1蓝图表1-2w**: 新增 `tenant_blueprint(id, name, module_set, quota_plan_id, seed_data JSON, branding_json, schema_version)`。把现有 seed 内容重构为按行业分类的 preset 数据包(`general_hospital`/`dialysis_center`/`checkup_clinic`)存 `config/presets/*.toml`(数据非迁移,可版本化)。
2. **P2provision 端点2-3w**: `POST /api/v1/admin/tenants/{id}/provision`,事务化建 tenant→角色/权限/菜单→points_account→触发 `tenant.provisioned` 事件(补齐 §3.4「每个事件有消费者」铁律)。配套 CLI `erp-server tenant create --blueprint=standard`(顺便补 PP-11 subcommand 缺失)。
3. **P3导出标杆1w**: 「导出当前租户配置为 preset」按钮白名单字段 + PII 过滤(与 PP-12 同源),把标杆客户最佳实践沉淀为可复用模板。
**effortEstimate**: 4-5 周
**expectedImpact**: 实施周期从 3-5 人天降到小时级;第 N 个客户复用第 1 个客户的最佳实践配置;支撑体检连锁批量获客;让销售/实施可在签单当天开通试用(医疗采购「先试后买」决策周期)。
**kpis**: 新租户开通耗时(目标 <30 分钟含验证preset 复用率(新租户用 preset 而非手工 SQL 的比例);试点客户首月留存。
**dependencies**: PP-11 CD pipeline + 迁移独立子命令preset schema_version + 增量 patch 机制版本兼容admin 审批流/邀请码(防刷租户消耗配额)。
---
### 举措 3 — 主动关怀 + 行为经济飞轮
**归并**: 增长专家 G1AI 队列接通)+ G2积分行为经济+ G4转介绍。把半成品 AI 队列和积分系统从被动记录变成留存引擎。
**rationale**: `claim_next` 存在但无消费方(`analysis_queue.rs:92`),客户基于「主动关怀」付费但队列是死存储。积分仅 3-4 触发点无消费压力,是债务不是货币。医疗行业 CAC 高,但现有患者家属是天然高质量线索池。
**phases**:
1. **P1队列消费者 MVP2w**: `tasks.rs` 新增 `start_auto_analysis_worker`tokio::spawn 循环调 `claim_next`,按 tenant 并发拉取,失败走 `retry_dead_letters`(同时修 PP-01。worker 数与 `ai_tenant_config` 配额做令牌桶联动(避免单租户耗尽 Provider
2. **P2三种触达2-3w**: 高风险患者→医护 action_inbox「建议介入」项化验上传→患者 AI 解读 push每日巡护→依从性下降患者关怀消息复用 `article_service.rs:228` 事件消费者模式)。每条触达带 `source=auto_analysis` + outcome 跟踪字段,回流增长看板。
3. **P3积分行为经济1-2w**: 扩充 earn 订阅BLE 上传 +X、连续达标 +bonus、健康评估 +Y、咨询评价 +Z复用 DomainEvent 无需新表);积分消耗侧新增「健康权益兑换」(优先挂号/远程咨询时长/家属账号)而非仅商城商品;`points_rule``decay_policy` 调参(过期已实现,需运营定义通胀曲线)。
4. **P4转介绍闭环2w**: `patient``referred_by_patient_id` 自引用 + `referral_code`;小程序「我的」生成小程序码;反作弊(同手机号/身份证仅一次被介绍,复用 `phone_hash` 盲索引)+ 首次就诊才发放(防刷);运营看板「转介绍漏斗」。
**effortEstimate**: 7-9 周(可分 P1→P4 串行,先有流水线再谈能力升级)
**expectedImpact**: 把「已付费但空转」的 AI 能力变成转介绍/续约引擎;患者依从性驱动数据密度;医疗转介绍产品化(合规边界内,奖励建档/健康行为而非就诊消费)。
**kpis**: AI 队列消费吞吐pending 积压 <阈值主动触达→就诊转化率积分兑换深度人均月兑换次数转介绍建档数→就诊数漏斗Top KOC 识别覆盖。
**dependencies**: PP-01 死信重试PP-05 队列消费者(即本举措 P1AI Provider 配额联动(`chat_handler` fallback chain医务审核行为定义防「诱导过度检查」合规`consent` 拦截器(家属数据共享授权链路,已有)。
**关键取舍**: 增长专家主张 AI 队列接通优先级高于 PP-01/PP-02 等基础设施修复。调和:同一个 worker 同时承载 AI 消费和死信重试(都用 retry 机制不冲突),但顺序上 PP-01 仍是 Phase 0 阻塞项(计量 consumer 需要重试兜底,否则触达丢失无人察觉)。
---
### 举措 4 — 套餐分层与价值证明Feature Gating + Health Score
**归并**: SaaS-PMP2Feature Gating+ PMP4ROI 仪表盘)+ 增长专家 G3租户健康度看板。把计量数据变成续约武器。
**rationale**: 常规 SaaS 只给客户看「使用量」;本举措做双视角——客户看自己 ROI续约理由平台看客户健康度流失预警。且不新搭数据仓库全用现有 `stats_service` + `usage_ledger` 在线计算(刻意避免 BI 工具运维负担,匹配 DevOps 4.2 短板现实)。
**phases**:
1. **P1EntitlementChecker2w**: `crates/erp-core` 定义 `EntitlementChecker` trait各 handler 入口(紧邻 `require_permission`)调 `require_feature("health.ai_copilot")`;集中 `entitlement_middleware` 按 route→feature 映射表统一拦截(防漏检=免费用户白嫖付费功能。区分边界permission 管「能不能做」安全entitlement 管「付费没付费」(商业)。
2. **P2套餐定义1w**: 预置 3 套餐存 `config/entitlements.toml`(非迁移,便于调整);前端 `apps/web` 新增「套餐管理」页给 super-admin小程序按 entitlements 动态显示/隐藏 AI 助手、深度报告入口。
3. **P3健康度+ROI2-3w**: `stats_service` 新增 `tenant_health_score`五维度活跃患者占比、AI 触达数、告警闭环率、随访完成率、医护日活);`GET /api/v1/admin/tenants/{id}/health-score` 给 super-admin 流失预警;每租户 AdminDashboard 新增「我的 ROI」卡片。用现有 `stats_dto` + 4 Dashboard 组件基础设施。
**effortEstimate**: 5-7 周
**expectedImpact**: 防止免费能力裸奔entitlement 漏检=收入损失);客户续约有量化依据;平台 NRR/流失预警从感觉变成可预警指标。
**kpis**: entitlement 检查覆盖率(计费端点/总端点);流失预警准确率(回测校准);续约率提升;客户 ROI 卡片使用率。
**dependencies**: PP-01 死信重试(「告警闭环率」指标依赖告警链路完整);运营/客户成功定义健康度权重(非技术拍板);跨租户聚合查询性能(物化视图或离线 rollup与举措 1 共用调度)。
---
### 举措 5 — 生态扩展预留(白标皮肤 + 开放 API
**归并**: SaaS-PMP5。为 6-12 个月后渠道分销(区域代理、体检连锁白标)铺路,**低成本预留**而非立即变现。
**rationale**: 小程序已投入做完整 CSS 变量主题体系(`tokens.scss` + `token-values.ts`,原为长者/医生模式服务),稍作扩展就是白标基础设施,把已沉没成本变成未来收入入口。
**phases**:
1. **P1白标皮肤1-2w与安全解耦可并行先做**: 抽象 `tenant.branding_json`logo_url, primary_color, app_name, login_bg_url`tenant.settings`零新表Web 侧 Ant Design ConfigProvider runtime 切 theme token小程序已有 `var(--tk-*)` 读取后动态注入 CSS 变量。
2. **P2开放 API3-4w严格 gate 在安全修复后)**: 现有 14 FHIR 端点旁新增 `GET /api/v1/open/patients` 等;`tenant_api_keys`scope 字段控制可访问资源);速率限制复用 rate_limit 中间件但独立配额API 调用写入举措 1 的 `metering_daily`为「API 调用收费」埋点)。
**effortEstimate**: 4-6 周P1 与 P2 可拆分P1 不依赖安全修复)
**expectedImpact**: 渠道分销/白标能力预留ISV 生态接入基础API 调用计费埋点。
**kpis**: 白标皮肤落地租户数;开放 API 接入 ISV 数API 调用计量覆盖率。
**dependencies**: **P2 必须在 PP-03 Redis 凭据轮换 + PP-07 RLS FORCE + PP-08 连接池修复完成之后**(开放 API 扩大攻击面API Key 管理需吊销/轮换/审计否则成为持续安全债务白标需明确「皮肤级」vs「独立实例」边界避免过度承诺触碰多租户架构边界
---
## 四、速赢1-2 周可落地)
1. **白标皮肤 P1branding_json + 主题注入)**`tenant.settings` 已存在m000001:28小程序 `var(--tk-*)` 已就绪Web ConfigProvider runtime 切换零新依赖。1-2 周可演示,与安全修复解耦可立即推进。把已沉没成本变成销售演示亮点。
2. **计量埋点 P1metering_daily 表 + 5 模块 Meter::record 挂载)** — 复用 `usage.rs:110` `aggregate_daily` 模式 + EventBus独立连接池避免 RLS 串扰。2 周可让首批租户的 AI/上传/咨询/告警/FHIR 用量进入统一账本,为定价提供真实数据,无需等价格表。
3. **积分 decay 参数化 + 行为事件订阅扩充**`expire_points``event.rs:772`)已实现,只需运营定义过期曲线 + 在 `event/points.rs` 加 BLE 上传/连续达标规则。1-2 周,零新表。
---
## 五、主题级风险
1. **计量精度争议**:漏计/重复计引发客户争议。需幂等 rollup`period+metric_key` 唯一索引)+ 对账报表。`device_active_count` 依赖 PP-02 分区先修好,否则 2026-09 起计量数据全断。
2. **配额硬上限阻断医疗业务**`hard_cap` 402 若误配会拦截 `critical_alert`/`article` 推送等安全相关事件。必须设白名单不参与配额这会让配额体系出现「holes」是真实产品权衡
3. **preset 与 schema 漂移**preset `seed_data` JSON 与迁移 schema 版本漂移风险高,需 `schema_version` + 增量 patch + 启动校验。导出标杆配置可能含敏感数据(阈值/药品字典),需白名单 + PII 过滤。
4. **AI Provider 成本线性增长**:主动关怀 worker 接通后AI 成本随租户数线性增长,必须配额+降级(`chat_handler` fallback chain+ 质量门禁(最小可读长度+引用校验,防 AI 输出空/think 块伤害患者体验)。
5. **医疗转介绍合规红线**《医疗广告管理办法》禁止诱导就医积分必须针对「健康行为」而非「就诊消费」——表述上避免「拉新返现」。HMS 已有 `consent` 拦截器 + `phone_hash` 盲索引是做合规转介绍的天然优势,但需医务审核行为定义。
6. **entitlement 漏检=收入损失**feature 检查点散落各 handler漏检=免费用户白嫖付费功能。需集中中间件而非散落调用。
7. **连接池分片过度设计争议**:架构师会质疑 `tenant_scoped_pool`(按 tenant_id 分片)过度设计,主张 `SET LOCAL IN TRANSACTION`。折中:仅 top-N 活跃租户分片,长尾走 SET LOCAL但医疗场景间歇性跨租户泄漏哪怕 0.01% 是合规红线。
8. **开放 API 安全债务**API Key 若无吊销/轮换/审计会成为持续安全债务,且必须 PP-03/07/08 修复后才能开放,否则放大攻击面。
---
## 六、调和专家分歧后的最终取舍
### 分歧 1商业化 vs 基础设施修复的优先级
- **SaaS-PMP/增长专家**:主张计量/onboarding 优先,商业化倒逼测试投入。
- **测试/安全专家**:主张 PP-10 测试 5.5、PP-01/02/07 等 P0 阻塞项必须先修,否则商业化是空中楼阁。
- **最终取舍****并行非串行**。Phase 0上线前 2 周)只做 4 CRITICAL 拆炸弹Phase 11-3 月)计量埋点 + entitlement 抽象与测试门禁、可观测性并行推进。商业化(尤其计量埋点与 onboarding确实能带来收入和客户压力倒逼投入**402 硬上限/配额中间件/开放 API 等「可阻断业务」的商业化能力严格 gate 在 PP-01/02/07/08 之后**。埋点可先做(只读,无阻断风险)。
### 分歧 2连接池分片 vs SET LOCAL
- **商业化架构师**:主张 `tenant_scoped_pool` per-tenant 分片。
- **架构师**:主张 `SET LOCAL IN TRANSACTION` 即可。
- **最终取舍****折中方案——top-N 活跃租户分片 + 长尾 SET LOCAL**。医疗场景跨租户泄漏是合规红线,分片工程成本可接受;但全量分片会显著增加 PG `max_connections` 压力,需 PgBouncer transaction pooling 前置。不追求一步到位。
### 分歧 3积分通胀decay是否伤害体验
- **增长专家**:主张积分 12 个月过期制造兑换压力。
- **产品/医疗专家**:可能认为过期伤害用户体验。
- **最终取舍****保留 decay 但分层**。基础积分(签到/上传)可较长过期或不过期;行为奖励积分(连续达标/转介绍设较短过期6-12 月)+ 高价值权益兑换(优先挂号/咨询时长)拉动兑换紧迫性。`expire_points` 已实现,调参即可。医疗合规上 decay 不得诱导「为积分过度检查」,需医务审核。
### 分歧 4计量计费层是否独立为 erp-billing crate
- **架构师**:会反驳计量污染业务模块边界,应独立 crate。
- **SaaS-PMP**:主张先在 erp-core 抽 trait + erp-server 轻量 rollup避免过早引入新 crate项目已 17 crate
- **最终取舍****先 trait 后 crate**。Phase 1 在 `erp-core` 定义 `EntitlementChecker`/`Meter` trait + `erp-server` 做 rollup 调度,验证商业模式后再视复杂度拆 `erp-billing` crate。不过早增加模块化复杂度。
### 分歧 5开放 API 时机
- **安全专家**:强烈反对 PP-03/07/08 未修复前推进开放 API。
- **SaaS-PMP**:主张 P5 第一步「白标皮肤」与安全风险解耦可并行先做。
- **最终取舍****拆分 P5**。白标皮肤branding_jsonPhase 1 可立即推进(与安全无关);开放 API 严格 gate 在 PP-03 Redis 轮换 + PP-07 RLS FORCE + PP-08 连接池修复之后Phase 2-3
### 核心立场SaaS-PMP 提出且被采纳)
SaaS 定价模型一旦随首批医疗客户固化(合同周期长、改价难),后续调整成本极高。**必须在签第 2-5 个客户前确立计量与分层的数据基础**——这是从交付到 SaaS 成败的分水岭。因此计量埋点(举措 1 P1+ entitlement 抽象(举措 4 P1必须在首批客户签约前完成即便 V1 尚未完全稳定。这是商业时间窗口驱动的,不是技术就绪度驱动的。
---
## 七、路线(与决策简报 00-INDEX.md 四阶段对齐)
| 阶段 | 时间 | 本主题交付 |
|------|------|-----------|
| **Phase 0** | T-0 ~ T+2w | 不阻塞上线。仅可做白标皮肤 P1branding_json作销售演示。所有计费/onboarding 推迟。 |
| **Phase 1** | T+1M ~ T+3M | 举措 1 P1-P2计量埋点 + 配额中间件 + 账单导出);举措 3 P1-P2队列消费者 MVP + 三种触达);举措 4 P1-P2EntitlementChecker + 套餐定义);举措 2 P1蓝图表。**与测试门禁/可观测性并行**。 |
| **Phase 2** | T+3M ~ T+6M | 举措 2 P2-P3provision 端点 + 导出标杆);举措 3 P3-P4积分行为经济 + 转介绍);举措 4 P3健康度 + ROI举措 5 P2开放 API安全修复后。 |
| **Phase 3** | T+6M ~ T+12M | 套餐矩阵成熟 + 渠道分销试点 + ISV 生态接入 + 量到价的迭代(基于真实账单数据调价)。连接池分片 top-N 方案落地(配合高可用)。 |
---
> 本章节所有论断均经代码核实(文件路径:行号见正文无臆测。核心增量判断HMS 的商业化基础设施零件(计量/配额/积分/队列/主题/事件总线)**都已存在但未缝合**,本主题的工作是「接线」而非「造零件」,因此工作量集中在 trait 抽象、中间件、rollup 调度与运营配置,不涉及大规模重写。