Most apps that try to recommend a meal start by interrogating you. How hungry are you, 1 to 10? Who are you eating with? How are you feeling today? I built one for myself, and the first decision I made was to throw all of that out. The only input it asks for is a tap on a button that says "shake."

Eight passive feature groups

Every time you tap shake, the app assembles a feature snapshot from eight groups and writes it as JSON onto the lottery result:

None of these need me to type anything. They all come from clocks, sensors, and history.

A model ladder, not a model

The recommendation engine is a stepladder of strategies, each with its own gate before the next one unlocks:

  1. uniform — pure random over the seed recipe pool, used until any feedback exists.
  2. weighted — rating × time decay, kicks in once a few accepts/rejects are logged.
  3. thompson — per-recipe Beta posterior, takes over once there's enough variance per recipe.
  4. lgbm_v1 — LightGBM binary classifier (accept vs reject) trained on the feature snapshots above.

LightGBM retraining runs nightly at 03:30 via a systemd timer. It splits 8:2 stratified, uses early stopping, and refuses to retrain if the feature schema hash has drifted since the last successful train. If the new model's AUC drops below 0.65, the previous version stays in production. A minimum of 200 feedback rows is required before LightGBM is allowed to take over from Thompson at all.

After dinner I take a photo of what I actually ate and send it to the app. Qwen-VL-Max identifies the dish and fills the meal log for me, so I never have to type a dish name either. The whole loop happens in the background — the app is doing math, I'm just eating.