Skip to content

Add income tracking (Phase 1) - #43

Merged
derekl-beep merged 3 commits into
mainfrom
claude/income-tracking
Jul 8, 2026
Merged

Add income tracking (Phase 1)#43
derekl-beep merged 3 commits into
mainfrom
claude/income-tracking

Conversation

@derekl-beep

Copy link
Copy Markdown
Owner

Summary

Adds income tracking as a parallel feature to expenses, so pasted bank-statement credit lines (payroll, interest, cashback, e-transfers) can be logged via chat instead of only being ignored or miscategorized as expenses.

This is Phase 1: add-via-chat + read-only list. Reimbursement is a category, not a linked relationship yet — matching a reimbursement to the specific expense it repays (e.g. a friend paying back their share of a dinner you logged) is a separate Phase 2, deliberately out of scope here so this could ship as a focused, reviewable slice.

  • Backend: new income table (mirrors expenses, minus split/flag/edit/delete), save_income/get_income tools, GET /income, a closed INCOME_CATEGORIES list (Salary, Interest, Rebate, Reimbursement, Transfer, Gift, Other), and SYSTEM prompt rules for classifying pasted credit/deposit lines as income vs expense.
  • Frontend: an Expenses/Income segmented toggle inside the existing Expenses pane (not a third top-level tab, so it stays out of the way on mobile), a read-only income list (mobile cards + desktop table) with a running total, refetched after every chat turn so a chat-driven save_income shows up without a reload.

Review

Ran this repo's adversarial code-review pass (8 independent finder angles + verification) against both the backend and frontend diffs before committing. Confirmed and fixed:

  • Id-collision risk: income.id and expenses.id are separate sequences that can collide, and nothing stopped the model from routing an income id into delete_expense/update_expense. The SYSTEM prompt now explicitly forbids this and tells the model to say edit/delete isn't supported for income yet.
  • Ambiguous category example: the Reimbursement vs. Transfer prompt examples overlapped ("a roommate's share of a bill" fit both). Reworded so Reimbursement strictly requires a matching expense already logged in the tracker.
  • Missing dedup guidance: the existing overlapping-screenshot dedup instruction only covered expenses; extended it to pasted income lines too, since income has no DB-level duplicate detection.
  • Small duplication cleanups: extracted a shared description-capitalization helper, added the missing description trigram index on income (mirrors the existing expense index; get_income's description_contains filter needs it).
  • Also fixed a gap outside the two target files: the Vite dev proxy was never updated for /income, which would have made the feature silently non-functional in local dev.

One angle (get_income duplicating get_expenses's query-builder almost line-for-line) and one (user_id injection via a hardcoded tuple in agent/main.py) were deliberately left as-is — both match a pre-existing pattern already used elsewhere in this codebase, and fixing them would mean refactoring working, already-tested expense code beyond this feature's scope.

Test plan

  • uv run pytest tests/ — 130 passed
  • npm run lint — clean
  • npm run build — clean
  • npx playwright test e2e/income.spec.js — 2/2 (desktop + mobile)
  • Full regression check: npx playwright test e2e/expenses.spec.js e2e/insights.spec.js — 18/18, no regressions
  • Live-verified via Playwright screenshots at 1440x900 and 390x844 (toggle, totals, empty state, category badge fallback for the new income categories)

Roadmap / future work

  • Phase 2: link a Reimbursement income row to the specific expense it repays (linked_expense_id), net-reimbursed badge on the expense row, and a net cash-flow (income − expenses) summary.
  • Income category badges (Salary, Rebate, etc.) currently fall back to a plain unstyled badge since CATEGORY_ICONS/CATEGORY_COLORS only cover expense categories — cosmetic, not blocking, worth a follow-up if income-specific styling is wanted.

Generated by Claude Code

claude added 3 commits July 8, 2026 14:20
Adds an income table, save_income/get_income tools, and a GET /income
endpoint, mirroring the existing expense feature but without
split/flag/edit/delete. The SYSTEM prompt now classifies pasted
bank-statement credit lines as income vs expense, with a closed
INCOME_CATEGORIES list (Salary, Interest, Rebate, Reimbursement,
Transfer, Gift, Other). Reimbursement is a category only for now —
linking a reimbursement to the expense it repays is Phase 2.

Also closes a couple of gaps an adversarial review pass found: income
and expense ids can collide, so the prompt now explicitly forbids
routing income ids into update_expense/delete_expense; the pasted-text
dedup instruction now covers income lines, not just expenses; and the
Reimbursement vs Transfer examples no longer overlap.
Adds an Expenses/Income segmented toggle to ExpenseTable's toolbar,
backed by a new fetchIncome() in App.jsx that mirrors fetchExpenses().
The income view is read-only for this phase: no add/edit/delete, no
filters — just a chronological list (mobile cards + desktop table)
with a running total, refetched after every chat turn so a
chat-driven save_income shows up without a reload.

Also adds the missing /income entry to the Vite dev proxy — the
backend commit added GET /income but never wired the dev server to
proxy it, which would have made the feature silently non-functional
outside of production.
Seeds two income rows (Salary/Payroll Deposit, Rebate/Cashback Reward)
for the e2e test user, and adds income.spec.js covering the
Expenses/Income toggle, the income total, and that expense-only
filter controls stay hidden in the income view.
@derekl-beep
derekl-beep merged commit 53a607b into main Jul 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants