Files
woogiandClaude Opus 4.8 1d248a8808 Initial commit: Conflict Checker
Cross-discipline design-contradiction checker for construction drawing
sets. Standalone tool broken out from Iron_Bid; a pipeline stage may
later fold back into Iron_Bid.

Pipeline: PDF->images -> per-sheet assertion extraction -> deterministic
clustering by location -> per-cluster reasoning -> report.
Includes CLI (cli/run_check.py) and web UI (backend/main.py).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 00:22:02 +00:00

89 lines
3.3 KiB
Python

"""
report.py - Stage 4: assemble the final report.
Produces a single JSON object (also the web API payload) and a human-readable
Markdown summary grouped by severity.
"""
from typing import List, Dict
from datetime import datetime, timezone
def build_report(conflicts: List[Dict], sheets: List[Dict], clusters: List[Dict],
source: str = "") -> Dict:
"""Assemble the structured report object."""
by_sev = {"high": 0, "medium": 0, "low": 0}
by_cat: Dict[str, int] = {}
for c in conflicts:
by_sev[c["severity"]] = by_sev.get(c["severity"], 0) + 1
by_cat[c["category"]] = by_cat.get(c["category"], 0) + 1
disciplines = sorted({s["discipline"] for s in sheets if s.get("discipline")})
return {
"source": source,
"generated_at": datetime.now(timezone.utc).isoformat(),
"summary": {
"sheets_analyzed": len(sheets),
"disciplines": disciplines,
"assertions_extracted": sum(len(s.get("assertions", [])) for s in sheets),
"clusters_checked": len(clusters),
"conflicts_found": len(conflicts),
"by_severity": by_sev,
"by_category": by_cat,
},
"conflicts": conflicts,
"sheets": [
{
"page_number": s.get("page_number"),
"sheet_number": s.get("sheet_number"),
"discipline": s.get("discipline"),
"sheet_title": s.get("sheet_title"),
"assertion_count": len(s.get("assertions", [])),
}
for s in sheets
],
}
def to_markdown(report: Dict) -> str:
s = report["summary"]
lines = [
f"# Conflict Check Report",
"",
f"- Source: `{report.get('source','')}`",
f"- Generated: {report.get('generated_at','')}",
f"- Sheets analyzed: {s['sheets_analyzed']} ({', '.join(s['disciplines']) or 'none'})",
f"- Assertions extracted: {s['assertions_extracted']}",
f"- Clusters checked: {s['clusters_checked']}",
f"- **Conflicts found: {s['conflicts_found']}** "
f"(high {s['by_severity']['high']}, medium {s['by_severity']['medium']}, low {s['by_severity']['low']})",
"",
]
if not report["conflicts"]:
lines += ["No cross-discipline conflicts detected.", ""]
return "\n".join(lines)
for sev in ("high", "medium", "low"):
group = [c for c in report["conflicts"] if c["severity"] == sev]
if not group:
continue
lines += [f"## {sev.capitalize()} severity ({len(group)})", ""]
for i, c in enumerate(group, 1):
lines += [
f"### {i}. [{c['category']}] {c['location']}",
f"- Disciplines: {', '.join(c.get('disciplines', []))}",
f"- Sheets: {', '.join(c.get('sheets', []))}",
f"- {c['description']}",
]
for ev in c.get("evidence", []):
lines.append(
f" - {ev.get('discipline','?')} ({ev.get('sheet','?')}): "
f"\"{ev.get('source_text','')}\""
)
if c.get("recommended_resolution"):
lines.append(f"- Resolution: {c['recommended_resolution']}")
lines.append(f"- Confidence: {c.get('confidence','')}")
lines.append("")
return "\n".join(lines)