retention

package
v0.9.2 Latest Latest
Warning

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

Go to latest
Published: Jul 15, 2026 License: MIT Imports: 4 Imported by: 0

Documentation

Overview

Package retention computes cohort retention — the other core product-analytics primitive: group users by the day they first showed up, then track what % come back on day 1, 2, ... N. Deterministic and storage-agnostic, like funnel.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DayN added in v0.2.0

func DayN(r Result, n int, now time.Time) (retained, size int)

DayN is the daily-period alias for PeriodN, kept so existing callers read unchanged.

func PeriodN added in v0.9.0

func PeriodN(r Result, n int, now time.Time) (retained, size int)

PeriodN aggregates period-n retention across cohorts HONESTLY: only cohorts whose period-n has fully elapsed as of `now` enter the denominator. Users who signed up yesterday cannot have day-7 (or week-2) activity yet — counting them would systematically understate retention (the classic retention-triangle mistake), and reporting period-n at all when no cohort is old enough would be a fabricated 0%. Uses the Result's own bucket, so it is correct for daily, weekly, and monthly grids alike. Every surface that summarizes retention (verdict, MCP, ask) must use this.

func Summarize added in v0.9.0

func Summarize(r Result, now time.Time) map[string]any

Types

type Cohort

type Cohort struct {
	Date     time.Time `json:"date"`
	Size     int       `json:"size"`     // users first seen on this day
	Returned []int     `json:"returned"` // Returned[n] = users active n days after Date (Returned[0] == Size)
}

Cohort is one first-seen day and how many of its users returned on each later day.

type CohortJSON added in v0.9.1

type CohortJSON struct {
	Date     time.Time `json:"date"`
	Size     int       `json:"size"`
	Returned []*int    `json:"returned"`
}

Summarize builds the honest headline retention percentages for a grid, picking a period set + labels that match the bucket (day 1/7/30, week 1/2/4, month 1/2/3). A period no cohort is old enough to observe is OMITTED, never reported as a fabricated 0%. This is the single source both the HTTP API and the MCP tool serialize, so the two can never disagree (agreement_test enforces it). Does NOT include the raw cohorts grid — callers add that. CohortJSON is the serialization shape shared by the HTTP API and the MCP tool, so the two can never disagree (agreement_test locks them). Returned[n] is nil for any period whose window has not started relative to now — an unobservable future day must serialize as null, never 0, or it reads as "retention cratered to 0%".

func SerializeCohorts added in v0.9.1

func SerializeCohorts(r Result, now time.Time) []CohortJSON

SerializeCohorts nulls out unobservable future periods, one definition for every surface.

type Result

type Result struct {
	Cohorts []Cohort `json:"cohorts"`
	MaxDays int      `json:"max_days"`          // max periods measured (kept name for compat)
	Bucket  string   `json:"bucket,omitempty"`  // "day" (default), "week", or "month" (30-day)
	Rolling bool     `json:"rolling,omitempty"` // true = "active on OR AFTER period n" (unbounded)
}

Result is the full retention grid (one row per cohort period).

func Compute

func Compute(events []event.Event, maxDays int, retentionEvent string) Result

Compute builds daily n-day retention over maxDays — the default. A user belongs to the cohort of their first event's (UTC) day; they "return on day n" if they have any event on the day n days after their first. retentionEvent optionally filters which events count as activity (empty = any event).

func ComputeBucketed added in v0.9.0

func ComputeBucketed(events []event.Event, maxPeriods int, retentionEvent, bucket string, rolling bool) Result

ComputeBucketed generalizes Compute to week/month periods and rolling mode:

  • bucket "week"/"month" groups cohorts + return periods into 7-/30-day blocks, so a weekly product's retention isn't understated by a daily read.
  • rolling=true counts a user as retained at period n if they were active on period n OR ANY LATER period (unbounded retention), instead of exactly on period n (classic).

Jump to

Keyboard shortcuts

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