重组为 backend/clients/docs 三层结构,并清理 git 污染。
将后端迁入 backend/,完善根目录 .gitignore,删除误提交的 .mypy_cache 缓存文件。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
101
backend/tests/test_session_rank.py
Normal file
101
backend/tests/test_session_rank.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import pytest
|
||||
|
||||
from app.algorithm_ipc.schema import WhitelistSpec
|
||||
from app.algorithm_runner.actionformer_gated.runner import ActionFormerSegmentRecord
|
||||
from app.algorithm_runner.segment_policy import (
|
||||
events_for_tear_record,
|
||||
pad_ranked_candidates_for_voice,
|
||||
rank_topk_for_candidates,
|
||||
)
|
||||
from app.domain.vision_prediction import PredictionCandidate
|
||||
from app.services.voice_confirm import build_prompt_text
|
||||
|
||||
|
||||
def test_rank_preserves_model_order_within_whitelist() -> None:
|
||||
"""清单顺序可能与模型置信度顺序不同;播报「第n个」应与模型 top1/top2 一致。"""
|
||||
topk = [
|
||||
PredictionCandidate(label="缝线", confidence=0.9),
|
||||
PredictionCandidate(label="纱布", confidence=0.5),
|
||||
]
|
||||
ordered = ["纱布", "缝线"]
|
||||
ranked = rank_topk_for_candidates(topk, ordered)
|
||||
assert [c.label for c in ranked] == ["缝线", "纱布"]
|
||||
|
||||
|
||||
def test_rank_without_candidates_keeps_model_order() -> None:
|
||||
topk = [
|
||||
PredictionCandidate(label="a", confidence=0.9),
|
||||
PredictionCandidate(label="b", confidence=0.5),
|
||||
]
|
||||
ranked = rank_topk_for_candidates(topk, [])
|
||||
assert [c.label for c in ranked] == ["a", "b"]
|
||||
|
||||
|
||||
def test_rank_drops_labels_not_on_surgery_whitelist() -> None:
|
||||
topk = [
|
||||
PredictionCandidate(label="缝线", confidence=0.9),
|
||||
PredictionCandidate(label="陌生类", confidence=0.5),
|
||||
PredictionCandidate(label="纱布", confidence=0.1),
|
||||
]
|
||||
ordered = ["纱布", "缝线"]
|
||||
ranked = rank_topk_for_candidates(topk, ordered)
|
||||
assert [c.label for c in ranked] == ["缝线", "纱布"]
|
||||
|
||||
|
||||
def test_pad_voice_options_fills_from_surgery_list() -> None:
|
||||
ranked = [PredictionCandidate(label="手套", confidence=0.88)]
|
||||
order = ["手套", "纱布", "缝线", "钳子"]
|
||||
out = pad_ranked_candidates_for_voice(ranked, order)
|
||||
assert [c.label for c in out] == ["手套", "纱布", "缝线"]
|
||||
assert out[0].confidence == pytest.approx(0.88)
|
||||
assert out[1].confidence == 0.0 and out[2].confidence == 0.0
|
||||
|
||||
|
||||
def test_pad_voice_respects_max_options() -> None:
|
||||
out = pad_ranked_candidates_for_voice([], ["a", "b", "c"], max_options=2)
|
||||
assert [c.label for c in out] == ["a", "b"]
|
||||
|
||||
|
||||
def test_events_for_tear_record_voice_has_three_options(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
from app.baked import pipeline as p
|
||||
|
||||
monkeypatch.setattr(p, "VOICE_CONFIRMATION_ENABLED", True, raising=False)
|
||||
monkeypatch.setattr(p, "VIDEO_AUTO_CONFIRM_CONFIDENCE", 0.99, raising=False)
|
||||
monkeypatch.setattr(p, "VIDEO_VOICE_CONFIRM_MIN_CONFIDENCE", 0.1, raising=False)
|
||||
|
||||
wl = WhitelistSpec(
|
||||
candidate_consumables=["手套", "纱布", "缝线"],
|
||||
name_to_id={"手套": "g1", "纱布": "g2", "缝线": "g3"},
|
||||
)
|
||||
rec = ActionFormerSegmentRecord(
|
||||
segment_index=1,
|
||||
start_sec=0.0,
|
||||
end_sec=2.0,
|
||||
mid_stream_sec=1.0,
|
||||
item_id="g1",
|
||||
item_name="手套",
|
||||
top1_conf=0.55,
|
||||
top2_name="",
|
||||
top2_conf=0.0,
|
||||
top3_name="",
|
||||
top3_conf=0.0,
|
||||
majority_ref="",
|
||||
)
|
||||
evs = events_for_tear_record(
|
||||
rec,
|
||||
wl=wl,
|
||||
camera_id="cam",
|
||||
wall_lo=100.0,
|
||||
wall_hi=102.0,
|
||||
cooldown_key="k",
|
||||
)
|
||||
assert len(evs) == 1
|
||||
assert evs[0]["type"] == "needs_voice_confirm"
|
||||
opts = evs[0]["options"]
|
||||
assert len(opts) == 3
|
||||
assert [o["label"] for o in opts] == ["手套", "纱布", "缝线"]
|
||||
text = build_prompt_text([(str(o["label"]), float(o["confidence"])) for o in opts])
|
||||
assert "第1个,手套。" in text
|
||||
assert "第2个,纱布。" in text
|
||||
assert "第3个,缝线。" in text
|
||||
|
||||
Reference in New Issue
Block a user