v1.1.0 — Scope coverage analysis
Finds the work that ended up in nobody's contract.
📖 Documentation · API · Full changelog
Scope coverage analysis
A scope gap is work that appears in the drawings but ends up in no trade's contract — classically at the seam between two trades, where each assumed the other had it. It surfaces during construction, and somebody pays for it then.
Every other tool in this space has to infer coverage from PDFs. ScopeMaker already holds each exhibit as structured rows, so the same question is just a query.
/projects/{id}/coverage lines up the specification sections claimed across every scope on a project and reports four things:
| What it means | |
|---|---|
| 🔴 Gap | Applies to a division on this project, claimed by nobody |
| 🟠 Overlap | A trade-specific section claimed by two trades — probably bought twice |
| 🔵 Shared seam | Cross-referenced by design and carried by several trades — correct, but the split still needs deciding |
| 🟡 Unassigned hand-off | An exclusion that pushes work to a division with no scope on the project |
The shared-seam distinction is the part that makes it usable. Four trades claiming 078413 Penetration Firestopping is not a double-buy — every trade firestops its own penetrations. Reporting that as an error would train people to ignore the whole page. But it isn't nothing either: who paints the exposed sprinkler pipe and who furnishes the access door for whose valve are exactly the questions that become change orders, so they get their own section.
Likewise, fire protection excluding the fire alarm is standard and correct — right up until you notice there's no Division 28 package on the job. Then it's a gap in the making, and it gets flagged.
Also lists bid packages with no scope written yet. Available as CSV for buyout meetings and at GET /api/v1/projects/{id}/coverage.
A hand-edited spec line still counts as claimed — the analysis prefers the structured id recorded at generation and falls back to a six-digit number in the text, so rewording a line cannot manufacture a phantom gap.
Fixes
.envwas never actually being read. Config classes reados.environat class-definition time, soload_dotenv()in the application factory ran too late to matter. TheflaskCLI masked this by loading dotenv itself; gunicorn or any script silently fell back to the default SQLite path and presented as a mysteriously empty database.- The licence file didn't match the declared licence —
LICENSEwas GPL-3.0 while everything else said MIT. It's now the MIT text.
Tooling
gh workflow run sample.yml renders a full set of exhibits as PDF, DOCX, Markdown and JSON and uploads them as an artifact — a way to review real output without installing the WeasyPrint native stack locally.
239 tests, green on Python 3.11 and 3.12 with the PDF stack installed.