Files
operating-room-monitor-server/app/services/surgery_pipeline.py
Kevin 69980d8073 feat: align surgery API with schemas and extend client tooling
- Refactor app API and schemas; adjust surgery pipeline, repository, and session manager.

- Improve consumption TSV logging and consumable vision integration; trim voice resolution.

- Add Baidu Face 1:N search script, .env.example entries, and client API integration doc.

- Update demo client, staging checklist, surgery interface doc, and related tests; add sample face image.

Made-with: Cursor
2026-04-23 16:09:20 +08:00

125 lines
4.4 KiB
Python
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.
"""手术录制与实时算法流水线(待接入真实子系统)。"""
from __future__ import annotations
import base64
from app.database import AsyncSessionLocal
from app.repositories.surgery_results import SurgeryResultRepository
from app.schemas import (
PendingConfirmationOption,
SurgeryConsumptionDetail,
SurgeryPendingConfirmationResponse,
)
from app.services.video.session_manager import CameraSessionManager
from fastapi.concurrency import run_in_threadpool
from app.services.voice_resolution import VoiceConfirmationService, VoiceResolveResult
from app.surgery_errors import SurgeryPipelineError
class SurgeryPipeline:
"""协调开录、停录与算法产出。路由仅在子系统确认后返回 HTTP 200。"""
def __init__(
self,
sessions: CameraSessionManager,
*,
result_repository: SurgeryResultRepository,
voice_confirmation: VoiceConfirmationService,
) -> None:
self._sessions = sessions
self._repo = result_repository
self._voice = voice_confirmation
async def start_recording(
self,
surgery_id: str,
camera_ids: list[str],
candidate_consumables: list[str],
) -> None:
"""启动关联摄像头录制。仅在确认已开录时返回;否则抛出 SurgeryPipelineError。"""
try:
await self._sessions.start_surgery(
surgery_id,
camera_ids,
candidate_consumables,
)
except SurgeryPipelineError:
raise
except ValueError as exc:
raise SurgeryPipelineError(
"RECORDING_CANNOT_START",
f"开录未能确认:{exc}",
) from exc
except RuntimeError as exc:
raise SurgeryPipelineError(
"RECORDING_CANNOT_START",
f"开录未能确认:{exc}",
) from exc
async def stop_recording(self, surgery_id: str) -> None:
"""停止该手术关联的摄像头录制。仅在确认已全部停录时返回。"""
try:
await self._sessions.stop_surgery(surgery_id, require_active=True)
except SurgeryPipelineError:
raise
async def get_consumption_details_for_client(
self,
surgery_id: str,
) -> list[SurgeryConsumptionDetail] | None:
"""进行中:返回内存明细;已结束:返回数据库最终结果;持久化失败时回退内存归档。"""
live = self._sessions.live_consumption_if_active(surgery_id)
if live is not None:
return live
async with AsyncSessionLocal() as session:
async with session.begin():
persisted = await self._repo.load_final_details(session, surgery_id)
if persisted is not None:
return persisted
return self._sessions.archived_consumption_fallback(surgery_id)
async def get_pending_confirmation_for_client(
self, surgery_id: str
) -> SurgeryPendingConfirmationResponse | None:
pending = self._sessions.next_pending_confirmation(surgery_id)
if pending is None:
return None
mp3 = await run_in_threadpool(
self._voice.synthesize_prompt_to_mp3,
pending.prompt_text,
)
b64 = base64.b64encode(mp3).decode("ascii")
return SurgeryPendingConfirmationResponse(
surgery_id=surgery_id,
confirmation_id=pending.id,
prompt_text=pending.prompt_text,
prompt_audio_mp3_base64=b64,
options=[
PendingConfirmationOption(label=a, confidence=b)
for a, b in pending.options
],
model_top1_label=pending.model_top1_label,
model_top1_confidence=pending.model_top1_confidence,
created_at=pending.created_at,
)
async def resolve_pending_confirmation_from_audio(
self,
surgery_id: str,
confirmation_id: str,
wav_bytes: bytes,
filename: str,
content_type: str | None,
) -> VoiceResolveResult:
"""上传医生语音 WAVMinIO 追溯 + 百度 ASR + 解析候选项并完成确认。"""
return await self._voice.resolve_from_wav(
surgery_id=surgery_id,
confirmation_id=confirmation_id,
wav_bytes=wav_bytes,
filename=filename,
content_type=content_type,
)