Files
operating-room-monitor-server/docs/客户端手术通信接口说明.md
Kevin 3d7bd70355 feat: 手术视频消耗、待确认与持久化改造
- 新增 Alembic 初始迁移、领域明细模型及归档持久化与重试链路\n- 拆分视频会话注册表、分类处理、推理时间窗聚合与流处理\n- 消耗日志:TSV/Markdown 含 top2/top3;item_id 优先产品编码;待确认记「待确认」行,语音确认后落正式行并更新汇总\n- 待确认时内存/DB 明细为占位行,确认后替换;拒绝时移除占位\n- 分类 probs 先 detach/cpu 再转 NumPy,修复 MPS/CUDA 上推理被静默跳过\n- 补充集成测试、归档与设备张量等单测

Made-with: Cursor
2026-04-23 20:42:21 +08:00

597 lines
16 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.
<style>
/*
* Obsidian 单文件修复:
* 只复制本 Markdown 到 Obsidian 时,也尽量保证中文在预览和 PDF 导出中可见。
*/
.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;
}
@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>
# 手术室监控服务:客户端手术通信接口说明
面向对接本 FastAPI 服务的客户端HIS、手麻、工作站等。字段、状态码与返回模型以 `/docs``/openapi.json` 为准,本文用于摘要和流程说明。
> [!summary] 常用响应模型
> - `SurgeryApiResponse`
> - `SurgeryResultResponse`
> - `SurgeryPendingConfirmationResponse`
> - 业务错误外形见 `SurgeryClientErrorResponse`
> - 内部演示页:`scripts/demo_client/`(仅供演示,不作为对外契约)
## 能力概览
- 探活:`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 后入账或关闭。该录音与播报音频无关。
> [!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`
- 端口:`38080`,生产环境以实际入口为准
- 路由:无全局前缀;业务接口位于 `/client/...`,健康检查位于 `/health`
- `start` / `end` 请求体JSON
- `resolve` 请求体:`multipart/form-data`,字段名为 `audio`
- 在线文档:`/docs``/redoc`
## 2. 摄像头 ID 与 RTSP
RTSP 地址、账号、口令等由客户端对接工程师提供给服务端运维,运维再写入服务端环境(例如 JSON 映射或环境变量)。业务程序不在客户端保存 RTSP客户端只在 `POST /client/surgeries/start` 中传 `camera_ids`
配置格式示例见 `app/resources/camera_rtsp_urls.sample.json`。配置项细节见 `.env.example``docs/video-backends.md`
**摄像头映射示例**
- `or-cam-01`
- RTSP`rtsp://...`(由现场或 NVR 文档整理后交给运维)
- 备注:术间、机位
- `or-cam-02`
- RTSP`...`
- 备注:`...`
> [!warning] 对接要求
> - `camera_ids` 必须与运维配置中的 key 完全一致
> - RTSP 不应硬编码在客户端业务程序中
> [!tip] 联调建议
> - 运维配置完成后,客户端使用上面清单中的 `camera_id` 调用 `start` 验证是否返回 `200`
> - 若返回 `503` 且 `detail.code = RECORDING_CANNOT_START`,优先核对 ID 拼写以及监控服务器侧网络连通性
## 3. HTTP 路由一览
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. 流程
### 4.1 时序图
```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 或 404
opt 有待确认
Client->>Client: 播放 prompt_audio_mp3_base64
Client->>API: POST .../resolvemultipart 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 状态图
```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. 接口详情
以下按“基本信息 -> 请求 -> 响应 -> 状态码”组织,与 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
{
"surgery_id": "123456",
"camera_ids": ["or-cam-01", "or-cam-02"],
"candidate_consumables": ["纱布", "缝线", "止血钳"]
}
```
**响应示例200**
```json
{
"surgery_id": "123456",
"status": "accepted",
"message": "摄像头录制已开始,手术已启动。"
}
```
### 5.3 结束手术
**基本信息**
- 方法:`POST`
- 路径:`/client/surgeries/end`
- Content-Type`application/json; charset=utf-8`
**业务说明**
停止该 `surgery_id` 关联的全部摄像头任务,只有在确认停录完成后才返回 `200`
**请求体JSON**
- `surgery_id`
- 类型:`string`
- 必填:是
- 说明6 位数字
**响应体200**
字段含义与 5.2 节一致,`message` 示例为 `摄像头录制已停止,手术已结束。`
**状态码**
- `200`:停录已确认
- `422`:参数校验失败
- `503`:停录未确认或故障;`detail.code` 常见为 `RECORDING_NOT_STOPPED`
**请求示例**
```json
{
"surgery_id": "123456"
}
```
### 5.4 查询手术结果
**基本信息**
- 方法:`GET`
- 路径:`/client/surgeries/{surgery_id}/result`
- 路径参数:`surgery_id`
- 请求体:无
**业务说明**
- 仅当存在至少一条消耗明细时返回 `200`
- 无明细(包括已归档但零消耗)、手术未开始、未成功开录或当前尚不可查时,返回 `503`
- 上述 `503` 场景的常见错误码为 `RESULT_NOT_READY`
**响应体200**
- `surgery_id`
- 类型:`string`
- 说明:手术号
- `status`
- 类型:`string`
- 说明:成功时通常为 `completed`
- `message`
- 类型:`string`
- 说明:说明
- `details`
- 类型:`array`
- 说明:消耗明细列表,字段见下文
- `summary`
- 类型:`array`
- 说明:按 `item_id` 汇总的结果,字段见下文
**`details[]` 元素**
- `item_id`
- 类型:`string`
- 说明:物品 ID有目录时多为产品编码否则通常与名称或模型类名一致
- `item_name`
- 类型:`string`
- 说明:物品名称
- `qty`
- 类型:`integer`
- 说明:本条记录数量,当前恒为 `1`;一次识别或一次人工确认只追加一条明细
- `doctor_id`
- 类型:`string`
- 说明:记账关联的医生或系统标识
- `timestamp`
- 类型:`string`
- 说明ISO 8601 时间(`date-time`
**`summary[]` 元素**
- `item_id`
- 类型:`string`
- 说明:与明细一致
- `item_name`
- 类型:`string`
- 说明:名称,通常取该 `item_id` 首条明细中的名称
- `total_quantity`
- 类型:`integer`
- 说明:该物品在本台手术中的合计数量,`>= 0`
**状态码**
- `200`:至少有一条明细
- `422``surgery_id` 路径不符合约束
- `503``RESULT_NOT_READY`,当前无可用明细或不可查
**响应示例200**
```json
{
"surgery_id": "123456",
"status": "completed",
"message": "查询成功。",
"details": [
{
"item_id": "HC001",
"item_name": "纱布",
"qty": 1,
"doctor_id": "D1001",
"timestamp": "2026-04-21T10:30:00+08:00"
}
],
"summary": [
{
"item_id": "HC001",
"item_name": "纱布",
"total_quantity": 1
}
]
}
```
### 5.5 拉取待确认耗材
**基本信息**
- 方法:`GET`
- 路径:`/client/surgeries/{surgery_id}/pending-confirmation`
- 路径参数:`surgery_id`
- 请求体:无
**业务说明**
- 返回当前 FIFO 队首的一条低置信度识别任务
- `prompt_audio_mp3_base64``prompt_text` 内容一致,为标准 Base64 的 MP3 字符串(无换行)
- 客户端解码后应按 `audio/mpeg` 播放
**响应体200**
- `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[]` 元素**
- `label`
- 类型:`string`
- 说明:展示给医生的选项名称
- `confidence`
- 类型:`number`
- 说明:该选项对应的置信度
**状态码**
- `200`:当前有一条待确认
- `404`:无待确认或手术未活跃;常见错误码为 `NO_PENDING_CONFIRMATION`
- `422`:例如话术为空导致无法 TTS错误码见响应`TTS_TEXT_EMPTY`
- `503`:语音服务未配置或 TTS 失败;例如 `BAIDU_NOT_CONFIGURED``TTS_ERROR`
### 5.6 提交待确认结果(医生语音)
**基本信息**
- 方法:`POST`
- 路径:`/client/surgeries/{surgery_id}/pending-confirmation/{confirmation_id}/resolve`
- Content-Type`multipart/form-data`
**路径参数**
- `surgery_id`
- 约束6 位数字
- 说明:同 5.0 节
- `confirmation_id`
- 约束:长度 1 到 128
- 说明:与 5.5 节响应中的 `confirmation_id` 一致
**请求体multipart**
- `audio`
- 类型:`file`
- 必填:是
- 说明:单个 `.wav` 文件;建议使用 16 kHz 单声道 PCM`.wav` 扩展名会返回 `422`
**业务说明**
音频上传至对象存储后执行 ASR 和候选解析。若识别为确认某个候选项,则记一条消耗;若识别为否认全部候选,则不记消耗。
**响应体200**
- `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 键,便于追溯
**状态码**
- `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`
**cURL 示例**
```bash
curl -sS -X POST \
"http://<主机>:38080/client/surgeries/123456/pending-confirmation/<confirmation_id>/resolve" \
-F "audio=@/path/to/voice.wav;type=audio/wav"
```