Skip to main content

Recombee integration

Recombee powers the two "Recommended For You" rows (feeds #2 and #3). Everything else is derived directly from Trakt or TMDB — Recombee handles the collaborative filtering that can't be computed from a single user's history alone.

Data flow

  ┌─────────────┐   daily     ┌──────────┐  items   ┌──────────┐
│ TMDB │────────────▶│ Reclio │─────────▶│ Recombee │
└─────────────┘ │ content │ │ catalog │
│ sync │ └─────┬────┘
└──────────┘ │
│ train
┌─────────────┐ adaptive ┌──────────┐ interactions │
│ Trakt │────────────▶│ Reclio │◀───────────────┘
│ history, │ │ user │
│ ratings, │ │ sync │ ranked IDs
│ watchlist │ └──────────┘─────────┐
└─────────────┘ │ ▼
│ ┌──────────┐
│ │ Recombee │
│ │ recommend│
│ └─────┬────┘
│ │ 50 movie + 50 show IDs
▼ │
┌──────────┐ │
│ Trakt │◀────────┘
│ managed │
│ lists │
└──────────┘


┌──────────┐
│ Chillio │
│ /feeds │
└──────────┘

Catalog properties

Per-title properties pushed to Recombee from content_sync:

PropertyTypeSource
titlestringTMDB title or name
overviewstringTMDB overview
genresset of stringsTMDB expanded genres
yearintParsed from release / first-air date
vote_averagedoubleTMDB vote_average
popularitydoubleTMDB popularity
media_typestring"movie" or "tv"
castset of stringsTop 5 from TMDB credits.cast
directorstringTMDB credits.crew where job == "Director"

item_id is always movie_{tmdb_id} or tv_{tmdb_id} — the type prefix prevents collisions between movies and shows that share a TMDB numeric id.

User interactions

Trakt signalRecombee callNotes
Watch history itemAddDetailView(user, item, ts)Implicit positive signal
Rating (1–10)AddRating(user, item, score, ts)Normalized to [-1.0, 1.0]; Trakt 5.5 ≈ 0, 10 → +1.0, 1 → −1.0
Watchlist entryAddBookmark(user, item)Strong positive intent signal

users.last_history_sync tracks the cutoff so we only push new interactions on subsequent syncs.

Why materialize into Trakt lists?

Rather than calling Recombee on every /feeds hit, we pre-materialize picks into two managed Trakt lists (Reclio • Recommended Movies, Reclio • Recommended Shows).

ConcernLive callMaterialized
/feeds latency budget (≤200ms)+100–200msZero extra calls
Recombee outage impactEvery /feeds failsPrevious recs still work
Chillio refreshes are frequentHammers RecombeeCheap DB read
Users see recs in Trakt tooNoYes — browsable at trakt.tv

Graceful degradation

If Recombee is unavailable (missing keys, network down, rate limit), get_recombee().available returns False and the Recommended For You rows fall back to a TMDB discover query filtered by the user's top genres. The Because You Watched rows, when their managed Trakt list is empty, fall back to TMDB /movie/{id}/recommendations for the last-watched item — and on cold-start, the vector similarity service seeds these rows from semantic neighbors. Chillio always gets a valid 10-feed response.

Recombee vs embeddings — which signal does what?

Reclio uses both collaborative filtering (Recombee) and content-based embeddings because they answer fundamentally different questions:

SignalQuestionStrengthWeakness
Recombee"What does this user want next?"Best when the user has 12+ Trakt interactions; learns from collective behavior across all usersCold-starts users badly; cold-starts new films invisibly
Embeddings"What is similar to this film?"Always works (no user data needed); catches semantic vibe genre tags missDoesn't know what you like, only what looks like X

The Because You Watched row is a 60/40 weighted blend of Recombee item-to-item + vector neighbors so it gets the best of both: Recombee's collective-behavior signal leads, embeddings fill in semantic neighbors Recombee's co-watch graph hasn't seen yet.

For new users (<5 Recombee results) the Recommended For You row falls back to vector neighbors of the user's highest-rated Trakt title so the home screen feels personalized from day one.

Diagnosing why nothing shows up in the Recombee web UI

The most common silent-failure mode is wrong_region — Reclio's local catalog is full of items, recombee_synced=true on every row, but the Recombee web UI shows zero items. New diagnostic endpoint detects exactly this:

curl -H "X-Admin-Token: T" https://<your-host>/admin/recombee/diagnose | jq .verdict

Returns one of: ok / wrong_region / unreachable / no_pushes_yet / writes_silently_failing / no_credentials / sdk_missing / client_init_failed. Each carries next_steps in plain English. See Troubleshooting for the full remediation matrix.

The hourly background sanity check (health-check job runs the same probe and logs a single WARNING with the verdict if anything degrades.