Platform Manual
Architecture, workflows, and user guide
18
Active projects
351
Tasks tracked
7
Fleet components
~$47
Per month (Azure)
BFAM Builders · Odin Research · Version 2.0 · July 2026

Contents

1. Platform overview 2. Architecture — the fleet 3. Data model 4. URLs and access 5. Workflows 6. Portfolio dashboard 7. Managing projects 8. Permitting workflow 9. Mural decision tree 10. Swimlane board 11. Quoting tool 12. Checkers portal 13. Admin guide 14. Deployment 15. Developer guide
01Platform overview

Fulcrum is a project and permitting management platform built by Odin Research for BFAM Builders. It manages the full lifecycle of QSR exterior remodel projects — from initial permitting through construction completion — and is deployed at:

https://bfam-fulcrum.centralus.cloudapp.azure.com

What Fulcrum replaces

WasNow
Permitting_Workflow_Tracker.xlsxLive task checklists per project
Email Transfer.xlsxSite details, contacts, activity log
Teams threadsStructured notes + automated notifications
Manual status updatesReal-time dashboard
Shared SharePoint spreadsheetCheckers read-only portal
Manual quote reformattingBuilt-in quoting tool

Project types

TypeDescriptionPermits
RereLights and minor changesElectrical (sometimes), Mural (sometimes)
RemodelFull exterior, 8-day crewBuilding (stamped plans), Electrical, Mural
02Architecture — the fleet

Fulcrum is a monorepo where each component is named after a Rebel Alliance ship. Each ship has a single responsibility.

Ghost
Core API — the command center
FastAPI · Python · 7+ routers · Pydantic v2
Tantive
Database — carries the cargo
PostgreSQL · SQLAlchemy · Alembic · 9 tables
Falcon
Integrations — connects everything
Teams · Smartsheet · Graph API · Key Vault
Phantom
Background jobs — runs quietly
Azure Functions · scheduled triggers
Radiant
Reporting — delivers information
Excel export · Jinja2 email · weekly reports
Razor
Frontend — what everyone sees
HTML · CSS · JS · 6 views
Sentinel
Auth — controls who boards
Azure AD · MSAL · RBAC · user allowlist
03Data model
TableDescriptionKey fields
sitesPhysical locationsstore_number, address, jurisdiction, portal_url, portal_username
usersBFAM team membersemail, display_name, role, azure_oid, is_active
projectsActive jobssite_id, project_type, status, is_archived, coordinator_name
permitsPermit tracksproject_id, permit_type, status, permit_number
tasksChecklist itemspermit_id, name, status, sequence, assigned_to_id, due_date
task_dependenciesTask locking logictask_id, depends_on_task_id, required_status
task_status_historyImmutable change logtask_id, old_status, new_status, changed_by_id, changed_at
contactsAHJ and other contactssite_id, project_id, name, phone, email, contact_type
notesActivity logproject_id, task_id, author_id, body, created_at
quotesProject quotesproject_id, created_by, total, line_items (JSONB)

User roles

RoleCan do
adminEverything — manage users, edit all data, reveal credentials
project_managerEdit projects, tasks, permits, notes
coordinatorUpdate task statuses, add notes
viewerRead-only — Checkers corporate accounts

Task statuses

not_started in_progress blocked ext_blocked complete locked rejected na
04URLs and access
URLWhoWhat
/All internal usersPortfolio dashboard — all projects
/project.html?id=XAll internal usersProject detail — tasks, notes, contacts
/board.htmlAll internal usersSwimlane task board
/quote.htmlAdmin, PMQuoting tool
/adminAdmin onlyUser management, site editing, templates
/checkersCheckers corporateRead-only portfolio view
/docsAll internal usersThis manual
All pages require Microsoft login. Users sign in with their existing @bfambuilders.com Microsoft accounts. Checkers contacts use their @checkers.com accounts.

BFAM team

NameEmailRole
Chad Buttreychadwick@bfambuilders.comAdmin
Ted Petersonted.peterson@bfambuilders.comAdmin
Jaci Maysjaci.mays@bfambuilders.comAdmin
Joshuajoshua@bfambuilders.comProject Manager
Dan Fentondan.fenton@bfambuilders.comCoordinator
Ronaldronald@bfambuilders.comViewer

Checkers corporate

NameEmailAccess
Kevin Sorensemsorensenk@checkers.com/checkers only (viewer)
Russell Bernstinebernstiner@checkers.com/checkers only (viewer)
05Workflows

New project workflow

