docs: T40 UI 审计报告 + wiki 更新 + Docker 配置

- T40 UI 审计计划和结果文档(docs/qa/)
- wiki 更新:miniprogram 设计系统合规审计记录 + index 关键数字更新
- 审计 V2 完整报告(docs/audits/v2/)
- 讨论记录文档(docs/discussions/)
- 设计规格和实施计划(docs/superpowers/)
- 角色测试计划和结果(docs/qa/role-test-*)
- Docker 生产部署配置
This commit is contained in:
iven
2026-05-13 23:29:42 +08:00
parent 212c08b7ae
commit df1d85bfde
78 changed files with 10345 additions and 39 deletions

View File

@@ -0,0 +1,783 @@
# AI 引擎 v2 架构设计规格
> **日期:** 2026-05-05 | **状态:** Draft | **范围:** erp-ai 模块演进
> **实施周期:** Q22-3 个月)| **方案:** 混合 — 结构化核心 + RAG 接口预留
## 1. 背景与动机
### 1.1 当前状态
erp-ai 模块已完成 Phase 1 MVP6 实体、SSE 流式分析、Claude 单 Provider具备
- `AiProvider` trait + `ClaudeProvider` 实现reqwest 直接调用 Anthropic API
- SSE 三层流式架构Provider → AnalysisService → Handler
- DB 级 SHA-256 缓存复用
- `LocalRulesEngine`10 条规则,已实现但未集成到路由层)
- 自动定时分析(每 24h 扫描高风险患者)
- 双通道输出解析 + AI 建议生命周期管理
- 透析 KDIGO 风险评分器14 条专科规则)
### 1.2 核心问题
1. **单点依赖** — 所有分析绑定 Claude无降级能力Provider 故障 = 服务不可用
2. **知识注入缺失** — Prompt 无结构化医学知识支撑,分析质量依赖模型通用能力
3. **无配额管控** — 无成本感知,无租户预算,商业化前提缺失
4. **管线断裂** — 事件驱动触发仅记录日志,无法自动响应体征异常等关键事件
5. **缓存效率低** — 仅 DB 级缓存,高频重复分析仍需查询数据库
### 1.3 设计目标
- **租户级 Provider 选择** — 客户选择本地Ollama或云端Claude/OpenAILLM
- **按分析类型可覆盖** — 不同分析类型可使用不同 Provider/模型
- **故障自动降级** — Provider 不可用时回退规则引擎,服务不中断
- **结构化知识库** — KDIGO 规则、药物相互作用、科室指南以结构化数据注入 Prompt
- **RAG 接口预留** — `KnowledgeSource` trait 统一抽象pgvector 扩展预启用
- **配额 & 成本感知** — 月度 token 预算、每患者日限、成本追踪、预算告警
- **事件驱动管线** — 体征异常/新化验报告/透析完成自动触发分析
- **两级缓存** — Redis TTL + DB 持久化,提升重复分析响应速度
---
## 2. 整体架构
### 2.1 核心数据流
```
┌─────────────────────────┐
│ 触发来源 │
│ SSE手动/定时/事件驱动 │
└───────────┬─────────────┘
┌───────────▼─────────────┐
│ 1. 配额检查 │
│ QuotaService.check() │
└───────────┬─────────────┘
│ 通过
┌───────────▼─────────────┐
│ 2. 缓存检查 │
│ CacheService.get() │
│ (Redis TTL + DB hash) │
└───┬───────────────┬─────┘
命中 │ │ 未命中
▼ ▼
直接返回 ┌───────────────────┐
│ 3. 路由决策 │
│ Router.resolve() │
│ (租户配置→分析类型) │
└──┬──────────┬─────┘
规则引擎 │ │ LLM
▼ ▼
LocalRules ProviderRegistry
(零成本) .get_provider()
┌────────▼────────┐
│ 4. 知识上下文注入 │
│ KnowledgeSource │
│ .get_context() │
└────────┬────────┘
┌────────▼────────┐
│ 5. 执行分析 │
│ Provider调用 │
│ + SSE流式返回 │
└────────┬────────┘
┌────────▼────────┐
│ 6. 后处理 │
│ 解析+建议+事件 │
│ + 用量记录 │
│ + 缓存写入 │
└─────────────────┘
```
### 2.2 新增文件结构
```
crates/erp-ai/src/
├── provider/
│ ├── mod.rs # AiProvider trait已有不变
│ ├── claude.rs # ClaudeProvider已有不变
│ ├── openai.rs # OpenAIProvider新增
│ ├── ollama.rs # OllamaProvider新增
│ └── registry.rs # ProviderRegistry新增
├── config.rs # AiConfig + ProviderConfig新增
├── knowledge/
│ ├── mod.rs # KnowledgeSource trait新增
│ ├── structured.rs # StructuredKnowledgeSource新增
│ └── vector.rs # VectorKnowledgeSource预留 stub
├── service/
│ ├── analysis.rs # 现有(扩展:集成 ProviderRegistry + 知识库上下文)
│ ├── auto_analysis.rs # 现有(改为入队逻辑)
│ ├── local_rules.rs # 现有(扩展:更多规则 + 与知识库联动)
│ ├── quota.rs # 配额服务(新增)
│ ├── cache.rs # 缓存服务(新增)
│ ├── analysis_queue.rs # 分析队列(新增)
│ └── ... # 其余不变
└── handler/
├── mod.rs # 现有(扩展:配额检查 + 降级逻辑)
├── provider_admin_handler.rs # Provider 管理 API新增
└── quota_handler.rs # 配额管理 API新增
```
### 2.3 核心抽象
```rust
// provider/registry.rs
pub struct ProviderRegistry {
providers: DashMap<String, ProviderEntry>,
health_checker: tokio::task::JoinHandle<()>,
}
pub struct ProviderEntry {
provider: Box<dyn AiProvider>,
config: ProviderConfig,
health: Arc<RwLock<ProviderHealth>>,
}
pub enum ProviderHealth {
Healthy { last_check: DateTime<Utc> },
Degraded { last_check: DateTime<Utc>, error: String },
Unavailable { since: DateTime<Utc>, error: String },
}
// config.rs
pub struct AiConfig {
pub default_provider: String,
pub providers: HashMap<String, ProviderConfig>,
pub cache_ttl_seconds: u64,
pub quota_check_enabled: bool,
}
pub struct ProviderConfig {
pub provider_type: ProviderType, // Claude / OpenAI / Ollama / Rules
pub api_key_env: Option<String>,
pub base_url: Option<String>,
pub default_model: String,
pub max_tokens: u32,
pub temperature: f32,
pub is_enabled: bool,
}
// knowledge/mod.rs
#[async_trait]
pub trait KnowledgeSource: Send + Sync {
async fn get_context(&self, query: &KnowledgeQuery) -> AiResult<KnowledgeContext>;
fn source_type(&self) -> &str;
async fn health_check(&self) -> bool;
async fn entry_count(&self) -> u64;
}
```
---
## 3. 多 Provider 路由引擎
### 3.1 路由决策流程
每次分析请求按以下优先级链解析 Provider
1. 分析类型覆盖(`ai_tenant_configs.analysis_type_overrides` JSONB
2. 租户默认 Provider`ai_tenant_configs.default_provider`
3. ProviderRegistry 健康检查 → 不可用时走降级链:
- 配置的 `fallback_provider`
- 其他可用 Provider按配置顺序
- `LocalRulesEngine`(零成本降级)
### 3.2 租户级 Provider 配置
新增数据库表:
```sql
CREATE TABLE ai_tenant_configs (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID NOT NULL, -- 不设外键tenants 表在 erp-auth跨模块不直接引用
default_provider VARCHAR(50) NOT NULL DEFAULT 'claude',
fallback_provider VARCHAR(50),
monthly_token_budget BIGINT,
analysis_type_overrides JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID NOT NULL,
updated_by UUID NOT NULL,
deleted_at TIMESTAMPTZ,
version INT NOT NULL DEFAULT 1,
UNIQUE(tenant_id)
);
```
`analysis_type_overrides` 示例:
```json
{
"lab_report": "claude",
"trends": "ollama",
"report_summary": "openai"
}
```
### 3.3 ProviderRegistry 核心方法
```rust
impl ProviderRegistry {
/// 解析最终使用的 Provider含降级链
pub async fn resolve(
&self,
tenant_config: &AiTenantConfig,
analysis_type: &AnalysisType,
) -> AiResult<ResolvedProvider>;
/// 注册新 Provider初始化阶段调用一次性构建
pub fn register(&self, name: String, provider: Box<dyn AiProvider>);
/// 获取指定 Provider用于 Admin 测试)
pub fn get_provider(&self, name: &str) -> Option<&dyn AiProvider>;
/// 全量健康检查(后台 60s 间隔)
pub async fn health_check_all(&self) -> HashMap<String, ProviderHealth>;
}
```
### 3.4 后台健康检查
每 60 秒对所有注册 Provider 执行轻量级 health_check
- Claude: `GET /v1/models`
- OpenAI: `GET /v1/models`
- Ollama: `GET /api/tags`
- 连续 3 次失败标记为 Unavailable
- 恢复后自动标记 Healthy
### 3.5 新增 Provider 实现
#### OpenAIProvider
```rust
pub struct OpenAiProvider {
client: reqwest::Client,
api_key: String,
base_url: String, // 默认 https://api.openai.com
}
```
- OpenAI Chat Completions API`/v1/chat/completions`
- SSE 流式解析 `data: [DONE]` 边界
- token 用量从 `usage` 字段提取
#### OllamaProvider
```rust
pub struct OllamaProvider {
client: reqwest::Client,
base_url: String, // 默认 http://localhost:11434
}
```
- Ollama API`/api/chat``stream: true`
- 无 API Key无 token 计费(成本为零)
- 适合私有化部署客户
### 3.6 Provider 管理 API
```
GET /api/v1/ai/providers — 列出所有 Provider 及健康状态
GET /api/v1/ai/providers/:name — 单个 Provider 详情
POST /api/v1/ai/providers/:name/test — 连通性测试
PUT /api/v1/ai/tenant-config — 更新租户 Provider 配置
GET /api/v1/ai/tenant-config — 获取租户配置
```
权限码:`ai.provider.manage`(全局 Provider 操作)、`ai.analysis.manage`(租户配置)
### 3.7 AnalysisService 重构
当前 `AnalysisService` 持有 `provider: Box<dyn AiProvider>`(单实例硬绑定)。重构为:
```rust
// 重构前service/analysis.rs
pub struct AnalysisService {
provider: Box<dyn AiProvider>, // 硬编码 Claude
// ...
}
// 重构后
pub struct AnalysisService {
registry: Arc<ProviderRegistry>, // 替换为 Registry
quota: Arc<QuotaService>,
cache: Arc<CacheService>,
knowledge: Arc<dyn KnowledgeSource>,
// ...
}
```
`AiState` 构造变更:
```rust
// state.rs 重构
pub struct AiState {
db: DatabaseConnection,
event_bus: Arc<EventBus>,
analysis: AnalysisService,
prompt: PromptService,
usage: UsageService,
suggestion: SuggestionService,
health_provider: Arc<dyn HealthDataProvider>,
// 新增字段由 AnalysisService 内部持有
}
```
迁移策略Phase 1 先保留 `AnalysisService` 接口不变,内部将 `provider` 替换为 `registry`,对外透明。
---
## 4. 知识库 & RAG 架构
### 4.1 三层知识模型
| 层级 | 范围 | 内容 | 存储 |
|------|------|------|------|
| L1 核心规则层 | 系统级 | KDIGO 分期规则、危急值阈值、药物相互作用规则 | `ai_knowledge_rules` 表 |
| L2 常识层 | 平台级 | ICD-10 映射、药物数据库、检验参考范围、通用健康建议 | `ai_knowledge_references` 表 |
| L3 本地化层 | 租户级 | 机构自定义指南、科室特色方案、本地化患者教育内容 | `ai_knowledge_guides` 表 |
### 4.2 知识查询接口
```rust
pub struct KnowledgeQuery {
pub analysis_type: AnalysisType,
pub patient_context: PatientSummary, // 脱敏后的患者概况
pub query_text: Option<String>,
pub tenant_id: Uuid,
}
// PatientSummary 获取方式:通过 raw SQL 查询(避免跨 crate 依赖 erp-health
// 与现有 handler/mod.rs 中 HealthDataProvider 的 raw SQL 模式一致
// 查询 patients 表基础信息(年龄/性别/标签),不查询 PII 字段
pub struct KnowledgeContext {
pub source: String,
pub context_text: String,
pub references: Vec<Reference>,
pub confidence: f32,
}
```
### 4.3 结构化知识源Phase 1 实现)
```rust
pub struct StructuredKnowledgeSource {
db: DatabaseConnection,
rules_engine: LocalRulesEngine,
}
impl StructuredKnowledgeSource {
/// 查询流程:
/// 1. 匹配 L1 规则conditions JSONB 匹配分析类型和患者状态)
/// 2. 查询 L2 参考category + tags 匹配)
/// 3. 查询 L3 指南tenant_id + department 匹配)
/// 4. 合并为 context_text按优先级排序
async fn query_structured(&self, query: &KnowledgeQuery) -> AiResult<Vec<KnowledgeEntry>>;
}
```
### 4.4 知识数据库表
```sql
-- 知识规则表
CREATE TABLE ai_knowledge_rules (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID,
category VARCHAR(100) NOT NULL,
rule_name VARCHAR(200) NOT NULL,
conditions JSONB NOT NULL,
conclusion TEXT NOT NULL,
priority INT NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID NOT NULL,
updated_by UUID NOT NULL,
deleted_at TIMESTAMPTZ,
version INT NOT NULL DEFAULT 1
);
-- 知识参考表
CREATE TABLE ai_knowledge_references (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID,
title VARCHAR(500) NOT NULL,
category VARCHAR(100) NOT NULL,
content TEXT NOT NULL,
source VARCHAR(200),
tags TEXT[],
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID NOT NULL,
updated_by UUID NOT NULL,
deleted_at TIMESTAMPTZ,
version INT NOT NULL DEFAULT 1
);
-- 知识指南表(租户级)
CREATE TABLE ai_knowledge_guides (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID NOT NULL,
title VARCHAR(500) NOT NULL,
department VARCHAR(100),
content TEXT NOT NULL,
applies_to TEXT[],
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID NOT NULL,
updated_by UUID NOT NULL,
deleted_at TIMESTAMPTZ,
version INT NOT NULL DEFAULT 1
);
```
### 4.5 知识上下文注入
通过 Prompt 模板变量注入:
```
基于以下知识库信息:
{{knowledge_context}}
分析以下患者数据:
{{sanitized_data}}
```
`KnowledgeSource.get_context()` 的返回值填入 `knowledge_context`
### 4.6 向量知识源Phase 2 预留)
Phase 1 启用 pgvector 扩展:
```sql
CREATE EXTENSION IF NOT EXISTS vector;
```
预留 `VectorKnowledgeSource` stubPhase 2 实现嵌入管道:
```sql
-- Phase 2 使用
-- CREATE TABLE ai_knowledge_embeddings (
-- id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
-- source_id UUID NOT NULL,
-- source_type VARCHAR(50) NOT NULL,
-- chunk_text TEXT NOT NULL,
-- embedding vector(1536),
-- metadata JSONB
-- );
```
### 4.7 知识库管理 API
```
GET /api/v1/ai/knowledge/rules — 规则列表
POST /api/v1/ai/knowledge/rules — 创建规则
PUT /api/v1/ai/knowledge/rules/:id — 更新规则
DELETE /api/v1/ai/knowledge/rules/:id — 软删除
GET /api/v1/ai/knowledge/references — 参考列表
POST /api/v1/ai/knowledge/references — 新增参考
PUT /api/v1/ai/knowledge/references/:id — 更新
DELETE /api/v1/ai/knowledge/references/:id — 软删除
GET /api/v1/ai/knowledge/guides — 指南列表
POST /api/v1/ai/knowledge/guides — 新增指南
PUT /api/v1/ai/knowledge/guides/:id — 更新
DELETE /api/v1/ai/knowledge/guides/:id — 软删除
```
权限码:`ai.knowledge.list` / `ai.knowledge.manage`
---
## 5. 缓存 & 事件驱动管线
### 5.1 两级缓存
```
请求 → L1 Redis 缓存 (TTL=1h)
│ 命中 → 直接返回
│ 未命中 ↓
→ L2 DB 缓存 (SHA-256 hash 复用,已有)
│ 命中 → 回填 Redis + 返回
│ 未命中 ↓
→ 执行完整分析 → 写入 Redis + DB
```
### 5.2 CacheService
```rust
pub struct CacheService {
redis: RedisConnection,
db: DatabaseConnection,
default_ttl: Duration,
}
// 缓存键格式: ai:cache:{tenant_id}:{analysis_type}:{input_hash}:{prompt_version}
impl CacheService {
pub async fn get(&self, key: &CacheKey) -> AiResult<Option<CachedAnalysis>>;
pub async fn set(&self, key: &CacheKey, value: &CachedAnalysis) -> AiResult<()>;
pub async fn invalidate_tenant(&self, tenant_id: Uuid) -> AiResult<()>;
}
```
缓存失效触发条件:
- Provider 切换 → `invalidate_tenant`
- Prompt 更新 → `invalidate_tenant`(新 prompt_version 自动失效旧缓存)
- 知识库更新 → `invalidate_tenant`(知识变化影响分析结果)
Redis 接入方式:
- Redis 连接池从 `AppState` 共享获取(与 erp-core 其他模块共用同一 Redis 实例)
- `erp-ai``Cargo.toml` 新增 `redis` + `deadpool-redis` 依赖
- Redis 不可用时自动降级为仅 DB 缓存:`CacheService::get()` 先查 RedisRedis 报错则静默降级查 DB不阻塞分析流程
### 5.3 事件驱动触发
扩展 `module.rs on_startup` 订阅:
| 事件 | 触发条件 | 分析类型 | 优先级 |
|------|----------|---------|--------|
| `health_data.critical_alert` | 体征超出参考范围erp-health 已发布) | 趋势分析 | 高 |
| `lab_report.uploaded` | 新化验报告上传erp-health 已发布) | 化验单解读 | 中 |
| `appointment.confirmed` | 预约确认/完成erp-health 已发布) | 报告摘要 | 低 |
| `dialysis.record.created` | 透析记录创建erp-health 已发布) | KDIGO 风险评估 | 高 |
| `ai.reanalysis.requested` | 建议执行后 7/14/30 天erp-ai 已发布) | 再分析 | 中 |
### 5.4 分析队列
```rust
pub struct AnalysisQueue {
db: DatabaseConnection,
max_concurrent: usize, // 默认 3
}
impl AnalysisQueue {
pub async fn enqueue(&self, job: AnalysisJob) -> AiResult<Uuid>;
pub async fn run_worker(&self, registry: &ProviderRegistry, cache: &CacheService);
pub async fn queue_status(&self, tenant_id: Uuid) -> AiResult<QueueStatus>;
}
```
新增数据库表:
```sql
CREATE TABLE ai_analysis_queue (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID NOT NULL,
patient_id UUID NOT NULL,
analysis_type VARCHAR(50) NOT NULL,
priority INT NOT NULL DEFAULT 0,
-- 状态: pending → running → completed/failed/cancelled
-- 重试: failed 且 retry_count < max_retries → pending
status VARCHAR(20) NOT NULL DEFAULT 'pending',
source_event VARCHAR(100),
scheduled_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
result_analysis_id UUID REFERENCES ai_analyses(id),
error_message TEXT,
retry_count INT NOT NULL DEFAULT 0,
max_retries INT NOT NULL DEFAULT 2,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
created_by UUID NOT NULL,
updated_by UUID NOT NULL,
deleted_at TIMESTAMPTZ,
version INT NOT NULL DEFAULT 1
);
```
### 5.5 与现有 auto_analysis 合并
当前 `auto_analysis.rs`(每 24h 扫描高风险患者)并入 `AnalysisQueue`
- 定时扫描变为队列的定时入队逻辑
- 队列负责实际执行调度和并发控制
- 保留"24h 扫描高风险患者"逻辑,但通过队列执行
---
## 6. 配额 & 成本管理
### 6.1 配额模型
```
租户配额 (ai_tenant_configs)
├── 月度 token 预算 (monthly_token_budget)
│ └── 80% → 告警事件
│ └── 100% → 拒绝请求 / 降级到规则引擎
├── 每患者每日分析次数 (default: 10)
└── 分析类型限制 (JSONB)
e.g. {"lab_report": 5, "trends": 20, "report_summary": 3}
```
### 6.2 QuotaService
```rust
pub struct QuotaService {
db: DatabaseConnection,
redis: RedisConnection,
}
pub struct QuotaCheckResult {
pub allowed: bool,
pub reason: Option<QuotaDenyReason>,
pub remaining_budget: Option<u64>,
pub remaining_daily: Option<u32>,
}
pub enum QuotaDenyReason {
MonthlyBudgetExhausted,
DailyLimitReached { limit: u32, current: u32 },
AnalysisTypeLimitReached { analysis_type: String, limit: u32 },
}
```
配额检查位于数据流第 1 步(路由之前):
- `allowed=true` → 继续
- `MonthlyBudgetExhausted` → 降级规则引擎
- 其他拒绝原因 → 返回 429 Too Many Requests
配额消耗统计复用已有 `ai_usage` 表:
- 月度 token 预算消耗通过 `SUM(input_tokens + output_tokens) WHERE tenant_id = ? AND created_at BETWEEN ? AND ?` 聚合查询
- 不新建独立的配额追踪表,避免数据冗余
- Redis 缓存当日/当月用量计数器TTL=24h/31d减少 DB 查询
### 6.3 成本估算
```rust
struct ModelPricing {
input_per_million: u32,
output_per_million: u32,
}
// 默认定价(可配置覆盖)
// Claude Sonnet: $3/M input, $15/M output
// Claude Haiku: $0.25/M input, $1.25/M output
// OpenAI GPT-4o: $2.5/M input, $10/M output
// Ollama (本地): $0
// LocalRules: $0
```
### 6.4 预算告警事件
```rust
pub struct BudgetAlertEvent {
tenant_id: Uuid,
alert_level: BudgetAlertLevel, // Warning(80%) / Critical(95%) / Exhausted(100%)
current_usage_tokens: u64,
budget_tokens: u64,
percentage: f32,
}
```
事件名:`ai.budget.alert`(携带 `alert_level` 字段区分 Warning / Critical / Exhausted
erp-message 模块订阅 → 通知租户管理员
### 6.5 配额管理 API
```
GET /api/v1/ai/tenant-config — 获取配额配置
PUT /api/v1/ai/tenant-config — 更新配额配置
GET /api/v1/ai/usage/overview — 当月用量概览
GET /api/v1/ai/usage/by-type — 按分析类型统计
GET /api/v1/ai/usage/by-day — 按日统计
GET /api/v1/ai/usage/by-provider — 按 Provider 统计
GET /api/v1/ai/cost/estimate/:type — 单次分析成本估算
```
---
## 7. 分阶段实施
### 7.1 Phase 1Week 1-3路由引擎 + 配额基础
| 任务 | 涉及文件 | 产出 |
|------|---------|------|
| `AiConfig` + `ProviderConfig` | `config.rs` (新) | TOML 配置解析 |
| `ProviderRegistry` + 健康检查 | `provider/registry.rs` (新) | Provider 注册/解析/降级 |
| `OpenAIProvider` | `provider/openai.rs` (新) | OpenAI 兼容 API |
| `OllamaProvider` | `provider/ollama.rs` (新) | 本地模型 |
| `ai_tenant_configs` 表 | migration + entity (新) | 租户配置 |
| `QuotaService` 基础版 | `service/quota.rs` (新) | 配额检查 + 用量记录 |
| Provider Admin API | `handler/provider_admin_handler.rs` (新) | 管理接口 |
| 集成到 AnalysisService | `service/analysis.rs` (改) | 路由决策替换硬编码 |
验收:租户可配置 Provider降级链工作超配额请求被拒绝或降级。
### 7.2 Phase 2Week 4-5缓存 + 事件驱动
| 任务 | 涉及文件 | 产出 |
|------|---------|------|
| `CacheService` Redis 缓存 | `service/cache.rs` (新) | 两级缓存 |
| `AnalysisQueue` | `service/analysis_queue.rs` (新) | 异步分析调度 |
| `ai_analysis_queue` 表 | migration + entity (新) | 队列持久化 |
| 事件订阅扩展 | `module.rs` (改) | 4 个新事件触发 |
| 合并 auto_analysis | `service/auto_analysis.rs` (改) | 入队逻辑 |
验收:缓存命中率 > 30%(重复分析),体征异常自动入队分析。
### 7.3 Phase 3Week 6-8知识库 + 成本管理 + Admin UI
| 任务 | 涉及文件 | 产出 |
|------|---------|------|
| `KnowledgeSource` trait | `knowledge/mod.rs` (新) | 统一抽象 |
| `StructuredKnowledgeSource` | `knowledge/structured.rs` (新) | 结构化查询 |
| 知识库表 (3 张) + pgvector | migration + entity (新) | 知识 CRUD + 向量预留 |
| 成本估算 + 预算告警 | `service/quota.rs` (扩展) | 成本追踪 |
| 用量统计 API | `handler/quota_handler.rs` (新) | 统计接口 |
| Web Admin UI | 前端 (新) | Provider/配额/知识库管理页 |
验收:结构化知识注入 Prompt 工作,单次分析成本可查,管理页面可用。
---
## 8. 新增权限码
| 权限码 | 说明 | 角色 | 状态 |
|--------|------|------|------|
| `ai.provider.manage` | Provider 级管理 | 超级管理员 | 已有module.rs 已声明) |
| `ai.knowledge.list` | 知识库查看 | 医护/管理员 | 新增 |
| `ai.knowledge.manage` | 知识库管理 | 管理员 | 新增 |
| `ai.quota.manage` | 配额管理 | 管理员 | 新增 |
已有权限码(无需新增):`ai.analysis.list/manage``ai.prompt.list/manage``ai.usage.list``ai.suggestion.list/manage``ai.provider.manage`
---
## 9. 新增事件类型
| 事件名 | 发布方 | 消费方 | 说明 |
|--------|--------|--------|------|
| `ai.budget.alert` | QuotaService | erp-message | 预算告警level 字段区分 Warning/Critical/Exhausted |
| `ai.analysis.queued` | AnalysisQueue | 日志 | 分析任务入队 |
---
## 10. 新增数据库表汇总
| 表名 | 用途 | Phase |
|------|------|-------|
| `ai_tenant_configs` | 租户 AI 配置 | Phase 1 |
| `ai_analysis_queue` | 分析任务队列 | Phase 2 |
| `ai_knowledge_rules` | 知识规则 | Phase 3 |
| `ai_knowledge_references` | 知识参考 | Phase 3 |
| `ai_knowledge_guides` | 知识指南 | Phase 3 |
| `ai_knowledge_embeddings` | 向量嵌入(预留) | Phase 4 |
所有表遵循项目规范:`id`UUIDv7`tenant_id``created_at``updated_at``version``deleted_at`(软删除)。
---
## 11. 风险与缓解
| 风险 | 概率 | 影响 | 缓解 |
|------|------|------|------|
| Ollama 本地模型质量不足 | 中 | 分析质量下降 | 保留 Claude 降级路径,本地模型仅用于基础分析 |
| Redis 不可用导致缓存失效 | 低 | 性能回退到 DB 查询 | CacheService 降级到仅 DB 缓存 |
| 知识库数据录入工作量 | 高 | Phase 3 延期 | 提供批量导入 API + 预置核心规则种子数据 |
| 多 Provider token 计量不一致 | 中 | 成本追踪偏差 | 统一从 Provider 响应的 usage 字段提取,不估算 |
| pgvector 扩展运维复杂度 | 低 | 数据库升级需求 | Docker 镜像预包含 pgvector无需额外编译 |

