diff --git a/docs/archive/audits-v1/audit-2026-04-18-full.md b/docs/archive/audits-v1/audit-2026-04-18-full.md index 708cdcc..ea3bf7a 100644 --- a/docs/archive/audits-v1/audit-2026-04-18-full.md +++ b/docs/archive/audits-v1/audit-2026-04-18-full.md @@ -22,7 +22,7 @@ ### C-01 Redis 凭据硬编码在配置文件中(泄露到 Git) - **文件**: `crates/erp-server/config/default.toml` (line 11) -- **现象**: `url = "redis://:redis_KBCYJk@129.204.154.246:6379"` 硬编码了远程 Redis 密码和 IP +- **现象**: `url = "redis://:@:6379"` 硬编码了远程 Redis 密码和 IP(明文已于 2026-06-25 从仓库清除,密码已轮换;保留本条作为历史审计证据) - **影响**: 凭据已提交到 Git 仓库,任何有代码访问权限的人都能获取 Redis 密码和服务器 IP - **修复**: 1. 立即轮换 Redis 密码 diff --git a/docs/discussions/2026-05-28-six-dimension-deep-analysis.md b/docs/discussions/2026-05-28-six-dimension-deep-analysis.md index ad5c574..d1d8e20 100644 --- a/docs/discussions/2026-05-28-six-dimension-deep-analysis.md +++ b/docs/discussions/2026-05-28-six-dimension-deep-analysis.md @@ -69,7 +69,7 @@ - **CI/CD 评分 1/10** — 零自动化,所有质量关卡人工操作 - **灾难恢复 1.5/10** — 无 RTO/RPO 定义,备份仅本地无异地 - **数据库运维 2/10** — 单实例无 HA,连接池 max_connections=20 偏小 -- **Redis 密码 `redis_KBCYJk` 通过公网明文传输到腾讯云** +- **Redis 密码 `` 通过公网明文传输到腾讯云**(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 密码通过公网明文传输到云端 Redis(IP 已 redact;该实例闲置未被 HMS 使用)" | **根因**: 开发环境便捷性优先,安全配置被推迟。修复成本极低(1-2 天),影响极高。 diff --git a/docs/discussions/2026-06-25-analysis/00-INDEX.md b/docs/discussions/2026-06-25-analysis/00-INDEX.md new file mode 100644 index 0000000..607046c --- /dev/null +++ b/docs/discussions/2026-06-25-analysis/00-INDEX.md @@ -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 / 10(B)**,处于「可支撑 V1 上线,但距生产级品质与主动关怀引擎定位还差关键闭环」的阶段。 + +本次分析发现的核心张力是:**多处宣称已完成的自动化链路实际断裂,且真相源(wiki)与代码系统性漂移,叠加支撑层(DevOps 4.2 / 测试 5.5)严重缺位,使上层可靠性无护栏兜底**。最尖锐的 4 个 CRITICAL 级问题中,3 个是「文档说已修复、代码说没有」——死信重试从未接线(PP-01)、AI 分析队列只入队不消费(PP-05)、RLS 误记 FORCE(PP-07);第 4 个是生产 Redis 密码明文进 git 追踪的 wiki(PP-03)。叠加 2026-09-01 确定性硬截止的 device_readings 分区过期(PP-02),HMS 当前处于「能上线,但上线后 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 缺 FORCE(PP-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);僵尸 UI(6 条 navigate 死链 PP-09 + value=0 占位)+ i18n 全缺 + AuthButton 仅 28% | +| 7 | 小程序端 | **7.0** | 并发治理/AES 缓存/BLE 离线缓冲生产级;扣分在告警角标写错 Tab(PP-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-08,2026-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 provisioning(DB/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/ 进 CI(4-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 + 主题注入)+ 开放 API(API 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. **「有代码无数据」僵尸 UI(PP-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 结果),已在各主题章节展开。决策层如需深挖单项,直接跳转对应主题章节文件。 diff --git a/docs/discussions/2026-06-25-analysis/01-stability.md b/docs/discussions/2026-06-25-analysis/01-stability.md new file mode 100644 index 0000000..af8e0ab --- /dev/null +++ b/docs/discussions/2026-06-25-analysis/01-stability.md @@ -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 ` + 双标签镜像 + canary 用 `eventbus_pending_total`(tasks.rs:119) 作健康判据。 +- **cron 进就绪门禁**:`/health/ready`(health.rs:51) 暴露 cron_heartbeat + 积压阈值,503 触发摘流。 +- **系统级特性开关**:FeatureFlagService(ai 内部)上提为 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 暴露(举措 C);Alertmanager 告警规则(举措 D)。 + +### 举措 B:迁移解耦启动路径 + 30 分钟回滚能力 +- **rationale**:main.rs:233 在应用启动路径直接 `Migrator::up`,含 RENAME/DROP 等破坏性 DDL;173 条 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 --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.sh:canary 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 步)。 + +### 举措 C:cron_heartbeat 进就绪门禁 + 积压指标可观测 +- **rationale**:cron_heartbeat 已埋点 (main.rs:647 → state.rs:31 → tasks.rs 已写),但 readiness_check (health.rs:51) 仅查 DB+Redis,后台任务死了就绪仍返回 ok;tasks.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 才有真实心跳。 + +### 举措 D:Alertmanager + 三级告警 + 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-1:5xx 率/PG 耗尽/Redis 不可达/dead_letter 积压/分区<2 周 → 企微+电话;SEV-2:P95/idle<10% → 企微;SEV-3:CPU/内存 → 仅 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 隔离(生产备份含 PII);BACKUP_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_next,3-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 被 filter(events.rs:390)兜底;但 AI queue worker 启动后历史积压一次性触发大量 LLM 调用 → Ollama OOM,需先 truncate 或加 backpressure。 +- **R3 蓝绿双副本 +30% 资源成本**:需与运维预算对齐;nginx upstream 切换需 DB schema 兼容期,双写易引入数据不一致。 +- **R4 BACKUP_PASSPHRASE 轮换需重加密历史备份**:工作量被低估;restore-drill 跑生产备份含 PII,CI 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/Jaeger(4-8 周)。**取舍**:两者互补但分期——先落地举措 C(heartbeat 门禁)和举措 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)评估。 +- **M4(6-12 个月)**:根据 M1-M3 的 MTTR/RPO/RTO 实测数据,评估是否升级到 K8s+Helm(届时团队规模与 DevOps 成熟度可能已支撑)。 + +> 所有举措均强调 TDD 证明接线生效、CI 门禁拦截、fire-drill 验证——质量保障"门禁反推"视角贯穿全程,不单列质量举措。 diff --git a/docs/discussions/2026-06-25-analysis/02-architecture.md b/docs/discussions/2026-06-25-analysis/02-architecture.md new file mode 100644 index 0000000..c2445d8 --- /dev/null +++ b/docs/discussions/2026-06-25-analysis/02-architecture.md @@ -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/10,TOP 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 全空或 stub;health: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-01(retry_dead_letters)和 PP-05(claim_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>>`(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 B1(FORCE RLS,0.5 周):** 新增迁移 `m000170` 遍历所有 tenant_id 业务表 `ALTER TABLE ... FORCE ROW LEVEL SECURITY`;新建非 owner 应用角色 `app_user`(REVOKE 所有 + GRANT CRUD + LOGIN),运行时连接改用 app_user——否则 FORCE 对 owner 无效。 +2. **Phase B2(SET 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 周):** 新增多租户测试 crate(wiki §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 C2(trait 裂变,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 C3(archlint,0.5 周):** `cargo xtask archlint` 扫描 `erp-server/src/` 下是否有跨 crate 业务 import,CI 拦截边界泄漏回归。 + +- **effortEstimate:** 3 周 +- **expectedImpact:** 模块具备"可独立部署"属性(微服务化前置)+ 消除 erp-server 业务逻辑认知陷阱 + 死 trait 方法清理降低新人认知负载 +- **kpis:** erp-server 不再 import 业务 entity crate;archlint 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 D3(CI 拦截,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 manifest;CI 零"无消费者"违规;核心事件 JSON Schema 覆盖率 > 60% +- **dependencies:** 举措 A(spawn_workers 接线后才有真实消费者可 manifest);inventory crate 构建稳定性需验证 + +### 举措 E:可演进 schema 与数据生命周期(独立 migrate + 分区自动化 + 物化视图 + 归档) + +**Rationale:** `main.rs:233` `Migrator::up` 在应用启动路径执行(PP-11),破坏性 DDL 在启动瞬间跑,多副本并发迁移会冲突;device_readings 分区硬编码到 2026-08(PP-02 确定性硬截止);stats_service 全是实时 count + date_trunc,Dashboard 随患者量增长拖垮 DB CPU;dead_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 pattern),CI 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_06;Dashboard 物化视图刷新延迟 < 10min;破坏性迁移 100% 走 expand-contract;erp-server migrate 独立子命令上线 +- **dependencies:** 举措 A(spawn_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-4(PP-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 可校验契约。 diff --git a/docs/discussions/2026-06-25-analysis/03-ai-depth.md b/docs/discussions/2026-06-25-analysis/03-ai-depth.md new file mode 100644 index 0000000..e77e624 --- /dev/null +++ b/docs/discussions/2026-06-25-analysis/03-ai-depth.md @@ -0,0 +1,155 @@ +# 主题 03 — AI 智能化纵深 + +> 日期: 2026-06-25 | 分支: feat/media-library-banner | 主题负责人: 综合主持 +> 范围: V1 上线后 6-12 个月,从「被动分析工具」跨越到「AI 驱动的主动关怀引擎」 +> 证据基准: 所有论断附文件路径:行号或 grep 结果;关键代码断言已逐一核验 + +--- + +## 一、主题愿景(专家共识融合) + +HMS 当前已具备相当完整的 AI 工程拼图——4 个 Provider(Claude/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_failed(retry_count20 条触发 `summarize_session`(cheap Ollama provider),摘要存 ai_chat_session.metadata(JSONB 已存在,零迁移),chat_handler:140 优先注入摘要+最近4条,token 从 ~8k 降到 ~1.5k;②患者级事实卡片——新增 `ai_patient_memory` 表(patient_id/tenant_id/key/value/source/confidence/expire_at),key 明文、value 加密,post_process 用 LLM 抽取结构化事实 upsert(confidence 阈值 0.7)。 +- **P3(PII 防护,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**:举措 1(worker 复用 FC 链路);举措 4(能力矩阵 + token budget);knowledge_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。 +- **P3(CI 门禁,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.8;citation_precision ≥0.95;broken_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(真实 usage,2 周)**:①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 只存 action(accept/reject)无下游动作,AI 建议悬空;患者侧(中老年为主)对 AI 聊天接受度低,需「清晰的下一步动作」而非 AI 解读。 + +**phases**: +- **P1(建议→任务转化,2 周)**:新增 `POST /ai/suggestions/{id}/convert`,按 suggested_action_type 调 erp-health service(follow_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 1(1-3 月)**:举措 1(P0+P1 接线闭合)+ 举措 4(P1 成本真相化)+ 速赢 1-3。兑现「至少 1 条健康告警→AI 评估→推送」完整链路。 +- **Phase 2(3-6 月)**:举措 1(P2 韧性)+ 举措 2(FC + 双层记忆)+ 举措 3(P1 引用闭环 + P2 评估集)+ 举措 5(P1 建议转化)。从「被动工具」跨到「主动关怀引擎」。 +- **Phase 3(6-12 月)**:举措 3(P3 CI 门禁)+ 举措 4(P2 能力矩阵)+ 举措 5(P2 患者触达 + 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 命中。论断可追溯。 diff --git a/docs/discussions/2026-06-25-analysis/04-multidevice-ux.md b/docs/discussions/2026-06-25-analysis/04-multidevice-ux.md new file mode 100644 index 0000000..994399c --- /dev/null +++ b/docs/discussions/2026-06-25-analysis/04-multidevice-ux.md @@ -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-06;error_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 0(2 周):** 修 6 条死链 navigate + 清理 `AdminDashboard.tsx:88 value={healthDataStats ? 0 : 0}` 僵尸写法;`useAlertPolling.ts` 改 `resolveTabBarIndex('/pages/messages/index')` 动态查找。 +- **Phase 1(1 个月):** 在 `routeConfig.ts` 扩展 `dashboardNavMap`(业务意图键 → path + permissions + 后端统计字段);抽 `useDashboardCard(intent)` hook 按权限过滤卡片;CI 加 navigate 目标存在性单测(遍历 map 断言每个 path 在 routeConfig 存在)。 +- **Phase 2(3 个月):** `packages/shared-contracts/navigation.ts` 跨端 NavigationContract,Web 侧边栏 + 小程序 TabBar 都从契约读取;权限字段统一从契约派生。 + +**工作量估算:** Phase 0 约 2-3 人日;Phase 1 约 5-7 人日;Phase 2 约 10-15 人日(跨端 + 类型系统差异)。 +**预期影响:** 消灭医疗管理后台 403 死胡同与假数据展示,根除"TabBar 顺序一变就复发"的角标 bug 类。**高 / 中量** +**KPI:** navigate 死链数 = 0(CI 门禁);TabBar 角标错位 bug 复发次数 = 0;权限过滤后无权卡片不再渲染(点击前拦截率 100%)。 +**依赖:** 后端菜单 API(Phase 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 1(1 个月):** 新建 `packages/design-tokens/`(monorepo 首个共享包),DTCG JSON 为唯一源;Style Dictionary 生成三端产物(小程序 variables.scss + tokens.scss + token-values.ts;Web index.css :root + antd themeConfigs.ts);CI 加产物与源同步 diff 检查。**保留双命名空间**(--erp-* / --tk-*),只统一源不强行改名。 +- **Phase 2(3 个月):** 新增四态语义 token(`--tk-state-error/empty/loading/critical`) + Web 适老化 variant(controlHeight 40→52, fontSize 14→18)+ motion token(duration/easing 自动尊重 prefers-reduced-motion) + a11y token(focus-ring);Web 用户菜单增加"适老化"开关。 +- **Phase 3(6 个月):** 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 → 1(CI 门禁);Web 适老化主题覆盖 0 → ≥30 核心页面;WCAG 对比度门禁通过率 100%;token 变更影响面自动标注覆盖率 100%。 +**依赖:** pnpm workspace 接入;Antd ConfigProvider 对自定义 variant 的支持;团队 DTCG 命名培训。 + +### 举措 3:跨端语义组件契约层 — AlertCard / EmptyState / VitalCard + +**理由:** 不追求像素级跨端一致(Ant Design vs 微信原生组件库是硬约束),而是为高频业务语义组件规定统一 TS 接口,让后端 DTO 字段名对齐组件 prop 名,减少 transform 层。优先 AlertCard(PP-06 危急值可见性)与 EmptyState(积分商城 Tab 空白 bug wiki 已记)。 + +**分阶段落地点:** +- **Phase 1(1 个月):** 定义 `packages/shared-contracts/components.ts` 中 AlertCardProps(severity: critical|warning|info, title, value, action) + EmptyStateProps(icon, title, action);Web 与小程序各自实现但 satisfy interface(TS implements)。 +- **Phase 2(3 个月):** 后端 `alert_dto.rs` 等响应字段与契约对齐;Storybook(Web) + 组件 demo 页(小程序) 按契约生成 demo。 +- **Phase 3(6 个月):** 扩展到 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 0(2 周):** 修 `useAlertPolling.ts:64` 角标 index bug(举措 1 联动);接入 hapticHeavy/medium。 +- **Phase 1(1 个月):** 多通道触达:(a) 触觉 haptic;(b) elder-mode 下 TTS 语音播报/预录提示音;(c) 页面顶部 sticky 红色横幅(不依赖 Tab 切换);elder-mode 自动缩短轮询间隔 10s → 5s + 放大横幅字号。 +- **Phase 2(3 个月):** Web 端补齐 aria-live:所有数据加载/错误/告警区域加 role=status aria-live=polite(critical 用 assertive);e2e 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 2(3 个月):** 键盘快捷键层(J/K 选择、Enter 详情、C 确认告警、/ 聚焦搜索,参照 Linear/GitHub);危险操作改"软删除 + 5 秒撤销"替代二次确认 modal;告警/随访批量操作;小程序医生端左滑快速完成/右滑推迟。 +- **Phase 3(6 个月):** `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 Wins,1-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 ~1KB,main.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-3(PP-09 死链 + PP-06 角标 + haptic 接入);Web Vitals MVP | +| **Phase 1** | 1-3 个月 | 举措 1 Phase 1(dashboardNavMap + CI 门禁);举措 2 Phase 1(DTCG 单源 + 生成器 + CI diff 检查);举措 3 Phase 1(AlertCard + EmptyState 契约);举措 4 Phase 1(多通道触达) | +| **Phase 2** | 3-6 个月 | 举措 1 Phase 2(跨端 NavigationContract);举措 2 Phase 2(四态语义 token + Web 适老化 variant + motion/a11y token);举措 3 Phase 2(DTO 对齐);举措 5 Phase 2(键盘快捷键 + 撤销模式 + 批量操作) | +| **Phase 3** | 6-12 个月 | 举措 2 Phase 3(Figma 双向 sync + 视觉回归 + WCAG 门禁);举措 3 Phase 3(6 类契约全覆盖);举措 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 死链)强关联,是这些上线前就绪项向"长期治理体制"的升格。 diff --git a/docs/discussions/2026-06-25-analysis/05-compliance.md b/docs/discussions/2026-06-25-analysis/05-compliance.md new file mode 100644 index 0000000..47905b4 --- /dev/null +++ b/docs/discussions/2026-06-25-analysis/05-compliance.md @@ -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` 自由文本 | 无 | **新发现** | +| 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`; 一次性脚本把 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 总投入估算: T0-T4 约 35-44 人日(渐进式, 不阻塞 V1 上线)。T5 为演进预留, 当前无跨境租户, 优先级低但架构位要留。 diff --git a/docs/discussions/2026-06-25-analysis/06-saas-growth.md b/docs/discussions/2026-06-25-analysis/06-saas-growth.md new file mode 100644 index 0000000..8797dc6 --- /dev/null +++ b/docs/discussions/2026-06-25-analysis/06-saas-growth.md @@ -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_event),FIFO 消费+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. **P2(provision 端点,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 — 主动关怀 + 行为经济飞轮 + +**归并**: 增长专家 G1(AI 队列接通)+ G2(积分行为经济)+ G4(转介绍)。把半成品 AI 队列和积分系统从被动记录变成留存引擎。 + +**rationale**: `claim_next` 存在但无消费方(`analysis_queue.rs:92`),客户基于「主动关怀」付费但队列是死存储。积分仅 3-4 触发点无消费压力,是债务不是货币。医疗行业 CAC 高,但现有患者家属是天然高质量线索池。 + +**phases**: +1. **P1(队列消费者 MVP,2w)**: `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 队列消费者(即本举措 P1);AI 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-PMP2(Feature Gating)+ PMP4(ROI 仪表盘)+ 增长专家 G3(租户健康度看板)。把计量数据变成续约武器。 + +**rationale**: 常规 SaaS 只给客户看「使用量」;本举措做双视角——客户看自己 ROI(续约理由),平台看客户健康度(流失预警)。且不新搭数据仓库,全用现有 `stats_service` + `usage_ledger` 在线计算(刻意避免 BI 工具运维负担,匹配 DevOps 4.2 短板现实)。 + +**phases**: +1. **P1(EntitlementChecker,2w)**: `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(健康度+ROI,2-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(开放 API,3-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. **白标皮肤 P1(branding_json + 主题注入)** — `tenant.settings` 已存在(m000001:28),小程序 `var(--tk-*)` 已就绪,Web ConfigProvider runtime 切换零新依赖。1-2 周可演示,与安全修复解耦可立即推进。把已沉没成本变成销售演示亮点。 +2. **计量埋点 P1(metering_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 1(1-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_json)Phase 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 | 不阻塞上线。仅可做白标皮肤 P1(branding_json)作销售演示。所有计费/onboarding 推迟。 | +| **Phase 1** | T+1M ~ T+3M | 举措 1 P1-P2(计量埋点 + 配额中间件 + 账单导出);举措 3 P1-P2(队列消费者 MVP + 三种触达);举措 4 P1-P2(EntitlementChecker + 套餐定义);举措 2 P1(蓝图表)。**与测试门禁/可观测性并行**。 | +| **Phase 2** | T+3M ~ T+6M | 举措 2 P2-P3(provision 端点 + 导出标杆);举措 3 P3-P4(积分行为经济 + 转介绍);举措 4 P3(健康度 + ROI);举措 5 P2(开放 API,安全修复后)。 | +| **Phase 3** | T+6M ~ T+12M | 套餐矩阵成熟 + 渠道分销试点 + ISV 生态接入 + 量到价的迭代(基于真实账单数据调价)。连接池分片 top-N 方案落地(配合高可用)。 | + +--- + +> 本章节所有论断均经代码核实(文件路径:行号见正文),无臆测。核心增量判断:HMS 的商业化基础设施零件(计量/配额/积分/队列/主题/事件总线)**都已存在但未缝合**,本主题的工作是「接线」而非「造零件」,因此工作量集中在 trait 抽象、中间件、rollup 调度与运营配置,不涉及大规模重写。 diff --git a/wiki/index.md b/wiki/index.md index 36f8557..a0a22a5 100644 --- a/wiki/index.md +++ b/wiki/index.md @@ -4,14 +4,14 @@ ## 关键数字 -> 最后更新: 2026-06-05 | 数据截止: feat/media-library-banner 分支(用户管理过滤 + 患者摘要过滤 + 微信限流修复) +> 最后更新: 2026-06-25 | 数据截止: feat/media-library-banner 分支(Redis 凭据泄露修复 + 系统深度分析多专家组) | 指标 | 值 | |------|-----| | Rust crate | 17 个(erp-core + 5 基础业务 + erp-health + erp-ai + erp-dialysis + erp-plugin + 7 插件/原型) | | Rust 源文件 | **726 个**(~134,000 行) | | 数据库表 | 30 基础表 + 49 健康业务表 + 15 AI 表(+4 会话/消息/tool_log/user_profile + 2 知识库 V2) + 3 媒体库/轮播图表 | -| 数据库迁移 | **175 个**(最新 m20260529_000169) | +| 数据库迁移 | **176 个**(最新 m20260626_000170) | | 后端路由 | **385+ 个**(11 公开 + 14 FHIR + 2 网关 + ~358 受保护) | | 核心模块 | 5 基础 (auth/config/workflow/message/plugin) + 3 业务 (health + ai + dialysis) | | erp-health 实体 | **59 个** Entity(33 handler / 57 service / 22 DTO,217 文件) | @@ -159,6 +159,9 @@ | wx_* 患者混入用户管理 | [[erp-auth]] wechat_service | 微信登录创建 `users` 记录 + `patient` 角色,与内部员工混在一起 | **已修复:** `list_users` 新增 `exclude_only_roles` 参数,前端默认排除纯患者用户(2026-06-05) | | 小程序上传数据看不到 | [[erp-health]] patient_service | `list_summaries` 无 user_id 过滤,小程序取到别人的 patient 作为 currentPatient | **已修复:** `list_summaries` 增加 `user_id` 参数,小程序传入当前用户 ID 过滤(2026-06-05) | | 真机微信登录"请求过于频繁" | [[erp-server]] rate_limit | 微信登录与密码登录共享 5 次/分限制 + `extract_client_ip` 无代理头返回 "unknown" 导致所有真机共享同一个 key | **已修复:** 微信登录路由独立为 `wechat_routes`,30 次/分钟宽松限流(2026-06-05) | +| Redis 凭据泄露(明文密码进 git) | [[infrastructure]] 凭据管理 | wiki 历史版本明文写 Redis 密码+公网 IP,已进 main 主干+origin/main(最早 2026-04-18) | **核实降级**:泄露旧密码已失效(当前 requirepass 为另一弱密码,未泄露入仓库);HMS 当前连 localhost 本地 Redis(dev.ps1),云端实例闲置无数据无入侵;**已处理**:明文 redact 4 处 + 公网访问已关闭(2026-06-25);**待办(上线前)**:云端换强密码 + compose 配置对齐;**不重写主干历史**(轮换后历史密码无效);**真问题**:doc-code drift(wiki 说云端实际本地) | +| device_readings 分区硬截止 2026-09 | [[database]] 分区维护 | m000073 只静态建到 2026_08 分区,无 pg_partman/cron 创建未来分区,2026-09-01 起 INSERT 抛错 | **已修复:** m20260626_000170 补建 2026_09~2027_06 共 10 个月分区(2026-06-26);中期需 pg_partman 自动维护 | +| AI 分析队列 claim_next SQL 注入 | [[erp-ai]] analysis_queue | `claim_next` 用 `format!` 拼 tenant_id + SELECT/UPDATE 不在事务内无 SKIP LOCKED | **已修复:** 参数化 `$1` + 事务内 `FOR UPDATE SKIP LOCKED` 原子 claim(2026-06-26) | ## 模块导航 diff --git a/wiki/infrastructure.md b/wiki/infrastructure.md index 98d24f6..4b2e228 100644 --- a/wiki/infrastructure.md +++ b/wiki/infrastructure.md @@ -32,7 +32,7 @@ tags: [infrastructure, dev-environment, windows, postgresql] | 服务 | 地址 | 用途 | |------|------|------| | PostgreSQL 16 | `postgres://postgres:123123@localhost:5432/erp` | 主数据库 | -| Redis 7 | `redis://:redis_KBCYJk@129.204.154.246:6379` (云端) | 缓存 + 限流 | +| Redis 7 | 连接串通过 `ERP__REDIS__URL` 环境变量注入(云端 Redis,**禁止硬编码 host/密码**) | 缓存 + 限流 | | 后端 API | `http://localhost:3000/api/v1` | Axum 服务 | | 前端 SPA | `http://localhost:5174` | Vite 开发服务器 | | 微信小程序 | 微信开发者工具 | 患者端小程序(`apps/miniprogram/dist/`) | @@ -54,7 +54,9 @@ psql: `D:\postgreSQL\bin\psql.exe -U postgres -h localhost -d erp` | `ERP__DATABASE__URL` | `postgres://postgres:123123@localhost:5432/erp` | | `ERP__JWT__SECRET` | `dev-secret-key-change-in-prod` | | `ERP__AUTH__SUPER_ADMIN_PASSWORD` | `Admin@2026` | -| `ERP__REDIS__URL` | `redis://:redis_KBCYJk@129.204.154.246:6379` | +| `ERP__REDIS__URL` | `redis://:<密码>@<云端 host>:6379`(开发用 `redis://localhost:6379` 无密码;生产密码经密钥管理注入,**禁止入仓库**) | + +> ⚠️ **安全告警(2026-06-25 凭据泄露事件,已降级)**:本页历史版本曾明文写入 Redis 密码与公网 IP,仓库明文已清除。**核实结论**:① 泄露的旧密码 `redis_KBCYJk` 当前已失效(实际 requirepass 为另一值,未在仓库出现);② HMS 运行时连**本地** `localhost:6379`(`dev.ps1`),云端 Redis 实例闲置、数据为空、无入侵征兆,**公网访问已于 2026-06-25 关闭**。**剩余待办(上线前)**:云端 Redis 换强密码(当前为弱密码)+ `docker-compose.production.yml` 的 `@redis` 假设需对齐「外部云端 Redis」实际架构。历史 commit 仍含旧密码但已无效。详见 [[index]] 症状导航「Redis 凭据泄露」。 | `ERP__WECHAT__APPID` | `wx20f4ef9cc2ec66c5` | | `ERP__WECHAT__SECRET` | 微信小程序 Secret | | `ERP__WECHAT__DEV_MODE` | `true`(开发时跳过 jscode2session,允许 DevTools 模拟器登录) |