Files
woogiandCursor bf508bfdf6
Docker Release / build-and-push (push) Successful in 57s
Docker Release / release (push) Skipped
Expand session notes with API, job log, and model selection details.
Keep NOTES.md current for new sessions after the job-log and dual-model UI work.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 20:21:35 -05:00

8.4 KiB
Raw Permalink Blame History

Session Notes — Conflict Checker

Orientation for a new coding session. Setup and Docker details live in README.md. This file tracks what the code actually does and what tends to waste time.

Last updated: 2026-07-31 · tip a6b0c8f on Gitea main

What this is

Cross-discipline design contradiction / senior-architect QAQC for construction drawing PDFs (Arch, Struct, Mech, Elec, Plumb, FP, etc.). Flags disagreements between disciplines before a set goes to bid/permit.

Not IronBids scope-ownership conflict checker.

Source of truth: Scout IT Gitea — gitea.scoutitsystems.com/woogi/Conflict_Checker.

Stack

Layer Detail
API Python 3.12, FastAPI, Uvicorn (backend/main.py)
UI Single static file frontend/index.html, served by FastAPI
Pipeline Shared by web + CLI: backend/pipeline/runner.py
LLM OpenRouter via openai SDK; default google/gemini-2.5-pro. Vision always OpenRouter; text stages can use local vLLM
PDF pdf2image + system poppler-utils → JPEG page images
Jobs In-memory threads (backend/jobs.py) — no Redis/DB
Deploy Docker Compose; app on port 8099

Live pipeline (authoritative)

README still describes an older 5-stage extract-then-compare loop. Trust runner.py. Actual flow:

PDF → images → extract → sheet index → jurisdiction
    → normalize → project intelligence (GOIDs)
    → cluster → conflict reason
    → QAQC / code / constructability
    → dedup-validate → risk → RFIs → report
Runner stage Module Notes
PDF → images pdf_processor Rasterize
Extract assertions extractor Vision, per sheet
Classify sheet index sheet_index LLM
Jurisdiction profile jurisdiction After cover meta
Normalize normalizer LLM batches
Project intelligence normalizer.build_project_intelligence GOIDs + relationships
Cluster llm_clusterer or clusterer Default CLUSTERER=llm
Conflicts conflict_checker Per-cluster vision reason
QAQC / code / constructability qaqc_review, code_review, constructability Full-set / batched
Validate & dedup validator Merges conflict + QAQC + code + construct issues
Risk / RFIs risk, rfi Text-only
Report report conflicts.json + report.md (+ stage JSON dumps when out_dir set)

