Files
fastapi/docs/03_api_design.md
T
2026-06-01 09:25:26 +08:00

673 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 后端 API 对接文档
## 1. 通用约定
Base URL
```text
http://127.0.0.1:8000/api/v1
```
必传 Header
| Header | 说明 |
|---|---|
| `X-User-Id` | 宿主系统传入的用户 ID,所有业务隔离依据 |
| `X-Entry-Scene` | 入口场景,前端 Demo 默认 `vue_demo` |
可传 Header
| Header | 说明 |
|---|---|
| `X-Tenant-Id` | 宿主系统租户/机构 ID |
| `X-Class-Id` | 教学班级 ID |
| `X-Role` | 用户角色 |
统一响应:
```json
{
"code": "OK",
"message": "success",
"data": {}
}
```
常见错误码:
| code | 含义 |
|---|---|
| `USER_ID_REQUIRED` | 缺少 `X-User-Id` |
| `CASE_NOT_FOUND` | 病例不存在或未启用 |
| `SESSION_NOT_FOUND` | 会话不存在或不属于当前用户 |
| `SESSION_STATUS_INVALID` | 当前阶段不允许该操作 |
| `INQUIRY_REQUIRED` | 完成问诊前至少需要一轮医生提问 |
| `DIAGNOSIS_REQUIRED` | 提交治疗前需要先提交诊断 |
| `TREATMENT_REQUIRED` | 生成评价前需要先提交治疗 |
| `ORDER_ITEM_NOT_FOUND` | 检查项目不存在 |
| `LLM_CALL_TIMEOUT` | LLM 普通调用超时 |
| `LLM_STREAM_TIMEOUT` | LLM 流式调用超时 |
| `LLM_STREAM_FAILED` | LLM 流式调用失败 |
| `CASE_SQL_FILE_INVALID` | 上传文件不是 `.sql` |
| `CASE_SQL_FILE_EMPTY` | 上传 SQL 文件为空 |
| `CASE_SQL_FILE_TOO_LARGE` | 上传 SQL 文件超过 5MB |
| `CASE_SQL_IMPORT_INVALID` | SQL 解析或字段映射校验失败 |
## 2. Agent Hello
| 项 | 内容 |
|---|---|
| Method | `GET` |
| Path | `/agent/hello` |
| Router | `agent.hello` |
| 用途 | 校验后端连接,返回当前用户上下文和功能开关 |
Response `data`
```json
{
"user": {
"user_id": "demo_user_001",
"tenant_id": null,
"role": null
},
"features": {
"stream_chat": true,
"score_types": ["percentage", "five_point"],
"pdf_export": true,
"knowledge_search": true,
"llm_mock_enabled": false,
"llm_fallback_to_mock": false
}
}
```
## 3. 病例列表
| 项 | 内容 |
|---|---|
| Method | `GET` |
| Path | `/cases` |
| Router | `cases.list_cases` |
| Service | `CaseService.list_cases` |
| 表 | `case_base` |
Query
| 参数 | 说明 |
|---|---|
| `department_id` | 科室筛选 |
| `training_type` | 训练类型筛选 |
| `mode` | `practice``teaching` |
Response `data`
```json
{
"items": [
{
"id": 1,
"title": "支气管肺炎 - 6岁男性患儿",
"department_id": 1,
"department_name": "儿科",
"difficulty": "medium",
"chief_complaint": "发热、咳嗽4天,喘息1天。",
"patient_age": 6,
"patient_gender": "male",
"training_type": "case_analysis",
"supported_modes": ["practice", "teaching"],
"has_teaching_video": false,
"has_knowledge_points": true
}
]
}
```
## 4. 病例详情
| 项 | 内容 |
|---|---|
| Method | `GET` |
| Path | `/cases/{case_id}` |
| Router | `cases.get_case_detail` |
| Service | `CaseService.get_case_detail` |
| 表 | `case_base``traditional_case``teaching_case``case_exam_item` |
Response `data`
```json
{
"id": 1,
"title": "支气管肺炎 - 6岁男性患儿",
"department_name": "儿科",
"chief_complaint": "发热、咳嗽4天,喘息1天。",
"patient_age": 6,
"patient_gender": "male",
"supported_modes": ["practice", "teaching"],
"exam_item_count": 5,
"has_knowledge_points": true
}
```
病例详情不返回标准答案、隐藏病史和完整评分细则。
## 5. 创建训练会话
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions` |
| Router | `sessions.create_session` |
| Service | `SessionService.create_session` |
| 表 | `training_session` |
Request
```json
{
"case_id": 1,
"training_type": "case_analysis",
"mode": "practice",
"score_type": "percentage"
}
```
Response `data`
```json
{
"session_id": 10,
"session_code": "sess_20260528100000_abcd1234",
"status": "inquiry",
"patient_opening": "家长:医生,孩子发烧咳嗽好几天了..."
}
```
校验:
- `case_id` 必须存在并启用。
- `mode` 当前使用 `practice``teaching`
- 创建会话时初始化短期 memory。
## 6. 普通问诊
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/chat` |
| Router | `sessions.chat` |
| Service | `SessionService.chat` |
| Agent | `PatientAgent.reply` |
Request
```json
{
"message": "孩子发热几天了?最高体温多少?"
}
```
Response `data`
```json
{
"reply": "发热有4天了,最高烧到39度多。",
"latency_ms": 2500,
"model": "deepseek-v4-pro",
"fallback_used": false
}
```
校验:会话必须属于当前 `X-User-Id`,且状态为 `inquiry`
## 7. 流式问诊
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/chat/stream` |
| Router | `sessions.chat_stream` |
| Service | `SessionService.stream_chat` |
| Agent | `PatientAgent.stream_reply` |
Request 同普通问诊。
SSE 事件:
```text
event: message_delta
data: {"delta":"发热有"}
event: message_done
data: {"latency_ms":3200,"first_token_ms":800,"model":"deepseek-v4-pro","fallback_used":false}
event: error
data: {"code":"LLM_STREAM_TIMEOUT","message":"AI 病人回复超时,请重试"}
```
前端收到 `message_done``error` 后必须结束 pending。
## 8. 查看提示
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/hints` |
| Router | `sessions.generate_hints` |
| Service | `SessionService.generate_hints` |
| Agent | `HintAgent.generate` |
Request
```json
{
"last_user_message": "孩子发热几天了?",
"scope": "current_conversation"
}
```
Response `data`
```json
{
"hints": ["可以继续追问最高体温、热型和退热药反应。"],
"missing_dimensions": ["既往史", "严重程度评估"],
"next_questions": ["孩子以前有没有喘息或哮喘史?"],
"recommended_orders": [
{"item_code": "oxygen_saturation", "reason": "用于判断缺氧和病情严重程度"}
]
}
```
校验:当前仅允许 `practice` 模式且会话状态为 `inquiry`
## 9. 检查项目列表
| 项 | 内容 |
|---|---|
| Method | `GET` |
| Path | `/sessions/{session_id}/order-items` |
| Router | `sessions.list_order_items` |
| Service | `OrderService.list_order_items` |
| 表 | `case_exam_item` |
Response `data`
```json
{
"items": [
{
"item_code": "complete_blood_count",
"item_name": "血常规",
"item_type": "lab",
"category": "实验室检查",
"ordered": false
}
]
}
```
## 10. 申请检查
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/orders` |
| Router | `sessions.create_order` |
| Service | `OrderService.create_order` |
| 表 | `training_order` |
Request
```json
{
"item_code": "complete_blood_count"
}
```
Response `data`
```json
{
"order_id": 1,
"item_code": "complete_blood_count",
"item_name": "血常规",
"result_text": "WBC 12.4×10^9/L,中性粒细胞72%。",
"result_structured": {},
"already_ordered": false,
"context_written": true
}
```
同一 `session_id + item_code` 幂等,重复申请返回已有记录,不重复写入 memory。
## 11. 完成问诊
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/complete-inquiry` |
| Router | `sessions.complete_inquiry` |
| Service | `SessionService.complete_inquiry` |
Response `data`
```json
{
"session_id": 10,
"status": "diagnosis"
}
```
校验:至少存在一轮医生提问。
## 12. 提交诊断
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/diagnosis` |
| Router | `sessions.submit_diagnosis` |
| Service | `SessionService.submit_diagnosis` |
| 表 | `training_submission` |
Request
```json
{
"primary_diagnosis": "支气管肺炎",
"differential_diagnoses": ["毛细支气管炎", "哮喘急性发作"],
"diagnosis_basis": "发热、咳嗽、喘息,结合肺部体征、炎症指标、胸片和血氧情况。"
}
```
Response `data`
```json
{
"status": "treatment"
}
```
## 13. 提交治疗
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/treatment` |
| Router | `sessions.submit_treatment` |
| Service | `SessionService.submit_treatment` |
| 表 | `training_submission` |
Request
```json
{
"treatment_principle": "抗感染、平喘、改善氧合、严密观察。",
"treatment_measures": "根据病情选择抗感染治疗,必要时雾化吸入,监测体温、呼吸和血氧。",
"risk_plan": "关注低氧、呼吸困难加重、持续高热、精神反应差。",
"communication": "向家属说明病情、用药注意事项和复诊/住院指征。",
"follow_up": "治疗后复查体温、呼吸、血氧和炎症指标。"
}
```
Response `data`
```json
{
"status": "evaluating"
}
```
## 14. 生成评价
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/sessions/{session_id}/evaluation` |
| Router | `sessions.create_evaluation` |
| Service | `EvaluationService.create_evaluation` |
| Agent | `ScoringAgent.score``ReportAgent` |
| 表 | `scoring_rule``knowledge_chunks``training_record` |
Request
```json
{
"score_type": "percentage"
}
```
Response `data`
```json
{
"evaluation_id": 1,
"score_type": "percentage",
"total_score": 82,
"dimension_scores": [],
"errors": [],
"improvement_plan": [],
"evidence_summary": [],
"guideline_refs": [],
"overall_comment": "完成了主要诊断链路,但检查利用和沟通细节仍需加强。"
}
```
评价完成后释放短期 memory,并写入 `training_record`
## 15. 历史评价列表
| 项 | 内容 |
|---|---|
| Method | `GET` |
| Path | `/evaluations` |
| Router | `evaluations.list_evaluations` |
| Service | `EvaluationService.list_history` |
| 表 | `training_record` |
Response `data`
```json
{
"items": [
{
"evaluation_id": 1,
"case_title": "支气管肺炎 - 6岁男性患儿",
"score_type": "percentage",
"total_score": 82,
"created_at": "2026-05-28T10:00:00",
"pdf_exported": true
}
]
}
```
## 16. 评价详情
| 项 | 内容 |
|---|---|
| Method | `GET` |
| Path | `/evaluations/{evaluation_id}` |
| Router | `evaluations.get_evaluation_detail` |
| Service | `EvaluationService.get_detail` |
| 表 | `training_record` |
Response `data` 为完整评价报告,并包含 `session_id``case_id``case_title``created_at``pdf_file_path`
## 17. 导出 PDF
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/evaluations/{evaluation_id}/export-pdf` |
| Router | `evaluations.export_pdf` |
| Service | `PdfExportService.export` |
| 表 | `training_record` |
Response `data`
```json
{
"export_id": 1,
"file_path": "storage/reports/training_record_1_percentage_xxx.pdf"
}
```
## 18. 知识检索
| 项 | 内容 |
|---|---|
| Method | `GET` |
| Path | `/knowledge/search` |
| Router | `knowledge.search_knowledge` |
| Service | `KnowledgeService.search_guidelines` |
| 表 | `knowledge_sources``knowledge_documents``knowledge_chunks` |
Query
| 参数 | 说明 |
|---|---|
| `department_id` | 科室 ID |
| `training_type` | 训练类型 |
| `q` | 关键词,逗号分隔 |
用途:评价生成前检索科室/类型下的评分指南。前端第一版不需要主动调用。
## 19. 病例 SQL 导入预检
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/imports/case-sql/preview` |
| Router | `imports.preview_case_sql` |
| Service | `CaseSqlImportService.preview` |
| 用途 | 前端上传接口解析后的 SQL 文件,后端只解析并校验,不写入数据库 |
Request
```text
Content-Type: multipart/form-data
file: case.sql
```
Response `data`
```json
{
"file_name": "case.sql",
"encoding": "utf-8",
"tables": {
"case_base": 1,
"traditional_case": 1,
"teaching_case": 1,
"scoring_rule": 5
},
"can_import": true,
"warnings": [],
"errors": [],
"preview_cases": [
{
"id": 1001,
"title": "儿童支气管肺炎",
"case_type": "diagnosis_treatment",
"difficulty": "medium"
}
]
}
```
校验逻辑:
- 必须携带 `X-User-Id`
- 文件后缀必须为 `.sql`,大小不超过 5MB。
- 只解析源 SQL 中的 `case_base``traditional_case``teaching_case``scoring_rule`
- 预检接口不执行源 SQL,不写入数据库。
- 源 SQL 中出现 `DROP TABLE``CREATE TABLE``ALTER TABLE` 等语句时只作为警告展示,导入器不会执行这些语句。
- 字段数量不匹配、非法字符串、JSON 字段非法时返回 `can_import=false``errors`
## 20. 病例 SQL 确认导入
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/imports/case-sql/apply` |
| Router | `imports.apply_case_sql` |
| Service | `CaseSqlImportService.apply` |
| 用途 | 预检通过后,将 SQL 中的病例源表数据映射写入当前数据库 |
| 表 | `case_base``traditional_case``teaching_case``scoring_rule``case_exam_item` |
Request
```text
Content-Type: multipart/form-data
file: case.sql
```
Response `data`
```json
{
"imported": true,
"file_name": "case.sql",
"encoding": "utf-8",
"inserted_or_updated_cases": 1,
"imported_traditional_cases": 1,
"imported_teaching_cases": 1,
"imported_scoring_rules": 5,
"generated_exam_items": 4,
"warnings": []
}
```
校验逻辑:
- 必须携带 `X-User-Id`
- 导入过程使用事务,任意表映射失败则整体回滚。
- `case_base``id` 更新或插入。
- `traditional_case``teaching_case``case_id` 更新或插入。
- `scoring_rule``case_id` 先删除旧规则再写入源规则。
- 源 SQL 缺少 `case_exam_item` 时,由后端根据病例文本生成基础检查项目,保障问诊训练链路可继续使用。
- 导入成功后前端刷新 `/cases`,新增病例即可进入训练。
## 21. LLM Fast 测试
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/llm/test/deepseek-fast` |
| Router | `llm_test.test_deepseek_fast` |
| Service | `OpenAICompatibleLLMClient.chat` |
Request
```json
{
"message": "请用一句话说明医疗问诊训练 Demo 的用途。"
}
```
Response `data`
```json
{
"model": "deepseek-v4-pro",
"first_token_ms": null,
"total_latency_ms": 3000,
"stream": false,
"mock_mode": false,
"fallback_used": false,
"thinking_enabled": false,
"reasoning_effort": null
}
```
## 22. LLM Reason 测试
| 项 | 内容 |
|---|---|
| Method | `POST` |
| Path | `/llm/test/deepseek-reason` |
| Router | `llm_test.test_deepseek_reason` |
| Service | `OpenAICompatibleLLMClient.stream_chat`,流式不兼容时降级到 `chat` |
Request 同 Fast 测试。Response 字段同 Fast 测试。