From df5e427232b48dc06f51b954e4ed2cece9686b97 Mon Sep 17 00:00:00 2001 From: Quick <31828688+Quick104@users.noreply.github.com> Date: Fri, 22 May 2026 15:35:42 -0400 Subject: [PATCH] feat(collections): add tmdb_discover genre templates Append the Phase-2 catalog from the collection-templates expansion plan: - 18 Popular by Genre (sort_order 5001..5018, daily 0 4 * * *, popularity.desc with vote_count_gte=300) - 18 Top Rated by Genre (sort_order 6001..6018, weekly 0 5 * * 0, vote_average.desc with vote_count_gte=1000) - 1 Kids Movies (sort_order 9050, weekly, Family genre with certification_lte=PG) Adds TestPhase2DiscoverTemplatesUseExpectedBands to pin the band ranges and the TMDB query parameters that define each section. Co-Authored-By: Claude Opus 4.7 (1M context) --- internal/collections/templates/builtin.go | 683 ++++++++++++++++++ .../collections/templates/templates_test.go | 76 ++ 2 files changed, 759 insertions(+) diff --git a/internal/collections/templates/builtin.go b/internal/collections/templates/builtin.go index 31fece5e..0d24e3e9 100644 --- a/internal/collections/templates/builtin.go +++ b/internal/collections/templates/builtin.go @@ -829,6 +829,689 @@ var builtinTemplates = []Template{ DefaultSyncSchedule: "0 6 * * *", MDBList: &MDBListSpec{URL: ""}, }, + + // ── Popular by Genre (TMDB Discover) ───────────────────────────────────── + // 5000s band. Each entry runs TMDB's /discover/movie endpoint sorted by + // popularity.desc with a 300-vote floor to keep emerging-but-thin titles + // from polluting the top of the list. Daily refresh at 04:00 matches the + // existing tmdb_popular_* cadence. + { + ID: "tmdb_discover_popular_action", + Title: "Popular Action", + Description: "Crowd-pleasing action films sorted by current TMDB popularity.", + Icon: "💥", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5001, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{28}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_adventure", + Title: "Popular Adventure", + Description: "Trending adventure movies driving the most TMDB attention right now.", + Icon: "🗺️", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5002, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{12}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_animation", + Title: "Popular Animation", + Description: "Animated features riding high on TMDB popularity, from family fare to adult animation.", + Icon: "🎨", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5003, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{16}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_comedy", + Title: "Popular Comedy", + Description: "Comedies pulling the most viewers across TMDB this week.", + Icon: "😂", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5004, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{35}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_crime", + Title: "Popular Crime", + Description: "Heists, mobsters, and procedurals dominating TMDB's crime charts.", + Icon: "🕵️", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5005, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{80}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_documentary", + Title: "Popular Documentary", + Description: "Most-watched documentary features on TMDB right now.", + Icon: "🎥", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5006, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{99}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_drama", + Title: "Popular Drama", + Description: "Drama films riding the top of TMDB's popularity rankings.", + Icon: "🎭", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5007, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{18}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_family", + Title: "Popular Family", + Description: "Family-friendly hits with the biggest current TMDB audience.", + Icon: "👨‍👩‍👧", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5008, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{10751}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_fantasy", + Title: "Popular Fantasy", + Description: "Magic, myths, and quests — the fantasy slate trending on TMDB.", + Icon: "🧙", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5009, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{14}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_history", + Title: "Popular History", + Description: "Historical dramas and period pieces ranked by TMDB popularity.", + Icon: "🏛️", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5010, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{36}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_horror", + Title: "Popular Horror", + Description: "The horror titles drawing the biggest TMDB crowds right now.", + Icon: "👻", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5011, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{27}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_music", + Title: "Popular Music", + Description: "Music biopics, concert films, and musicals climbing TMDB's charts.", + Icon: "🎵", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5012, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{10402}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_mystery", + Title: "Popular Mystery", + Description: "Whodunits and puzzlers with the most active TMDB audience.", + Icon: "🔍", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5013, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{9648}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_romance", + Title: "Popular Romance", + Description: "Love stories pulling the most viewers across TMDB's romance shelf.", + Icon: "💞", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5014, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{10749}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_science_fiction", + Title: "Popular Science Fiction", + Description: "Sci-fi blockbusters and cult favourites sorted by TMDB heat.", + Icon: "🚀", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5015, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{878}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_thriller", + Title: "Popular Thriller", + Description: "Tense, fast-moving thrillers ranked by current TMDB popularity.", + Icon: "🗡️", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5016, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{53}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_war", + Title: "Popular War", + Description: "War films and combat dramas trending on TMDB this week.", + Icon: "⚔️", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5017, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{10752}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + { + ID: "tmdb_discover_popular_western", + Title: "Popular Western", + Description: "Westerns drawing the most TMDB attention, from classics to revisionist tales.", + Icon: "🤠", + Category: CategoryPopular, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 5018, + DefaultSyncSchedule: "0 4 * * *", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{37}, + SortBy: "popularity.desc", + VoteCountGte: 300, + }, + }, + + // ── Top Rated by Genre (TMDB Discover) ─────────────────────────────────── + // 6000s band. Sorted by vote_average.desc with a 1000-vote floor — high + // enough to filter out fluke perfect scores while still leaving room for + // well-regarded mid-tier titles. Refresh weekly on Sunday at 05:00. + { + ID: "tmdb_discover_top_rated_action", + Title: "Top Rated Action", + Description: "The highest-scoring action films on TMDB with enough votes to count.", + Icon: "💥", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6001, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{28}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_adventure", + Title: "Top Rated Adventure", + Description: "Best-reviewed adventure films across TMDB, weighted by user votes.", + Icon: "🗺️", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6002, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{12}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_animation", + Title: "Top Rated Animation", + Description: "The animated films TMDB users score highest, from Pixar to anime.", + Icon: "🎨", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6003, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{16}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_comedy", + Title: "Top Rated Comedy", + Description: "The best-rated comedies on TMDB, surfacing classics over flash-in-the-pan hits.", + Icon: "😂", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6004, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{35}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_crime", + Title: "Top Rated Crime", + Description: "The most acclaimed crime films on TMDB, gathered by user vote.", + Icon: "🕵️", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6005, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{80}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_documentary", + Title: "Top Rated Documentary", + Description: "Documentaries with the strongest TMDB scores and a solid vote count.", + Icon: "🎥", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6006, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{99}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_drama", + Title: "Top Rated Drama", + Description: "TMDB's best-rated dramas — character-led, story-first cinema.", + Icon: "🎭", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6007, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{18}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_family", + Title: "Top Rated Family", + Description: "Family films that earn TMDB's highest user scores.", + Icon: "👨‍👩‍👧", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6008, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{10751}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_fantasy", + Title: "Top Rated Fantasy", + Description: "Fantasy films with the best TMDB ratings across the genre.", + Icon: "🧙", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6009, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{14}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_history", + Title: "Top Rated History", + Description: "The most acclaimed historical films on TMDB, sorted by vote average.", + Icon: "🏛️", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6010, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{36}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_horror", + Title: "Top Rated Horror", + Description: "The best-rated horror films on TMDB, vote-weighted to keep the bar honest.", + Icon: "👻", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6011, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{27}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_music", + Title: "Top Rated Music", + Description: "Music films that earn TMDB's strongest user reception, from biopics to musicals.", + Icon: "🎵", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6012, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{10402}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_mystery", + Title: "Top Rated Mystery", + Description: "Mystery films with the highest TMDB scores and a healthy vote base.", + Icon: "🔍", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6013, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{9648}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_romance", + Title: "Top Rated Romance", + Description: "Romance films with TMDB's best-loved ratings and a real audience behind them.", + Icon: "💞", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6014, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{10749}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_science_fiction", + Title: "Top Rated Science Fiction", + Description: "Science fiction films at the very top of TMDB's vote averages.", + Icon: "🚀", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6015, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{878}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_thriller", + Title: "Top Rated Thriller", + Description: "Thrillers that crowd the upper end of TMDB's vote averages.", + Icon: "🗡️", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6016, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{53}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_war", + Title: "Top Rated War", + Description: "The most acclaimed war films on TMDB, sorted by user vote average.", + Icon: "⚔️", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6017, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{10752}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + { + ID: "tmdb_discover_top_rated_western", + Title: "Top Rated Western", + Description: "Westerns with the best TMDB scores, from classic horse operas to modern revisions.", + Icon: "🤠", + Category: CategoryTopRated, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 6018, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{37}, + SortBy: "vote_average.desc", + VoteCountGte: 1000, + }, + }, + + // ── Kids Movies (TMDB Discover) ────────────────────────────────────────── + // 9000s band (Misc/Kids slot). Combines TMDB's Family genre with a PG + // certification ceiling so the result tracks something an actual kid can + // watch, not just a soft-PG-13 family-marketed action film. + { + ID: "tmdb_discover_kids_movies", + Title: "Kids Movies", + Description: "Family-genre films capped at a PG certification — safe picks for younger viewers.", + Icon: "🧸", + Category: CategoryEditorial, + Source: SourceTMDBDiscover, + MediaKind: MediaMovie, + DefaultLimit: builtinDefaultLimit, + DefaultSortOrder: 9050, + DefaultSyncSchedule: "0 5 * * 0", + TMDBDiscover: &TMDBDiscoverSpec{ + MediaType: "movie", + WithGenres: []int{10751}, + SortBy: "popularity.desc", + CertificationLte: "PG", + VoteCountGte: 100, + }, + }, } var builtinBundles = []Bundle{ diff --git a/internal/collections/templates/templates_test.go b/internal/collections/templates/templates_test.go index b1a74275..38685034 100644 --- a/internal/collections/templates/templates_test.go +++ b/internal/collections/templates/templates_test.go @@ -455,6 +455,82 @@ func TestCategoryLabelKnowsAllBuiltinCategories(t *testing.T) { } } +// TestPhase2DiscoverTemplatesUseExpectedBands pins the Phase-2 TMDB Discover +// genre matrix to its documented sort-order bands and TMDB query parameters. +// Drift here would either reorder the gallery in surprising ways (band +// regressions) or change what a "Popular Action" / "Top Rated Action" template +// actually returns from TMDB (filter regressions) — both silent failures from +// the operator's perspective, so make them loud. +func TestPhase2DiscoverTemplatesUseExpectedBands(t *testing.T) { + const ( + popularPrefix = "tmdb_discover_popular_" + topRatedPrefix = "tmdb_discover_top_rated_" + kidsPrefix = "tmdb_discover_kids_" + ) + var popularCount, topRatedCount, kidsCount int + for _, tmpl := range List() { + switch { + case strings.HasPrefix(tmpl.ID, popularPrefix): + popularCount++ + if tmpl.Source != SourceTMDBDiscover { + t.Errorf("%s: Source = %q, want %q", tmpl.ID, tmpl.Source, SourceTMDBDiscover) + } + if tmpl.TMDBDiscover == nil { + t.Fatalf("%s: TMDBDiscover spec is nil", tmpl.ID) + } + if tmpl.DefaultSortOrder < 5000 || tmpl.DefaultSortOrder > 5999 { + t.Errorf("%s: DefaultSortOrder %d outside Popular band [5000, 5999]", tmpl.ID, tmpl.DefaultSortOrder) + } + if tmpl.TMDBDiscover.SortBy != "popularity.desc" { + t.Errorf("%s: SortBy = %q, want popularity.desc", tmpl.ID, tmpl.TMDBDiscover.SortBy) + } + if tmpl.TMDBDiscover.VoteCountGte != 300 { + t.Errorf("%s: VoteCountGte = %d, want 300", tmpl.ID, tmpl.TMDBDiscover.VoteCountGte) + } + case strings.HasPrefix(tmpl.ID, topRatedPrefix): + topRatedCount++ + if tmpl.Source != SourceTMDBDiscover { + t.Errorf("%s: Source = %q, want %q", tmpl.ID, tmpl.Source, SourceTMDBDiscover) + } + if tmpl.TMDBDiscover == nil { + t.Fatalf("%s: TMDBDiscover spec is nil", tmpl.ID) + } + if tmpl.DefaultSortOrder < 6000 || tmpl.DefaultSortOrder > 6999 { + t.Errorf("%s: DefaultSortOrder %d outside Top Rated band [6000, 6999]", tmpl.ID, tmpl.DefaultSortOrder) + } + if tmpl.TMDBDiscover.SortBy != "vote_average.desc" { + t.Errorf("%s: SortBy = %q, want vote_average.desc", tmpl.ID, tmpl.TMDBDiscover.SortBy) + } + if tmpl.TMDBDiscover.VoteCountGte != 1000 { + t.Errorf("%s: VoteCountGte = %d, want 1000", tmpl.ID, tmpl.TMDBDiscover.VoteCountGte) + } + case strings.HasPrefix(tmpl.ID, kidsPrefix): + kidsCount++ + if tmpl.Source != SourceTMDBDiscover { + t.Errorf("%s: Source = %q, want %q", tmpl.ID, tmpl.Source, SourceTMDBDiscover) + } + if tmpl.TMDBDiscover == nil { + t.Fatalf("%s: TMDBDiscover spec is nil", tmpl.ID) + } + if tmpl.DefaultSortOrder < 9000 || tmpl.DefaultSortOrder > 9999 { + t.Errorf("%s: DefaultSortOrder %d outside Misc/Kids band [9000, 9999]", tmpl.ID, tmpl.DefaultSortOrder) + } + if tmpl.TMDBDiscover.CertificationLte != "PG" { + t.Errorf("%s: CertificationLte = %q, want PG", tmpl.ID, tmpl.TMDBDiscover.CertificationLte) + } + } + } + if popularCount != 18 { + t.Errorf("Popular by Genre count = %d, want 18", popularCount) + } + if topRatedCount != 18 { + t.Errorf("Top Rated by Genre count = %d, want 18", topRatedCount) + } + if kidsCount != 1 { + t.Errorf("Kids count = %d, want 1", kidsCount) + } +} + // TestPhase1MDBListTemplatesHaveSortOrderInExpectedBands verifies that the // Phase-1 MDBList-backed templates land in the sort-order bands documented in // docs/superpowers/plans/2026-05-22-collection-templates-expansion.md. The