Files
operating-room-monitor-server/backend/tests/test_session_rank.py
Kevin 1af442481e 重组为 backend/clients/docs 三层结构,并清理 git 污染。
将后端迁入 backend/,完善根目录 .gitignore,删除误提交的 .mypy_cache 缓存文件。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-21 16:02:25 +08:00

102 lines
3.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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