BankBot-AI / backend /app /ai /simulation.py
mohsin-devs's picture
Deploy to HF
a282d4b
Raw
History Blame Contribute Delete
8.32 kB
from sqlalchemy.orm import Session
from app.database.models import Account, Goal, Investment, Subscription
from app.ai.forecasting import get_cashflow_metrics
def simulate_purchase_impact(db: Session, user_id: str, amount: float, category: str, merchant: str):
"""
Simulates buying a large asset or item (e.g. a car) and assesses risk.
"""
accounts = db.query(Account).filter(Account.user_id == user_id).all()
total_balance = sum(acc.balance for acc in accounts)
checking_acc = next((a for a in accounts if a.type.lower() == "checking"), None)
# Target emergency fund amount
goals = db.query(Goal).filter(Goal.user_id == user_id).all()
emergency_goal = next((g for g in goals if "emergency" in g.title.lower()), None)
emergency_threshold = emergency_goal.target_amount if emergency_goal else 3000.0
new_balance = total_balance - amount
# Cashflow metrics
_, daily_income, daily_spending = get_cashflow_metrics(db, user_id)
monthly_net = (daily_income - daily_spending) * 30.4
# Risk Analysis
risk_level = "low"
reasons = []
if amount > total_balance:
risk_level = "critical"
reasons.append("Purchase exceeds your total available balance, requiring debt.")
elif new_balance < emergency_threshold:
risk_level = "high"
reasons.append(f"This purchase depletes your emergency buffer (threshold of ${emergency_threshold:,.2f}).")
elif amount > total_balance * 0.3:
risk_level = "medium"
reasons.append("Single purchase consumes more than 30% of your total liquid cash.")
if monthly_net < 0 and amount > 500:
risk_level = "high"
reasons.append("You have a negative monthly cashflow; making large purchases increases financial strain.")
# Recommendations
recommendation = ""
if risk_level == "critical":
recommendation = "❌ Strongly advise against this purchase. Consider financing options, delaying, or establishing a dedicated goal."
elif risk_level == "high":
recommendation = "⚠️ Refrain from this purchase if possible. Rebuilding your emergency fund should be prioritized."
elif risk_level == "medium":
recommendation = "💡 Proceed with caution. Consider trimming discretionary expenses next month to offset the cost."
else:
recommendation = "✅ Purchase is safe. It fits within your financial profile without impacting key safety buffers."
return {
"purchase_amount": amount,
"merchant": merchant,
"category": category,
"current_balance": round(total_balance, 2),
"projected_balance": round(max(0.0, new_balance), 2),
"savings_impact": {
"immediate_reduction": round(amount, 2),
"emergency_buffer_breached": new_balance < emergency_threshold,
"emergency_threshold": round(emergency_threshold, 2)
},
"risk_analysis": {
"risk_level": risk_level,
"reasons": reasons
},
"recommendation": recommendation
}
def simulate_investment_impact(db: Session, user_id: str, monthly_sip: float, asset_type: str, lump_sum: float = 0.0):
"""
Simulates investment growth and evaluates opportunity cost.
"""
# Expected annual returns based on asset type
returns_map = {
"stock": 0.10, # 10%
"crypto": 0.20, # 20%
"mutual_fund": 0.08, # 8%
"fd": 0.05, # 5%
"bond": 0.04 # 4%
}
apr = returns_map.get(asset_type.lower(), 0.07)
# Calculate current balance
accounts = db.query(Account).filter(Account.user_id == user_id).all()
total_balance = sum(acc.balance for acc in accounts)
# Cashflow metrics
_, daily_income, daily_spending = get_cashflow_metrics(db, user_id)
monthly_net = (daily_income - daily_spending) * 30.4
# Check if SIP is affordable
is_affordable = monthly_net >= monthly_sip
growth_projection = []
current_value = lump_sum
total_invested = lump_sum
# 5-year monthly projection
for month in range(0, 61):
if month > 0:
current_value = (current_value + monthly_sip) * (1 + apr / 12)
total_invested += monthly_sip
if month in [12, 36, 60]: # Save 1, 3, 5 year markers
growth_projection.append({
"year": month // 12,
"total_invested": round(total_invested, 2),
"future_value": round(current_value, 2),
"earnings": round(max(0.0, current_value - total_invested), 2)
})
risk_level = "low"
if asset_type.lower() == "crypto":
risk_level = "high"
elif asset_type.lower() in ["stock", "mutual_fund"] and monthly_sip > monthly_net * 0.5:
risk_level = "medium"
recommendation = ""
if not is_affordable:
recommendation = f"⚠️ Your monthly net surplus (${monthly_net:,.2f}) is lower than the planned SIP (${monthly_sip:,.2f}). This may lead to checking overdrafts."
else:
recommendation = f"✅ Excellent choice. Investing ${monthly_sip:,.2f} monthly in {asset_type} is fully supported by your net cashflow."
return {
"asset_type": asset_type,
"monthly_sip": round(monthly_sip, 2),
"lump_sum": round(lump_sum, 2),
"is_affordable": is_affordable,
"growth_projection": growth_projection,
"risk_analysis": {
"risk_level": risk_level,
"expected_annual_return": apr
},
"savings_impact": {
"opportunity_cost_yearly": round(monthly_sip * 12, 2),
"monthly_surplus_retaining": round(max(0.0, monthly_net - monthly_sip), 2)
},
"recommendation": recommendation
}
def simulate_subscription_cancellation(db: Session, user_id: str, subscription_ids: list):
"""
Simulates the financial benefit of cancelling one or more subscriptions.
"""
subs = db.query(Subscription).filter(
Subscription.user_id == user_id,
Subscription.id.in_(subscription_ids)
).all()
if not subs:
return {"message": "No matching subscriptions found for cancellation simulation."}
monthly_savings = 0.0
yearly_savings = 0.0
cancelled_details = []
for sub in subs:
cost = sub.amount
is_monthly = sub.billing_cycle.lower() == "monthly"
m_cost = cost if is_monthly else (cost / 12)
y_cost = (cost * 12) if is_monthly else cost
monthly_savings += m_cost
yearly_savings += y_cost
cancelled_details.append({
"id": sub.id,
"merchant": sub.merchant,
"amount": sub.amount,
"billing_cycle": sub.billing_cycle
})
# Relate savings to user's Goals
goals = db.query(Goal).filter(Goal.user_id == user_id).all()
first_goal = goals[0] if goals else None
goal_impact = None
if first_goal:
months_saved = 0.0
remaining_needed = max(0.0, first_goal.target_amount - first_goal.current_amount)
if monthly_savings > 0:
months_saved = remaining_needed / (remaining_needed / 12 if remaining_needed > 0 else 1) # simple logic
# Let's say if they direct this money to goal, it reduces target time by:
months_saved = (remaining_needed / monthly_savings) if remaining_needed > 0 else 0
goal_impact = {
"goal_title": first_goal.title,
"target_amount": round(first_goal.target_amount, 2),
"months_to_reach_with_savings": round(months_saved, 1) if monthly_savings > 0 else 0
}
# Recommendations
recommendation = f"Cancelling these subscriptions yields ${monthly_savings:,.2f} per month (${yearly_savings:,.2f} annually). Reinvesting these funds into high-yield savings or mutual funds is recommended."
return {
"cancelled_subscriptions": cancelled_details,
"monthly_savings": round(monthly_savings, 2),
"yearly_savings": round(yearly_savings, 2),
"goal_impact": goal_impact,
"recommendation": recommendation
}