Preconstruction software built on the firm's own job history
A client's construction team sat through a preconstruction software demo and decided to build instead. Here is what we built from their real estimates, and the rules that keep AI out of the bid totals.
The construction arm of a commercial real estate developer had years of estimates, bid tabs, and job costs spread across shared drive folders and spreadsheets. Every new estimate started from a template and someone's memory of what similar jobs cost. Leadership had just reviewed a demo of a preconstruction software platform and asked whether we could build it instead. Our recommendation was simpler than the demo: get the historicals into one place first, then layer AI on top.
The head of preconstruction had one objection to AI, and it shaped the whole build. Every sub submits differently. Some say they will do the job for a lump sum "per the drawings" and list nothing else. A tool that reads that as "everything included" puts a wrong number in the bid.
What we built
A Next.js app on Supabase and Vercel with two lenses over one database: Precon and Construction.
The precon side covers a project pipeline in the team's own stage names, conceptual estimates built from comparable past jobs, cash flow spread across each bid package's timing, bid packages and bid lists, bid leveling, and award. The construction side covers subcontracts opened automatically from awards, job cost by cost code, change orders, and insurance and bond compliance.
Underneath sit tested engines for cost code normalization, time and location adjustment, conceptual estimating with back-checks that fail loudly, cash flow, and leveling.
Three rules that carry the weight
Silence is not consent. When a proposal says nothing about a scope item, leveling marks it "unclear," never included and never excluded. A machine suggestion never enters a total. Only a verdict a person has accepted counts, and the screen shows the machine's original call beside the person's so the audit trail survives. The rule is enforced in the math, in a database constraint, and in the interface.
A plug and an adjustment are different money. An adjustment is a number a sub gave you. A plug is a number you made up because they did not pick up the phone. They add the same way and carry completely different risk, so they are tracked and reported separately, and plug exposure as a share of the leveled total is the number to check before award.
Consent for automated calls is a recorded fact. Leadership wanted AI agents making sub follow-up calls. Under the FCC's February 2024 ruling, an AI-generated voice counts as an artificial voice under the TCPA, which requires prior express consent. Each bidder carries a consent flag, date, and source, the API refuses an automated call without it, and the database refuses it too. Counsel still signs off on the consent language, but the data model will not have to change when they do.
The real data rewrote the assumptions
The first real files invalidated the app's biggest assumption. The firm does not estimate on the current MasterFormat structure. It uses the older 16-division structure with four- and five-digit codes, the same codes its construction accounting system carries. The team's stated goal was a clean handoff from preconstruction to operations on one set of cost codes, so we extracted all 426 of the firm's real codes, replaced the code engine, and keyed bid packages by trade instead of division. Plumbing, HVAC, and fire sprinkler share a division but go out as separate packages, and keying by division would have merged them.
Then the job book arrived: 23 real job folders, 493 cost lines, 13 estimate revisions, and zero lines left unclassified once the import was done. That ingest taught us a few things:
- The markup ladder compounds. Every estimate applies insurance first, then overhead and profit on a subtotal that already includes the insurance. Treating the two as a flat sum lands the price low. Neither rate is fixed either, so both are stored per estimate.
- Formatting can misfile almost everything. The estimate sheets write codes like "16400.000". Counting digits across the decimal made them eight-digit numbers, and 87 percent of real cost lines landed in an "unknown" division until that was fixed.
- Template dates lie. A date cell held the day the template was saved, not the day the job was estimated. Read naively, it would have back-dated current work and corrupted every escalation calculation. File names carried the real dates.
- The firm's own data had gaps. One job number used twice, one missing from the sequence, and a workbook whose hand-typed markup cells did not agree with its own percentages. Every one was flagged for a person. None was silently fixed.
Estimate accuracy, with guards
The head of preconstruction asked for one screen in particular: here is what we estimated, here is what it actually cost, and if we are consistently off by five percent in a category, tell us. That is now an accuracy view with a calibration factor per category. It is also the easiest screen in the app to mislead with, so it has three guards. It uses the median, so one bad job cannot set policy. It refuses to recommend a factor with fewer than five closed jobs. And it refuses when the middle half of the sample spreads more than 12 points, and says so in words. The factor is opt-in, and any copied summary shows the unadjusted figure too. Its numbers become real as final costs from closed jobs are loaded.
From lead to change orders
The last gap was the full flow. A new job could be created, priced, and moved through the pipeline, and then it dead-ended, because nothing could create a bid package for it. The screens only looked complete because sample data filled them.
Now a live job shows a buyout checklist built from its estimate, one package per trade line, with budgets taken from the estimate. The firm's own staffing, insurance, and fee never become packages. Each package gets a bid desk (invite subs, record bids and revisions, add scope lines), award opens a subcontract on the right cost code at the leveled amount, and change orders move committed and projected cost. We verified it end to end with 40 API checks, then drove the same job through the screens.
That walkthrough found seven bugs. The worst: any code starting with 99 was imported as markup, which swept the painting division into markup on every job.
What we deliberately did not build
Takeoff, because the team already owns a tool for it. Accounting, because the existing system stays. And a bid marketplace, because the team had cancelled several and did not want another. The goal was the firm's own data in one place, with AI that never touches a total without a person signing off.
What other operators can take from this
Your closed jobs are the most accurate cost index you will ever have for your market, and no competitor has them. Load them first. The AI layer is only as good as the history underneath it, and the history is usually messier than anyone expects, so build the importer to flag what it does not understand instead of guessing.
If your estimates still start from a blank template, book a Systems Audit.
Filed by
John McIntosh
Founder, Salvus AI
Replacing a loan management platform with a tracker that reads the loan documents
A client was pricing an off-the-shelf loan management platform for document imaging and deadline tracking. We built that part instead. Here is how it reads a closing binder, and the part we told them not to build.
A pursuit platform that scores public work before the team spends money chasing it
We built a pursuit platform for a client's construction team. It pulls public opportunities from federal sources, scores each one against what the firm actually builds, and tracks who else is bidding.
Talk to us
Want this kind of work shipped for you?
Salvus AI deploys per-client isolated MCP servers and the automations that run on them. Client success with real numbers, no shared tenancy.