popularity

package
v0.58.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package popularity ranks host content by a named, bounded policy over the signal plane's canonical window metrics (docs/popularity-policy.md).

reach   = log10(1 + viewers)
engage  = (viewer_engagement_sum + e0·ke) / (viewers + ke)
finish  = (completers + f0·kf) / (viewers + kf)
approve = (positive + a0·ka) / (positive + negative + ka)
revisit = returning_viewers / (viewers + kr)
rank    = reach × (1 + we·engage + wf·finish + wa·approve + wr·revisit)

Every quality term lies in [0, 1], so reach (distinct subjects) dominates and the multiplier is bounded by MaxQuality. Engagement averages sessions within each viewer first; each viewer contributes at most one engagement unit and one returning-viewer observation. Priors are pseudo-observations: one vote moves approve by at most 1/(ka+1). Time only selects the window; no term depends on when an observation happened inside it. The same formula runs in ClickHouse (RankExpr, global top-N) and in Go (Score, candidate sets), and public counts are the raw metrics, never derived from a rank.

Index

Constants

View Source
const CompletionRatio = 0.9

CompletionRatio is the covered share of the selected version at which a session counts as completed.

View Source
const DefaultCacheTTL = 5 * time.Minute

DefaultCacheTTL bounds how stale a cached global ranking may be: the rollup behind it is daily, so minutes change nothing a reader would notice.

View Source
const DefaultName = "v1"

DefaultName is the policy a host gets when it configures none.

View Source
const DefaultPeriod = "30d"

DefaultPeriod is the window a listing gets when it names none.

View Source
const DefaultTaxonomyCandidateLimit = 2000

DefaultTaxonomyCandidateLimit bounds the ranked works a taxonomy listing aggregates; see docs/popularity-policy.md for what the bound means.

Variables

View Source
var Periods = []string{"7d", "30d", "90d", "365d", "all"}

Periods are the only public popularity windows: literal whole-UTC-day windows with equal weight inside, "all" unbounded.

View Source
var PolicyV1 = Policy{
	Name:    "v1",
	Weights: Weights{Engagement: 0.35, Completion: 0.25, Approval: 0.40, Revisit: 0.10},
	Priors: Priors{
		Engagement: 0.40, EngagementWeight: 10,
		Completion: 0.30, CompletionWeight: 10,
		Approval: 0.75, ApprovalWeight: 5,
		RevisitWeight: 10,
	},
}

PolicyV1 is the qualified policy: weights and priors selected by the judged fixture recorded in docs/popularity-policy.md.

Functions

func Names

func Names() []string

Names lists the registered policies.

func SessionScore

func SessionScore(covered, total, activeS uint32, secondsPerUnit float64) (score int16, completed bool)

SessionScore is the pure form of SessionScorer for hosts that score outside a registered Scorer.

func WindowForPeriod

func WindowForPeriod(period string, now time.Time) (signal.Window, error)

WindowForPeriod maps a public period onto the window ending on now's UTC day. Anything else is an error, never a silent default.

Types

type Cache

type Cache interface {
	Get(ctx context.Context, key string) ([]byte, bool)
	Set(ctx context.Context, key string, value []byte, ttl time.Duration)
}

Cache memoizes global rankings. Optional: without one every read hits the signal plane. Keys carry the tenant, policy name, content kind, window and bounds, so two policies sharing one cache never read each other's entries.

type Catalog

type Catalog interface {
	Assignments(ctx context.Context, tenant, contentKind, taxonomyKind string, contentIDs []string) (map[string][]contentref.TaxonomyID, error)
}

Catalog is the host join port taxonomy popularity derives from: which taxonomy records (artist, series, tag, creator, character, season, ...) a work is assigned to. ContentKit never records a signal against a taxonomy id; a taxonomy record ranks by its member works. Assignments returns, per work id, the taxonomy ids of taxonomyKind effective on the work (work ∪ its versions); ids without assignments are absent.

type CatalogFunc

type CatalogFunc func(ctx context.Context, tenant, contentKind, taxonomyKind string, contentIDs []string) (map[string][]contentref.TaxonomyID, error)

CatalogFunc adapts a function to Catalog.

func (CatalogFunc) Assignments

func (f CatalogFunc) Assignments(ctx context.Context, tenant, contentKind, taxonomyKind string, contentIDs []string) (map[string][]contentref.TaxonomyID, error)

type Config

type Config struct {
	Source Source
	Policy Policy
	// Catalog enables Taxonomy. Optional.
	Catalog Catalog
	// Cache memoizes Popular and Taxonomy under policy-named keys. Optional.
	Cache    Cache
	CacheTTL time.Duration // default DefaultCacheTTL
	// TaxonomyCandidateLimit is the ranked-work bound of Taxonomy (default
	// DefaultTaxonomyCandidateLimit). Part of the cache key.
	TaxonomyCandidateLimit int
}

Config builds a Ranker. Hosts pass a policy name resolved through ByName or a Policy of their own; either is validated.

type Hit

type Hit struct {
	ContentID string
	Score     float64
	signal.ContentMetrics
}

Hit is one ranked work: the policy score next to its raw window metrics. Public counts come from the metrics, never from the score.

func (Hit) MeanEngagement

func (h Hit) MeanEngagement() float64

MeanEngagement is the mean session score in [0, 1]: ScoreSum / (100·Views).

type MemoryCache

type MemoryCache struct {
	// contains filtered or unexported fields
}

MemoryCache is a process-local Cache with per-entry expiry.

func NewMemoryCache

func NewMemoryCache() *MemoryCache

