-
Notifications
You must be signed in to change notification settings - Fork 3
ADR 019 Discretionary Funding
Accepted
The budget system tracks financing sources (bank loans, credit lines, savings) and links them to budget lines via budget_source_id. However, two categories of spending fall outside this model:
- Invoice remainders -- when an invoice total exceeds the sum of its itemized budget line amounts, the difference is unaccounted for by any financing source.
-
Unassigned budget lines -- budget lines with
budget_source_id = NULLhave no financing source attribution.
Without a way to represent these amounts, the budget overview cannot fully reconcile total project spending against financing sources. Users see a gap between total costs and the sum of source-attributed amounts.
-
Virtual computed row (no DB row): Generate a "Discretionary" entry at the API layer without storing it. This avoids schema changes but makes it impossible to treat the discretionary source consistently in queries, sorting, and UI -- it becomes a special case everywhere.
-
Separate
discretionary_amountfield on budget overview: Add a dedicated field to the overview response. This is simpler but means the discretionary concept only exists in the overview, not in the budget sources list where users expect to see all funding sources. -
System-managed sentinel row (chosen): Seed a real
budget_sourcesrow with adiscretionarysource type andis_discretionaryflag. This lets the discretionary source appear naturally in the budget sources list, use the same response shape, and participate in sorting and filtering without special-casing the API contract.
Introduce a system-managed "Discretionary Funding" sentinel row in the budget_sources table:
-
Migration 0021 adds an
is_discretionary INTEGER NOT NULL DEFAULT 0column and expands thesource_typeCHECK constraint to include'discretionary'. - The migration seeds a row with id
discretionary-system, source_typediscretionary, total_amount0, andis_discretionary = 1. - The
discretionarysource type is not available in create or update schemas -- only the system seed creates it. - The discretionary source cannot be deleted (returns 409
DISCRETIONARY_SOURCE) and itssourceTypecannot be changed. - Its
totalAmountis always 0. All meaningful amounts (usedAmount,paidAmount,projectedAmount, etc.) are computed at the API layer from invoice remainders and NULL-source budget lines. - In the budget sources list response, non-discretionary sources sort alphabetically first, with the discretionary source appearing last.
The BudgetSourceResponse type gains three new fields:
-
paidAmount: number-- sum of paid invoice amounts linked to this source's budget lines -
projectedAmount: number-- blended projection using confidence margins -
isDiscretionary: boolean-- identifies the system sentinel row
Easier:
- Budget overview can fully reconcile all spending against financing sources
- The discretionary source uses the same API shape as all other sources -- no special-casing in the frontend
- Sorting places the discretionary source last, giving it a clear "catch-all" visual position
Harder:
- Delete and update operations must guard against modifying the discretionary source
- The
is_discretionaryflag introduces a concept of "system-managed" rows, which is new to the schema - Future migrations must preserve the sentinel row (the
WHERE NOT EXISTSguard in migration 0021 handles re-runs)