Bankbot / backend /app /ai /forecasting.py
mohsin-devs's picture
Deploy to HF Space
7a30d73
Raw
History Blame Contribute Delete
8.18 kB
from datetime import datetime, timedelta
import numpy as np
from sqlalchemy.orm import Session
from app.database.models import Account, Transaction, Goal, Investment, Subscription
def get_cashflow_metrics(db: Session, user_id: str, days: int = 90):
"""
Computes daily average income and spending based on historical transactions.
"""
# Fetch checking & savings accounts for user
accounts = db.query(Account).filter(Account.user_id == user_id).all()
account_ids = [acc.id for acc in accounts]
if not account_ids:
return 0.0, 0.0, 0.0 # Current balance, avg daily income, avg daily spending
current_balance = sum(acc.balance for acc in accounts)
# Fetch recent transactions
cutoff = datetime.utcnow() - timedelta(days=days)
txns = db.query(Transaction).filter(
Transaction.account_id.in_(account_ids),
Transaction.timestamp >= cutoff
).all()
if not txns:
return current_balance, 0.0, 0.0
total_income = sum(t.amount for t in txns if t.type.lower() == "credit")
total_spending = sum(t.amount for t in txns if t.type.lower() == "debit")
avg_daily_income = total_income / days
avg_daily_spending = total_spending / days
return current_balance, avg_daily_income, avg_daily_spending
def predict_future_balance(db: Session, user_id: str, projection_days: int = 90):
"""
Predicts future balances and returns trend description.
"""
current_balance, daily_income, daily_spending = get_cashflow_metrics(db, user_id)
net_daily = daily_income - daily_spending
projected_balance = max(0.0, current_balance + (net_daily * projection_days))
# Calculate percentage change
if current_balance > 0:
percent_change = (projected_balance - current_balance) / current_balance * 100
else:
percent_change = 0.0
# Generate human-friendly description
if percent_change < 0:
insight = f"If current spending continues, your total balance may decrease by {abs(percent_change):.1f}% (down to ${projected_balance:,.2f}) in {projection_days} days."
elif percent_change > 0:
insight = f"Based on current trends, your total balance is projected to grow by {percent_change:.1f}% (up to ${projected_balance:,.2f}) in {projection_days} days."
else:
insight = "Your financial trajectory is steady with minor balance fluctuations."
# Generate daily data points for charts
chart_data = []
for day in range(0, projection_days + 1, 5):
val = max(0.0, current_balance + (net_daily * day))
date_str = (datetime.utcnow() + timedelta(days=day)).strftime("%Y-%m-%d")
chart_data.append({"date": date_str, "balance": round(val, 2)})
return {
"current_balance": round(current_balance, 2),
"projected_balance": round(projected_balance, 2),
"percent_change": round(percent_change, 2),
"net_daily": round(net_daily, 2),
"insight": insight,
"chart_data": chart_data
}
def forecast_savings_and_investments(db: Session, user_id: str, projection_months: int = 12):
"""
Projects savings and investment growth.
"""
accounts = db.query(Account).filter(Account.user_id == user_id).all()
savings_balance = sum(acc.balance for acc in accounts if acc.type.lower() == "savings")
checking_balance = sum(acc.balance for acc in accounts if acc.type.lower() == "checking")
investments = db.query(Investment).filter(Investment.user_id == user_id).all()
total_invested = sum(inv.current_value for inv in investments)
# Subscriptions and recurring bills
subs = db.query(Subscription).filter(Subscription.user_id == user_id, Subscription.active == True).all()
monthly_sub_cost = sum(sub.amount if sub.billing_cycle.lower() == "monthly" else (sub.amount / 12) for sub in subs)
# Let's assume standard default rates if not specified
savings_apr = 0.04 # 4% APY
investment_apr = 0.08 # 8% APY
# We assume the user saves 10% of their net income monthly (derived from transaction history)
_, daily_income, daily_spending = get_cashflow_metrics(db, user_id)
monthly_income = daily_income * 30.4
monthly_spending = daily_spending * 30.4
net_monthly = max(0.0, monthly_income - monthly_spending)
monthly_savings_addition = net_monthly * 0.5 # Put 50% of net into savings
monthly_investment_addition = net_monthly * 0.3 # Put 30% of net into investments
savings_data = []
investment_data = []
debt_data = []
current_savings = savings_balance
current_inv = total_invested
# Let's model a baseline debt if the user has a Goal of type "debt" or a general dummy debt
# We will look for Goals containing "debt" or "loan"
goals = db.query(Goal).filter(Goal.user_id == user_id).all()
debt_goal = next((g for g in goals if "debt" in g.title.lower() or "loan" in g.title.lower()), None)
total_debt = 5000.0 # Default initial simulated debt if none found
if debt_goal:
total_debt = max(0.0, debt_goal.target_amount - debt_goal.current_amount)
monthly_debt_payment = min(total_debt, max(150.0, net_monthly * 0.1)) # Assume 10% of net or at least $150
for month in range(0, projection_months + 1):
# Compounding savings
if month > 0:
current_savings = (current_savings + monthly_savings_addition) * (1 + savings_apr / 12)
current_inv = (current_inv + monthly_investment_addition) * (1 + investment_apr / 12)
total_debt = max(0.0, total_debt - monthly_debt_payment)
label = f"Month {month}"
savings_data.append({"month": label, "amount": round(current_savings, 2)})
investment_data.append({"month": label, "amount": round(current_inv, 2)})
debt_data.append({"month": label, "amount": round(total_debt, 2)})
return {
"projection_months": projection_months,
"monthly_savings_addition": round(monthly_savings_addition, 2),
"monthly_investment_addition": round(monthly_investment_addition, 2),
"savings_growth": savings_data,
"investment_growth": investment_data,
"debt_decline": debt_data,
"total_projected_savings": round(current_savings, 2),
"total_projected_investments": round(current_inv, 2),
"total_remaining_debt": round(total_debt, 2)
}
def simulate_future_scenarios(db: Session, user_id: str, projection_months: int = 6):
"""
Simulates three scenarios: Status Quo, Frugal (cut spending 20%), and Luxury (increase spending 15%).
"""
current_balance, daily_income, daily_spending = get_cashflow_metrics(db, user_id)
monthly_income = daily_income * 30.4
monthly_spending = daily_spending * 30.4
scenarios = {
"status_quo": {"spend_mult": 1.0, "name": "Status Quo (Current spending)"},
"frugal": {"spend_mult": 0.8, "name": "Frugal Mode (Cut non-essentials by 20%)"},
"lifestyle_inflation": {"spend_mult": 1.15, "name": "Lifestyle Inflation (+15% spending)"}
}
results = {}
for key, config in scenarios.items():
mult = config["spend_mult"]
projected_spend = monthly_spending * mult
net_monthly = monthly_income - projected_spend
balance_trend = []
balance = current_balance
for m in range(0, projection_months + 1):
if m > 0:
balance = max(0.0, balance + net_monthly)
balance_trend.append({"month": f"M{m}", "balance": round(balance, 2)})
results[key] = {
"name": config["name"],
"monthly_income": round(monthly_income, 2),
"monthly_spending": round(projected_spend, 2),
"net_monthly": round(net_monthly, 2),
"balance_projection": balance_trend,
"final_balance": round(balance, 2),
"savings_change_pct": round(((balance - current_balance) / current_balance * 100) if current_balance > 0 else 0.0, 2)
}
return results