fix(family): include HSA depository accounts in tax-advantaged exclusion#2004
Conversation
`Family#tax_advantaged_account_ids` is the ID set the budget engine uses to exclude tax-advantaged account activity from income / expense / cashflow totals. PR we-promise#724 originated this method and explicitly listed HSA in scope ("401k, IRA, HSA, Roth IRA, etc."), but the implementation only joined `investments` and `cryptos`. `Depository::SUBTYPES["hsa"]` already exists and Plaid routes `depository.hsa` accounts to `Depository` (not `Investment`) via `PlaidAccount::TypeMappable`, so HSA cash accounts were silently absent from the filter and HSA contributions/withdrawals showed up in household expense totals. - Add `Depository::TAX_ADVANTAGED_SUBTYPES = %w[hsa]` + a `tax_treatment` instance method (mirrors `Investment#tax_treatment`). `TaxTreatable#tax_advantaged?` picks it up via the existing `respond_to?` check, so `Account#tax_advantaged?` now flips to true for HSA depositories without touching the concern. - Extract `Family#tax_advantaged_depository_account_ids` (private) that joins `depositories` and filters by `Depository::TAX_ADVANTAGED_SUBTYPES`, mirroring the existing `investment_ids` / `crypto_ids` extraction style. Append it to the union in `tax_advantaged_account_ids`. Behavior change is scoped: HSA depositories now exit the budget engine via the same path as 401k / IRA / Roth IRA. Non-HSA depositories continue to report `tax_treatment: :taxable` (was `nil`), so `Account#taxable?` returns true for them via the existing `== :taxable` clause — no expense-total change for Checking / Savings / CD / Money Market. Tests: - `test/models/account_test.rb` — rewrite "tax_treatment returns nil for non-investment accounts" (was implicitly testing the bug) into two tests: one asserting `:taxable` for non-HSA depositories and a new sibling asserting `nil` for accountables that genuinely lack `tax_treatment` (CreditCard). Add an HSA-depository test asserting `tax_advantaged?`. - `test/models/income_statement_test.rb` — new test asserting an HSA depository is included in `tax_advantaged_account_ids` and a `savings` depository is not. No schema migration, no controller change, no provider integration change.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughThis PR classifies HSA depositories as tax-advantaged by adding ChangesHSA Depository Tax Treatment and Family Filtering
Sequence DiagramsequenceDiagram
participant Family
participant Accounts
participant Depository
Family->>Accounts: tax_advantaged_account_ids (joins depositories)
Accounts->>Depository: where(subtype IN Depository::TAX_ADVANTAGED_SUBTYPES)
Depository-->>Accounts: matching account ids
Accounts-->>Family: return depository account ids
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8e5368da24
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| # adding it here transparently flips `Account#tax_advantaged?` for HSA | ||
| # depositories without touching the concern itself. | ||
| def tax_treatment | ||
| TAX_ADVANTAGED_SUBTYPES.include?(subtype) ? :tax_advantaged : :taxable |
There was a problem hiding this comment.
Avoid marking every depository account as taxable
For every non-HSA Depository (checking, savings, CD, money market), this now returns :taxable instead of nil. The account header renders the tax badge whenever account.tax_treatment.present? (app/views/accounts/show/_header.html.erb), so ordinary bank account pages will now show a “Taxable” pill even though tax treatment was previously only surfaced for account types that supported it. This affects the common non-HSA depository path; consider keeping non-HSA depositories nil (or gating the badge) while still treating hsa as tax-advantaged.
Useful? React with 👍 / 👎.
jjmata
left a comment
There was a problem hiding this comment.
Well-researched fix that closes a real gap — PR #724 listed HSA in scope but the implementation only covered investments and cryptos, and Plaid's depository.hsa routing made this silently wrong for real users.
A few observations:
Design is consistent with the existing pattern. Depository#tax_treatment mirrors Investment#tax_treatment and the cryptos.tax_treatment enum, and TaxTreatable's respond_to? check picks it up without touching the concern. The private tax_advantaged_depository_account_ids query mirrors the investment_ids/crypto_ids extraction style — easy to read and follow.
Memoization is preserved correctly. The new private method is called inside the @tax_advantaged_account_ids ||= begin...end block, so the extra query is incurred at most once per family object lifetime.
Tests cover both inclusion and exclusion paths, including the fixture checking account — the @family.instance_variable_set(:@tax_advantaged_account_ids, nil) cache-bust in the income statement test is the right way to handle this.
One small comment on comment verbosity: The private method has a 5-line explanatory comment. Per the project's convention (CLAUDE.md), comments should only appear when the WHY is non-obvious. The method name tax_advantaged_depository_account_ids plus the Depository::TAX_ADVANTAGED_SUBTYPES constant make the intent clear; the comment about "why extracted rather than inlined" is more of a PR-description thought than a code comment. Not a blocker.
No schema change, no API contract change, tests pass the inclusion/exclusion contract — looks good to me.
Generated by Claude Code
luckyPipewrench
left a comment
There was a problem hiding this comment.
The tax_advantaged_account_ids fix is correct and the tests are great. But there is a UI change.
Depository#tax_treatment now returns :taxable for non-HSA subtypes where it used to return nil. The header renders the tax badge on account.tax_treatment.present? (app/views/accounts/show/_header.html.erb:15), so every checking, savings, CD, and money-market account now shows a "Taxable" pill it never had before.
I traced the rest: that badge is the only unintended change. taxable? and tax_advantaged? have no callers in app/ or lib/, so the HSA exclusion itself is clean. Cleanest fix is to return nil for non-HSA depositories. That keeps hsa in the filter and drops the new pills on ordinary bank accounts.
Looks good otherwise.
updated according to your feedback. |
Summary
Family#tax_advantaged_account_idsinapp/models/family.rb:243-263is the ID set the budget engine uses to exclude tax-advantaged account activity from income / expense / cashflow totals. PR #724 originated this method and explicitly listed HSA as an in-scope account type ("401k, IRA, HSA, Roth IRA, etc."), but the implementation only joinedinvestmentsandcryptos.Depository::SUBTYPES["hsa"]already exists (app/models/depository.rb:9) and Plaid routesdepository.hsaaccounts toDepository(notInvestment) viaPlaidAccount::TypeMappable(app/models/plaid_account/type_mappable.rb:35), so a real-world Plaid-linked HSA cash account was silently absent from the filter and HSA contributions / withdrawals showed up in household expense totals — exactly the noise PR #724 was designed to suppress.The symmetric gap on
Account#tax_advantaged?(in theTaxTreatableconcern) returnedfalsefor HSA depositories becauseDepository#respond_to?(:tax_treatment)wasfalse.Fixes #2003.
Fix
app/models/depository.rb— addTAX_ADVANTAGED_SUBTYPES = %w[hsa].freezeconstant + atax_treatmentinstance method. MirrorsInvestment#tax_treatment(SUBTYPES.dig(subtype, :tax_treatment) || :taxable) and thecryptos.tax_treatmentenum. TheTaxTreatableconcern picks the new method up via its existingrespond_to?check, soAccount#tax_advantaged?flips totruefor HSA depositories without touching the concern.app/models/family.rb— add a privatetax_advantaged_depository_account_idsmethod that joinsdepositoriesand filters byDepository::TAX_ADVANTAGED_SUBTYPES, mirroring the existinginvestment_ids/crypto_idsextraction style. Append it to the union intax_advantaged_account_ids. Memoization preserved.Behavior change scope
HSA depositories now exit the budget engine via the same path as 401k / IRA / Roth IRA —
IncomeStatement::Totals,FamilyStats,CategoryStats, andTransaction::Searchconsume the corrected ID set transparently.Non-HSA depositories (Checking, Savings, CD, Money Market) now report
tax_treatment: :taxable(previouslynil).Account#taxable?continues to returntruefor them via the existingtax_treatment == :taxableclause inTaxTreatable— no expense-total change, no UI change.Tests
test/models/account_test.rb— rewrite the existingtax_treatment returns nil for non-investment accountstest (it was implicitly testing the bug) into two tests: one asserting:taxablefor non-HSA depositories, and a new sibling assertingnilfor accountables that genuinely lacktax_treatment(using a CreditCard). Add a newtax_advantaged? returns true for HSA depository accountstest.test/models/income_statement_test.rb— new testfamily.tax_advantaged_account_ids includes HSA depository accounts and excludes non-HSA depositoriesasserting both inclusion and exclusion paths against an HSA depository and asavingsdepository created on the fixture family.Files changed
app/models/depository.rb(+15)app/models/family.rb(+13 / -1)test/models/account_test.rb(+41 / -5)test/models/income_statement_test.rb(+31)No schema migration, no controller change, no provider integration change, no view edit, no API contract change.
Related
Family#tax_advantaged_account_ids. Body listed HSA in scope; the implementation handled Investment + Crypto branches but missed Depository HSA. This PR completes what Exclude tax-advantaged account activity from budget & add provider data quality warnings #724 announced.Investment::SUBTYPES.tax_treatmentmetadata that PR Exclude tax-advantaged account activity from budget & add provider data quality warnings #724 keys off. Same shape thatDepositorywas missing.fix(reports): exclude investment_contribution from expense totals (#1750)— adjacent in spirit, different code path (modifiesBUDGET_EXCLUDED_KINDSandinvestment_contributionTransaction kind handling, nottax_advantaged_account_ids). Test-file overlap ontest/models/income_statement_test.rbonly — different line regions, trivial rebase if needed.app/models/plaid_account/type_mappable.rb:35/:68— the upstream mapping that routesdepository.hsatoDepository, making this bug fire for real Plaid users.Summary by CodeRabbit
New Features
Tests