The problem
Sales reps at a manufacturer built every quote by hand. A customer emailed a list of parts, sometimes a bill of materials, sometimes a paragraph, and a rep looked up each item in the ERP one at a time. A good day was a couple of quotes.
The data didn’t help. The ERP, the CRM and the pricing warehouse each held part of the truth, they synced on different schedules, and reporting lagged a day behind the work.
The tempting answer
The obvious move in 2025 was to hand the whole thing to a language model: give it the email, get back a priced quote. It demos beautifully. It is also how a confident, wrong price ends up in front of a customer, and in quoting, one wrong price costs more trust than a hundred fast ones earn.
The rule: the model never does the math
So I split the job in two.
What the model does: reads messy emails and bills of materials, matches parts against the catalog and cross-reference data, shows its confidence as high, medium or low (not a fake-precise percentage), explains its reasoning, and raises flags: a competitor part number, a vague description, an item not in the catalog, a possible duplicate, a cross-sell nobody mentioned.
What the model never does: produce a number. Every price comes from a deterministic truth layer over the ERP and pricing warehouse. The suggested unit price is a three-month average that pre-fills the field and stays visible in its own column, so a rep who edits it never loses the context.
The guardrails follow from the rule. When the input isn’t enough, the agent says what’s missing instead of guessing. Submit stays disabled until required fields are filled. Every line can be reviewed, regenerated or searched by hand, and every quote keeps a version history the rep can restore.
Constraints that shaped it
- One door into the ERP. There was exactly one approved way into the system of record, and real-time sync through it wasn’t affordable. We built a single create, update and submit endpoint and polled for status instead of chasing webhooks.
- More scope than people. Seven engineers, and the first sprint was planned at well over what seven people could deliver. Half my job early on was deciding what we would not build yet.
- Someone else inherits the code. I set a “no vibe-coded apps” rule: AI could help write it, but the code had to be readable and DRY for the client’s own team, because they would own it after us.
The fixes nobody asked for
Midway through, I noticed the Python service opened a fresh, single-use database connection for every query. Requests were effectively standing in line for one connection at a time. I refactored it onto a shared connection pool so queries could run side by side, and along the way added caching, cleaned up the database views and simplified the connection architecture.
Nobody had filed a ticket for any of it. The same went for security: reviewing the routes, I found endpoints that didn’t require authentication and closed them. And because the client’s team would run the portal after we left, I built them an admin dashboard to manage the quote engine’s portal themselves.
What happened
We shipped the three-system integration, built to cut quote turnaround for 60+ sales reps from days to hours. When another engineer took over maintenance after me, their review estimated I had written 80 to 90% of the quote engine. The truth-layer pattern became my studio’s reference architecture for AI products and anchored a pitch to a new client.
What I can’t give you is a measured before and after. We didn’t track time-to-quote from the start, so days to hours is what the platform made possible, not a number I measured across the sales team.
What I’d do differently
It shipped with a tail of high-priority bugs at handoff, and adoption was weaker than I wanted. Two lessons I took from that:
- Adoption is a product problem, not a launch task. Next time I’d put two reps in the loop from week one and track time-to-quote from the first sprint, not the last.
- Writing most of the code is a strength and a warning. I’d spend more of that energy making the team faster, and less of it being the fastest person on the team.
