feat: refocus on drawings — code/ADA gated off, drawing-integrity wave, Brain-directed clarification
- ENABLE_CODE_REVIEW flag (default off): skips code/ADA/jurisdiction review path in both pipelines; nothing deleted, one env flag to restore. - Per-sheet Drawing Integrity QA wave (agent + classic, default on): dangling refs, on-sheet contradictions, dimension sanity, missing sheet essentials, tag hygiene. New DrawingIntegrityAgent + classic stage. - Broadened conflict critic: intra-sheet + same-discipline contradictions, not just cross-discipline. - Wave 6.5 Brain-directed clarification (bounded hub-and-spoke): Brain names uncertain findings, verify_evidence requests route through the wave-5b verifier; refuted findings suppressed. One planning call + capped verifies, single iteration. Shared _build_verify_scopes across 5b and 6.5. - Config knobs, .env.example, frontend copy, tests (182 passing).
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
"""Unit tests for the per-sheet DrawingIntegrityAgent and scope builder."""
|
||||
|
||||
import backend.agents.integrity_agent as integ
|
||||
from backend.agents.base import AgentScope, AgentUsage
|
||||
from backend.agents.integrity_agent import (
|
||||
DrawingIntegrityAgent, build_integrity_scopes,
|
||||
)
|
||||
from backend import config
|
||||
|
||||
|
||||
def _sheet(page, sheet_number, n_assertions):
|
||||
return {
|
||||
"sheet_number": sheet_number,
|
||||
"page_number": page,
|
||||
"sheet_title": f"Sheet {sheet_number}",
|
||||
"discipline": "Architectural",
|
||||
"assertions": [
|
||||
{"object_type": "note", "source_text": f"note {i}"}
|
||||
for i in range(n_assertions)
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def test_build_scopes_skips_sparse_sheets(monkeypatch):
|
||||
monkeypatch.setattr(config, "INTEGRITY_MIN_ASSERTIONS", 3)
|
||||
sheets = [
|
||||
_sheet(1, "A101", 5), # kept
|
||||
_sheet(2, "A102", 2), # skipped (too sparse)
|
||||
_sheet(3, "A103", 3), # kept (== floor)
|
||||
]
|
||||
page_to_b64 = {1: "IMG1", 2: "IMG2", 3: "IMG3"}
|
||||
page_to_text = {1: "text one", 2: "text two", 3: "text three"}
|
||||
scopes = build_integrity_scopes(sheets, page_to_b64, page_to_text)
|
||||
ids = sorted(s.scope_id for s in scopes)
|
||||
assert ids == ["integrity:1", "integrity:3"]
|
||||
# Each scope carries its own page image + text layer.
|
||||
by_id = {s.scope_id: s for s in scopes}
|
||||
assert by_id["integrity:1"].payload["image_b64"] == "IMG1"
|
||||
assert by_id["integrity:1"].payload["text_layer"] == "text one"
|
||||
|
||||
|
||||
def test_agent_parses_and_anchors_sheet(monkeypatch):
|
||||
"""Findings with blank sheets get anchored to the scope's sheet number."""
|
||||
captured = {}
|
||||
|
||||
def fake_call_json(**kwargs):
|
||||
captured.update(kwargs)
|
||||
return {"issues": [
|
||||
{"issue_id": "DI-1", "severity": "high", "confidence": "high",
|
||||
"category": "dangling_reference", "sheets": [],
|
||||
"description": "Detail callout 5/A101 has no detail 5 on this sheet",
|
||||
"evidence": [{"sheet": "A101", "source_text": "5/A101"}]},
|
||||
]}
|
||||
|
||||
monkeypatch.setattr(integ, "call_json", fake_call_json)
|
||||
sheet = _sheet(1, "A101", 5)
|
||||
scope = AgentScope("integrity:1", {
|
||||
"sheet": sheet, "page_number": 1,
|
||||
"image_b64": "IMG1", "text_layer": "the deterministic text layer",
|
||||
})
|
||||
result = DrawingIntegrityAgent(AgentUsage()).run(scope)
|
||||
assert result.error == ""
|
||||
assert len(result.artifacts) == 1
|
||||
finding = result.artifacts[0]
|
||||
assert finding["source_stage"] == "drawing_integrity"
|
||||
assert finding["sheets"] == ["A101"] # anchored
|
||||
assert finding["agent"] == "drawing_integrity"
|
||||
assert finding["scope_id"] == "integrity:1"
|
||||
# The image + text layer reached the model.
|
||||
assert captured["images_b64"] == ["IMG1"]
|
||||
assert "the deterministic text layer" in captured["user_text"]
|
||||
|
||||
|
||||
def test_agent_empty_issues_is_clean(monkeypatch):
|
||||
monkeypatch.setattr(integ, "call_json", lambda **k: {"issues": []})
|
||||
scope = AgentScope("integrity:1", {
|
||||
"sheet": _sheet(1, "A101", 5), "page_number": 1,
|
||||
"image_b64": "IMG1", "text_layer": "t",
|
||||
})
|
||||
result = DrawingIntegrityAgent(AgentUsage()).run(scope)
|
||||
assert result.error == ""
|
||||
assert result.artifacts == []
|
||||
|
||||
|
||||
def test_agent_survives_call_failure(monkeypatch):
|
||||
def boom(**kwargs):
|
||||
raise RuntimeError("model exploded")
|
||||
monkeypatch.setattr(integ, "call_json", boom)
|
||||
scope = AgentScope("integrity:1", {
|
||||
"sheet": _sheet(1, "A101", 5), "page_number": 1,
|
||||
"image_b64": "IMG1", "text_layer": "t",
|
||||
})
|
||||
result = DrawingIntegrityAgent(AgentUsage()).run(scope)
|
||||
assert "model exploded" in result.error
|
||||
assert result.artifacts == []
|
||||
Reference in New Issue
Block a user