Skip to content

v1.1.0 — Scope coverage analysis

Choose a tag to compare

@ibuilder ibuilder released this 07 Aug 14:44
· 26 commits to main since this release

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

  • .env was never actually being read. Config classes read os.environ at class-definition time, so load_dotenv() in the application factory ran too late to matter. The flask CLI 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 licenceLICENSE was 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.