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
DayN is the daily-period alias for PeriodN, kept so existing callers read unchanged.
func PeriodN ¶ added in v0.9.0
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.
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 ¶
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).