NewMemoryCache returns an empty MemoryCache.

func (*MemoryCache) Get

func (c *MemoryCache) Get(_ context.Context, key string) ([]byte, bool)

func (*MemoryCache) Set

func (c *MemoryCache) Set(_ context.Context, key string, value []byte, ttl time.Duration)

Set stores value; ttl <= 0 never expires.

type Policy

type Policy struct {
	Name    string
	Weights Weights
	Priors  Priors
}

Policy is one named, immutable ranking. Changing a weight or prior is a new name, never an edit of an existing one: configuration and cache keys carry the name.

func ByName

func ByName(name string) (Policy, error)

ByName resolves a configured policy. Unknown names are errors so a host refuses to start rather than ranking under a silent default.

func (Policy) MaxQuality

func (p Policy) MaxQuality() float64

MaxQuality is the largest multiplier the quality terms can reach.

func (Policy) RankExpr

func (p Policy) RankExpr() string

RankExpr renders Score as a ClickHouse expression over the window metric columns for signal.PopularOptions.RankExpr. Parameters are rendered as literals; nothing user-controlled reaches it.

func (Policy) Score

func (p Policy) Score(m signal.ContentMetrics) float64

Score ranks one work's window metrics. Works without a view score 0.

func (Policy) Validate

func (p Policy) Validate() error

Validate rejects parameters outside the documented bounds.

type Priors

type Priors struct {
	Engagement       float64
	EngagementWeight float64
	Completion       float64
	CompletionWeight float64
	Approval         float64
	ApprovalWeight   float64
	RevisitWeight    float64
}

Priors smooth each term with pseudo-observations: a prior mean in [0, 1] and its weight (>= 1) in pseudo-viewers or pseudo-votes.

type Ranker

type Ranker struct {
	// contains filtered or unexported fields
}

Ranker applies one policy to one tenant's signal plane.

func New

func New(cfg Config) (*Ranker, error)

New validates the policy and returns the Ranker.

func (*Ranker) Candidates

func (r *Ranker) Candidates(ctx context.Context, contentKind string, ids []string, window signal.Window) ([]Hit, error)

Candidates scores a host-selected set of works (an artist's galleries, a search page, a tag) in Go over one Metrics read: score descending, content id ascending. Candidates without a view in the window are absent, exactly as in Popular.

func (*Ranker) Policy

func (r *Ranker) Policy() Policy

Policy returns the ranking this Ranker applies.

func (*Ranker) Popular

func (r *Ranker) Popular(ctx context.Context, contentKind string, window signal.Window, limit int) ([]Hit, error)

Popular returns the top limit works of one kind in a window, ranked by the policy inside ClickHouse (RankExpr). Only works with a view in the window rank; ties break on content id. The ranking is global, so one cache entry serves every reader; hosts page by asking for offset+limit and slicing.

func (*Ranker) Scores

func (r *Ranker) Scores(ctx context.Context, contentKind string, ids []string, window signal.Window) (map[string]float64, error)

Scores is Candidates as content id → score.

func (*Ranker) Taxonomy

func (r *Ranker) Taxonomy(ctx context.Context, contentKind, taxonomyKind string, window signal.Window) ([]TaxonomyHit, error)

Taxonomy ranks the taxonomy records of one kind by the window popularity of their member works, joined through the Catalog over the top TaxonomyCandidateLimit ranked works: score = summed member scores. The listing is approximate: a record with no member in that slice is absent and counts describe only the slice.

type SessionScorer

type SessionScorer struct {
	// SecondsPerUnit is the active time per covered unit at which a session
	// counts as consumed rather than flipped through (8 for gallery pages).
	// Zero means the units are seconds themselves: dwell is 1.
	SecondsPerUnit float64
	// CoveredKey names the Payload entry holding the covered units when the
	// host keeps Progress as its resume anchor; empty reads Progress.
	CoveredKey string
}

SessionScorer is the v1 session engagement score, a signal.Scorer for one content kind: coverage of the selected version times dwell.

coverage = min(1, covered / total)
dwell    = min(1, active_s / (covered × SecondsPerUnit))
score    = round(100 × coverage × (0.6 + 0.4 × dwell))      ∈ [0, 100]

covered is the distinct units exposed (pages, seconds) and total the selected version's units (ProgressMax), so size never enters: a 6-page gallery read fully scores the same as a 200-page one read fully. Completed at CompletionRatio of the units. Non-view signals pass through unchanged.

func (SessionScorer) Score

Score implements signal.Scorer.

type Source

type Source interface {
	Tenant() string
	Popular(ctx context.Context, contentKind string, opts signal.PopularOptions) ([]signal.PopularHit, error)
	Metrics(ctx context.Context, refs []signal.ContentRef, window signal.Window) (map[signal.ContentKey]signal.ContentMetrics, error)
}

Source reads one tenant's canonical window projections; contentkit.Hub satisfies it. Every reference the Ranker builds carries Source.Tenant().

type TaxonomyHit

type TaxonomyHit struct {
	TaxonomyID   contentref.TaxonomyID
	TaxonomyKind string
	Score        float64 // summed member scores
	ContentCount uint64  // ranked members inside the candidate bound
	Viewers      uint64  // summed member viewers (a subject viewing two members counts twice)
	MeanScore    float64 // Score / ContentCount
}

TaxonomyHit is one ranked taxonomy record, derived from its member works.

type Weights

type Weights struct {
	Engagement float64
	Completion float64
	Approval   float64
	Revisit    float64
}

Weights are the quality term weights, each in [0, 1].

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL