- 用 OR_SITE_CONFIG_JSON_FILE 统一术间配置(video_rtsp_urls + voice_or_room_bindings) - VoiceTerminalHub:assignment、WS 推送与 HTTP 查询;开录/停录后 notify - 一键联调 orchestrate-and-start 与 /client/surgeries/start 共用指派逻辑,修复 demo 路径不发 WS - 语音桌面端:SIGINT 退出、shutdown 清理、仅 WS 指派、固定 pending 轮询间隔、界面仅保留录音时长 - 新增/调整契约与绑定测试,文档与示例配置同步 Made-with: Cursor
21 KiB
手术室监控服务:客户端手术通信接口说明
能力概览
| 能力 | 说明 |
|---|---|
| 探活 | 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 后入账或关闭。该录音与播报音频无关。 |
1. 服务与基础信息
| 项目 | 说明 |
|---|---|
| 协议 | HTTP/HTTPS |
| 端口 | 38080,生产环境以实际入口为准 |
| 路由 | 无全局前缀;业务接口位于 /client/...,健康检查位于 /health |
start / end 请求体 |
JSON |
resolve 请求体 |
multipart/form-data,字段名为 audio |
| 在线文档 | /docs、/redoc |
2. 摄像头 ID 与 RTSP
RTSP 地址、账号、口令等由客户端对接工程师提供给服务端运维,运维再写入服务端环境。客户端只在 POST /client/surgeries/start 中传 camera_ids。
| camera_id | RTSP | 备注 |
|---|---|---|
or-cam-01 |
rtsp://...(由现场或 NVR 文档整理后交给运维) |
术间、机位 |
or-cam-02 |
... |
... |
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 |
提交医生答复 |
| 7 | GET |
/client/voice-terminals/{terminal_id}/assignment |
可选:查询当前指派(调试或简易集成;官方桌面客户端仅用 WebSocket) |
| 8 | WS |
/client/voice-terminals/ws?terminal_id=... |
语音桌面终端长连接,接收开录/停录指派(推荐) |
术间与语音终端绑定(服务端配置)
- 唯一配置源:环境变量
OR_SITE_CONFIG_JSON_FILE指向手术室 站点 JSON(UTF-8),须同时包含video_rtsp_urls与voice_or_room_bindings(见仓库app/resources/or_site_config.sample.json)。voice_or_room_bindings为数组,每项含or_room_id、camera_ids、voice_terminal_id;camera_ids在数组内须唯一,voice_terminal_id全局唯一。 POST /client/surgeries/start在 HTTP 200 且开录已成功 后:用请求体中的camera_ids在voice_or_room_bindings中解析终端(精确匹配术间 camera 集合,或 开录路集为某术间 camera 集合的子集 时匹配该术间);命中则向对应voice_terminal_id推送action":"start"(并更新 assignment);未配置站点文件、或数组为空、或未命中则仅打日志,不影响 200。POST /client/surgeries/end在停录 HTTP 200 后:向该手术会话记录的终端推送action":"end"(并清除 assignment)。- 推送 JSON 形如:
{"type":"voice_assignment","action":"start"|"end","surgery_id":"123456"}。 - 多 worker:当前实现为进程内内存;多 Uvicorn worker 时需 sticky session 或 Redis 等另行同步。
4. 流程
4.1 时序图
sequenceDiagram
participant Client as 客户端
participant Server as 服务端
Client->>Server: POST /client/surgeries/start
Note over Client,Server: body: surgery_id, camera_ids, candidate_consumables
Server-->>Client: 200 accepted(开录已确认)
par 术中
loop 轮询结果
Client->>Server: GET .../result
Server-->>Client: 200 或 503 RESULT_NOT_READY
end
loop 轮询待确认
Client->>Server: GET .../pending-confirmation
Server-->>Client: 200 或 404
opt 有待确认
Client->>Client: 播放 prompt_audio_mp3_base64
Client->>Server: POST .../resolve(multipart audio)
Server-->>Client: 200 accepted
end
end
end
Client->>Server: POST /client/surgeries/end
Server-->>Client: 200 accepted(停录已确认)
Client->>Server: GET .../result
Server-->>Client: 200(持久化后可查时返回)
4.2 状态图
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
| 约束项 | 说明 |
|---|---|
| 长度 | 固定 6 |
| 字符集 | 仅数字 |
| 正则 | ^\d{6}$ |
业务错误响应
多数业务失败在 4xx 或 5xx 下返回如下 JSON:
{
"detail": {
"code": "错误码字符串",
"message": "人类可读说明",
"surgery_id": "123456"
}
}
5.1 探活
基本信息
| 项目 | 内容 |
|---|---|
| 方法 | GET |
| 路径 | /health |
| 请求体 | 无 |
响应说明
| HTTP | 说明 | 响应体示例 |
|---|---|---|
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中的每个摄像头建立拉流与推理任务,只有在确认开录成功(如首帧就绪)后才返回 HTTP200。 -
candidate_consumables为空时,服务端会展开为目录中的全部耗材名。
请求体(JSON)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
surgery_id |
string |
是 | 6 位数字 |
camera_ids |
string[] |
是 | 至少 1 个;必须与运维配置的摄像头 ID 完全一致,见第 2 节 |
candidate_consumables |
string[] |
否 | 非空时仅这些名称参与自动记账与待确认;缺省或 [] 时使用全部候选 |
响应体(200)
| 字段 | 类型 | 说明 |
|---|---|---|
surgery_id |
string |
与请求一致 |
status |
string |
成功时通常为 accepted |
message |
string |
说明文案 |
状态码
| HTTP | 说明 |
|---|---|
200 |
开录已确认 |
422 |
参数校验失败,例如 surgery_id 非 6 位或 camera_ids 为空数组 |
503 |
开录未确认或录制子系统故障;detail.code 常见为 RECORDING_CANNOT_START |
请求示例
{
"surgery_id": "123456",
"camera_ids": ["or-cam-01", "or-cam-02"],
"candidate_consumables": ["纱布", "缝线", "止血钳"]
}
响应示例(200)
{
"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 示例为 摄像头录制已停止,手术已结束。
状态码
| HTTP | 说明 |
|---|---|
200 |
停录已确认 |
422 |
参数校验失败 |
503 |
停录未确认或故障;detail.code 常见为 RECORDING_NOT_STOPPED |
请求示例
{
"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 |
状态码
| HTTP | 说明 |
|---|---|
200 |
至少有一条明细 |
422 |
surgery_id 路径不符合约束 |
503 |
RESULT_NOT_READY,当前无可用明细或不可查 |
响应示例(200)
{
"surgery_id": "123456",
"status": "completed",
"message": "查询成功。",
"details": [
{
"item_id": "19246-3-14",
"item_name": "医用纱布敷料",
"qty": 1,
"doctor_id": "6611",
"timestamp": "2026-04-21T10:30:00+08:00"
}
],
"summary": [
{
"item_id": "19246-3-14",
"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 |
该选项对应的置信度 |
状态码
| HTTP | 说明 |
|---|---|
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 键,便于追溯 |
状态码
| HTTP | 说明 |
|---|---|
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 示例
curl -sS -X POST \
"http://<主机>:38080/client/surgeries/123456/pending-confirmation/<confirmation_id>/resolve" \
-F "audio=@/path/to/voice.wav;type=audio/wav"
5.7 语音终端 assignment(HTTP,可选)
路径 GET /client/voice-terminals/{terminal_id}/assignment
仓库内 手术室耗材语音确认桌面客户端 仅通过 §5.8 WebSocket 接收指派,不调用本接口。此处供运维脚本、未实现 WS 的第三方临时拉取 active_surgery_id。
响应 200
| 字段 | 类型 | 说明 |
|---|---|---|
voice_terminal_id |
string |
与路径一致 |
active_surgery_id |
string | null |
当前指派手术 6 位号;无指派时为 null |
5.8 语音终端 WebSocket
路径 GET ws://<主机>:<端口>/client/voice-terminals/ws?terminal_id=<终端ID>(HTTPS 部署时使用 wss://)
说明
- 连接成功后,若服务端已有该终端的 assignment,会立即收到一条
action":"start"的 JSON(与下文推送格式一致)。 - 术中由服务端在
start/end成功后 向已连接终端推送 JSON:{"type":"voice_assignment","action":"start"|"end","surgery_id":"123456"}。 - 客户端可发送任意文本作心跳;服务端当前仅依赖 WebSocket 协议级 ping(由网关或客户端库实现)。