Kansho/backend/tests/test_detect_pipeline.py
Lars 0277039b07
All checks were successful
Deploy Development / deploy (push) Successful in 54s
Test Suite / pytest-backend (push) Successful in 3m7s
Test Suite / smoke-dev (push) Successful in 0s
Test Suite / frontend-build (push) Successful in 16s
Keep confirmed detect cues from opening a second type-conflict review.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-12 13:28:08 +02:00

80 lines
3.9 KiB
Python

"""Unit tests for student/teacher comparison. No database."""
from __future__ import annotations
import os
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
os.environ.setdefault("KANSHO_PROVIDER_KEY", "")
os.environ.setdefault("KANSHO_DETECT_PROVIDER_KEY", "")
from unittest.mock import patch
from detect_learning import canonicalize_detect_mode, decision_for_mention
from detect_pipeline import (
MentionSpan,
compact_diff_counters,
compare_mentions,
map_entity_type,
protects,
same_mention,
types_equivalent,
)
def expect(ok: bool, message: str) -> None:
if not ok:
raise SystemExit(f"FAIL: {message}")
print(f"OK {message}")
def main() -> None:
expect(canonicalize_detect_mode("learning") == "learning_full", "learning aliases to learning_full")
expect(canonicalize_detect_mode("semantic") == "bypass", "semantic aliases to bypass")
expect(canonicalize_detect_mode("wordlist") is None, "unknown mode is rejected")
expect(map_entity_type("person") == "PERSON", "person maps to PERSON")
expect(map_entity_type("place") == "PLACE", "place maps to PLACE")
expect(protects("PERSON") and not protects("FOOD"), "PERSON protects, FOOD does not")
expect(types_equivalent("PERSON", "PLACE"), "similar protectable types are equivalent")
expect(not types_equivalent("FOOD", "PERSON"), "FOOD versus PERSON is never equivalent")
guessed = MentionSpan("Sushi", 20, 25, "PERSON", "identity", "candidate", False, "aß")
teacher_food = MentionSpan("Sushi", 20, 25, "FOOD", "not_identity", "teacher", False, "aß")
offered, offered_counters = compare_mentions("p", "Ich aß Sushi.", [guessed], [teacher_food])
expect(offered_counters["diff_type_conflict"] == 1, "unconfirmed FOOD versus PERSON is counted")
expect(any(item.get("reason") == "type_conflict" for item in offered), "unconfirmed type conflict is offered")
confirmed = MentionSpan("Sushi", 20, 25, "FOOD", "not_identity", "cue", True, "rohan")
teacher_person = MentionSpan("Sushi", 20, 25, "PERSON", "identity", "teacher", False, "rohan")
kept, kept_counters = compare_mentions("p", "Rohan Sushi essen", [confirmed], [teacher_person])
expect(kept_counters["diff_type_conflict"] == 1, "confirmed cue still counts FOOD versus PERSON")
expect(not kept, "confirmed cue is not re-asked on type conflict")
inner = MentionSpan("Sushi", 20, 25, "PERSON", "identity", "cue", True, "frau")
outer = MentionSpan("Frau Sushi", 15, 25, "PERSON", "identity", "teacher", False, "frau")
expect(same_mention(inner, outer), "contained span is the same mention")
food = MentionSpan("Sushi", 20, 25, "FOOD", "not_identity", "teacher", False, "aß")
expect(same_mention(inner, food), "same offsets stay one mention even when types differ")
compact = compact_diff_counters({"diff_agree": 2, "diff_type_conflict": 1, "label": "Sushi"})
expect(compact["diff_agree"] == 2 and compact["diff_type_conflict"] == 1, "known counters are kept")
expect("label" not in compact and "Sushi" not in str(compact), "compact counters drop plaintext")
with patch("detect_learning.get_cue_decision", return_value=None), patch(
"detect_learning.get_sense",
return_value={"identity_hits": 3, "non_identity_hits": 0, "ambiguous": False},
), patch("detect_learning.cue_for_mention", return_value="aß"):
expect(
decision_for_mention("p", "Sushi", "Ich aß Sushi.", 7, 12) == "ask",
"Tag-21: identity-only sense does not lock a new cue",
)
with patch("detect_learning.get_cue_decision", return_value="identity"), patch(
"detect_learning.cue_for_mention", return_value="frau"
):
expect(
decision_for_mention("p", "Sushi", "meine Frau Sushi", 11, 16) == "identity",
"stored cue remains binding",
)
print("detect pipeline unit ok")
if __name__ == "__main__":
main()