将后端迁入 backend/,完善根目录 .gitignore,删除误提交的 .mypy_cache 缓存文件。 Co-authored-by: Cursor <cursoragent@cursor.com>
102 lines
3.6 KiB
Python
102 lines
3.6 KiB
Python
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
|
||
|