修复 2 CRITICAL + 4 HIGH + 3 MEDIUM: - CRITICAL: 路由注册改为 public_routes/protected_routes 静态方法 - CRITICAL: 补全 HealthDataProvider trait + DTO 定义 + 注入机制 - HIGH: 修复 async_trait + impl Stream 不兼容 (改用 Pin<Box<dyn Stream>>) - HIGH: 新增 AiConfig 配置段 + config.toml 定义 - HIGH: sanitized_input 改为 AES-256 加密 + 90 天自动清理 - HIGH: 新增 futures/tokio-stream/async-stream 依赖说明 - MEDIUM: 事件改为 DomainEvent 字符串模式 - MEDIUM: 权限码改为 .list/.manage 实体命名模式 - MEDIUM: 补全 AiState + FromRef 注入模式 - MEDIUM: 补全缓存设计 (Redis/键格式/TTL/Hash算法)
644 lines
21 KiB
Markdown
644 lines
21 KiB
Markdown
# erp-ai 模块设计规格
|
||
|
||
> 日期: 2026-04-25 | 状态: 设计审批通过,待实施
|
||
> 技术架构演进方向: 实时能力层 → AI 智能分析流
|
||
|
||
---
|
||
|
||
## 1. 背景与目标
|
||
|
||
HMS 健康管理平台已完成全部基础模块和健康业务模块的开发。为提升患者端体验、降低医患沟通成本,引入 **AI 智能分析能力**,让患者在小程序端即可获得化验单解读、健康趋势分析、个性化体检方案和报告摘要等 AI 驱动的自助服务。
|
||
|
||
**核心目标:**
|
||
- 患者按需获取 AI 流式解读,无需等待医生电话
|
||
- 模型无关的 AI Gateway,支持多提供商切换和 A/B 测试
|
||
- 严格数据脱敏,PII 不离开服务器
|
||
- Prompt 模板数据库化管理,支持版本控制和回滚
|
||
- 架构预留异步分析管道,平滑演进到混合模式
|
||
|
||
---
|
||
|
||
## 2. 模块定位
|
||
|
||
### 2.1 在 HMS 架构中的位置
|
||
|
||
```
|
||
erp-core (ErpModule trait + EventBus + AppState)
|
||
↑
|
||
erp-health ──publish──→ {patient.lab-report.created, health.vital-signs.updated, ...}
|
||
↑
|
||
erp-ai (NEW) ──subscribe──→ health 事件 (Phase 3 预留)
|
||
──publish──→ {ai.analysis.completed, ai.analysis.failed}
|
||
↑
|
||
erp-server (组装入口)
|
||
```
|
||
|
||
### 2.2 为什么是独立模块而非内嵌
|
||
|
||
- AI 是跨横切平台能力,非健康专属(未来消息、工作流、客服都可能用)
|
||
- 遵循 CLAUDE.md 铁律:业务 crate 之间禁止直接依赖
|
||
- 通过 EventBus + `HealthDataProvider` trait 获取数据,模块边界清晰
|
||
- 提供商切换、成本管理、脱敏策略全部内聚在 erp-ai 内部
|
||
|
||
---
|
||
|
||
## 3. 架构设计
|
||
|
||
### 3.1 Crate 结构
|
||
|
||
```
|
||
crates/erp-ai/
|
||
├── Cargo.toml
|
||
└── src/
|
||
├── lib.rs # 模块入口, ErpModule impl
|
||
├── error.rs # AiError → AppError
|
||
├── provider/ # AI 提供商抽象
|
||
│ ├── mod.rs # AiProvider trait 定义
|
||
│ ├── claude.rs # Claude API 实现
|
||
│ ├── openai.rs # OpenAI API 实现 (Phase 2)
|
||
│ └── config.rs # 提供商配置 & 路由策略
|
||
├── pipeline/ # 分析管道
|
||
│ ├── mod.rs # AnalysisPipeline trait
|
||
│ ├── request.rs # 请求驱动管道 (Phase 1, SSE)
|
||
│ └── async.rs # 异步管道 (Phase 3, stub)
|
||
├── sanitization/ # 数据脱敏层
|
||
│ ├── mod.rs # DeIdentificationService
|
||
│ └── rules.rs # 脱敏规则
|
||
├── prompt/ # Prompt 管理
|
||
│ ├── mod.rs # PromptManager trait
|
||
│ └── template.rs # 模板渲染引擎
|
||
├── entity/ # SeaORM Entity
|
||
│ ├── ai_prompt.rs
|
||
│ ├── ai_analysis.rs
|
||
│ └── ai_usage.rs
|
||
├── service/ # 业务逻辑
|
||
│ ├── analysis.rs # AnalysisService (核心编排)
|
||
│ ├── prompt.rs # PromptService (CRUD + 版本)
|
||
│ └── usage.rs # UsageService (用量追踪)
|
||
└── handler/ # Axum 路由
|
||
├── mod.rs
|
||
├── analysis.rs # SSE 流式分析端点
|
||
├── prompt.rs # Prompt 管理 CRUD
|
||
└── usage.rs # 用量统计端点
|
||
```
|
||
|
||
### 3.2 核心抽象
|
||
|
||
```rust
|
||
/// AI 提供商 trait
|
||
/// 使用 Pin<Box<dyn Stream>> 避免 async_trait + impl Trait 不兼容问题
|
||
#[async_trait]
|
||
pub trait AiProvider: Send + Sync {
|
||
async fn stream_generate(
|
||
&self,
|
||
req: GenerateRequest,
|
||
) -> Result<Pin<Box<dyn Stream<Item = Result<Chunk>> + Send>>>;
|
||
async fn generate(&self, req: GenerateRequest) -> Result<GenerateResponse>;
|
||
fn name(&self) -> &str;
|
||
async fn health_check(&self) -> Result<bool>;
|
||
}
|
||
|
||
/// 分析管道 trait (Phase 1: RequestPipeline; Phase 3: + AsyncPipeline)
|
||
pub trait AnalysisPipeline: Send + Sync {
|
||
fn analyze(
|
||
&self,
|
||
ctx: AnalysisContext,
|
||
) -> Pin<Box<dyn Stream<Item = AnalysisChunk> + Send>>;
|
||
}
|
||
|
||
/// 数据脱敏 trait — 输入为 HealthDataProvider 返回的 DTO,非原始 SeaORM Entity
|
||
pub trait DataSanitizer: Send + Sync {
|
||
fn sanitize(&self, data: &HealthAnalysisInput) -> Result<SanitizedData>;
|
||
}
|
||
|
||
/// 分析上下文
|
||
pub struct AnalysisContext {
|
||
pub trigger: AnalysisTrigger,
|
||
pub tenant_id: Uuid,
|
||
pub user_id: Uuid,
|
||
pub analysis_type: AnalysisType,
|
||
pub source_ref: String,
|
||
}
|
||
|
||
pub enum AnalysisTrigger {
|
||
Request, // Phase 1: 患者主动请求
|
||
Event, // Phase 3: 异步事件触发 (预留)
|
||
}
|
||
```
|
||
|
||
### 3.3 路由注册(遵循现有 public_routes / protected_routes 模式)
|
||
|
||
```rust
|
||
/// erp-ai 模块注册(参照 erp-health 的注册方式)
|
||
pub struct AiModule;
|
||
|
||
impl AiModule {
|
||
pub fn public_routes<S>() -> Router<S>
|
||
where
|
||
S: Clone + Send + Sync + 'static,
|
||
AiState: FromRef<S>,
|
||
{
|
||
Router::new()
|
||
.nest("/api/v1/ai/analyze", analyze::public_routes::<S>())
|
||
}
|
||
|
||
pub fn protected_routes<S>() -> Router<S>
|
||
where
|
||
S: Clone + Send + Sync + 'static,
|
||
AiState: FromRef<S>,
|
||
{
|
||
Router::new()
|
||
.nest("/api/v1/ai/analyze", analyze::protected_routes::<S>())
|
||
.nest("/api/v1/ai/analysis", analysis::routes::<S>())
|
||
.nest("/api/v1/ai/prompts", prompt::routes::<S>())
|
||
.nest("/api/v1/ai/usage", usage::routes::<S>())
|
||
.nest("/api/v1/ai/admin", admin::routes::<S>())
|
||
}
|
||
}
|
||
|
||
// erp-server/src/main.rs 中合并路由:
|
||
// let app = Router::new()
|
||
// .merge(AuthModule::public_routes())
|
||
// // ...
|
||
// .merge(AiModule::public_routes())
|
||
// .merge(AuthModule::protected_routes())
|
||
// // ...
|
||
// .merge(AiModule::protected_routes())
|
||
```
|
||
|
||
### 3.4 AiState 注入(遵循现有 FromRef 模式)
|
||
|
||
```rust
|
||
/// erp-ai 的 Axum State
|
||
pub struct AiState {
|
||
pub db: DatabaseConnection,
|
||
pub event_bus: EventBus,
|
||
pub provider_registry: ProviderRegistry,
|
||
pub sanitizer: Arc<dyn DataSanitizer>,
|
||
pub prompt_manager: Arc<dyn PromptManager>,
|
||
pub redis: deadpool_redis::Pool,
|
||
pub config: AiConfig,
|
||
}
|
||
|
||
// erp-server/src/state.rs 中新增:
|
||
// impl FromRef<AppState> for AiState { ... }
|
||
```
|
||
|
||
### 3.5 HealthDataProvider trait 与数据 DTO
|
||
|
||
```rust
|
||
// === erp-core 中新增 ===
|
||
|
||
/// 健康数据提供者 trait,由 erp-health 实现
|
||
/// 通过 AppState 中的 Arc<dyn HealthDataProvider> 注入到 erp-ai
|
||
#[async_trait]
|
||
pub trait HealthDataProvider: Send + Sync {
|
||
/// 获取化验报告(指标列表)
|
||
async fn get_lab_report(
|
||
&self,
|
||
tenant_id: Uuid,
|
||
report_id: Uuid,
|
||
) -> AppResult<LabReportDto>;
|
||
|
||
/// 获取生命体征趋势数据
|
||
async fn get_vital_signs(
|
||
&self,
|
||
tenant_id: Uuid,
|
||
patient_id: Uuid,
|
||
metrics: &[String],
|
||
range: TimeRange,
|
||
) -> AppResult<Vec<VitalSignDto>>;
|
||
|
||
/// 获取患者摘要(用于个性化方案,不含 PII 的 DTO)
|
||
async fn get_patient_summary(
|
||
&self,
|
||
tenant_id: Uuid,
|
||
patient_id: Uuid,
|
||
) -> AppResult<PatientSummaryDto>;
|
||
|
||
/// 获取完整健康报告(用于摘要生成)
|
||
async fn get_full_report(
|
||
&self,
|
||
tenant_id: Uuid,
|
||
report_id: Uuid,
|
||
) -> AppResult<HealthReportDto>;
|
||
}
|
||
|
||
// === DTO 定义(erp-core 中,已脱去 PII 的数据传输对象)===
|
||
// 注意: erp-health 实现此 trait 时,返回的 DTO 不包含患者姓名、身份证号等 PII
|
||
// 而是只包含 age_group / sex / 科室 / 检验指标 / 诊断编码 等医疗数据
|
||
|
||
pub struct LabReportDto {
|
||
pub age_group: String, // "40-50"
|
||
pub sex: String, // "male" / "female"
|
||
pub department: String, // 科室
|
||
pub report_date: String, // 报告日期
|
||
pub items: Vec<LabItemDto>, // 检验项目列表
|
||
}
|
||
|
||
pub struct LabItemDto {
|
||
pub name: String, // 指标名称
|
||
pub value: f64, // 检测值
|
||
pub unit: String, // 单位
|
||
pub reference_range: String, // 参考范围
|
||
pub is_abnormal: bool, // 是否异常
|
||
}
|
||
|
||
pub struct VitalSignDto {
|
||
pub metric: String, // 指标名
|
||
pub values: Vec<(String, f64)>, // (日期, 值) 时间序列
|
||
pub unit: String,
|
||
}
|
||
|
||
pub struct PatientSummaryDto {
|
||
pub age_group: String,
|
||
pub sex: String,
|
||
pub chronic_conditions: Vec<String>, // 慢性病编码
|
||
pub medications: Vec<String>, // 当前用药
|
||
pub family_history: Vec<String>, // 家族史
|
||
pub last_checkup_date: String,
|
||
}
|
||
|
||
pub struct HealthReportDto {
|
||
pub age_group: String,
|
||
pub sex: String,
|
||
pub department: String,
|
||
pub report_date: String,
|
||
pub sections: Vec<ReportSectionDto>,
|
||
}
|
||
|
||
pub struct ReportSectionDto {
|
||
pub title: String,
|
||
pub findings: Vec<String>,
|
||
pub abnormal_items: Vec<String>,
|
||
}
|
||
```
|
||
|
||
### 3.6 事件定义(遵循现有 DomainEvent 字符串模式)
|
||
|
||
```rust
|
||
// AI 事件使用现有 DomainEvent 结构,event_type 为字符串
|
||
// 而非独立的 AiEvent enum
|
||
|
||
// ai.analysis.completed 事件 payload:
|
||
// {
|
||
// "analysis_id": "uuid",
|
||
// "patient_id": "uuid",
|
||
// "analysis_type": "lab_report",
|
||
// "status": "completed",
|
||
// "model_used": "claude-sonnet-4-6"
|
||
// }
|
||
|
||
// ai.analysis.failed 事件 payload:
|
||
// {
|
||
// "analysis_id": "uuid",
|
||
// "patient_id": "uuid",
|
||
// "analysis_type": "lab_report",
|
||
// "error": "provider timeout"
|
||
// }
|
||
```
|
||
|
||
### 3.7 权限码(遵循 .list / .manage 实体命名模式)
|
||
|
||
```rust
|
||
/// 权限码遵循 CLAUDE.md 规范: 每个实体声明 .list + .manage
|
||
pub const AI_PERMISSIONS: &[&str] = &[
|
||
"ai.analysis.list", // 查看分析历史列表
|
||
"ai.analysis.manage", // 请求新分析 / 管理分析结果
|
||
"ai.prompt.list", // 查看 Prompt 列表
|
||
"ai.prompt.manage", // 创建/编辑/激活/回滚 Prompt
|
||
"ai.usage.list", // 查看用量统计
|
||
"ai.provider.manage", // 管理提供商配置
|
||
];
|
||
```
|
||
|
||
### 3.8 配置结构(遵循现有 AppConfig 模式)
|
||
|
||
```toml
|
||
# config/default.toml 中新增 [ai] 段
|
||
|
||
[ai]
|
||
default_provider = "claude"
|
||
cache_ttl_seconds = 604800 # 7 天
|
||
rate_limit_patient_daily = 10
|
||
rate_limit_admin_hourly = 100
|
||
sanitized_input_retention_days = 90 # 脱敏输入保留 90 天
|
||
|
||
[ai.providers.claude]
|
||
api_key_env = "ANTHROPIC_API_KEY" # 从环境变量读取
|
||
model = "claude-sonnet-4-6"
|
||
max_tokens = 2048
|
||
temperature = 0.3
|
||
|
||
[ai.providers.openai]
|
||
enabled = false
|
||
api_key_env = "OPENAI_API_KEY"
|
||
model = "gpt-4o"
|
||
```
|
||
|
||
```rust
|
||
// erp-server/src/config.rs 新增:
|
||
#[derive(Debug, Deserialize)]
|
||
pub struct AiConfig {
|
||
pub default_provider: String,
|
||
pub cache_ttl_seconds: u64,
|
||
pub rate_limit_patient_daily: u32,
|
||
pub rate_limit_admin_hourly: u32,
|
||
pub sanitized_input_retention_days: u32,
|
||
pub providers: HashMap<String, ProviderConfig>,
|
||
}
|
||
```
|
||
|
||
### 3.9 流式依赖
|
||
|
||
```toml
|
||
# erp-ai/Cargo.toml 新增依赖
|
||
[dependencies]
|
||
futures = "0.3"
|
||
tokio-stream = "0.1"
|
||
async-stream = "0.3"
|
||
# axum 已内置 SSE 支持: axum::response::sse::{Event, Sse}
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 数据流设计
|
||
|
||
### 4.1 请求驱动管道 (Phase 1)
|
||
|
||
```
|
||
患者点击"AI解读" → POST /api/v1/ai/analyze/lab-report
|
||
→ 鉴权 (JWT → tenant_id + user_id)
|
||
→ 权限检查 (ai.analysis.request)
|
||
→ 加载数据 (via HealthDataProvider trait)
|
||
→ DeIdentificationService.sanitize()
|
||
→ PromptManager.render(template, sanitized_data)
|
||
→ AiProvider.stream_generate(prompt)
|
||
→ SSE 流式返回给前端
|
||
→ 存储完整结果到 ai_analysis_results
|
||
→ 记录用量到 ai_usage_logs
|
||
→ 发布 ai.analysis.completed 事件
|
||
```
|
||
|
||
### 4.2 SSE 事件格式
|
||
|
||
```
|
||
event: chunk
|
||
data: {"content": "您的血常规检查中,", "index": 1}
|
||
|
||
event: metadata
|
||
data: {"model": "claude-sonnet-4-6", "tokens": {"input": 856, "output": 423}, "duration_ms": 3200}
|
||
|
||
event: done
|
||
data: {"analysis_id": "uuid-xxx", "status": "completed"}
|
||
```
|
||
|
||
### 4.3 四种分析场景
|
||
|
||
| 场景 | 端点 | 输入 | Prompt 模板 |
|
||
|------|------|------|------------|
|
||
| 化验单解读 | `POST /ai/analyze/lab-report` | report_id | lab_report_interpretation |
|
||
| 趋势分析 | `POST /ai/analyze/trends` | patient_id + metrics + 时间范围 | health_trend_analysis |
|
||
| 个性化方案 | `POST /ai/analyze/checkup-plan` | patient_id | personalized_checkup_plan |
|
||
| 报告摘要 | `POST /ai/analyze/report-summary` | report_id | report_summary_generation |
|
||
|
||
---
|
||
|
||
## 5. 数据库设计
|
||
|
||
### 5.1 ai_prompts
|
||
|
||
| 列 | 类型 | 说明 |
|
||
|----|------|------|
|
||
| id | UUID v7 | 主键 |
|
||
| tenant_id | UUID | 租户 |
|
||
| name | VARCHAR(100) | 模板标识 |
|
||
| description | TEXT | 用途描述 |
|
||
| system_prompt | TEXT | 系统角色 Prompt |
|
||
| user_prompt_template | TEXT | 用户 Prompt 模板 ({{variable}} 占位符) |
|
||
| variables_schema | JSONB | 模板变量 JSON Schema |
|
||
| model_config | JSONB | {provider, model, temperature, max_tokens} |
|
||
| version | INT | 语义版本(自增) |
|
||
| is_active | BOOLEAN | 当前激活版本 |
|
||
| category | VARCHAR(50) | analysis / summary / suggestion |
|
||
| tags | JSONB | 标签数组 |
|
||
| + 标准审计字段 | | tenant_id, created_at, updated_at, created_by, updated_by, deleted_at, version |
|
||
|
||
### 5.2 ai_analysis_results
|
||
|
||
| 列 | 类型 | 说明 |
|
||
|----|------|------|
|
||
| id | UUID v7 | 主键 |
|
||
| tenant_id | UUID | 租户 |
|
||
| patient_id | UUID | 患者 |
|
||
| analysis_type | VARCHAR(50) | lab_report / trend / checkup_plan / report_summary |
|
||
| source_ref | VARCHAR(200) | 来源引用 |
|
||
| prompt_id | UUID | 关联 Prompt |
|
||
| prompt_version | INT | Prompt 版本号 |
|
||
| model_used | VARCHAR(100) | 实际模型 |
|
||
| input_data_hash | VARCHAR(64) | SHA-256(缓存键) |
|
||
| sanitized_input | JSONB | 脱敏输入快照(AES-256 加密存储,90天自动清理) |
|
||
| result_content | TEXT | AI 完整输出 |
|
||
| result_metadata | JSONB | tokens/耗时/模型信息 |
|
||
| status | VARCHAR(20) | pending / streaming / completed / failed |
|
||
| error_message | TEXT | 失败原因 |
|
||
| + 标准审计字段 | | |
|
||
|
||
### 5.3 ai_usage_logs
|
||
|
||
| 列 | 类型 | 说明 |
|
||
|----|------|------|
|
||
| id | UUID v7 | 主键 |
|
||
| tenant_id | UUID | 租户 |
|
||
| provider | VARCHAR(50) | claude / openai / local |
|
||
| model | VARCHAR(100) | 具体模型 |
|
||
| analysis_type | VARCHAR(50) | 分析类型 |
|
||
| input_tokens | INT | |
|
||
| output_tokens | INT | |
|
||
| duration_ms | INT | |
|
||
| cost_cents | INT | 费用(分) |
|
||
| is_cache_hit | BOOLEAN | |
|
||
| created_at | TIMESTAMP | |
|
||
|
||
---
|
||
|
||
## 6. 数据脱敏与安全
|
||
|
||
### 6.1 脱敏规则
|
||
|
||
- **移除**: 姓名、身份证号、手机号、地址、出生日期、医生姓名
|
||
- **泛化**: 精确年龄 → 年龄段 (18-30, 30-40, 40-50, ...)
|
||
- **保留**: 科室、检验指标、诊断编码、用药记录、家族史
|
||
|
||
### 6.2 安全防护层
|
||
|
||
1. **速率限制**: 患者端 10次/天/人,管理端 100次/小时/租户 (Redis Token Bucket)
|
||
2. **输入验证**: schema 校验,租户隔离校验
|
||
3. **Prompt 注入防护**: JSON 序列化注入,不做字符串拼接
|
||
4. **输出过滤**: 正则扫描处方药推荐/诊断结论,强制追加免责声明
|
||
5. **审计日志**: sanitized_input AES-256 加密存储,90 天自动清理
|
||
|
||
### 6.3 缓存设计
|
||
|
||
**缓存位置:** Redis
|
||
**缓存键:** `ai:cache:{tenant_id}:{analysis_type}:{input_data_hash}:{prompt_version}`
|
||
**TTL:** 7 天(可配置 `ai.cache_ttl_seconds`)
|
||
**Hash 计算:** 对 HealthDataProvider 返回的 DTO 做 canonical JSON 序列化后 SHA-256
|
||
**失效策略:** TTL 自然过期;Prompt 版本切换后旧缓存键自然不同
|
||
|
||
```
|
||
缓存命中 (Redis GET) → 直接返回完整 result_content (非流式,秒级)
|
||
缓存未命中 + AI 可用 → 正常 SSE 流式分析 → 完成后写入 Redis
|
||
缓存未命中 + AI 不可用 + 有旧版本 → 返回旧结果 + 标注"基于历史版本"
|
||
缓存未命中 + AI 不可用 + 无历史 → 本地规则引擎(50 条常见指标)
|
||
```
|
||
|
||
### 6.4 sanitized_input 数据生命周期
|
||
|
||
- 写入时: AES-256-GCM 加密后存储为 JSONB(密钥从环境变量加载)
|
||
- 查询时: 仅管理端有权限解密查看,且需 `ai.analysis.manage` 权限
|
||
- 清理: 后台定时任务每日扫描,超过 `sanitized_input_retention_days`(默认 90 天)的记录自动清空此字段
|
||
- 审计: 清理操作本身记录审计日志
|
||
|
||
---
|
||
|
||
## 7. API 设计
|
||
|
||
```
|
||
/api/v1/ai/
|
||
├── analyze/
|
||
│ ├── POST /lab-report SSE 化验单解读
|
||
│ ├── POST /trends SSE 健康趋势分析
|
||
│ ├── POST /checkup-plan SSE 个性化体检方案
|
||
│ └── POST /report-summary SSE 报告摘要生成
|
||
├── analysis/
|
||
│ ├── GET /history JSON 分析历史列表
|
||
│ └── GET /:id JSON 分析详情
|
||
├── prompts/ (需 ai.prompt.manage)
|
||
│ ├── GET / JSON Prompt 列表
|
||
│ ├── POST / JSON 新建 Prompt
|
||
│ ├── GET /:id JSON Prompt 详情
|
||
│ ├── PUT /:id JSON 编辑 (自动+1版本)
|
||
│ ├── POST /:id/activate JSON 激活指定版本
|
||
│ ├── GET /:id/versions JSON 版本历史
|
||
│ └── POST /:id/rollback JSON 回滚
|
||
├── usage/ (需 ai.usage.view)
|
||
│ ├── GET /stats JSON 用量统计
|
||
│ ├── GET /costs JSON 费用明细
|
||
│ └── GET /models JSON 模型分布
|
||
└── admin/
|
||
├── GET /providers JSON 提供商状态
|
||
├── PUT /providers/:name/config JSON 更新配置
|
||
└── POST /providers/:name/test JSON 连通性测试
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 前端设计
|
||
|
||
### 8.1 小程序 (Phase 1)
|
||
|
||
- 报告详情页新增「AI 智能解读」卡片,引导点击
|
||
- 新增 AI 解读展示页,SSE 逐字流式渲染
|
||
- 历史解读列表(点击查看过往分析)
|
||
|
||
### 8.2 Web 管理后台 (Phase 2)
|
||
|
||
- AI Prompt 管理页面:列表/编辑/版本历史/diff 对比/回滚
|
||
- AI 用量统计仪表盘:按天/模型/场景维度
|
||
- 提供商状态与配置管理
|
||
|
||
---
|
||
|
||
## 9. 扩展路线图
|
||
|
||
### Phase 1 — MVP (约 2-3 周)
|
||
|
||
- erp-ai crate 骨架 + ErpModule 实现
|
||
- AiProvider trait + Claude SSE 实现
|
||
- DeIdentificationService
|
||
- PromptManager + 3 张表 + migration
|
||
- 4 个分析端点
|
||
- 缓存 + 降级规则引擎
|
||
- 小程序 AI 解读功能
|
||
- 速率限制 + 审计日志
|
||
|
||
### Phase 2 — 运营强化 (约 2 周)
|
||
|
||
- Web Prompt 管理完整 UI
|
||
- 版本 diff 对比 + 回滚
|
||
- 用量统计仪表盘
|
||
- OpenAI 提供商实现
|
||
- 成本告警
|
||
- 分享功能
|
||
|
||
### Phase 3 — 混合管道 (约 2-3 周)
|
||
|
||
- AsyncAnalysisPipeline 实现
|
||
- EventBus 订阅 health 事件
|
||
- 后台预分析队列
|
||
- 消息中心通知集成
|
||
- WebSocket StreamingProvider 实现
|
||
- 本地模型对接 (Ollama/vLLM)
|
||
|
||
### Phase 4 — 智能化 (持续)
|
||
|
||
- Prompt A/B 测试
|
||
- 分析效果评分
|
||
- 多轮对话式解读
|
||
- 多模态输入 (OCR)
|
||
- 知识库 RAG
|
||
|
||
### Phase 1 架构预留点
|
||
|
||
| 预留点 | 位置 | 做法 |
|
||
|--------|------|------|
|
||
| AnalysisPipeline trait | pipeline/mod.rs | RequestPipeline 唯一实现,Phase 3 加 AsyncPipeline |
|
||
| StreamingProvider trait | provider/mod.rs | SSE 唯一实现,Phase 3 加 WebSocket |
|
||
| AnalysisContext.trigger | pipeline/mod.rs | 枚举 Request \| Event,Phase 3 加 Event |
|
||
| HealthDataProvider trait | erp-core | erp-health 实现,erp-ai 注入使用 |
|
||
| 事件发布 | service/analysis.rs | 完成后发布事件,Phase 1 无消费者也发布 |
|
||
| model_config 字段 | ai_prompts 表 | 每模板独立配置 provider/model |
|
||
|
||
---
|
||
|
||
## 10. 关键文件清单
|
||
|
||
| 区域 | 文件 | 操作 |
|
||
|------|------|------|
|
||
| `Cargo.toml` (workspace root) | `[workspace].members` + `[workspace.dependencies]` | 添加 erp-ai |
|
||
| erp-core | `src/health_provider.rs` | 新增 HealthDataProvider trait + DTO 定义 |
|
||
| erp-core | `src/events.rs` | 新增 ai.analysis.* 事件类型常量 |
|
||
| erp-core | `src/permission.rs` | 新增 ai.* 权限码 |
|
||
| erp-health | `src/health_provider_impl.rs` | 新增 HealthDataProvider trait impl |
|
||
| erp-ai | 整个 crate | 新建 |
|
||
| erp-server | `src/main.rs` | 合并 AiModule::public/protected_routes |
|
||
| erp-server | `src/state.rs` | 新增 `impl FromRef<AppState> for AiState` |
|
||
| erp-server | `src/config.rs` | 新增 AiConfig 结构 |
|
||
| erp-server | `config/default.toml` | 新增 `[ai]` 配置段 |
|
||
| erp-server/migration | 新增 migration | 3 张表 (ai_prompts, ai_analysis_results, ai_usage_logs) |
|
||
| erp-server/Cargo.toml | dependencies | 添加 erp-ai 依赖 |
|
||
| apps/miniprogram | 报告详情页 + 解读页 | 新增/修改 |
|
||
| apps/web | Prompt 管理页面 | Phase 2 新增 |
|
||
|
||
---
|
||
|
||
## 11. 验证方案
|
||
|
||
### 后端验证
|
||
- `cargo check` — 全 workspace 编译通过
|
||
- `cargo test -p erp-ai` — 单元测试覆盖 provider/sanitization/prompt/service
|
||
- 启动后端服务,通过 curl 测试 SSE 端点流式输出
|
||
- 通过 Swagger UI 测试 Prompt CRUD 和用量统计端点
|
||
|
||
### 小程序验证
|
||
- 打开报告详情页,确认「AI 解读」卡片展示
|
||
- 点击触发 SSE 流式渲染,确认逐字输出效果
|
||
- 测试缓存命中(第二次请求秒返回)
|
||
- 测试降级(停止 AI 服务后请求,确认降级提示)
|
||
|
||
### 安全验证
|
||
- 确认发送给 AI 的数据不包含 PII
|
||
- 确认速率限制生效(超过 10 次返回 429)
|
||
- 确认审计日志完整记录
|