Files
operating-room-monitor-server/docs/客户端手术通信接口说明.md
Kevin 04866559db 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
2026-04-21 18:33:54 +08:00

379 lines
17 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.
# 手术室监控服务 · 客户端手术通信接口说明
本文档描述客户端与手术室监控服务端之间,围绕「单台手术生命周期」进行通信的 HTTP 接口。便于联调、评审与对外同步;与 OpenAPISwagger中的定义一致。
---
## 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`。 |