bootstrap
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
from copy import deepcopy
|
||||
|
||||
from cashflow_model import Account, FinancialModel
|
||||
|
||||
|
||||
class ForecastError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ForecastService:
|
||||
def __init__(self, model: FinancialModel):
|
||||
self.model = deepcopy(model)
|
||||
|
||||
def forecast_cashflow(self, months: int = 12) -> list[dict]:
|
||||
if months < 1:
|
||||
raise ForecastError("months must be >= 1")
|
||||
|
||||
results = []
|
||||
for account in self.model.accounts:
|
||||
balance = account.balance
|
||||
monthly = self._project_account(account, months)
|
||||
for m in range(months):
|
||||
balance = monthly[m]["balance"]
|
||||
results.append({
|
||||
"account": account.name,
|
||||
"month": m + 1,
|
||||
"balance": round(balance, 2),
|
||||
"income": round(monthly[m]["income"], 2),
|
||||
"expenses": round(monthly[m]["expenses"], 2),
|
||||
})
|
||||
return results
|
||||
|
||||
def _project_account(self, account: Account, months: int) -> list[dict]:
|
||||
balance = account.balance
|
||||
monthly = []
|
||||
for m in range(months):
|
||||
income = 0.0
|
||||
expenses = 0.0
|
||||
|
||||
for t in self.model.transactions:
|
||||
if t.account == str(account.id):
|
||||
if t.amount > 0:
|
||||
income += t.amount
|
||||
else:
|
||||
expenses += abs(t.amount)
|
||||
|
||||
for r in self.model.recurring:
|
||||
if r.category == "income":
|
||||
income += abs(r.amount)
|
||||
else:
|
||||
expenses += abs(r.amount)
|
||||
|
||||
income += self._asset_income(account)
|
||||
expenses += self._liability_cost(account)
|
||||
|
||||
balance += income - expenses
|
||||
|
||||
asset_growth = sum(
|
||||
a.value * a.growth_rate / 12
|
||||
for a in self.model.assets
|
||||
)
|
||||
balance += asset_growth
|
||||
|
||||
monthly.append({
|
||||
"balance": balance,
|
||||
"income": income,
|
||||
"expenses": expenses,
|
||||
})
|
||||
return monthly
|
||||
|
||||
def _asset_income(self, account: Account) -> float:
|
||||
return sum(
|
||||
a.value * a.growth_rate / 12
|
||||
for a in self.model.assets
|
||||
)
|
||||
|
||||
def _liability_cost(self, account: Account) -> float:
|
||||
total = 0.0
|
||||
for liability in self.model.liabilities:
|
||||
interest_cost = liability.balance * liability.interest / 100 / 12
|
||||
total += interest_cost
|
||||
liability.balance -= liability.payment - interest_cost
|
||||
if liability.balance < 0:
|
||||
liability.balance = 0
|
||||
return total
|
||||
|
||||
def summary(self, months: int = 12) -> dict:
|
||||
results = self.forecast_cashflow(months)
|
||||
if not results:
|
||||
return {"total_balance": 0, "total_income": 0, "total_expenses": 0, "months": months}
|
||||
|
||||
final = results[-1]
|
||||
all_income = sum(r["income"] for r in results)
|
||||
all_expenses = sum(r["expenses"] for r in results)
|
||||
return {
|
||||
"total_balance": final["balance"],
|
||||
"total_income": round(all_income, 2),
|
||||
"total_expenses": round(all_expenses, 2),
|
||||
"months": months,
|
||||
}
|
||||
Reference in New Issue
Block a user