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:
Kevin
2026-04-23 20:42:21 +08:00
parent 69980d8073
commit 3d7bd70355
55 changed files with 4544 additions and 2050 deletions

View File

@@ -6,7 +6,7 @@ from sqlalchemy import delete, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.models import SurgeryFinalResult, SurgeryResultDetailRow
from app.schemas import SurgeryConsumptionDetail, SurgeryConsumptionStored
from app.domain.consumption import SurgeryConsumptionStored
class SurgeryResultRepository:
@@ -47,7 +47,8 @@ class SurgeryResultRepository:
async def load_final_details(
self, session: AsyncSession, surgery_id: str
) -> list[SurgeryConsumptionDetail] | None:
) -> list[SurgeryConsumptionStored] | None:
"""返回领域对象列表(含 sourceHTTP 层的转换由 pipeline 负责。"""
res = await session.execute(
select(SurgeryFinalResult).where(SurgeryFinalResult.surgery_id == surgery_id)
)
@@ -60,13 +61,21 @@ class SurgeryResultRepository:
.order_by(SurgeryResultDetailRow.id)
)
rows = q.scalars().all()
return [
SurgeryConsumptionDetail(
item_id=r.item_id,
item_name=r.item_name,
qty=r.quantity,
doctor_id=r.doctor_id,
timestamp=r.recorded_at,
out: list[SurgeryConsumptionStored] = []
for r in rows:
pend: str | None = None
iid = r.item_id
if iid.startswith("pending:"):
pend = iid.removeprefix("pending:")
out.append(
SurgeryConsumptionStored(
item_id=r.item_id,
item_name=r.item_name,
qty=r.quantity,
doctor_id=r.doctor_id,
timestamp=r.recorded_at,
source=r.source,
pending_confirmation_id=pend,
)
)
for r in rows
]
return out