"""Tests for messy-notes routing into per-section RICS buckets. These tests pin the deterministic keyword router (no LLM). The router has to be reliable offline because the front-end calls it on every upload, and the LLM refinement pass is opt-in. """ from __future__ import annotations import pytest from app.generator.notes_router import route_notes @pytest.mark.asyncio async def test_route_explicit_section_code_wins() -> None: lines = ["E2: tiles slipped on north pitch"] out = await route_notes(lines, survey_level=3) assert out.bullets_by_section.get("E2") == ["E2: tiles slipped on north pitch"] @pytest.mark.asyncio async def test_route_roof_keywords_to_E2() -> None: lines = ["several roof tiles slipped at the ridge"] out = await route_notes(lines, survey_level=3) assert "E2" in out.bullets_by_section assert any("tiles slipped" in b for b in out.bullets_by_section["E2"]) @pytest.mark.asyncio async def test_route_boiler_to_G2() -> None: lines = ["boiler 18 years old, no recent service certificate"] out = await route_notes(lines, survey_level=3) assert "G2" in out.bullets_by_section @pytest.mark.asyncio async def test_route_damp_to_F9() -> None: lines = ["rising damp visible at rear hallway skirting"] out = await route_notes(lines, survey_level=3) # damp is the F9 primary signal; skirting also touches F7 — confirm damp wins assert "F9" in out.bullets_by_section @pytest.mark.asyncio async def test_route_unmatched_line_returned_in_unrouted() -> None: lines = ["[surveyor signature on file]"] out = await route_notes(lines, survey_level=3) # Generic chatter that doesn't map cleanly to any section assert out.unrouted_lines == lines or "__unrouted__" not in out.bullets_by_section @pytest.mark.asyncio async def test_route_mixed_dump_splits_across_sections() -> None: lines = [ "tile slipped on rear pitch", "gutters overflowing at front elevation", "consumer unit pre-2008, no RCD", "rising damp at rear hallway", ] out = await route_notes(lines, survey_level=3) codes = set(out.bullets_by_section.keys()) # Each of these should land in a distinct section assert "E2" in codes # roof tile assert "E3" in codes # gutters assert "G1" in codes # consumer unit / RCD assert "F9" in codes # damp @pytest.mark.asyncio async def test_route_respects_survey_level() -> None: lines = ["roof tiles slipped"] out_l1 = await route_notes(lines, survey_level=1) out_l3 = await route_notes(lines, survey_level=3) # Both packs have an E2-like roof section; the routing should succeed for both. assert any(k.startswith("E") for k in out_l1.bullets_by_section) assert any(k.startswith("E") for k in out_l3.bullets_by_section)