import type { Category, Transaction, MonthlySnapshot } from '../types' export function getTotalBudget(categories: Category[]): number { return categories.reduce((sum, c) => sum + getEffectiveBudget(c), 0) } export function getTotalSpent(categories: Category[]): number { return categories.reduce((sum, c) => sum + c.spent, 0) } export function getEffectiveBudget(category: Category, month?: number): number { const base = month !== undefined && category.seasonalBaseline ? getSeasonalValue(category.seasonalBaseline, month) : category.budget return base + (category.rolloverEnabled ? category.rolloverAmount : 0) } function getSeasonalValue(baseline: { jan: number; feb: number; mar: number; apr: number; may: number; jun: number; jul: number; aug: number; sep: number; oct: number; nov: number; dec: number }, month: number): number { const map = [baseline.jan, baseline.feb, baseline.mar, baseline.apr, baseline.may, baseline.jun, baseline.jul, baseline.aug, baseline.sep, baseline.oct, baseline.nov, baseline.dec] return map[month] ?? baseline.jan } export function getRemaining(category: Category): number { return getEffectiveBudget(category) - category.spent } export function getProgressPercent(category: Category): number { const budget = getEffectiveBudget(category) if (budget <= 0) return 0 return Math.min(100, Math.round((category.spent / budget) * 100)) } export function addTransaction( categories: Category[], transactions: Transaction[], tx: Transaction ): { categories: Category[]; transactions: Transaction[] } { const cats = categories.map(c => ({ ...c })) const txs = [...transactions, tx] if (tx.isSplit && tx.items) { for (const item of tx.items) { const cat = cats.find(c => c.id === item.categoryId) if (cat) { cat.spent += item.amount } } } else { const cat = cats.find(c => c.id === tx.categoryId) if (cat) { cat.spent += tx.amount } } return { categories: cats, transactions: txs } } export function removeTransaction( categories: Category[], transactions: Transaction[], txId: string ): { categories: Category[]; transactions: Transaction[] } { const tx = transactions.find(t => t.id === txId) if (!tx) return { categories, transactions } const cats = categories.map(c => ({ ...c })) const txs = transactions.filter(t => t.id !== txId) if (tx.isSplit && tx.items) { for (const item of tx.items) { const cat = cats.find(c => c.id === item.categoryId) if (cat) { cat.spent = Math.max(0, cat.spent - item.amount) } } } else { const cat = cats.find(c => c.id === tx.categoryId) if (cat) { cat.spent = Math.max(0, cat.spent - tx.amount) } } return { categories: cats, transactions: txs } } export function rollOverMonth(categories: Category[]): Category[] { return categories.map(c => { const remaining = getEffectiveBudget(c) - c.spent return { ...c, rolloverAmount: c.rolloverEnabled ? c.rolloverAmount + remaining : c.rolloverAmount, spent: 0, } }) } export function computeLifestyleCreep( snapshots: MonthlySnapshot[], currentMonth: string ): { categoryId: string; changePercent: number; isInflation: boolean }[] { const current = snapshots.find(s => s.month === currentMonth) const lastYear = currentMonth.slice(0, 4) const prevYear = String(Number(lastYear) - 1) const prevMonthKey = prevYear + currentMonth.slice(4) const previous = snapshots.find(s => s.month === prevMonthKey) if (!current || !previous) return [] return Object.entries(current.categories).map(([catId, curr]) => { const prev = previous.categories[catId] if (!prev || prev.budget <= 0) return { categoryId: catId, changePercent: 0, isInflation: false } const change = ((curr.budget - prev.budget) / prev.budget) * 100 return { categoryId: catId, changePercent: Math.round(change * 10) / 10, isInflation: change > 5 } }) } export function getNetWorth(accounts: { balance: number }[]): number { return accounts.reduce((sum, a) => sum + a.balance, 0) }