Add income tracking Phase 3: net budget math + income edit/delete - #46
Merged
Conversation
Closes the last two deferred pieces from the income tracking plan:
- get_budget_status now nets a category's spent total against any
income linked to its expenses via reimburses_expense_id, clipped at
0 per-expense so an over-reimbursed expense can't drag a category
negative. This differs deliberately from get_category_breakdown and
the other historical-spend tools, which still report gross amounts
actually paid - the SYSTEM prompt now explains the distinction so
the model doesn't get caught flat-footed if a user notices the two
don't match for the same transactions.
- update_income/delete_income (hard delete - income has no FK
dependents, unlike expenses) with matching tools and PATCH/DELETE
/income/{id} endpoints. update_income deliberately never touches
reimburses_expense_id, keeping that link_income_to_expense's
exclusive job.
- A "Net" cash-flow stat (all-time income minus expenses) next to the
existing Total in ExpenseTable's header.
Deliberately did NOT add a cash-flow chat tool/slash command - the
roadmap already flags the analytics command surface as possibly
over-built, so this stays UI-only.
Ran the same three-angle adversarial review as prior phases. Found
and fixed a "four tools" miscount in the SYSTEM prompt (five tools
are actually split by table now) and added the gross-vs-net budget
clarification above. Everything else checked out: parameterized
queries, no dangling references from the hard delete (nothing else
FKs to income.id), netCashFlow correctly recomputes after chat-driven
saves, and the unscoped-by-income-date netting in get_budget_status
is intentional - it ties the reimbursement to the expense's own
economics, not to whenever the income happened to get recorded,
consistent with how update_expense can already retroactively change
a "closed" month's numbers.
Verified: 150 backend tests, 68 e2e tests, lint, and build all green.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 3 of income tracking — closes the two pieces deliberately deferred out of Phase 2 (#45): net budget math for reimbursed expenses, and the ability to edit/delete income entries at all.
get_budget_status's "spent" figure now nets each expense against any income linked to it viareimburses_expense_id, clipped at 0 per-expense so an over-reimbursed expense can't drag a category negative (e.g. a $60 dinner with a $30 linked reimbursement now counts as $30 spent against the Dining budget, not $60).update_income/delete_income(hard delete — unlike expenses, nothing else referencesincome.idas a foreign key, so there's no soft-delete need), matching chat tools, andPATCH/DELETE /income/{id}endpoints mirroring the existing expense pattern.update_incomedeliberately never touchesreimburses_expense_id— that stayslink_income_to_expense's exclusive job, keeping the two tools non-overlapping.Deliberately not added: a
get_cash_flowchat tool or/cashflowslash command. This repo's own roadmap already flags the analytics tool/command surface as possibly over-built (worth assessing real usage before investing further), so the net figure stays UI-only rather than adding a 13th query tool.Review
Ran this repo's three-angle adversarial review pass (line-by-line, cross-file/removed-behavior, CLAUDE.md conventions) against the diff before committing. Confirmed: no SQL injection risk in the new dynamic queries, no dangling references from
delete_income's hard delete (nothing else FKs toincome.id, so a deleted reimbursement correctly and automatically un-reimburses its expense on the next read), the frontendnetCashFlowfigure correctly recomputes after any chat-driven save (bothincome/expensesprops refetch together), and the id-collision guidance introduced in earlier phases still holds without contradiction.Two real findings, both fixed:
update_expense,delete_expense,update_income,delete_income,link_income_to_expense) — miscount corrected.get_budget_status's netting is deliberately unscoped by the reimbursement's own date (it ties to the expense's month, regardless of when the income was recorded or linked) and deliberately differs fromget_category_breakdown/get_monthly_trend/etc., which still report gross amounts actually paid. Neither is a bug — the budget figure should reflect true net out-of-pocket cost, while historical-spend tools should reflect what was actually paid at the time — but nothing explained the distinction, so a user could get two different numbers for the same transactions with the model having no way to explain why. Added a paragraph to the SYSTEM prompt covering both points.Test plan
uv run pytest tests/— 150 passed (includes new tests for the netting math, over-reimbursement clipping,update_income/delete_income)npm run lint/npm run build— cleannpx playwright test e2e/cashflow.spec.js— 2/2 (Net figure correct and stable across the Expenses/Income toggle)npm run test:e2e— 68/68, no regressionsRoadmap / future work
Income tracking (Phases 1–3) is now feature-complete per the original plan. Nothing further currently tracked for this feature.
Generated by Claude Code