TBBN.Merchant Platform docs
Architecturedocs/architecture/matching-algorithm-spec.md

Matching Algorithm Spec

Why not just "newest / highest value / closest value"

A naive matching algorithm (sort by recency, or by absolute/closest price) does not scale past a small catalog and produces poor matches once cross-merchant volume grows. TBBN instead computes a Trade Compatibility Score per candidate pair and ranks by that.

Trade Compatibility Score

score = 0.35 * wantsMatch
      + 0.20 * categoryMatch
      + 0.15 * brandMatch
      + 0.10 * conditionMatch
      + 0.10 * valueMatch
      + 0.05 * geographyMatch
      + 0.05 * reputationMatch

Each sub-score is normalized to [0, 100]; the weighted sum is the final score out of 100.

  • wantsMatch (35%) — the dominant signal. Compares listing B's {category, subcategory, brand} against listing A's listing_wants rows (and vice versa for a symmetric two-way score). An exact category+subcategory+brand hit on a declared want scores 100; a category-only hit scores partial credit (~40); no overlap scores 0. This is what turns TBBN from a search engine into a matching engine (see docs/business/glossary.md — "Wants").
  • categoryMatch (20%) — top-level category equality between what's offered and what's wanted, independent of the explicit wants array (covers sellers who browse without having filled in wants).
  • brandMatch (15%) — brand equality/similarity.
  • conditionMatch (10%) — condition compatibility (e.g. both "Used - Good" scores higher than "New" vs. "Used - Fair").
  • valueMatch (10%) — how close the two TradeValues are; smaller absolute/relative difference scores higher, since a near-even trade requires less settlement friction.
  • geographyMatch (5%) — proximity/same-country bonus, relevant for anything where shipping cost materially affects the trade's attractiveness (deliberately low weight — TBBN is cross-border by design, see db-erd.md's globalization requirements).
  • reputationMatch (5%) — the offering seller's reputation_score (from reputation-service, Phase 12 — until that service is built this factor defaults to a neutral 50 for every seller, not 0, so early-network sellers aren't penalized for the absence of reputation data).

Worked example (from source spec)

Seller A wants: Apple Watch (category: Electronics, brand: Apple). Seller B has: Apple Watch (category: Electronics, brand: Apple, condition: Used - Good).

wantsMatch = 100 (exact category+brand hit) · categoryMatch = 100 · brandMatch = 100 · conditionMatch ≈ 90 · valueMatch depends on the two listings' TradeValue closeness (assume ~85) · geographyMatch and reputationMatch assume neutral (~50 each absent better data).

score = 0.35*100 + 0.20*100 + 0.15*100 + 0.10*90 + 0.10*85 + 0.05*50 + 0.05*50
      = 35 + 20 + 15 + 9 + 8.5 + 2.5 + 2.5
      = 92.5

Close to the 96 figure cited in source material for a similarly strong match — small differences come from the assumed condition/value/geography/reputation inputs in this illustrative example, not from a different formula.

Implementation note

Implement the scorer as a pure function in packages/core-matching (scoreTradeCompatibility( listingA, listingB, context) => { score, breakdown }) before wiring it into a live recommendation API — this lets the algorithm be validated against worked examples like the one above independent of infrastructure concerns, per docs/phase-plan/roadmap.md Phase 7 guidance.

Trade Value formula

TradeValue = OriginalPrice + RequestedAmount − WillingToPay

OriginalPrice is always merchant-supplied (TBBN never sets or discounts prices). RequestedAmount and WillingToPay are mutually exclusive per listing (see docs/business/conflict-resolution-log.md item 7) — at most one is non-zero.

Example A — Item A $500, Item B $450 → B pays $50 (the difference). Example B — Item A $500, Item B $600 → A receives $100. Example C (multi-item) — Item A $500 vs. Items B totaling $600 (200+150+250) → A pays $0 (B's side already exceeds A's side; no additional payment required from A, and the excess is simply the cost of B choosing to offer more value, not something TBBN redistributes).

Roadmap-only: ring trades / multi-hop trades

Documented, not built. Example: A wants a camera, B wants a guitar, C wants a laptop; A has a guitar, B has a laptop, C has a camera. No pairwise match exists, but the cycle A→C→B→A satisfies everyone. This requires a graph-search matching layer on top of the pairwise Trade Compatibility Score and is explicitly Phase 21 — the intended long-term moat, not a near-term deliverable. packages/core-matching's stub interface should not preclude this (keep the scorer pairwise-composable), but no ring-trade logic exists in this scaffold.