Phase 1d — Recommendation Engine & Browse UI #5

Closed
opened 2026-10-08 07:30:17 +00:00 by agent-pi · 2 comments
Collaborator

The core feature: tell a user what to watch next.

This issue ports the recommendation engine proven in the spike (TMDB recommendations/similar fan-out, bayesian quality weighting, franchise cap, TV multi-seed requirement, genre-affinity discovery layer, wildcard injection, release-date filter) and builds the server-rendered browse UI — paginated movie/TV lists, a details page, rate/hide/mark-watched actions, the unrated-watched view, and dark/light themes.

The core feature: tell a user what to watch next. This issue ports the recommendation engine proven in the spike (TMDB recommendations/similar fan-out, bayesian quality weighting, franchise cap, TV multi-seed requirement, genre-affinity discovery layer, wildcard injection, release-date filter) and builds the server-rendered browse UI — paginated movie/TV lists, a details page, rate/hide/mark-watched actions, the unrated-watched view, and dark/light themes.
Author
Collaborator

Plan of attack for 1d, ported from the spike's round-2 (scoring formula reverse-checked against its tables: score = adjacency × clip((bayes/6)², 0.3, 2.0), adjacency = Σ(seed rating − 5), bayes m=300 C=6.0 — Alien 26×1.87≈48 ✓, Kill Bill 18×1.72≈31 ✓).

Findings

  • recommendations table does not exist yet (migration 2 stopped at dismissals) → migration 3.
  • Franchise cap needs belongs_to_collection, which only exists on movie details, not fan-out payloads → per-candidate detail fetch after the vote floor (vote_count ≥ 100).
  • Fan-out: TMDB /{movie,tv}/{id}/recommendations + /similar page 1 per seed; seeds = ledger items rated ≥ 8 (spike-faithful).
  • This box has no TMDB token → engine verified via MockTransport; the local stages (seeds/adjacency/rank/store) also smoke-tested against the real 1c-imported ledger.

Rulings (full list in the plan file)

  • Franchise cap is movies only (TMDB TV has no collection field; shows rely on the ≥ 2-seed rule).
  • Seen-filter: ledger ∪ dismissals ∪ (when connected) the user's Jellyfin library via a new paginated provider-ids call; library exclusion is best-effort.
  • Discovery: top-3 ledger genres OR-joined, vote_count.gte=100, release-date ≤ today, bayes-sorted.
  • Wildcards: 8+8 drawn with random.Random(user_id) from discovery mid-tier (ranks 10–60).
  • Recompute replaces the user's recommendation rows wholesale; candidate media upserts reuse the 1c preserve-if-present semantics.
  • UI: zero-JS; /movies, /shows, /discover, /item/{id}, /library/unrated, /actions/{rate,hide,seen,refresh} (rate handles the rate+watched / rate+hide combos), /theme cookie override with prefers-color-scheme default; 20/page pagination.

Branch: feat/issue-5-phase-1d-recommendations

Commits: ci fold (max_diff_lines 5000) → recommendations schema + ledger helpers → TMDB client → engine + recommend job → browse UI; then review fix wave. No new dependencies; no dependency bump today (consumed this morning).

Plan of attack for 1d, ported from the spike's round-2 (scoring formula reverse-checked against its tables: score = adjacency × clip((bayes/6)², 0.3, 2.0), adjacency = Σ(seed rating − 5), bayes m=300 C=6.0 — Alien 26×1.87≈48 ✓, Kill Bill 18×1.72≈31 ✓). **Findings** - `recommendations` table does not exist yet (migration 2 stopped at dismissals) → migration 3. - Franchise cap needs `belongs_to_collection`, which only exists on movie *details*, not fan-out payloads → per-candidate detail fetch after the vote floor (`vote_count ≥ 100`). - Fan-out: TMDB `/{movie,tv}/{id}/recommendations` + `/similar` page 1 per seed; seeds = ledger items rated ≥ 8 (spike-faithful). - This box has no TMDB token → engine verified via MockTransport; the local stages (seeds/adjacency/rank/store) also smoke-tested against the real 1c-imported ledger. **Rulings (full list in the plan file)** - Franchise cap is **movies only** (TMDB TV has no collection field; shows rely on the ≥ 2-seed rule). - Seen-filter: ledger ∪ dismissals ∪ (when connected) the user's Jellyfin **library** via a new paginated provider-ids call; library exclusion is best-effort. - Discovery: top-3 ledger genres OR-joined, `vote_count.gte=100`, release-date ≤ today, bayes-sorted. - Wildcards: 8+8 drawn with `random.Random(user_id)` from discovery mid-tier (ranks 10–60). - Recompute replaces the user's recommendation rows wholesale; candidate media upserts reuse the 1c preserve-if-present semantics. - UI: zero-JS; `/movies`, `/shows`, `/discover`, `/item/{id}`, `/library/unrated`, `/actions/{rate,hide,seen,refresh}` (rate handles the rate+watched / rate+hide combos), `/theme` cookie override with `prefers-color-scheme` default; 20/page pagination. **Branch:** `feat/issue-5-phase-1d-recommendations` **Commits:** ci fold (max_diff_lines 5000) → recommendations schema + ledger helpers → TMDB client → engine + recommend job → browse UI; then review fix wave. No new dependencies; no dependency bump today (consumed this morning).
Author
Collaborator

Merged via PR #10 (e939d3c). Recommendation engine (spike round-2 port, migration 3, worker recommend job) and the zero-JS browse UI are on main; 305 tests, real-ledger smoke verified, whole-branch review approved.

Merged via PR #10 (e939d3c). Recommendation engine (spike round-2 port, migration 3, worker `recommend` job) and the zero-JS browse UI are on `main`; 305 tests, real-ledger smoke verified, whole-branch review approved.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
marvin8/xique#5
No description provided.