Spaces:
Runtime error
Runtime error
File size: 6,228 Bytes
aad7814 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | """Guard tests for block-level context inheritance + the routing tier lock.
These pin the *structural* contract of how multi-sentence note blocks route β
NOT the wording of any particular surveyor's notes. Surveyors pack several
sentences (and several elements) into one paragraph; only the first sentence of
a block usually carries the section label/code. The parser therefore:
* lets a sentence with no signal INHERIT the block's running section,
* lets a STRONG signal (explicit code / element label / high-confidence keyword)
set or override that running section,
* keeps a WEAK general-keyword match from overriding an established section
(so generic "repairs and repointing" boilerplate is not dragged to the walls),
* always files a HAZARD (I-group) mention in its own section without letting it
hijack the surrounding element context.
"""
from __future__ import annotations
from backend.core.notes_keyword_router import (
TIER_EXPLICIT,
TIER_GENERAL,
TIER_HIGH,
TIER_LABEL,
TIER_NONE,
classify_note_cascade,
classify_note_cascade_with_tier,
)
from backend.core.notes_parser import UNASSIGNED, parse_notes_to_sections
from backend.core.rics_canonical_l3 import build_canonical_template_schema
def _schema():
return build_canonical_template_schema()
def _by_id(notes) -> dict[str, str]:
return {n.section_id: n.text.lower() for n in notes}
# ββ Routing tier contract (router level, schema-independent) βββββββββββββββββ
def test_tier_explicit_code():
sid, tier, _ = classify_note_cascade_with_tier("F6, drainage chamber lifted.")
assert (sid, tier) == ("F6", TIER_EXPLICIT)
def test_tier_element_label():
sid, tier, _ = classify_note_cascade_with_tier("Main roof: slipped slates noted.")
assert (sid, tier) == ("D2", TIER_LABEL)
def test_tier_high_confidence_keyword():
sid, tier, _ = classify_note_cascade_with_tier("Ceilings show some deflection.")
assert (sid, tier) == ("E2", TIER_HIGH)
def test_tier_none_for_nonsense():
sid, tier, _ = classify_note_cascade_with_tier("xyzzy plugh frobnicator quux")
assert (sid, tier) == (UNASSIGNED, TIER_NONE)
def test_cascade_backcompat_scores_unchanged():
"""The legacy (section, score, body) contract still scores 1.0 / 0.0."""
assert classify_note_cascade("Ceilings show some deflection.")[1] == 1.0
assert classify_note_cascade("xyzzy plugh frobnicator quux")[1] == 0.0
def test_bare_repointing_is_weak_but_wall_repointing_is_strong():
"""Unqualified repointing is ambiguous (weak); wall-qualified stays strong."""
sid, tier, _ = classify_note_cascade_with_tier("Repairs and repointing are recommended.")
assert (sid, tier) == ("D4", TIER_GENERAL)
sid2, tier2, _ = classify_note_cascade_with_tier("Wall repointing is recommended.")
assert (sid2, tier2) == ("D4", TIER_HIGH)
# ββ Block-level context inheritance (parser level, canonical schema) βββββββββ
def test_signal_less_continuation_inherits_label_block():
notes = parse_notes_to_sections(
"Chimney stack: brick chimney generally sound. It should be monitored over time.",
_schema(),
)
by_id = _by_id(notes)
assert "monitored over time" in by_id.get("D1", "")
def test_weak_keyword_does_not_override_established_context():
"""The core fix: generic repointing inside a chimney block stays with D1."""
notes = parse_notes_to_sections(
"Chimney stack: brick chimney inspected. Repairs and repointing are recommended.",
_schema(),
)
by_id = _by_id(notes)
assert "repointing" in by_id.get("D1", "")
assert "repointing" not in by_id.get("D4", "")
def test_strong_keyword_overrides_established_context():
notes = parse_notes_to_sections(
"Roof structure: trusses and purlins inspected from the loft. "
"Ceilings throughout show some movement.",
_schema(),
)
by_id = _by_id(notes)
assert "trusses" in by_id.get("E1", "") or "purlins" in by_id.get("E1", "")
assert "ceilings" in by_id.get("E2", "")
def test_explicit_code_mid_block_breaks_context():
notes = parse_notes_to_sections(
"E1 roof structure of traditional design with adequate support. "
"E3 damp was noted to an internal partition wall.",
_schema(),
)
by_id = _by_id(notes)
assert "roof structure" in by_id.get("E1", "")
assert "damp" in by_id.get("E3", "")
def test_weak_hazard_files_to_hazard_without_hijacking_context():
"""A weak (general-tier) hazard is filed to I3 but does not capture the
block: the following signal-less sentence still inherits the element."""
notes = parse_notes_to_sections(
"Staircase: timber staircase inspected. The balustrade is a safety concern. "
"Otherwise it remains secure.",
_schema(),
)
by_id = _by_id(notes)
assert "staircase" in by_id.get("E7", "")
assert "balustrade" in by_id.get("I3", "")
assert "remains secure" in by_id.get("E7", "")
assert "balustrade" not in by_id.get("E7", "")
def test_block_start_weak_keyword_establishes_context():
"""When a block opens with only a weak keyword, it still sets the context so
signal-less continuations are not lost to UNASSIGNED."""
notes = parse_notes_to_sections(
"Some repointing is needed. It should be carried out by a competent contractor.",
_schema(),
)
by_id = _by_id(notes)
assert "repointing" in by_id.get("D4", "")
assert "competent contractor" in by_id.get("D4", "")
def test_paragraph_boundary_resets_context():
notes = parse_notes_to_sections(
"Chimney stack: brick chimney noted. Needs attention in due course.\n\n"
"Windows: timber sash units. Some are painted shut.",
_schema(),
)
by_id = _by_id(notes)
assert "needs attention" in by_id.get("D1", "")
assert "painted shut" in by_id.get("D5", "")
assert "painted shut" not in by_id.get("D1", "")
def test_signal_less_block_with_no_context_stays_unassigned():
notes = parse_notes_to_sections("Xyzzy plugh frobnicator quux.", _schema())
assert any(n.section_id == UNASSIGNED for n in notes)
|