feat: 语音确认、联调与运维增强

- 语音:序数解析(第一个/第二个等)、解析失败计数与 API detail.retry_remaining;
  百度 ASR 固定 dev_pid 为普通话;SurgeryPipelineError 支持 extra 并入 HTTP detail。
- Demo:demo 路由与假 RTSP、客户端 index 与 README;BackendResolver 与配置调整。
- 可观测:消耗 TSV 日志、语音文件日志、终端 Markdown 辅助;相关测试与依赖更新。
- 注意:.env 仍被 gitignore,本地密钥不会进入本提交。

Made-with: Cursor
This commit is contained in:
Kevin
2026-04-23 14:24:20 +08:00
parent 42720f81cf
commit 0c05463617
39 changed files with 3030 additions and 143 deletions

View File

@@ -156,9 +156,7 @@ async def test_handle_skips_when_candidate_list_empty() -> None:
confidence=0.99,
topk=[PredictionCandidate(label="纱布", confidence=0.99)],
)
await mgr._handle_classification_result(
state=state, cls_res=res
)
await mgr._handle_classification_result(state=state, cls_res=res)
assert state.details == []
assert state.pending_fifo == []
@@ -199,9 +197,7 @@ async def test_handle_skips_below_voice_floor() -> None:
confidence=0.4,
topk=[PredictionCandidate(label="纱布", confidence=0.4)],
)
await mgr._handle_classification_result(
state=state, cls_res=res
)
await mgr._handle_classification_result(state=state, cls_res=res)
assert state.details == []
assert state.pending_fifo == []
@@ -221,9 +217,7 @@ async def test_handle_auto_vision_confirm() -> None:
confidence=0.99,
topk=[PredictionCandidate(label="纱布", confidence=0.99)],
)
await mgr._handle_classification_result(
state=state, cls_res=res
)
await mgr._handle_classification_result(state=state, cls_res=res)
assert len(state.details) == 1
assert state.details[0].source == "vision"
assert state.details[0].item_id == "纱布"
@@ -247,9 +241,7 @@ async def test_handle_high_conf_top1_not_in_candidates_enqueues_pending() -> Non
PredictionCandidate(label="缝线", confidence=0.2),
],
)
await mgr._handle_classification_result(
state=state, cls_res=res
)
await mgr._handle_classification_result(state=state, cls_res=res)
assert state.details == []
assert len(state.pending_fifo) == 1
pid = state.pending_fifo[0]
@@ -276,9 +268,7 @@ async def test_handle_mid_confidence_enqueues_pending() -> None:
PredictionCandidate(label="缝线", confidence=0.3),
],
)
await mgr._handle_classification_result(
state=state, cls_res=res
)
await mgr._handle_classification_result(state=state, cls_res=res)
assert len(state.pending_fifo) == 1
@@ -299,9 +289,7 @@ async def test_handle_voice_disabled_no_pending_for_mid_conf() -> None:
confidence=0.5,
topk=[PredictionCandidate(label="纱布", confidence=0.5)],
)
await mgr._handle_classification_result(
state=state, cls_res=res
)
await mgr._handle_classification_result(state=state, cls_res=res)
assert state.pending_fifo == []
assert state.details == []
@@ -322,12 +310,8 @@ async def test_handle_vision_cooldown_skips_duplicate() -> None:
confidence=0.99,
topk=[PredictionCandidate(label="纱布", confidence=0.99)],
)
await mgr._handle_classification_result(
state=state, cls_res=res
)
await mgr._handle_classification_result(
state=state, cls_res=res
)
await mgr._handle_classification_result(state=state, cls_res=res)
await mgr._handle_classification_result(state=state, cls_res=res)
assert len(state.details) == 1
@@ -350,12 +334,8 @@ async def test_handle_pending_dedupe_cooldown() -> None:
PredictionCandidate(label="缝线", confidence=0.2),
],
)
await mgr._handle_classification_result(
state=state, cls_res=res
)
await mgr._handle_classification_result(
state=state, cls_res=res
)
await mgr._handle_classification_result(state=state, cls_res=res)
await mgr._handle_classification_result(state=state, cls_res=res)
assert len(state.pending_fifo) == 1