Spaces:
Running
Running
| import os | |
| from datetime import datetime, timedelta | |
| from sqlalchemy.orm import Session | |
| from app.database.models import User, Account, Transaction, Goal, Investment, Subscription | |
| from app.ai.forecasting import get_cashflow_metrics | |
| from app.ai.ollama_integration import get_ai_response | |
| def calculate_financial_health_score(db: Session, user_id: str): | |
| """ | |
| Computes a multi-dimensional Financial Health Score (0-100) based on real database records. | |
| """ | |
| accounts = db.query(Account).filter(Account.user_id == user_id).all() | |
| total_balance = sum(acc.balance for acc in accounts) | |
| savings_balance = sum(acc.balance for acc in accounts if acc.type.lower() == "savings") | |
| # Cashflow | |
| current_balance, daily_income, daily_spending = get_cashflow_metrics(db, user_id) | |
| monthly_income = max(1000.0, daily_income * 30.4) | |
| monthly_spending = daily_spending * 30.4 | |
| # 1. Savings Consistency (20 pts) | |
| # Check frequency of saving transactions or goal additions | |
| txns = db.query(Transaction).join(Account).filter( | |
| Account.user_id == user_id, | |
| Transaction.type == "credit", | |
| Transaction.category == "Income" | |
| ).count() | |
| # Let's say if they have active goals with current_amount > 0, they get higher points | |
| goals = db.query(Goal).filter(Goal.user_id == user_id).all() | |
| goal_savings = sum(g.current_amount for g in goals) | |
| savings_score = 10.0 | |
| if goal_savings > 1000: | |
| savings_score += 10.0 | |
| elif goal_savings > 0: | |
| savings_score += 5.0 | |
| # 2. Debt Ratio (20 pts) | |
| # Estimate EMIs or goals with "debt" | |
| debt_goals = sum(g.target_amount - g.current_amount for g in goals if "debt" in g.title.lower() or "loan" in g.title.lower()) | |
| # Standard monthly debt service (estimate 10% of debt or $150 minimum if debt exists) | |
| est_monthly_debt = max(0.0, debt_goals * 0.05) | |
| debt_to_income = est_monthly_debt / monthly_income | |
| debt_score = 20.0 | |
| if debt_to_income > 0.40: | |
| debt_score = 5.0 | |
| elif debt_to_income > 0.20: | |
| debt_score = 12.0 | |
| elif debt_to_income > 0.05: | |
| debt_score = 18.0 | |
| # 3. Spending Discipline (20 pts) | |
| # Ratio of monthly spending to monthly income | |
| savings_rate = (monthly_income - monthly_spending) / monthly_income if monthly_income > 0 else 0 | |
| discipline_score = 10.0 | |
| if savings_rate >= 0.30: | |
| discipline_score = 20.0 | |
| elif savings_rate >= 0.15: | |
| discipline_score = 16.0 | |
| elif savings_rate >= 0.0: | |
| discipline_score = 12.0 | |
| # 4. Emergency Fund (20 pts) | |
| # Do they have 3-6 months of expenses in savings? | |
| monthly_expenses = max(500.0, monthly_spending) | |
| months_buffer = savings_balance / monthly_expenses | |
| emergency_score = 0.0 | |
| if months_buffer >= 6.0: | |
| emergency_score = 20.0 | |
| elif months_buffer >= 3.0: | |
| emergency_score = 15.0 | |
| elif months_buffer >= 1.0: | |
| emergency_score = 8.0 | |
| # 5. Investment Index (10 pts) | |
| investments = db.query(Investment).filter(Investment.user_id == user_id).all() | |
| inv_total = sum(i.current_value for i in investments) | |
| investment_score = 0.0 | |
| if inv_total > 5000: | |
| investment_score = 10.0 | |
| elif inv_total > 0: | |
| investment_score = 6.0 | |
| # 6. Subscription Efficiency (10 pts) | |
| subs = db.query(Subscription).filter(Subscription.user_id == user_id, Subscription.active == True).all() | |
| sub_cost = sum(s.amount if s.billing_cycle.lower() == "monthly" else (s.amount / 12) for s in subs) | |
| sub_ratio = sub_cost / monthly_income | |
| sub_score = 10.0 | |
| if sub_ratio > 0.10: # More than 10% of income on subscriptions | |
| sub_score = 3.0 | |
| elif sub_ratio > 0.05: # More than 5% | |
| sub_score = 7.0 | |
| # Calculate Overall Score | |
| overall_score = savings_score + debt_score + discipline_score + emergency_score + investment_score + sub_score | |
| overall_score = min(100.0, max(0.0, overall_score)) | |
| # Actionable improvements list | |
| improvements = [] | |
| if savings_score < 15: | |
| improvements.append("Set up automated transfers to your Savings account right after payday.") | |
| if debt_score < 15: | |
| improvements.append("Prioritize high-interest debt payoffs using the debt avalanche method.") | |
| if discipline_score < 15: | |
| improvements.append("Discretionary spending (shopping & dining) is high. Try implementing a $50 weekly limit.") | |
| if emergency_score < 15: | |
| improvements.append(f"Build savings buffer. Try to accumulate at least ${monthly_expenses * 3:,.2f} (3 months of expenses).") | |
| if investment_score < 6: | |
| improvements.append("Start a low-cost stock index fund SIP to counter inflation.") | |
| if sub_score < 8: | |
| improvements.append("Conduct an audit of active subscriptions. Cancel duplicate/unused memberships.") | |
| if not improvements: | |
| improvements.append("Maintain your current financial habits; your portfolio is highly optimized!") | |
| # AI Explanation | |
| user = db.query(User).filter(User.id == user_id).first() | |
| persona = user.financial_personality if user else "Saver" | |
| ai_prompt = f""" | |
| The user is a '{persona}' with a Financial Health Score of {overall_score:.0f}/100. | |
| Sub-scores: | |
| - Savings Consistency: {savings_score:.0f}/20 | |
| - Debt Management: {debt_score:.0f}/20 | |
| - Spending Discipline: {discipline_score:.0f}/20 | |
| - Emergency Fund: {emergency_score:.0f}/20 | |
| - Investment Allocation: {investment_score:.0f}/10 | |
| - Subscription Management: {sub_score:.0f}/10 | |
| Write a concise, professional financial analyst explanation of this score. Detail the primary strengths and key weaknesses. | |
| Do NOT write a generic chatbot reply. Keep it to 3-4 sentences. Format like a Bloomberg analyst report. | |
| """ | |
| from app.ai.ollama_integration import has_active_ai_backend | |
| explanation = None | |
| if has_active_ai_backend(): | |
| try: | |
| # Hard 8-second timeout so the health score endpoint never hangs | |
| import threading | |
| result = [None] | |
| def _call(): | |
| result[0] = get_ai_response(ai_prompt) | |
| t = threading.Thread(target=_call, daemon=True) | |
| t.start() | |
| t.join(timeout=8) | |
| explanation = result[0] | |
| except Exception: | |
| pass | |
| if not explanation: | |
| explanation = f"As a {persona}, your financial health score of {overall_score:.0f} reflects solid fundamentals with opportunities to optimize emergency allocations and subscription efficiencies. Focus on automating savings and expanding investments." | |
| return { | |
| "overall_score": round(overall_score, 0), | |
| "categories": { | |
| "savings_consistency": {"score": round(savings_score, 0), "max": 20}, | |
| "debt_ratio": {"score": round(debt_score, 0), "max": 20}, | |
| "spending_discipline": {"score": round(discipline_score, 0), "max": 20}, | |
| "emergency_funds": {"score": round(emergency_score, 0), "max": 20}, | |
| "investments": {"score": round(investment_score, 0), "max": 10}, | |
| "subscription_management": {"score": round(sub_score, 0), "max": 10} | |
| }, | |
| "explanation": explanation, | |
| "actionable_improvements": improvements | |
| } | |
| def generate_daily_briefing(db: Session, user_id: str): | |
| """ | |
| Pulls complete financial context and generates a personalized daily financial briefing. | |
| """ | |
| user = db.query(User).filter(User.id == user_id).first() | |
| if not user: | |
| return {"briefing": "User not found."} | |
| # Collect data | |
| accounts = db.query(Account).filter(Account.user_id == user_id).all() | |
| total_balance = sum(acc.balance for acc in accounts) | |
| goals = db.query(Goal).filter(Goal.user_id == user_id).all() | |
| goals_summary = [f"{g.title}: {g.current_amount}/{g.target_amount}" for g in goals] | |
| investments = db.query(Investment).filter(Investment.user_id == user_id).all() | |
| inv_summary = [f"{i.asset_name} ({i.type}): Current Value ${i.current_value:,.2f}" for i in investments] | |
| # Cashflow | |
| current_balance, daily_income, daily_spending = get_cashflow_metrics(db, user_id) | |
| monthly_income = daily_income * 30.4 | |
| monthly_spending = daily_spending * 30.4 | |
| # Format AI Prompt | |
| ai_prompt = f""" | |
| You are an AI Wealth Advisor and Predictive Banking Engine. Generate a personalized daily financial briefing for {user.profile_data.get('name', 'User')}. | |
| Financial Summary: | |
| - User Personality: {user.financial_personality} | |
| - Total Account Balance: ${total_balance:,.2f} | |
| - Estimated Monthly Income: ${monthly_income:,.2f} | |
| - Estimated Monthly Spending: ${monthly_spending:,.2f} | |
| - Active Goals: {', '.join(goals_summary) if goals_summary else 'None'} | |
| - Investments: {', '.join(inv_summary) if inv_summary else 'None'} | |
| Generate a 3-paragraph daily briefing. | |
| Paragraph 1: Summary of their current liquidity and portfolio health. | |
| Paragraph 2: One specific recommendation regarding their savings goals or investment potential. | |
| Paragraph 3: A behavioral spending insight warning based on their spending velocity. | |
| Style: Bloomberg Terminal style, highly intelligent, concise, financially meaningful, human-like. | |
| Avoid boilerplate generic remarks (e.g. 'You should try saving more money'). Use exact figures. | |
| """ | |
| from app.ai.ollama_integration import has_active_ai_backend | |
| briefing = None | |
| if has_active_ai_backend(): | |
| try: | |
| import threading | |
| result = [None] | |
| def _call(): | |
| result[0] = get_ai_response(ai_prompt) | |
| t = threading.Thread(target=_call, daemon=True) | |
| t.start() | |
| t.join(timeout=10) | |
| briefing = result[0] | |
| except Exception: | |
| pass | |
| if not briefing: | |
| briefing = f"DAILY BRIEFING:\n\nYour liquid capital stands at ${total_balance:,.2f}. Portfolio indicators suggest regular cashflow velocity. Based on your {user.financial_personality} profile, we advise dedicating a portion of your net surplus to your active goals to optimize compound growth. Avoid non-essential weekend dining and retail spikes to maintain your target trajectory." | |
| return { | |
| "date": datetime.utcnow().strftime("%Y-%m-%d"), | |
| "user_name": user.profile_data.get('name', 'User'), | |
| "briefing": briefing, | |
| "metrics": { | |
| "total_liquid_capital": round(total_balance, 2), | |
| "monthly_income_projection": round(monthly_income, 2), | |
| "monthly_burn_rate": round(monthly_spending, 2) | |
| } | |
| } | |