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

@@ -30,7 +30,7 @@ def test_live_consumption_requires_non_empty_details() -> None:
)
st = SurgerySessionState(candidate_consumables=["纱布"])
run = RunningSurgery(stop_event=asyncio.Event(), state=st, tasks=[])
mgr._active["123456"] = run
mgr._registry._active["123456"] = run
st.ready.set()
assert mgr.live_consumption_if_active("123456") is None
@@ -59,7 +59,7 @@ async def test_resolve_voice_accepts_label_on_surgery_list_not_in_topk_options()
model_top1_confidence=0.41,
)
st.pending_fifo.append(pid)
mgr._active["123456"] = RunningSurgery(
mgr._registry._active["123456"] = RunningSurgery(
stop_event=asyncio.Event(), state=st, tasks=[]
)
@@ -95,7 +95,7 @@ async def test_resolve_pending_appends_voice_detail() -> None:
)
st.pending_fifo.append(pid)
run = RunningSurgery(stop_event=asyncio.Event(), state=st, tasks=[])
mgr._active["123456"] = run
mgr._registry._active["123456"] = run
await mgr.resolve_pending_confirmation(
"123456", pid, chosen_label="纱布", rejected=False
@@ -129,7 +129,7 @@ async def test_resolve_reject_closes_without_detail() -> None:
model_top1_confidence=0.4,
)
st.pending_fifo.append(pid)
mgr._active["123456"] = RunningSurgery(
mgr._registry._active["123456"] = RunningSurgery(
stop_event=asyncio.Event(), state=st, tasks=[]
)
@@ -171,14 +171,10 @@ async def test_archive_retry_loop_starts() -> None:
result_repository=None,
)
await mgr.start_archive_retry_loop()
assert mgr._retry_task is not None
mgr._retry_stop.set()
mgr._retry_task.cancel()
try:
await mgr._retry_task
except asyncio.CancelledError:
pass
mgr._retry_task = None
persister = mgr._archive
assert persister._retry_task is not None
await mgr.shutdown()
assert persister._retry_task is None
@pytest.mark.asyncio
@@ -242,9 +238,12 @@ async def test_handle_high_conf_top1_not_in_candidates_enqueues_pending() -> Non
],
)
await mgr._handle_classification_result(state=state, cls_res=res)
assert state.details == []
assert len(state.details) == 1
assert state.details[0].item_name == "待确认"
assert state.details[0].source == "pending_confirmation"
assert len(state.pending_fifo) == 1
pid = state.pending_fifo[0]
assert state.details[0].pending_confirmation_id == pid
assert "缝线" in state.pending_by_id[pid].prompt_text
@@ -270,6 +269,8 @@ async def test_handle_mid_confidence_enqueues_pending() -> None:
)
await mgr._handle_classification_result(state=state, cls_res=res)
assert len(state.pending_fifo) == 1
assert len(state.details) == 1
assert state.details[0].item_name == "待确认"
@pytest.mark.asyncio
@@ -360,7 +361,7 @@ async def test_resolve_invalid_chosen_label() -> None:
model_top1_confidence=0.4,
)
st.pending_fifo.append(pid)
mgr._active["123456"] = RunningSurgery(
mgr._registry._active["123456"] = RunningSurgery(
stop_event=asyncio.Event(), state=st, tasks=[]
)
with pytest.raises(SurgeryPipelineError) as excinfo:
@@ -407,7 +408,7 @@ async def test_resolve_second_time_not_found() -> None:
model_top1_confidence=0.4,
)
st.pending_fifo.append(pid)
mgr._active["123456"] = RunningSurgery(
mgr._registry._active["123456"] = RunningSurgery(
stop_event=asyncio.Event(), state=st, tasks=[]
)
await mgr.resolve_pending_confirmation(
@@ -442,7 +443,7 @@ async def test_resolve_already_resolved_status() -> None:
)
st.pending_by_id[pid] = pending
st.pending_fifo.append(pid)
mgr._active["123456"] = RunningSurgery(
mgr._registry._active["123456"] = RunningSurgery(
stop_event=asyncio.Event(), state=st, tasks=[]
)
pending.status = "confirmed"