{
  "license": "CC BY 4.0",
  "licenseUrl": "https://creativecommons.org/licenses/by/4.0/",
  "recipe": {
    "slug": "weekly-cohort-retention",
    "title": "Calculate Weekly Cohort Retention",
    "testedWith": "Apache DataFusion 45.2.0",
    "lastReviewed": "2026-07-27"
  },
  "contract": {
    "tableName": "product_events",
    "schema": [
      {
        "name": "timestamp_utc",
        "type": "Timestamp",
        "description": "When the activity occurred."
      },
      {
        "name": "user_id",
        "type": "Utf8",
        "description": "Stable user identifier."
      },
      {
        "name": "event_name",
        "type": "Utf8",
        "description": "Product event name."
      }
    ]
  },
  "query": "WITH first_activity AS (\n  SELECT\n    user_id,\n    date_trunc('week', MIN(timestamp_utc)) AS cohort_week\n  FROM product_events\n  WHERE event_name = 'first_value_completed'\n  GROUP BY user_id\n),\nweekly_activity AS (\n  SELECT DISTINCT\n    user_id,\n    date_trunc('week', timestamp_utc) AS activity_week\n  FROM product_events\n  WHERE event_name = 'core_feature_used'\n),\ncohort_activity AS (\n  SELECT\n    f.cohort_week,\n    (date_part('epoch', a.activity_week)\n      - date_part('epoch', f.cohort_week)) / 604800 AS week_number,\n    COUNT(DISTINCT a.user_id) AS retained_users\n  FROM first_activity f\n  JOIN weekly_activity a ON f.user_id = a.user_id\n  WHERE a.activity_week >= f.cohort_week\n  GROUP BY\n    f.cohort_week,\n    (date_part('epoch', a.activity_week)\n      - date_part('epoch', f.cohort_week)) / 604800\n),\ncohort_sizes AS (\n  SELECT cohort_week, COUNT(*) AS cohort_size\n  FROM first_activity\n  GROUP BY cohort_week\n)\nSELECT\n  c.cohort_week,\n  c.week_number,\n  c.retained_users,\n  s.cohort_size,\n  100.0 * c.retained_users / NULLIF(s.cohort_size, 0) AS retention_pct\nFROM cohort_activity c\nJOIN cohort_sizes s ON c.cohort_week = s.cohort_week\nWHERE c.week_number BETWEEN 0 AND 12\nORDER BY c.cohort_week, c.week_number;",
  "fixtureKind": "reproducible",
  "inputRows": [
    {
      "timestamp_utc": "2026-07-20T10:00:00.000Z",
      "user_id": "older-1",
      "event_name": "first_value_completed"
    },
    {
      "timestamp_utc": "2026-07-21T10:00:00.000Z",
      "user_id": "older-1",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-27T10:00:00.000Z",
      "user_id": "older-1",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-20T11:00:00.000Z",
      "user_id": "older-2",
      "event_name": "first_value_completed"
    },
    {
      "timestamp_utc": "2026-07-21T11:00:00.000Z",
      "user_id": "older-2",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-27T11:00:00.000Z",
      "user_id": "older-2",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-20T12:00:00.000Z",
      "user_id": "older-3",
      "event_name": "first_value_completed"
    },
    {
      "timestamp_utc": "2026-07-21T12:00:00.000Z",
      "user_id": "older-3",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-20T13:00:00.000Z",
      "user_id": "older-4",
      "event_name": "first_value_completed"
    },
    {
      "timestamp_utc": "2026-07-21T13:00:00.000Z",
      "user_id": "older-4",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-27T14:00:00.000Z",
      "user_id": "newer-1",
      "event_name": "first_value_completed"
    },
    {
      "timestamp_utc": "2026-07-28T14:00:00.000Z",
      "user_id": "newer-1",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-27T15:00:00.000Z",
      "user_id": "newer-2",
      "event_name": "first_value_completed"
    },
    {
      "timestamp_utc": "2026-07-28T15:00:00.000Z",
      "user_id": "newer-2",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-27T16:00:00.000Z",
      "user_id": "newer-3",
      "event_name": "first_value_completed"
    },
    {
      "timestamp_utc": "2026-07-28T16:00:00.000Z",
      "user_id": "newer-3",
      "event_name": "core_feature_used"
    }
  ],
  "illustrativeInputRows": [
    {
      "timestamp_utc": "2026-07-20T10:00:00.000Z",
      "user_id": "older-1",
      "event_name": "first_value_completed"
    },
    {
      "timestamp_utc": "2026-07-21T10:00:00.000Z",
      "user_id": "older-1",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-27T10:00:00.000Z",
      "user_id": "older-1",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-20T11:00:00.000Z",
      "user_id": "older-2",
      "event_name": "first_value_completed"
    },
    {
      "timestamp_utc": "2026-07-21T11:00:00.000Z",
      "user_id": "older-2",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-27T11:00:00.000Z",
      "user_id": "older-2",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-20T12:00:00.000Z",
      "user_id": "older-3",
      "event_name": "first_value_completed"
    },
    {
      "timestamp_utc": "2026-07-21T12:00:00.000Z",
      "user_id": "older-3",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-20T13:00:00.000Z",
      "user_id": "older-4",
      "event_name": "first_value_completed"
    },
    {
      "timestamp_utc": "2026-07-21T13:00:00.000Z",
      "user_id": "older-4",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-27T14:00:00.000Z",
      "user_id": "newer-1",
      "event_name": "first_value_completed"
    },
    {
      "timestamp_utc": "2026-07-28T14:00:00.000Z",
      "user_id": "newer-1",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-27T15:00:00.000Z",
      "user_id": "newer-2",
      "event_name": "first_value_completed"
    },
    {
      "timestamp_utc": "2026-07-28T15:00:00.000Z",
      "user_id": "newer-2",
      "event_name": "core_feature_used"
    },
    {
      "timestamp_utc": "2026-07-27T16:00:00.000Z",
      "user_id": "newer-3",
      "event_name": "first_value_completed"
    },
    {
      "timestamp_utc": "2026-07-28T16:00:00.000Z",
      "user_id": "newer-3",
      "event_name": "core_feature_used"
    }
  ],
  "expectedOutput": {
    "columns": [
      "cohort_week",
      "week_number",
      "retained_users",
      "cohort_size",
      "retention_pct"
    ],
    "rows": [
      {
        "cohort_week": "2026-07-20",
        "week_number": 0,
        "retained_users": 4,
        "cohort_size": 4,
        "retention_pct": 100
      },
      {
        "cohort_week": "2026-07-20",
        "week_number": 1,
        "retained_users": 2,
        "cohort_size": 4,
        "retention_pct": 50
      },
      {
        "cohort_week": "2026-07-27",
        "week_number": 0,
        "retained_users": 3,
        "cohort_size": 3,
        "retention_pct": 100
      }
    ]
  },
  "notes": [
    "The included synthetic input rows generate the expected output when timestamps are shifted into the documented query window.",
    "Run this fixture in the public browser SQL playground; the selected query executes locally with DuckDB-Wasm.",
    "Adapt time windows, thresholds, identities, and business definitions before operational use."
  ]
}