AMPED / HomeHeroes · replaces LeadByte · v2 pacing queue

How the router decides — every branch, lead to assignment

One decision per lead: which buyer gets it. Everything's on this page — the full decision map (every branch and layer a lead passes through, below), the live step-through where you watch the queue reorder lead by lead, and the calculator where you drag weights and coverage and watch the distribution respond. Start with the map, then drive the logic yourself.

When a lead comes in

The full decision path — every branch & layer, to assignment
A lead is born from an ad, hits a lander, and enters here with a zip + offer type. Follow the arrows: it's located to a county group, filtered by eligibility, then ranked by pacing deficit — the one new piece. Diamonds are branches; the dashed arrow is the pacing loop that feeds the next lead's math.
flowchart TB
  start(["Lead submitted
(zip + offer type)"]):::start subgraph L["1 · LOCATE"] direction TB county["Derive county from ZIP
(USPS crosswalk)"]:::proc group["Place in that county's GROUP"]:::proc end subgraph F["2 · FILTER — eligibility gate"] direction TB gate[/"Keep only buyers passing ALL:
active · serves zip · buys offer · credits ≥ price
· under caps · in schedule · filters"/]:::gate elig{"Any eligible
buyers?"}:::dec end subgraph R["3 · RANK — pacing deficit (the brain)"] direction TB over{"Manual override
buyer eligible?"}:::dec calc["For each eligible buyer:
target = weightShare × N
deficit = target − received"]:::proc pin{"Buyer pinned an
offer mix?"}:::dec smin["score = min(deficit, offer headroom)"]:::proc sdef["score = deficit"]:::proc rank["Sort by score ↓
ties: longest-waiting ↑ · bigger weight ↓ · id"]:::proc win["Winner = rank #1"]:::proc end house(["House-buyer fallback
(never dropped)"]):::term assign(["Assign + debit
(atomic · fully logged)"]):::assign start --> county --> group --> gate --> elig elig -- "No" --> house elig -- "Yes" --> over over -- "Yes (force)" --> win over -- "No" --> calc --> pin pin -- "Yes" --> smin --> rank pin -- "No" --> sdef --> rank rank --> win --> assign assign -. "updates N & received
(30-day rolling window)" .-> calc classDef start fill:#12294a,stroke:#2f81f7,stroke-width:2px,color:#eaf1fb; classDef proc fill:#16233b,stroke:#334867,color:#eaf1fb; classDef dec fill:#2a2416,stroke:#ffb020,stroke-width:2px,color:#ffe6bd; classDef gate fill:#1c2a12,stroke:#c6f24e,stroke-width:2px,color:#eef3d9; classDef assign fill:#123227,stroke:#3ad29f,stroke-width:2px,color:#d6f7ec; classDef term fill:#2c1620,stroke:#ff5c72,stroke-width:2px,color:#ffd7dd;

The one idea

The queue is the priority. A buyer that can't get leads (small coverage) sees its deficit grow, floating it to the top — so it wins its next eligible lead automatically. No manual "priority spot."

Deficit — dial ①

# per buyer, in-group, 30d target = share × N deficit = target − received pick: max deficit, eligible

Offer mix — dial ②

# if a buyer pins panel/EV headroom = offerTarget − got score = min(deficit, headroom) # caps EV share; one queue

Tie-breaks

  1. bigger deficit
  2. longest since last lead
  3. bigger weight
  4. buyer id (stable)

Legend

start / lead in
step (compute)
decision / branch
eligibility filter
assign + debit
fallback (house)

Step 3 — Eligibility gate

A hard yes/no filter, before anyone is ranked
Ranking never sees a buyer who fails here. This is the same gate the engine already has — unchanged. A buyer must pass every check to make the queue.
CheckPasses when…Notes
Activebuyer + their routing profile are on, not pauseda paused buyer is invisible
Zip (geo)a coverage row matches the lead's zip (exact / prefix / region)zero coverage rows = covers nothing
Offerbuyer buys this service type & has a price rowmissing price = ineligible (not a free lead)
Creditsprepaid balance ≥ the lead's priceprepay model, no overdraft
Capsunder daily / weekly / monthly limitbuyer-local calendar windows
Schedulenow is inside the buyer's hours/daysbad timezone fails closed
Filtersevery per-buyer rule passes (homeowner-only, etc.)AND of all rules

Step 4 — The deficit queue (the brain)

Watch it think, lead by lead
This is the whole change from today's engine. Below is the live SD group. Hit Next lead and watch each buyer's deficit (how many leads they're owed) update, the queue reorder, and the winner get picked. Notice how Voltwell — only 18 zips — keeps climbing while it's ineligible, then wins the instant one of its zips comes up.
# per buyer, inside the group, over the last 30 days target = (weight ÷ Σweights) × N # N = leads the group got deficit = target − received # how many they're owed (+ = behind, − = ahead) winner = eligible buyer with the biggest deficit ties → longest waiting → bigger weight → id
Press Next lead to route the first lead.
BuyerWeightZipsReceived TargetDeficitEligible?
Cumulative share vs 30-day target

❌ Static weighting (LeadByte)

Picks by a fixed weight % every lead, independently. It never notices Voltwell falling behind because its zips are rare — so Voltwell gets ~1–2% instead of its 16%. A "priority spot" is the manual band-aid for this blindness.

✅ Deficit queue

Every lead Voltwell can't get grows its deficit, floating it to the top. The next lead in its 18 zips → Voltwell is #1 and wins. It catches up to the ceiling of what's physically available, automatically. The queue is the priority.

The second dial — offer mix

One queue, but each buyer can pin panel vs EV
Total weight decides who's next. Offer mix is a secondary cap so a buyer isn't flooded with low-quality EV beyond their ad ratio. It stays one queue — not separate per-offer queues (which would skew total volume for buyers who take both).
# buyer pinned 75% panel / 25% EV offerTarget = mix(offer) × target offerHeadroom = offerTarget − receivedOf(offer) score = min( deficit , offerHeadroom ) # at their EV quota → EV score drops → # the EV lead flows to a buyer who still wants it # no pin → score = deficit (natural mix)

Why not just split into two queues?

Because a buyer who accepts both panel and EV would then sit in both and pull double volume vs a single-offer buyer next in line. Keeping one queue with a per-offer cap preserves each buyer's total share while still honoring their mix. The simulator below has an offer-mix toggle so you can see it.

Interactive simulator

Drag the dials, run leads, read the result
Change each buyer's weight, coverage size, and offer pin; change the lead supply; run the stream through naïve weighting vs the deficit queue side by side. The grey tick on each bar is the buyer's weight target; the green tick is their supply ceiling (the max they could ever win given coverage).
Buyers (SD group)
Lead supply
Lower "volume in Voltwell's zips" = more leads land outside its area = harder to hit its weight (that's Anthony's case). Raise it and its supply ceiling rises.

Tie-breaks, edge cases & operator controls

The corners — so there are no surprises

⚖ Ties

Equal deficit → longest since last lead (round-robin) → bigger weight → id. Fully deterministic; shadow-mode reproduces exactly.

🧊 Cold start

Empty window → everyone deficit 0 → falls back to round-robin by weight. Converges within ~2× the buyer count.

🈳 Nobody eligible

No coverage / all capped / all out of credits → house-buyer fallback. A lead is never dropped.

🎯 Manual override

"Feed buyer X everything it can take until I turn it off." Force-assigns any lead they're eligible for; the rest fall through to the queue.

🔥 Burn budget fast

Set a buyer's weight above their budget share → the deficit naturally over-delivers to them. (Point Loma at 52%.)

⛰ Supply ceiling

A buyer can't win more than their zips produce. If Voltwell's zips carry 10%, 10% is success — 16% is physically impossible.

🔀 Concurrency

Two leads racing for the same #1 are serialized by the atomic assign; the loser re-ranks against fresh counts.

🕶 Shadow first

Runs beside LeadByte logging what it would do, ≥2 weeks, before any cutover. Zero risk to live routing.

🔁 Overflow

The share a low-coverage buyer can't absorb flows to the all-county catch-all by weight. Nothing is stranded.

Open decisions to lock

Five calls for Matthew & Anthony
D-1 · Pacing window
How far back does "behind" look? 30 days rolling (matches dedup) is the recommended default — smooth and forgiving. Shorter (7d) reacts faster but is noisier. It's a per-funnel knob.
D-2 · When offer pins can't all be met
If everyone pins 75/25 but the ads deliver 50/50, the surplus EV has to land somewhere. Default: it goes to whoever is least over their EV quota. Alternative: name one buyer the "EV sponge." Which do you want?
D-3 · Live-count vs stored counter
Start by counting the 30-day window on each decision (simplest, exact). Move to a maintained pacing counter only if decision speed ever matters at volume. Recommend start simple.
D-4 · Accelerated pacing
Inflating a weight is usually enough to "burn fast." Add a true ASAP target (spend $X by date) only if you actually need it. Defer.
D-5 · Cap the catch-all?
Point Loma absorbing overflow past 52% is intended (burns their budget). If you ever want a hard ceiling, that's just their monthly cap.
Same engine underneath: uses buyer_routing_profiles.weight (already in the schema), county groups ride the existing region coverage, every decision writes a full trace to routing_decisions. The only real change is the ranking layer. Algorithm family: Weighted Deficit Round Robin (the fair-share scheduler that runs internet routers), with a hard eligibility gate. This page's step-through and simulator run the exact logic from sim.mjs.