- Rust 90.5%
- HTML 7.9%
- Shell 0.6%
- Dockerfile 0.4%
- JavaScript 0.4%
- Other 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| assets | ||
| docs | ||
| migrations | ||
| scripts | ||
| src | ||
| templates | ||
| tests | ||
| theme | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| config.example.toml | ||
| config.toml | ||
| Containerfile | ||
| ingress.yaml | ||
| input.css | ||
| manifest.yaml | ||
| provenance.json | ||
| README.md | ||
| tailwind.config.js | ||
ConstructionBot
Procore RFI dashboard for the gal tenant. Phase 1 (Layer A): operator drafts RFIs, attaches vendor PDFs, submits to the Procore sandbox. Phase 3 (Layer B): sage-LLM extraction from uploaded RFI PDFs with a human review gate before any Procore write.
Architecture
- Rust/axum + askama + htmx — single binary, tenant Postgres, tokio background tasks in-process.
- No secrets in the repo — DATABASE_URL, Procore client credentials, and
SAGE_API_KEY arrive via env vars (manifest
dit.secrets, resolved on the runner from OpenBao). Non-secret config inconfig.toml. - Human gate — every RFI passes through operator review before Procore. The extraction pre-fills the form; the operator edits and submits.
- sha256-keyed cache — same PDF → same cached extraction, zero double sage compute (phase3-draft AC-3).
Quick start (local dev)
export DATABASE_URL=postgres://constructionbot:pw@localhost:5432/constructionbot
export PROCORE_CLIENT_ID=... # sandbox DMSA client id
export PROCORE_CLIENT_SECRET=... # sandbox DMSA client secret
export SAGE_API_KEY=... # sage auth (B0 confirms requirement)
cargo run -- --check-config # validate config.toml
cargo run # serve on :8080
Tests
cargo test --lib # unit tests (offline, no DB needed)
cargo clippy --all-targets -- -D warnings # lint (clean)
scripts/no_hardcoded_endpoints.sh # endpoint gate
# Integration tests (need DB + pdftotext):
nix-shell -p poppler-utils --run \
'DATABASE_URL=postgres://... cargo test --test extraction_cache -- --ignored --nocapture'
# Corpus worksheet (need DB + pdftotext + sage):
nix-shell -p poppler-utils --run \
'DATABASE_URL=... SAGE_API_KEY=... cargo test --test extraction_corpus -- --ignored --nocapture'
Phase 3 calibration — pilot week
Procedure
After deploying Layer B, run the extraction corpus suite per prompt change and record per-file edit counts:
-
Run the corpus suite with the current prompt:
nix-shell -p poppler-utils --run \ 'DATABASE_URL=... SAGE_API_KEY=... cargo test --test extraction_corpus -- --ignored --nocapture' -
Review the worksheet — for each fixture, the suite prints:
- subject, question, confidence, assignee hint, spec section, drawings
-
Upload each fixture through the dashboard and click "Create RFI from extraction." Count how many fields you manually edited before submitting.
-
Record edit counts in the table below.
-
Iterate the prompt — edit
extraction_system_promptinconfig.toml, restart the container (ditc deploy), re-run the suite.
Calibration table
| Fixture | Prompt ID | Subject edits | Question edits | Assignee edits | Total edits | Notes |
|---|---|---|---|---|---|---|
| rfi1_civil_conflicting | ||||||
| rfi2_foundation_conduits | ||||||
| rfi4_mat_slab_transition |
Target (phase3-draft)
- 80% of vendor RFI uploads yield an extraction where the operator can click "Create RFI" with ≤2 manual edits.
- Below 50% = Phase 3 extraction is net-negative; evaluate PlanGrep pivot or drop the feature.
Prompt iteration (phase3-draft AC-5)
Edit extraction_system_prompt in config.toml and restart the container.
Each prompt change gets a new prompt_id (short hash of the prompt text),
stored per extraction row for attribution. The corpus suite prints the
prompt_id so you can compare results across prompt versions.