Documentation
¶
Overview ¶
Package personalize is Ether's Netflix-style personalization engine.
Viewers only ever use Plex. The engine makes each viewer's Plex Home and library sections feel hand-curated for them, invisibly, by choosing which rows they get, in which order, with which titles, for the device and time they are watching:
- a taste model per viewer (taste.go): affinities for genres, keywords, people, decades and languages, with time decay, learned from completions, rewatches, abandons, ratings and impressions that never led to a play, plus habits (session length, peak hours, device, binge and rewatch tendencies) and owner overrides;
- candidate rows from generators (generators.go): Continue Watching, Up Next, Top 10, Because you watched, More from <person>, Trending in your taste, Fans of what you watch also loved and Trending with fans of what you watch (crowd.go, buzz.go), Hidden gems, New this week, Comfort rewatches, Something different, Short on time, Finish the franchise, owner collections, LLM themes, Trakt, and popularity rows for new viewers;
- assembly per request context (assemble.go): context-aware item scoring, MMR diversity, cross-row dedupe, bandit row ranking with daily-seeded Thompson exploration (bandit.go), the owner's Home layout document (homelayout.go) and daily rotation;
- a feedback loop: every served row is recorded as an impression (impressions.go) and every play is attributed to the row it came from (plays.go), which feeds both the taste model and the bandit.
Models are built in the background (on a schedule, after watch-state changes and on demand) and cached per viewer; a /hubs request only runs assembly over the cache and never waits on TMDB or the LLM.
The Engine implements core.Recommender, core.ContextualRecommender, core.ImpressionSink and core.PersonalScorer, plus the owner-facing methods the admin API needs (owner.go).
Index ¶
- Constants
- type Discovery
- func (d *Discovery) ContinueAllowed(ctx context.Context, uid int64, t *core.Title) bool
- func (d *Discovery) FilterContinue(ctx context.Context, u *core.User, titles []core.Title) []core.Title
- func (d *Discovery) Hubs(ctx context.Context, u *core.User, limit int) ([]core.Hub, error)
- func (d *Discovery) Rediscover(ctx context.Context, u *core.User, limit int) (core.Hub, error)
- func (d *Discovery) RemoveContinue(ctx context.Context, uid int64, key core.RatingKey) error
- func (d *Discovery) RestoreContinue(ctx context.Context, uid int64, key core.RatingKey) error
- func (d *Discovery) ReviewCollections(base core.Collections) core.Collections
- func (d *Discovery) YearInReview(ctx context.Context, u *core.User, year, limit int) (core.Hub, error)
- type Engine
- func (e *Engine) Activity(ctx context.Context, days int) ([]core.ActivityDay, error)
- func (e *Engine) AvailableRows(ctx context.Context) ([]core.RowDescriptor, error)
- func (e *Engine) Close()
- func (e *Engine) FavoritesChanged(userID int64)
- func (e *Engine) FilterRecommendations(ctx context.Context, u *core.User, items []core.Title) ([]core.Title, error)
- func (e *Engine) FinalizeHome(ctx context.Context, user *core.User, hc core.HubContext, section string, ...) ([]core.Hub, error)
- func (e *Engine) FinalizeNeutralHome(ctx context.Context, user *core.User, situation core.HomeSituation, ...) ([]core.NeutralHomeRow, error)
- func (e *Engine) Flush(ctx context.Context)
- func (e *Engine) HomeCandidatesFor(ctx context.Context, u *core.User, hc core.HubContext, limit int) ([]core.Hub, error)
- func (e *Engine) HomeCandidatesWithPolicy(ctx context.Context, u *core.User, hc core.HubContext, limit int, ...) ([]core.Hub, error)
- func (e *Engine) HomeHubs(ctx context.Context, user *core.User, limit int) ([]core.Hub, error)
- func (e *Engine) HomeHubsFor(ctx context.Context, user *core.User, hc core.HubContext, limit int) ([]core.Hub, error)
- func (e *Engine) HomeLayout(ctx context.Context) (core.HomeLayout, error)
- func (e *Engine) HubItemsFor(ctx context.Context, u *core.User, hc core.HubContext, id string, limit int) (core.Hub, bool)
- func (e *Engine) InvalidateOwnerRows()
- func (e *Engine) LayoutDefaults() map[string]int
- func (e *Engine) MicroReason(ctx context.Context, user *core.User, t *core.Title, now time.Time) core.MicroReason
- func (e *Engine) ModelReady(userID int64) bool
- func (e *Engine) NotInterestedChanged(userID int64)
- func (e *Engine) NotePlayback(user *core.User, key core.RatingKey)
- func (e *Engine) PostPlay(ctx context.Context, user *core.User, justWatched core.RatingKey, ...) ([]core.PostPlayPick, error)
- func (e *Engine) PreviewHome(ctx context.Context, user *core.User, hc core.HubContext, sectionArg string, ...) (*core.HomePreview, error)
- func (e *Engine) PreviewLayout(ctx context.Context, user *core.User, hc core.HubContext, section string, ...) (*core.HomePreview, error)
- func (e *Engine) RankerInsight(ctx context.Context) (core.RankerInsight, error)
- func (e *Engine) Rebuild(ctx context.Context, user *core.User) error
- func (e *Engine) RecentActivity(ctx context.Context, userID int64, limit int) ([]core.ViewerActivity, error)
- func (e *Engine) RecordDetailOpen(ctx context.Context, user *core.User, t *core.Title)
- func (e *Engine) RecordImpressions(user *core.User, hc core.HubContext, hubs []core.Hub)
- func (e *Engine) RecordNeutralImpression(user *core.User, imp core.NeutralImpression)
- func (e *Engine) Refresh(ctx context.Context, user *core.User) error
- func (e *Engine) Related(ctx context.Context, user *core.User, key core.RatingKey, limit int) ([]core.Hub, error)
- func (e *Engine) RowInsights(ctx context.Context, days int) ([]core.RowInsight, error)
- func (e *Engine) ScoreTitles(ctx context.Context, user *core.User, titles []core.Title) (map[core.RatingKey]float64, error)
- func (e *Engine) SectionCandidatesFor(ctx context.Context, u *core.User, t core.MediaType, hc core.HubContext, ...) ([]core.Hub, error)
- func (e *Engine) SectionCandidatesWithPolicy(ctx context.Context, u *core.User, section core.MediaType, hc core.HubContext, ...) ([]core.Hub, error)
- func (e *Engine) SectionHubs(ctx context.Context, user *core.User, t core.MediaType, limit int) ([]core.Hub, error)
- func (e *Engine) SectionHubsFor(ctx context.Context, user *core.User, t core.MediaType, hc core.HubContext, ...) ([]core.Hub, error)
- func (e *Engine) SemanticRowKind(id string) string
- func (e *Engine) SetBus(bus clusterbus.Bus)
- func (e *Engine) SetExtraHomeRows(fn func(context.Context, *core.User, core.HubContext, string, int) []core.Hub)
- func (e *Engine) SetHomeLayout(ctx context.Context, d core.HomeLayout) (core.HomeLayout, error)
- func (e *Engine) SetTasteOverrides(ctx context.Context, user *core.User, os []core.TasteOverride) (*core.TasteProfile, error)
- func (e *Engine) Taste(ctx context.Context, user *core.User) (*core.TasteProfile, error)
- func (e *Engine) TrainRanker(ctx context.Context) (core.RankerInsight, error)
- func (e *Engine) ViewerStats(ctx context.Context, userIDs []int64) (map[int64]core.ViewerStats, error)
- type Evidence
- type FavoriteSignal
- type NotInterestedSignal
- type Options
- type Problem
- type RankerOptions
- type Taste
- type TrendSource
- type UserSource
- type ValidationError
Constants ¶
const ( KindFavorites = "favorites" KindContinue = "continue-watching" KindUpNext = "up-next" KindTop10 = "top-10" KindTop10Today = "top-10-today" KindShowcase = "genre-showcase" KindBecause = "because-you-watched" KindPerson = "more-from-person" KindTrendingTaste = "trending-in-your-taste" KindHiddenGems = "hidden-gems" KindNewThisWeek = "new-this-week" KindComfort = "comfort-rewatches" KindDifferent = "something-different" KindShortOnTime = "short-on-time" KindFranchise = "finish-the-franchise" KindLLM = "llm" KindTrending = "trending" // Crowd rows (crowd.go): public co-engagement over the viewer's // favourites, and its intersection with what is trending now. KindCrowd = "fans-also-loved" KindCrowdTrending = "trending-with-fans" KindPopularServer = "popular-on-server" KindNewReleases = "new-releases" KindTopRated = "top-rated" KindPopular = "popular" KindAiringToday = "airing-today" KindTraktRecs = "trakt-recommendations" KindTraktWatch = "trakt-watchlist" KindRecentlyAdded = "recently-added" // built by the Plex front end in sections KindPlaylists = "playlists" // built by the Plex front end on Home // Movies → Recommended lenses (lenses.go): type-native rows that // explore the viewer's film taste from one angle each. KindTasteCluster = "taste-cluster" // "Heist Thrillers", "Sci-Fi Thrillers" KindEra = "era-favourites" // "Best of the '90s for You" KindWorld = "world-cinema" // "Korean Cinema for You" KindOccasion = "occasion" // "Date Night", "Family Night", "Under 100 Minutes" KindAcclaimed = "acclaimed-unseen" // acclaimed titles by fit KindTopGenre = "top-rated-in-genre" // "Top-Rated Thrillers" in the viewer's genres KindBridge = "taste-bridge" // "Where <Seed A> Meets <Seed B>" KindStudio = "more-from-studio" // "More from A24" KindNewSeasons = "new-seasons" // TV Shows → Recommended (lenses_tv.go) KindFinishShow = "finish-what-you-started" KindStartNew = "start-something-new" KindBinge = "complete-series" KindLimited = "limited-series" KindAiringTaste = "airing-for-you" KindNetwork = "more-from-network" KindSticksLanding = "sticks-the-landing" KindStandalone = "standalone-episodes" // KindCollectionPrefix + collection id is an owner collection row. KindCollectionPrefix = "collection:" )
Row kinds. A kind is the stable, owner-facing identity of a row family: layout rules, insights and the bandit all work per kind. Hub ids are per row ("home.because.<key>"), kinds are not.
const ( EvidenceFinished = "finished" EvidenceWatching = "watching" EvidenceAbandoned = "abandoned" EvidenceRated = "rated" EvidenceIgnored = "ignored" // shown repeatedly, never played )
Evidence kinds.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Discovery ¶
type Discovery struct {
// NotInterested applies only to recommendation rows, never Continue.
NotInterested func(context.Context, *core.User) ([]NotInterestedSignal, error)
// contains filtered or unexported fields
}
Discovery adds viewer-owned utility rows without changing Designer layout.
func NewDiscovery ¶
func (*Discovery) ContinueAllowed ¶
func (*Discovery) FilterContinue ¶
func (d *Discovery) FilterContinue(ctx context.Context, u *core.User, titles []core.Title) []core.Title
FilterContinue deduplicates episodes by their parent show's TMDB identity.
func (*Discovery) Rediscover ¶
func (*Discovery) RemoveContinue ¶
RemoveContinue stores a sticky show-level dismissal, so the next episode or a repeated timeline cannot immediately put a dismissed show back on Home.
func (*Discovery) RestoreContinue ¶
func (*Discovery) ReviewCollections ¶
func (d *Discovery) ReviewCollections(base core.Collections) core.Collections
ReviewCollections retains the collection engine as the authority for owner collections and adds virtual collections assembled from the viewer's history.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine is the personalization engine.
func New ¶
New builds the engine and starts its background work (impression writer, event handling, model builders, scheduler). Call Close on shutdown.
func (*Engine) Activity ¶
Activity returns server-wide viewing per local day for the last `days` days, oldest first, including empty days.
func (*Engine) AvailableRows ¶
AvailableRows lists the row kinds layout policies can target, plus owner collection rows recently served, each with the pages it can appear on (Sections) and its designer group.
func (*Engine) Close ¶
func (e *Engine) Close()
Close stops background work, writing any queued impressions.
func (*Engine) FavoritesChanged ¶ added in v0.3.0
FavoritesChanged schedules a local rebuild and notifies peers without blocking the caller's committed mutation. Bursts coalesce on the normal rebuild queue. The caller must not use this after Close.
func (*Engine) FilterRecommendations ¶ added in v0.3.0
func (e *Engine) FilterRecommendations(ctx context.Context, u *core.User, items []core.Title) ([]core.Title, error)
FilterRecommendations applies explicit exclusions without changing the catalogue, watchlist or direct item access.
func (*Engine) FinalizeHome ¶
func (e *Engine) FinalizeHome(ctx context.Context, user *core.User, hc core.HubContext, section string, hubs []core.Hub) ([]core.Hub, error)
FinalizeHome arranges the complete Plex row list after all producers ran. Unknown/custom hubs with no catalogue items are deliberately preserved.
func (*Engine) FinalizeNeutralHome ¶ added in v0.3.0
func (e *Engine) FinalizeNeutralHome(ctx context.Context, user *core.User, situation core.HomeSituation, section string, candidates []core.NeutralHomeRow) ([]core.NeutralHomeRow, error)
func (*Engine) HomeCandidatesFor ¶
func (*Engine) HomeCandidatesWithPolicy ¶ added in v0.3.0
func (e *Engine) HomeCandidatesWithPolicy(ctx context.Context, u *core.User, hc core.HubContext, limit int, allow func(context.Context, *core.User, *core.Title) bool) ([]core.Hub, error)
HomeCandidatesWithPolicy applies per-request visibility before ranking.
func (*Engine) HomeHubs ¶
HomeHubs implements core.Recommender with a context derived from the clock only (device unknown).
func (*Engine) HomeHubsFor ¶
func (e *Engine) HomeHubsFor(ctx context.Context, user *core.User, hc core.HubContext, limit int) ([]core.Hub, error)
HomeHubsFor implements core.ContextualRecommender: the viewer's Home rows for this context, served from the precomputed model. The Continue Watching row is returned as a placeholder ("home.continue", no items) marking where the Plex front end should put its own.
func (*Engine) HomeLayout ¶
func (*Engine) HubItemsFor ¶ added in v0.2.56
func (e *Engine) HubItemsFor(ctx context.Context, u *core.User, hc core.HubContext, id string, limit int) (core.Hub, bool)
HubItemsFor resolves a specific hub by identifier from the viewer's warm model, the generic model, or owner collection rows when the active page no longer includes that row (e.g. a cold-start showcase row after the viewer's personal model finishes building, or a shelf dropped under a wider count).
func (*Engine) InvalidateOwnerRows ¶ added in v0.2.56
func (e *Engine) InvalidateOwnerRows()
InvalidateOwnerRows drops cached owner collection rows and assembled-page memos so collection creates, edits, deletes and rebuilds take effect on the next Home or section request.
func (*Engine) LayoutDefaults ¶
func (*Engine) MicroReason ¶
func (e *Engine) MicroReason(ctx context.Context, user *core.User, t *core.Title, now time.Time) core.MicroReason
MicroReason implements core.MicroReasoner. It only reads t.
func (*Engine) ModelReady ¶ added in v0.3.0
ModelReady reports whether a request can rank without waiting for a build. A fresh viewer model or the generic model is enough. Plex still waits in modelFor when neither exists; Jellyfin and Emby Next Up use this so a cold start serves the un-personalized order instead.
func (*Engine) NotInterestedChanged ¶ added in v0.3.0
NotInterestedChanged requests taste rebuild and cluster invalidation after a committed mutation. Request-time snapshots prevent stale cached exposure.
func (*Engine) NotePlayback ¶
NotePlayback implements core.IntentTracker: playback starting within intentConvertWindow of an open for the same title removes that open — it converted, and the play itself lands in the watch history.
func (*Engine) PostPlay ¶
func (e *Engine) PostPlay(ctx context.Context, user *core.User, justWatched core.RatingKey, hc core.HubContext, limit int) ([]core.PostPlayPick, error)
PostPlay implements core.PostPlayer.
func (*Engine) PreviewHome ¶
func (e *Engine) PreviewHome(ctx context.Context, user *core.User, hc core.HubContext, sectionArg string, limit int) (*core.HomePreview, error)
PreviewHome returns exactly what the Plex front end would serve the viewer in this context (the same assembly as HomeHubsFor and SectionHubsFor), plus the rows the front end adds itself (Continue Watching and Recently Added in sections, Recent Playlists on Home), marked source "plex". Rows hidden by a layout policy are included last with Hidden set; Plex never receives them.
section is "" or "home", "movies", "shows", or a library section id. The daypart and weekend flag are derived from hc.Now in server local time (hc.Device is kept as given).
func (*Engine) PreviewLayout ¶
func (e *Engine) PreviewLayout(ctx context.Context, user *core.User, hc core.HubContext, section string, limit int, d core.HomeLayout) (*core.HomePreview, error)
PreviewLayout assembles an unsaved document without changing persisted state, memoized production layouts, impressions, or viewer overrides.
func (*Engine) RankerInsight ¶
RankerInsight reports the learned ranker for the owner UI: when it last trained, on how much data, its held-out AUC against the hand-tuned baseline, whether it is active, and its weights by magnitude.
func (*Engine) Rebuild ¶
Rebuild builds a viewer's model now and waits for it (owner refresh, tests).
func (*Engine) RecentActivity ¶
func (e *Engine) RecentActivity(ctx context.Context, userID int64, limit int) ([]core.ViewerActivity, error)
RecentActivity lists a viewer's latest viewings, newest first.
func (*Engine) RecordDetailOpen ¶
RecordDetailOpen implements core.IntentTracker: one single-item detail open. Re-opening the same title refreshes its vote instead of stacking.
func (*Engine) RecordImpressions ¶
RecordImpressions implements core.ImpressionSink: it records what a user was shown, asynchronously. It never blocks.
func (*Engine) RecordNeutralImpression ¶ added in v0.3.0
func (e *Engine) RecordNeutralImpression(user *core.User, imp core.NeutralImpression)
RecordNeutralImpression records a validated server-issued row snapshot using the existing impression and ranker paths.
func (*Engine) Refresh ¶
Refresh rebuilds a viewer's model now (and asks the LLM for fresh themed rows when it supports invalidation).
func (*Engine) Related ¶
func (e *Engine) Related(ctx context.Context, user *core.User, key core.RatingKey, limit int) ([]core.Hub, error)
Related implements core.Recommender by combining the base recommender's detail-page rows with personalized context-aware shelves (Closer to Your Taste, Same Vibe Under 105 Minutes, and high-affinity craft/auteur matches) when the viewer's model is warm.
func (*Engine) RowInsights ¶
RowInsights reports, per row kind, how often it was shown and played over the last `days` days (row-days shown; plays attributed).
func (*Engine) ScoreTitles ¶
func (e *Engine) ScoreTitles(ctx context.Context, user *core.User, titles []core.Title) (map[core.RatingKey]float64, error)
ScoreTitles implements core.PersonalScorer: taste fit blended with quality, roughly -0.7..1 (higher = better fit for this viewer).
func (*Engine) SectionCandidatesFor ¶
func (*Engine) SectionCandidatesWithPolicy ¶ added in v0.3.0
func (e *Engine) SectionCandidatesWithPolicy(ctx context.Context, u *core.User, section core.MediaType, hc core.HubContext, limit int, allow func(context.Context, *core.User, *core.Title) bool) ([]core.Hub, error)
SectionCandidatesWithPolicy applies viewer visibility before section ranking.
func (*Engine) SectionHubs ¶
func (e *Engine) SectionHubs(ctx context.Context, user *core.User, t core.MediaType, limit int) ([]core.Hub, error)
SectionHubs implements core.Recommender.
func (*Engine) SectionHubsFor ¶
func (e *Engine) SectionHubsFor(ctx context.Context, user *core.User, t core.MediaType, hc core.HubContext, limit int) ([]core.Hub, error)
SectionHubsFor implements core.ContextualRecommender for a library section's rows (below the front end's Continue Watching and Recently Added).
func (*Engine) SemanticRowKind ¶ added in v0.3.0
SemanticRowKind returns the registered producer kind for a ranked row. The fallback matches the existing impression classification for external rows.
func (*Engine) SetBus ¶
func (e *Engine) SetBus(bus clusterbus.Bus)
SetBus connects the cluster bus (nil disconnects). A standalone server never calls it.
func (*Engine) SetExtraHomeRows ¶
func (e *Engine) SetExtraHomeRows(fn func(context.Context, *core.User, core.HubContext, string, int) []core.Hub)
SetExtraHomeRows supplies the additional production rows for owner previews. Wire once during startup, before serving requests.
func (*Engine) SetHomeLayout ¶
func (e *Engine) SetHomeLayout(ctx context.Context, d core.HomeLayout) (core.HomeLayout, error)
func (*Engine) SetTasteOverrides ¶
func (e *Engine) SetTasteOverrides(ctx context.Context, user *core.User, os []core.TasteOverride) (*core.TasteProfile, error)
SetTasteOverrides replaces the owner's corrections for a viewer and rebuilds their model so Plex reflects them on the next Home load.
func (*Engine) Taste ¶
Taste returns the viewer's taste profile (from the cached model when there is one).
func (*Engine) TrainRanker ¶
TrainRanker labels closed serves, retrains the learned ranker on the serve log now and installs the result (active only when it passed the gate). It returns the new insight.
func (*Engine) ViewerStats ¶
func (e *Engine) ViewerStats(ctx context.Context, userIDs []int64) (map[int64]core.ViewerStats, error)
ViewerStats summarises each viewer's last 30 days.
type Evidence ¶
type Evidence struct {
Key core.RatingKey
Title core.Title
Weight float64 // signed, decayed, clamped to ±2
Raw float64 // before decay
At time.Time
Kind string
Completions int // finished viewings (movies) or finished episodes (shows)
Rewatched bool
Rating float64
Progress float64
IgnoredDays int
}
Evidence is what one title says about a viewer's taste. Episodes fold onto their show.
type FavoriteSignal ¶ added in v0.3.0
Options build an Engine. DB and Catalog are required; every other dependency is optional and its rows simply drop out when absent. FavoriteSignal is a viewer-owned title or person and its last favorite time.
type NotInterestedSignal ¶ added in v0.3.0
NotInterestedSignal is explicit viewer feedback for one title.
type Options ¶
type Options struct {
// Favorites is optional; nil or empty leaves existing ranking unchanged.
Favorites func(context.Context, *core.User) ([]FavoriteSignal, error)
// NotInterested supplies explicit recommendation exclusions for this viewer.
NotInterested func(context.Context, *core.User) ([]NotInterestedSignal, error)
// NotInterestedSnapshot returns signals and their atomically-read shared revision.
NotInterestedSnapshot func(context.Context, *core.User) ([]NotInterestedSignal, uint64, error)
FilterAvailable func(context.Context, []core.Title) []core.Title
// Owner previews use the same effective viewer and title policy as Plex.
UserPolicy func(*core.User) *core.User
Allowed func(*core.User, *core.Title) bool
DB *sql.DB
Catalog core.Catalog
State core.State
// Events delivers watch-state changes (internal/state.State).
Events recommend.EventSource
// Users lists viewers for background precompute (core.Auth).
Users UserSource
// Collections supplies owner-designed collection rows.
Collections core.CollectionRows
LLM recommend.LLMSource
Trakt recommend.TraktSource
Playlists core.Playlists
// Base answers detail-page Related rows (internal/recommend).
Base core.Recommender
// Dependencies constructs consumers of this engine's scorer before any
// background work starts. This resolves the scorer/row-source construction
// cycle without publishing a partially wired engine through setters.
Dependencies func(core.PersonalScorer) (core.Recommender, core.CollectionRows, error)
// ExtraHomeRows supplies optional integration rows to owner previews.
ExtraHomeRows func(context.Context, *core.User, core.HubContext, string, int) []core.Hub
// Sections maps library section ids to their type, for per-user
// allowed sections (default 1 = movies, 2 = shows).
Sections map[int]core.MediaType
// ServerName is the friendly name shown in "Popular on <name>".
ServerName func() string
// ImageURL builds browser-loadable artwork URLs for the owner UI
// (default /ether/api/image?path=…&w=…).
ImageURL func(path string, width int) string
Logger *slog.Logger
Now func() time.Time
Location *time.Location // server local time (default time.Local)
// Busy reports whether anyone is streaming right now (nil = never).
// Background work that can wait, like retraining the ranker, waits.
Busy func() bool
// IntentEnabled reports whether live session intent (#32) is on
// (nil = on); consulted per signal so owner edits apply live.
IntentEnabled func() bool
// ClockFormat reports the micro-reason finish-time style (#31):
// "12h", "24h", a locale like "en-GB", or "" for 12h.
ClockFormat func() string
ModelTTL time.Duration // rebuild an active viewer's model after this (default 3h)
RefreshEvery time.Duration // background scheduler period (default 15m; <0 disables)
RebuildDebounce time.Duration // after a watch-state change (default 20s)
Retention time.Duration // item impressions (default 60 days)
Workers int // concurrent model builds (default 2)
ColdWait time.Duration // how long a request may wait for the first-ever model (default 1.5s)
OwnerRowsWait time.Duration // how long a request may wait for owner rows it has never fetched (default 1.5s)
SourceTimeout time.Duration // Trakt and owner collection calls (default 16s)
LLMTimeout time.Duration // LLM themed rows during a build (default 60s)
BuildTimeout time.Duration // one model build (default 3m)
MaxHomeRows int // default 20 across the complete composed page
MaxSectionRows int // default 16
// MinRowItems is the fewest titles an engine row may show (default 12;
// negative disables the floor). Continue Watching, Up Next and owner
// collections are exempt; Top 10 and smaller requested pages use their cap.
MinRowItems int
// Exploration scales Thompson sampling (0 = pure exploitation, 1 =
// full sampling; default 0.5; negative = 0).
Exploration float64
HalfLife time.Duration // taste decay half-life (default 120 days)
ImpressionBuffer int // queued impressions before dropping (default 4096)
FlushEvery time.Duration // impression write interval (default 2s)
Ranker RankerOptions // learned ranker (ranker.go)
// OnBuilt runs after a viewer model is stored. It must return quickly
// and must not build another model.
OnBuilt func(userID int64)
}
type RankerOptions ¶
type RankerOptions struct {
// LogRows × LogItems is the part of a served page logged (default 8×12).
LogRows, LogItems int
// MaxServesPerDay caps logged pages per viewer per day (default 48).
MaxServesPerDay int
// MaxItems caps logged items overall (default 1,000,000).
MaxItems int
// Retention of the serve log (default 60 days).
Retention time.Duration
// TrainEvery is the retraining period (default 6h). Negative disables
// the background loop; it is also off when Options.RefreshEvery is
// negative, unless set explicitly.
TrainEvery time.Duration
// RetrainLabels retrains early once this many new labels arrived
// (default 1000).
RetrainLabels int
// TrainHour is the local hour training prefers (default 4, the quiet
// small hours; negative = any hour). A model older than two days
// trains at the next quiet moment regardless. Training never starts
// while Options.Busy reports viewers streaming.
TrainHour int
// MinExamples / MinPositives gate activation (default 2000 / 30).
MinExamples, MinPositives int
// MinLift is the held-out AUC margin over the baseline required to
// activate (default 0.02).
MinLift float64
// MaxBlend bounds the model's share of an item score (default 0.35).
MaxBlend float64
// Disabled turns logging, training and inference off.
Disabled bool
}
RankerOptions tune the learned ranker. Zero values take the defaults.
type Taste ¶
type Taste struct {
UserID int64
BuiltAt time.Time
Confidence float64 // 0..1
Genre map[string]float64 // -1..1
Keyword map[string]float64
Person map[int64]float64
People map[int64]personInfo
Decade map[int]float64
Language map[string]float64
Franchise map[int64]float64
Studio map[string]float64
// Evidence is the per-title evidence the model was built from, by
// descending |weight|.
Evidence []Evidence
Habits core.TasteHabits
// DaypartGenre is a learned genre lift per daypart (-0.5..0.5): what
// this viewer watches more of in the morning than at night.
DaypartGenre map[string]map[string]float64
// ContextGenre and DeviceGenre disentangle sub-personas across
// (weekday|weekend × daypart) and device class (TV vs mobile).
ContextGenre map[string]map[string]float64
DeviceGenre map[string]map[string]float64
// watches per "weekday|weekend/daypart" bucket.
MovieShare map[string]float64
Overrides []core.TasteOverride
// contains filtered or unexported fields
}
Taste is one viewer's taste model: signed affinities for genres, keywords, people, decades, languages and franchises, learned from their watch history, plays, ratings and impressions, with owner overrides on top. It is deterministic for a given input and clock, and explains itself (Fit returns a reason; Profile lists the weights).
func (*Taste) ContextGenreLift ¶ added in v0.2.7
func (ts *Taste) ContextGenreLift(hc core.HubContext, gs []string) float64
ContextGenreLift returns the viewer's contextual persona lift (-0.5..0.5) for a title's normalized genres in hc, blending the specific (weekend × daypart) mode, daypart lift, and device mode.
func (*Taste) Fit ¶
Fit scores how well a title matches the taste, roughly -1..1, and explains the strongest positive reason.
fit = 0.40·genre + 0.15·keywords + 0.25·people + 0.08·decade
+ language (±0.07, see languageFit) + 0.05·franchise
A genre weighted -0.6 or below (usually an owner override) vetoes the title: its fit is capped at 0 and pushed down by 0.4×that weight.
type TrendSource ¶
type TrendSource interface {
PublicList(ctx context.Context, list string, t core.MediaType, limit int) ([]core.Title, error)
}
TrendSource is optionally implemented by the Trakt dependency (internal/trakt.Client): Trakt's public, crowd-wide lists. It returns (nil, nil) when Trakt is not configured.
type UserSource ¶
UserSource lists the server's users (core.Auth satisfies it).
type ValidationError ¶
type ValidationError struct {
Problems []Problem
}
ValidationError lists problems with an owner-supplied document (Home layout, taste overrides), addressed by dotted field path.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
func (*ValidationError) InvalidFields ¶
func (e *ValidationError) InvalidFields() [][2]string
InvalidFields returns (field, message) pairs; the admin API maps them to its validation error shape.
Source Files
¶
- assemble.go
- bandit.go
- build.go
- buzz.go
- cached_titles.go
- candidates.go
- capability_assertions.go
- cluster.go
- crowd.go
- discovery.go
- discovery_collections.go
- engine.go
- generators.go
- habits.go
- homelayout.go
- homelead.go
- hooks.go
- impressions.go
- kinds.go
- layout.go
- lenses.go
- lenses_tv.go
- microreason.go
- neutral_impression.go
- not_interested.go
- owner.go
- plays.go
- postplay.go
- presentation.go
- quality.go
- ranker.go
- ranker_train.go
- row_behavior.go
- servelog.go
- sessionintent.go
- store.go
- surfaces.go
- taste.go
- toppicks.go
- tuning.go
- viewer_lifecycle.go