built, not rolled outclaude code command · python · hubspot
problem
Building a quote meant looking up prices, working out discounts and recurring fees, checking margins, then re-keying it all into the CRM by hand.
what i built
A Claude Code command, /cpq quote, that takes a deal described in plain words. Python prices it, and the quote, line items, deal and contact links are created in HubSpot with the right account manager assigned.
status
It is built and working, but hasn't been rolled out to the team yet, so there are no usage numbers.
demo
claude code · sales
>/cpq quote 12 displays with players, mounts and install for Northwind Cafes, 3 years of software and support
●
cpq· priced 6 line items · drafting the quote in HubSpot
Quote Q-1042
Northwind Cafes · 36-month term
contact
Dana Reyes, operations lead
also involved
Sam Whitfield, finance, signs off the spend
account manager
J. Okafor, assigned to the deal
qty
item
unit
total
12
Commercial Display 55in
1,200.00
14,400.00
12
Media Player X1
450.00
5,400.00
12
Wall Mount Kit
120.00
1,440.00
12
Standard Installation
300.00
3,600.00
volume discount, 10+ screens: 5% of hardware
-1,062.00
bundle discount, hardware + software + install: 8% of hardware
-1,699.20
one-time total
22,078.80
12
Content licence, per screen per month
25.00
300.00
12
Pro support, per screen per month
15.00
180.00
recurring, per month
480.00
recurring, over 36 months
17,280.00
contract value
39,358.80
Needs approval: the 13% hardware discount is over the 10% limit, so this quote went to HubSpot's approval step.
Created in HubSpot: the deal, this quote, 6 line items, and links to the contact and company.
The customer, people, products and prices in this illustration are made up.
how it works
/cpq quote. Someone describes the deal in plain words in Claude Code.
read the request. Claude turns the request into structured inputs: customer, products, quantities and term.
price. Python looks up each product in the price list, applies volume and bundle discounts, and works out the recurring fees.
check limits. The discount and margin are checked against limits. Anything over a limit is marked for approval.
write to hubspot. The deal, quote and line items are created and linked to the right contact and company, and the person responsible for the deal is assigned.
only if over the limit. The quote goes to HubSpot's own approval step.
breakdown. Claude replies with a full breakdown of the deal and everyone involved.
code
pricing.pysimplified rewrite
from __future__ import annotations
from dataclasses import dataclass, field
from decimal import Decimal as D, ROUND_HALF_UP
from enum import Enum
Kind = Enum("Kind", "DISPLAY PLAYER MOUNT INSTALL LICENCE SUPPORT")
HARDWARE = {Kind.DISPLAY, Kind.PLAYER, Kind.MOUNT}
BUNDLE = {Kind.DISPLAY, Kind.PLAYER, Kind.INSTALL, Kind.LICENCE}
VOLUME_FROM, VOLUME_RATE = 10, D("0.05")
BUNDLE_RATE = D("0.08")
DISCOUNT_LIMIT = D("0.10")
MARGIN_FLOOR = D("0.20")
# volume + bundle stack to 13%, so big bundles always need approval
def price(lines: dict[str, int], book: dict[str, Item], months: int) -> Quote:
unknown = sorted(set(lines) - set(book))
if unknown:
raise ValueError(f"not in the price list: {', '.join(unknown)}")
if months <= 0 or any(qty <= 0 for qty in lines.values()):
raise ValueError("quantities and term must be positive")
items = [(book[sku], qty) for sku, qty in lines.items()]
one_off = sum(i.price * q for i, q in items if not i.recurring)
hardware = sum(i.price * q for i, q in items if i.kind in HARDWARE)
monthly = sum(i.price * q for i, q in items if i.recurring)
cost = sum(i.cost * q * (months if i.recurring else 1) for i, q in items)
discounts = {}
if sum(q for i, q in items if i.kind is Kind.DISPLAY) >= VOLUME_FROM:
discounts["volume"] = cents(hardware * VOLUME_RATE)
if BUNDLE <= {i.kind for i, _ in items}:
discounts["bundle"] = cents(hardware * BUNDLE_RATE)
off = sum(discounts.values())
one_time = one_off - off
contract = one_time + monthly * months
quote = Quote(cents(one_time), cents(monthly), cents(contract), discounts)
# approval itself happens in HubSpot
if hardware and off / hardware > DISCOUNT_LIMIT:
quote.approvals.append(Approval("discount", off / hardware, DISCOUNT_LIMIT))
margin = (contract - cost) / contract if contract else D(0)
if margin < MARGIN_FLOOR:
quote.approvals.append(Approval("margin", margin, MARGIN_FLOOR))
return quote
@dataclass(frozen=True)
class Item:
sku: str
kind: Kind
price: D # per screen per month if recurring
cost: D
recurring: bool = False
@dataclass(frozen=True)
class Approval:
rule: str
actual: D
limit: D
@dataclass
class Quote:
one_time: D
monthly: D
contract_value: D
discounts: dict[str, D]
approvals: list[Approval] = field(default_factory=list)
def cents(x) -> D:
return D(x).quantize(D("0.01"), ROUND_HALF_UP)
A simplified rewrite of the pricing step, not the production code. Run on the demo's order, it returns the quote above, including the approval flag. The products, prices, costs and limits are made up, and the HubSpot write isn't shown.
technical breakdown
from request to quote
CPQ stands for configure, price, quote. The command takes a request the way a salesperson would say it and returns a finished quote plus a breakdown of the deal: the parties and stakeholders, what is being sold, what it costs, and what still needs sign-off.
pricing
Pricing runs in Python: the price-list lookup, volume and bundle discounts, recurring fees, and the margin and approval limits. The same request always produces the same numbers.
approvals
When a quote is over a limit, it goes to HubSpot's own approval step. The automation doesn't add a second approval channel.
what gets written to hubspot
The deal, with a full breakdown of the parties and stakeholders.
The quote and its line items.
Links to the right contact and company.
The account manager, or whoever is responsible for the deal.
stack
Python
HubSpot (deals, quotes, line items, approvals)
Claude Code command
status
It is built and working, at a digital-signage company. It hasn't been rolled out to the team yet, so I have no usage figures or time savings to report. Rolling it out is the next step.