Expand session notes with API, job log, and model selection details.
Docker Release / build-and-push (push) Successful in 57s
Docker Release / release (push) Skipped

Keep NOTES.md current for new sessions after the job-log and dual-model UI work.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-31 20:21:35 -05:00
co-authored by Cursor
parent a6b0c8fdfa
commit bf508bfdf6
+58 -7
View File
@@ -2,6 +2,8 @@
Orientation for a new coding session. Setup and Docker details live in [README.md](README.md). This file tracks what the code actually does and what tends to waste time. Orientation for a new coding session. Setup and Docker details live in [README.md](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 ## 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. 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.
@@ -51,15 +53,56 @@ PDF → images → extract → sheet index → jurisdiction
Design notes for Stage 2/3 engines also live under `Changes/*.docx`. 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](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](backend/models_catalog.py)). Per-run picks go through `set_model_overrides()` in [backend/llm.py](backend/llm.py); runner clears them in `finally`. Hybrid: text dropdown also names the local model override and OpenRouter fallback.
## Where to change what ## Where to change what
| Concern | File | | Concern | File |
|---------|------| |---------|------|
| Prompts, vocab, conflict taxonomy | [backend/prompts.py](backend/prompts.py) | | Prompts, vocab, conflict taxonomy | [backend/prompts.py](backend/prompts.py) |
| Env knobs | [backend/config.py](backend/config.py), [backend/.env.example](backend/.env.example) | | Env knobs | [backend/config.py](backend/config.py), [backend/.env.example](backend/.env.example) |
| HTTP API | [backend/main.py](backend/main.py)`/health`, `/models`, `/check`, `/jobs/{id}`, `/jobs/{id}/log`, `/jobs/{id}/sheet-image/{page}` | | HTTP API | [backend/main.py](backend/main.py) |
| UI (upload, models, live log, results) | [frontend/index.html](frontend/index.html) |
| CLI tuning loop | [cli/run_check.py](cli/run_check.py) | | CLI tuning loop | [cli/run_check.py](cli/run_check.py) |
| LLM client, cache, cost, per-run model overrides | [backend/llm.py](backend/llm.py) | | LLM client, cache, cost, model overrides | [backend/llm.py](backend/llm.py) |
| OpenRouter vision/text model lists | [backend/models_catalog.py](backend/models_catalog.py) | | OpenRouter vision/text model lists | [backend/models_catalog.py](backend/models_catalog.py) |
| Job registry + stdout tee log | [backend/jobs.py](backend/jobs.py), [backend/job_log.py](backend/job_log.py) | | Job registry + stdout tee log | [backend/jobs.py](backend/jobs.py), [backend/job_log.py](backend/job_log.py) |
| Stage helpers (prompt render, issue validate) | [backend/pipeline/_stage.py](backend/pipeline/_stage.py) | | Stage helpers (prompt render, issue validate) | [backend/pipeline/_stage.py](backend/pipeline/_stage.py) |
@@ -72,25 +115,33 @@ Older prompt snapshot: `backend/prompts.py.v1`.
1. **Prompt placeholders** — Use `str.replace` via `_stage.render`, never `str.format`. Prompts contain literal `{` JSON braces. 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. 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; reports on disk under `backend/outputs/<job_id>/` can still be reloaded via `get_job` disk fallback. Run logs persist as `job.log` in that same folder and via `GET /jobs/{id}/log`. 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 pin**`httpx==0.27.2` with `openai==1.51.0`. httpx ≥0.28 breaks openais `proxies=` kwarg. 4. **Dependency pin**`httpx==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`). 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. 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). 7. **Grounding guard** — Extractor drops objects whose numeric claims are not in `source_text` (graphical-only objects allowed).
8. **Cost counters**Module-global LLM cost accounting; overlapping jobs share counters. Same for stdout tee logging and per-run model overrides. 8. **Module globals** LLM cost counters, stdout tee, and model overrides are process-global; overlapping jobs can interleave (single-user tool assumption).
9. **Samples**`samples/*.pdf` are gitignored; drop PDFs locally for CLI runs. 9. **Samples**`samples/*.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. 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. **Two models** — Vision (`MODEL`, image stages) and text (`TEXT_MODEL`, non-image). UI exposes separate dropdowns from OpenRouters `/models` (cached ~1h). Hybrid still runs vision on OpenRouter; text dropdown also covers local name override + cloud fallback. 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. **Job log** — Pipeline `print()` output is teed into memory + `outputs/<job_id>/job.log`. Status polls include `log_tail`; completed/error jobs include full `log`. 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 ## Quick start pointers
- Full setup: [README.md](README.md) (`docker compose up -d --build` → http://localhost:8099). - Full setup: [README.md](README.md) (`docker compose up -d --build` → http://localhost:8099).
- Local CLI: `python cli/run_check.py samples/your_set.pdf --out out/your_set`. - 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`. - Prompt iteration: set `LLM_CACHE=true` in `backend/.env` so unchanged stages replay for free; clear with `rm -rf backend/.llm_cache`.
- Artifacts: `assertions.json`, `clusters.json`, per-stage JSON, `conflicts.json`, `report.md` under the chosen `out_dir` or `backend/outputs/`. - 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). - 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) ## 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`. 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`.