Fix contribution compounding: monthly contributions are now added to asset values before each GBM step so they grow with market returns, rather than being summed as a static lump at each period. Add year-by-year milestone table below the fan chart showing P10/P50/P90 portfolio values at each annual checkpoint up to the selected horizon. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
129 lines
3.1 KiB
TypeScript
129 lines
3.1 KiB
TypeScript
import { api } from "./client";
|
|
|
|
export interface CategoryForecast {
|
|
category_id: string;
|
|
category_name: string;
|
|
monthly_avg: number;
|
|
algorithm: "sarima" | "holt_winters" | "average";
|
|
actuals: { date: string; amount: number }[];
|
|
forecast: { date: string; amount: number; lower: number; upper: number; lower_95: number; upper_95: number }[];
|
|
}
|
|
|
|
export interface SpendingForecastResponse {
|
|
categories: CategoryForecast[];
|
|
}
|
|
|
|
export interface NetWorthProjectionResponse {
|
|
history: { date: string; value: number }[];
|
|
projections: {
|
|
conservative: { date: string; value: number }[];
|
|
base: { date: string; value: number }[];
|
|
optimistic: { date: string; value: number }[];
|
|
};
|
|
insufficient_data: boolean;
|
|
}
|
|
|
|
export interface PercentilePath {
|
|
date: string;
|
|
value: number;
|
|
}
|
|
|
|
export interface MonteCarloMilestone {
|
|
year: number;
|
|
date: string;
|
|
p10: number;
|
|
p50: number;
|
|
p90: number;
|
|
}
|
|
|
|
export interface MonteCarloResponse {
|
|
dates: string[];
|
|
percentiles: {
|
|
p10: PercentilePath[];
|
|
p25: PercentilePath[];
|
|
p50: PercentilePath[];
|
|
p75: PercentilePath[];
|
|
p90: PercentilePath[];
|
|
};
|
|
current_value: number;
|
|
expected_value: number;
|
|
probability_of_gain: number;
|
|
milestones: MonteCarloMilestone[];
|
|
insufficient_data: boolean;
|
|
}
|
|
|
|
export interface BudgetForecastItem {
|
|
category_id: string;
|
|
category_name: string;
|
|
budget_amount: number;
|
|
spent_so_far: number;
|
|
forecast_month_total: number;
|
|
daily_velocity: number;
|
|
probability_overspend: number;
|
|
days_remaining: number;
|
|
}
|
|
|
|
export interface BudgetForecastResponse {
|
|
forecasts: BudgetForecastItem[];
|
|
message?: string;
|
|
}
|
|
|
|
export interface CashFlowDay {
|
|
date: string;
|
|
balance: number;
|
|
upper: number;
|
|
lower: number;
|
|
avg_inflow: number;
|
|
avg_outflow: number;
|
|
negative_risk: boolean;
|
|
}
|
|
|
|
export interface UpcomingPayment {
|
|
name: string;
|
|
date: string;
|
|
amount: number;
|
|
at_risk: boolean;
|
|
}
|
|
|
|
export interface CashFlowResponse {
|
|
current_balance: number;
|
|
avg_daily_inflow: number;
|
|
avg_daily_outflow: number;
|
|
forecast: CashFlowDay[];
|
|
negative_risk_days: string[];
|
|
upcoming_payments: UpcomingPayment[];
|
|
history_days: number;
|
|
}
|
|
|
|
export async function getSpendingForecast(): Promise<SpendingForecastResponse> {
|
|
const res = await api.get("/predictions/spending");
|
|
return res.data;
|
|
}
|
|
|
|
export async function getNetWorthProjection(years = 5): Promise<NetWorthProjectionResponse> {
|
|
const res = await api.get("/predictions/net-worth", { params: { years } });
|
|
return res.data;
|
|
}
|
|
|
|
export async function postMonteCarlo(params: {
|
|
years?: number;
|
|
n_simulations?: number;
|
|
annual_contribution?: number;
|
|
}): Promise<MonteCarloResponse> {
|
|
const res = await api.post("/predictions/monte-carlo", {
|
|
years: params.years ?? 5,
|
|
n_simulations: params.n_simulations ?? 1000,
|
|
annual_contribution: params.annual_contribution ?? 0,
|
|
});
|
|
return res.data;
|
|
}
|
|
|
|
export async function getBudgetForecast(): Promise<BudgetForecastResponse> {
|
|
const res = await api.get("/predictions/budget-forecast");
|
|
return res.data;
|
|
}
|
|
|
|
export async function getCashFlowForecast(): Promise<CashFlowResponse> {
|
|
const res = await api.get("/predictions/cashflow");
|
|
return res.data;
|
|
}
|