feat: refocus on drawings — code/ADA gated off, drawing-integrity wave, Brain-directed clarification
Docker Release / build-and-push (push) Successful in 1m8s
Docker Release / release (push) Skipped

- 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:
2026-08-20 15:10:32 -05:00
parent bae608a505
commit d37ac8c1c7
22 changed files with 1917 additions and 63 deletions
+51 -4
View File
@@ -12,6 +12,15 @@ load_dotenv(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env"))
_BASE_DIR = os.path.dirname(os.path.abspath(__file__))
_TRUTHY = ("1", "true", "yes", "on")
def _flag(name: str, default: str) -> bool:
"""Parse a boolean env knob. Accepts 1/true/yes/on (case-insensitive) so a
knob set to "1" behaves the same as one set to "true" — mixing bare
`== "true"` comparisons with this set silently disabled features."""
return os.getenv(name, default).strip().lower() in _TRUTHY
# -- AI Backend (OpenRouter) ----------------------------------------
# One multimodal model does both extraction (Stage 1) and conflict
# reasoning (Stage 3). Override MODEL per-stage if you ever split them.
@@ -47,6 +56,33 @@ AGENT_CONFLICT_CONCURRENCY = int(os.getenv("AGENT_CONFLICT_CONCURRENCY", "4"))
AGENT_SPECIALIST_CONCURRENCY = int(os.getenv("AGENT_SPECIALIST_CONCURRENCY", "4"))
AGENT_RFI_CONCURRENCY = int(os.getenv("AGENT_RFI_CONCURRENCY", "4"))
# -- Review focus toggles -------------------------------------------
# ENABLE_CODE_REVIEW gates the code/ADA/jurisdiction review path in BOTH
# pipelines. Default OFF: the product's focus is drawing-integrity and
# cross-discipline coordination, not code/accessibility compliance. When
# False the CodeAgent wave (agent) and the Code/ADA stage (classic) are
# skipped entirely, by_stage.code reports 0, and nothing in the ADA corpus
# or jurisdiction meta is deleted so the path can be re-enabled with one env
# flag. Set ENABLE_CODE_REVIEW=1 to restore code/ADA findings.
ENABLE_CODE_REVIEW = _flag("ENABLE_CODE_REVIEW", "false")
# Per-sheet Drawing Integrity QA wave (agent + classic). This is the
# drawing-focused pass: it reads ONE sheet's own objects + image + text layer
# and flags problems internal to that sheet -- dangling detail/callout/keynote
# references, schedule-vs-plan or legend disagreements on the same sheet,
# dimension strings that do not sum, missing title-block/scale/north-arrow,
# and notes that contradict each other. It complements (does not replace) the
# cross-sheet conflict critic. Default ON.
ENABLE_DRAWING_INTEGRITY = _flag("ENABLE_DRAWING_INTEGRITY", "true")
AGENT_INTEGRITY_MODEL = os.getenv("AGENT_INTEGRITY_MODEL", "") or MODEL
AGENT_INTEGRITY_CONCURRENCY = int(os.getenv("AGENT_INTEGRITY_CONCURRENCY", "4"))
AGENT_INTEGRITY_MAX_IMAGES = int(os.getenv("AGENT_INTEGRITY_MAX_IMAGES", "1"))
AGENT_INTEGRITY_MAX_ASSERTIONS = int(os.getenv("AGENT_INTEGRITY_MAX_ASSERTIONS", "80"))
INTEGRITY_MAX_TOKENS = int(os.getenv("INTEGRITY_MAX_TOKENS", "16384"))
# Skip sheets with fewer than this many extracted objects -- too sparse for a
# meaningful internal-consistency pass (avoids burning a call on near-empty pages).
INTEGRITY_MIN_ASSERTIONS = int(os.getenv("INTEGRITY_MIN_ASSERTIONS", "3"))
# Wave 5b evidence verification (vision fact-check of cited sheet text)
AGENT_VERIFY_MODEL = os.getenv("AGENT_VERIFY_MODEL", "") or MODEL
AGENT_VERIFY_CONCURRENCY = int(os.getenv("AGENT_VERIFY_CONCURRENCY", "4"))
@@ -59,6 +95,17 @@ AGENT_VERIFY_SEVERITIES = {
AGENT_VERIFY_REASONING_EFFORT = os.getenv("AGENT_VERIFY_REASONING_EFFORT", "low").strip()
VERIFY_MAX_TOKENS = int(os.getenv("VERIFY_MAX_TOKENS", "8192"))
# Wave 6.5 Brain-directed clarification. After the Brain merge, the Brain may
# name findings it is unsure about and emit typed clarification requests; v1
# executes verify_evidence requests by routing them back through the wave-5b
# EvidenceVerifierAgent (fresh page images + hi-DPI evidence crops + text-layer
# oracle). Bounded: one planning call, at most BRAIN_CLARIFY_MAX_REQUESTS
# verifications, a single iteration. Reuses AGENT_VERIFY_* / VERIFY_* knobs for
# the verification calls. Default ON.
ENABLE_BRAIN_CLARIFY = _flag("ENABLE_BRAIN_CLARIFY", "true")
BRAIN_CLARIFY_MAX_REQUESTS = int(os.getenv("BRAIN_CLARIFY_MAX_REQUESTS", "8"))
BRAIN_CLARIFY_MAX_TOKENS = int(os.getenv("BRAIN_CLARIFY_MAX_TOKENS", "4096"))
# -- Text-layer grounding (deterministic PDF text layer via PyMuPDF) ----
# The vector text layer is extracted once per job and grounds the extractor,
# rescues misquoted-but-real values in the grounding guard, and serves the
@@ -118,8 +165,8 @@ EXTRACT_REASONING_MAX_TOKENS = int(os.getenv("EXTRACT_REASONING_MAX_TOKENS", "20
# structuring pass (rung 2), then deterministic text-layer fallback stubs
# (rung 3) so no text-bearing page goes dark.
EXTRACT_COVERAGE_FLOOR = float(os.getenv("EXTRACT_COVERAGE_FLOOR", "0.6"))
EXTRACT_TEXT_RETRY_ENABLED = os.getenv("EXTRACT_TEXT_RETRY_ENABLED", "true").lower() == "true"
EXTRACT_FALLBACK_ENABLED = os.getenv("EXTRACT_FALLBACK_ENABLED", "true").lower() == "true"
EXTRACT_TEXT_RETRY_ENABLED = _flag("EXTRACT_TEXT_RETRY_ENABLED", "true")
EXTRACT_FALLBACK_ENABLED = _flag("EXTRACT_FALLBACK_ENABLED", "true")
EXTRACT_FALLBACK_MAX_OBJECTS = int(os.getenv("EXTRACT_FALLBACK_MAX_OBJECTS", "200"))
REASON_MAX_TOKENS = int(os.getenv("REASON_MAX_TOKENS", "4096"))
@@ -191,5 +238,5 @@ SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
SMTP_USER = os.getenv("SMTP_USER", "")
SMTP_PASSWORD = os.getenv("SMTP_PASSWORD", "")
SMTP_FROM = os.getenv("SMTP_FROM", "")
SMTP_USE_TLS = os.getenv("SMTP_USE_TLS", "true").lower() == "true"
SMTP_USE_SSL = os.getenv("SMTP_USE_SSL", "false").lower() == "true"
SMTP_USE_TLS = _flag("SMTP_USE_TLS", "true")
SMTP_USE_SSL = _flag("SMTP_USE_SSL", "false")