The retention grid
Build the cohort-by-week retention grid from two event exports, with weeks that have not happened yet left blank rather than counted as zero.
On this page
The retention query returns one long column; the grid a tenant can read needs the ragged right edge handled, and the spreadsheet that did it last quarter counted every blank as zero.
The situation
A tenant's success manager asks for week-N retention by sign-up week for the 600 agents on the drafting assistant. The analyst's spreadsheet pivoted the retention query and reported the newest cohort at 0% for week 2, because week 2 had not happened yet and the pivot filled the cell with 0. The tenant read it as a collapse. The data is two exports: signups, one row per agent with the day they signed up, and events, one row per activity event with the day it happened; any event counts as active. The rules the grid must follow, from the cohorts lesson: a cohort is a sign-up week starting on Monday; week N for an agent is days 7N to 7N+6 after their own sign-up day, so a Friday signer's week 1 starts on the next Friday; an agent is active in a week once, however many events they logged; and a week is reported for a cohort only when it has closed for every member, measured against the latest date anywhere in the data. Not yet closed is None, never 0.
The question
Write the function so that the grid has one row per cohort, every row the same length, and a blank cell wherever the week has not happened.
def solve(signups, events):
"""Week-N retention per sign-up cohort.
signups: [(user_id, 'YYYY-MM-DD')] — one row per user, the day they signed up.
events: [(user_id, 'YYYY-MM-DD')] — one row per activity event; any event counts.
Returns [(cohort, size, [pct, ...]), ...] in cohort order: cohort is the ISO date of
the Monday of the sign-up week, size the users in it, pct[N] the percentage of the
cohort active in days 7N..7N+6 after their own sign-up day, one decimal. A week is
reported only once it has closed for every member, else None; all rows share a length.
"""
signups = [('a', '2026-08-03'), ('b', '2026-08-05'), ('c', '2026-08-07'), ('d', '2026-08-09'),
('e', '2026-08-10'), ('f', '2026-08-12'), ('g', '2026-08-19')]
events = a on 08-03, 08-11, 08-20, 09-01 · b on 08-05, 08-13 · c on 08-07 · d on 08-09, 08-24
e on 08-10, 08-18 · f on 08-12, 08-13, 08-20, 08-27 · g on 08-19, 08-30
solve(signups, events) -> [('2026-08-03', 4, [100.0, 50.0, 50.0]),
('2026-08-10', 2, [100.0, 100.0, 50.0]),
('2026-08-17', 1, [100.0, 100.0, None])]
A strong answer · Rubric · Where answers go wrong · Solution
Write your answer first, then open