Design notes for Stage 2/3 engines also live under Changes/*.docx.

HTTP API (current)

Method Path Purpose
GET /health Liveness + default model / text_model + key/email flags
GET /models OpenRouter catalog split into vision[] / text[] + defaults (cached ~1h)
POST /check Upload PDF; returns {job_id} immediately
GET /jobs/{id} Status poll. Running: stage + log_tail. Done/error: report and/or error + full log
GET /jobs/{id}/log Full run log JSON (lines, text); ?plain=1 for text/plain
GET /jobs/{id}/sheet-image/{page} JPEG of source PDF page for the sheet viewer
GET / Serves frontend/index.html

/check form fields

  • Required: file (PDF)
  • Optional: notification_email, project_name, address, occupancy, work_type
  • Compute: text_local (true = hybrid local text)
  • Models: vision_model, text_model (OpenRouter ids; blank = config defaults)

Job logs

Pipeline print() is teed for the job thread (backend/job_log.py):

  • Live: GET /jobs/{id}log_tail (last 80 lines)
  • Done/error: same payload includes full log
  • Disk: backend/outputs/<job_id>/job.log (survives restart; status registry does not)
  • API: GET /jobs/{id}/log or ?plain=1
  • UI: “Run log” panel updates while running; stays visible after finish/fail

Vision vs text models

Two models, not one:

Role Config Stages Backend
Vision MODEL Extract, conflict reason (images) Always OpenRouter
Text TEXT_MODEL (falls back to MODEL) Sheet index, jurisdiction, normalize, cluster(LLM), QAQC, code, construct, validate, risk, RFI OpenRouter, or local when hybrid

UI: two dropdowns filled from GET /models (backend/models_catalog.py). Per-run picks go through set_model_overrides() in backend/llm.py; runner clears them in finally. Hybrid: text dropdown also names the local model override and OpenRouter fallback.

Where to change what

Concern File
Prompts, vocab, conflict taxonomy backend/prompts.py
Env knobs backend/config.py, backend/.env.example
HTTP API backend/main.py
UI (upload, models, live log, results) frontend/index.html
CLI tuning loop cli/run_check.py
LLM client, cache, cost, model overrides backend/llm.py
OpenRouter vision/text model lists backend/models_catalog.py
Job registry + stdout tee log backend/jobs.py, backend/job_log.py
Stage helpers (prompt render, issue validate) backend/pipeline/_stage.py
Code text corpus (Stage 7) backend/code_corpus/
Hybrid local LLM helper scripts/setup_vllm.sh

Older prompt snapshot: backend/prompts.py.v1.

Gotchas

  1. Prompt placeholders — Use str.replace via _stage.render, never str.format. Prompts contain literal { JSON braces.
  2. Clustering — Default is LLM (CLUSTERER=llm); empty LLM result falls back to deterministic. Deterministic clusters need ≥2 disciplines (or schedule-vs-plan); single-discipline “missing” gaps are a known limit.
  3. Jobs are in-memory — Process restart clears job status; outputs/<job_id>/ (report + job.log + source.pdf) still reload via disk fallback.
  4. Dependency pinhttpx==0.27.2 with openai==1.51.0. httpx ≥0.28 breaks openais proxies= kwarg.
  5. Code corpus licensing — Only ada_2010.txt is shipped. Do not paste IBC/IFC/IECC without a license (see backend/code_corpus/README.md).
  6. Dual assertion schema — Newer {sheet, objects[]} is mapped to legacy {assertions[]} with attribute/value for older stages.
  7. Grounding guard — Extractor drops objects whose numeric claims are not in source_text (graphical-only objects allowed).
  8. Module globals — LLM cost counters, stdout tee, and model overrides are process-global; overlapping jobs can interleave (single-user tool assumption).
  9. Samplessamples/*.pdf are gitignored; drop PDFs locally for CLI runs.
  10. README drift — Treat README for setup/CI; treat this file + runner.py for pipeline truth. prompts.py header may still say some prompts are unwired — they are wired through the runner.
  11. Git identity — This box has no user.name / user.email; commits need GIT_AUTHOR_* / GIT_COMMITTER_* env vars (do not git config). Remote push to Gitea works.
  12. No local Python deps on host — App is meant to run in Docker; bare python3 imports may miss dotenv / httpx. Prefer docker compose.

Quick start pointers

  • Full setup: README.md (docker compose up -d --buildhttp://localhost:8099).
  • Local CLI: python cli/run_check.py samples/your_set.pdf --out out/your_set.
  • Prompt iteration: set LLM_CACHE=true in backend/.env so unchanged stages replay for free; clear with rm -rf backend/.llm_cache.
  • Artifacts per job: assertions.json, clusters.json, per-stage JSON, conflicts.json, report.md, job.log, source.pdf under backend/outputs/<job_id>/.
  • No automated test suite; validate via CLI dumps and golden-set diffs (described in README).

Recent work (2026-07-31)

Shipped on main as a6b0c8f:

  • Per-job run log (tee + disk + API + UI)
  • Separate vision/text model dropdowns backed by OpenRouter /models
  • Session notes file (this doc)

Conflict categories (taxonomy)

Defined in backend/prompts.py: dimensional_disagreement, elevation_disagreement, location_mismatch, missing_element, schedule_vs_plan_mismatch, detail_vs_plan_mismatch, tag_or_reference_inconsistency, spatial_clash, note_or_spec_contradiction.