Files
John Wilganowski 1c1d2ff21b
Docker Release / build-and-push (push) Successful in 1m10s
Docker Release / release (push) Skipped
Add required human review gate to the Agent pipeline.
Agent web jobs now stop after Brain consolidation and enter needs_review
with a persisted review queue (blocking: high-severity, low-confidence,
sensitive-category findings; audit sample of clean clusters). Humans
decide confirm/reject/unsure/needs_clarification via new review API and
frontend queue; a finalizer applies decisions (rejections suppressed with
reason codes), performs bounded targeted reruns for clarifications,
drafts RFIs only for kept issues, and only then marks the job done and
sends the final email. Two-phase email (review-required, then final
report), per-decision feedback labels with redacted aggregate metrics,
restart recovery from job artifacts, and CLI --no-review bypass.
Classic pipeline unchanged. 65 non-LLM tests.
2026-07-28 19:23:57 +00:00

86 lines
3.1 KiB
Python

#!/usr/bin/env python3
"""
run_check.py - CLI entry point for the conflict checker (the tuning loop).
Usage:
python cli/run_check.py path/to/set.pdf [--out out_dir]
Run from the project root so `backend` is importable.
"""
import os
import sys
import argparse
# Make the project root importable when run as a script.
_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _ROOT not in sys.path:
sys.path.insert(0, _ROOT)
from backend import config # noqa: E402
from backend.agents.runner import run_agent_pipeline # noqa: E402
from backend.pipeline.runner import run_pipeline # noqa: E402
def main() -> int:
parser = argparse.ArgumentParser(description="Cross-discipline drawing conflict checker")
parser.add_argument("pdf", help="Path to the PDF drawing set")
parser.add_argument("--out", default=None,
help="Directory for artifacts (default: out/<pdf-stem>)")
parser.add_argument("--mode", choices=("classic", "agent"), default="classic",
help="Pipeline implementation to run (default: classic)")
parser.add_argument("--project-name", default=None)
parser.add_argument("--address", default=None)
parser.add_argument("--occupancy", default=None)
parser.add_argument("--work-type", default=None,
help="new_building | remodel | tenant_improvement | addition | ...")
parser.add_argument("--no-review", action="store_true",
help="Agent mode only: skip the human-review gate and finish the run "
"(overrides AGENT_REQUIRE_REVIEW=true)")
args = parser.parse_args()
if not os.path.isfile(args.pdf):
print(f"Error: file not found: {args.pdf}")
return 1
project_input = {
k: v for k, v in {
"project_name": args.project_name, "address": args.address,
"occupancy": args.occupancy, "work_type": args.work_type,
}.items() if v
}
out_dir = args.out or os.path.join("out", os.path.splitext(os.path.basename(args.pdf))[0])
if args.mode == "agent":
report = run_agent_pipeline(
args.pdf,
out_dir=out_dir,
project_input=project_input or None,
source_name=os.path.basename(args.pdf),
require_review=config.AGENT_REQUIRE_REVIEW and not args.no_review,
)
else:
report = run_pipeline(
args.pdf,
out_dir=out_dir,
project_input=project_input or None,
source_name=os.path.basename(args.pdf),
)
s = report["summary"]
print("\n" + "=" * 60)
print(f" Conflicts found: {s['conflicts_found']} "
f"(high {s['by_severity']['high']}, "
f"medium {s['by_severity']['medium']}, "
f"low {s['by_severity']['low']})")
if s.get("agent_status") == "needs_review":
print(" Stopped for human review - finalize via the web UI, "
"or rerun with --no-review.")
else:
print(f" Report: {os.path.join(out_dir, 'report.md')}")
print("=" * 60)
return 0
if __name__ == "__main__":
raise SystemExit(main())