Viewr
A video app with a real recommendation engine behind it, not a chronological list.
The two-stage funnel
Every surface runs the same shape: candidate generation reduces the catalogue to hundreds by approximate nearest-neighbour retrieval over two-tower embeddings, then a ranking model scores those candidates. What changes per surface is the objective and the signal weighting, not the structure.
One item store, many surfaces
Home, watch-next, search and subscriptions read from a single shared item store. Keeping four disjoint databases is the mistake this design exists to avoid, because it makes every signal four times more expensive to collect.
Signals
Ranking weights engagement signals against satisfaction signals. Engagement is cheap and misleading on its own; satisfaction is sparse. The per-surface weighting is where most of the tuning lives.
surface: home
engagement click, watch_time
satisfaction completion, explicit_rating
weights 0.6 / 0.4Rebuilding the index
Embeddings are recomputed in a batch job and the ANN index is rebuilt from them. Retrieval reads the built index; nothing computes embeddings at request time.
$ python -m viewr.jobs.embeddings
$ python -m viewr.jobs.build_index --out index/current