An AI-assisted plan-review tool for municipal building/codes reviewers. It reads a 2D architectural floor plan and produces a reviewer-ready compliance checklist against IBC 2021 egress and occupancy provisions — every finding carrying its reasoning and its code citation.
It assists a reviewer. It never approves or denies a plan.
git clone https://github.com/Pothan0/floorplan_reviewer.gitcd floorplan_reviewer && npm installnpm run devOpen http://localhost:3000. Click any sample drawing and the full review runs — no API key, no network, no configuration. That path is worth trying first.
Six samples ship with it. Four are ordinary submittals; two are deliberately deficient and worth opening first, because they fail for opposite reasons:
- Taproom TI — a classification failure. The applicant calculated occupant load with the tables-and-chairs factor (15 sf net) and posted 120; the standing bar area is 5 sf net, which puts it at 520 and cascades into exit count, egress width, common path and remoteness. The building is sprinklered, but §1020.5 Exception 2 does not list Group A, so the 20 ft dead-end limit still governs.
- Warehouse → Office conversion — a geometry failure. Loads and widths are all fine; what fails is a 200 ft deep floor plate with exits only at the ends and no sprinklers.
Requires Node 20.9+ (developed on Node 24).
The built-in samples and manual entry work offline. A Gemini key is needed for two things: uploading your own drawing, and the retrieval layer that attaches code sections to findings. Neither the checklist nor its verdicts depend on it.
cp .env.example .env.localPut your key in .env.local:
GEMINI_API_KEY=your_key_here
Get one free from Google AI Studio. Restart the dev server afterwards. Without a key, uploading shows a friendly notice instead of failing — the compliance engine is unaffected.
The model defaults to gemini-3.6-flash; override with GEMINI_MODEL in .env.local.
Skip this unless upload fails with unable to get local issuer certificate or a bare
fetch failed.
Networks running TLS inspection (Zscaler, Netskope, Palo Alto…) re-sign HTTPS with a private root CA. Browsers trust it via the OS certificate store, but Node ships its own CA bundle and ignores the OS store, so server-side calls to Gemini fail.
npm run setup:caThat exports the proxy's root CA to certs/corporate-root-ca.pem (Windows; the script
prints the macOS/Linux equivalent). npm run dev then picks it up automatically.
This appends one trusted issuer — certificate verification stays fully on. Do not use
NODE_TLS_REJECT_UNAUTHORIZED=0. The variable must be set before Node starts, which is
why npm run dev goes through scripts/dev.mjs; setting it in .env.local will not work.
| Command | What it does |
|---|---|
npm run dev |
Dev server, with corporate-CA handling |
npm run dev:plain |
Stock next dev, no CA handling |
npm run build |
Production build |
npm run setup:ca |
Export the corporate root CA (see above) |
npm run export:samples |
Re-render the sample plans to PNGs in demo-plans/ |
npm run lint |
ESLint |
Two kinds of work, deliberately kept apart: reading a drawing is perceptual and sometimes wrong; applying the code must be reproducible. So the model is confined to perception, and a trust boundary sits between it and everything else.
| Stage | What happens | Who does it |
|---|---|---|
| Read | Two parallel Gemini calls: one reads the overall dimensions and units, the other reads each room's box, name, use and any visible doors | Model |
| Measure | Scale is calibrated per axis, room sizes computed, printed dimensions reconciled against scaled ones | Code |
| Confirm | Extracted boxes are drawn back onto the original drawing; every value is editable | Reviewer |
| Evaluate | IBC rules applied; findings emitted with reasoning and citations | Code |
The model is never asked to do arithmetic, convert a unit, or decide compliance.
Compliance is calculated, not retrieved — each rule is a threshold applied to a measured value, and asking a language model to decide whether 245 ft exceeds 200 ft would throw away the one property that makes this reviewable. So retrieval is not in the decision path.
What it does instead is answer the question a reviewer asks after reading a finding: what else in the code bears on this? Every finding carries the sections that can modify it — §1017.2's nine cross-referenced conditions under a travel-distance failure, §1004.5's approval exception under an occupant-load discrepancy — set out in full, with the section number linking to the adopted text.
Two properties make that safe to print next to a verdict:
- It cannot change a finding. The engine has already decided by the time retrieval runs. Context is added to the response, never fed back in.
- It runs after, and independently. If
/api/contextfails or no key is configured, the checklist is unaffected.
Relations that are known are asserted rather than embedded, and retrieval supplements above
a strict similarity floor. That is a measured decision, not a shortcut: the eligible corpus
is a dozen chunks all describing egress in one code, so they sit close together in embedding
space and cosine scores come out nearly uniform. Retrieval earns its place on the open
question box (/api/ask), where the query is unconstrained and the full corpus is in play.
The corpus is generated from the rule tables in src/lib/ibc/data.ts — no ICC code text
is reproduced in this repository.
- Absent data is never a failure. No doors detected → needs review, never fail.
- Doors are never invented. A fabricated opening makes an unsafe plan look compliant.
- Estimates are asymmetric. A straight-line distance over a limit fails (the routed path can only be longer); under a limit it is needs review, never a pass.
- No finding without a citation. The section reference is part of the result type.
- Self-checking. Horizontal and vertical scales are cross-checked; a disagreement over 5% is reported instead of silently skewing every room.
src/
app/
page.tsx 3-stage flow: intake → confirm → checklist
api/extract/route.ts Gemini vision → structured plan
api/context/route.ts Code sections attached to each finding
api/ask/route.ts Open questions, answered only from the corpus
components/
Intake.tsx Upload, samples, manual entry
PlanOverlay.tsx Extracted boxes drawn on the original drawing
PlanEditor.tsx Confirm/correct the extraction
Checklist.tsx Findings grouped by category
CheckCard.tsx One finding + reasoning trace + code sections
CodeLookup.tsx Ask-the-code box
lib/
geometry.ts Calibration, measurement, egress geometry
planSvg.ts Renders a plan as a life-safety sheet
samples.ts Six sample plans, two deliberately deficient
rag/
corpus.ts Chunks generated from the rule tables
store.ts In-memory cosine store over the corpus
ibc/
data.ts IBC 2021 rule tables + citations
engine.ts Deterministic compliance engine (no LLM)
types.ts Domain types
scripts/
dev.mjs Dev launcher; sets NODE_EXTRA_CA_CERTS
setup-ca.mjs Exports the corporate root CA
export-samples.mts Renders sample plans to PNG
demo-plans/ Sample drawings you can upload
src/lib/ibc/engine.ts is pure functions over a confirmed plan — no model, no network, no
randomness. Same input, same findings, every time.
Encoded from IBC 2021 and verified section by section against ICC Digital Codes, which is also where every citation in the UI links:
- Occupant load — §1004.2, Table 1004.5 (with gross/net basis)
- Exit count — Table 1006.2.1, §1006.2.1.1 tiers
- Egress capacity — §1005.3.2
- Common path — Table 1006.2.1
- Travel distance — Table 1017.2 (per occupancy group)
- Exit remoteness — §1007.1.1 (½ / ⅓ diagonal)
- Door size — §1010.1.1
- Ceiling height — §1003.2 / §1208.2
- Corridors — Table 1020.3 width, §1020.5 dead ends
- IRC scope-out — Group R-3 dwellings routed to IRC R310/R311
Section numbering moved between the 2018 and 2021 editions in two of these — dead ends went from §1020.4 to §1020.5 and corridor width from §1020.2 to §1020.3 — and both were wrong here until they were checked against the published text.
- Irregular (L-shaped) footprints defeat single-dimension calibration. Flagged, not silently wrong — a real permit set produced an 11% scale disagreement and said so.
- Room boxes trace the drawn extent rather than the inside face of walls, so scaled dimensions read ~1–3% large. A printed room dimension always wins when one exists.
- Travel distance and common path are straight-line estimates, not routed paths.
- Corridors and stairways must be entered by hand; they are not read from drawings.
- Stairway egress capacity (§1005.3.1), accessibility (Ch. 11), fire-resistance (Ch. 7) and height/area (Ch. 5) are out of scope.
- No persistence, export or permit-system integration — findings live for the session.
US jurisdictions adopt amended IBC editions rather than the base code, which is why state
codes (Ohio's OBC, Florida's FBC) are IBC-derived. Every threshold lives in typed tables in
src/lib/ibc/data.ts rather than being spread through the logic, so adopting one means
overlaying an amendment set on those tables. The engine itself does not change.
Demonstration prototype applying a curated subset of IBC 2021 Chapter 10 and Table 1004.5. Not affiliated with the ICC. It does not verify all applicable code, adopted local amendments, or accessibility provisions, and it does not constitute plan approval. Always defer to the adopted local code and a licensed plans examiner.