1
Create the project
Click "+ New project" on the dashboard. Enter site details, type, coordinator, and permit tracks.
2
Tasks created automatically
Fulcrum reads JSON templates and creates the full task checklist. Tasks start as Not started or Locked.
3
Assign coordinator and tasks
Click "Unassigned" on the project detail page to assign from the user dropdown.
4
Work through the checklist
Update task statuses as work progresses. Each change is logged with timestamp and user.
5
Permits issued
Teams notification fires automatically when permits are complete.
6
Construction handoff
Project moves to Construction. Pre-construction logistics tasks created. CF handoff is manual.
7
Archive on completion
Archive the project from dashboard or detail page. Data is preserved, never deleted.

Automated workflows (Phantom)

TriggerAction
Every hourSmartsheet sync — pulls construction dates from Checkers
Monday 7am ChicagoWeekly blocker report emailed to team
Daily 8am ChicagoEscalation — Teams ping for tasks blocked 3+ days
Task → BlockedImmediate Teams notification
All permits issuedTeams notification to #construction-handoff
06Portfolio dashboard

The main view at / shows all active projects at a glance.

ElementDescription
Summary cardsTotal projects, permits complete, in progress, mural pending
Filter buttonsAll / Rere only / Remodel only / Complete / In progress
Progress bars% of tasks complete per permit track
Status badgesGreen=complete, Blue=in progress, Amber=ext blocked, Red=blocked, Gray=not started
+ New projectOpens new project modal
Board linkNavigates to swimlane board
Docs linkOpens this manual
Show archivedToggle archived projects
Dark modeSun/moon toggle — persists across all pages
Sign outSigns out of Microsoft account
07Managing projects

Project detail page

PanelWhat you can do
Project headerSee name, type, status, address. Click coordinator to assign.
Permit tracksCollapsible checklists. Click status dropdown to update. Hover task for × remove. Click permit number to edit.
+ Add taskAdd ad hoc tasks below each permit track.
Activity logAll notes newest-first. Add note at bottom.
AHJ & ContactsJurisdiction contacts. + Add contact to add new ones.
QuotesSaved quotes with date and total. + New quote to create one.
Archive buttonNext to status badge. Confirms before archiving.

Archiving vs deleting

Projects are never hard-deleted. Archive hides them from the dashboard but preserves all data. Toggle "Show archived" to restore. Use the Unarchive button on an archived project to make it active again.

08Permitting workflow

Building permit — 14 tasks

#Task
1Identify AHJ + GIS lookup
2Look up requirements on city/county website
3Check for online portal, create account if available
4Submit via email/mail if no portal
5Discuss registration, plan submittal, additional requirements with AHJ
6Pull all required submittals and workflow
7Request required docs (insurance cert, bond, tax office, NOC, LOA)
8Complete contractor registration
9Submit registration (license, COI, BTR, bond)
10Coordinate sub-contractor registration
11Complete and submit permit application
12Coordinate with electrical and other sub trades
13Complete and hold all submission documents
14Pay fees and respond to all plan review comments
Entering a permit number: click the permit number area in the track header (shows "Add permit #" if empty). Type the number and press Enter.
09Mural approval decision tree

The mural permit follows a decision tree — options are tried in order, stopping when one is approved. Locked options unlock automatically when the previous is rejected.

OptionDescriptionUnlocks when
1Mural on both sidesAlways available first
2Mural on one sideOption 1 rejected
3Chevron paintedOption 2 rejected
4Parallelogram paintedOption 3 rejected
Jurisdiction-specific notes are in the activity log for each project — check these before submitting to know what each AHJ has accepted or rejected historically.
10Swimlane board

The board at /board.html shows all actionable tasks across all projects in five columns: Not started, In progress, Ext. blocked, Blocked, Complete.

  • Filter by person using the dropdown — shows one coordinator's full workload
  • Filter by project type (Rere/Remodel)
  • Locked and N/A tasks are hidden — only actionable tasks shown
  • Click any card to open the project detail page
  • Overdue tasks show in red
Use the person filter before weekly meetings — shows exactly what each coordinator is working on and what's blocked.
11Quoting tool

The quoting tool at /quote.html replaces the manual process of reformatting IAM quotes into BFAM format.

Creating a quote

1
Select a project
Pick an existing project or enter a new site name.
2
Add line items
Description, category, quantity, unit, unit cost, markup %. Total calculates automatically.
3
Import from IAM (optional)
Paste tab-separated IAM quote data into the import area. Line items populate automatically.
4
Review summary
Subtotal, markup amount, grand total, and margin % shown in the summary panel.
5
Export or save
Copy to clipboard (TSV for Excel), download as CSV, or save to the project in Fulcrum.

Saved quotes appear in the Quotes panel on the project detail page. Click any saved quote to reopen and edit it.

12Checkers corporate portal

Checkers corporate contacts access a read-only view at /checkers. They sign in with their existing Checkers Microsoft accounts.

Visible to Checkers

  • All project names and store numbers
  • Permit status per project
  • Mural status
  • Construction scheduled start date
  • Overall progress bar

Hidden from Checkers

  • Internal notes and activity log
  • Portal credentials
  • AHJ contact details
  • Task-level detail
  • Internal team assignments
