{
  "license": "CC BY 4.0",
  "licenseUrl": "https://creativecommons.org/licenses/by/4.0/",
  "recipe": {
    "slug": "payment-failure-recovery",
    "title": "Measure Payment-Failure Recovery",
    "testedWith": "Apache DataFusion 45.2.0",
    "lastReviewed": "2026-07-27"
  },
  "contract": {
    "tableName": "invoice_events",
    "schema": [
      {
        "name": "timestamp_utc",
        "type": "Timestamp",
        "description": "Invoice event time."
      },
      {
        "name": "invoice_id",
        "type": "Utf8",
        "description": "Stable provider invoice identifier."
      },
      {
        "name": "event_name",
        "type": "Utf8",
        "description": "invoice_payment_failed or invoice_paid."
      },
      {
        "name": "amount_usd",
        "type": "Float64",
        "description": "Normalized invoice amount."
      },
      {
        "name": "failure_type",
        "type": "Utf8",
        "description": "Safe categorized failure reason."
      }
    ]
  },
  "query": "WITH failures AS (\n  SELECT\n    invoice_id,\n    MIN(timestamp_utc) AS first_failed_at,\n    MAX(amount_usd) AS amount_usd,\n    MIN(failure_type) AS failure_type\n  FROM invoice_events\n  WHERE event_name = 'invoice_payment_failed'\n    AND timestamp_utc >= now() - INTERVAL '60 days'\n  GROUP BY invoice_id\n),\nrecoveries AS (\n  SELECT\n    invoice_id,\n    MIN(timestamp_utc) AS recovered_at\n  FROM invoice_events\n  WHERE event_name = 'invoice_paid'\n  GROUP BY invoice_id\n)\nSELECT\n  failures.failure_type,\n  COUNT(*) AS failed_invoices,\n  SUM(CASE\n    WHEN recoveries.recovered_at > failures.first_failed_at THEN 1\n    ELSE 0\n  END) AS recovered_invoices,\n  100.0 * SUM(CASE\n    WHEN recoveries.recovered_at > failures.first_failed_at THEN 1\n    ELSE 0\n  END) / NULLIF(COUNT(*), 0) AS recovery_rate_pct,\n  SUM(CASE\n    WHEN recoveries.recovered_at IS NULL THEN failures.amount_usd\n    ELSE 0.0\n  END) AS unrecovered_amount_usd\nFROM failures\nLEFT JOIN recoveries\n  ON recoveries.invoice_id = failures.invoice_id\nGROUP BY failures.failure_type\nORDER BY unrecovered_amount_usd DESC;",
  "fixtureKind": "schema-example",
  "inputRows": [
    {
      "timestamp_utc": "2026-07-28T10:00:00Z",
      "invoice_id": "invoice_id_example_1",
      "event_name": "workflow_completed",
      "amount_usd": 12,
      "failure_type": "failure_type_example_1"
    },
    {
      "timestamp_utc": "2026-07-28T11:00:00Z",
      "invoice_id": "invoice_id_example_2",
      "event_name": "workflow_completed",
      "amount_usd": 37,
      "failure_type": "failure_type_example_2"
    },
    {
      "timestamp_utc": "2026-07-28T12:00:00Z",
      "invoice_id": "invoice_id_example_3",
      "event_name": "workflow_completed",
      "amount_usd": 24,
      "failure_type": "failure_type_example_3"
    }
  ],
  "illustrativeInputRows": [
    {
      "timestamp_utc": "2026-07-28T10:00:00Z",
      "invoice_id": "invoice_id_example_1",
      "event_name": "workflow_completed",
      "amount_usd": 12,
      "failure_type": "failure_type_example_1"
    },
    {
      "timestamp_utc": "2026-07-28T11:00:00Z",
      "invoice_id": "invoice_id_example_2",
      "event_name": "workflow_completed",
      "amount_usd": 37,
      "failure_type": "failure_type_example_2"
    },
    {
      "timestamp_utc": "2026-07-28T12:00:00Z",
      "invoice_id": "invoice_id_example_3",
      "event_name": "workflow_completed",
      "amount_usd": 24,
      "failure_type": "failure_type_example_3"
    }
  ],
  "expectedOutput": {
    "columns": [
      "failure_type",
      "failed_invoices",
      "recovered_invoices",
      "recovery_rate_pct",
      "unrecovered_amount_usd"
    ],
    "rows": [
      {
        "failure_type": "insufficient_funds",
        "failed_invoices": 142,
        "recovered_invoices": 89,
        "recovery_rate_pct": 62.68,
        "unrecovered_amount_usd": 6840
      },
      {
        "failure_type": "expired_card",
        "failed_invoices": 58,
        "recovered_invoices": 47,
        "recovery_rate_pct": 81.03,
        "unrecovered_amount_usd": 1320
      },
      {
        "failure_type": "authentication_required",
        "failed_invoices": 36,
        "recovered_invoices": 18,
        "recovery_rate_pct": 50,
        "unrecovered_amount_usd": 2940
      }
    ]
  },
  "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."
  ]
}