feat: 手术视频消耗、待确认与持久化改造
- 新增 Alembic 初始迁移、领域明细模型及归档持久化与重试链路\n- 拆分视频会话注册表、分类处理、推理时间窗聚合与流处理\n- 消耗日志:TSV/Markdown 含 top2/top3;item_id 优先产品编码;待确认记「待确认」行,语音确认后落正式行并更新汇总\n- 待确认时内存/DB 明细为占位行,确认后替换;拒绝时移除占位\n- 分类 probs 先 detach/cpu 再转 NumPy,修复 MPS/CUDA 上推理被静默跳过\n- 补充集成测试、归档与设备张量等单测 Made-with: Cursor
This commit is contained in:
@@ -4,7 +4,10 @@ from __future__ import annotations
|
||||
|
||||
import base64
|
||||
|
||||
from sqlalchemy.ext.asyncio import async_sessionmaker
|
||||
|
||||
from app.database import AsyncSessionLocal
|
||||
from app.domain.consumption import SurgeryConsumptionStored
|
||||
from app.repositories.surgery_results import SurgeryResultRepository
|
||||
from app.schemas import (
|
||||
PendingConfirmationOption,
|
||||
@@ -18,6 +21,20 @@ from app.services.voice_resolution import VoiceConfirmationService, VoiceResolve
|
||||
from app.surgery_errors import SurgeryPipelineError
|
||||
|
||||
|
||||
def _stored_to_response(rows: list[SurgeryConsumptionStored]) -> list[SurgeryConsumptionDetail]:
|
||||
"""领域对象 → HTTP DTO 的单向转换,仅在返回给客户端的边界调用。"""
|
||||
return [
|
||||
SurgeryConsumptionDetail(
|
||||
item_id=r.item_id,
|
||||
item_name=r.item_name,
|
||||
qty=r.qty,
|
||||
doctor_id=r.doctor_id,
|
||||
timestamp=r.timestamp,
|
||||
)
|
||||
for r in rows
|
||||
]
|
||||
|
||||
|
||||
class SurgeryPipeline:
|
||||
"""协调开录、停录与算法产出。路由仅在子系统确认后返回 HTTP 200。"""
|
||||
|
||||
@@ -27,10 +44,12 @@ class SurgeryPipeline:
|
||||
*,
|
||||
result_repository: SurgeryResultRepository,
|
||||
voice_confirmation: VoiceConfirmationService,
|
||||
session_factory: async_sessionmaker | None = None,
|
||||
) -> None:
|
||||
self._sessions = sessions
|
||||
self._repo = result_repository
|
||||
self._voice = voice_confirmation
|
||||
self._session_factory: async_sessionmaker = session_factory or AsyncSessionLocal
|
||||
|
||||
async def start_recording(
|
||||
self,
|
||||
@@ -72,13 +91,16 @@ class SurgeryPipeline:
|
||||
"""进行中:返回内存明细;已结束:返回数据库最终结果;持久化失败时回退内存归档。"""
|
||||
live = self._sessions.live_consumption_if_active(surgery_id)
|
||||
if live is not None:
|
||||
return live
|
||||
async with AsyncSessionLocal() as session:
|
||||
return _stored_to_response(live)
|
||||
async with self._session_factory() 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)
|
||||
return _stored_to_response(persisted)
|
||||
archived = self._sessions.archived_consumption_fallback(surgery_id)
|
||||
if archived is not None:
|
||||
return _stored_to_response(archived)
|
||||
return None
|
||||
|
||||
async def get_pending_confirmation_for_client(
|
||||
self, surgery_id: str
|
||||
|
||||
Reference in New Issue
Block a user