Share this URL with Kevin and Russell: https://bfam-fulcrum.centralus.cloudapp.azure.com/checkers
13Admin guide

Admin page at /admin — accessible to Admin role only.

Users section

  • Lists all users with name, email, role, and status
  • Change role via dropdown — saves immediately
  • Deactivate blocks login without deleting data
  • Delete permanently removes the user and their allowlist entry
  • Add user form creates DB record and adds to allowlist
  • You cannot deactivate or delete yourself

Sites section

Click any field to edit inline — address, jurisdiction, portal URL, portal username. Changes save on Enter or blur.

Templates section

View the JSON task templates that define permitting checklists. Read-only in the UI. To modify templates, edit the JSON files in templates/ on the server.

After editing sentinel/users.json manually on the server, always validate it before restarting Ghost: python3 -c "from sentinel.middleware import validate_users_json; validate_users_json()"

Adding portal passwords to Key Vault

cd ~/fulcrum && source venv/bin/activate PYTHONPATH=/home/fulcrum/fulcrum python3 -c " from falcon.keyvault import set_secret set_secret('portal-pw-STORENUMBER', 'the-password') "
14Deployment
Production: https://bfam-fulcrum.centralus.cloudapp.azure.com — Azure VM, Central US

Infrastructure

ServiceDetailsCost/mo
Azure VMfulcrum-vm-01, Standard_F1als_v7, Ubuntu 24.04~$44
PostgreSQLLocal on VM (not managed service)$0
SSLLet's Encrypt via Certbot — auto-renews$0
DNSbfam-fulcrum.centralus.cloudapp.azure.com$0
Total~$44/mo

Deploy an update

# On your WSL machine — push changes git add . && git commit -m "your message" && git push # On the Azure VM — pull and restart ssh fulcrum@20.29.87.140 cd ~/fulcrum && git pull sudo systemctl restart fulcrum

Useful commands on the VM

# Check Ghost status sudo systemctl status fulcrum # View live logs sudo journalctl -u fulcrum -f # Restart Ghost sudo systemctl restart fulcrum # Restart Nginx sudo systemctl restart nginx # Check PostgreSQL sudo systemctl status postgresql # Database backup pg_dump -U fulcrum -h 127.0.0.1 fulcrum > backup_$(date +%Y%m%d).sql

Environment variables

All config lives in ~/fulcrum/.env on the Azure VM. Never commit this file. Key variables:

DATABASE_URL=postgresql+psycopg2://fulcrum:fulcrum@127.0.0.1:5432/fulcrum APP_ENV=production AZURE_TENANT_ID=07949940-2dd4-4dae-939b-59b0aad66c4a AZURE_CLIENT_ID=a40edb8e-bb0e-4e00-b190-e7ee2eee1c55
15Developer guide

Repository

github.com/tedbp19/fulcrum — private. Main branch is production.

Local development

cd ~/fulcrum source venv/bin/activate # activate Python env # Start PostgreSQL (WSL quirk) sudo -u postgres /usr/lib/postgresql/16/bin/pg_ctl \ -D /var/lib/postgresql/16/main \ -o "-c config_file=/etc/postgresql/16/main/postgresql.conf" \ -l /var/log/postgresql/postgresql-16-main.log start # Start Ghost uvicorn ghost.main:app --reload --host 0.0.0.0 --port 8000
For local dev, set APP_ENV=development and comment out AZURE_TENANT_ID in .env to enable the auth bypass. Never deploy with these settings.

Database migrations

cd ~/fulcrum # Generate migration after model changes PYTHONPATH=/home/tedbp/fulcrum alembic -c tantive/alembic.ini \ revision --autogenerate -m "description" # Apply migrations PYTHONPATH=/home/tedbp/fulcrum alembic -c tantive/alembic.ini upgrade head
After generating a migration, check for the PostgreSQL enum create_type=False issue — autogenerate doesn't handle shared enum types correctly. See existing migrations for the pattern.

Adding a permit template

Drop a new JSON file in templates/ following the existing format. Run scripts/migrate/04_seed_tasks.py to instantiate tasks for existing projects.

GitHub repository structure

fulcrum/ ├── ghost/ FastAPI backend (main.py, routers/, schemas/) ├── tantive/ Database (models/, alembic/, database.py) ├── falcon/ Integrations (teams.py, graph.py, keyvault.py) ├── phantom/ Azure Functions background jobs ├── radiant/ Reporting (blockers.py, excel_export.py) ├── razor/ Frontend HTML/CSS/JS + assets/auth.js ├── sentinel/ Auth (auth.py, middleware.py, users.json) ├── templates/ JSON permit task templates ├── scripts/ Migration scripts (run in order 01-06) ├── infra/ Nginx + systemd configs └── data/ Local data files (gitignored)