feat: 手术视频消耗、待确认与持久化改造
- 新增 Alembic 初始迁移、领域明细模型及归档持久化与重试链路\n- 拆分视频会话注册表、分类处理、推理时间窗聚合与流处理\n- 消耗日志:TSV/Markdown 含 top2/top3;item_id 优先产品编码;待确认记「待确认」行,语音确认后落正式行并更新汇总\n- 待确认时内存/DB 明细为占位行,确认后替换;拒绝时移除占位\n- 分类 probs 先 detach/cpu 再转 NumPy,修复 MPS/CUDA 上推理被静默跳过\n- 补充集成测试、归档与设备张量等单测 Made-with: Cursor
This commit is contained in:
@@ -1,213 +0,0 @@
|
||||
# 手术室监控服务 — 客户端 HTTP API 对接说明
|
||||
|
||||
本文档面向**集成我方 FastAPI 后端的客户系统**(HIS、手麻、护理工作站、自研终端等),说明如何通过 HTTP 调用完成「手术开始 / 结束」「耗材结果查询」「低置信度耗材语音确认」等能力。
|
||||
|
||||
> **说明**:仓库内 `scripts/demo_client/` 等演示页面仅用于**内部联调与测试**,不代表生产对接规范;生产环境请按本文档与 OpenAPI 契约自行实现客户端。
|
||||
|
||||
---
|
||||
|
||||
## 1. 服务与发现
|
||||
|
||||
|
||||
| 项目 | 说明 |
|
||||
| ------- | ------------------------------------------------------------- |
|
||||
| 默认监听 | 应用默认 `0.0.0.0:38080`(以实际部署为准,可能经反向代理改写路径或端口) |
|
||||
| 基础路径 | 路由**无全局前缀**;下文路径均为相对服务根路径 |
|
||||
| OpenAPI | 服务启动后可访问 `**/docs`(Swagger UI)**、`**/redoc`** 获取实时 Schema 与试调 |
|
||||
| 健康检查 | `GET /health`:探活与数据库连通性(降级时可能返回 503) |
|
||||
| 跨域 CORS | 仅当服务端开启演示用 CORS 配置时对浏览器页面生效;**服务端对接通常不受 CORS 限制** |
|
||||
|
||||
|
||||
认证方式以部署约定为准;当前公开路由未在文档层强制 API Key,若贵方环境增加了网关鉴权,请在请求头中按网关要求携带。
|
||||
|
||||
---
|
||||
|
||||
## 2. 客户端 API 一览(`/client/...`)
|
||||
|
||||
所有「客户集成」接口均位于 `**/client`** 命名空间下。
|
||||
|
||||
|
||||
| 方法 | 路径 | 摘要 |
|
||||
| ------ | ------------------------------------------------------------------------------- | ------------------------- |
|
||||
| `POST` | `/client/surgeries/start` | 开始手术:确认摄像头开录成功后返回 |
|
||||
| `POST` | `/client/surgeries/end` | 结束手术:确认停录成功后返回 |
|
||||
| `GET` | `/client/surgeries/{surgery_id}/result` | 查询该台手术的耗材明细与汇总 |
|
||||
| `GET` | `/client/surgeries/{surgery_id}/pending-confirmation` | 拉取一条待确认项(含 TTS 音频 Base64) |
|
||||
| `POST` | `/client/surgeries/{surgery_id}/pending-confirmation/{confirmation_id}/resolve` | 上传医生语音 WAV,完成确认或否认 |
|
||||
|
||||
|
||||
路径参数 `**surgery_id`**:固定 **6 位数字**(正则 `^\d{6}$`)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 端到端业务流程(推荐时序)
|
||||
|
||||
以下为客户系统与手术室监控服务之间的**推荐调用顺序**与并行关系。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as 客户系统
|
||||
participant API as 监控服务 API
|
||||
|
||||
Client->>API: POST /client/surgeries/start
|
||||
Note over Client,API: body: surgery_id, camera_ids, candidate_consumables
|
||||
API-->>Client: 200 accepted(开录已确认)
|
||||
|
||||
par 术中轮询
|
||||
loop 按需轮询
|
||||
Client->>API: GET .../result
|
||||
API-->>Client: 200 明细+汇总 或 503 RESULT_NOT_READY
|
||||
end
|
||||
loop 语音确认闭环(若启用)
|
||||
Client->>API: GET .../pending-confirmation
|
||||
API-->>Client: 200 待确认+MP3 或 404 无待确认
|
||||
opt 有待确认
|
||||
Client->>Client: 播放 prompt_audio_mp3_base64
|
||||
Client->>API: POST .../resolve(multipart audio)
|
||||
API-->>Client: 200 accepted
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Client->>API: POST /client/surgeries/end
|
||||
API-->>Client: 200 accepted(停录已确认)
|
||||
|
||||
Client->>API: GET .../result
|
||||
API-->>Client: 200 归档后持久化结果(若可查)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### 3.1 手术生命周期(状态视角)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[未开始] -->|POST start 成功| B[录制中 / 推理中]
|
||||
B -->|GET result 可 200| C[可查消耗]
|
||||
B -->|GET pending 可 200| D[有待确认]
|
||||
D -->|POST resolve| B
|
||||
B -->|POST end 成功| E[已结束]
|
||||
E -->|GET result| C
|
||||
```
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口说明与请求/响应要点
|
||||
|
||||
### 4.1 `POST /client/surgeries/start`
|
||||
|
||||
**Content-Type**:`application/json`
|
||||
|
||||
**请求体(摘要)**
|
||||
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ----------------------- | -------- | ------------------------------------------------------------- |
|
||||
| `surgery_id` | string | 6 位数字手术号 |
|
||||
| `camera_ids` | string[] | 至少 1 个;需与贵方 RTSP/SDK 映射中的 **摄像头 ID** 一致 |
|
||||
| `candidate_consumables` | string[] | 可选;**本台手术可能用到的耗材名称清单**。服务端仅对该清单内耗材做自动记账与待确认追问;为空则只做拉流推理、不写入消耗 |
|
||||
|
||||
|
||||
**成功(200)**:`SurgeryApiResponse` — `status` 一般为 `accepted`,表示**服务端已确认开录完成**。
|
||||
|
||||
**失败**:常见 `**503 Service Unavailable`**,`detail` 内含业务 `code`(如录制子系统未就绪、开录未确认等)。开录/停录类错误会按服务端配置**自动重试**;仍失败则返回最后一次错误信息。
|
||||
|
||||
### 4.2 `POST /client/surgeries/end`
|
||||
|
||||
**Content-Type**:`application/json`
|
||||
|
||||
**请求体**:`{ "surgery_id": "123456" }`
|
||||
|
||||
**成功(200)**:停录已确认。
|
||||
|
||||
**失败**:`503` 同 start,表示未能在确认摄像头全部停录后完成请求。
|
||||
|
||||
### 4.3 `GET /client/surgeries/{surgery_id}/result`
|
||||
|
||||
**幂等只读**。术中返回当前内存已记账结果;结束后返回数据库持久化结果(以服务端实现为准)。
|
||||
|
||||
**成功(200)**:`SurgeryResultResponse`
|
||||
|
||||
- `details`:多行消耗明细,字段顺序为 `**item_id` → `item_name` → `qty` → `doctor_id` → `timestamp`**
|
||||
- `summary`:按 `item_id` 汇总的 `total_quantity`
|
||||
|
||||
`**503`**:`detail.code === "RESULT_NOT_READY"` — 尚无该手术可查询结果(未开始、未成功开录或暂无可返回数据)。
|
||||
|
||||
### 4.4 `GET /client/surgeries/{surgery_id}/pending-confirmation`
|
||||
|
||||
用于**低置信度识别**的人工确认闭环(需服务端启用语音确认及相关配置)。
|
||||
|
||||
**成功(200)**:包含 `confirmation_id`、`prompt_text`、候选项 `options`、`prompt_audio_mp3_base64`(与话术一致的 **MP3 标准 Base64**,无换行)、模型 Top1 等。
|
||||
|
||||
`**404`**:当前无待确认或手术未在进行(`NO_PENDING_CONFIRMATION`)。
|
||||
|
||||
`**422` / `503`**:话术为空、音频无效、ASR/TTS/MinIO/百度语音等异常时返回,详见响应 `detail.code`。
|
||||
|
||||
### 4.5 `POST /client/surgeries/{surgery_id}/pending-confirmation/{confirmation_id}/resolve`
|
||||
|
||||
**Content-Type**:`multipart/form-data`
|
||||
|
||||
**表单字段**
|
||||
|
||||
|
||||
| 字段 | 说明 |
|
||||
| ------- | -------------------------------------------------------- |
|
||||
| `audio` | 单个 `**.wav`** 文件;建议 16 kHz 单声道 PCM;其他格式服务端可能尝试 ffmpeg 转码 |
|
||||
|
||||
|
||||
**成功(200)**:`SurgeryPendingConfirmationResolveResponse` — 含 `resolved_label`、`rejected`、`asr_text`、`audio_object_key` 等。
|
||||
|
||||
**常见 HTTP 状态**:`404`(确认项不存在或已失效)、`409`(已处理)、`422`(音频/解析问题)、`503`(外部依赖故障)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 错误约定
|
||||
|
||||
FastAPI 对 `HTTPException` 的 JSON 外形一般为:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": {
|
||||
"code": "业务错误码",
|
||||
"message": "人类可读说明",
|
||||
"surgery_id": "123456"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
部分错误可能在 `detail` 中附带额外字段(如重试剩余次数),请以实际响应与 OpenAPI 为准。
|
||||
|
||||
**校验错误(422)**:请求体或路径不符合 Pydantic 校验时,FastAPI 可能返回标准 `422` 验证错误结构(与上表「对象型 detail」不同),请客户端分别解析。
|
||||
|
||||
---
|
||||
|
||||
## 6. 与内部演示能力的关系(非客户必选)
|
||||
|
||||
以下路由用于**内部一键联调**,客户生产系统**无需依赖**:
|
||||
|
||||
|
||||
| 路径 | 说明 |
|
||||
| ------------------------------------------- | -------------------------------------------------- |
|
||||
| `GET /internal/demo/orchestrator-status` | 探测演示编排是否开启、RTSP 配置文件是否设置等 |
|
||||
| `POST /internal/demo/orchestrate-and-start` | 仅在服务端开启 `DEMO_ORCHESTRATOR_ENABLED` 时注册;用于演示环境串联开录 |
|
||||
|
||||
|
||||
客户正式对接应直接调用 `**/client/...`**,并在贵方环境中配置真实的 `camera_ids` 与视频后端映射。
|
||||
|
||||
---
|
||||
|
||||
## 7. 联调建议
|
||||
|
||||
1. 先调用 `**GET /health`** 确认服务与数据库可用。
|
||||
2. 用 `**POST /client/surgeries/start`** 验证 `camera_ids` 与现场 RTSP/SDK 配置一致,避免 503。
|
||||
3. `**candidate_consumables**` 与实际手术耗材名称尽量与院内目录或模型标签对齐,减少待确认次数。
|
||||
4. 结果查询 `**503**` 时建议**退避重试**(术中数据尚未就绪属正常现象)。
|
||||
5. 以 `**/docs`** 导出或对照契约测试,与贵方 CI 中的契约测试对齐。
|
||||
|
||||
---
|
||||
|
||||
## 8. 文档修订
|
||||
|
||||
接口行为以部署实例的 **OpenAPI(`/docs`)** 与代码为准;字段含义补充见仓库内 `app/schemas.py` 中各模型的 `description`。
|
||||
@@ -4,42 +4,37 @@
|
||||
|
||||
## 环境
|
||||
|
||||
- [ ] `GET /health` 返回 `200`,`database: connected`
|
||||
- [ ] 环境变量:`VIDEO_RTSP_URLS_JSON` 或 `VIDEO_RTSP_URLS_JSON_FILE` 与客户端 `camera_ids` 一致
|
||||
- [ ] `MINIO_*`、`BAIDU_SPEECH_*` 已配置(语音确认链路)
|
||||
- [ ] 模型权重路径可读(容器内挂载 `app/resources/*.pt`)
|
||||
- `GET /health` 返回 `200`,`database: connected`
|
||||
- 环境变量:`VIDEO_RTSP_URLS_JSON` 或 `VIDEO_RTSP_URLS_JSON_FILE` 与客户端 `camera_ids` 一致
|
||||
- `MINIO_`*、`BAIDU_SPEECH_*` 已配置(语音确认链路)
|
||||
- 模型权重路径可读(容器内挂载 `app/resources/*.pt`)
|
||||
|
||||
## 主流程
|
||||
|
||||
1. **开始手术** `POST /client/surgeries/start`
|
||||
- [ ] 请求体含 6 位 `surgery_id`、`camera_ids`、`candidate_consumables`(非空才会记账)
|
||||
- [ ] 返回 `200`,日志中各路 RTSP 首帧就绪
|
||||
|
||||
2. **进行中查询(可选)** `GET /client/surgeries/{id}/result`
|
||||
- [ ] 在已有至少一条明细后返回 `200`;仅开录尚无明细时可能 `503 RESULT_NOT_READY`(与实现一致)
|
||||
|
||||
3. **低置信追问**
|
||||
- [ ] `GET /client/surgeries/{id}/pending-confirmation` 有任务时 `200`,含 `prompt_text`、`options`
|
||||
- [ ] 客户端对 `prompt_text` **TTS 播报**,采集医生回答为 **WAV**
|
||||
- [ ] `POST .../pending-confirmation/{confirmation_id}/resolve`,`multipart` 字段名 `audio`
|
||||
- [ ] 确认后明细中出现 `source=voice`;否认不增加明细
|
||||
|
||||
4. **结束手术** `POST /client/surgeries/end`
|
||||
- [ ] 返回 `200`,摄像头任务停止
|
||||
|
||||
5. **最终结果** `GET /client/surgeries/{id}/result`
|
||||
- [ ] 返回 `200`,`details` / `summary` 与术中所见一致
|
||||
|
||||
6. **数据库**
|
||||
- [ ] `surgery_final_results` / `surgery_result_details` 有对应 `surgery_id`
|
||||
- [ ] `voice_confirmation_audits` 在语音确认路径有追溯行(成功/失败分支视联调覆盖而定)
|
||||
1. **开始手术** `POST /client/surgeries/start`
|
||||
- 请求体含 6 位 `surgery_id`、`camera_ids`;`candidate_consumables` 可空(空则全量目录/模型类名)
|
||||
- 返回 `200`,日志中各路 RTSP 首帧就绪
|
||||
2. **进行中查询(可选)** `GET /client/surgeries/{id}/result`
|
||||
- 至少一条明细时 `200`;无明细(开录后尚无、已归档但零消耗等)为 `503 RESULT_NOT_READY`
|
||||
3. **低置信追问**
|
||||
- `GET /client/surgeries/{id}/pending-confirmation` 有任务时 `200`,含 `prompt_text`、`options`
|
||||
- 客户端对 `prompt_text` **TTS 播报**,采集医生回答为 **WAV**
|
||||
- `POST .../pending-confirmation/{confirmation_id}/resolve`,`multipart` 字段名 `audio`
|
||||
- 确认后明细中出现 `source=voice`;否认不增加明细
|
||||
4. **结束手术** `POST /client/surgeries/end`
|
||||
- 返回 `200`,摄像头任务停止
|
||||
5. **最终结果** `GET /client/surgeries/{id}/result`
|
||||
- 有明细时 `200`,`details` / `summary` 与术中所见一致;整台手术无任何明细时为 `503 RESULT_NOT_READY`
|
||||
6. **数据库**
|
||||
- `surgery_final_results` / `surgery_result_details` 有对应 `surgery_id`
|
||||
- `voice_confirmation_audits` 在语音确认路径有追溯行(成功/失败分支视联调覆盖而定)
|
||||
|
||||
## 失败与重试(抽样)
|
||||
|
||||
- [ ] RTSP 不可达:`start` 最终 `503`,消息含开录失败说明
|
||||
- [ ] MinIO 不可用:`resolve` 返回 `503` 或业务码 `MINIO_*`
|
||||
- [ ] 停录后写库失败:服务日志提示归档;后台重试或修复 DB 后可再次 `start` 同号前会先尝试落归档(见接口说明)
|
||||
- RTSP 不可达:`start` 最终 `503`,消息含开录失败说明
|
||||
- MinIO 不可用:`resolve` 返回 `503` 或业务码 `MINIO_`*
|
||||
- 停录后写库失败:服务日志提示归档;后台重试或修复 DB 后可再次 `start` 同号前会先尝试落归档(见接口说明)
|
||||
|
||||
## 与文档
|
||||
|
||||
- 客户端集成以 **OpenAPI**(`/docs`)与 [客户端手术通信接口说明](./客户端手术通信接口说明.md) 为准;**待确认 resolve 为 multipart WAV**,非 JSON `chosen_label`。
|
||||
- 客户端集成以 **OpenAPI**(`/docs`)与 [客户端手术通信接口说明](./客户端手术通信接口说明.md) 为准;**待确认 resolve 为 multipart WAV**,非 JSON `chosen_label`。
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
## RTSP 模式(默认)
|
||||
|
||||
1. 配置 **`camera_id` → RTSP URL** 映射,任选其一或组合使用:
|
||||
- **`VIDEO_RTSP_URLS_JSON_FILE`**:指向 UTF-8 JSON 文件(对象键为与请求一致的 `camera_id`)。仓库示例:[`app/resources/camera_rtsp_urls.sample.json`](../app/resources/camera_rtsp_urls.sample.json)(示例 ID:`or-cam-01`、`or-cam-02`)。
|
||||
- **`VIDEO_RTSP_URLS_JSON`**:内联 JSON 字符串;与文件合并时**覆盖同键**。
|
||||
- **`VIDEO_RTSP_URL_TEMPLATE`**:单模板,可用 `{camera_id}`。
|
||||
1. 配置 `**camera_id` → RTSP URL** 映射,任选其一或组合使用:
|
||||
- `**VIDEO_RTSP_URLS_JSON_FILE`**:指向 UTF-8 JSON 文件(对象键为与请求一致的 `camera_id`)。仓库示例:`[app/resources/camera_rtsp_urls.sample.json](../app/resources/camera_rtsp_urls.sample.json)`(示例 ID:`or-cam-01`、`or-cam-02`)。
|
||||
- `**VIDEO_RTSP_URLS_JSON**`:内联 JSON 字符串;与文件合并时**覆盖同键**。
|
||||
- `**VIDEO_RTSP_URL_TEMPLATE`**:单模板,可用 `{camera_id}`。
|
||||
2. 调用 `POST /client/surgeries/start` 时,`camera_ids` 必须能在上述配置中解析出 RTSP 地址。
|
||||
3. **开录确认**:每路摄像头在超时内成功打开并读到**首帧**后,才认为该路已开录。
|
||||
|
||||
@@ -26,20 +26,20 @@ SDK **不作为构建期依赖**:将厂商提供的 Linux x86_64 动态库挂
|
||||
行为概要:
|
||||
|
||||
1. 进程内对 `NET_DVR_Init` 使用引用计数;每路使用 SDK 的工作线程在登录后 `NET_DVR_Logout`,线程结束时配对 `NET_DVR_Cleanup`。
|
||||
2. 若 `HIKVISION_SDK_FALLBACK_TO_RTSP=true`(默认),在**无法加载动态库**、**登录失败**或**未配置凭据**时,自动回退到 `VIDEO_RTSP_*` 映射拉流。
|
||||
2. 若 `HIKVISION_SDK_FALLBACK_TO_RTSP=true`(默认),在**无法加载动态库**、**登录失败**或**未配置凭据**时,自动回退到 `VIDEO_RTSP_`* 映射拉流。
|
||||
|
||||
**注意**:`NET_DVR_Login_V30` 的设备信息结构体在不同 SDK 版本上可能存在差异;若登录异常,请优先使用 RTSP 回退或按厂商文档校对 ctypes 绑定。
|
||||
|
||||
## 推理与结果查询
|
||||
|
||||
- 开录后按 `VIDEO_INFERENCE_INTERVAL_SEC` 抽帧,依次调用耗材分类与撕扯动作模型。
|
||||
- **候选耗材清单**(开始手术请求体中的 `candidate_consumables`)为**硬约束**:若为空,服务端**不会**写入任何消耗明细(仅拉流推理);非空时仅允许清单内标签自动记账。
|
||||
- **候选耗材清单**(`candidate_consumables`):非空时**仅**清单内名称参与自动记账与待确认;**缺省或 `[]`** 时,用耗材目录 Excel **全部商品名**作为候选;无目录则用分类模型**全部类名**。
|
||||
- 当分类 Top1 置信度 **≥** `VIDEO_AUTO_CONFIRM_CONFIDENCE`(**默认 0.9**)且标签在候选清单内时,自动写入一条 `source=vision` 的消耗明细;**低于**该线的识别需人工确认(在语音下沿之上且能展示候选项时入队)。
|
||||
- 置信度在 \[`VIDEO_VOICE_CONFIRM_MIN_CONFIDENCE`, `VIDEO_AUTO_CONFIRM_CONFIDENCE`\) 等区间且存在可向医生展示的候选时,会生成**待确认**任务;客户端 `GET /client/surgeries/{surgery_id}/pending-confirmation`,确认后 `POST .../pending-confirmation/{id}/resolve` 等。
|
||||
- 已有至少一条消耗明细后,`GET /client/surgeries/{surgery_id}/result` 返回 200;若已开录但尚未产生任何明细,返回 503 `RESULT_NOT_READY`。
|
||||
- 置信度在 `VIDEO_VOICE_CONFIRM_MIN_CONFIDENCE`, `VIDEO_AUTO_CONFIRM_CONFIDENCE` 等区间且存在可向医生展示的候选时,会生成**待确认**任务;客户端 `GET /client/surgeries/{surgery_id}/pending-confirmation`,确认后 `POST .../pending-confirmation/{id}/resolve` 等。
|
||||
- `GET /client/surgeries/{surgery_id}/result` 仅在存在**至少一条**消耗明细时返回 200;无明细(已开录但尚未记账、已结束但零消耗、或尚无归档等)返回 503 `RESULT_NOT_READY`。
|
||||
- 同类物品写入受 `VIDEO_DETAIL_COOLDOWN_SEC` 节流。
|
||||
- RTSP 读帧连续失败达到 `VIDEO_READ_FAILURE_RECONNECT_THRESHOLD` 时会 `release` 并尝试重连,间隔 `VIDEO_RECONNECT_BACKOFF_SECONDS`。
|
||||
|
||||
## 相关环境变量
|
||||
|
||||
详见仓库根目录 `.env.example` 中「视频:RTSP + 可选海康 HCNetSDK」一节。
|
||||
详见仓库根目录 `.env.example` 中「视频:RTSP + 可选海康 HCNetSDK」一节。
|
||||
@@ -1,86 +1,320 @@
|
||||
# 手术室监控服务 · 客户端手术通信接口说明
|
||||
<style>
|
||||
/*
|
||||
* Obsidian 单文件修复:
|
||||
* 只复制本 Markdown 到 Obsidian 时,也尽量保证中文在预览和 PDF 导出中可见。
|
||||
*/
|
||||
|
||||
本文档描述客户端与手术室监控服务端之间,围绕「单台手术生命周期」进行通信的 HTTP 接口。便于联调、评审与对外同步;与 OpenAPI(Swagger)中的定义一致。
|
||||
.markdown-rendered,
|
||||
.markdown-preview-view,
|
||||
.markdown-source-view.mod-cm6 .cm-contentContainer,
|
||||
.markdown-source-view.mod-cm6 .cm-scroller,
|
||||
.mermaid,
|
||||
.mermaid svg,
|
||||
.mermaid svg text,
|
||||
.mermaid svg tspan,
|
||||
.mermaid svg foreignObject {
|
||||
font-family:
|
||||
"PingFang SC",
|
||||
"Hiragino Sans GB",
|
||||
"Noto Sans CJK SC",
|
||||
"Source Han Sans SC",
|
||||
"Microsoft YaHei",
|
||||
sans-serif !important;
|
||||
font-variant-ligatures: none !important;
|
||||
}
|
||||
|
||||
---
|
||||
.markdown-rendered code,
|
||||
.markdown-rendered pre,
|
||||
.markdown-preview-view code,
|
||||
.markdown-preview-view pre,
|
||||
code,
|
||||
pre,
|
||||
kbd,
|
||||
samp {
|
||||
font-family:
|
||||
"Sarasa Mono SC",
|
||||
"Maple Mono SC",
|
||||
"SF Mono",
|
||||
"Menlo",
|
||||
"Monaco",
|
||||
"Consolas",
|
||||
"PingFang SC",
|
||||
monospace !important;
|
||||
font-variant-ligatures: none !important;
|
||||
}
|
||||
|
||||
## 1. 概述
|
||||
@media print {
|
||||
body,
|
||||
.markdown-preview-view,
|
||||
.markdown-rendered,
|
||||
.markdown-preview-section,
|
||||
.markdown-rendered p,
|
||||
.markdown-rendered li,
|
||||
.markdown-rendered blockquote,
|
||||
.markdown-rendered table,
|
||||
.markdown-rendered thead,
|
||||
.markdown-rendered tbody,
|
||||
.markdown-rendered tr,
|
||||
.markdown-rendered th,
|
||||
.markdown-rendered td,
|
||||
.markdown-rendered strong,
|
||||
.markdown-rendered em,
|
||||
.markdown-rendered span,
|
||||
.markdown-rendered div,
|
||||
.markdown-rendered h1,
|
||||
.markdown-rendered h2,
|
||||
.markdown-rendered h3,
|
||||
.markdown-rendered h4,
|
||||
.markdown-rendered h5,
|
||||
.markdown-rendered h6,
|
||||
.markdown-rendered .callout,
|
||||
.markdown-rendered .callout-title,
|
||||
.markdown-rendered .callout-content,
|
||||
.mermaid,
|
||||
.mermaid svg,
|
||||
.mermaid svg text,
|
||||
.mermaid svg tspan,
|
||||
.mermaid svg foreignObject {
|
||||
font-family:
|
||||
"PingFang SC",
|
||||
"Hiragino Sans GB",
|
||||
"Noto Sans CJK SC",
|
||||
"Source Han Sans SC",
|
||||
"Microsoft YaHei",
|
||||
sans-serif !important;
|
||||
font-variant-ligatures: none !important;
|
||||
}
|
||||
|
||||
code,
|
||||
pre,
|
||||
kbd,
|
||||
samp,
|
||||
.markdown-rendered code,
|
||||
.markdown-rendered pre,
|
||||
.markdown-rendered pre code,
|
||||
.markdown-rendered .inline-code {
|
||||
font-family:
|
||||
"Sarasa Mono SC",
|
||||
"Maple Mono SC",
|
||||
"SF Mono",
|
||||
"Menlo",
|
||||
"Monaco",
|
||||
"Consolas",
|
||||
"PingFang SC",
|
||||
monospace !important;
|
||||
font-variant-ligatures: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
| 能力 | 说明 |
|
||||
| ----- | ------------------------------------------------------ |
|
||||
| 开始手术 | 请求开始手术;服务端启动摄像头录制,**仅在确认开录完成后**返回 HTTP 200。 |
|
||||
| 结束手术 | 请求结束手术;服务端停止摄像头录制,**仅在确认停录完成后**返回 HTTP 200。 |
|
||||
| 查询结果 | 根据手术 6 位号查询消耗明细与汇总;**仅在已开录且至少已有一条消耗明细后**返回 HTTP 200。 |
|
||||
| 待确认耗材 | 低置信度时服务端排队一条待确认任务;客户端拉取话术(TTS)并在医生确认后回传,**不阻塞**后续视频推理。 |
|
||||
# 手术室监控服务:客户端手术通信接口说明
|
||||
|
||||
面向对接本 FastAPI 服务的客户端(HIS、手麻、工作站等)。字段、状态码与返回模型以 `/docs` 和 `/openapi.json` 为准,本文用于摘要和流程说明。
|
||||
|
||||
**约定:**
|
||||
> [!summary] 常用响应模型
|
||||
> - `SurgeryApiResponse`
|
||||
> - `SurgeryResultResponse`
|
||||
> - `SurgeryPendingConfirmationResponse`
|
||||
> - 业务错误外形见 `SurgeryClientErrorResponse`
|
||||
> - 内部演示页:`scripts/demo_client/`(仅供演示,不作为对外契约)
|
||||
|
||||
- **开始 / 结束** 使用 `POST`,请求体为 **JSON**(`Content-Type: application/json`)。
|
||||
- **查询结果** 使用 `GET`,**无请求体**;手术号放在 **URL 路径** 中(见 4.3),符合「只读资源用 GET」的惯例。
|
||||
- 手术标识 `**surgery_id`**:必须为 **恰好 6 位数字**(正则 `^\d{6}$`),例如 `123456`。
|
||||
## 能力概览
|
||||
|
||||
---
|
||||
- 探活:`GET /health`,用于检查进程和数据库状态,详见 5.1 节。
|
||||
- 开始手术:`POST /client/surgeries/start`,只有在开录确认成功后才返回 `200`。
|
||||
- 结束手术:`POST /client/surgeries/end`,只有在停录确认成功后才返回 `200`。
|
||||
- 查询结果:`GET /client/surgeries/{surgery_id}/result`,至少存在一条消耗明细时返回 `200`;否则返回 `503`,常见错误码为 `RESULT_NOT_READY`。
|
||||
- 待确认播报:`GET /client/surgeries/{surgery_id}/pending-confirmation`,拉取队首低置信度任务,返回话术文本和 MP3 Base64。
|
||||
- 待确认答复:`POST /client/surgeries/{surgery_id}/pending-confirmation/{confirmation_id}/resolve`,上传医生答复的 WAV 录音,服务端完成 ASR 后入账或关闭。该录音与播报音频无关。
|
||||
|
||||
## 2. 基础信息
|
||||
> [!important] HTTP 约定
|
||||
> - `start` 和 `end` 使用 `POST` + `application/json`
|
||||
> - `result` 使用 `GET`
|
||||
> - `resolve` 使用 `POST` + `multipart/form-data`
|
||||
> - `surgery_id` 固定为 6 位数字,正则为 `^\d{6}$`
|
||||
> - `resolve` 路径中的 `confirmation_id` 必须与待确认接口返回值一致
|
||||
> - `camera_ids` 必须与第 2 节清单及运维配置完全一致
|
||||
|
||||
## 1. 服务与基础信息
|
||||
|
||||
| 项目 | 说明 |
|
||||
| ---------- | ------------------------------------------------------- |
|
||||
| 协议 | HTTP/HTTPS |
|
||||
| 请求体格式 | 开始/结束:`application/json`;查询结果:无 body |
|
||||
| 响应体格式 | JSON |
|
||||
| 路径前缀 | 服务端根路径下直接挂载,例如 `https://<主机>:<端口>/client/surgeries/...` |
|
||||
| 默认服务端口(开发) | `38080`(以实际部署为准) |
|
||||
- 协议:`HTTP/HTTPS`
|
||||
- 端口:`38080`,生产环境以实际入口为准
|
||||
- 路由:无全局前缀;业务接口位于 `/client/...`,健康检查位于 `/health`
|
||||
- `start` / `end` 请求体:JSON
|
||||
- `resolve` 请求体:`multipart/form-data`,字段名为 `audio`
|
||||
- 在线文档:`/docs`、`/redoc`
|
||||
|
||||
## 2. 摄像头 ID 与 RTSP
|
||||
|
||||
> **说明:** 若生产环境存在网关或反向代理,请将上表中的「主机、端口、是否 HTTPS」替换为对外统一入口地址。
|
||||
RTSP 地址、账号、口令等由客户端对接工程师提供给服务端运维,运维再写入服务端环境(例如 JSON 映射或环境变量)。业务程序不在客户端保存 RTSP,客户端只在 `POST /client/surgeries/start` 中传 `camera_ids`。
|
||||
|
||||
---
|
||||
配置格式示例见 `app/resources/camera_rtsp_urls.sample.json`。配置项细节见 `.env.example` 与 `docs/video-backends.md`。
|
||||
|
||||
## 3. 接口列表
|
||||
**摄像头映射示例**
|
||||
|
||||
- `or-cam-01`
|
||||
- RTSP:`rtsp://...`(由现场或 NVR 文档整理后交给运维)
|
||||
- 备注:术间、机位
|
||||
- `or-cam-02`
|
||||
- RTSP:`...`
|
||||
- 备注:`...`
|
||||
|
||||
| 序号 | 方法 | 路径 | 说明 |
|
||||
| --- | ------ | ------------------------------------------------------------------------------- | --------- |
|
||||
| 1 | `POST` | `/client/surgeries/start` | 开始手术 |
|
||||
| 2 | `POST` | `/client/surgeries/end` | 结束手术 |
|
||||
| 3 | `GET` | `/client/surgeries/{surgery_id}/result` | 查询手术结果 |
|
||||
| 4 | `GET` | `/client/surgeries/{surgery_id}/pending-confirmation` | 拉取一条待确认耗材 |
|
||||
| 5 | `POST` | `/client/surgeries/{surgery_id}/pending-confirmation/{confirmation_id}/resolve` | 提交医生确认结果 |
|
||||
> [!warning] 对接要求
|
||||
> - `camera_ids` 必须与运维配置中的 key 完全一致
|
||||
> - RTSP 不应硬编码在客户端业务程序中
|
||||
|
||||
> [!tip] 联调建议
|
||||
> - 运维配置完成后,客户端使用上面清单中的 `camera_id` 调用 `start` 验证是否返回 `200`
|
||||
> - 若返回 `503` 且 `detail.code = RECORDING_CANNOT_START`,优先核对 ID 拼写以及监控服务器侧网络连通性
|
||||
|
||||
---
|
||||
## 3. HTTP 路由一览
|
||||
|
||||
## 4. 接口详情
|
||||
1. `GET /health`:探活
|
||||
2. `POST /client/surgeries/start`:开始手术
|
||||
3. `POST /client/surgeries/end`:结束手术
|
||||
4. `GET /client/surgeries/{surgery_id}/result`:查询手术结果
|
||||
5. `GET /client/surgeries/{surgery_id}/pending-confirmation`:拉取待确认耗材
|
||||
6. `POST /client/surgeries/{surgery_id}/pending-confirmation/{confirmation_id}/resolve`:提交待确认结果(WAV)
|
||||
|
||||
### 4.1 开始手术
|
||||
## 4. 流程
|
||||
|
||||
**用途:** 在手术开始时,由客户端向服务端上报手术编号、参与采集的摄像头,以及本台手术可能涉及的耗材清单;**服务端启动关联摄像头录制**。
|
||||
### 4.1 时序图
|
||||
|
||||
**成功条件(HTTP 200):** 仅在服务端**确认摄像头已开始录制**之后,才返回 **HTTP 200**。不得在「仅收到请求、尚未开录」时返回 200。
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as 客户系统
|
||||
participant API as 监控服务 API
|
||||
|
||||
Client->>API: POST /client/surgeries/start
|
||||
Note over Client,API: body: surgery_id, camera_ids, candidate_consumables
|
||||
API-->>Client: 200 accepted(开录已确认)
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | ------------------------- |
|
||||
| 方法 | `POST` |
|
||||
| 路径 | `/client/surgeries/start` |
|
||||
par 术中
|
||||
loop 轮询结果
|
||||
Client->>API: GET .../result
|
||||
API-->>Client: 200 或 503 RESULT_NOT_READY
|
||||
end
|
||||
loop 轮询待确认(若启用)
|
||||
Client->>API: GET .../pending-confirmation
|
||||
API-->>Client: 200 或 404
|
||||
opt 有待确认
|
||||
Client->>Client: 播放 prompt_audio_mp3_base64
|
||||
Client->>API: POST .../resolve(multipart audio)
|
||||
API-->>Client: 200 accepted
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Client->>API: POST /client/surgeries/end
|
||||
API-->>Client: 200 accepted(停录已确认)
|
||||
|
||||
**请求体字段:**
|
||||
Client->>API: GET .../result
|
||||
API-->>Client: 200(持久化后可查时返回)
|
||||
```
|
||||
|
||||
### 4.2 状态图
|
||||
|
||||
| 字段名 | 类型 | 必填 | 说明 |
|
||||
| ----------------------- | ---------- | --- | ----------------------------------------------------------------------------------------- |
|
||||
| `surgery_id` | `string` | 是 | 手术 6 位号,必须为 6 位数字。 |
|
||||
| `camera_ids` | `string[]` | 是 | 摄像头 ID 列表,至少 1 个元素;须与服务端配置的 RTSP 映射键一致(示例见 `app/resources/camera_rtsp_urls.sample.json`)。 |
|
||||
| `candidate_consumables` | `string[]` | 否 | 本台手术允许记账的耗材名称清单。**为空或未传则不会写入任何消耗**(仅拉流推理);非空时自动记账与待确认仅针对清单内名称。 |
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[未开始] -->|start 200| B[录制 / 推理中]
|
||||
B -->|result 200| C[有消耗数据可查]
|
||||
B -->|pending 200| D[待确认]
|
||||
D -->|resolve| B
|
||||
B -->|end 200| E[已结束]
|
||||
E -->|result 200| C
|
||||
```
|
||||
|
||||
## 5. 接口详情
|
||||
|
||||
**说明:** 若该 `surgery_id` 在服务端仍存在**尚未写入数据库**的上一台手术内存归档,开始新会话前会先尝试落库;落库失败则返回 **503**(`RECORDING_CANNOT_START`),避免静默丢失数据。
|
||||
以下按“基本信息 -> 请求 -> 响应 -> 状态码”组织,与 OpenAPI 中 `tags: client` 和 `health` 一致。
|
||||
|
||||
**请求示例:**
|
||||
### 5.0 通用约定
|
||||
|
||||
**路径参数 `surgery_id`**
|
||||
|
||||
- 长度:固定 `6`
|
||||
- 字符集:仅数字
|
||||
- 正则:`^\d{6}$`
|
||||
|
||||
**业务错误响应**
|
||||
|
||||
多数业务失败在 `4xx` 或 `5xx` 下返回如下 JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": {
|
||||
"code": "错误码字符串",
|
||||
"message": "人类可读说明",
|
||||
"surgery_id": "123456"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5.1 探活
|
||||
|
||||
**基本信息**
|
||||
|
||||
- 方法:`GET`
|
||||
- 路径:`/health`
|
||||
- 请求体:无
|
||||
|
||||
**响应说明**
|
||||
|
||||
- `200`
|
||||
- 说明:进程正常且数据库可连通
|
||||
- 响应体示例:`{"status":"ok","database":"connected"}`
|
||||
- `503`
|
||||
- 说明:数据库不可用(降级)
|
||||
- 响应体示例:`{"status":"degraded","database":"unavailable"}`
|
||||
|
||||
### 5.2 开始手术
|
||||
|
||||
**基本信息**
|
||||
|
||||
- 方法:`POST`
|
||||
- 路径:`/client/surgeries/start`
|
||||
- Content-Type:`application/json; charset=utf-8`
|
||||
|
||||
**业务说明**
|
||||
|
||||
- 服务端会为 `camera_ids` 中的每个摄像头建立拉流与推理任务,只有在确认开录成功(如首帧就绪)后才返回 HTTP `200`
|
||||
- 若同一 `surgery_id` 存在尚未落库的历史归档,服务端会先尝试写入数据库;失败时可能返回 `503`(如 `RECORDING_CANNOT_START`),以避免静默丢数据
|
||||
- `candidate_consumables` 为空时,服务端会展开为目录 Excel 中的全部商品名,或在未配置目录时展开为分类模型的全部类名
|
||||
|
||||
**请求体(JSON)**
|
||||
|
||||
- `surgery_id`
|
||||
- 类型:`string`
|
||||
- 必填:是
|
||||
- 说明:6 位数字,与路径规则一致
|
||||
- `camera_ids`
|
||||
- 类型:`string[]`
|
||||
- 必填:是
|
||||
- 说明:至少 1 个;必须与运维配置的摄像头 ID 完全一致,见第 2 节
|
||||
- `candidate_consumables`
|
||||
- 类型:`string[]`
|
||||
- 必填:否
|
||||
- 说明:非空时仅这些名称参与自动记账与待确认;缺省或 `[]` 时使用全部候选
|
||||
|
||||
**响应体(200)**
|
||||
|
||||
- `surgery_id`
|
||||
- 类型:`string`
|
||||
- 说明:与请求一致
|
||||
- `status`
|
||||
- 类型:`string`
|
||||
- 说明:成功时通常为 `accepted`
|
||||
- `message`
|
||||
- 类型:`string`
|
||||
- 说明:说明文案
|
||||
|
||||
**状态码**
|
||||
|
||||
- `200`:开录已确认
|
||||
- `422`:参数校验失败,例如 `surgery_id` 非 6 位或 `camera_ids` 为空数组
|
||||
- `503`:开录未确认或录制子系统故障;`detail.code` 常见为 `RECORDING_CANNOT_START`
|
||||
|
||||
**请求示例**
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -90,17 +324,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
**成功响应(HTTP 200):** 表示开录已确认。
|
||||
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| ------------ | -------- | ---------------------------- |
|
||||
| `surgery_id` | `string` | 回显手术 6 位号。 |
|
||||
| `status` | `string` | 处理状态(例如 `accepted` 表示开录已确认)。 |
|
||||
| `message` | `string` | 人类可读的说明文案。 |
|
||||
|
||||
|
||||
**响应示例:**
|
||||
**响应示例(200)**
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -110,34 +334,36 @@
|
||||
}
|
||||
```
|
||||
|
||||
**重试:** 开录调用失败时,服务端会按配置**自动重试**若干次(间隔若干秒);**全部尝试仍失败**后再返回 **HTTP 503**。环境变量:`SURGERY_RECORDING_MAX_ATTEMPTS`(默认 3,含首次)、`SURGERY_RECORDING_RETRY_DELAY_SECONDS`(默认 `1.0`)。
|
||||
### 5.3 结束手术
|
||||
|
||||
**失败响应(HTTP 503):** 重试用尽仍无法在约定条件下确认开录时返回。响应体见 **§5.2**(OpenAPI 模型 `SurgeryClientErrorResponse`,错误码示例:`RECORDING_CANNOT_START`);`detail.message` 中会注明已重试次数。
|
||||
**基本信息**
|
||||
|
||||
---
|
||||
- 方法:`POST`
|
||||
- 路径:`/client/surgeries/end`
|
||||
- Content-Type:`application/json; charset=utf-8`
|
||||
|
||||
### 4.2 结束手术
|
||||
**业务说明**
|
||||
|
||||
**用途:** 在手术结束时,由客户端请求服务端结束该 `surgery_id` 对应手术:**服务端须停止关联摄像头的录制**。
|
||||
停止该 `surgery_id` 关联的全部摄像头任务,只有在确认停录完成后才返回 `200`。
|
||||
|
||||
**成功条件(HTTP 200):** 仅在服务端**确认所有关联摄像头已停止录制**之后,才返回 **HTTP 200**。不得在「仅收到请求、尚未停录」时返回 200。
|
||||
**请求体(JSON)**
|
||||
|
||||
- `surgery_id`
|
||||
- 类型:`string`
|
||||
- 必填:是
|
||||
- 说明:6 位数字
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | ----------------------- |
|
||||
| 方法 | `POST` |
|
||||
| 路径 | `/client/surgeries/end` |
|
||||
**响应体(200)**
|
||||
|
||||
字段含义与 5.2 节一致,`message` 示例为 `摄像头录制已停止,手术已结束。`
|
||||
|
||||
**请求体字段:**
|
||||
**状态码**
|
||||
|
||||
- `200`:停录已确认
|
||||
- `422`:参数校验失败
|
||||
- `503`:停录未确认或故障;`detail.code` 常见为 `RECORDING_NOT_STOPPED`
|
||||
|
||||
| 字段名 | 类型 | 必填 | 说明 |
|
||||
| ------------ | -------- | --- | ------------------ |
|
||||
| `surgery_id` | `string` | 是 | 手术 6 位号,必须为 6 位数字。 |
|
||||
|
||||
|
||||
**请求示例:**
|
||||
**请求示例**
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -145,100 +371,76 @@
|
||||
}
|
||||
```
|
||||
|
||||
**成功响应(HTTP 200):** 表示停录已完成。
|
||||
### 5.4 查询手术结果
|
||||
|
||||
**基本信息**
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| ------------ | -------- | ---------------------------- |
|
||||
| `surgery_id` | `string` | 回显手术 6 位号。 |
|
||||
| `status` | `string` | 处理状态(例如 `accepted` 表示停录已确认)。 |
|
||||
| `message` | `string` | 人类可读的说明文案。 |
|
||||
- 方法:`GET`
|
||||
- 路径:`/client/surgeries/{surgery_id}/result`
|
||||
- 路径参数:`surgery_id`
|
||||
- 请求体:无
|
||||
|
||||
**业务说明**
|
||||
|
||||
**响应示例:**
|
||||
- 仅当存在至少一条消耗明细时返回 `200`
|
||||
- 无明细(包括已归档但零消耗)、手术未开始、未成功开录或当前尚不可查时,返回 `503`
|
||||
- 上述 `503` 场景的常见错误码为 `RESULT_NOT_READY`
|
||||
|
||||
```json
|
||||
{
|
||||
"surgery_id": "123456",
|
||||
"status": "accepted",
|
||||
"message": "摄像头录制已停止,手术已结束。"
|
||||
}
|
||||
```
|
||||
**响应体(200)**
|
||||
|
||||
**重试:** 停录调用失败时,服务端会按配置**自动重试**(与开始手术相同的环境变量);**全部尝试仍失败**后再返回 **HTTP 503**。
|
||||
- `surgery_id`
|
||||
- 类型:`string`
|
||||
- 说明:手术号
|
||||
- `status`
|
||||
- 类型:`string`
|
||||
- 说明:成功时通常为 `completed`
|
||||
- `message`
|
||||
- 类型:`string`
|
||||
- 说明:说明
|
||||
- `details`
|
||||
- 类型:`array`
|
||||
- 说明:消耗明细列表,字段见下文
|
||||
- `summary`
|
||||
- 类型:`array`
|
||||
- 说明:按 `item_id` 汇总的结果,字段见下文
|
||||
|
||||
**失败响应(HTTP 503):** 重试用尽仍无法确认停录完成时返回。响应体见 **§5.2**(错误码示例:`RECORDING_NOT_STOPPED`);`detail.message` 中会注明已重试次数。
|
||||
**`details[]` 元素**
|
||||
|
||||
---
|
||||
- `item_id`
|
||||
- 类型:`string`
|
||||
- 说明:物品 ID;有目录时多为产品编码,否则通常与名称或模型类名一致
|
||||
- `item_name`
|
||||
- 类型:`string`
|
||||
- 说明:物品名称
|
||||
- `qty`
|
||||
- 类型:`integer`
|
||||
- 说明:本条记录数量,当前恒为 `1`;一次识别或一次人工确认只追加一条明细
|
||||
- `doctor_id`
|
||||
- 类型:`string`
|
||||
- 说明:记账关联的医生或系统标识
|
||||
- `timestamp`
|
||||
- 类型:`string`
|
||||
- 说明:ISO 8601 时间(`date-time`)
|
||||
|
||||
### 4.3 查询手术结果
|
||||
**`summary[]` 元素**
|
||||
|
||||
**用途:** 根据 `surgery_id` 查询该台手术下耗材消耗明细及按物品汇总。
|
||||
- `item_id`
|
||||
- 类型:`string`
|
||||
- 说明:与明细一致
|
||||
- `item_name`
|
||||
- 类型:`string`
|
||||
- 说明:名称,通常取该 `item_id` 首条明细中的名称
|
||||
- `total_quantity`
|
||||
- 类型:`integer`
|
||||
- 说明:该物品在本台手术中的合计数量,`>= 0`
|
||||
|
||||
**成功条件(HTTP 200):** 仅在**已开录**且**至少已有一条消耗明细**(自动识别或医生确认)之后返回 **HTTP 200** 及 `details` / `summary`。若已开录但尚无明细,返回 **503**(见 **§5.2**,错误码 `RESULT_NOT_READY`)。
|
||||
**状态码**
|
||||
|
||||
- `200`:至少有一条明细
|
||||
- `422`:`surgery_id` 路径不符合约束
|
||||
- `503`:`RESULT_NOT_READY`,当前无可用明细或不可查
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --------------------------------------- |
|
||||
| 方法 | `GET` |
|
||||
| 路径 | `/client/surgeries/{surgery_id}/result` |
|
||||
|
||||
|
||||
**路径参数:**
|
||||
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
| ------------ | -------- | --- | ------------------------------ |
|
||||
| `surgery_id` | `string` | 是 | 手术 6 位号,必须为 6 位数字,出现在 URL 路径中。 |
|
||||
|
||||
|
||||
**请求示例:**
|
||||
|
||||
```http
|
||||
GET /client/surgeries/123456/result HTTP/1.1
|
||||
Host: <主机>:<端口>
|
||||
```
|
||||
|
||||
(浏览器或客户端直接访问完整 URL 即可,例如 `https://<主机>:<端口>/client/surgeries/123456/result`。)
|
||||
|
||||
**成功响应(HTTP 200):**
|
||||
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| ------------ | -------- | ---------------------------------------------------------------- |
|
||||
| `surgery_id` | `string` | 手术 6 位号。 |
|
||||
| `status` | `string` | 成功时一般为 `completed`(以服务端约定为准)。 |
|
||||
| `message` | `string` | 说明信息。 |
|
||||
| `details` | 数组 | **消耗明细**:按事件发生,可能有多行;每行含物品、数量、医生、时间。 |
|
||||
| `summary` | 数组 | **按物品汇总**:同一 `item_id` 在 `details` 中 `quantity` 的合计,便于客户端直接展示总计。 |
|
||||
|
||||
|
||||
`**details[]` 中每一项(明细行):**
|
||||
|
||||
|
||||
| 字段名 | 类型 | 必填 | 说明 |
|
||||
| ----------- | --------- | --- | ---------------------------------------------------------------- |
|
||||
| `item_id` | `string` | 是 | 物品 ID。 |
|
||||
| `item_name` | `string` | 是 | 物品名称。 |
|
||||
| `quantity` | `integer` | 是 | 本条记录对应的消耗数量(非负整数)。 |
|
||||
| `doctor_id` | `string` | 是 | 医生 ID。 |
|
||||
| `timestamp` | `string` | 是 | 记录时间,**ISO 8601**(JSON 中为 ISO 格式字符串,与 OpenAPI 中 `date-time` 一致)。 |
|
||||
| `source` | `string` | 否 | `vision` 自动识别;`voice` 医生通过待确认接口确认。 |
|
||||
|
||||
|
||||
`**summary[]` 中每一项(汇总行):**
|
||||
|
||||
|
||||
| 字段名 | 类型 | 必填 | 说明 |
|
||||
| ---------------- | --------- | --- | ------------------------------------- |
|
||||
| `item_id` | `string` | 是 | 物品 ID。 |
|
||||
| `item_name` | `string` | 是 | 物品名称(与明细中该 ID 首次出现时的名称一致,具体规则以服务端为准)。 |
|
||||
| `total_quantity` | `integer` | 是 | 该物品在本台手术中的消耗数量**合计**。 |
|
||||
|
||||
|
||||
**约定:** `summary` 应由服务端根据 `details` 按 `item_id` 汇总得到,保证与明细一致。
|
||||
|
||||
**响应示例:**
|
||||
**响应示例(200)**
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -249,136 +451,146 @@ Host: <主机>:<端口>
|
||||
{
|
||||
"item_id": "HC001",
|
||||
"item_name": "纱布",
|
||||
"quantity": 2,
|
||||
"qty": 1,
|
||||
"doctor_id": "D1001",
|
||||
"timestamp": "2026-04-21T10:30:00+08:00"
|
||||
},
|
||||
{
|
||||
"item_id": "HC001",
|
||||
"item_name": "纱布",
|
||||
"quantity": 1,
|
||||
"doctor_id": "D1002",
|
||||
"timestamp": "2026-04-21T11:05:00+08:00"
|
||||
},
|
||||
{
|
||||
"item_id": "HC002",
|
||||
"item_name": "缝线",
|
||||
"quantity": 1,
|
||||
"doctor_id": "D1001",
|
||||
"timestamp": "2026-04-21T10:45:00+08:00"
|
||||
}
|
||||
],
|
||||
"summary": [
|
||||
{ "item_id": "HC001", "item_name": "纱布", "total_quantity": 3 },
|
||||
{ "item_id": "HC002", "item_name": "缝线", "total_quantity": 1 }
|
||||
{
|
||||
"item_id": "HC001",
|
||||
"item_name": "纱布",
|
||||
"total_quantity": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
### 5.5 拉取待确认耗材
|
||||
|
||||
### 4.4 拉取待确认耗材
|
||||
**基本信息**
|
||||
|
||||
**用途:** 当模型置信度不足但存在候选时,服务端将任务放入 FIFO 队列。客户端轮询本接口获取**队首**一条待确认项,使用 `prompt_text` 进行 TTS 播报,并由医生口述选择;**服务端视频推理不等待本步骤**。
|
||||
- 方法:`GET`
|
||||
- 路径:`/client/surgeries/{surgery_id}/pending-confirmation`
|
||||
- 路径参数:`surgery_id`
|
||||
- 请求体:无
|
||||
|
||||
**成功条件(HTTP 200):** 当前手术进行中且队列非空。
|
||||
**业务说明**
|
||||
|
||||
**失败(HTTP 404):** 无待确认项或手术未在进行。`detail.code` 示例:`NO_PENDING_CONFIRMATION`。
|
||||
- 返回当前 FIFO 队首的一条低置信度识别任务
|
||||
- `prompt_audio_mp3_base64` 与 `prompt_text` 内容一致,为标准 Base64 的 MP3 字符串(无换行)
|
||||
- 客户端解码后应按 `audio/mpeg` 播放
|
||||
|
||||
**响应体(200)**
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | ----------------------------------------------------- |
|
||||
| 方法 | `GET` |
|
||||
| 路径 | `/client/surgeries/{surgery_id}/pending-confirmation` |
|
||||
- `surgery_id`
|
||||
- 类型:`string`
|
||||
- 说明:手术号
|
||||
- `confirmation_id`
|
||||
- 类型:`string`
|
||||
- 说明:待确认项 ID;提交 5.6 节接口时原样放入路径
|
||||
- `prompt_text`
|
||||
- 类型:`string`
|
||||
- 说明:播报或展示用语,与 MP3 内容一致
|
||||
- `prompt_audio_mp3_base64`
|
||||
- 类型:`string`
|
||||
- 说明:MP3 的 Base64
|
||||
- `options`
|
||||
- 类型:`array`
|
||||
- 说明:候选项列表,字段见下文
|
||||
- `model_top1_label`
|
||||
- 类型:`string`
|
||||
- 说明:模型原始 Top1 类名,可能不在本台候选内
|
||||
- `model_top1_confidence`
|
||||
- 类型:`number`
|
||||
- 说明:Top1 置信度
|
||||
- `created_at`
|
||||
- 类型:`string`
|
||||
- 说明:创建时间(ISO 8601)
|
||||
|
||||
**`options[]` 元素**
|
||||
|
||||
**响应字段(节选):** `confirmation_id`、`prompt_text`、`options[]`(`label` + `confidence`)、`model_top1_label`、`model_top1_confidence`、`created_at`。
|
||||
- `label`
|
||||
- 类型:`string`
|
||||
- 说明:展示给医生的选项名称
|
||||
- `confidence`
|
||||
- 类型:`number`
|
||||
- 说明:该选项对应的置信度
|
||||
|
||||
---
|
||||
**状态码**
|
||||
|
||||
### 4.5 提交耗材确认结果
|
||||
- `200`:当前有一条待确认
|
||||
- `404`:无待确认或手术未活跃;常见错误码为 `NO_PENDING_CONFIRMATION`
|
||||
- `422`:例如话术为空导致无法 TTS;错误码见响应,如 `TTS_TEXT_EMPTY`
|
||||
- `503`:语音服务未配置或 TTS 失败;例如 `BAIDU_NOT_CONFIGURED`、`TTS_ERROR`
|
||||
|
||||
**用途:** 客户端采集医生回答的 **WAV 音频**并上传;服务端将音频存入 MinIO、调用百度 ASR 识别、解析 4.4 返回的候选项;**确认**则记一条 `source=voice` 的消耗明细,**否认**则关闭该待确认项且不记账。
|
||||
### 5.6 提交待确认结果(医生语音)
|
||||
|
||||
**基本信息**
|
||||
|
||||
| 项目 | 内容 |
|
||||
| -------------- | ------------------------------------------------------------------------------- |
|
||||
| 方法 | `POST` |
|
||||
| 路径 | `/client/surgeries/{surgery_id}/pending-confirmation/{confirmation_id}/resolve` |
|
||||
| `Content-Type` | `multipart/form-data` |
|
||||
- 方法:`POST`
|
||||
- 路径:`/client/surgeries/{surgery_id}/pending-confirmation/{confirmation_id}/resolve`
|
||||
- Content-Type:`multipart/form-data`
|
||||
|
||||
**路径参数**
|
||||
|
||||
**请求体(multipart):**
|
||||
- `surgery_id`
|
||||
- 约束:6 位数字
|
||||
- 说明:同 5.0 节
|
||||
- `confirmation_id`
|
||||
- 约束:长度 1 到 128
|
||||
- 说明:与 5.5 节响应中的 `confirmation_id` 一致
|
||||
|
||||
**请求体(multipart)**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| ------- | --- | --- | -------------------------------------------------------- |
|
||||
| `audio` | 文件 | 是 | 医生语音 `**.wav`**;建议 16 kHz 单声道 PCM,其他格式服务端可尝试用 ffmpeg 转码。 |
|
||||
- `audio`
|
||||
- 类型:`file`
|
||||
- 必填:是
|
||||
- 说明:单个 `.wav` 文件;建议使用 16 kHz 单声道 PCM;非 `.wav` 扩展名会返回 `422`
|
||||
|
||||
**业务说明**
|
||||
|
||||
**成功响应(HTTP 200):** `SurgeryPendingConfirmationResolveResponse`:`resolved_label`、`rejected`、`asr_text`、`audio_object_key` 等(与 OpenAPI 一致)。
|
||||
音频上传至对象存储后执行 ASR 和候选解析。若识别为确认某个候选项,则记一条消耗;若识别为否认全部候选,则不记消耗。
|
||||
|
||||
**错误:** `404`(项不存在或手术未活跃)、`409`(已处理)、`422`(空文件、非 `.wav`、ASR/解析失败等业务码见 `detail.code`)、`503`(MinIO/百度未配置或上传失败等)。
|
||||
**响应体(200)**
|
||||
|
||||
> **说明:** 人工追问的 **TTS 播报由客户端**根据 4.4 的 `prompt_text` 完成;服务端不要求部署扬声器/麦克风。
|
||||
- `surgery_id`
|
||||
- 类型:`string`
|
||||
- 说明:手术号
|
||||
- `confirmation_id`
|
||||
- 类型:`string`
|
||||
- 说明:待确认 ID
|
||||
- `status`
|
||||
- 类型:`string`
|
||||
- 说明:成功时为 `accepted`
|
||||
- `message`
|
||||
- 类型:`string`
|
||||
- 说明:说明
|
||||
- `resolved_label`
|
||||
- 类型:`string | null`
|
||||
- 说明:确认后的耗材名称;否认全部候选时为 `null`
|
||||
- `rejected`
|
||||
- 类型:`boolean`
|
||||
- 说明:是否否认全部候选,不记消耗时为 `true`
|
||||
- `asr_text`
|
||||
- 类型:`string | null`
|
||||
- 说明:语音识别文本
|
||||
- `audio_object_key`
|
||||
- 类型:`string | null`
|
||||
- 说明:对象存储中的原始 WAV 键,便于追溯
|
||||
|
||||
---
|
||||
**状态码**
|
||||
|
||||
## 5. 错误与校验
|
||||
- `200`:已受理并完成解析
|
||||
- `404`:确认项不存在或手术未活跃;例如 `CONFIRMATION_NOT_FOUND`
|
||||
- `409`:当前确认项已处理过;例如 `CONFIRMATION_ALREADY_RESOLVED`
|
||||
- `422`:空文件、非 `.wav`、`VOICE_AUDIO_INVALID`、ASR/解析失败等,具体错误码见响应
|
||||
- `503`:MinIO、百度等依赖不可用;例如 `MINIO_NOT_CONFIGURED`、`MINIO_UPLOAD_FAILED`、`BAIDU_NOT_CONFIGURED`
|
||||
|
||||
### 5.1 参数校验(HTTP 422)
|
||||
**cURL 示例**
|
||||
|
||||
当参数不符合约束时(例如 `surgery_id` 不是 6 位数字、开始手术时 `camera_ids` 为空数组等),服务端通常返回 **HTTP 422**,响应体为 FastAPI/Pydantic 风格的校验错误详情。
|
||||
|
||||
建议在客户端侧对 `surgery_id` 先做本地校验,减少无效请求。
|
||||
|
||||
### 5.2 业务未就绪(HTTP 503)
|
||||
|
||||
当**成功条件未满足**(开录/停录未确认、或查询结果时算法结果尚未就绪)时,服务端返回 **HTTP 503**,响应体为 JSON,且 `**detail` 为对象**(与 OpenAPI 中的 `**SurgeryClientErrorResponse`** / `**SurgeryClientErrorDetail`** 一致):
|
||||
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| ------------------- | -------- | ---------------------------------------------------------------------------- |
|
||||
| `detail.code` | `string` | 业务错误码,如 `RECORDING_CANNOT_START`、`RECORDING_NOT_STOPPED`、`RESULT_NOT_READY`。 |
|
||||
| `detail.message` | `string` | 人类可读说明。 |
|
||||
| `detail.surgery_id` | `string` | 手术 6 位号。 |
|
||||
|
||||
|
||||
**示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": {
|
||||
"code": "RESULT_NOT_READY",
|
||||
"message": "仅在已开录且算法已产生可查询的实时计算结果后返回 HTTP 200;当前条件不满足。",
|
||||
"surgery_id": "123456"
|
||||
}
|
||||
}
|
||||
```bash
|
||||
curl -sS -X POST \
|
||||
"http://<主机>:38080/client/surgeries/123456/pending-confirmation/<confirmation_id>/resolve" \
|
||||
-F "audio=@/path/to/voice.wav;type=audio/wav"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 实现与演进说明(给阅读者)
|
||||
|
||||
- **开始 / 结束 / 查询结果** 与录制、算法流水线的具体绑定以实现为准;**未满足约定条件时不返回 200**(见各节成功条件),与 **OpenAPI(`/docs` 或 `/openapi.json`)** 中声明的 **200 / 503 / 422** 一致。
|
||||
- **人工确认**由客户端完成 TTS 与拾音(ASR);服务端只提供结构化候选与话术,不要求部署环境具备扬声器/麦克风。
|
||||
- 接入真实子系统后,仍应保持:成功响应体与 `SurgeryApiResponse`、`SurgeryResultResponse` 模型一致;503 与 `SurgeryClientErrorResponse` 一致。
|
||||
|
||||
联调时请以 **OpenAPI 文档**(如 `/docs`)为准,本文档与之同步维护。
|
||||
|
||||
---
|
||||
|
||||
## 7. 文档修订
|
||||
|
||||
|
||||
| 版本 | 日期 | 说明 |
|
||||
| --- | ---------- | ---------------------------------------------------------------- |
|
||||
| 1.6 | 2026-04-21 | 待确认耗材接口;候选清单硬约束;查询结果需至少一条明细;客户端侧人工确认。 |
|
||||
| 1.5 | 2026-04-21 | 开始/结束手术:录制流水线失败时重试,仍失败再 503;可配置 `SURGERY_RECORDING_`*。 |
|
||||
| 1.4 | 2026-04-21 | 与 OpenAPI 对齐:开始/结束/查询的 200/503 条件及 `SurgeryClientErrorResponse`。 |
|
||||
| 1.3 | 2026-04-21 | 结束手术:仅在实际停录确认后返回 HTTP 200;否则 503。 |
|
||||
| 1.2 | 2026-04-21 | 查询结果响应增加 `details`(物品 id/名称/数量/医生/时间)与 `summary`(按物品汇总)。 |
|
||||
| 1.1 | 2026-04-21 | 查询结果改为 `GET /client/surgeries/{surgery_id}/result`。 |
|
||||
| 1.0 | 2026-04-21 | 初版,`POST /client/surgeries/start`、`POST /client/surgeries/end`。 |
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user