""" Unit tests for eval/grounded.py — shared matching primitives + check_no_invention. These tests use the same sys.path bootstrap as test_eval_matching.py. No app import needed — grounded.py is stdlib-only. Test coverage: - check_no_invention: returns [] when all deadline values appear verbatim in transcription - check_no_invention: returns non-empty list when a deadline value is NOT in transcription - normalize, value_found, extract_values_from_section: return identical results to known cases from test_eval_matching (regression against moved functions) - import run_eval still works after refactor (eval-compat regression) """ import os import sys # Ensure project root and eval/ are on the path regardless of where pytest is run from. _REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) _EVAL_DIR = os.path.join(_REPO_ROOT, "eval") if _REPO_ROOT not in sys.path: sys.path.insert(0, _REPO_ROOT) if _EVAL_DIR not in sys.path: sys.path.insert(0, _EVAL_DIR) import grounded # direct import (eval/ is on sys.path) from types import SimpleNamespace as S # --------------------------------------------------------------------------- # check_no_invention: no invented values — returns empty list # --------------------------------------------------------------------------- def test_check_no_invention_returns_empty_when_all_values_in_transcription(): """Returns [] when every deadline value appears verbatim in the transcription.""" result = S( transcription="betala 1 234 kr senast 2026-06-15", deadlines="- 2026-06-15 — sista dag\n- 1 234 kr — belopp", ) invented = grounded.check_no_invention(result) assert invented == [] def test_check_no_invention_returns_nonempty_for_invented_value(): """Returns a non-empty list when a deadline value is NOT in transcription.""" result = S( transcription="betala senast 2026-06-15", # "2026-07-99" NOT in transcription deadlines="- 2026-07-99 — invented date\n- 2026-06-15 — real date", ) invented = grounded.check_no_invention(result) assert len(invented) >= 1 assert "2026-07-99" in invented def test_check_no_invention_empty_deadlines_returns_empty(): """Empty deadlines section → no values extracted → returns [].""" result = S( transcription="betala senast 2026-06-15", deadlines="", ) invented = grounded.check_no_invention(result) assert invented == [] def test_check_no_invention_none_found_returns_empty(): """'None found.' sentinel in deadlines → no values extracted → returns [].""" result = S( transcription="informational letter", deadlines="None found.", ) invented = grounded.check_no_invention(result) assert invented == [] def test_check_no_invention_all_invented_returns_all(): """All values invented → all returned in invented list.""" result = S( transcription="hej det här är ett informationsbrev", deadlines="- 2099-01-01 — fake\n- 99 999 kr — fake amount", ) invented = grounded.check_no_invention(result) assert len(invented) == 2 # --------------------------------------------------------------------------- # Regression: moved functions return identical results to test_eval_matching cases # --------------------------------------------------------------------------- def test_normalize_case_insensitive(): """normalize lowercases input (regression from test_eval_matching).""" assert grounded.normalize("1 234,56 KR") == grounded.normalize("1 234,56 kr") def test_normalize_collapses_multiple_spaces(): """Multiple spaces collapse to single space.""" assert grounded.normalize("a b") == "a b" def test_value_found_long_date_in_transcription(): """An ISO date is found when present.""" haystack = "Du ska betala senast 2026-06-15. Glöm inte." assert grounded.value_found("2026-06-15", haystack) is True def test_value_found_short_boundary_inside_longer(): """A 3-digit ref is NOT found inside a longer run of digits.""" haystack = "ref 1234567 i systemet" assert grounded.value_found("123", haystack) is False def test_value_found_short_boundary_standalone(): """A 3-digit ref IS found when standing alone.""" haystack = "OCR-nummer 123 gäller" assert grounded.value_found("123", haystack) is True def test_extract_em_dash_separator(): """Em-dash separator: verbatim value before ' — '.""" section = "- 15 juni 2026 — last day to file\n- 1 234 kr — amount owed" vals = grounded.extract_values_from_section(section) assert vals == ["15 juni 2026", "1 234 kr"] def test_extract_hyphen_with_spaces_separator(): """Hyphen-with-spaces separator.""" section = "- 2026-06-15 - sista betalningsdag\n- 1 234 kr - fakturabelopp" vals = grounded.extract_values_from_section(section) assert vals == ["2026-06-15", "1 234 kr"] def test_extract_none_found_yields_empty(): """'None found.' lines yield no values.""" assert grounded.extract_values_from_section("None found.") == [] def test_extract_date_with_internal_hyphens_preserved(): """ISO date is not split by internal hyphens.""" section = "- 2026-06-15 — sista dag" vals = grounded.extract_values_from_section(section) assert vals == ["2026-06-15"] # --------------------------------------------------------------------------- # Eval-compat regression: import run_eval still works after refactor # --------------------------------------------------------------------------- def test_import_run_eval_succeeds(): """import run_eval still works after the grounded.py refactor.""" import run_eval # noqa: F401 — just checking importability assert hasattr(run_eval, "normalize"), "run_eval.normalize must be re-bound" assert hasattr(run_eval, "value_found"), "run_eval.value_found must be re-bound" assert hasattr(run_eval, "extract_values_from_section"), ( "run_eval.extract_values_from_section must be re-bound" ) def test_run_eval_normalize_still_accessible_via_namespace(): """import run_eval as e; e.normalize(...) still resolves after refactor.""" import run_eval as e # Same case as test_eval_matching.py lines 44-45 assert e.normalize("A b") == "a b" if __name__ == "__main__": import pytest pytest.main([__file__, "-v"])