48 lines
4.9 KiB
Markdown
48 lines
4.9 KiB
Markdown
# RANKING — Bike-Tinder
|
||
|
||
The deck is ordered by predicted taste. Three rungs, in order: the hunt's existing rubric (v1), a cheap explainable boost from swipe clusters (v1.x), then a small learned model (Phase 3). Nothing ever becomes a black box.
|
||
|
||
## Signals
|
||
- Like = strong positive (+1). Maybe = weak positive (+0.4) on the *direction* — "right lane, wrong trim" (Glenn's read): the family/silhouette reads positive, the specific build is uncertain; maybes replenish via second looks + variant hunts. Nope = negative (−1). List-view edits count identically; the latest call wins (full history lives in `swipe_events`).
|
||
- Planned v2 signals: dwell time on card, "open listing" click-outs.
|
||
|
||
## Features (v1)
|
||
Per listing: family tier (from the hunt rubric), displacement (cc), price, year, km, km/year, dealer vs private, place/region, keyword flags from title + description (MVI/inspection, whitewalls, spokes, windshield, saddlebags, ABS, bobber/custom), color words (red is known lore), title tokens (make/model). Numeric features normalized; missing values neutral.
|
||
- Single-source v1 makes source/region features near-constant — kept in the schema for v2.
|
||
|
||
## Stage 1 — Cold start
|
||
Order by the hunt's existing rubric score (family tiers + MVI / whitewalls / windshield / spokes / ABS bonuses + price fit). This is already explainable (reason strings exist in the hunt system). Deck = rubric order until ≥ 20 swipes.
|
||
|
||
## Stage 1.5 — Cheap swipe boost (v1.x, post-launch)
|
||
Before any model exists, the importer can nudge `score` with plain swarm signals — e.g. +N when recent likes cluster in a family, small time-decay otherwise — recomputed each import, zero ML, fully explainable. Ships as v1.x if Glenn wants it; v1 itself stays pure rubric.
|
||
|
||
## Stage 2 — P3 taste engine (frozen 2026-11-10)
|
||
|
||
Deterministic, explainable, pure-Python. Learning reads `swipe_events` only.
|
||
|
||
**Event weights.** action: like +1.0 · maybe +0.4 · nope −1.0; round multiplier: round 1 ×1, round ≥2 **×2** (second strikes hit twice as hard — reaffirmations and flips both); recency: ×0.5^(age_days / 21). Event weight = product.
|
||
|
||
**Feature weights.** Feature set per listing: family · price band (`<3k, 3–5k, 5–8k, 8–12k, 12k+`) · displacement band (from title/desc) · year band · km band (when present) · each extras flag · seller type · silhouette keywords (bobber/custom/classic/cruiser) · color words · region. For each (feature, value): `raw = Σ event weights`; `shrunk = raw × n/(n+3)` — no single strike taboos a family.
|
||
|
||
**Score.** `taste_raw` = Σ shrunk weights over the listing's values; `taste_norm` = percentile rank among active listings (0–100). Blend: `w = min(0.85, N/60)` where N = total events; `points = clip( round( (1−w)·rubric + w·taste_norm + agent_adj ), 0, 100 )`. `agent_adj` = Σ profile adjustments (clamped ±10 per listing). Breakdown for the dossier: rubric share · top 3 ± taste features · agent lines — all source-labeled.
|
||
|
||
**Section recipe.** 25 cards = 2 wildcards + ≤5 returns + remainder new (points DESC). Constraints: ≤4 per family per section; ≥3 price bands when the pool allows; shortfalls fill from new.
|
||
|
||
**Wildcards.** Candidates: active, unseen, not in the last two sections. Exclusions: families with ≥3 cumulative nopes and net-negative weight. Prefer: different family AND different price band than the section's top new items. Score = `0.5·points + 0.5·novelty` (novelty = distance from the liked-profile centroid). Picks: 2 per section.
|
||
|
||
**Returns ("second looks").** Pool + caps per DATA-MODEL. Order: maybe > like > surprising nope (points ≥55 or net-positive family); rank within role by points DESC. A re-nope (round≥2 nope) rests the item permanently in v1.
|
||
|
||
**Guardrails.** Never hide (List untouched; ranking only reorders). The ramp keeps cold start on rubric; the engine must run with zero profile (w=0). Agent offline → compose on the last profile (source-labeled). **No LLM inside any request path or computed score** — the pass only writes the profile.
|
||
|
||
## Explainability
|
||
Each card can show up to 2 chips: top positive contributions for that bike ("low km for the money", "Vulcan family", "red", "MVI"). Chips derive from the live feature contributions; during cold start they come from the rubric's existing reasons.
|
||
|
||
## Evaluation
|
||
- Track: swipes/session, like-rate trend, maybe-rate, undo rate, and "liked-item rank" (how deep liked items sat — should trend shallow).
|
||
- P3 adds: hit rate (likes/decided · likes+maybes), returns re-strike rate, wildcard hit rate, flip count — surfaced in the Trends tab.
|
||
- Notifications (decided 2026-11-10): in-app "new since last visit" badge only — no Conduit pushes. A weekly digest remains possible later if asked for.
|
||
|
||
## Open decisions
|
||
- Maybe-handling — **resolved 2026-11-10 (Glenn):** weak positive on the direction ("right lane, wrong trim"); maybes replenish via second looks + variant hunts.
|
||
- Distance feature (map-based proximity) — likely v2.
|