"""Grounding-guard rescue tier, text-layer prompt block, and render hygiene.""" from backend import config from backend.pipeline._stage import render from backend.pipeline.extractor import ( _is_grounded, _normalize_sheet, _text_layer_block, ) from backend.prompts import EXTRACTOR_USER_INSTRUCTION, VERIFY_USER_INSTRUCTION PAGE_TEXT = "NOTES: (5) 2X6 STUD PACK AT BEARING. HSS16X4 BEAM. 7'-0\" AFF." def _parsed(value, source_text): return { "sheet": {"sheet_number": "S401"}, "objects": [{ "object_id": "o1", "object_type": "framing", "name": "stud pack", "attributes": {"count": value}, "source_text": source_text, }], } def test_rescue_tier_keeps_and_stamps(): """Digits absent from source_text but present in the page text layer: kept, stamped grounding=text_layer (vision quoted imperfectly).""" sheet = _normalize_sheet(_parsed("(2)", "(2) 2x6 STUD PACK"), 1, page_text=PAGE_TEXT) # "(2)" is not grounded by its own source_text alone? it is - use a value # whose digits differ from the quote to exercise the rescue path. sheet = _normalize_sheet(_parsed("5", "(2) 2x6 STUD PACK"), 1, page_text=PAGE_TEXT) assert len(sheet["assertions"]) == 1 assert sheet["assertions"][0]["grounding"] == "text_layer" def test_no_rescue_without_page_text(): sheet = _normalize_sheet(_parsed("5", "(2) 2x6 STUD PACK"), 1) assert sheet["assertions"] == [] def test_still_dropped_when_digits_nowhere(): sheet = _normalize_sheet(_parsed("99", "(2) 2x6 STUD PACK"), 1, page_text=PAGE_TEXT) assert sheet["assertions"] == [] def test_is_grounded_backward_compatible(): assert _is_grounded("(5)", "(5) 2x6 STUD PACK") is True # Digit-run guard is a set check: "(3)" has no support anywhere. assert _is_grounded("(3)", "(5) 2x6 STUD PACK") is False assert _is_grounded("(3)", "(5) 2x6 STUD PACK", page_text="(3) 2x6 STUD PACK") is True def test_text_layer_block_empty_without_layer(): assert _text_layer_block({"page_number": 1}) == "" assert _text_layer_block({"page_number": 1, "text_layer": None}) == "" def test_text_layer_block_appends_and_caps(monkeypatch): block = _text_layer_block({"page_number": 1, "text_layer": PAGE_TEXT}) assert "TEXT LAYER" in block and "STUD PACK" in block monkeypatch.setattr(config, "TEXT_LAYER_MAX_CHARS", 50) block = _text_layer_block({"page_number": 1, "text_layer": "x" * 500}) assert len(block.split(":\n", 1)[1]) == 50 def test_verify_instruction_fully_rendered(): """render() silently leaves missing keys as literals - both placeholders must be substituted at the (single) verify render site.""" out = render(VERIFY_USER_INSTRUCTION, {"finding": "FINDING_JSON", "text_layer": "PAGE_TEXT"}) assert "{finding}" not in out and "{text_layer}" not in out assert "FINDING_JSON" in out and "PAGE_TEXT" in out def test_extractor_instruction_fully_substituted(): page = {"page_number": 1, "text_layer": PAGE_TEXT} out = (EXTRACTOR_USER_INSTRUCTION.replace("{sheet_hint}", "") + _text_layer_block(page)) assert "{sheet_hint}" not in out