# 手术室监控服务 · 客户端手术通信接口说明 本文档描述客户端与手术室监控服务端之间,围绕「单台手术生命周期」进行通信的 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`。 |