- backend/text_layer.py: PyMuPDF text-layer extraction, fuzzy evidence
bbox matching, 300-DPI crop rendering, coverage-gap signal
- extractor (classic + agent): TEXT LAYER block appended at call sites;
grounding guard gains text-layer rescue tier (grounding=text_layer stamp)
- verifier: {text_layer} oracle excerpt + evidence-located hi-DPI crops
replacing full-page images (fallback preserved, I2 guard intact)
- coverage gaps: text-bearing pages with zero extraction -> failed-scope
gap findings (agent) / log-only (classic)
- config knobs: TEXT_LAYER_ENABLED/MIN_CHARS/MAX_CHARS, VERIFY_TEXT_MAX_CHARS,
VERIFY_HI_DPI_CROPS, VERIFY_CROP_DPI, VERIFY_CROP_MARGIN_PTS
- tests: 22 new (text_layer unit, grounding/render, runner-level flow)
Spec: docs/superpowers/specs/2026-08-12-text-layer-grounding-design.md
87 lines
3.3 KiB
Python
87 lines
3.3 KiB
Python
"""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
|