Skip to content

Ranker v2: judged quality prior, featured home layout, evals - #38

Merged
AdvayMonga merged 1 commit into
mainfrom
feat/ranker-v2
Sep 21, 2026
Merged

AdvayMonga merged 1 commit into
mainfrom
feat/ranker-v2

Conversation

@AdvayMonga

Copy link
Copy Markdown
Owner

What

Replaces the home-feed ranker and adds an offline eval so ranking changes are measured instead of guessed.

  • Quality prior — every food gets a role + appeal (1–5) from data/food_labels.json (2,021 judged foods, hand-editable). Unseen foods are rated by a 189 KB numpy-only model. No API calls at ranking time.
  • Ranker — drops toppings/condiments/plain staples, then scores by appeal + rarity + favorites + taste affinity + student interest. Up to 3 dishes per station; best 20 per hall and meal flagged featured.
  • iOS — home feed lays out by featured (falls back to 5 stations × 3 when the API doesn't send it); See More reveals the rest; removed the substring name filter; versioned the feed cache.

Bugs fixed

  • Scraper wrote frequency to one meal-period row only → 88% of dinner rows had none, and the ranker treated that as a "rotating special".
  • Feed de-duplicated across halls and meals → a third of good mains were missing from a screen.
  • View/search timestamps are BSON dates but were compared with ISO strings → popularity, recent views and trending searches matched nothing since June.
  • "Trending" needed only one favorite.

Results (14 days of real menus, guest view)

main this PR
Junk on screen 21% 0% (2.5% if every food were new)
Great mains on screen ~50% 83%
Recommended items that fit the user 8% 89%

python evals/run_eval.py prints the scorecard; pytest gates it.

After merge

  • POST /api/backfill-frequency once (admin) to repair existing menu rows.
  • Redeploy the scraper Lambda for the UpdateMany fix.

🤖 Generated with Claude Code

Replace the feed ranker's calorie/ingredient heuristics with a per-food
quality rating (role + appeal), and measure ranking with an offline eval.

Ranking
- food_quality.py: role/appeal per food from data/food_labels.json (2,021
  judged foods, hand-editable); unseen foods rated by a small numpy-only
  linear model. No API calls at ranking time.
- ranker.py: gate out toppings/condiments/plain staples, score by appeal +
  rarity + favorites + taste affinity + student interest. Up to 3 dishes
  per station; the best 20 per hall and meal are flagged `featured`.
- Taste affinity sums per-favorite similarity (z-scored against the day's
  menu) and is normalized by sqrt(#favorites).
- Student interest counts distinct students per dish (home-feed views
  count half) and gains weight as the app gathers evidence.

Bug fixes
- Scraper set `frequency` on one meal-period row only (UpdateOne), and the
  ranker treated a missing value as a rotating special.
- Feed de-duplicated rec_nums across halls and meals, hiding dishes served
  at both lunch and dinner.
- item_views/search_queries timestamps are BSON dates but were compared to
  ISO strings, so view popularity, recent views and trending searches
  matched nothing.
- Trending required only one favorite.

iOS
- Home feed lays out by `featured` (legacy 5x3 fallback when absent);
  See More reveals the rest. Removed the substring name filter that hid
  dishes like "Butternut ..." and "... w/ Ranch". Versioned the feed cache.

Evals
- evals/run_eval.py replays 14 days of menus and grades what the home
  screen shows; pytest gates fail CI on a ranking regression.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@AdvayMonga
AdvayMonga merged commit 9ba0560 into main Sep 21, 2026
2 checks passed
@AdvayMonga
AdvayMonga deleted the feat/ranker-v2 branch September 22, 2026 20:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant