| [89156c1] | 1 | SET search_path TO trekr;
|
|---|
| 2 |
|
|---|
| 3 | -- P6 Q1 (Annual): Finance budget resilience and income stability leaderboard.
|
|---|
| 4 | -- Change report_year in params CTE when needed.
|
|---|
| 5 | WITH params AS (
|
|---|
| 6 | SELECT 2026::int AS report_year
|
|---|
| 7 | ),
|
|---|
| 8 | months AS (
|
|---|
| 9 | SELECT generate_series(1, 12) AS month_no
|
|---|
| 10 | ),
|
|---|
| 11 | finance_base AS (
|
|---|
| 12 | SELECT
|
|---|
| 13 | fu.user_id,
|
|---|
| 14 | u.username,
|
|---|
| 15 | u.email,
|
|---|
| 16 | COALESCE(fu.spending_budget, 0) AS spending_budget,
|
|---|
| 17 | COALESCE(fu.saving_budget, 0) AS saving_budget,
|
|---|
| 18 | COALESCE(fu.investing_budget, 0) AS investing_budget,
|
|---|
| 19 | COALESCE(fu.donation_budget, 0) AS donation_budget,
|
|---|
| 20 | COALESCE(fu.credit, 0) AS credit
|
|---|
| 21 | FROM finance_users fu
|
|---|
| 22 | JOIN users u ON u.user_id = fu.user_id
|
|---|
| 23 | ),
|
|---|
| 24 | monthly_income AS (
|
|---|
| 25 | SELECT
|
|---|
| 26 | fb.user_id,
|
|---|
| 27 | m.month_no,
|
|---|
| 28 | COALESCE(SUM(i.amount), 0) AS month_income
|
|---|
| 29 | FROM finance_base fb
|
|---|
| 30 | CROSS JOIN months m
|
|---|
| 31 | LEFT JOIN incomes i
|
|---|
| 32 | ON i.user_id = fb.user_id
|
|---|
| 33 | AND EXTRACT(YEAR FROM i.date)::int = (SELECT report_year FROM params)
|
|---|
| 34 | AND EXTRACT(MONTH FROM i.date)::int = m.month_no
|
|---|
| 35 | GROUP BY fb.user_id, m.month_no
|
|---|
| 36 | ),
|
|---|
| 37 | monthly_income_ranked AS (
|
|---|
| 38 | SELECT
|
|---|
| 39 | mi.*,
|
|---|
| 40 | DENSE_RANK() OVER (PARTITION BY mi.user_id ORDER BY mi.month_income DESC, mi.month_no ASC) AS best_month_rank,
|
|---|
| 41 | DENSE_RANK() OVER (PARTITION BY mi.user_id ORDER BY mi.month_income ASC, mi.month_no ASC) AS worst_month_rank
|
|---|
| 42 | FROM monthly_income mi
|
|---|
| 43 | ),
|
|---|
| 44 | annual_income AS (
|
|---|
| 45 | SELECT
|
|---|
| 46 | user_id,
|
|---|
| 47 | SUM(month_income) AS total_income,
|
|---|
| 48 | AVG(month_income) AS avg_monthly_income,
|
|---|
| 49 | STDDEV_SAMP(month_income) AS income_stddev,
|
|---|
| 50 | MAX(month_income) AS best_month_income,
|
|---|
| 51 | MIN(month_income) AS worst_month_income,
|
|---|
| 52 | COUNT(*) FILTER (WHERE month_income > 0) AS active_income_months
|
|---|
| 53 | FROM monthly_income
|
|---|
| 54 | GROUP BY user_id
|
|---|
| 55 | ),
|
|---|
| 56 | best_worst_months AS (
|
|---|
| 57 | SELECT
|
|---|
| 58 | user_id,
|
|---|
| 59 | MAX(month_no) FILTER (WHERE best_month_rank = 1) AS best_month_no,
|
|---|
| 60 | MAX(month_no) FILTER (WHERE worst_month_rank = 1) AS worst_month_no
|
|---|
| 61 | FROM monthly_income_ranked
|
|---|
| 62 | GROUP BY user_id
|
|---|
| 63 | )
|
|---|
| 64 | SELECT
|
|---|
| 65 | fb.user_id,
|
|---|
| 66 | fb.username,
|
|---|
| 67 | fb.email,
|
|---|
| 68 | (fb.spending_budget + fb.saving_budget + fb.investing_budget + fb.donation_budget) * 12 AS planned_annual_budget,
|
|---|
| 69 | ai.total_income AS actual_annual_income,
|
|---|
| 70 | ai.avg_monthly_income,
|
|---|
| 71 | ai.active_income_months,
|
|---|
| 72 | ai.best_month_income,
|
|---|
| 73 | ai.worst_month_income,
|
|---|
| 74 | bwm.best_month_no,
|
|---|
| 75 | bwm.worst_month_no,
|
|---|
| 76 | ROUND(
|
|---|
| 77 | (ai.income_stddev / NULLIF(ai.avg_monthly_income, 0))::numeric,
|
|---|
| 78 | 4
|
|---|
| 79 | ) AS income_volatility_cv,
|
|---|
| 80 | ROUND(
|
|---|
| 81 | (ai.total_income - (fb.spending_budget * 12))::numeric,
|
|---|
| 82 | 2
|
|---|
| 83 | ) AS annual_free_cash_after_spending,
|
|---|
| 84 | ROUND(
|
|---|
| 85 | ((fb.spending_budget * 12) / NULLIF(ai.total_income, 0))::numeric,
|
|---|
| 86 | 4
|
|---|
| 87 | ) AS spending_pressure_ratio,
|
|---|
| 88 | ROUND(
|
|---|
| 89 | (fb.credit / NULLIF(ai.total_income, 0))::numeric,
|
|---|
| 90 | 4
|
|---|
| 91 | ) AS leverage_ratio,
|
|---|
| 92 | DENSE_RANK() OVER (
|
|---|
| 93 | ORDER BY
|
|---|
| 94 | (ai.total_income - (fb.spending_budget * 12)) DESC,
|
|---|
| 95 | ((fb.spending_budget * 12) / NULLIF(ai.total_income, 0)) ASC,
|
|---|
| 96 | fb.user_id ASC
|
|---|
| 97 | ) AS finance_resilience_rank
|
|---|
| 98 | FROM finance_base fb
|
|---|
| 99 | JOIN annual_income ai ON ai.user_id = fb.user_id
|
|---|
| 100 | JOIN best_worst_months bwm ON bwm.user_id = fb.user_id
|
|---|
| 101 | ORDER BY finance_resilience_rank, fb.user_id;
|
|---|
| 102 |
|
|---|