Skip to content

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:

  1. Projected revenue — the trailing-N-month average of non-void invoice totals. Default N = 3.
  2. Committed fixed costs — the sum of every RecurringExpense occurrence 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: disposableAmount formatted as currency, with disposablePct as 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, hide disposablePct and show "Not enough sales history yet."
  • A drilldown view should list the contributing RecurringExpense rows so the owner can pause one to free budget.

Edge cases

  • No invoices in lookbackprojectedRevenue = 0, disposablePct = 0. disposableAmount will 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.
  • occurrencesRemaining running out mid-period → only the remaining occurrences count.