Persist app logs under logs/app with 7-day rotation, suppress routine health/pending access lines, raise default VIDEO_OPEN_TIMEOUT_SEC to 90s, and document consumable codes plus client timeout guidance. Co-authored-by: Cursor <cursoragent@cursor.com>
37 KiB
Executable File
手术室监控服务:客户端手术通信接口说明
Changelog
对接方请按部署版本核对本节;已发布路径与字段语义以正文为准,本节仅记录相对上一版的客户端需改动点。
2026-05-26(续)
行为 / 运维
| 项 | 变更 |
|---|---|
POST /client/surgeries/start |
默认 RTSP 开录等待由 45s 调至 90s;客户端 HTTP 超时建议 ≥ 300s |
| 服务端日志 | 应用日志持久化至 backend/logs/app/server.log,7 天轮转/保留;access 日志隐藏 GET /health 200 与 pending 轮询 404 |
2026-05-26
文档
| 接口 | 变更 | 客户端需改动 |
|---|---|---|
POST /internal/demo/offline-batch 及配套 GET |
新增 §5.7–§5.9:链路 3 离线 MP4 上传、耗时轮询、标注视频下载 | 非 RTSP 集成时可走上传视频路径;仍用 GET /client/surgeries/{surgery_id}/result 查消耗,无需 start / end |
2026-05-25
行为对齐(实现已与正文一致)
| 接口 | 变更 | 客户端需改动 |
|---|---|---|
GET /client/surgeries/{surgery_id}/result |
无明细时的 503 统一返回 detail.code = RESULT_NOT_READY(detail.message 仍为人可读原因) |
勿再依赖已废弃的 SURGERY_NOT_STARTED、SURGERY_STARTING、SURGERY_IN_PROGRESS_NO_DETAILS、SURGERY_ENDED_NO_CONSUMPTION 等细分码;判断 503 时用 RESULT_NOT_READY |
POST .../pending-confirmation/{confirmation_id}/resolve |
ASR/解析可重试失败(如 VOICE_ASR_FAILED)改回 HTTP 422,不再返回 200 + status: "failed" |
删除对 200/status=failed/error_code 的处理;可重试失败按 422 + detail.code 重录上传 |
POST /client/surgeries/start → candidate_consumables |
新增:可仅传产品编码(label_id,与 consumable_classifier_labels.yaml 一致) |
可传 ["14764-2-4"] 或 [{"消耗品编号":"14764-2-4"}];服务端解析为类名后参与推理,响应与其它字段不变 |
未变更
/client/...路由路径、HTTP 方法、surgery_id约束、成功体字段名与 §5 各节描述一致。
能力概览
| 能力 | 说明 |
|---|---|
| 探活 | GET /health,用于检查进程和数据库状态,详见 5.1 节。 |
| 开始手术 | POST /client/surgeries/start,只有在开录确认成功后才返回 200(链路 1 · 真 RTSP)。 |
| 结束手术 | POST /client/surgeries/end,只有在停录确认成功后才返回 200(链路 1)。 |
| 上传视频 | POST /internal/demo/offline-batch,上传单路 MP4 离线推理(链路 3);无需 start / end,不触发语音终端;详见 5.7 节。需运维开启 DEMO_ORCHESTRATOR_ENABLED=true。 |
| 查询结果 | GET /client/surgeries/{surgery_id}/result,至少存在一条消耗明细时返回 200;否则返回 503,detail.code 为 RESULT_NOT_READY。链路 1 / 3 共用。 |
| 待确认播报 | 官方浏览器客户端(仓库 clients/voice-confirmation/,可独立部署):同一 WebSocket 上推送 voice_pending(载荷与 GET .../pending-confirmation 成功体一致,另含 type);无队首时 voice_pending_empty;不轮询 GET。第三方仍可用 GET .../pending-confirmation 拉取队首。链路 3 无待确认。 |
| 待确认答复 | POST /client/surgeries/{surgery_id}/pending-confirmation/{confirmation_id}/resolve,上传医生答复的 WAV 录音,服务端完成 ASR 后入账或关闭。该录音与播报音频无关。 |
1. 服务与基础信息
| 项目 | 说明 |
|---|---|
| 协议 | HTTP/HTTPS |
| 端口 | 38080,生产环境以实际入口为准 |
| 路由 | 无全局前缀;业务接口位于 /client/...,健康检查位于 /health;链路 3 上传位于 /internal/demo/... |
start / end 请求体 |
JSON |
| 链路 3 上传请求体 | multipart/form-data,字段见 5.7 节 |
resolve 请求体 |
multipart/form-data,字段名为 audio |
| 在线文档 | /docs、/redoc |
| 语音确认官方页面 | 仓库 clients/voice-confirmation/(静态资源,与 API 分宿;需为浏览器配置 CORS,见该目录 README.md) |
局域网部署时,API 应监听或发布到可被语音终端访问的地址(例如 0.0.0.0:38080 发布为 http://192.168.1.100:38080)。语音确认页面的 服务端 Base URL 必须填写该 API 根地址;浏览器端会用同一主机和端口生成 WebSocket 地址:ws://192.168.1.100:38080/client/voice-terminals/ws?terminal_id=...。如果语音页与 API 不同源,需配置 CORS 放行语音页来源。
2. 摄像头 ID 与 RTSP
RTSP 地址、账号、口令等由客户端对接工程师提供给服务端运维,运维再写入服务端环境。客户端只在 链路 1 的 POST /client/surgeries/start 中传 camera_ids。链路 3 上传视频不需要 camera_ids。
| camera_id | RTSP | 备注 |
|---|---|---|
or-cam-01 |
rtsp://admin:Aa183137@192.168.3.2:554/Streaming/Channels/101 |
测试手术室,主码流 ch.101 |
or-cam-02 |
rtsp://admin:Aa183137@192.168.3.3:554/Streaming/Channels/101 |
同上 |
or-cam-03 |
rtsp://admin:Aa183137@192.168.3.4:554/Streaming/Channels/101 |
同上 |
or-cam-04 |
rtsp://admin:Aa183137@192.168.3.5:554/Streaming/Channels/101 |
同上 |
Docker 部署:API 在容器内拉流时,上表这类术间摄像头局域网 IP通常可继续使用(出站经宿主机路由,须宿主机已能访问该网段)。若 RTSP 实际跑在宿主机本机(假流等),URL 中的主机应使用 host.docker.internal,勿写 127.0.0.1;详见 docs/video-backends.md 与 backend/docker-compose.yml。
3. HTTP 路由一览
| 序号 | 方法 | 路径 | 说明 |
|---|---|---|---|
| 1 | GET |
/health |
探活 |
| 2 | POST |
/client/surgeries/start |
开始手术(链路 1) |
| 3 | POST |
/client/surgeries/end |
结束手术(链路 1) |
| 4 | GET |
/client/surgeries/{surgery_id}/result |
查询手术结果(链路 1 / 3 共用) |
| 5 | GET |
/client/surgeries/{surgery_id}/pending-confirmation |
拉取待确认耗材(链路 1) |
| 6 | POST |
/client/surgeries/{surgery_id}/pending-confirmation/{confirmation_id}/resolve |
提交医生答复(链路 1) |
| 7 | POST |
/internal/demo/offline-batch |
链路 3:上传 MP4 离线推理(需 DEMO_ORCHESTRATOR_ENABLED=true) |
| 8 | GET |
/internal/demo/offline-batch/{surgery_id}/timing |
链路 3:查询文本 / 标注视频各阶段耗时 |
| 9 | GET |
/internal/demo/offline-batch/{surgery_id}/visualization |
链路 3:下载标注 MP4(可选) |
| 10 | GET |
/internal/demo/recording-modes-status |
可选:探测链路 3 是否已启用 |
| 11 | GET |
/client/voice-terminals/{terminal_id}/assignment |
可选:查询当前指派(调试或简易集成;官方浏览器客户端仅用 WebSocket) |
| 12 | WS |
/client/voice-terminals/ws?terminal_id=... |
语音终端长连接,接收开录/停录指派(推荐;与 clients/voice-confirmation 共用) |
术间与语音终端绑定(服务端配置)
- 唯一配置源:环境变量
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"}。 - 待确认队列(与 HTTP GET 成功体对齐):有新队首或队首变化时推送
{"type":"voice_pending", "surgery_id":"...", "confirmation_id":"...", "pending_queue_length":1, "pending_queue_position":1, "pending_cumulative_ordinal":1, "prompt_text":"...", "prompt_audio_mp3_base64":"...", "options":[...], "model_top1_label":"...", "model_top1_confidence":0.0, "created_at":"..."}(字段与GET /client/surgeries/{surgery_id}/pending-confirmation的 200 响应相同,并多一个type)。FIFO 无待确认项时推送{"type":"voice_pending_empty","surgery_id":"123456"}。触发时机包括:开录指派后(后台任务)、视觉入队待确认后、医生resolve成功弹出队首后、终端 WebSocket 刚连接且当前已有指派 时(先补发voice_assignmentstart,再补发队首或 empty)。 - 多 worker:当前实现为进程内内存;多 Uvicorn worker 时需 sticky session 或 Redis 等另行同步。
4. 流程
4.1 时序图(链路 1 · 真 RTSP)
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 状态图(链路 1)
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
4.3 时序图(链路 3 · 上传 MP4)
与 §4.1 并行存在,适用于已有完整术间录像、无需实时拉流与语音待确认的场景。
sequenceDiagram
participant Client as 客户端
participant Server as 服务端
opt 探测(可选)
Client->>Server: GET /internal/demo/recording-modes-status
Server-->>Client: demo_recording_modes_enabled
end
Client->>Server: POST /internal/demo/offline-batch
Note over Client,Server: multipart: surgery_id, video1, candidate_consumables_json, include_visualization
Server-->>Client: 200 accepted(TSV 推理已完成)
Client->>Server: GET /client/surgeries/{surgery_id}/result
Server-->>Client: 200 或 503 RESULT_NOT_READY
opt include_visualization=true
loop 直至 video_status=ready 或 failed
Client->>Server: GET .../offline-batch/{surgery_id}/timing
Server-->>Client: video_status pending / ready / failed
end
Client->>Server: GET .../offline-batch/{surgery_id}/visualization
Server-->>Client: video/mp4
end
与链路 1 的差异
| 项目 | 链路 1(RTSP) | 链路 3(上传 MP4) |
|---|---|---|
| 开录 / 停录 | 需要 start / end |
不需要 |
| 语音终端 / 待确认 | 有 | 无(结果直接入库) |
| 查结果 | GET .../result |
同上 |
| 服务端开关 | RTSP 站点配置 | 另需 DEMO_ORCHESTRATOR_ENABLED=true |
5. 接口详情
路径参数 surgery_id
| 约束项 | 说明 |
|---|---|
| 长度 | 固定 6 |
| 字符集 | 仅数字 |
| 正则 | ^\d{6}$ |
业务错误响应
/client/... 多数业务失败在 4xx 或 5xx 下返回如下 JSON:
{
"detail": {
"code": "错误码字符串",
"message": "人类可读说明",
"surgery_id": "123456"
}
}
§5.7–§5.9(链路 3)的部分校验失败返回 FastAPI 默认 detail 字符串(无 code 包装),见各节状态码表。
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。 -
本接口同步阻塞直至开录确认;默认 RTSP 等待约 95 秒/次、最多 3 次重试。客户端 HTTP 超时建议 ≥ 300 秒(Apipost 等工具默认 30–60 秒易触发
ESOCKETTIMEDOUT)。服务端可通过VIDEO_OPEN_TIMEOUT_SEC调大(默认 90)。 -
candidate_consumables缺省或[]时,服务端会展开为consumable_classifier_labels.yaml中的全部类名(无有效 yaml 时开录失败)。 -
非空时,每项可为耗材名称或产品编码(
label_id);编码通过 yaml 解析为类名后再参与推理与白名单。亦支持医院导出对象(见下表)。 -
41 类完整类名与产品编码对照:见
docs/耗材产品编码与类名对照表.md(须用完整类名或编码,口语简称如「纱布」不会自动映射)。
请求体(JSON)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
surgery_id |
string |
是 | 6 位数字 |
camera_ids |
string[] |
是 | 至少 1 个;必须与运维配置的摄像头 ID 完全一致,见第 2 节 |
candidate_consumables |
string[] 或对象数组 |
否 | 非空时仅这些名称/编码参与自动记账与待确认;缺省或 [] 时使用全部候选。字符串可为类名或 label_id;对象见下 |
candidate_consumables 数组元素
| 形式 | 示例 | 说明 |
|---|---|---|
| 名称字符串 | "医用纱布敷料" |
须与 yaml / 模型类名完全一致(见对照表) |
| 编码字符串 | "14764-2-4" |
与 yaml 中 label_id 一致,服务端解析为类名 |
| 导出对象(名称) | {"消耗品编号":"14764-2-4","名称":"一次性使用手术单"} |
以 名称(或 name)为准 |
| 导出对象(仅编号) | {"消耗品编号":"14764-2-4"} |
按编号解析为类名 |
响应体(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", "or-cam-03", "or-cam-04"],
"candidate_consumables": ["纱布", "缝线", "止血钳"]
}
仅传产品编码时:
{
"surgery_id": "123456",
"camera_ids": ["or-cam-01"],
"candidate_consumables": ["14764-2-4", "8036-5-22"]
}
响应示例(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的detail.code为RESULT_NOT_READY;detail.message说明具体原因(如未开始、进行中尚无明细、已结束无消耗等)。 -
链路 3:离线 batch 完成后结果直接写入数据库,通过本接口查询;无需先调用
start/end。若 batch 识别结果为零条明细,本接口仍返回503。
响应体(200)
| 字段 | 类型 | 说明 |
|---|---|---|
surgery_id |
string |
手术号 |
status |
string |
成功时通常为 completed |
message |
string |
说明 |
details |
array |
消耗明细列表,字段见下文 |
summary |
array |
按 item_id 汇总的结果,字段见下文 |
details[] 元素
| 字段 | 类型 | 说明 |
|---|---|---|
item_id |
string |
物品 ID;有 yaml 时多为 label_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 |
detail.code 为 RESULT_NOT_READY;message 为具体原因 |
响应示例(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 节接口时原样放入路径 |
pending_queue_length |
int |
当前 FIFO 中仍为 pending 的条数(含本条) |
pending_queue_position |
int |
本条在 pending_fifo 中的排队序号(1-based,队首为 1) |
pending_cumulative_ordinal |
int |
本场手术中待确认任务累计入队序号(第几条入队) |
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 |
无待确认或手术未活跃;detail.code 为 NO_PENDING_CONFIRMATION |
422 |
例如话术为空导致无法 TTS;detail.code 如 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 路径参数 |
confirmation_id |
长度 1 到 128 | 与 5.5 节响应中的 confirmation_id 一致 |
请求体(multipart)
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
audio |
file |
是 | 单个 .wav 文件;建议使用 16 kHz 单声道 PCM;非 .wav 扩展名会返回 422 |
业务说明
音频上传至对象存储后执行 ASR 和候选解析。若识别为确认某个候选项,则记一条消耗;若识别为否认全部候选,则不记消耗。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 |
确认项不存在或手术未活跃;detail.code 如 CONFIRMATION_NOT_FOUND |
409 |
当前确认项已处理过;detail.code 如 CONFIRMATION_ALREADY_RESOLVED |
422 |
空文件、非 .wav、VOICE_AUDIO_INVALID、ASR/解析可重试失败(如 VOICE_ASR_FAILED、VOICE_TEXT_EMPTY、VOICE_PARSE_FAILED)等 |
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 上传手术视频(链路 3 · 离线 batch)
基本信息
| 项目 | 内容 |
|---|---|
| 方法 | POST |
| 路径 | /internal/demo/offline-batch |
| Content-Type | multipart/form-data |
前置条件
- 服务端环境变量
DEMO_ORCHESTRATOR_ENABLED=true。未开启时本路径返回404,detail为Demo recording modes disabled (set DEMO_ORCHESTRATOR_ENABLED=true).。 - 可先调用
GET /internal/demo/recording-modes-status查看demo_recording_modes_enabled是否为true(该探测接口始终注册,不依赖上述开关)。
业务说明
- 上传单路完整 MP4(字段名
video1),服务端调用离线算法包(algorithm_subprocesses/5.15/main.py)完成整段推理,解析 TSV 后将消耗明细直接写入数据库。 - 不启动 RTSP 实时会话,不调用
start/end,不触发语音终端或待确认队列。 - 请求在服务端同步阻塞直至 TSV 推理结束才返回
200;长视频可能耗时较久,客户端应设置足够长的 HTTP 超时。 - 相同视频内容(SHA-256)+ 相同候选清单会命中结果缓存(跨
surgery_id复用),响应message中含cache=hit或cache=miss。 candidate_consumables_json的语义与 5.2 节candidate_consumables一致(名称、产品编码、医院导出对象);缺省"[]"时展开为 yaml 全部类名。
请求体(multipart)
| 字段名 | 类型 | 必填 | 说明 |
|---|---|---|---|
surgery_id |
string |
是 | 6 位数字 |
video1 |
file |
是 | 单路完整 MP4;空文件返回 422 |
candidate_consumables_json |
string |
否 | JSON 数组字符串,默认 "[]";格式同 5.2 节候选清单 |
include_visualization |
boolean |
否 | 是否后台生成标注 MP4;默认 false。为 true 时响应含 visualization_url,标注视频异步生成,需轮询 5.8 节 |
响应体(200)
| 字段 | 类型 | 说明 |
|---|---|---|
surgery_id |
string |
与请求一致 |
status |
string |
成功时为 accepted |
message |
string |
含 rows= 行数、`cache=hit |
visualization_url |
string | null |
include_visualization=true 时为相对路径 /internal/demo/offline-batch/{surgery_id}/visualization;否则为 null |
doctor_name |
string | null |
算法输出的医生姓名(若识别到) |
doctor_id |
string | null |
医生 ID(若识别到) |
doctor_display |
string | null |
展示用医生信息 |
text_duration_sec |
number |
TSV / 主流程耗时(秒,保留 3 位小数) |
video_duration_sec |
number | null |
标注视频耗时;未请求或仍在后台时为 null |
total_duration_sec |
number |
已完成阶段的合计耗时(秒) |
状态码
| HTTP | 说明 |
|---|---|
200 |
离线推理已完成,结果已入库(可能为零条明细) |
404 |
Demo 模式未启用 |
422 |
surgery_id 非 6 位、candidate_consumables_json 非法、video1 为空等 |
500 |
上传落盘失败 |
503 |
离线 batch 子进程失败;detail 形如 offline batch failed: ... |
后续步骤
- 调用
GET /client/surgeries/{surgery_id}/result(5.4 节)查询消耗明细。 - 若
include_visualization=true,轮询GET .../offline-batch/{surgery_id}/timing(5.8 节)直至video_status为ready或failed,再GET .../visualization(5.9 节)下载 MP4。
cURL 示例
curl -sS -X POST "http://<主机>:38080/internal/demo/offline-batch" \
-F "surgery_id=123456" \
-F "video1=@/path/to/surgery.mp4;type=video/mp4" \
-F 'candidate_consumables_json=["14764-2-4","8036-5-22"]' \
-F "include_visualization=true"
响应示例(200)
{
"surgery_id": "123456",
"status": "accepted",
"message": "非实时精确视频处理完成;rows=3 cache=miss;医生=张三 (6611);标注视频后台生成中(完成后刷新 visualization URL,24 小时内有效)",
"visualization_url": "/internal/demo/offline-batch/123456/visualization",
"doctor_name": "张三",
"doctor_id": "6611",
"doctor_display": "张三 (6611)",
"text_duration_sec": 842.5,
"video_duration_sec": null,
"total_duration_sec": 842.5
}
5.8 查询离线 batch 各阶段耗时
基本信息
| 项目 | 内容 |
|---|---|
| 方法 | GET |
| 路径 | /internal/demo/offline-batch/{surgery_id}/timing |
| 路径参数 | surgery_id(6 位数字) |
| 请求体 | 无 |
业务说明
- 在 5.7 节上传完成后可用;用于轮询标注视频是否生成完毕。
video_status:skipped(未请求标注视频)、pending(后台生成中)、ready(可下载)、failed(生成失败)。
响应体(200)
| 字段 | 类型 | 说明 |
|---|---|---|
surgery_id |
string |
手术号 |
text_duration_sec |
number |
TSV 主流程耗时(秒) |
video_duration_sec |
number | null |
标注视频耗时;未完成时为 null |
total_duration_sec |
number |
合计耗时(秒) |
video_status |
string |
skipped / pending / ready / failed |
状态码
| HTTP | 说明 |
|---|---|
200 |
有该 surgery_id 的 timing 记录 |
404 |
Demo 未启用,或尚无 timing 记录 |
422 |
surgery_id 不符合约束 |
5.9 获取离线 batch 标注视频
基本信息
| 项目 | 内容 |
|---|---|
| 方法 | GET |
| 路径 | /internal/demo/offline-batch/{surgery_id}/visualization |
| 路径参数 | surgery_id(6 位数字) |
| 请求体 | 无 |
业务说明
- 仅当 5.7 节
include_visualization=true且后台生成成功时可用。 - 响应为
video/mp4文件流,文件名{surgery_id}_result_vis.mp4。 - 标注文件默认保留 24 小时(
VIDEO_BATCH_VIS_TTL_HOURS),过期后返回404。
状态码
| HTTP | 说明 |
|---|---|
200 |
MP4 文件流 |
404 |
Demo 未启用、尚未生成或已过期 |
422 |
surgery_id 不符合约束 |
5.10 语音终端 assignment(HTTP,可选)
路径 GET /client/voice-terminals/{terminal_id}/assignment
仓库内 手术室耗材语音确认浏览器客户端(clients/voice-confirmation/)仅通过 §5.11 WebSocket 接收指派,不调用本接口。此处供运维脚本、未实现 WS 的第三方临时拉取 active_surgery_id。
响应 200
| 字段 | 类型 | 说明 |
|---|---|---|
voice_terminal_id |
string |
与路径一致 |
active_surgery_id |
string | null |
当前指派手术 6 位号;无指派时为 null |
5.11 语音终端 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(由网关或客户端库实现)。