v1.0.0 — ScopeMaker
Construction scope of work exhibits, generated properly.
📖 Documentation · Deployment · API · Clause library
This is a complete rewrite
The repository was previously a browser-only prototype that could not run: every page loaded js/procore-api.js, js/exhibit-generator.js, js/database-service.js and js/export-utils.js, and none of those files were ever committed. v1.0.0 replaces it with a Flask 3 application built around the part that actually matters — generating the scope, not the Procore connector.
| Prototype | v1.0.0 | |
|---|---|---|
| Does it run? | ❌ Four referenced JS files never committed | ✅ Flask 3, 9 blueprints, service layer |
| OAuth secret | ❌ Browser localStorage |
✅ Server-side only, Fernet-encrypted at rest |
| PDF export | ❌ html2canvas screenshot on one A4 page |
✅ WeasyPrint paged media, selectable text, Page N of M |
| DOCX export | ❌ alert('would be implemented here') |
✅ python-docx, formatting runs, live page fields |
| Scope content | ❌ One hardcoded Fire Protection sample | ✅ 236 clauses, 139 spec sections, 20+ trades |
| CSI divisions | ❌ 16 hand-typed, several nonexistent | ✅ Canonical MasterFormat 2020, reserved excluded |
| Storage | ❌ Browser localStorage |
✅ PostgreSQL, SQLAlchemy 2.0, Alembic |
| Accounts | ❌ None | ✅ Organizations, roles, invitations, OIDC SSO |
| Tests | ❌ None | ✅ 216 tests, CI on Python 3.11 and 3.12 |
| Third-party JS | ❌ 8 CDN <script> tags |
✅ Zero — strict default-src 'self' CSP |
What makes it useful
Cross-division specification references. A Division 21 fire protection package is automatically offered the Division 07 firestopping, Division 08 access doors and Division 28 fire alarm interface it is contractually responsible for. Missing those is where scope gaps come from.
Numbering that means something. Outline labels are computed once and written literally into every format, so clause 3.2.4 identifies the same sentence in the PDF, the Word file, the Markdown and the JSON.
Documents that survive review. Real paged PDFs with running headers, footers and page numbers, and text that is selectable and searchable. Word files you can redline.
Versioned and auditable. Issuing a scope freezes an immutable revision. Later edits create a new version, so what went out with the subcontract is preserved verbatim.
Getting started
git clone https://github.com/ibuilder/procore-exhibit-generator.git
cd procore-exhibit-generator
cp .env.example .env
python -c "import secrets; print('SECRET_KEY=' + secrets.token_urlsafe(64))"
python -c "from cryptography.fernet import Fernet; print('ENCRYPTION_KEY=' + Fernet.generate_key().decode())"
docker compose up --build
docker compose exec web flask create-user you@example.com --org acme --role admin
docker compose exec web flask demo-data --org acmeImportant
Outside Docker, PDF export needs WeasyPrint's native libraries (Pango, cairo, GDK-PixBuf). Without them the app still runs and DOCX, HTML, Markdown and JSON all export — only PDF is disabled, and the UI says so. Run flask check-pdf to see where you stand; the deployment guide has the per-platform commands.
Security fixes
- Removed the Procore client secret from browser
localStorage, where any XSS could read it - Migrated off traditional Procore service accounts (retired 2025-03-18) to Developer Managed Service Accounts
- Argon2id password hashing, hash-only API token storage,
bleachallowlist sanitization ALLOWED_HOSTSvalidation, open-redirect checks, boundedProxyFixtrust- Tenant isolation enforced in one place; cross-tenant access returns 404, not 403
- Production config refuses to boot without secrets, and refuses SQLite
Breaking changes
Everything. There is no upgrade path from the prototype — it had no persistent storage to migrate. The static HTML files have been removed and the product is renamed ScopeMaker.
Full detail in CHANGELOG.md.
Not affiliated with or endorsed by Procore Technologies, Inc. CSI MasterFormat division titles are used for identification; the complete section list is published and copyrighted by the Construction Specifications Institute.