- 新增 Alembic 初始迁移、领域明细模型及归档持久化与重试链路\n- 拆分视频会话注册表、分类处理、推理时间窗聚合与流处理\n- 消耗日志:TSV/Markdown 含 top2/top3;item_id 优先产品编码;待确认记「待确认」行,语音确认后落正式行并更新汇总\n- 待确认时内存/DB 明细为占位行,确认后替换;拒绝时移除占位\n- 分类 probs 先 detach/cpu 再转 NumPy,修复 MPS/CUDA 上推理被静默跳过\n- 补充集成测试、归档与设备张量等单测 Made-with: Cursor
16 KiB
手术室监控服务:客户端手术通信接口说明
面向对接本 FastAPI 服务的客户端(HIS、手麻、工作站等)。字段、状态码与返回模型以 /docs 和 /openapi.json 为准,本文用于摘要和流程说明。
[!summary] 常用响应模型
SurgeryApiResponseSurgeryResultResponseSurgeryPendingConfirmationResponse- 业务错误外形见
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/jsonresult使用GETresolve使用POST+multipart/form-datasurgery_id固定为 6 位数字,正则为^\d{6}$resolve路径中的confirmation_id必须与待确认接口返回值一致camera_ids必须与第 2 节清单及运维配置完全一致
1. 服务与基础信息
- 协议:
HTTP/HTTPS - 端口:
38080,生产环境以实际入口为准 - 路由:无全局前缀;业务接口位于
/client/...,健康检查位于/health start/end请求体:JSONresolve请求体: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 文档整理后交给运维) - 备注:术间、机位
- RTSP:
or-cam-02- RTSP:
... - 备注:
...
- RTSP:
[!warning] 对接要求
camera_ids必须与运维配置中的 key 完全一致- RTSP 不应硬编码在客户端业务程序中
[!tip] 联调建议
- 运维配置完成后,客户端使用上面清单中的
camera_id调用start验证是否返回200- 若返回
503且detail.code = RECORDING_CANNOT_START,优先核对 ID 拼写以及监控服务器侧网络连通性
3. HTTP 路由一览
GET /health:探活POST /client/surgeries/start:开始手术POST /client/surgeries/end:结束手术GET /client/surgeries/{surgery_id}/result:查询手术结果GET /client/surgeries/{surgery_id}/pending-confirmation:拉取待确认耗材POST /client/surgeries/{surgery_id}/pending-confirmation/{confirmation_id}/resolve:提交待确认结果(WAV)
4. 流程
4.1 时序图
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 .../resolve(multipart 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 状态图
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:
{
"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中的每个摄像头建立拉流与推理任务,只有在确认开录成功(如首帧就绪)后才返回 HTTP200 - 若同一
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
请求示例
{
"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 示例为 摄像头录制已停止,手术已结束。
状态码
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
- 类型:
状态码
200:至少有一条明细422:surgery_id路径不符合约束503:RESULT_NOT_READY,当前无可用明细或不可查
响应示例(200)
{
"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_CONFIRMATION422:例如话术为空导致无法 TTS;错误码见响应,如TTS_TEXT_EMPTY503:语音服务未配置或 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_FOUND409:当前确认项已处理过;例如CONFIRMATION_ALREADY_RESOLVED422:空文件、非.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"