Construction · AI estimating · Built 2026
Plan set in. Priced estimate out.
Commercial gate and access control jobs are bid off plan sets that run dozens of pages. Somebody has to find every gate, operator, fence run and card reader, count them, price them from the catalog, and turn it all into a quote. We built the engine that does the first draft, using the contractor's own pricebook, and shows its work so the estimator stays in charge.
- Client
- Citadel, commercial gate and access control
- Industry
- Construction, site work
- Connects to
- ServiceTitan pricebook
- Built
- March to September 2026
By the numbers
- 3: AI passes over every plan set: read, check for misses, count symbols
- 8: Closest catalog items weighed for every unmatched line
- 750: Automated tests guarding the pricing math
- 0: Lines priced without the estimator seeing the source
Source: the engine's code and test suite, September 2026.
Estimating was the bottleneck on growth.
Every bid starts the same way: a plan set, a highlighter, and hours of counting. Gates, operators, fence runs, bollards, card readers, each one found on the drawings, counted, and priced from the catalog by hand.
Miss a count and you eat it on the job. Price a spool of wire per foot and the bid is off by thousands. And the estimators who know how to do this well are the hardest people in the business to hire.
The contractor didn't want an AI that guesses. They wanted a fast first draft built from their own prices, with every number traceable, so an estimator could check it instead of starting from scratch.
An estimating engine that shows its work.
- Reads the whole plan set: Upload the PDF, a spreadsheet, or a written brief. Big plan sets are split automatically, and the AI pulls project details from the title block.
- Breaks callouts into real parts: A 'rolling gate with fence' becomes the leaf, track, rollers, footings and operator, each tagged as hardware or scope.
- Checks its own work: A second pass asks what was missed. A third counts the symbols on the drawings against the plan's key and flags any quantity that doesn't agree.
- Prices from your catalog: Each line is matched to the contractor's ServiceTitan pricebook: exact part numbers first, then the closest items by meaning, re-ranked by AI only when it's confident.
- Refuses bad matches: If a match's units don't fit the line, it's thrown out. A 500-foot spool never gets priced per foot.
- Builds the quote: Approve the workbook and the engine drafts the quote with base bid and alternates, inclusions and exclusions, and exports a branded PDF and Excel file.
From upload to a quote the estimator trusts.
- Upload: The estimator drops in the plan set, a spreadsheet, or a written scope.
- Read and itemize: AI reads every page and lists every item with a quantity and a unit.
- Audit the counts: Separate passes look for misses and check counts against the drawing symbols. Disagreements are flagged, never silently changed.
- Match the pricebook: Lines are matched to the contractor's own catalog, synced nightly from ServiceTitan. The matched cost is locked onto the line.
- Price the rest: Anything left gets an AI price anchored to the contractor's own similar items, with a confidence level and a one-line reason to accept or reject.
- Review and quote: The estimator edits directly or through a chat assistant that can only propose changes, then generates the quote.
Two rules that keep the numbers honest.
AI prices start from your own costs
When a line has no exact catalog match, the model is handed the contractor's most similar real items and their true costs, and told to price relative to those, not from a generic market guess.
let pricingBasis: String = anchored ? """
Each line may include a "comparable_costs" array: real cataloged items from THIS \
contractor's own pricebook that are similar to the line, each with its "name", its true \
vendor "cost_cents", and a "cosine" similarity in [0, 1] (higher = more similar). These are \
your PRICE ANCHORS. Price the material RELATIVE to them: interpolate or extrapolate from \
these real numbers, and prefer the closest comparable (highest cosine). Do NOT invent a \
figure detached from the anchors; they reflect this contractor's actual costs far better \
than a generic market guess. (Anchors inform the MATERIAL cost only: estimate labor \
independently.)
""" : """
These lines have NO catalogue comparable. Price each one from its description, quantity \
and unit of measure, using your knowledge of current market rates for this trade and \
region, and the project context.
"""
A spool never gets priced per foot
Catalog matches are thrown out when the units don't fit the line. Without this check, a spool of wire matched to an 85-foot run would be priced 85 times over.
switch Self.snapshotUnitVerdict(recordUnit: match.unitOfMeasure, lineUnit: lineUnit) {
case .reject:
// Units disagree: keep the item as a price hint, let AI pricing handle the line
return LineMatchResolution(snapshotMatch: nil, hintMatch: match, matchMethod: nil)
case .allow:
return LineMatchResolution(snapshotMatch: match, hintMatch: match, matchMethod: matchMethod)
}
/// This is the catastrophic path: a 500 ft spool with no unit, snapshotted
/// onto an 85 LF line, prices the spool 85 times over. A countable "each"
/// line has no such multiplier risk, so a blank unit there is assumed.
static let dimensionalUnits: Set<String> = ["LF", "SF", "SY", "CY", "GAL", "LB", "HR"]
static let countableUnits: Set<String> = ["EA", "PR"]
Built with: Swift server, PostgreSQL with pgvector, React, TypeScript, Claude (plan reading, pricing, quotes), OpenAI embeddings, ServiceTitan API
A first draft the estimator checks instead of builds.
The estimator's job moves from counting to judging: review what the engine found, look hard at anything it flagged, and send the quote.
- Every line traces back to a page of the plan set and a price source: catalog match, anchored AI price, or flagged for a human.
- Missed items and miscounts get a second and third look before a person ever sees the draft.
- Prices come from the contractor's own ServiceTitan catalog, synced nightly, and don't drift after the estimate is built.
- Nothing changes without the estimator's approval, including every edit the chat assistant suggests.
Your best people spend their day counting.
- Bids, quotes or proposals take hours of manual take-off.
- Your pricing lives in a system your estimators re-key from.
- You'd bid more work if quoting didn't depend on one or two people.
Book a call with Josh | See how custom builds work
Frequently asked questions
- What does the Citadel estimating engine do?
- It reads commercial gate and access control plan sets, itemizes every gate, operator, fence run and access device, checks its own counts, matches each line to the contractor's ServiceTitan pricebook, prices anything unmatched from the contractor's own comparable costs, and drafts a quote the estimator reviews.
- Does the AI make up prices?
- No. Lines are matched to the contractor's own catalog first. Anything unmatched is priced relative to their most similar real catalog items and shown with a confidence level and a reason. Lines with nothing to anchor to are flagged for a person.
- Can AI estimating work for other trades?
- Yes. The same pattern (read the documents, itemize, check the counts, match to your own pricebook, keep a human on the final number) works anywhere bids start from drawings, specs or scopes.
All work