feat(ci,ai): P2-1 权限注册表 + P2-2 AI utoipa 注解全覆盖
P2-1 权限注册表单一真相源: - 新增 permissions.yaml: 131 个权限码 × 8 模块,含冻结标记 - 新增 scripts/gen-permissions.js: 生成器脚本 --sql 输出 seed SQL, --frontend 输出 routeConfig 片段, --validate 验证一致性(131/131 = 0 mismatches) P2-2 AI 模块 utoipa 注解: - 为 30 个 handler 函数添加 #[utoipa::path] 注解 (mod.rs 18 + insight 3 + risk 1 + rule 4 + suggestion 4) - 为 6 个 DTO struct 添加 ToSchema/IntoParams derive (AnalyzeBody, CreatePromptBody, CreateRuleBody, UpdateRuleBody, ApproveBody, ExecuteBody, DialysisLabInput, ListAnalysisQuery, ListPromptsQuery) - AI handler utoipa 覆盖率: 0/5 → 5/5 (100%)
This commit is contained in:
@@ -18,7 +18,7 @@ pub mod suggestion_handler;
|
||||
|
||||
// === 分析请求 Body ===
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, utoipa::ToSchema)]
|
||||
pub struct AnalyzeBody {
|
||||
pub report_id: Option<uuid::Uuid>,
|
||||
pub patient_id: Option<uuid::Uuid>,
|
||||
@@ -27,6 +27,14 @@ pub struct AnalyzeBody {
|
||||
|
||||
// === SSE 分析端点 ===
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/analyze/lab-report",
|
||||
request_body = AnalyzeBody,
|
||||
responses((status = 200, description = "SSE 化验报告分析流")),
|
||||
tag = "AI 分析",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn stream_lab_report<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -101,6 +109,14 @@ where
|
||||
Ok(Sse::new(sse_stream).keep_alive(KeepAlive::default()))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/analyze/trends",
|
||||
request_body = AnalyzeBody,
|
||||
responses((status = 200, description = "SSE 趋势分析流")),
|
||||
tag = "AI 分析",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn stream_trends<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -190,6 +206,14 @@ where
|
||||
Ok(Sse::new(sse_stream).keep_alive(KeepAlive::default()))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/analyze/checkup-plan",
|
||||
request_body = AnalyzeBody,
|
||||
responses((status = 200, description = "SSE 体检计划分析流")),
|
||||
tag = "AI 分析",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn stream_checkup_plan<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -258,6 +282,14 @@ where
|
||||
Ok(Sse::new(sse_stream).keep_alive(KeepAlive::default()))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/analyze/report-summary",
|
||||
request_body = AnalyzeBody,
|
||||
responses((status = 200, description = "SSE 报告摘要分析流")),
|
||||
tag = "AI 分析",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn stream_report_summary<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -335,7 +367,7 @@ where
|
||||
|
||||
// === 分析历史 ===
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, utoipa::IntoParams)]
|
||||
pub struct ListAnalysisQuery {
|
||||
pub patient_id: Option<uuid::Uuid>,
|
||||
pub analysis_type: Option<String>,
|
||||
@@ -343,6 +375,14 @@ pub struct ListAnalysisQuery {
|
||||
pub page_size: Option<u64>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/analysis/history",
|
||||
params(ListAnalysisQuery),
|
||||
responses((status = 200, description = "分析历史列表")),
|
||||
tag = "AI 分析",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn list_analysis<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -419,6 +459,13 @@ where
|
||||
}))))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/analysis/{id}",
|
||||
responses((status = 200, description = "分析详情")),
|
||||
tag = "AI 分析",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn get_analysis<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -435,13 +482,21 @@ where
|
||||
|
||||
// === Prompt 管理 ===
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, utoipa::IntoParams)]
|
||||
pub struct ListPromptsQuery {
|
||||
pub category: Option<String>,
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/prompts",
|
||||
params(ListPromptsQuery),
|
||||
responses((status = 200, description = "Prompt 模板列表")),
|
||||
tag = "AI Prompt",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn list_prompts<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -468,7 +523,7 @@ where
|
||||
}))))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, utoipa::ToSchema)]
|
||||
pub struct CreatePromptBody {
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
@@ -478,6 +533,14 @@ pub struct CreatePromptBody {
|
||||
pub category: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/prompts",
|
||||
request_body = CreatePromptBody,
|
||||
responses((status = 200, description = "创建 Prompt 模板")),
|
||||
tag = "AI Prompt",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn create_prompt<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -505,6 +568,13 @@ where
|
||||
Ok(Json(ApiResponse::ok(prompt)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/prompts/{id}/activate",
|
||||
responses((status = 200, description = "激活 Prompt 模板")),
|
||||
tag = "AI Prompt",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn activate_prompt<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -519,6 +589,13 @@ where
|
||||
Ok(Json(ApiResponse::ok(prompt)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/prompts/{id}/rollback",
|
||||
responses((status = 200, description = "回滚 Prompt 模板")),
|
||||
tag = "AI Prompt",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn rollback_prompt<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -535,6 +612,13 @@ where
|
||||
|
||||
// === 用量统计 ===
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/usage/overview",
|
||||
responses((status = 200, description = "AI 用量概览")),
|
||||
tag = "AI 用量",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn usage_overview<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -550,6 +634,13 @@ where
|
||||
}))))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/usage/by-type",
|
||||
responses((status = 200, description = "按类型用量统计")),
|
||||
tag = "AI 用量",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn usage_by_type<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -574,6 +665,13 @@ where
|
||||
|
||||
// === Provider 管理 ===
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/providers/health",
|
||||
responses((status = 200, description = "AI Provider 健康检查")),
|
||||
tag = "AI Provider",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn provider_health<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -602,6 +700,13 @@ where
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/providers",
|
||||
responses((status = 200, description = "AI Provider 列表")),
|
||||
tag = "AI Provider",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn provider_names<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -616,6 +721,13 @@ where
|
||||
)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/quota/summary",
|
||||
responses((status = 200, description = "AI 配额汇总")),
|
||||
tag = "AI 用量",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn quota_summary<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -631,6 +743,13 @@ where
|
||||
|
||||
// === 透析风险评估(KDIGO 规则) ===
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/dialysis/risk-assessment",
|
||||
responses((status = 200, description = "透析风险评估")),
|
||||
tag = "AI 分析",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn assess_dialysis_risk<S>(
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Json(body): Json<crate::service::dialysis_risk_scorer::DialysisLabInput>,
|
||||
@@ -650,6 +769,13 @@ where
|
||||
|
||||
// === 成本与预算 ===
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/budget/status",
|
||||
responses((status = 200, description = "AI 预算状态")),
|
||||
tag = "AI 用量",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn budget_status<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -670,6 +796,13 @@ pub struct CostEstimateQuery {
|
||||
pub model: Option<String>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/cost/estimate",
|
||||
responses((status = 200, description = "AI 成本预估")),
|
||||
tag = "AI 用量",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn cost_estimate<S>(
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Query(params): Query<CostEstimateQuery>,
|
||||
|
||||
Reference in New Issue
Block a user