Replacing an 11-tab pool estimating spreadsheet with a custom ops app
A SoCal pool builder was quoting custom pools out of an 11-tab Excel workbook — hundreds of active pricing items, fragile formulas, and no reliable way to know when supplier costs had changed. I replaced the workbook with a custom estimating and operations platform that the customer owns outright.
§ 01 · The problemThe estimating spreadsheet was the company
When the customer first walked me through their day, the operations stack was a single Excel workbook — 11 tabs, hundreds of line items, and the whole company’s pricing logic baked into it. Steel, Excavation, Plumbing, Gas, Electrical, Shotcrete, Tile & Coping, Equipment, Plaster, Options, Soft Costs — one tab per trade, formulas chained across all of them, and at the end a “Break Out Price List” tab that summed it all into a customer-facing number.
Quoting a custom pool meant opening the workbook, hunting for the right line items across all 11 tabs, hand-keying quantities, hoping no formula had been overwritten since the last quote, and trusting that the prices in those cells reflected what suppliers were actually charging this month. The tabs had real math — rebar count from the square root of interior area, shotcrete yards as IA × 0.032, plaster tier matched by interior-area bracket and perimeter bracket — and any one of those formulas could silently get clobbered when someone clicked into the wrong cell.
The wedge here was straightforward: replace the workbook with one app that holds every line item once, recalculates derived values automatically, tracks every price change with a timestamp, and warns you on the summary page if a quote you’re looking at has prices that drifted from the current book. The customer would own the source code, the data, and the database. No subscription.
Same pricing logic, two failure modes. The workbook spreads it across 11 tabs — one stray click can clobber a formula and silently break next month’s quote. The app holds every line item once, derives values from project dimensions, and writes a row to price_history every time a price changes.
Why not just buy something?
Buildertrend, JobTread, Houzz Pro, Projul — the off-the-shelf options for a builder this size — all solve a broader workflow than the actual problem here. The problem was narrow: preserve the existing estimating logic exactly, remove the spreadsheet’s failure modes, and make quoting faster without forcing the company into someone else’s process. A subscription tool would have flipped that around — the company bending its workflow to fit the software, paying every month forever, and still re-keying numbers because the SaaS didn’t quite know what a pool was.
The app was built around how the company already sold pools. Along the way we deliberately said no to features that look good on a competitor’s marketing page but create bloat at this company size: full Gantt scheduling, email marketing, daily field logs, vendor portals, lead scoring, revenue forecasting. Pool builders run on referrals, not drip campaigns. The result is a tool that does the customer’s work, not generic-contractor work the customer has to translate.
§ 02 · ScopeOne document. Signed before any code.
Every project starts with a SCOPE document. It’s short, opinionated, and names what’s out of scope as carefully as what’s in. For this engagement, the Phase 1 scope was the Estimator — price book admin plus the dimension-driven quote builder, sharing one customers / projects / estimates model. Everything else (quote expiration, user roles, customer portal, dashboards) was named in the Phase 2 candidate list and explicitly deferred. Most of it later got built; some of it is still on the “never” list on purpose.
The OUT-of-scope list is doing real work here. Naming the Phase 2 candidates kept the engagement honest about what Phase 1 was; naming the “never” items kept the customer’s tool from drifting into generic-contractor software.
§ 03 · PlanThe implementation plan, before any code
After SCOPE, I write the PLAN. It’s the implementation strategy — concrete enough to argue with, abstract enough to not pretend to be code. The customer reviews it, pushes back, signs off. Then I start building.
wrangler deploy
ships everything. New-developer onboarding is zero.
3. No ORM. SQL is hand-written. Schema is
small enough to read at a glance; ORMs hide more
than they help at this size.
4. Foreign keys enforced everywhere.
Referential integrity is the database’s job, not
the application’s. Impossible to forget.
## Build order (Phase 1)
- Schema + migrations + seed from extracted spreadsheet data
- Worker API: price book CRUD + estimate calc endpoint
- Frontend: Price Book admin (CRUD UI for items, tiers, plaster grid)
- Frontend: Estimator (dimensions → auto-calc → walkthrough → summary)
- Match the spreadsheet’s “Break Out Price List” tab exactly
- Side-by-side test against the spreadsheet on a real
recent quote — Phase 1 isn’t done until numbers match
## Risks
- Excel-formula fidelity drift → mitigation: every
formula extracted and reproduced literally; numerical
diff against the spreadsheet before declaring done
- Price book stays stale (same failure as the spreadsheet)
→ mitigation: price_history audit table + drift
detection on summary view (shipped in Phase 1.5)§ 04 · BuildWhat we shipped
Once dimensions are in, the system handles the math, the pricing, the drift check, and the PDF. The human picks which line items apply — the call that needs judgment.
Phase 1 shipped the Estimator and the Price Book. Phase 1.5 added quote expiration and price-drift detection. Phase 2 added user management and roles. Phase 3 added customers, projects, dashboards, and the client portal. Each phase was scoped, priced, and signed before the previous one shipped — the customer paying for what the next phase was worth before any of it got written. The discipline is the deliverable as much as the code is.
Items & Pricing
The Items & Categories step inside the estimator (demo data shown). Each of the 11 trades from the original Excel workbook is its own tab; line items pre-populate from the price book at their current rates and can be toggled in, adjusted, or noted per estimate. The 3-step wizard — Project Info, Items & Categories, Summary & Price — carries every quote through the same path, every time.
Items & Current Pricing
price_history — that’s what makes drift detection on quotes possible later.The Price Book screen, demo data shown. Every line item that an estimate can pull from lives here, in one place, at the current rate. The sidebar shows all 11 trades; the main view shows the items in the active trade with their current price and last-changed date. The audit trail is what later powers price-drift detection on saved quotes.
Spec at a glance
| Frontend | Single static HTML page. React 18 + ReactDOM loaded from CDN. Babel transpiled in-browser. No bundler, no build step. |
| Backend | Single Cloudflare Worker. Routes manually dispatched (no framework). |
| Database | Cloudflare D1 (SQLite at the edge). One schema, all foreign keys enforced. |
| Auth | Username + password, refresh-token rotation in HttpOnly cookies, no third-party auth provider. |
| Price freshness | Every price change writes to a price_history audit table. Estimates lock prices at quote time and compare against the current price book on every read — drift surfaces on the summary, never silently. |
| PDF export | Pure HTML → print stylesheet. Quotes are real, paginated documents, not screenshots. |
| Hosting | Cloudflare Pages + Workers. Deployed to the customer’s own ops subdomain. No dedicated server bill at the customer’s current traffic level. |
§ 05 · ShipWhat changed for the customer
Phase 1 went live. The owner started using it for real quotes; the spreadsheet got archived. Phase 1.5 followed quickly — the first time someone updated a price in the price book that affected a quote already sent, the drift card on the summary made it impossible to miss. Phase 2 (users + roles) and Phase 3 (customers + projects + portal + dashboard) followed as the tool earned the trust to grow into more of the operation.
The drift card on a quote summary, demo data shown. Every estimate locks the prices it was created with; on every read, the summary compares those locked prices against the current price book and surfaces any line that drifted. The user keeps the quoted prices or refreshes from the book — explicitly, never silently.
collapsed into one app
customer kept buying
and runbook
at this traffic level
The customer owns the source code, the data, and a runbook for keeping it running. Hosting runs on Cloudflare infrastructure with no dedicated server bill at this traffic level. What the customer bought was a one-time engagement they could come back to when they wanted more — not a subscription that bills whether they use it or not.
You’ve been pricing SaaS.
If your business is running on a spreadsheet everyone’s afraid to break — or on subscriptions that still don’t fit how you work — let’s talk. The first conversation is free. If it’s a fit, the next thing you’ll get is a written SCOPE document.
Get in touch →Clients aren’t named on a public site. References available on request to qualified prospects.