View File

@@ -0,0 +1,75 @@
# HMS 夯实基础设计规格
> 日期: 2026-05-05 | 版本: 1.0 | 状态: Approved
## 1. 概述
### 1.1 问题
HMS 已完成主体功能开发18 crate / 328 路由 / 46 health 实体 / 772 后端测试),但存在三个结构性问题:
1. **安全漏洞未清零** — 多专家组审查发现 5 CRITICAL + 10 HIGH + 8 MEDIUM + 5 LOW
2. **功能膨胀** — 20 个功能域中 7 个在当前定位下不是首发重点
3. **UX 不统一** — 各端视觉风格、交互模式、错误处理不一致;小程序 60 页面功能过度
### 1.2 目标
暂停新功能开发,用 6-8 周夯实安全基础、统一设计系统、打磨核心流程、精简小程序,产出可上生产的基础版本。
### 1.3 范围
**保留并完善12 个功能域):** 患者管理、健康数据/体征、告警系统、行动收件箱、AI 智能分析、随访管理、咨询管理、内容管理、积分商城、线下活动、统计仪表盘、设备与数据采集
**冻结推迟7 个模块):** 护理计划、班次管理、家庭代理、药物记录、透析管理、医生排班、预约管理
## 2. Phase 1安全清零2-3 周)
### 2.1 CRITICAL5 个)
| ID | 问题 | 修复方案 |
|----|------|----------|
| C1 | FHIR `allowed_patient_ids=None` 无限制访问 | None 视为空列表(拒绝所有) |
| C2 | AI 队列 `claim_next` 绕过 RLS 租户隔离 | 添加 tenant_idSET `app.current_tenant_id` |
| C3 | FHIR `$everything` 子查询缺 tenant_id | 每个子查询加 TenantId 过滤 |
| C4 | `.env.bak` 泄露 AES 密钥 | 删除文件、轮换密钥 |
| C5 | Docker 硬编码默认密码 | 改用 .env 注入 |
### 2.2 HIGH10 个)
| ID | 问题 | 修复方案 |
|----|------|----------|
| H1 | FHIR converter 输出加密密文 | 解密后再输出或脱敏 |
| H2 | 审计日志泄漏加密密文 | 加密字段记录 REDACTED |
| H3 | Refresh Token 验证缺 tenant_id | 添加 tenant_id 过滤 |
| H4 | Token revoke 无租户校验 | 增加 tenant_id 参数 |
| H5 | readiness_check 泄露内部信息 | 替换为 generic message |
| H6 | OAuth handler expect() panic | 改为返回 Internal Error |
| H7 | AI 提示词模板 Prompt Injection | 安全检查 + 限制权限 |
| H8 | Web JWT 存 localStorage | 评估迁移到 httpOnly cookie |
| H9 | 小程序加密密钥嵌入客户端 | 配合服务端 session 失效机制 |
| H10 | Debug 构建绕过 KEK 要求 | CI 检查 + 编译守卫 |
## 3. Phase 2冻结推迟模块2-3 天)
冻结 7 个模块(护理计划/班次/家庭代理/药物/透析/排班/预约),策略:
- 不删代码,保留后端路由和数据库迁移
- 菜单迁移新增 disabled 标记
- 前端路由守卫检查 enabled 标记
- 小程序同步隐藏入口
## 4. Phase 3设计系统统一1 周)
统一范围色板、StatusTag、错误提示、日期格式、表格布局模式。
适老化标准:正文 ≥ 16px、按钮 ≥ 48px、对比度 ≥ 4.5:1。
## 5. Phase 4核心流程打磨1-2 周)
12 个保留功能域的前后端闭环验证和 UX 打磨。
## 6. Phase 5小程序精简与分层2 周)
60 页 → ~20 页,砍掉 BLE 同步/知情同意/医生端重表单,适老化改造,多角色分层。
## 7. 验证标准
每个 Phasecargo check + test 通过、浏览器操作无 500、小程序可导航、冻结模块已隐藏、pnpm build 通过。

