feat: surgery pipeline API, video inference, voice confirm, and tests
- Add FastAPI routes for surgery start/end, results, pending confirmation (WAV upload), and health checks. - Implement RTSP/Hikvision capture, consumable classification, session manager, MinIO/Baidu voice resolution, and DB persistence. - Add documentation (client API, video backends, staging checklist) and sample camera/RTSP config. - Add pytest suite (API contract, session manager, voice, repositories, pipeline persistence) and httpx dev dependency. - Replace deprecated HTTP_422_UNPROCESSABLE_ENTITY with HTTP_422_UNPROCESSABLE_CONTENT. - Fix SurgeryPipeline DB reads to use an explicit transaction with autobegin disabled. Made-with: Cursor
This commit is contained in:
46
docs/staging-regression-checklist.md
Normal file
46
docs/staging-regression-checklist.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# 预发 / 联调回归清单
|
||||
|
||||
在具备 **Postgres**、**MinIO**、可访问 **RTSP**(或海康 SDK 环境)、**百度语音** 的条件下,按下列顺序手工或脚本验证核心闭环。自动化测试见 `tests/`。
|
||||
|
||||
## 环境
|
||||
|
||||
- [ ] `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`;否认不增加明细
|
||||
- [ ] (可选)`GET /internal/surgeries/{id}/voice-status` 查看队列与最近 ASR 摘要
|
||||
|
||||
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` 在语音确认路径有追溯行(成功/失败分支视联调覆盖而定)
|
||||
|
||||
## 失败与重试(抽样)
|
||||
|
||||
- [ ] RTSP 不可达:`start` 最终 `503`,消息含开录失败说明
|
||||
- [ ] MinIO 不可用:`resolve` 返回 `503` 或业务码 `MINIO_*`
|
||||
- [ ] 停录后写库失败:服务日志提示归档;后台重试或修复 DB 后可再次 `start` 同号前会先尝试落归档(见接口说明)
|
||||
|
||||
## 与文档
|
||||
|
||||
- 客户端集成以 **OpenAPI**(`/docs`)与 [客户端手术通信接口说明](./客户端手术通信接口说明.md) 为准;**待确认 resolve 为 multipart WAV**,非 JSON `chosen_label`。
|
||||
45
docs/video-backends.md
Normal file
45
docs/video-backends.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# 视频双后端说明(RTSP / 海康 HCNetSDK)
|
||||
|
||||
## 目标容器
|
||||
|
||||
- **推荐**:`Linux x86_64` + **glibc**(与当前 `python:3.13-slim-bookworm` 一致)。
|
||||
- **不推荐**:Alpine(musl)加载海康预编译 `.so` 往往失败。
|
||||
- 镜像已安装 **ffmpeg** 与 OpenCV 常用系统库,便于 `cv2.VideoCapture(..., cv2.CAP_FFMPEG)` 拉 RTSP。
|
||||
|
||||
## 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}`。
|
||||
2. 调用 `POST /client/surgeries/start` 时,`camera_ids` 必须能在上述配置中解析出 RTSP 地址。
|
||||
3. **开录确认**:每路摄像头在超时内成功打开并读到**首帧**后,才认为该路已开录。
|
||||
|
||||
## 海康官方 SDK 模式(可选)
|
||||
|
||||
SDK **不作为构建期依赖**:将厂商提供的 Linux x86_64 动态库挂载到容器内(例如 `/opt/hikvision/lib/libhcnetsdk.so`),并设置:
|
||||
|
||||
- `HIKVISION_SDK_ENABLED=true`
|
||||
- `HIKVISION_DEVICE_IP` / `HIKVISION_USER` / `HIKVISION_PASSWORD`(以及可选端口)
|
||||
- `HIKVISION_PREVIEW_RTSP_TEMPLATE` 或 `HIKVISION_CAMERA_RTSP_URLS_JSON`:登录成功后仍通过 **RTSP** 取帧送模型(与常见部署一致:SDK 负责设备会话,码流仍走 RTSP)。
|
||||
|
||||
行为概要:
|
||||
|
||||
1. 进程内对 `NET_DVR_Init` 使用引用计数;每路使用 SDK 的工作线程在登录后 `NET_DVR_Logout`,线程结束时配对 `NET_DVR_Cleanup`。
|
||||
2. 若 `HIKVISION_SDK_FALLBACK_TO_RTSP=true`(默认),在**无法加载动态库**、**登录失败**或**未配置凭据**时,自动回退到 `VIDEO_RTSP_*` 映射拉流。
|
||||
|
||||
**注意**:`NET_DVR_Login_V30` 的设备信息结构体在不同 SDK 版本上可能存在差异;若登录异常,请优先使用 RTSP 回退或按厂商文档校对 ctypes 绑定。
|
||||
|
||||
## 推理与结果查询
|
||||
|
||||
- 开录后按 `VIDEO_INFERENCE_INTERVAL_SEC` 抽帧,依次调用耗材分类与撕扯动作模型。
|
||||
- **候选耗材清单**(开始手术请求体中的 `candidate_consumables`)为**硬约束**:若为空,服务端**不会**写入任何消耗明细(仅拉流推理);非空时仅允许清单内标签自动记账。
|
||||
- 当分类 Top1 置信度 ≥ `VIDEO_AUTO_CONFIRM_CONFIDENCE` 且标签在候选清单内时,自动写入一条 `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_DETAIL_COOLDOWN_SEC` 节流。
|
||||
- RTSP 读帧连续失败达到 `VIDEO_READ_FAILURE_RECONNECT_THRESHOLD` 时会 `release` 并尝试重连,间隔 `VIDEO_RECONNECT_BACKOFF_SECONDS`。
|
||||
|
||||
## 相关环境变量
|
||||
|
||||
详见仓库根目录 `.env.example` 中「视频:RTSP + 可选海康 HCNetSDK」一节。
|
||||
378
docs/客户端手术通信接口说明.md
Normal file
378
docs/客户端手术通信接口说明.md
Normal file
@@ -0,0 +1,378 @@
|
||||
# 手术室监控服务 · 客户端手术通信接口说明
|
||||
|
||||
本文档描述客户端与手术室监控服务端之间,围绕「单台手术生命周期」进行通信的 HTTP 接口。便于联调、评审与对外同步;与 OpenAPI(Swagger)中的定义一致。
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
|
||||
| 能力 | 说明 |
|
||||
| ---- | ---------------------------------------------------- |
|
||||
| 开始手术 | 请求开始手术;服务端启动摄像头录制,**仅在确认开录完成后**返回 HTTP 200。 |
|
||||
| 结束手术 | 请求结束手术;服务端停止摄像头录制,**仅在确认停录完成后**返回 HTTP 200。 |
|
||||
| 查询结果 | 根据手术 6 位号查询消耗明细与汇总;**仅在已开录且至少已有一条消耗明细后**返回 HTTP 200。 |
|
||||
| 待确认耗材 | 低置信度时服务端排队一条待确认任务;客户端拉取话术(TTS)并在医生确认后回传,**不阻塞**后续视频推理。 |
|
||||
|
||||
|
||||
**约定:**
|
||||
|
||||
- **开始 / 结束** 使用 `POST`,请求体为 **JSON**(`Content-Type: application/json`)。
|
||||
- **查询结果** 使用 `GET`,**无请求体**;手术号放在 **URL 路径** 中(见 4.3),符合「只读资源用 GET」的惯例。
|
||||
- 手术标识 `**surgery_id`**:必须为 **恰好 6 位数字**(正则 `^\d{6}$`),例如 `123456`。
|
||||
|
||||
---
|
||||
|
||||
## 2. 基础信息
|
||||
|
||||
|
||||
| 项目 | 说明 |
|
||||
| ---------- | ------------------------------------------------------- |
|
||||
| 协议 | HTTP/HTTPS |
|
||||
| 请求体格式 | 开始/结束:`application/json`;查询结果:无 body |
|
||||
| 响应体格式 | JSON |
|
||||
| 路径前缀 | 服务端根路径下直接挂载,例如 `https://<主机>:<端口>/client/surgeries/...` |
|
||||
| 默认服务端口(开发) | `38080`(以实际部署为准) |
|
||||
|
||||
|
||||
> **说明:** 若生产环境存在网关或反向代理,请将上表中的「主机、端口、是否 HTTPS」替换为对外统一入口地址。
|
||||
|
||||
---
|
||||
|
||||
## 3. 接口列表
|
||||
|
||||
|
||||
| 序号 | 方法 | 路径 | 说明 |
|
||||
| --- | ------ | --------------------------------------- | ------ |
|
||||
| 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` | 提交医生确认结果 |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 4. 接口详情
|
||||
|
||||
### 4.1 开始手术
|
||||
|
||||
**用途:** 在手术开始时,由客户端向服务端上报手术编号、参与采集的摄像头,以及本台手术可能涉及的耗材清单;**服务端启动关联摄像头录制**。
|
||||
|
||||
**成功条件(HTTP 200):** 仅在服务端**确认摄像头已开始录制**之后,才返回 **HTTP 200**。不得在「仅收到请求、尚未开录」时返回 200。
|
||||
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | ------------------------- |
|
||||
| 方法 | `POST` |
|
||||
| 路径 | `/client/surgeries/start` |
|
||||
|
||||
|
||||
**请求体字段:**
|
||||
|
||||
|
||||
| 字段名 | 类型 | 必填 | 说明 |
|
||||
| ----------------------- | ---------- | --- | ---------------------- |
|
||||
| `surgery_id` | `string` | 是 | 手术 6 位号,必须为 6 位数字。 |
|
||||
| `camera_ids` | `string[]` | 是 | 摄像头 ID 列表,至少 1 个元素;须与服务端配置的 RTSP 映射键一致(示例见 `app/resources/camera_rtsp_urls.sample.json`)。 |
|
||||
| `candidate_consumables` | `string[]` | 否 | 本台手术允许记账的耗材名称清单。**为空或未传则不会写入任何消耗**(仅拉流推理);非空时自动记账与待确认仅针对清单内名称。 |
|
||||
|
||||
**说明:** 若该 `surgery_id` 在服务端仍存在**尚未写入数据库**的上一台手术内存归档,开始新会话前会先尝试落库;落库失败则返回 **503**(`RECORDING_CANNOT_START`),避免静默丢失数据。
|
||||
|
||||
|
||||
**请求示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"surgery_id": "123456",
|
||||
"camera_ids": ["or-cam-01", "or-cam-02"],
|
||||
"candidate_consumables": ["纱布", "缝线", "止血钳"]
|
||||
}
|
||||
```
|
||||
|
||||
**成功响应(HTTP 200):** 表示开录已确认。
|
||||
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| ------------ | -------- | ---------------------------- |
|
||||
| `surgery_id` | `string` | 回显手术 6 位号。 |
|
||||
| `status` | `string` | 处理状态(例如 `accepted` 表示开录已确认)。 |
|
||||
| `message` | `string` | 人类可读的说明文案。 |
|
||||
|
||||
|
||||
**响应示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"surgery_id": "123456",
|
||||
"status": "accepted",
|
||||
"message": "摄像头录制已开始,手术已启动。"
|
||||
}
|
||||
```
|
||||
|
||||
**重试:** 开录调用失败时,服务端会按配置**自动重试**若干次(间隔若干秒);**全部尝试仍失败**后再返回 **HTTP 503**。环境变量:`SURGERY_RECORDING_MAX_ATTEMPTS`(默认 3,含首次)、`SURGERY_RECORDING_RETRY_DELAY_SECONDS`(默认 `1.0`)。
|
||||
|
||||
**失败响应(HTTP 503):** 重试用尽仍无法在约定条件下确认开录时返回。响应体见 **§5.2**(OpenAPI 模型 `SurgeryClientErrorResponse`,错误码示例:`RECORDING_CANNOT_START`);`detail.message` 中会注明已重试次数。
|
||||
|
||||
---
|
||||
|
||||
### 4.2 结束手术
|
||||
|
||||
**用途:** 在手术结束时,由客户端请求服务端结束该 `surgery_id` 对应手术:**服务端须停止关联摄像头的录制**。
|
||||
|
||||
**成功条件(HTTP 200):** 仅在服务端**确认所有关联摄像头已停止录制**之后,才返回 **HTTP 200**。不得在「仅收到请求、尚未停录」时返回 200。
|
||||
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | ----------------------- |
|
||||
| 方法 | `POST` |
|
||||
| 路径 | `/client/surgeries/end` |
|
||||
|
||||
|
||||
**请求体字段:**
|
||||
|
||||
|
||||
| 字段名 | 类型 | 必填 | 说明 |
|
||||
| ------------ | -------- | --- | ------------------ |
|
||||
| `surgery_id` | `string` | 是 | 手术 6 位号,必须为 6 位数字。 |
|
||||
|
||||
|
||||
**请求示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"surgery_id": "123456"
|
||||
}
|
||||
```
|
||||
|
||||
**成功响应(HTTP 200):** 表示停录已完成。
|
||||
|
||||
|
||||
| 字段名 | 类型 | 说明 |
|
||||
| ------------ | -------- | ---------------------------- |
|
||||
| `surgery_id` | `string` | 回显手术 6 位号。 |
|
||||
| `status` | `string` | 处理状态(例如 `accepted` 表示停录已确认)。 |
|
||||
| `message` | `string` | 人类可读的说明文案。 |
|
||||
|
||||
|
||||
**响应示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"surgery_id": "123456",
|
||||
"status": "accepted",
|
||||
"message": "摄像头录制已停止,手术已结束。"
|
||||
}
|
||||
```
|
||||
|
||||
**重试:** 停录调用失败时,服务端会按配置**自动重试**(与开始手术相同的环境变量);**全部尝试仍失败**后再返回 **HTTP 503**。
|
||||
|
||||
**失败响应(HTTP 503):** 重试用尽仍无法确认停录完成时返回。响应体见 **§5.2**(错误码示例:`RECORDING_NOT_STOPPED`);`detail.message` 中会注明已重试次数。
|
||||
|
||||
---
|
||||
|
||||
### 4.3 查询手术结果
|
||||
|
||||
**用途:** 根据 `surgery_id` 查询该台手术下耗材消耗明细及按物品汇总。
|
||||
|
||||
**成功条件(HTTP 200):** 仅在**已开录**且**至少已有一条消耗明细**(自动识别或医生确认)之后返回 **HTTP 200** 及 `details` / `summary`。若已开录但尚无明细,返回 **503**(见 **§5.2**,错误码 `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` 汇总得到,保证与明细一致。
|
||||
|
||||
**响应示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"surgery_id": "123456",
|
||||
"status": "completed",
|
||||
"message": "查询成功。",
|
||||
"details": [
|
||||
{
|
||||
"item_id": "HC001",
|
||||
"item_name": "纱布",
|
||||
"quantity": 2,
|
||||
"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 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.4 拉取待确认耗材
|
||||
|
||||
**用途:** 当模型置信度不足但存在候选时,服务端将任务放入 FIFO 队列。客户端轮询本接口获取**队首**一条待确认项,使用 `prompt_text` 进行 TTS 播报,并由医生口述选择;**服务端视频推理不等待本步骤**。
|
||||
|
||||
**成功条件(HTTP 200):** 当前手术进行中且队列非空。
|
||||
|
||||
**失败(HTTP 404):** 无待确认项或手术未在进行。`detail.code` 示例:`NO_PENDING_CONFIRMATION`。
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 方法 | `GET` |
|
||||
| 路径 | `/client/surgeries/{surgery_id}/pending-confirmation` |
|
||||
|
||||
**响应字段(节选):** `confirmation_id`、`prompt_text`、`options[]`(`label` + `confidence`)、`model_top1_label`、`model_top1_confidence`、`created_at`。
|
||||
|
||||
---
|
||||
|
||||
### 4.5 提交耗材确认结果
|
||||
|
||||
**用途:** 客户端采集医生回答的 **WAV 音频**并上传;服务端将音频存入 MinIO、调用百度 ASR 识别、解析 4.4 返回的候选项;**确认**则记一条 `source=voice` 的消耗明细,**否认**则关闭该待确认项且不记账。
|
||||
|
||||
| 项目 | 内容 |
|
||||
| --- | --- |
|
||||
| 方法 | `POST` |
|
||||
| 路径 | `/client/surgeries/{surgery_id}/pending-confirmation/{confirmation_id}/resolve` |
|
||||
| `Content-Type` | `multipart/form-data` |
|
||||
|
||||
**请求体(multipart):**
|
||||
|
||||
| 字段 | 类型 | 必填 | 说明 |
|
||||
| --- | --- | --- | --- |
|
||||
| `audio` | 文件 | 是 | 医生语音 **`.wav`**;建议 16 kHz 单声道 PCM,其他格式服务端可尝试用 ffmpeg 转码。 |
|
||||
|
||||
**成功响应(HTTP 200):** `SurgeryPendingConfirmationResolveResponse`:`resolved_label`、`rejected`、`asr_text`、`audio_object_key` 等(与 OpenAPI 一致)。
|
||||
|
||||
**错误:** `404`(项不存在或手术未活跃)、`409`(已处理)、`422`(空文件、非 `.wav`、ASR/解析失败等业务码见 `detail.code`)、`503`(MinIO/百度未配置或上传失败等)。
|
||||
|
||||
> **说明:** 人工追问的 **TTS 播报由客户端**根据 4.4 的 `prompt_text` 完成;服务端不要求部署扬声器/麦克风。
|
||||
|
||||
---
|
||||
|
||||
## 5. 错误与校验
|
||||
|
||||
### 5.1 参数校验(HTTP 422)
|
||||
|
||||
当参数不符合约束时(例如 `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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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