200 lines
7.0 KiB
Python
200 lines
7.0 KiB
Python
"""
|
|
Chart data builder and financial simulation engine
|
|
|
|
This module:
|
|
- Simulates future asset projections
|
|
- Handles inflation calculations
|
|
- Calculates compound interest on deficits
|
|
- Generates data in format compatible with Remotion charts
|
|
"""
|
|
|
|
from typing import Dict, Any, List
|
|
import math
|
|
|
|
|
|
class FinancialSimulator:
|
|
"""Simulate financial projections for Life Plan analysis"""
|
|
|
|
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
self.config = config or {}
|
|
self.inflation_rate = self.config.get("inflation_rate", 0.02) # 2% default
|
|
self.deficit_interest_rate = self.config.get("deficit_interest_rate", 0.15) # 15% default
|
|
self.investment_return = self.config.get("investment_return", 0.03) # 3% default
|
|
|
|
def simulate_timeline(
|
|
self,
|
|
current_age: int,
|
|
retirement_age: int,
|
|
life_expectancy: int,
|
|
current_assets: float,
|
|
annual_income: float,
|
|
annual_expenses: float,
|
|
life_events: Optional[List[Dict]] = None,
|
|
) -> List[Dict[str, Any]]:
|
|
"""
|
|
Simulate year-by-year financial timeline
|
|
|
|
Args:
|
|
current_age: Current age of representative
|
|
retirement_age: Planned retirement age
|
|
life_expectancy: Expected life expectancy
|
|
current_assets: Current asset balance (万円)
|
|
annual_income: Annual income (万円)
|
|
annual_expenses: Annual expenses (万円)
|
|
life_events: List of major life events {year, type, amount}
|
|
|
|
Returns:
|
|
List of yearly snapshots compatible with YearlySnapshot interface
|
|
"""
|
|
timeline = []
|
|
current_year = 2025 # TODO: Make this dynamic
|
|
asset_balance = current_assets
|
|
|
|
life_events = life_events or []
|
|
|
|
for age in range(current_age, life_expectancy + 1):
|
|
year = current_year + (age - current_age)
|
|
|
|
# Adjust income (stops at retirement)
|
|
income = annual_income if age < retirement_age else 0
|
|
|
|
# Apply inflation to expenses
|
|
years_from_now = age - current_age
|
|
inflated_expenses = annual_expenses * math.pow(1 + self.inflation_rate, years_from_now)
|
|
|
|
# Check for life events
|
|
event_cost = self._get_event_cost(year, life_events)
|
|
total_expenses = inflated_expenses + event_cost
|
|
|
|
# Calculate net cash flow
|
|
net_cash_flow = income - total_expenses
|
|
|
|
# Update asset balance
|
|
if asset_balance >= 0:
|
|
# Positive balance: apply investment return
|
|
asset_balance = asset_balance * (1 + self.investment_return) + net_cash_flow
|
|
else:
|
|
# Negative balance: apply deficit interest (compound debt)
|
|
asset_balance = asset_balance * (1 + self.deficit_interest_rate) + net_cash_flow
|
|
|
|
# Create snapshot
|
|
snapshot = {
|
|
"year": year,
|
|
"ages": {
|
|
"rep": age,
|
|
"spouse": None, # TODO: Add spouse calculation
|
|
},
|
|
"financials": {
|
|
"income": round(income, 0),
|
|
"expenditure": round(total_expenses, 0),
|
|
"assetBalance": round(asset_balance, 0),
|
|
},
|
|
"events": self._get_event_labels(year, life_events),
|
|
"isCritical": asset_balance < 0,
|
|
}
|
|
|
|
timeline.append(snapshot)
|
|
|
|
return timeline
|
|
|
|
def _get_event_cost(self, year: int, life_events: List[Dict]) -> float:
|
|
"""Get total event cost for a specific year"""
|
|
total = 0
|
|
for event in life_events:
|
|
if event.get("year") == year:
|
|
total += event.get("amount", 0)
|
|
return total
|
|
|
|
def _get_event_labels(self, year: int, life_events: List[Dict]) -> List[str]:
|
|
"""Get event labels for a specific year"""
|
|
labels = []
|
|
for event in life_events:
|
|
if event.get("year") == year:
|
|
labels.append(event.get("label", "Event"))
|
|
return labels
|
|
|
|
def build_cashflow_data(self, timeline: List[Dict]) -> List[Dict]:
|
|
"""
|
|
Convert timeline to CashFlow chart format
|
|
|
|
Args:
|
|
timeline: YearlySnapshot format timeline
|
|
|
|
Returns:
|
|
List compatible with CashFlowData interface
|
|
"""
|
|
cashflow = []
|
|
|
|
for snapshot in timeline:
|
|
cashflow.append({
|
|
"year": snapshot["year"],
|
|
"age": snapshot["ages"]["rep"],
|
|
"income": {
|
|
"total": snapshot["financials"]["income"],
|
|
},
|
|
"expenses": {
|
|
"living": snapshot["financials"]["expenditure"] * 0.4, # Mock breakdown
|
|
"housing": snapshot["financials"]["expenditure"] * 0.3,
|
|
"education": snapshot["financials"]["expenditure"] * 0.15,
|
|
"tax": snapshot["financials"]["expenditure"] * 0.1,
|
|
"event": snapshot["financials"]["expenditure"] * 0.05,
|
|
},
|
|
"totalExpense": snapshot["financials"]["expenditure"],
|
|
"netCashFlow": snapshot["financials"]["income"] - snapshot["financials"]["expenditure"],
|
|
"assetBalance": snapshot["financials"]["assetBalance"],
|
|
})
|
|
|
|
return cashflow
|
|
|
|
|
|
def build_chart_data(pdf_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""
|
|
Build chart data from PDF extraction results
|
|
|
|
Args:
|
|
pdf_data: Structured data from PDF extraction
|
|
|
|
Returns:
|
|
Dictionary with 'assetDepletion' and 'cashFlow' chart data
|
|
"""
|
|
simulator = FinancialSimulator()
|
|
|
|
# Extract parameters from PDF data
|
|
current_age = pdf_data.get("current_age", 35)
|
|
retirement_age = pdf_data.get("retirement_age", 65)
|
|
life_expectancy = pdf_data.get("life_expectancy", 100)
|
|
current_assets = pdf_data.get("current_assets", 5000)
|
|
annual_income = pdf_data.get("annual_income", 600)
|
|
annual_expenses = pdf_data.get("annual_expenses", 450)
|
|
life_events = pdf_data.get("life_events", [])
|
|
|
|
# Simulate timeline
|
|
timeline = simulator.simulate_timeline(
|
|
current_age=current_age,
|
|
retirement_age=retirement_age,
|
|
life_expectancy=life_expectancy,
|
|
current_assets=current_assets,
|
|
annual_income=annual_income,
|
|
annual_expenses=annual_expenses,
|
|
life_events=life_events,
|
|
)
|
|
|
|
# Build cashflow data
|
|
cashflow = simulator.build_cashflow_data(timeline)
|
|
|
|
return {
|
|
"assetDepletion": {
|
|
"timeline": timeline,
|
|
"meta": {
|
|
"currencyUnit": "万円",
|
|
"startYear": timeline[0]["year"] if timeline else 2025,
|
|
},
|
|
},
|
|
"cashFlow": {
|
|
"timeline": cashflow,
|
|
"meta": {
|
|
"currencyUnit": "万円",
|
|
},
|
|
},
|
|
}
|