""" Goal Service — projected completion, emergency fund calc. """ from datetime import date from dateutil.relativedelta import relativedelta from app.extensions import db from app.models.goal import Goal from app.models.transaction import Transaction from sqlalchemy import func def get_projected_completion(goal): """ Estimate completion date based on average monthly contribution. Returns date or None if can't be calculated. """ if goal.is_completed: return goal.completed_at remaining = float(goal.target_amount) - float(goal.current_amount) if remaining <= 0: return date.today() contributions = goal.contributions.all() if len(contributions) < 2: return None # Average monthly contribution from history contribs_sorted = sorted(contributions, key=lambda c: c.date) first = contribs_sorted[0].date last = contribs_sorted[-1].date total_contrib = sum(float(c.amount) for c in contribs_sorted) months_elapsed = ( (last.year - first.year) * 12 + (last.month - first.month) ) or 1 avg_monthly = total_contrib / months_elapsed if avg_monthly <= 0: return None months_needed = remaining / avg_monthly projected = date.today() + relativedelta(months=int(months_needed) + 1) return projected def get_emergency_fund_status(): """ Calculate emergency fund status: - 3-month and 6-month expense targets - Current liquid assets (checking + savings + cash) """ from app.models.account import Account # Average monthly expense (last 3 months) today = date.today() three_months_ago = today - relativedelta(months=3) total_expenses = db.session.query( func.coalesce(func.sum(Transaction.amount), 0) ).filter( Transaction.transaction_type == 'expense', Transaction.date >= three_months_ago, Transaction.date <= today, ).scalar() avg_monthly = float(total_expenses) / 3 # Liquid assets liquid = db.session.query( func.coalesce(func.sum(Account.balance), 0) ).filter( Account.is_active == True, Account.account_type.in_(['checking', 'savings', 'cash']), ).scalar() liquid = float(liquid) months_covered = (liquid / avg_monthly) if avg_monthly > 0 else 0 return { 'avg_monthly_expense': avg_monthly, 'liquid_assets': liquid, 'target_3mo': avg_monthly * 3, 'target_6mo': avg_monthly * 6, 'months_covered': round(months_covered, 1), 'pct_3mo': min(round((liquid / (avg_monthly * 3)) * 100, 1), 100) if avg_monthly > 0 else 0, 'pct_6mo': min(round((liquid / (avg_monthly * 6)) * 100, 1), 100) if avg_monthly > 0 else 0, }