Spaces:
Runtime error
Runtime error
| """Tests for the surgical accuracy fixes: property guard, note extraction, | |
| and 'specified' token resolution.""" | |
| from __future__ import annotations | |
| from types import SimpleNamespace | |
| from backend.core import property_blocklist | |
| from backend.core.property_blocklist import ( | |
| categories_for, | |
| filter_hits_by_property, | |
| is_paragraph_compatible, | |
| ) | |
| from backend.core.specified_resolver import resolve_specified_tokens | |
| from backend.core.survey_notes import build_property_context, parse_notes | |
| _NOTES = ( | |
| "Mid-terraced Victorian townhouse, freehold, built 1887, four storeys.\n" | |
| "Within a conservation area. Flood Zone 1. EPC band D, score 58.\n" | |
| "Welsh slate pitched roof; north slope ~15% slates damaged; reroof Β£12k-Β£18k.\n" | |
| "Front chimney stack render spalling on the south face.\n" | |
| "Viessmann Vitodens 200-W condensing boiler in the basement, 22 radiators.\n" | |
| "180L unvented cylinder.\n" | |
| "Southeast corner, first floor, diagonal stepped crack 4mm max.\n" | |
| "Rear garage corrugated roof panels likely contain asbestos.\n" | |
| "London Plane tree 4.5m from the rear elevation.\n" | |
| "Total essential repairs Β£53k-Β£134k." | |
| ) | |
| # ββ Fix 3: structured note extraction ββββββββββββββββββββββββββββββββββββββββ | |
| def test_parse_notes_identity_fields(): | |
| n = parse_notes(_NOTES) | |
| assert n.construction_year == 1887 | |
| assert n.storeys == 4 | |
| assert n.tenure == "freehold" | |
| assert n.is_conservation_area is True | |
| assert n.flood_zone == "Zone 1" | |
| assert n.epc_band == "D" | |
| assert n.epc_score == 58 | |
| assert "terraced" in n.property_type.lower() | |
| def test_parse_notes_flags_and_costs(): | |
| n = parse_notes(_NOTES) | |
| assert n.has_asbestos is True | |
| assert "asbestos" in n.asbestos_location.lower() | |
| assert n.has_structural_crack is True | |
| assert "4mm" in n.crack_description.lower().replace(" ", "") | |
| assert n.tree_species == "london plane" | |
| assert n.tree_distance_m == 4.5 | |
| assert (12000, 18000) in n.cost_estimates.values() | |
| assert n.cost_total_essential == (53000, 134000) | |
| def test_parse_notes_section_findings_populated(): | |
| n = parse_notes(_NOTES) | |
| assert isinstance(n.section_findings, dict) | |
| # Keyworded lines should route somewhere rather than vanish entirely. | |
| assert sum(len(v) for v in n.section_findings.values()) >= 1 | |
| def test_build_property_context_session_overrides_notes(): | |
| n = parse_notes(_NOTES) | |
| ctx = build_property_context(n, property_type="detached house", tenure="leasehold") | |
| assert ctx["property_type"] == "detached house" | |
| assert ctx["tenure"] == "leasehold" | |
| assert ctx["construction_year"] == 1887 | |
| def test_build_property_context_falls_back_to_notes(): | |
| n = parse_notes(_NOTES) | |
| ctx = build_property_context(n) | |
| assert "terraced" in ctx["property_type"].lower() | |
| assert ctx["tenure"] == "freehold" | |
| # ββ Fix 1: property-type guard βββββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_categories_for_house_and_terraced(): | |
| assert categories_for({"property_type": "mid-terraced townhouse"}) >= {"house", "terraced"} | |
| assert categories_for({"property_type": "purpose-built flat"}) == {"flat"} | |
| assert categories_for({"property_type": ""}) == set() | |
| assert categories_for(None) == set() | |
| def test_house_blocks_flat_terminology(): | |
| ctx = {"property_type": "mid-terraced house"} | |
| flat_para = ( | |
| "The managing agent should provide the PEEP and confirm whether a stay put " | |
| "policy applies to the communal areas and flat entrance doors." | |
| ) | |
| assert is_paragraph_compatible(flat_para, ctx) is False | |
| def test_terraced_blocks_front_driveway_and_integral_garage(): | |
| ctx = {"property_type": "mid-terraced townhouse"} | |
| assert is_paragraph_compatible("There is a front driveway laid with block paving.", ctx) is False | |
| assert is_paragraph_compatible("The integral garage is accessed from the hallway.", ctx) is False | |
| def test_flat_report_keeps_communal_content(): | |
| ctx = {"property_type": "second-floor flat"} | |
| flat_para = "The communal areas are maintained by the managing agent." | |
| assert is_paragraph_compatible(flat_para, ctx) is True | |
| def test_unknown_property_type_never_filters(): | |
| para = "The communal areas are maintained by the managing agent." | |
| assert is_paragraph_compatible(para, {"property_type": ""}) is True | |
| assert is_paragraph_compatible(para, None) is True | |
| def test_filter_hits_drops_incompatible(): | |
| ctx = {"property_type": "mid-terraced house"} | |
| hits = [ | |
| SimpleNamespace(text="The chimney stack mortar is eroded and needs repointing."), | |
| SimpleNamespace(text="Refer to the managing agent regarding the communal areas."), | |
| ] | |
| kept = filter_hits_by_property(hits, ctx) | |
| assert len(kept) == 1 | |
| assert "chimney" in kept[0].text | |
| def test_guard_disabled_passthrough(monkeypatch): | |
| monkeypatch.setattr(property_blocklist.settings, "property_guard_enabled", False) | |
| ctx = {"property_type": "mid-terraced house"} | |
| assert is_paragraph_compatible("communal areas managing agent", ctx) is True | |
| # ββ Fix 2: 'specified' / redaction token resolution ββββββββββββββββββββββββββ | |
| def test_redaction_tokens_become_visible_placeholder(): | |
| para = "Where partly visible, there are soil pipes to the [REDACTED_NAME_1] side of the property." | |
| out = resolve_specified_tokens(para, section_code="F6") | |
| assert "REDACTED" not in out | |
| assert "specified" not in out.lower() | |
| assert "[SURVEYOR TO CONFIRM]" in out | |
| def test_sentence_initial_token_dropped_and_capitalised(): | |
| para = "[REDACTED_NAME_1] repairs tend to be expensive due to scaffolding." | |
| out = resolve_specified_tokens(para, section_code="D1") | |
| assert out.startswith("Repairs tend to be expensive") | |
| assert "REDACTED" not in out | |
| def test_double_bracket_token_resolved(): | |
| para = "The roof is of hipped pitch and [[REDACTED_NAME_2]] construction." | |
| out = resolve_specified_tokens(para, section_code="D2") | |
| assert "[[" not in out | |
| assert "REDACTED" not in out | |
| assert "[SURVEYOR TO CONFIRM] construction" in out | |
| def test_standalone_token_line_removed(): | |
| para = "Roof inspected.\n[REDACTED_ADDRESS_1]\nNo defects noted." | |
| out = resolve_specified_tokens(para, section_code="D2") | |
| assert "REDACTED" not in out | |
| assert "Roof inspected." in out | |
| assert "No defects noted." in out | |
| def test_legitimate_specified_word_is_preserved(): | |
| # 'specified' is a normal English word; the resolver must not corrupt it. | |
| para = "The works were completed using the specified materials and methods." | |
| out = resolve_specified_tokens(para, section_code="E3") | |
| assert out == para | |
| def test_clean_paragraph_unchanged(): | |
| para = "The chimney stack mortar is eroded and requires repointing. Condition Rating 2." | |
| out = resolve_specified_tokens(para, section_code="D1") | |
| assert out == para | |