From 6c30617afb8f55f0ae672a24e95b16e30a3aeb3f Mon Sep 17 00:00:00 2001 From: Priyanka Punukollu Date: Thu, 26 Feb 2026 16:43:39 -0600 Subject: [PATCH] feat: add wealth gap visualizer with Fed Reserve benchmarks Made-with: Cursor --- agent/evals/test_wealth_visualizer.py | 66 ++++++++++ agent/tools/wealth_visualizer.py | 174 ++++++++++++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 agent/evals/test_wealth_visualizer.py create mode 100644 agent/tools/wealth_visualizer.py diff --git a/agent/evals/test_wealth_visualizer.py b/agent/evals/test_wealth_visualizer.py new file mode 100644 index 000000000..a593d7046 --- /dev/null +++ b/agent/evals/test_wealth_visualizer.py @@ -0,0 +1,66 @@ +import sys +import os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'tools')) +from wealth_visualizer import analyze_wealth_position + + +def test_wealth_above_median(): + result = analyze_wealth_position( + portfolio_value=94000, age=34, annual_income=120000 + ) + assert result["current_position"]["total_net_worth"] == 94000 + assert "above median" in result["current_position"]["you_vs_median"] + assert result["retirement_projection"]["monthly_income_at_retirement"] > 0 + assert "honest_assessment" in result + + +def test_wealth_below_median(): + result = analyze_wealth_position( + portfolio_value=15000, age=45, annual_income=80000 + ) + # 45-54 median is $247k, $15k is well below + assert result["current_position"]["total_net_worth"] == 15000 + assert result["current_position"]["total_net_worth"] < 247000 + assert "honest_assessment" in result + + +def test_wealth_includes_real_estate(): + result = analyze_wealth_position( + portfolio_value=94000, age=40, + annual_income=150000, real_estate_equity=140000 + ) + assert result["current_position"]["total_net_worth"] == 234000 + assert result["current_position"]["real_estate_equity"] == 140000 + + +def test_early_retirement_scenario(): + result = analyze_wealth_position( + portfolio_value=500000, age=40, + annual_income=200000, target_retirement_age=55 + ) + assert result["retirement_projection"]["years_to_retirement"] == 15 + assert len(result["what_if_scenarios"]) >= 2 + + +def test_retirement_math_reasonable(): + result = analyze_wealth_position( + portfolio_value=100000, age=35, + annual_income=100000, annual_savings=15000, + target_retirement_age=65 + ) + projected = result["retirement_projection"]["projected_total_at_retirement"] + assert projected > 700000 + assert projected < 5000000 + + +def test_savings_grade_low_vs_high(): + result_low = analyze_wealth_position( + 50000, 30, 100000, annual_savings=5000 + ) + result_high = analyze_wealth_position( + 50000, 30, 100000, annual_savings=30000 + ) + low_grade = result_low["savings_analysis"]["savings_grade"] + high_grade = result_high["savings_analysis"]["savings_grade"] + assert low_grade in ["critical", "minimum", "low"] + assert high_grade in ["excellent", "exceptional"] diff --git a/agent/tools/wealth_visualizer.py b/agent/tools/wealth_visualizer.py new file mode 100644 index 000000000..d89c39fee --- /dev/null +++ b/agent/tools/wealth_visualizer.py @@ -0,0 +1,174 @@ +""" +Wealth Gap Visualizer +Compares actual net worth against Federal Reserve median wealth by age group. +Projects retirement income and shows what-if scenarios. +Source: Federal Reserve Survey of Consumer Finances 2022 +""" + +FED_WEALTH_DATA = { + "under_35": { + "median": 39000, "p25": 7000, + "p75": 168000, "p90": 466000, + }, + "35_to_44": { + "median": 135000, "p25": 22000, + "p75": 461000, "p90": 1100000, + }, + "45_to_54": { + "median": 247000, "p25": 43000, + "p75": 791000, "p90": 1900000, + }, + "55_to_64": { + "median": 365000, "p25": 71000, + "p75": 1200000, "p90": 2900000, + }, + "65_to_74": { + "median": 409000, "p25": 83000, + "p75": 1380000, "p90": 3200000, + }, +} + +SAVINGS_GRADES = { + "exceptional": (0.30, "You are building wealth aggressively"), + "excellent": (0.20, "You are on track for most goals"), + "good": (0.15, "Solid progress"), + "minimum": (0.10, "Basic — consider increasing"), + "critical": (0.05, "Below recommended — increase urgently"), + "low": (0.0, "Saving very little — prioritize this"), +} + + +def _get_age_bracket(age: int) -> str: + if age < 35: + return "under_35" + elif age < 45: + return "35_to_44" + elif age < 55: + return "45_to_54" + elif age < 65: + return "55_to_64" + else: + return "65_to_74" + + +def analyze_wealth_position( + portfolio_value: float, + age: int, + annual_income: float, + annual_savings: float = None, + target_retirement_age: int = 65, + real_estate_equity: float = 0, +) -> dict: + """Compare net worth against Fed Reserve benchmarks and project retirement.""" + + # Step 2: Total net worth + total_net_worth = portfolio_value + real_estate_equity + + # Step 3: Percentile position + bracket_key = _get_age_bracket(age) + bracket = FED_WEALTH_DATA[bracket_key] + + if total_net_worth >= bracket["p90"]: + position = "top 10%" + elif total_net_worth >= bracket["p75"]: + position = "75th-90th percentile" + elif total_net_worth >= bracket["median"]: + position = "50th-75th percentile" + elif total_net_worth >= bracket["p25"]: + position = "25th-50th percentile" + else: + position = "bottom 25%" + + diff_from_median = total_net_worth - bracket["median"] + if diff_from_median >= 0: + vs_median = f"+${diff_from_median:,.0f} above median" + else: + vs_median = f"${abs(diff_from_median):,.0f} below median" + + # Step 4: Savings analysis + savings = annual_savings if annual_savings is not None else annual_income * 0.15 + savings_rate = savings / annual_income if annual_income > 0 else 0 + + grade = "low" + for g, (threshold, _) in SAVINGS_GRADES.items(): + if savings_rate >= threshold: + grade = g + break + + # Step 5: Retirement projection + years = max(1, target_retirement_age - age) + growth_rate = 0.07 + + future_portfolio = portfolio_value * ((1 + growth_rate) ** years) + future_savings = savings * ( + ((1 + growth_rate) ** years - 1) / growth_rate + ) + total_at_retirement = future_portfolio + future_savings + monthly_retirement_income = (total_at_retirement * 0.04) / 12 + + # Step 6: What-if scenarios + # Scenario 1: save 5% more + extra_annual = annual_income * 0.05 + extra_future = extra_annual * ( + ((1 + growth_rate) ** years - 1) / growth_rate + ) + extra_monthly = (extra_future * 0.04) / 12 + + # Scenario 2: retire 5 years earlier + years_early = max(1, years - 5) + early_portfolio = portfolio_value * ((1 + growth_rate) ** years_early) + early_savings_val = savings * ( + ((1 + growth_rate) ** years_early - 1) / growth_rate + ) + early_monthly = ((early_portfolio + early_savings_val) * 0.04) / 12 + + # Build honest assessment + peer_clause = f"You are in the {position} for your age group." + retirement_clause = ( + f"At your current savings rate, you can expect " + f"${round(monthly_retirement_income):,}/mo at retirement." + ) + honest_assessment = f"{peer_clause} {retirement_clause}" + + return { + "current_position": { + "age": age, + "total_net_worth": total_net_worth, + "portfolio_value": portfolio_value, + "real_estate_equity": real_estate_equity, + "vs_peers": position, + "median_for_age": bracket["median"], + "you_vs_median": vs_median, + "percentile_estimate": position, + }, + "savings_analysis": { + "annual_savings_used": savings, + "savings_rate": round(savings_rate, 3), + "savings_grade": grade, + "assessment": SAVINGS_GRADES[grade][1], + }, + "retirement_projection": { + "target_retirement_age": target_retirement_age, + "years_to_retirement": years, + "projected_total_at_retirement": round(total_at_retirement), + "monthly_income_at_retirement": round(monthly_retirement_income), + "assumptions": "7% annual growth, 4% withdrawal rate", + }, + "what_if_scenarios": [ + { + "scenario": "Save 5% more per year", + "extra_monthly_at_retirement": round(extra_monthly), + "description": ( + f"Adding ${extra_annual:,.0f}/yr gives " + f"${round(extra_monthly):,} more per month at retirement" + ), + }, + { + "scenario": f"Retire 5 years earlier (age {target_retirement_age - 5})", + "monthly_income": round(early_monthly), + "vs_normal_retirement": round(early_monthly - monthly_retirement_income), + }, + ], + "honest_assessment": honest_assessment, + "data_source": "Federal Reserve Survey of Consumer Finances 2022", + }