Disposable budget¶
Backend domain: app/graphql/budgets/services/disposable_budget_service.py
RBAC Path: DASHBOARD
Migration: none (computed from existing tables)
Overview¶
The disposable-budget KPI answers the owner question "after my fixed costs, how much do I have left to spend?" by combining two pieces of data already in the system:
- Projected revenue — the trailing-N-month average of non-void invoice totals. Default
N = 3. - Committed fixed costs — the sum of every
RecurringExpenseoccurrence projected to fire inside the target month.
disposableAmount = projectedRevenue − committedFixedCosts
disposablePct = disposableAmount / projectedRevenue × 100
The KPI is computed on demand from invoice_balances + recurring_expenses; no new tables.
GraphQL surface¶
query Disposable {
getDisposableBudget(
targetYear: 2026
targetMonth: 6
revenueLookbackMonths: 3
) {
periodStart
periodEnd
projectedRevenue
committedFixedCosts
disposableAmount
disposablePct
revenueLookbackMonths
}
}
All arguments are optional:
| Arg | Default | Notes |
|---|---|---|
targetYear |
current year | Use together with targetMonth. |
targetMonth |
current month (1–12) | Period is the whole calendar month. |
revenueLookbackMonths |
3 |
Must be ≥ 1. Higher = smoother. |
Projection logic¶
Revenue¶
For period start S, projected revenue is:
sum(invoice_balances.total_amount
for non-void invoices with invoice_date in [S − N months, S − 1 day])
/ N
Fixed costs¶
For each active RecurringExpense, the service walks nextRunDate forward using the same advance_next_run_date helper that powers the materializer. Every firing date that lands inside [periodStart, periodEnd] adds subtotalAmount + taxAmount − discountAmount to the total. The walk respects endDate and occurrencesRemaining and is capped at 366 steps to guard against pathological intervals.
This means the projection matches what will actually post if no schedule changes happen — it's not an estimate based on historical averages.
Frontend handoff notes¶
- Headline number on the owner dashboard:
disposableAmountformatted as currency, withdisposablePctas a sub-label ("32% of projected revenue"). - Color the card: green ≥ 25%, yellow 10–25%, red < 10%, neutral when
projectedRevenue = 0(new tenants with no invoice history). - Show a small "i" tooltip explaining: "Projected from last 3 months of invoices minus your committed recurring expenses for {month}."
- When
projectedRevenue = 0, hidedisposablePctand show "Not enough sales history yet." - A drilldown view should list the contributing
RecurringExpenserows so the owner can pause one to free budget.
Edge cases¶
- No invoices in lookback →
projectedRevenue = 0,disposablePct = 0.disposableAmountwill be negative (=−committedFixedCosts); display it as "Sin proyección de ingresos" instead of a literal negative number. - Inactive recurring expense → skipped entirely.
- Recurring expense with
endDate < periodStart→ contributes 0. occurrencesRemainingrunning out mid-period → only the remaining occurrences count.