source: db-scripts/p6_q1_annual_finance_budget_resilience.sql

Last change on this file was 89156c1, checked in by Andrej <asumanovski@…>, 3 months ago

Phase 6 documentation

  • Property mode set to 100644
File size: 3.3 KB
Line 
1SET 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.
5WITH params AS (
6 SELECT 2026::int AS report_year
7),
8months AS (
9 SELECT generate_series(1, 12) AS month_no
10),
11finance_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),
24monthly_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),
37monthly_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),
44annual_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),
56best_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)
64SELECT
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
98FROM finance_base fb
99JOIN annual_income ai ON ai.user_id = fb.user_id
100JOIN best_worst_months bwm ON bwm.user_id = fb.user_id
101ORDER BY finance_resilience_rank, fb.user_id;
102
Note: See TracBrowser for help on using the repository browser.