{
  "license": "CC BY 4.0",
  "licenseUrl": "https://creativecommons.org/licenses/by/4.0/",
  "recipe": {
    "slug": "monthly-recurring-revenue-movement",
    "title": "Calculate Monthly Recurring Revenue Movement",
    "testedWith": "Apache DataFusion 45.2.0",
    "lastReviewed": "2026-07-27"
  },
  "contract": {
    "tableName": "billing_events",
    "schema": [
      {
        "name": "timestamp_utc",
        "type": "Timestamp",
        "description": "Billing movement time."
      },
      {
        "name": "account_id",
        "type": "Utf8",
        "description": "Stable billing account identifier."
      },
      {
        "name": "movement_type",
        "type": "Utf8",
        "description": "new, expansion, contraction, churn, or reactivation."
      },
      {
        "name": "mrr_change_usd",
        "type": "Float64",
        "description": "Signed normalized recurring-revenue change."
      }
    ]
  },
  "query": "SELECT\n  date_trunc('month', timestamp_utc) AS month,\n  SUM(CASE\n    WHEN movement_type IN ('new', 'expansion', 'reactivation')\n    THEN mrr_change_usd ELSE 0.0\n  END) AS growth_mrr_usd,\n  -SUM(CASE\n    WHEN movement_type IN ('contraction', 'churn')\n    THEN mrr_change_usd ELSE 0.0\n  END) AS lost_mrr_usd,\n  SUM(mrr_change_usd) AS net_mrr_change_usd,\n  COUNT(DISTINCT account_id) AS accounts_changed\nFROM billing_events\nWHERE timestamp_utc >= now() - INTERVAL '12 months'\nGROUP BY date_trunc('month', timestamp_utc)\nORDER BY month;",
  "fixtureKind": "schema-example",
  "inputRows": [
    {
      "timestamp_utc": "2026-07-28T10:00:00Z",
      "account_id": "account_1",
      "movement_type": "movement_type_example_1",
      "mrr_change_usd": 12
    },
    {
      "timestamp_utc": "2026-07-28T11:00:00Z",
      "account_id": "account_2",
      "movement_type": "movement_type_example_2",
      "mrr_change_usd": 37
    },
    {
      "timestamp_utc": "2026-07-28T12:00:00Z",
      "account_id": "account_3",
      "movement_type": "movement_type_example_3",
      "mrr_change_usd": 24
    }
  ],
  "illustrativeInputRows": [
    {
      "timestamp_utc": "2026-07-28T10:00:00Z",
      "account_id": "account_1",
      "movement_type": "movement_type_example_1",
      "mrr_change_usd": 12
    },
    {
      "timestamp_utc": "2026-07-28T11:00:00Z",
      "account_id": "account_2",
      "movement_type": "movement_type_example_2",
      "mrr_change_usd": 37
    },
    {
      "timestamp_utc": "2026-07-28T12:00:00Z",
      "account_id": "account_3",
      "movement_type": "movement_type_example_3",
      "mrr_change_usd": 24
    }
  ],
  "expectedOutput": {
    "columns": [
      "month",
      "growth_mrr_usd",
      "lost_mrr_usd",
      "net_mrr_change_usd",
      "accounts_changed"
    ],
    "rows": [
      {
        "month": "2026-05",
        "growth_mrr_usd": 18400,
        "lost_mrr_usd": 7200,
        "net_mrr_change_usd": 11200,
        "accounts_changed": 93
      },
      {
        "month": "2026-06",
        "growth_mrr_usd": 22100,
        "lost_mrr_usd": 9800,
        "net_mrr_change_usd": 12300,
        "accounts_changed": 117
      },
      {
        "month": "2026-07",
        "growth_mrr_usd": 20700,
        "lost_mrr_usd": 13400,
        "net_mrr_change_usd": 7300,
        "accounts_changed": 126
      }
    ]
  },
  "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."
  ]
}