View File

@@ -0,0 +1,301 @@
# HMS 质量验证策略 — 分层端到端验证
> 日期: 2026-05-05 | 状态: Draft
## 1. 背景
HMS 健康管理平台已完成 Phase 0-1 的功能开发Phase 2 Web UI 补全正在进行中。系统当前拥有:
- 18 个 Rust crate87k 行后端代码328 个 API 路由
- 46 个健康业务实体80+ 个 health 模块端点
- Web 管理后台 36+ 条健康路由,微信小程序 40+ 页面
- 772 个后端单元/集成测试97.5% 通过率)
**核心矛盾:** 功能完整度很高(前后端几乎全部贯通),但从未从业务角色视角进行过端到端的系统性验证。无法确认所有业务链路闭环、各角色的功能是否可用、是否存在过度开发。
**触发因素:** 已签约的血透中心客户要求 1-2 周内看到可试用的版本。
## 2. 策略概述
采用**分层验证**策略Layer C
- **第一周Day 0-5** 角色场景冒烟测试 — 定义 6 条端到端业务链路,手动走通并记录问题
- **第二周Day 6-10** 修复 + 固化 — 修复 P0 问题,将通过的链路固化为 Playwright 自动化测试
## 3. 前置准备Day 0
开始冒烟测试前,必须确认以下基础环境:
| 检查项 | 验证方式 | 通过标准 |
|--------|---------|---------|
| 后端服务启动 | `cd crates/erp-server && cargo run` | 无报错,监听 3000 端口 |
| 前端开发服务 | `cd apps/web && pnpm dev` | 无报错,访问 localhost:5174 正常 |
| 数据库迁移 | 查看启动日志 | 所有迁移成功执行 |
| 初始种子数据 | 检查数据库 | 有默认管理员账号 + 基础科室/角色数据 |
| 小程序开发工具 | 微信开发者工具加载项目 | 编译无错误,模拟器正常运行 |
| API 文档 | 访问 `/api/docs/openapi.json` | OpenAPI spec 正常返回 |
**种子数据最低要求:**
- 1 个管理员账号admin/Admin@2026
- 2 个科室(血透室、体检科)
- 4 个用户2 医生 + 2 护士),已分配对应角色
- 5 个患者档案(其中 2 个需绑定微信测试号,供 S4 使用)
- 下周的排班数据
- 基础告警规则(血压/心率阈值)
**已知审计问题确认Day 0 必须检查):**
| 审计 ID | 问题描述 | 状态 | 冒烟测试影响 |
|---------|---------|------|-------------|
| CRITICAL-1 | 小程序晚间血压丢失(`blood_pressure_evening` 类型缺失) | wiki 显示已修复 | S4 步骤 3 涉及体征录入,需确认晚间血压可正常保存 |
| CRITICAL-2 | 告警权限码拼写错误(`health.alert.manage` vs `health.alerts.manage` | 待确认 | S2 步骤 6-7 涉及告警查看/处理,如权限码错误将导致 403 |
| HIGH-1 | 透析管理小程序端缺失 | 未修复 | S4 仅验证 Web 端透析功能,小程序端降级为"查看透析记录" |
| HIGH-2 | 知情同意小程序端缺失 | 未修复 | S4 跳过知情同意功能验证 |
| HIGH-3 | 前端日志严重不足 | 未修复 | 不阻塞冒烟测试,但影响问题定位效率 |
**降级策略:** HIGH-1/HIGH-2 未修复的小程序功能,在 S4 中标记为 SKIP不阻塞场景判定。
## 4. 冒烟测试场景
### 4.0 场景数据依赖
```
S1 系统初始化(数据基础)
├── S2 透析日流程(依赖 S1 创建的科室、护士、排班)
├── S3 患者管理(依赖 S1 创建的医生、患者数据)
├── S4 小程序核心(依赖 S1 创建的患者 + 微信测试号绑定)
├── S5 运营配置(依赖 S1 创建的告警规则基础数据)
└── S6 关怀闭环(依赖 S1 创建的患者/医生数据)
```
**硬前置:** S1 必须先通过,否则 S2-S6 无法执行。如果 S1 FAIL优先修复 S1 再继续。
### 4.1 场景定义
#### S1: 系统初始化(管理员)
| # | 步骤 | 操作路径 | 端 | 期望结果 |
|---|------|---------|----|---------|
| 1 | 管理员登录 | `/` 登录页 | Web | 进入工作台首页,仪表盘渲染正常 |
| 2 | 创建科室 | 侧边栏 > 组织管理 > `/organizations` > 新增科室 | Web | 科室列表显示血透室、体检科 |
| 3 | 添加医生 ×2 + 护士 ×2 | 侧边栏 > 用户管理 > `/users` > 新增用户 | Web | 用户列表显示新创建的用户 |
| 4 | 分配角色和权限 | 侧边栏 > 角色管理 > `/roles` > 编辑角色 > 绑定用户 | Web | 角色绑定生效,权限控制正确 |
| 5 | 创建下周排班 | 侧边栏 > 健康管理 > 排班管理 > `/health/schedules` > 新增排班 | Web | 排班日历视图显示排班数据 |
| 6 | 查看统计看板 | 侧边栏 > 健康管理 > 统计报表 > `/health/statistics` | Web | 图表渲染正常,数据不为空 |
**验证重点:** 系统初始化后所有基础数据就绪,后续场景可使用这些数据。
---
#### S2: 透析日流程(护士)
| # | 步骤 | 操作路径 | 端 | 期望结果 |
|---|------|---------|----|---------|
| 1 | 护士登录 | `/` 登录页 | Web | 进入工作台,看到今日待办 |
| 2 | 查看今日排班 | 侧边栏 > 健康管理 > 排班管理 > `/health/schedules` | Web | 显示今日透析排班列表 |
| 3 | 患者签到 | 侧边栏 > 健康管理 > 预约管理 > `/health/appointments` > 签到按钮 | Web | 患者状态变为"已签到" |
| 4 | 采集体征 | 患者详情页 > `/health/patients/:id` > 体征 Tab > 录入按钮 | Web | 体征数据保存成功(血压/心率/体温) |
| 5 | 记录透析会话 | 侧边栏 > 健康管理 > 透析管理 > `/health/dialysis` > 新建透析记录 > 状态按钮切换(待开始 → 进行中 → 已完成) | Web | 透析记录完整,状态流转正确 |
| 6 | 触发异常 | 患者详情页体征录入 > 输入超标血压值(如 200/120 | Web | 告警自动生成,告警列表可见 |
| 7 | 确认告警 | 侧边栏 > 健康管理 > 告警列表 > `/health/alerts` > 处理按钮 | Web | 告警状态变为"已处理" |
| 8 | 填写交接班记录 | 侧边栏 > 健康管理 > 班次管理 > `/health/shifts/:id` > 交接记录 Tab > 新增 | Web | 交接班记录保存成功 |
**验证重点:** 血透中心最核心的日常工作流,一条龙走通。
---
#### S3: 患者管理与决策(医生)
| # | 步骤 | 操作路径 | 端 | 期望结果 |
|---|------|---------|----|---------|
| 1 | 医生登录 | `/` 登录页 | Web | 进入医生工作台/仪表盘 |
| 2 | 查看患者列表 | 侧边栏 > 健康管理 > 患者管理 > `/health/patients` | Web | 列表显示患者,可搜索/筛选 |
| 3 | 查看患者详情 | 患者列表 > 点击某患者 > `/health/patients/:id` | Web | 详情页显示基本信息、体征、诊断、用药 |
| 4 | 查看体征趋势图 | 患者详情页 > 趋势 Tab | Web | 趋势图表渲染正确,数据连续 |
| 5 | 审阅透析处方 | 小程序医生端 > 透析 > 处方列表 > `pages/doctor/prescription/detail` | MP | 处方详情显示正常 |
| 6 | 创建随访计划 | 侧边栏 > 健康管理 > 随访任务 > `/health/follow-up-tasks` > 新建 | Web | 随访任务生成成功 |
| 7 | 查看 AI 分析报告 | 侧边栏 > 健康管理 > AI 分析 > `/health/ai-analysis` | Web | AI 分析结果正常展示 |
| 8 | 处理行动收件箱 | 侧边栏 > 健康管理 > 行动收件箱 > `/health/action-inbox` > 完成按钮 | Web | 任务可标记完成 |
**验证重点:** 医生日常查看数据 + 做决策的完整流程。
---
#### S4: 小程序核心体验(患者)
| # | 步骤 | 操作路径 | 端 | 期望结果 |
|---|------|---------|----|---------|
| 1 | 微信登录 → 手机绑定 | 首页 `pages/index/index` > 登录按钮 > `pages/login/index` | MP | 登录成功,进入健康首页(`ERP__WECHAT__DEV_MODE=true` 跳过 jscode2session |
| 2 | 查看健康首页 | 底部 Tab > 健康 > `pages/health/index` | MP | 显示今日体征/待办/通知 |
| 3 | 体征数据录入 | 健康首页 > 体征录入 > `pages/pkg-health/input/index` | MP | 数据提交成功(含晚间血压验证) |
| 4 | 查看体征趋势 | 健康首页 > 趋势查看 > `pages/pkg-health/trend/index` | MP | 趋势图表渲染正常 |
| 5 | 查看预约列表 | 底部 Tab > 预约 > `pages/appointment/index` | MP | 显示预约记录 |
| 6 | 查看告警通知 | 健康首页 > 告警 > `pages/pkg-health/alerts/index` | MP | 告警列表正常显示 |
| 7 | 查看用药记录 | 个人中心 > 用药记录 > `pages/pkg-profile/medication/index` | MP | 用药列表显示正常 |
| 8 | 查看诊断记录 | 个人中心 > 诊断记录 > `pages/pkg-profile/diagnoses/index` | MP | 诊断记录显示正常 |
> **注意:** 透析管理HIGH-1和知情同意HIGH-2的小程序端尚未实现本场景跳过这两项。
**验证重点:** 患者端小程序的基础可用性。
---
#### S5: 运营配置(管理员)
| # | 步骤 | 操作路径 | 端 | 期望结果 |
|---|------|---------|----|---------|
| 1 | 配置告警规则 | 侧边栏 > 健康管理 > 告警规则 > `/health/alert-rules` > 新建规则 | Web | 规则保存成功,列表显示 |
| 2 | 配置危急值阈值 | 侧边栏 > 健康管理 > 危急值阈值 > `/health/critical-value-thresholds` > 新建 | Web | 阈值保存成功 |
| 3 | 注册 BLE 网关 | 侧边栏 > 健康管理 > BLE 网关 > `/health/ble-gateways` > 新建网关 | Web | 网关显示为在线/离线状态 |
| 4 | 创建知情同意模板 | 侧边栏 > 健康管理 > 知情同意 > `/health/consents` > 新建 | Web | 模板保存成功 |
| 5 | 创建随访模板 | 侧边栏 > 健康管理 > 随访模板 > `/health/follow-up-templates` > 新建 | Web | 模板保存成功 |
| 6 | 检查侧边栏菜单完整性 | 以管理员登录,逐一点击侧边栏所有健康管理子菜单 | Web | 所有健康模块功能在菜单中可见(参见第 7 节缺口清单) |
**验证重点:** 管理后台的配置能力是否完整,菜单可见性是否正确。
---
#### S6: 关怀闭环(医生)
| # | 步骤 | 操作路径 | 端 | 期望结果 |
|---|------|---------|----|---------|
| 1 | 创建护理计划 | 侧边栏 > 健康管理 > 护理计划 > `/health/care-plans` > 新建 > 添加条目 | Web | 计划保存成功,条目可见 |
| 2 | 查看行动收件箱 | 侧边栏 > 健康管理 > 行动收件箱 > `/health/action-inbox` | Web | 显示待处理行动(与 S3 步骤 8 共享页面,重点关注护理计划相关行动) |
| 3 | 回复咨询消息 | 侧边栏 > 健康管理 > 咨询管理 > `/health/consultations/:id` > 发送消息 | Web | 消息发送成功 |
| 4 | 审批 AI 建议 | 行动收件箱 > AI 建议 Tab > 审批按钮 | Web | 建议状态变更 |
| 5 | 记录结果测量 | 护理计划详情 > `/health/care-plans/:id` > 结果测量 Tab > 新增 | Web | 测量数据保存成功 |
| 6 | 查看内容管理文章 | 侧边栏 > 健康管理 > 文章管理 > `/health/articles` | Web | 文章列表和详情正常显示 |
> **S3 与 S6 的边界:** S3 侧重"数据查看与决策"查看趋势、开处方、AI 报告S6 侧重"计划执行与闭环"(护理计划、咨询回复、结果测量)。行动收件箱在两个场景中都会用到但关注点不同。
**验证重点:** 护理计划 → 执行 → 测量结果的关怀闭环。
### 4.2 场景优先级
| 优先级 | 场景 | 原因 |
|--------|------|------|
| P0 | S1 系统初始化 | 所有后续场景的数据基础 |
| P0 | S2 透析日流程 | 血透中心最核心的业务流程 |
| P0 | S3 患者管理 | 医生日常工作的核心路径 |
| P0 | S4 小程序核心 | 患者端唯一入口,必须可用 |
| P1 | S5 运营配置 | 管理能力,首次演示可以后补 |
| P1 | S6 关怀闭环 | 旗舰功能但复杂度高,可降级 |
## 5. 判定标准
### 5.1 步骤级判定
| 状态 | 含义 | 处理方式 |
|------|------|---------|
| PASS | 步骤完全通过 | 记录,无需修复 |
| PARTIAL | 步骤可用但有瑕疵 | 记录问题,不阻塞后续 |
| FAIL | 步骤无法完成 | 记录并立即标记为 BUG |
### 5.2 场景级判定
| 状态 | 含义 |
|------|------|
| PASS | 全部步骤 PASS |
| PASS_WITH_ISSUES | 全部关键步骤 PASS有 PARTIAL 项 |
| FAIL | 任一关键步骤 FAIL |
### 5.3 BUG 优先级
| 级别 | 条件 | 修复时限 |
|------|------|---------|
| BLOCKER | P0 场景的关键步骤 FAIL | 当天修复 |
| CRITICAL | P0 场景非关键步骤 FAIL或数据不一致 | 48h 内修复 |
| HIGH | P1 场景 FAIL | 第二周修复 |
| MEDIUM | PARTIAL 问题UI 错位、文案错误等) | 记录,按优先级排期 |
| LOW | 建议性改进 | 积压 |
## 6. 执行计划
### 6.1 第一周:冒烟测试
| 天 | 日期 | 任务 | 交付物 |
|----|------|------|--------|
| Day 0 | W1-Mon | 前置环境检查 + 种子数据准备 | 环境就绪确认 |
| Day 1 | W1-Tue | S1 系统初始化 + 菜单可见性排查 | S1 验证报告 + 菜单缺口清单 |
| Day 2 | W1-Wed | S2 透析日流程 | S2 验证报告 |
| Day 3 | W1-Thu | S3 患者管理与决策 | S3 验证报告 |
| Day 4 | W1-Fri | S4 小程序核心体验 | S4 验证报告 |
| Day 5 | W1-Sat/Sun | S5 运营配置 + S6 关怀闭环 + 汇总 | 全场景验证报告 + BUG 清单 |
### 6.2 第二周:修复 + 固化
| 天 | 任务 | 交付物 |
|----|------|--------|
| Day 6-7 | BLOCKER + CRITICAL BUG 修复 | 修复提交 |
| Day 8 | P0 场景回归验证(重跑修复步骤 + 前后各一个相邻步骤) | 回归报告 |
| Day 9-10 | S2 透析日流程 Playwright 自动化 + P1 场景验证 + 质量报告 | 测试脚本 + 完整质量报告 |
## 7. 菜单可见性排查
根据代码分析,以下功能的路由已注册但可能不在侧边栏菜单中显示(需要通过数据库迁移或手动配置添加菜单项):
- 透析管理
- 护理计划
- 班次管理
- 用药记录
- BLE 网关
- 危急值阈值
- 诊断记录
- 家庭健康代理
- 知情同意
- 随访模板
- 行动收件箱
- 内容管理(文章/分类/标签)
- 实时监控
- OAuth 合作方
**排查方式:** 以管理员登录后查看侧边栏,逐一确认以上功能是否有菜单入口。缺失的需创建菜单迁移文件。
## 8. 第二周 Playwright 自动化范围
优先固化最核心的 S2 透析日流程为自动化测试。每个场景预计 4-8 小时(含调试),因此两周内只覆盖 S2
1. **S2 透析日流程Day 9-10** — 登录 → 排班查看 → 体征录入 → 透析记录 → 告警处理
S1 和 S3 的自动化留到后续迭代。现有 Playwright 基础设施(`apps/web/e2e/`)已有 page object 和 fixture 模式可复用。
**自动化质量标准:**
- 每个关键步骤至少一个断言
- flaky 测试最大重试 2 次
- 测试数据通过 API setup 生成,不依赖手动准备
小程序端S4暂不纳入自动化微信开发者工具的自动化测试生态不成熟持续手动验证。
## 9. 交付物清单
| 交付物 | 产出时间 | 说明 |
|--------|---------|------|
| 环境就绪确认 | Day 0 | 所有前置检查通过 |
| 可重复执行的种子数据脚本 | Day 0 | SQL 或 seed 脚本,可一键初始化测试数据 |
| 6 份场景验证报告 | Day 1-5 | 每条链路的步骤级结果 |
| 菜单缺口清单 | Day 1 | 需要补充的侧边栏菜单项 |
| BUG 清单 | Day 5 | 按优先级排列的完整问题列表 |
| 修复提交记录 | Day 6-8 | 所有 BLOCKER/CRITICAL 的修复 |
| Playwright 测试脚本S2 | Day 9-10 | 透析日流程自动化测试 |
| 质量报告 | Day 10 | 两周验证总结 + 发布建议 |
### 质量报告模板
质量报告应包含以下内容:
1. **场景判定汇总** — 6 个场景的最终判定PASS / PASS_WITH_ISSUES / FAIL
2. **BUG 清单及修复状态** — 所有发现的问题、当前状态(已修复/待修复/降级)
3. **发布风险评估** — GO / CONDITIONAL GO / NO-GO 判定及理由
4. **遗留问题清单** — 未修复问题的清单、影响范围和后续计划
5. **下一步建议** — 第二阶段验证或正式发布的前置条件
## 10. 风险与应对
| 风险 | 概率 | 影响 | 应对 |
|------|------|------|------|
| 种子数据不完整S1 无法执行 | 中 | 高 | Day 0 优先准备可重复执行的种子数据脚本 |
| BLOCKER 数量过多,修复超过 2 天 | 中 | 高 | 降级 P1 场景,集中修复 P0 |
| 小程序登录流程不通 | 中 | 高 | 提前准备测试号和 mock 环境(`ERP__WECHAT__DEV_MODE=true` |
| 微信开发者工具版本兼容性导致登录失败 | 中 | 中 | 使用稳定版开发者工具,避免最新 beta 版 |
| 菜单缺失导致功能"找不到" | 高 | 低 | Day 1 集中排查并补充菜单迁移 |
| 两周时间不够完成所有修复 | 中 | 中 | 只交付 P0 通过的版本给客户 |

View File

@@ -0,0 +1,926 @@
# HMS Copilot 基因化设计规格
> 日期: 2026-05-11 | 状态: Draft | 分支: feat/media-library-banner
> 讨论来源: 2026-05-11 发散式互动探讨
---
## §1 愿景与定位
### 1.1 问题陈述
当前 erp-ai 模块是一个独立的 AI 分析工具,覆盖 3 个场景(化验单解读、趋势分析、报告摘要),用户需要主动点击"AI 分析"按钮才能触发。AI 与系统的关系是"附加工具"——不用它,系统照常运转。
这带来三个问题:
1. **医护端:被动发现** — 医护需要主动查数据、看报告才能发现异常。高风险患者的风险信号淹没在数据海洋中,依赖医护的经验和注意力。
2. **患者端:沟通空白** — 血透机构无互联网医院资质,医生不能在线与患者对话产生诊断行为。患者离院后的疑问、不适、焦虑没有合规渠道可以解答。
3. **系统层面AI 价值未被释放** — AI 只在用户主动触发时才工作99% 的运行时间处于闲置状态,但它本可以持续观察数据、发现模式、生成洞察。
### 1.2 Copilot 定义
Copilot 将 AI 从"工具"转变为"基因"——一个始终在场、主动观察、适时建议的智能层。
它不是系统的一个器官,而是弥漫在每个交互点的基础能力。就像免疫系统不是一个独立的器官,而是无处不在的防御能力。
**Copilot 不是什么:**
- 不是自动决策系统——它不替医护做决定
- 不是诊断工具——它不做医疗诊断
- 不是聊天机器人——它有深度上下文感知能力
- 不是 erp-ai 的替代品——它是 erp-ai 的进化形态
**Copilot 是什么:**
- 一个"永远醒着的观察者"——持续监控数据变化
- 一个"适时开口的顾问"——在关键时刻主动提供建议
- 一个"合规的沟通桥梁"——在法律允许范围内连接医护和患者
- 一个"越用越了解你的助手"——基于患者数据提供个性化洞察
### 1.3 核心价值主张
**医护端:从"查数据发现问题"到"被推送需要关注的风险"**
| 现在 | Copilot 之后 |
|------|-------------|
| 医护每天花 30 分钟逐个查看患者数据 | Copilot 自动筛选高风险患者,推送到仪表盘 |
| 异常数据依赖医护经验发现 | 规则引擎 + LLM 自动检测异常并分级告警 |
| 随访计划从空白模板开始写 | Copilot 基于风险画像推荐个性化随访方案 |
| 咨询时需要手动翻看患者历史 | Copilot 侧边栏实时展示背景和追问建议 |
**患者端:合规替代医患在线沟通**
血透机构的核心痛点:想服务好患者、想提高随访率、想增加到院量,但没有互联网医院资质,医生不能在线"看病"。
Copilot 以"AI 健康管家"身份填补这一空白:
- 解答患者疑问(在合规边界内)
- 提供健康科普和个性化数据解读
- 引导需要关注的症状到院就医
- 驱动患者日常互动(每日问候、健康打卡、积分激励)
### 1.4 业务驱动力
**合规痛点是 Copilot 患者端存在的根本理由:**
血透机构没有互联网医院资质,意味着:
- 医生不能在线与患者进行问诊对话
- 不能在线给出诊断或治疗建议
- 不能在线开具处方
但患者离院后确有大量沟通需求:用药疑问、不适咨询、复查提醒、心理支持。这些需求目前没有合规渠道可以满足。
Copilot 作为 AI 客服/管家:
- 不做诊断、不开处方、不给治疗建议
- 在合规边界内解答患者疑问
- 智能识别需要就医的情况并引导到院
- 所有输出经过合规审查引擎自动检查
这不仅是功能创新,是解决了一个真实的合规痛点。
### 1.5 设计原则
1. **Copilot 不替人做决定** — 只建议,医护审批。患者端不诊断,只引导。
2. **规则保底LLM 拓展** — 规则引擎保证确定性和可解释性LLM 提供超越规则的洞察。规则是下限LLM 是上限。
3. **合规第一** — 患者端所有 AI 输出必须经过合规审查引擎。宁可过度保守,不可越界。
4. **渐进式渗透** — 从一个触点(风险画像)开始,逐步扩展到全系统。不追求一步到位。
---
## §2 架构总览
### 2.1 分层架构
```
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 前端呈现层 │
│ ├─ 医护端:<CopilotCard /> <CopilotBadge /> <CopilotAlert /> │
│ └─ 患者端:对话式 UI嵌入小程序消息体系
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Copilot API 层 │
│ ├─ GET /copilot/insights — 获取预计算洞察 │
│ ├─ POST /copilot/chat — 患者端对话(经合规审查) │
│ └─ GET /copilot/patients/{id}/risk — 获取风险评分 │
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Copilot 引擎层 │
│ ├─ 混合评分引擎(规则引擎 + LLM 补充) │
│ ├─ 意图识别引擎(患者端对话分类) │
│ ├─ 合规审查引擎(关键词 + 语义双层) │
│ └─ 洞察调度器(决定何时/如何生成洞察) │
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 数据层 │
│ ├─ copilot_insights — 预计算洞察存储(带过期时间) │
│ ├─ copilot_risk_snapshots — 患者风险评分快照 │
│ ├─ copilot_chat_logs — 患者端对话审查日志(合规审计) │
│ └─ copilot_rules — 规则引擎配置(可动态调整) │
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 触发层 │
│ ├─ 异步:事件总线订阅 → 后台预计算洞察 │
│ └─ 同步API 请求时 → 合并预计算结果 + 实时补充 │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 2.2 混合触发模型
洞察通过两种机制产生,互相补充:
**异步预计算(后台):**
事件总线上的健康数据变更事件触发 Copilot 后台引擎,异步生成洞察并写入存储。医护在打开页面前,相关洞察已经准备就绪。
| 触发源 | 时机 | 延迟 | 示例 |
|--------|------|------|------|
| 事件总线订阅 | 数据写入后秒级 | < 5s | 血压录入 → 后台更新风险评分 |
| 定时任务 | 每日凌晨 | 批量 | 重新计算所有在管患者风险评分 |
| 化验报告确认 | 报告确认后 | < 10s | 肌酐异常 → 生成告警洞察 |
**同步实时补充(前端请求时):**
医护或患者打开页面时,前端调用 Copilot API。API 先返回预计算的洞察,再根据当前上下文(如正在查看的页面、当前对话内容)实时补充额外建议。
| 触发源 | 时机 | 延迟 | 示例 |
|--------|------|------|------|
| 页面加载 | 用户请求时 | < 500ms | 打开患者档案 → 返回风险画像 |
| 对话消息 | 患者发消息时 | < 2s | 患者提问 → 意图识别 + 合规审查 + 回复 |
| 随访创建 | 医护操作时 | < 1s | 创建随访 → 返回推荐方案 |
**合并策略:** API 响应中同时包含 `precomputed`(预计算结果,即时返回)和 `realtime`(实时补充,可能稍慢)。前端先渲染预计算部分,实时部分流式追加。
### 2.3 与现有 erp-ai 的关系
Copilot 不替换 erp-ai而是在其上构建。现有 erp-ai 的 AI Provider 抽象OpenAI / Claude / Qwen / Ollama、Prompt 模板管理、SSE 流式输出能力完全保留。
```
erp-ai保持不变
├─ AI Provider 抽象4 个 Provider
├─ Prompt 模板管理Handlebars
└─ SSE 流式输出
Copilot 引擎层(新增,调用 erp-ai
├─ 规则引擎 — 纯 Rust 实现,不依赖 AI Provider
├─ LLM 补充分析 — 调用 erp-ai 的 Provider 能力
├─ 合规审查 — 调用 erp-ai 的 Provider 能力
└─ 意图识别 — 调用 erp-ai 的 Provider 能力
```
**分层依赖关系:**
- 规则引擎:完全离线可用,不依赖任何 AI Provider
- LLM 补充 / 合规审查 / 意图识别:依赖 erp-ai 的 Provider 抽象
- AI Provider 不可用时Copilot 降级为纯规则模式
### 2.4 降级策略
当 AI 服务不可用时Provider 宕机、配额用尽、网络故障Copilot 分层降级:
| 降级级别 | 触发条件 | 影响范围 | 表现 |
|----------|---------|---------|------|
| 正常 | 所有 Provider 可用 | 全部功能 | 规则 + LLM 混合模式 |
| 一级降级 | 主 Provider 不可用,备用可用 | LLM 延迟增加 | 自动切换到备用 Provider |
| 二级降级 | 所有 LLM Provider 不可用 | 医护端部分降级 | 规则评分正常,无 LLM 补充;患者端仅回答服务类问题,健康类问题使用安全兜底模板 |
| 三级降级 | Copilot 引擎整体不可用 | 全部 Copilot 功能 | 静默降级,系统回到无 Copilot 状态,不阻塞任何业务流程 |
**关键设计约束Copilot 的任何故障都不能阻塞 HMS 核心业务流程。** 患者管理、预约、随访、咨询等功能在 Copilot 完全不可用时必须正常运转。
---
## §3 医护端 Copilot
医护端 Copilot 不改变现有工作流,而是在每个关键节点"旁边"插入智能建议。医护照常操作Copilot 在适当的时候主动开口。
4 个触点形成闭环:风险画像(基础)→ 异常检测(感知)→ 随访推荐(决策)→ 咨询辅助(执行)→ 回到异常检测。
### 3.1 触点①:患者风险画像(基础层)
**触发时机:** 医护打开任意患者相关页面时(同步 API 调用 + 预计算数据)
**呈现方式:** 患者姓名旁的风险等级徽章 + 可展开的 Copilot 洞察卡片
**评分机制(混合):**
Layer 1 — 规则引擎(确定性):
- 医疗专家定义规则,存储在 `copilot_rules` 表中
- 每条规则包含:条件表达式、风险分值(+1~+5、严重度、建议文案
- 规则以 JSON 表达式存储,支持动态加载和机构自定义
- 基础风险分 = 所有匹配规则的分值之和
Layer 2 — LLM 补充分析(拓展性):
- 输入:患者近期数据 + 规则评分结果
- 输出:自然语言的补充风险描述、建议、相似病例参考
- 非阻塞LLM 失败时仅展示规则结果
内置规则覆盖 5 大类:
| 类别 | 示例规则 | 分值范围 |
|------|---------|---------|
| 体征异常 | 收缩压连续>140、体重周增幅>2kg | +1~3 |
| 化验异常 | eGFR<60、肌酐环比>20%、血钾>5.5 | +2~5 |
| 依从性 | 随访失约>2次、药物依从性<80% | +1~2 |
| 透析质量 | Kt/V<1.2、透析间期体重增长>5% | +2~4 |
| 综合风险 | 多指标同时异常叠加 | 叠加计算 |
风险等级映射0-2 低 | 3-5 中 | 6-8 高 | 9-10 危急
**规则条件表达式 SchemaJSONLogic 子集):**
采用 JSONLogic 风格的表达式格式,支持嵌套逻辑组合,存储在 `copilot_rules.condition_expr` 字段中。
```json
// 示例:收缩压连续 >140
{
"and": [
{ ">=": [{ "var": "vital_signs.systolic.latest" }, 140] },
{ ">=": [{ "var": "vital_signs.systolic.prev1" }, 140] },
{ ">=": [{ "var": "vital_signs.systolic.prev2" }, 140] }
]
}
// 示例eGFR < 60
{ "<": [{ "var": "lab_reports.egfr.latest" }, 60] }
// 示例:肌酐环比 > 20%
{
">": [
{ "var": "lab_reports.creatinine.change_pct" },
20
]
}
```
支持的数据引用路径:
- `vital_signs.{指标}.latest` / `.prev1` / `.prev2` — 最近 3 次体征值
- `lab_reports.{指标}.latest` / `.change_pct` — 最新化验值 / 环比变化百分比
- `follow_up.missed_count` — 随访失约次数
- `dialysis.ktv.latest` — 最新透析充分性指标
- `medication.adherence_rate` — 药物依从性百分比
支持的操作符:`>`, `>=`, `<`, `<=`, `==`, `!=`, `and`, `or`, `!`, `in`, `var`
规则引擎在 Rust 中实现为递归下降的 JSONLogic 解释器,不依赖外部 DSL。
### 3.2 触点②:健康数据异常检测(感知层)
**触发时机:** 新体征/化验数据入库时(异步事件驱动)
**呈现方式:**
- 实时:医护首页仪表盘的 Copilot 告警卡片
- 推送:浏览器通知(严重异常时)
**告警分级:**
| 级别 | 条件 | 示例 | 呈现 |
|------|------|------|------|
| 🔴 危急 | 危及生命或需立即干预 | 血钾>6.0、严重低血压 | 首页置顶 + 浏览器通知 |
| 🟡 警告 | 需要关注,非紧急 | 收缩压>160、肌酐环比>30% | 首页告警列表 |
| 🟢 提示 | 轻度异常或趋势变化 | 体重周增幅>1.5kg、血压趋势上升 | 患者档案内洞察卡片 |
**与现有告警系统的关系:**
当前 HMS 已有 `health.alerts` 模块。Copilot 异常检测不替代,而是增强:
- 现有告警:基于固定阈值的简单规则(血压>140
- Copilot 告警:基于趋势 + 上下文的智能判断血压从130快速升到150比稳定在145更值得关注
两者共存。固定阈值走现有系统,趋势/上下文异常走 Copilot。
### 3.3 触点③:随访计划智能推荐(决策层)
**触发时机:** 医护为患者创建或编辑随访计划时
**呈现方式:** 随访表单右侧的 Copilot 推荐面板
**生成逻辑:**
1. 规则引擎匹配患者疾病类型 → 基础随访模板(如 CKD 4 期标准随访方案)
2. 规则引擎叠加风险因素 → 调整频率和关注指标
3. LLM 补充 → 基于近期数据生成个性化问诊要点
**输出内容:**
- 推荐随访频率(如"每 2 周 1 次")及理由
- 关注指标列表(如"肾功能、电解质、甲状旁腺激素"
- 建议问诊要点(如"近期是否有恶心、食欲下降、尿量变化"
医护可选择"采纳全部"、"选择性采纳"或"不采纳"。
### 3.4 触点④:在线咨询实时辅助(执行层)
**触发时机:** 医护进入咨询对话时
**呈现方式:** 对话界面侧边栏的 Copilot 面板,不侵入对话区域
**生成逻辑:**
1. 加载患者风险画像(触点①的预计算结果)
2. 实时分析患者消息内容
3. LLM 生成:建议追问方向 + 注意事项提醒 + 过敏/禁忌提示
**输出内容:**
- 患者背景摘要(诊断、透析方案、上次关键指标)
- 建议追问方向(如"浮肿是双侧还是单侧?"
- 注意事项(如"该患者对 XX 药物过敏"
医护可选择"一键插入"追问问题到回复框。
### 3.5 数据流闭环
```
① 风险画像(基础层)
│ 输出:风险评分 + 规则匹配结果 + LLM 补充
② 异常检测(感知层)← 新数据入库事件触发
│ 输出:分级告警 + 异常指标
③ 随访推荐(决策层)← 风险评分 + 异常指标输入
│ 输出:随访方案建议 + 关注指标 + 问诊要点
④ 咨询辅助(执行层)← 患者档案 + 对话内容输入
│ 输出:追问建议 + 注意事项 + 过敏提醒
└─→ 产生新的健康数据/咨询记录 → 回到 ②
```
每个触点的输出是下一个触点的输入。数据在闭环中越转越丰富Copilot 的建议也越来越精准。
---
## §4 患者端 Copilot
### 4.1 角色定位与行为边界
患者端 Copilot 以"小H 健康管家"的身份存在,是血透机构无互联网医院资质下的合规医患沟通桥梁。
**角色:** AI 客服 + 健康管家,嵌入小程序消息体系
**行为边界:**
| 可以做 | 不可以做 |
|--------|---------|
| 解释化验单指标含义(科普) | 诊断疾病("你得了XX" |
| 生活方式建议(饮食、运动) | 开处方("吃XX药" |
| 预约引导、流程咨询 | 预测疗效("吃了会好" |
| 健康数据通俗解读 | 替代医生评估 |
| 紧急情况引导就医 | 推荐特定治疗方案 |
| 基于数据的关怀提醒 | 承诺治疗结果 |
### 4.2 产品形态:对话式嵌入消息体系
"小H"作为小程序内的一个"联系人"出现在消息列表中,患者可以像微信聊天一样互动。
**与 erp-message 模块的关系:**
小H 对话**不复用** erp-message 的消息系统。erp-message 管理的是医护之间的通知消息而小H 对话是 AI 驱动的实时交互两者的数据模型、推送机制、存储需求完全不同。小H 对话使用独立的 `copilot_chat_logs` 表存储。
**消息列表集成方式:**
- 小程序 TabBar 中的"咨询"标签(现有)中新增"小H 健康管家"入口卡片
- 点击进入独立的 Copilot 对话页面(新页面,不属于现有消息列表)
- 不修改现有消息 TabBar 的结构和功能
**微信服务号模板消息推送:**
透析日提醒、复查提醒等需要通过微信服务号模板消息推送。这需要:
- 机构在微信公众平台注册并认证服务号
- 申请模板消息权限并创建所需模板
- 后端集成微信模板消息 API
此功能作为 Phase 4 后期可选增强不影响核心对话功能。Phase 4 MVP 仅实现小程序内对话。
交互入口:
- "咨询"Tab 中的"小H 健康管家"卡片 → 对话窗口
- 首页 AI 问候卡片 → 点击进入对话
- 各健康数据页面的"问小H"按钮 → 带上下文进入对话
### 4.3 意图识别引擎
患者消息先过 LLM 意图分类再路由到不同处理逻辑。5 种意图类型,按优先级排序:
| 优先级 | 意图类型 | 示例 | 处理方式 | 合规要求 |
|--------|---------|------|---------|---------|
| 1 | 紧急情况 | "我胸痛"、"喘不上气"、"出血不止" | 优先响应 + 强制引导就医 + 通知医护 | 必须包含"请立即就医或拨打120" |
| 2 | 健康咨询 | "头晕是不是血压高了"、"这个指标什么意思" | 科普式回答 + 基于患者数据的个性化解读 | 禁止诊断性语言,必须附"建议到院评估" |
| 3 | 服务咨询 | "怎么预约"、"透析时间"、"收费多少" | 规则库直接匹配回答 | 无特殊合规要求 |
| 4 | 情感关怀 | "我不想透析了"、"好累啊"、"谢谢小H" | 共情回应 + 自然过渡到健康话题 | 不做健康承诺 |
| 5 | 闲聊 | "今天天气怎么样"、"你好" | 友好回应 + 巧妙关联健康 | 保持角色一致性 |
**分类策略:** 单次 LLM 调用完成分类(低 token 消耗的快速分类 prompt延迟 < 500ms。分类结果缓存到对话上下文中连续同类消息可跳过重复分类。
### 4.4 对话上下文管理
每次对话自动注入患者上下文,使"小H"真正"认识"患者:
```
上下文结构(后端自动组装,前端不可篡改):
{
"patient": {
"name": "张三",
"age": 62,
"diagnosis": "CKD 4期",
"dialysis_schedule": "每周二、四、六 下午",
"allergies": ["青霉素"],
"medications": ["硝苯地平", "碳酸氢钠"]
},
"recent_data": {
"last_bp": "135/85",
"last_weight": "68.5kg",
"last_dialysis": "2026-05-09",
"next_dialysis": "2026-05-13",
"next_checkup": "2026-05-15"
},
"risk_summary": {
"score": 7,
"level": "中高",
"top_risks": ["eGFR快速下降", "血压趋势上升"]
},
"conversation_summary": "最近5轮对话摘要..."
}
```
**设计约束:**
- 上下文由后端自动组装,前端不可篡改
- 对话历史保留最近 5 轮摘要(控制 token 消耗)
- 敏感字段(详细诊断、具体用药剂量)不注入患者端上下文,只在医护端可见
- 上下文随每次请求刷新,确保数据时效性
### 4.5 引导到院策略
"引导到院"不是生硬的"请去医院",而是基于上下文的自然引导。通过规则 + LLM 配合实现:
**规则驱动引导(确定性):**
- 患者提到任何身体不适 → 触发引导
- 患者问药物相关问题 → 触发引导
- 患者数据持续异常(后台检测)→ 主动推送提醒
- 患者表达消极情绪("不想来了")→ 共情 + 正面引导
**引导话术模板(可配置):**
- 症状类 → "XX可能有多种原因建议让医生当面评估。要不要帮您预约"
- 用药类 → "用药调整需要医生评估。我帮您看看最近有没有门诊?"
- 消极类 → "理解您可能有些疲惫。规律透析很重要,要不看看有没有更方便的时间段?"
---
## §5 合规审查引擎
### 5.1 双层审查架构
患者端 Copilot 的每一条 AI 输出都必须经过合规审查。审查不通过则自动修正,不阻断对话流程。
**Layer 1 — 关键词过滤(规则层,< 5ms**
使用 Aho-Corasick 多模式字符串匹配精确子串匹配扫描预定义的违规词表。不使用正则表达式——Aho-Corasick 只做子串匹配,不支持模式,因此违规词表需列举具体词组而非模式。
| 扫描维度 | 违规关键词(示例,非穷举) | 严重度 |
|---------|---------|--------|
| 诊断类 | "确诊为"、"诊断为"、"你得了"、"诊断结果是" | CRITICAL |
| 处方类 | "建议你吃"、"开点"、"处方"、"调整药量" | CRITICAL |
| 疗效类 | "吃了会好"、"可以治愈"、"保证能好" | HIGH |
| 评估类 | "我判断"、"我认定" | HIGH |
| 承诺类 | "肯定没问题"、"绝对不会出问题" | MEDIUM |
| 误导安慰类 | "完全不用担心"、"绝对没事" | MEDIUM |
命中违规 → 标记违规片段 → 进入修正流程。
未命中 → 进入 Layer 2。
**Layer 2 — 语义审查LLM 层,< 200ms**
通过低 token 消耗的快速分类 prompt 进行语义级审查,捕捉绕过关键词的隐性违规:
```
Prompt: "以下AI回复是否存在医疗合规问题
A.无问题 B.含诊断 C.含处方建议 D.含疗效承诺 E.其他违规
只输出字母。"
实现:调用 erp-ai Provider优先本地 Ollama 降成本)
```
返回 A → 放行。返回 B/C/D/E → 标记违规类型 → 进入修正流程。
### 5.2 审查规则配置
合规过滤规则与风险评分规则§3.1 的 `copilot_rules` 表)是不同的数据结构,使用独立的内存加载方式:
- **风险评分规则** → 存储 `copilot_rules` 表(含 condition_expr、score通过事件驱动执行
- **合规过滤规则** → 使用 Rust 代码内嵌的静态词表 + 可选的 `copilot_compliance_rules` 表(机构自定义扩展词)
机构自定义合规词表在 Phase 4 MVP 中不实现。MVP 使用代码内嵌的固定词表,覆盖 §5.1 中列出的标准违规关键词。后续版本可通过管理后台动态管理词表。
```json
{
"rule_id": "no_diagnosis",
"category": "diagnosis",
"severity": "critical",
"keywords": ["确诊为", "诊断为", "你得了", "诊断结果是", "可以确诊"],
"replacement_template": "这个情况建议让医生当面评估一下",
"auto_fix": true
}
```
内置规则分类CRITICAL诊断/处方)→ 自动修正不可跳过HIGH疗效/评估)→ 自动修正MEDIUM绝对化/误导)→ LLM 重写。
### 5.3 修正策略
三级修正,逐级升格:
**策略 1 — 模板替换(关键词违规,确定性高):**
- 直接用预设安全模板替换违规片段
- 例:"可能是高血压引起的头晕" → "头晕可能有多种原因,建议到院让医生评估"
**策略 2 — LLM 重写(语义违规,需理解上下文):**
- Prompt"将以下回复改写为合规版本,移除诊断/处方语言,改为引导到院,保持关怀语气"
- 重写后再次过 Layer 1 审查
**策略 3 — 兜底降级(两次修正仍不通过):**
- 使用预设安全模板:
- "感谢您的提问,这个问题建议您下次来的时候直接跟医生聊聊。要不要我帮您预约?"
### 5.4 降级策略AI 不可用时)
当 LLM 服务不可用时合规审查降级为纯规则模式Layer 1 only
- 仅回答服务咨询类问题(预约、流程、地址等)
- 健康类问题统一使用安全兜底模板
- 不尝试生成个性化健康解读
- 对话 UI 显示"小H 暂时只能回答预约和流程类问题,健康问题建议直接咨询医生"
### 5.5 审计追踪
每条患者端对话的审查记录持久化存储到 `copilot_chat_logs` 表:
| 字段 | 说明 |
|------|------|
| user_message | 患者原文 |
| intent_classification | 意图分类结果 |
| ai_raw_response | AI 原始输出(修正前) |
| layer1_result | 关键词审查结果 |
| layer2_result | 语义审查结果 |
| violations_found | 违规项列表 |
| fix_strategy | 修正策略类型 |
| final_response | 最终发给患者的文本 |
**数据保留策略:**
- 审查日志保留 3 年(医疗数据合规要求)
- 原始输出与最终输出的对比可用于持续评估审查准确性
- 机构可定期审查 AI 对话是否有违规漏过
---
## §6 技术设计
### 6.1 Crate 架构
扩展现有 erp-ai crate在其内部新增 `copilot/` 子模块。不新建独立 crate。
理由Copilot 的 AI 调用复用 erp-ai 的 Provider 抽象层,无需重复实现。规则引擎虽不依赖 AI但作为子模块放在 erp-ai 内部更内聚。如未来 Copilot 发展到需要独立部署,再拆分为微服务。
```
crates/erp-ai/src/
├── copilot/ (新增)
│ ├── mod.rs — 模块入口
│ ├── engine.rs — 洞察调度器
│ ├── rules.rs — 规则引擎(条件解析 + 评分)
│ ├── scoring.rs — 混合评分(规则分 + LLM 补充)
│ ├── intent.rs — 患者端意图识别
│ ├── compliance.rs — 合规审查引擎
│ └── context.rs — 对话上下文组装
├── handler/ (新增)
│ ├── insight_handler.rs — 洞察查询 API
│ ├── chat_handler.rs — 患者对话 API
│ └── risk_handler.rs — 风险评分 API
├── entity/ (新增)
│ ├── copilot_insights.rs
│ ├── copilot_risk_snapshots.rs
│ ├── copilot_chat_logs.rs
│ └── copilot_rules.rs
├── service/ (新增)
│ ├── insight_service.rs
│ ├── risk_service.rs
│ ├── chat_service.rs
│ └── compliance_service.rs
└── event/ (新增)
└── copilot_consumer.rs — 订阅 health 模块事件
```
### 6.2 数据库设计
4 张新表均包含标准字段id, tenant_id, created_at, updated_at, created_by, updated_by, deleted_at, version
**跨 crate 外键策略:** copilot_insights 和 copilot_risk_snapshots 中的 `patient_id` 使用逻辑关联(无外键约束),不直接引用 erp-health 的 `patients` 表。理由:根据架构铁律"模块间只通过事件总线和 trait 通信"erp-ai 不应直接依赖 erp-health 的表结构。数据一致性通过事件驱动保证——patient.created 事件触发初始化patient 数据变更通过事件通知。
**copilot_rules — 规则配置**
```sql
CREATE TABLE copilot_rules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
name VARCHAR(200) NOT NULL,
category VARCHAR(50) NOT NULL, -- vital_signs/lab/adherence/dialysis/composite
condition_expr JSONB NOT NULL, -- 规则条件表达式
score SMALLINT NOT NULL, -- +1 ~ +5
severity VARCHAR(20) NOT NULL, -- info/warning/critical
suggestion TEXT,
enabled BOOLEAN DEFAULT true,
sort_order INT DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
created_by UUID, updated_by UUID,
deleted_at TIMESTAMPTZ, version INT DEFAULT 1
);
```
**copilot_insights — 洞察存储**
```sql
CREATE TABLE copilot_insights (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
patient_id UUID NOT NULL, -- 逻辑关联 patients 表,无外键约束(跨 crate
insight_type VARCHAR(50) NOT NULL, -- risk_score/anomaly/follow_up_hint/consult_hint
source VARCHAR(20) NOT NULL, -- rule/llm/hybrid
severity VARCHAR(20),
title VARCHAR(500) NOT NULL,
content JSONB NOT NULL,
rule_matches JSONB,
llm_supplement TEXT,
expires_at TIMESTAMPTZ NOT NULL,
is_read BOOLEAN DEFAULT false,
is_dismissed BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
created_by UUID, updated_by UUID,
deleted_at TIMESTAMPTZ, version INT DEFAULT 1
);
```
**copilot_risk_snapshots — 风险评分快照**
```sql
CREATE TABLE copilot_risk_snapshots (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
patient_id UUID NOT NULL, -- 逻辑关联 patients 表,无外键约束(跨 crate
risk_score SMALLINT NOT NULL, -- 0-10
risk_level VARCHAR(20) NOT NULL, -- low/medium/high/critical
rule_details JSONB NOT NULL,
llm_summary TEXT,
computed_at TIMESTAMPTZ NOT NULL,
data_freshness JSONB,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
created_by UUID, updated_by UUID,
deleted_at TIMESTAMPTZ, version INT DEFAULT 1
);
```
**copilot_chat_logs — 对话审查日志**
```sql
CREATE TABLE copilot_chat_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
tenant_id UUID NOT NULL,
patient_id UUID NOT NULL,
session_id UUID NOT NULL,
user_message TEXT NOT NULL,
intent_classification VARCHAR(30),
ai_raw_response TEXT,
layer1_result JSONB,
layer2_result JSONB,
violations_found JSONB,
fix_strategy VARCHAR(30),
final_response TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT now(),
updated_at TIMESTAMPTZ DEFAULT now(),
created_by UUID, updated_by UUID,
deleted_at TIMESTAMPTZ, version INT DEFAULT 1
);
```
### 6.3 API 设计
**医护端 API需 JWT 认证 + 权限码):**
| 方法 | 路径 | 权限码 | 说明 |
|------|------|--------|------|
| GET | `/api/v1/copilot/insights` | copilot.insights.list | 查询洞察列表(支持按患者/类型/严重度过滤) |
| GET | `/api/v1/copilot/insights/{id}` | copilot.insights.list | 获取单条洞察详情 |
| POST | `/api/v1/copilot/insights/{id}/dismiss` | copilot.insights.manage | 标记洞察已处理/忽略 |
| GET | `/api/v1/copilot/patients/{id}/risk` | copilot.risk.view | 获取患者风险画像 |
| GET | `/api/v1/copilot/patients/{id}/followup-hint` | copilot.risk.view | 获取随访推荐建议 |
| GET | `/api/v1/copilot/patients/{id}/consult-hint` | copilot.risk.view | 获取咨询辅助建议 |
| GET | `/api/v1/copilot/rules` | copilot.rules.list | 列出规则 |
| POST | `/api/v1/copilot/rules` | copilot.rules.manage | 创建规则 |
| PUT | `/api/v1/copilot/rules/{id}` | copilot.rules.manage | 更新规则 |
**患者端 API需患者 JWT**
患者端与医护端共享同一套 JWT 认证体系erp-auth。小程序通过微信登录获取 token`apps/miniprogram/src/services/` 的现有 auth 流程),该 token 包含 `user_id``tenant_id`。Copilot API 通过 `user_id` 关联 `patients` 表确定患者身份。
`copilot.chat.patient` 权限码在角色初始化时自动赋予"患者"角色(非管理后台角色),无需手动分配。
| 方法 | 路径 | 说明 |
|------|------|------|
| POST | `/api/v1/copilot/chat` | 发送消息,返回合规审查后的回复 |
| GET | `/api/v1/copilot/chat/history` | 获取对话历史(分页) |
| GET | `/api/v1/copilot/chat/daily-greeting` | 获取今日个性化问候 |
**权限码汇总:**
```
copilot.insights.list — 查看洞察列表
copilot.insights.manage — 处理/忽略洞察
copilot.risk.view — 查看风险画像
copilot.rules.list — 查看规则
copilot.rules.manage — 管理规则
copilot.chat.patient — 患者端对话(患者角色自带)
```
### 6.4 事件订阅
Copilot 引擎订阅以下 erp-health 模块事件。事件名称已对齐 `crates/erp-health/src/event/mod.rs` 中的实际常量:
**已有事件(可直接订阅):**
| 事件常量 | 触发动作 |
|---------|---------|
| `daily_monitoring.created` | 体征数据录入 → 异常检测 + 风险评分刷新 |
| `lab_report.reviewed` | 化验报告审核 → 化验异常检测 + 风险评分刷新 |
| `follow_up.completed` | 随访完成 → 更新随访依从性 |
| `follow_up.overdue` | 随访失约 → 风险评分 +1 + 生成告警 |
| `patient.created` | 患者建档 → 初始化风险基线 |
**需新增事件(在 erp-health 中添加发布点):**
| 事件常量 | 发布时机 | 触发动作 |
|---------|---------|---------|
| `appointment.completed` | 预约状态变为已完成 | 更新依从性数据 + 风险评分刷新 |
事件消费流程EventBus 收到事件 → copilot_consumer 匹配事件类型 → 调用规则引擎评估 → 生成/更新洞察 → 写入 copilot_insights → 如有异常告警则通知医护端。
### 6.5 前端组件设计
**医护端 React 组件apps/web/src/components/Copilot/**
```
├── CopilotBadge.tsx — 风险等级徽章(嵌入患者列表/详情页)
├── CopilotCard.tsx — 洞察卡片(风险画像/异常告警)
├── CopilotAlert.tsx — 告警通知(仪表盘/首页)
├── CopilotPanel.tsx — 侧边栏面板(随访推荐/咨询辅助)
├── CopilotInsightList.tsx — 洞察列表页
└── hooks/
├── useCopilotInsights.ts — 洞察数据 hook
└── useCopilotRisk.ts — 风险评分 hook
```
**患者端小程序组件apps/miniprogram/src/pages/copilot/**
```
├── index.tsx — 对话主页
└── components/
├── ChatBubble.tsx — 消息气泡
├── QuickActions.tsx — 快捷入口
└── InputBar.tsx — 输入框
```
---
## §7 实施阶段
总体策略:自下而上,每个阶段交付可验证的价值。
### Phase 0基础设施地基
**目标:** 搭建 Copilot 引擎骨架,让它能"跑起来"
| 任务 | 产出 | 依赖 |
|------|------|------|
| 数据库迁移 | 4 个迁移文件copilot_rules, copilot_insights, copilot_risk_snapshots, copilot_chat_logs | 无 |
| 规则引擎核心 | `copilot/rules.rs` — 条件解析 + 评分计算 | 无 |
| 洞察存储 CRUD | `insight_service.rs` — 写入/查询/过期清理 | 迁移文件 |
| 风险评分基础框架 | `scoring.rs` — 规则评分逻辑(纯规则,无 LLM | 规则引擎 |
| Copilot API 骨架 | 3 个 handler + 路由注册 | 洞察存储 |
| 预置规则种子数据 | 10-15 条内置规则(体征/化验/依从性) | 迁移文件 |
**验收标准:**
- `cargo check` 通过
- 内置规则对患者数据跑通评分逻辑
- API 可查询风险评分和洞察列表
### Phase 1医护端风险画像第一个可见价值
**目标:** 医护打开患者档案时,能看到 Copilot 风险徽章和洞察卡片
| 任务 | 产出 | 依赖 |
|------|------|------|
| 事件消费者 | `copilot_consumer.rs` — 订阅体征/化验事件 | Phase 0 |
| 风险评分刷新(异步) | 事件触发 → 规则评估 → 写入风险快照 | 事件消费者 |
| LLM 补充分析集成 | 规则评分结果 → 调用 erp-ai → LLM 补充洞察 | erp-ai Provider |
| 前端 CopilotBadge | 患者列表/详情页风险徽章 | API |
| 前端 CopilotCard | 可展开的洞察卡片 | API |
| 每日风险快照批量刷新 | 定时任务:凌晨重算所有在管患者 | 评分逻辑 |
**验收标准:**
- 录入新体征数据后,风险评分自动更新
- 医护端患者详情页显示风险徽章和洞察卡片
- LLM 补充分析正常返回(非阻塞,失败降级为纯规则)
### Phase 2异常检测 + 告警推送
**目标:** 健康数据入库时自动检测异常,推送告警给医护
| 任务 | 产出 | 依赖 |
|------|------|------|
| 异常检测规则扩展 | 新增趋势类/复合类规则 | Phase 1 |
| 告警洞察生成 | 异常 → 生成洞察 → 推送通知 | 事件消费者 |
| 前端 CopilotAlert | 仪表盘告警卡片 + 浏览器通知 | API |
| 告警处理工作流 | 标记已处理 / 忽略 / 升级 | API |
**验收标准:**
- 危急值(如血钾 >6.0)入库后秒级生成告警
- 医护仪表盘显示分级告警列表
- 告警可标记处理状态
### Phase 3随访推荐 + 咨询辅助
**目标:** Copilot 在医护创建随访/咨询时提供智能建议
| 任务 | 产出 | 依赖 |
|------|------|------|
| 随访推荐逻辑 | 基于风险画像 + 疾病模板 → 推荐随访方案 | Phase 1 风险数据 |
| 咨询辅助逻辑 | 患者档案 + 对话内容 → 追问建议 | Phase 1 + 对话上下文 |
| 前端 CopilotPanel | 随访/咨询页面侧边栏 | API |
| 一键采纳/插入 | 医护可将建议直接填入表单 | 前端组件 |
**验收标准:**
- 创建随访计划时 Copilot 面板显示个性化建议
- 咨询对话时侧边栏显示患者背景和追问建议
- 建议可一键插入到表单/回复框
### Phase 4患者端 CopilotAI 客服/管家)
**目标:** 患者小程序内可与小H对话合规解答、引导到院
| 任务 | 产出 | 依赖 |
|------|------|------|
| 意图识别引擎 | `intent.rs` — 5 类意图分类 | erp-ai Provider |
| 合规审查引擎 | `compliance.rs` — 双层审查 + 自动修正 | 意图识别 |
| 对话上下文组装 | `context.rs` — 患者数据 + 对话历史自动注入 | 风险画像数据 |
| 对话 API | `chat_handler.rs` — 消息收发 + 审查 | 合规引擎 |
| 小程序对话页面 | 聊天 UI + 快捷入口 | API |
| 每日问候生成 | 基于患者数据的个性化推送 | 风险画像 + 定时任务 |
| 审计日志 | 对话审查记录持久化 | 合规引擎 |
**验收标准:**
- 患者可发送消息,获得合规审查后的回复
- 诊断性/处方性提问被自动修正为引导到院
- 对话记录完整可审计
- 每日个性化问候正常推送
### Phase 5日活引擎小程序游戏化
**目标:** 积分体系 + AI 问候驱动患者日常互动
| 任务 | 产出 | 依赖 |
|------|------|------|
| 每日任务系统 | 打卡/录入/阅读任务定义 + 积分规则 | Phase 4 |
| 积分经济扩展 | 分层兑换:服务特权 + 实物商品 | 现有积分模块 |
| 连续打卡 + 勋章 | streak 追踪 + 成就系统 | 任务系统 |
| AI 问候与任务联动 | 问候消息嵌入任务入口 | Phase 4 + 任务系统 |
| 小程序首页改版 | 任务入口 + 积分展示 + AI 问候卡片 | 前端 |
**验收标准:**
- 患者每日可完成健康任务获得积分
- 积分可兑换服务特权/实物商品
- 连续打卡有加成奖励
- AI 问候与当日任务关联
### 阶段依赖总览
```
Phase 0基础设施
Phase 1风险画像← 第一个可见价值
├─────────────────┐
▼ ▼
Phase 2异常检测 Phase 3随访/咨询辅助)
│ │
└────────┬────────┘
Phase 4患者端 Copilot← 合规 AI 客服
Phase 5日活引擎← 游戏化闭环
```
### 预估工作量
| 阶段 | 后端 | 前端 | 数据库 | 核心挑战 |
|------|------|------|--------|---------|
| Phase 0 | 5-7 天 | — | 2 天 | 规则引擎的条件表达式设计 |
| Phase 1 | 5-7 天 | 3-5 天 | — | LLM 集成的稳定性与降级 |
| Phase 2 | 3-5 天 | 2-3 天 | — | 告警分级与推送策略 |
| Phase 3 | 5-7 天 | 3-5 天 | — | 随访模板与疾病类型的映射 |
| Phase 4 | 7-10 天 | 5-7 天 | 1 天 | 合规审查的准确性与延迟 |
| Phase 5 | 5-7 天 | 5-7 天 | 1 天 | 积分经济平衡 |