11 — Price-Pack Selector (the pick-side lever broaden-union needed)
Doc 10 shipped broadenUnion (candidate recall) and showed the residual lever is a
pick-side discriminator over the broaden-union top-K that can tell same-family pack
variants apart — the 208 rank-misses (54.7% of wrong binds), 76.4% of which are same-family
wrong-pack. Doc 10 also proved packAwareRerank is structurally inert: the pack/size
token is absent on 94% of extracted CK lines (they extract as bare "CLASSIC CARAWAY"),
so a name-based pack reranker has nothing to act on.
This doc specifies and measures the deterministic version of that pick-side lever:
pricePackSelect. The signal the bare-name line does carry is its unit price.
The signal
Section titled “The signal”CK’s WhereFour catalog prices each pack variant distinctly (list_price on the Typesense
product), and the separation is large for the dominant confusions:
| Family | 6/16 oz | 9/36 oz | 2 gal | 12/16 fl oz | 12/3.5 fl oz |
|---|---|---|---|---|---|
| Classic Caraway Kraut | $18 | $55.08 | $24 | — | — |
| Kimchi Pickle Chips | — | — | — | $30.82 | $11.06 |
A "CLASSIC CARAWAY" line with unit_price ≈ $20 sits ~10× closer to the 6/16 oz tier
($18) than to the 9/36 oz tier ($55) that the name-rank binds. The customer pays a
variable markup over WhereFour list (~10–30% observed), so the match must be relative
and decisive, never nearest-absolute.
Coverage: ~96% of extracted CK lines carry a unit_price; 55% of catalog products carry a
positive tier price. The lever no-ops on the rest.
What it does
Section titled “What it does”In findProductMatchesBatch Priority 3, gated pricePackSelect (default-OFF,
connection-scoped via extra_config.matchingConfig), among same-family near-tie candidates
it prefers the pack whose tier price is decisively consistent with the line’s unit
price. Pure pick-side tie-break — the candidate pool, the sort, the exact-id preference and
the relevance gate are unchanged. Logic in pickPriceConsistentPack
(utils/matching-helpers.ts), a flip requires all of:
- near — challenger within
PRICE_PACK_NEAR(0.35 relative) of one of its tiers; - same-family — pack-stripped name overlap with the incumbent ≥
PRICE_PACK_FAMILY_OVERLAP(0.5), so price can never cross product lines on a coincidence; - decisive — the incumbent has no usable price OR is ≥
PRICE_PACK_DECISIVE(2.5×) farther from the line price than the challenger.
The decisive-margin (3), not the near-band (1), is what prevents wrong flips: close-priced siblings ($18 vs $24) are never swapped because neither is 2.5× the other.
Measured lift (real matcher, 207-order corpus, conn 4f234677, history truth)
Section titled “Measured lift (real matcher, 207-order corpus, conn 4f234677, history truth)”| Config | Recall | Precision | Wrong | Unmatched |
|---|---|---|---|---|
| baseline (doc 02 baseline of record) | 56.5% | 61.3% | 38.7% | 7.9% |
pricePackSelect alone | 57.2% | 61.5% | 38.5% | 7.0% |
broadenUnion alone | 59.2% | 60.7% | 39.3% | 2.5% |
broadenUnion + pricePackSelect | 71.1% | 73.7% | 26.3% | 3.6% |
The two levers only work together — the doc-10 thesis, now measured end-to-end:
broadenUnionalone moves wrong-match the wrong way (+0.6pp): it pulls the right sibling into the pool but the name-rank picks the wrong pack more, so wrong-among-matched rises even as recall and unmatched improve. It is candidate generation with no picker.pricePackSelectalone is ~neutral (−0.2pp): without the widened pool the right sibling usually isn’t a P3 candidate to pick.- Combined: wrong-match 38.7% → 26.3% (−12.4pp), recall 56.5% → 71.1% (+14.6pp),
precision 61.3% → 73.7%.
broadenUnionsupplies the sibling;pricePackSelectpicks it.
Causal isolation (per-line, broadenUnion on, pps off→on, 207 orders)
Section titled “Causal isolation (per-line, broadenUnion on, pps off→on, 207 orders)”195 predictions change: 133 wrong→correct (wins), 0 correct→wrong (zero confident-wrong regressions), 51 wrong→wrong (neutral — a different wrong pack), 11 →unmatched (5 wrong→unmatched, ~6 correct→unmatched, i.e. dropped to operator review, never to a wrong bind). For a fail-closed auto-submit system, trading ~6 auto-correct binds for review to fix 133 confident-wrong binds is the right trade. The transitions are textbook:
[wrong→correct] "KRAUT CL CARAWAY" qty=100 unit=20.25 base=Classic Caraway Kraut - 9/36 oz Pack ($55.08) pps =Classic Caraway Kraut - 6/16 oz Pack ($18) truth=1616CC[wrong→correct] "PICKLED RED ONION" qty=90 unit=22 base=Pura Vida Pickled Red Onion 2 Gallon ($45) pps =Pickled Red Onion 6/16 oz Case ($18.75) truth=0616POConservatism / overfit guard
Section titled “Conservatism / overfit guard”- Off-path byte-identical. Worktree baseline (no flags) = main baseline exactly
(602/380/84 over 1066). All new reads are inside the
options?.pricePackSelectguard;quantity/unitPriceare threaded but never read off-path. Other connections unaffected. - Connection-scoped (D11). CK overfit risk is bounded because the flag is org/connection gated and cannot touch other customers. Same held-out discipline as doc 10 applies before flipping prod: confirm the lift holds on a held-out sender-domain split.
- No price-weight tuning. The lever is a hard decisive gate, not a fitted weight; the
three constants (
NEAR/FAMILY_OVERLAP/DECISIVE) are unit-tested against the known CK pack pairs inmatching-helpers.test.ts.
Recommended rollout
Section titled “Recommended rollout”Ship broadenUnion + pricePackSelect together for CK; neither alone is worth
flipping. Same prereq as doc 10: the extra_config PUT-persist gap (decisions.md open
items) is ship-blocking for prod toggling.
Artifacts
Section titled “Artifacts”- Selector + helpers:
apps/webapp/src/utils/matching-helpers.ts(pickPriceConsistentPack,priceTierDistance,productTierPrices). - Wiring:
apps/webapp/src/services/search/typesense-search-service.ts(MatchGateOptions.pricePackSelect, Priority-3 block). - Eval:
apps/webapp/scripts/run-ck-match-eval.ts --broaden-union --price-pack-select. - Determinism control:
packages/eval/scripts/run-ck-eval-repeats.sh.