Files
operating-room-monitor-server/backend/tests/test_session_rank.py

102 lines
3.6 KiB
Python
Raw Normal View History

2026-05-21 15:48:03 +08:00
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