Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import anthropic | |
| import json | |
| # โโ constants โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| EXPENSE_CATS = [ | |
| "Housing", "Food & groceries", "Transport", "Utilities", | |
| "Entertainment", "Health", "Education", "Clothing", "Other" | |
| ] | |
| CAT_LIMITS = { | |
| "Housing": 30, "Food & groceries": 15, "Transport": 15, | |
| "Utilities": 10, "Entertainment": 10, "Health": 10, | |
| "Education": 10, "Clothing": 5, "Other": 10 | |
| } | |
| # โโ helpers โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def fmt(n: float) -> str: | |
| return f"${round(n):,}" | |
| def pct(part: float, whole: float, decimals: int = 1) -> str: | |
| if whole == 0: | |
| return "0%" | |
| return f"{round(part / whole * 100, decimals)}%" | |
| # โโ core calculation โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def calculate_budget( | |
| gross_income: float, | |
| fed_tax_pct: float, | |
| state_tax_pct: float, | |
| fica_pct: float, | |
| medicare_pct: float, | |
| k401_pct: float, | |
| housing: float, | |
| food: float, | |
| transport: float, | |
| utilities: float, | |
| entertainment: float, | |
| health: float, | |
| education: float, | |
| clothing: float, | |
| other: float, | |
| api_key: str, | |
| ): | |
| if gross_income <= 0: | |
| return ( | |
| "โ ๏ธ Please enter a valid gross income.", | |
| "", "", "", "", "", "" | |
| ) | |
| # โโ deductions โโ | |
| fed_amt = gross_income * fed_tax_pct / 100 | |
| state_amt = gross_income * state_tax_pct / 100 | |
| fica_amt = gross_income * fica_pct / 100 | |
| medicare_amt = gross_income * medicare_pct / 100 | |
| k401_amt = gross_income * k401_pct / 100 | |
| total_tax = fed_amt + state_amt + fica_amt + medicare_amt | |
| total_ded = total_tax + k401_amt | |
| takehome = gross_income - total_ded | |
| # โโ income flow summary โโ | |
| flow_md = f"""## Income flow | |
| | | Amount | % of gross | | |
| |---|---|---| | |
| | **Gross income** | **{fmt(gross_income)}** | 100% | | |
| | Federal tax | -{fmt(fed_amt)} | {pct(fed_amt, gross_income)} | | |
| | State tax | -{fmt(state_amt)} | {pct(state_amt, gross_income)} | | |
| | Social Security | -{fmt(fica_amt)} | {pct(fica_amt, gross_income)} | | |
| | Medicare | -{fmt(medicare_amt)} | {pct(medicare_amt, gross_income)} | | |
| | 401(k) | -{fmt(k401_amt)} | {pct(k401_amt, gross_income)} | | |
| | **Take-home pay** | **{fmt(takehome)}** | {pct(takehome, gross_income)} | | |
| """ | |
| # โโ expense breakdown โโ | |
| expenses = { | |
| "Housing": housing, | |
| "Food & groceries": food, | |
| "Transport": transport, | |
| "Utilities": utilities, | |
| "Entertainment": entertainment, | |
| "Health": health, | |
| "Education": education, | |
| "Clothing": clothing, | |
| "Other": other, | |
| } | |
| used = {k: v for k, v in expenses.items() if v > 0} | |
| total_exp = sum(used.values()) | |
| remaining = takehome - total_exp | |
| exp_rows = [] | |
| for cat, amt in used.items(): | |
| limit = CAT_LIMITS.get(cat, 10) | |
| cat_pct = round(amt / takehome * 100, 1) if takehome > 0 else 0 | |
| status = "โ " if cat_pct <= limit * 0.8 else ("โ ๏ธ" if cat_pct <= limit else "๐ด") | |
| exp_rows.append( | |
| f"| {status} {cat} | {fmt(amt)} | {cat_pct}% | {limit}% |" | |
| ) | |
| exp_md = f"""## Spending breakdown (vs take-home) | |
| | Category | Amount | % of take-home | Limit | | |
| |---|---|---|---| | |
| {''.join(chr(10) + r for r in exp_rows)} | |
| **Total expenses:** {fmt(total_exp)} | **Remaining:** {fmt(remaining)} ({pct(remaining, takehome)} of take-home) | |
| """ | |
| # โโ summary metrics โโ | |
| rem_emoji = "โ " if remaining >= 0 else "๐ด" | |
| summary_md = f"""## Summary | |
| | | | | |
| |---|---| | |
| | Gross income | {fmt(gross_income)} | | |
| | Total taxes | {fmt(total_tax)} ({pct(total_tax, gross_income)}) | | |
| | 401(k) saved | {fmt(k401_amt)} ({pct(k401_amt, gross_income)}) | | |
| | Take-home | {fmt(takehome)} | | |
| | Total expenses | {fmt(total_exp)} | | |
| | {rem_emoji} Remaining | {fmt(remaining)} | | |
| """ | |
| # โโ AI advice via streaming โโ | |
| if not api_key.strip(): | |
| ai_advice = "โ ๏ธ Enter your Anthropic API key to get personalized AI advice." | |
| else: | |
| exp_lines = "\n".join( | |
| f"- {cat}: {fmt(amt)} ({round(amt/takehome*100,1) if takehome else 0}% of take-home, limit {CAT_LIMITS.get(cat,10)}%)" | |
| for cat, amt in used.items() | |
| ) | |
| prompt = f"""You are a friendly, practical personal finance advisor. Analyze this monthly budget and give 5-6 specific, actionable suggestions. Be direct โ use numbered points. Reference the actual dollar amounts. | |
| BUDGET: | |
| Gross income: {fmt(gross_income)}/month | |
| Taxes: {fmt(total_tax)} ({pct(total_tax, gross_income)} of gross) | |
| 401(k): {fmt(k401_amt)} ({pct(k401_amt, gross_income)} of gross) | |
| Take-home: {fmt(takehome)}/month | |
| EXPENSES: | |
| {exp_lines} | |
| Total expenses: {fmt(total_exp)} | |
| Remaining: {fmt(remaining)} ({pct(remaining, gross_income)} of gross) | |
| Give honest advice referencing the real numbers. 1-2 sentences per point.""" | |
| try: | |
| client = anthropic.Anthropic(api_key=api_key.strip()) | |
| ai_advice = "" | |
| with client.messages.stream( | |
| model="claude-sonnet-4-5", | |
| max_tokens=1000, | |
| messages=[{"role": "user", "content": prompt}], | |
| ) as stream: | |
| for text in stream.text_stream: | |
| ai_advice += text | |
| yield flow_md, exp_md, summary_md, ai_advice, \ | |
| fmt(gross_income), fmt(takehome), fmt(remaining) | |
| return | |
| except anthropic.AuthenticationError: | |
| ai_advice = "โ Invalid API key. Check your Anthropic API key and try again." | |
| except Exception as e: | |
| ai_advice = f"โ Error calling AI: {str(e)}" | |
| yield flow_md, exp_md, summary_md, ai_advice, \ | |
| fmt(gross_income), fmt(takehome), fmt(remaining) | |
| # โโ Gradio UI โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| with gr.Blocks( | |
| title="AI Budget Advisor", | |
| theme=gr.themes.Soft(primary_hue="violet"), | |
| css=""" | |
| .section-header { font-weight: 600; font-size: 1rem; margin-bottom: 4px; } | |
| .metric-box { text-align: center; padding: 8px; } | |
| """ | |
| ) as demo: | |
| gr.Markdown("# ๐ฐ AI Budget Advisor") | |
| gr.Markdown( | |
| "Enter your gross income, pre-tax deductions, and monthly expenses. " | |
| "Add your [Anthropic API key](https://console.anthropic.com/) to get AI-powered personalized advice." | |
| ) | |
| with gr.Row(): | |
| api_key_input = gr.Textbox( | |
| label="๐ Anthropic API key", | |
| placeholder="sk-ant-...", | |
| type="password", | |
| scale=2, | |
| ) | |
| gr.Markdown("---") | |
| # โโ INCOME โโ | |
| with gr.Row(): | |
| with gr.Column(): | |
| gr.Markdown("### ๐ฅ Gross income (before taxes)") | |
| gross_income = gr.Number(label="Monthly gross income ($)", value=5000, minimum=0) | |
| # โโ PRE-TAX DEDUCTIONS โโ | |
| gr.Markdown("### ๐๏ธ Pre-tax deductions") | |
| with gr.Row(): | |
| fed_tax = gr.Slider(0, 50, value=22, step=0.5, label="Federal income tax (%)") | |
| state_tax = gr.Slider(0, 15, value=5, step=0.5, label="State income tax (%)") | |
| with gr.Row(): | |
| fica = gr.Slider(0, 10, value=6.2, step=0.1, label="Social Security / FICA (%)") | |
| medicare = gr.Slider(0, 5, value=1.45, step=0.05, label="Medicare (%)") | |
| with gr.Row(): | |
| k401 = gr.Slider(0, 30, value=6, step=0.5, label="401(k) contribution (%)") | |
| gr.Markdown("---") | |
| # โโ EXPENSES โโ | |
| gr.Markdown("### ๐ธ Monthly expenses") | |
| with gr.Row(): | |
| housing = gr.Number(label="Housing ($)", value=1200, minimum=0) | |
| food = gr.Number(label="Food & groceries ($)", value=400, minimum=0) | |
| transport = gr.Number(label="Transport ($)", value=200, minimum=0) | |
| with gr.Row(): | |
| utilities = gr.Number(label="Utilities ($)", value=150, minimum=0) | |
| entertainment = gr.Number(label="Entertainment ($)", value=100, minimum=0) | |
| health = gr.Number(label="Health ($)", value=0, minimum=0) | |
| with gr.Row(): | |
| education = gr.Number(label="Education ($)", value=0, minimum=0) | |
| clothing = gr.Number(label="Clothing ($)", value=0, minimum=0) | |
| other = gr.Number(label="Other ($)", value=0, minimum=0) | |
| gr.Markdown("---") | |
| calc_btn = gr.Button("๐งฎ Calculate + get AI advice", variant="primary", size="lg") | |
| # โโ OUTPUTS โโ | |
| gr.Markdown("---") | |
| gr.Markdown("## Results") | |
| with gr.Row(): | |
| out_gross = gr.Textbox(label="Gross income", interactive=False) | |
| out_takehome = gr.Textbox(label="Take-home pay", interactive=False) | |
| out_remain = gr.Textbox(label="Remaining", interactive=False) | |
| with gr.Row(): | |
| with gr.Column(): | |
| out_flow = gr.Markdown(label="Income flow") | |
| with gr.Column(): | |
| out_summary = gr.Markdown(label="Summary") | |
| out_expenses = gr.Markdown(label="Spending breakdown") | |
| gr.Markdown("### ๐ค AI budget advisor") | |
| out_ai = gr.Markdown(label="AI advice", value="_AI advice will appear here after you calculate._") | |
| # โโ wire up โโ | |
| calc_btn.click( | |
| fn=calculate_budget, | |
| inputs=[ | |
| gross_income, | |
| fed_tax, state_tax, fica, medicare, k401, | |
| housing, food, transport, utilities, | |
| entertainment, health, education, clothing, other, | |
| api_key_input, | |
| ], | |
| outputs=[out_flow, out_expenses, out_summary, out_ai, | |
| out_gross, out_takehome, out_remain], | |
| ) | |
| gr.Markdown( | |
| "---\n" | |
| "_Powered by [Claude](https://anthropic.com). " | |
| "This is a financial planning tool, not professional financial advice._" | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |