Skip to content

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.

CK’s WhereFour catalog prices each pack variant distinctly (list_price on the Typesense product), and the separation is large for the dominant confusions:

Family6/16 oz9/36 oz2 gal12/16 fl oz12/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.

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:

  1. near — challenger within PRICE_PACK_NEAR (0.35 relative) of one of its tiers;
  2. 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;
  3. 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)”
ConfigRecallPrecisionWrongUnmatched
baseline (doc 02 baseline of record)56.5%61.3%38.7%7.9%
pricePackSelect alone57.2%61.5%38.5%7.0%
broadenUnion alone59.2%60.7%39.3%2.5%
broadenUnion + pricePackSelect71.1%73.7%26.3%3.6%

The two levers only work together — the doc-10 thesis, now measured end-to-end:

  • broadenUnion alone 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.
  • pricePackSelect alone 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%. broadenUnion supplies the sibling; pricePackSelect picks 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=0616PO
  • Off-path byte-identical. Worktree baseline (no flags) = main baseline exactly (602/380/84 over 1066). All new reads are inside the options?.pricePackSelect guard; quantity/unitPrice are 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 in matching-helpers.test.ts.

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.

  • 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.