{
  "license": "CC BY 4.0",
  "licenseUrl": "https://creativecommons.org/licenses/by/4.0/",
  "recipe": {
    "slug": "active-user-stickiness",
    "title": "Calculate DAU, WAU, and Product Stickiness",
    "testedWith": "Apache DataFusion 45.2.0",
    "lastReviewed": "2026-07-27"
  },
  "contract": {
    "tableName": "product_events",
    "schema": [
      {
        "name": "timestamp_utc",
        "type": "Timestamp",
        "description": "Activity time."
      },
      {
        "name": "user_id",
        "type": "Utf8",
        "description": "Stable actor identifier."
      },
      {
        "name": "event_name",
        "type": "Utf8",
        "description": "Qualifying product action."
      }
    ]
  },
  "query": "WITH daily_users AS (\n  SELECT DISTINCT\n    date_trunc('day', timestamp_utc) AS activity_day,\n    user_id\n  FROM product_events\n  WHERE timestamp_utc >= now() - INTERVAL '35 days'\n    AND event_name IN ('feature_used', 'dashboard_viewed', 'query_run')\n),\ndays AS (\n  SELECT DISTINCT activity_day\n  FROM daily_users\n),\nactivity AS (\n  SELECT\n    days.activity_day,\n    COUNT(DISTINCT CASE\n      WHEN daily_users.activity_day = days.activity_day\n      THEN daily_users.user_id\n    END) AS dau,\n    COUNT(DISTINCT daily_users.user_id) AS wau\n  FROM days\n  JOIN daily_users\n    ON daily_users.activity_day BETWEEN\n      days.activity_day - INTERVAL '6 days' AND days.activity_day\n  GROUP BY days.activity_day\n)\nSELECT\n  activity_day,\n  dau,\n  wau,\n  100.0 * dau / NULLIF(wau, 0) AS stickiness_pct\nFROM activity\nORDER BY activity_day;",
  "fixtureKind": "schema-example",
  "inputRows": [
    {
      "timestamp_utc": "2026-07-28T10:00:00Z",
      "user_id": "user_1",
      "event_name": "workflow_completed"
    },
    {
      "timestamp_utc": "2026-07-28T11:00:00Z",
      "user_id": "user_2",
      "event_name": "workflow_completed"
    },
    {
      "timestamp_utc": "2026-07-28T12:00:00Z",
      "user_id": "user_3",
      "event_name": "workflow_completed"
    }
  ],
  "illustrativeInputRows": [
    {
      "timestamp_utc": "2026-07-28T10:00:00Z",
      "user_id": "user_1",
      "event_name": "workflow_completed"
    },
    {
      "timestamp_utc": "2026-07-28T11:00:00Z",
      "user_id": "user_2",
      "event_name": "workflow_completed"
    },
    {
      "timestamp_utc": "2026-07-28T12:00:00Z",
      "user_id": "user_3",
      "event_name": "workflow_completed"
    }
  ],
  "expectedOutput": {
    "columns": [
      "activity_day",
      "dau",
      "wau",
      "stickiness_pct"
    ],
    "rows": [
      {
        "activity_day": "2026-07-25",
        "dau": 812,
        "wau": 5120,
        "stickiness_pct": 15.86
      },
      {
        "activity_day": "2026-07-26",
        "dau": 846,
        "wau": 5244,
        "stickiness_pct": 16.13
      },
      {
        "activity_day": "2026-07-27",
        "dau": 910,
        "wau": 5408,
        "stickiness_pct": 16.83
      }
    ]
  },
  "notes": [
    "The compact input rows demonstrate field names and JSON types; they are not claimed to generate the displayed aggregate by themselves.",
    "The expected output is deterministic synthetic review data for the documented output shape.",
    "Adapt time windows, thresholds, identities, and business definitions before operational use."
  ]
}