feat(balance): Preserve historical balances as waypoints for linked accounts#1663
feat(balance): Preserve historical balances as waypoints for linked accounts#1663CrossDrain wants to merge 13 commits intowe-promise:mainfrom
Conversation
…napTrade, Plaid) When updating a linked account's balance, the previous day's current_anchor is now preserved as a reconciliation valuation before being replaced. This creates a chain of API-reported balance waypoints over time. The ReverseCalculator has been updated to treat these reconciliation valuations as reset points during reverse syncs, ensuring historical balances accurately reflect the known API-reported values even with incomplete transaction history.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughLinked-account balance updates now convert a previous-day ChangesReconciliation Waypoint Preservation & Consumption
Sequence DiagramsequenceDiagram
actor User
participant LinkedAcct as Linked Account
participant CurrBalMgr as CurrentBalanceManager
participant Valuations as Valuations/Entries
participant RevCalc as ReverseCalculator
participant SyncCache as SyncCache
User->>LinkedAcct: set_current_balance(new_amount, today)
LinkedAcct->>CurrBalMgr: set_current_balance_for_linked_account()
activate CurrBalMgr
CurrBalMgr->>Valuations: load current_anchor (if any)
alt stale previous-day current_anchor exists
CurrBalMgr->>Valuations: update valuation.kind -> "reconciliation"
CurrBalMgr->>Valuations: update entry.name via Valuation.build_reconciliation_name(...)
CurrBalMgr->>CurrBalMgr: clear memoized `@current_anchor_valuation`
end
CurrBalMgr->>Valuations: create or update today's current_anchor (clear memoized anchor after create)
deactivate CurrBalMgr
User->>RevCalc: calculate(date_range)
activate RevCalc
loop For each date (oldest -> newest)
RevCalc->>SyncCache: get_valuation(date)
SyncCache-->>RevCalc: valuation (maybe)
alt reconciliation valuation present
RevCalc->>RevCalc: set end balances from valuation.amount
RevCalc->>RevCalc: set start balances = end balances
RevCalc->>RevCalc: set market_value_change = 0
else
RevCalc->>RevCalc: use opening anchor or derive from prior balances
end
end
deactivate RevCalc
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related issues
Possibly related PRs
Suggested labels
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: d31c2f7a99
ℹ️ 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".
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
app/models/account/current_balance_manager.rb (1)
99-111:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMake stale-anchor rotation and new-anchor creation atomic.
Line 102 can convert the old
current_anchortoreconciliation, but Lines 109-110 create the replacement anchor outside that same transaction. If creation fails, the account can be left without anycurrent_anchor.Proposed fix
def set_current_balance_for_linked_account(balance) - preserve_anchor_as_reconciliation_if_stale if current_anchor_valuation - - # Re-check: the memoized value was cleared if the anchor was converted - if current_anchor_valuation - changes_made = update_current_anchor(balance) - Result.new(success?: true, changes_made?: changes_made, error: nil) - else - create_current_anchor(balance) - Result.new(success?: true, changes_made?: true, error: nil) - end + ActiveRecord::Base.transaction do + preserve_anchor_as_reconciliation_if_stale if current_anchor_valuation + + # Re-check: the memoized value was cleared if the anchor was converted + if current_anchor_valuation + changes_made = update_current_anchor(balance) + Result.new(success?: true, changes_made?: changes_made, error: nil) + else + create_current_anchor(balance) + Result.new(success?: true, changes_made?: true, error: nil) + end + end endAlso applies to: 123-136
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/models/account/current_balance_manager.rb` around lines 99 - 111, The stale-anchor rotation and new-anchor creation must be made atomic: wrap the preserve_anchor_as_reconciliation_if_stale call and the subsequent create_current_anchor/update_current_anchor calls inside a single database transaction so that conversion and replacement happen together or are rolled back on error; modify set_current_balance_for_linked_account to start a transaction around the branch that may call preserve_anchor_as_reconciliation_if_stale and then call create_current_anchor or update_current_anchor within that transaction, returning Result only after commit, and apply the same transactional fix to the analogous block referenced later (the other method handling anchor rotation/creation).app/models/balance/reverse_calculator.rb (1)
15-42:⚠️ Potential issue | 🟠 Major | ⚡ Quick winLimit waypoint reset to reconciliation valuations only.
Line 27 currently resets balances for any valuation returned by
sync_cache.get_valuation(date). Since that lookup can return non-reconciliation valuations too, this can bypass normal flow reversal on those dates.Proposed fix
- valuation = sync_cache.get_valuation(date) + valuation = sync_cache.get_valuation(date) + reconciliation_waypoint = valuation if valuation&.entryable&.reconciliation? @@ - elsif valuation + elsif reconciliation_waypoint @@ - total_balance: valuation.amount, + total_balance: reconciliation_waypoint.amount, date: date ) - end_non_cash_balance = valuation.amount - end_cash_balance + end_non_cash_balance = reconciliation_waypoint.amount - end_cash_balance🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/models/balance/reverse_calculator.rb` around lines 15 - 42, The current branch treats any valuation returned by sync_cache.get_valuation(date) as a reconciliation waypoint and resets balances; change it to only reset when the valuation is actually a reconciliation. Update the elsif block to check a reconciliation marker on the valuation (e.g., if valuation.reconciliation? or valuation.reconciliation == true) before performing the reset logic in reverse_calculator.rb (the block using derive_cash_balance_on_date_from_total, end_cash_balance/end_non_cash_balance, start_cash_balance/start_non_cash_balance, and market_value_change). If the valuation is present but not a reconciliation, fall through to the existing else (normal flow reversal) instead of resetting.test/models/balance/reverse_calculator_test.rb (1)
31-76: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick winAdd a regression case for current-day flows alongside a reconciliation waypoint.
This scenario validates the waypoint reset well, but it does not protect against accidentally skipping current-day transaction/trade flow reversal when a
current_anchorvaluation is present onDate.current(Line 34 setup path). A focused case with a same-day transaction would close that gap.Suggested test shape
+ test "reconciliation waypoint does not suppress current-day flow reversal" do + account = create_account_with_ledger( + account: { type: Depository, balance: 20000, cash_balance: 20000, currency: "USD" }, + entries: [ + { type: "current_anchor", date: Date.current, balance: 20000 }, + { type: "transaction", date: Date.current, amount: 500 }, + { type: "reconciliation", date: 2.days.ago, balance: 17000 }, + { type: "opening_anchor", date: 4.days.ago, balance: 15000 } + ] + ) + + calculated = Balance::ReverseCalculator.new(account).calculate + # Assert Date.current start/end reflect reversal of today's transaction. + end🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/models/balance/reverse_calculator_test.rb` around lines 31 - 76, Add a regression test to the existing "reconciliation valuations act as waypoints..." test suite that ensures same-day (Date.current) transaction/trade flows are processed/reversed even when a current_anchor valuation exists; update or create a new test that uses create_account_with_ledger to include a current_anchor at Date.current, a reconciliation waypoint earlier (e.g., 2.days.ago), and also inject a transaction/trade entry dated Date.current (or a flow affecting that day) so that Balance::ReverseCalculator#calculate must account for that flow; assert expected results with assert_calculated_ledger_balances ensuring the Date.current ledger shows the flow effect (not ignored) while earlier dates still reset to the reconciliation waypoint.
🧹 Nitpick comments (1)
test/models/account/current_balance_manager_test.rb (1)
234-234: ⚡ Quick winAvoid hard-coded reconciliation label in assertion.
Use the name builder instead of a literal string so this test stays stable if label text changes.
Proposed fix
- assert_equal "Manual balance update", preserved_valuation.entry.name + assert_equal Valuation.build_reconciliation_name(`@linked_account.accountable_type`), preserved_valuation.entry.name🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/models/account/current_balance_manager_test.rb` at line 234, The assertion uses a hard-coded label string; replace the literal "Manual balance update" with the canonical name builder used by the reconciliation code so the test follows production naming logic—specifically, change the expectation on preserved_valuation.entry.name to call the reconciliation name builder (the same builder used when creating the entry in production) instead of the literal string so the test remains stable when label text changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/models/account/current_balance_manager.rb`:
- Line 130: The info-level log in Rails.logger.info currently includes sensitive
monetary data (entry.amount); remove the amount from the info payload and log
only non-sensitive identifiers (e.g., account.id and entry.date) in the
current_balance_manager.rb code path where the conversion to reconciliation is
logged; if the amount is needed for troubleshooting, either log it at debug
level or log a redacted/boolean indicator (e.g., "has_amount=true") instead of
entry.amount, and update the log call that references entry.amount accordingly.
---
Outside diff comments:
In `@app/models/account/current_balance_manager.rb`:
- Around line 99-111: The stale-anchor rotation and new-anchor creation must be
made atomic: wrap the preserve_anchor_as_reconciliation_if_stale call and the
subsequent create_current_anchor/update_current_anchor calls inside a single
database transaction so that conversion and replacement happen together or are
rolled back on error; modify set_current_balance_for_linked_account to start a
transaction around the branch that may call
preserve_anchor_as_reconciliation_if_stale and then call create_current_anchor
or update_current_anchor within that transaction, returning Result only after
commit, and apply the same transactional fix to the analogous block referenced
later (the other method handling anchor rotation/creation).
In `@app/models/balance/reverse_calculator.rb`:
- Around line 15-42: The current branch treats any valuation returned by
sync_cache.get_valuation(date) as a reconciliation waypoint and resets balances;
change it to only reset when the valuation is actually a reconciliation. Update
the elsif block to check a reconciliation marker on the valuation (e.g., if
valuation.reconciliation? or valuation.reconciliation == true) before performing
the reset logic in reverse_calculator.rb (the block using
derive_cash_balance_on_date_from_total, end_cash_balance/end_non_cash_balance,
start_cash_balance/start_non_cash_balance, and market_value_change). If the
valuation is present but not a reconciliation, fall through to the existing else
(normal flow reversal) instead of resetting.
In `@test/models/balance/reverse_calculator_test.rb`:
- Around line 31-76: Add a regression test to the existing "reconciliation
valuations act as waypoints..." test suite that ensures same-day (Date.current)
transaction/trade flows are processed/reversed even when a current_anchor
valuation exists; update or create a new test that uses
create_account_with_ledger to include a current_anchor at Date.current, a
reconciliation waypoint earlier (e.g., 2.days.ago), and also inject a
transaction/trade entry dated Date.current (or a flow affecting that day) so
that Balance::ReverseCalculator#calculate must account for that flow; assert
expected results with assert_calculated_ledger_balances ensuring the
Date.current ledger shows the flow effect (not ignored) while earlier dates
still reset to the reconciliation waypoint.
---
Nitpick comments:
In `@test/models/account/current_balance_manager_test.rb`:
- Line 234: The assertion uses a hard-coded label string; replace the literal
"Manual balance update" with the canonical name builder used by the
reconciliation code so the test follows production naming logic—specifically,
change the expectation on preserved_valuation.entry.name to call the
reconciliation name builder (the same builder used when creating the entry in
production) instead of the literal string so the test remains stable when label
text changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 775ce475-0f7a-4068-95fa-d142af60911d
📒 Files selected for processing (4)
app/models/account/current_balance_manager.rbapp/models/balance/reverse_calculator.rbtest/models/account/current_balance_manager_test.rbtest/models/balance/reverse_calculator_test.rb
The ReverseCalculator was incorrectly treating the current_anchor valuation (on Date.current) as a reconciliation waypoint, causing it to reset the balance and ignore same-day transactions. This fix adds a check to ensure only true reconciliation entries (entryable.reconciliation?) trigger the reset behavior. Additionally, set_current_balance_for_linked_account is now wrapped in a database transaction to ensure atomicity when preserving stale anchors and creating/updating the current anchor. Logging has been improved to use debug level for amount details. A regression test was added to verify that same-day flows are correctly processed when a current_anchor exists on the current date.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
test/models/account/current_balance_manager_test.rb (1)
230-234: ⚡ Quick winAdd a date assertion to fully validate historical preservation of the converted anchor.
The test verifies
kind,amount, andnameon the preserved reconciliation, but omits theentry.date. The entire motivation of this feature is that the anchor is preserved at its original historical date — if production code ever inadvertently updated the date during conversion, no assertion here would catch it. Inside thetravel_toblock,Date.yesterdayrefers to the original creation date, making this a one-liner.✅ Proposed addition
preserved_valuation = Valuation.find(original_id) assert_equal "reconciliation", preserved_valuation.kind assert_equal 1000, preserved_valuation.entry.amount assert_equal Valuation.build_reconciliation_name(`@linked_account.accountable_type`), preserved_valuation.entry.name + assert_equal Date.yesterday, preserved_valuation.entry.date🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/models/account/current_balance_manager_test.rb` around lines 230 - 234, The test currently checks preserved_valuation.kind/amount/name but misses verifying the preserved entry date; inside the travel_to block add an assertion that preserved_valuation.entry.date equals Date.yesterday to ensure the converted anchor kept its original historical date (locate preserved_valuation from Valuation.find(original_id) and assert on preserved_valuation.entry.date).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@test/models/account/current_balance_manager_test.rb`:
- Around line 230-234: The test currently checks
preserved_valuation.kind/amount/name but misses verifying the preserved entry
date; inside the travel_to block add an assertion that
preserved_valuation.entry.date equals Date.yesterday to ensure the converted
anchor kept its original historical date (locate preserved_valuation from
Valuation.find(original_id) and assert on preserved_valuation.entry.date).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: bc0c8428-7ea5-4a40-8b96-d312825b038b
📒 Files selected for processing (4)
app/models/account/current_balance_manager.rbapp/models/balance/reverse_calculator.rbtest/models/account/current_balance_manager_test.rbtest/models/balance/reverse_calculator_test.rb
✅ Files skipped from review due to trivial changes (1)
- app/models/balance/reverse_calculator.rb
🚧 Files skipped from review as they are similar to previous changes (2)
- test/models/balance/reverse_calculator_test.rb
- app/models/account/current_balance_manager.rb
Add validation that valuation entries created during balance preservation are dated as of yesterday. This prevents future-dated entries and maintains temporal accuracy in financial snapshots.
|
Solid approach and well-documented. A few things worth discussing before merge: Behavior reversal for existing user-created reconciliation valuations Redundant nested transaction in Debug log leaks balance amount Rails.logger.debug("[AnchorRotation] ... amount=#{entry.amount}")
Misplaced comment indentation The waypoint concept and the two-level tests (rotation + reverse calculator) are well-designed and the comprehensive test coverage is appreciated. Generated by Claude Code |
…t in current balance manager
|
I've pushed updates to address Claude's feedback: (1dbcc0b)
Regarding the existing user-created reconciliations on linked accounts—the UI actually hides the "Update balance" button for linked accounts, so users can't actively create manual reconciliations for them. That said, what do you think about accounts that were originally created as manual accounts (and had manual reconciliations added) but were later connected to an institution? In that scenario, treating those historical manual valuations as hard-reset waypoints seems like the right call since they represent confirmed, user-verified balances from before the account was linked. Does that perspective make sense to you, or do you think we should handle those specific edge cases differently? |
|
Can you review, @sokie? 🙏 |
|
Following up on CrossDrain's open question about accounts that migrated from manual to linked: the reasoning is solid. A reconciliation valuation on a previously-manual account represents a user-confirmed balance snapshot, and treating it as a hard-reset waypoint is exactly the right semantic. The only inherent limitation is that if the user originally entered an incorrect value, the waypoint will anchor the error — but that's not addressable at this layer, and the UI already allows editing or deleting reconciliation entries if that happens. One additional test scenario worth adding to increase confidence: multiple accumulated waypoints (e.g., 3–5 consecutive daily syncs creating a chain of reconciliation waypoints). The current tests cover the single-waypoint case in the reverse calculator. A multi-waypoint fixture would confirm that the calculator correctly threads through each waypoint in sequence — resetting independently at each one — without unexpected interactions near the Minor: Generated by Claude Code |
…ceManager and add regression test for consecutive reconciliation waypoints
There was a problem hiding this comment.
🧹 Nitpick comments (2)
app/models/account/current_balance_manager.rb (2)
134-134: 💤 Low value
has_amountflag adds no signal — consider dropping it.
entry.amountis aNumeric/BigDecimal, and Rails'Numeric#present?istruefor any value including0. Since a persisted valuation entry effectively always has a non-nil amount,has_amountwill betruein virtually every log line and conveys no diagnostic value. For traceability,account.id+dateare already sufficient; if you want a stable join key for ops, preferentry_id/valuation_id.Proposed simplification
- Rails.logger.info("[AnchorRotation] Converted current_anchor to reconciliation for account #{account.id}, date=#{entry.date}, has_amount=#{entry.amount.present?}") + Rails.logger.info("[AnchorRotation] Converted current_anchor to reconciliation for account #{account.id}, entry_id=#{entry.id}, date=#{entry.date}")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/models/account/current_balance_manager.rb` at line 134, The log line in current_balance_manager.rb that calls Rails.logger.info with "[AnchorRotation] Converted current_anchor to reconciliation for account #{account.id}, date=#{entry.date}, has_amount=#{entry.amount.present?}" includes a redundant has_amount flag because entry.amount.present? is always true for persisted numeric/BigDecimal values; remove the has_amount fragment or replace it with a stable identifier like entry.id (or valuation_id) for traceability. Locate the Rails.logger.info call (in the CurrentBalanceManager / anchor rotation conversion code) and update the message to omit has_amount or append "entry_id=#{entry.id}" (or "valuation_id=...") instead, keeping account.id and entry.date intact.
155-176: 💤 Low valueInner
ActiveRecord::Base.transactionis now redundant.When
update_current_anchoris called fromset_current_balance_for_linked_account(Line 109), it already runs inside the outer transaction opened on Line 102, so the inner block creates an unnecessary savepoint. This mirrors the inner-transaction concern that was already addressed inpreserve_anchor_as_reconciliation_if_stale. Since this method only mutates a singleentry, the outer transaction is sufficient.Proposed simplification
def update_current_anchor(balance) changes_made = false - ActiveRecord::Base.transaction do - # Update associated entry attributes - entry = current_anchor_valuation.entry + # Update associated entry attributes + entry = current_anchor_valuation.entry - if entry.amount != balance - entry.amount = balance - changes_made = true - end + if entry.amount != balance + entry.amount = balance + changes_made = true + end - if entry.date != Date.current - entry.date = Date.current - changes_made = true - end + if entry.date != Date.current + entry.date = Date.current + changes_made = true + end - entry.save! if entry.changed? - end + entry.save! if entry.changed? changes_made endNote: if
update_current_anchoris ever called outside the linked-account flow, the outer transaction still applies becauseset_current_balanceis the only public entry point and it always wraps via the linked path; otherwise the singleentry.save!is itself atomic.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/models/account/current_balance_manager.rb` around lines 155 - 176, Remove the redundant inner ActiveRecord::Base.transaction block inside update_current_anchor: delete the transaction do/end wrapper and leave the existing logic that updates entry (via current_anchor_valuation.entry), sets entry.amount and entry.date as needed, and calls entry.save! if entry.changed?; this relies on the outer transaction started by set_current_balance_for_linked_account and mirrors the earlier change made in preserve_anchor_as_reconciliation_if_stale to avoid unnecessary savepoints.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@app/models/account/current_balance_manager.rb`:
- Line 134: The log line in current_balance_manager.rb that calls
Rails.logger.info with "[AnchorRotation] Converted current_anchor to
reconciliation for account #{account.id}, date=#{entry.date},
has_amount=#{entry.amount.present?}" includes a redundant has_amount flag
because entry.amount.present? is always true for persisted numeric/BigDecimal
values; remove the has_amount fragment or replace it with a stable identifier
like entry.id (or valuation_id) for traceability. Locate the Rails.logger.info
call (in the CurrentBalanceManager / anchor rotation conversion code) and update
the message to omit has_amount or append "entry_id=#{entry.id}" (or
"valuation_id=...") instead, keeping account.id and entry.date intact.
- Around line 155-176: Remove the redundant inner ActiveRecord::Base.transaction
block inside update_current_anchor: delete the transaction do/end wrapper and
leave the existing logic that updates entry (via
current_anchor_valuation.entry), sets entry.amount and entry.date as needed, and
calls entry.save! if entry.changed?; this relies on the outer transaction
started by set_current_balance_for_linked_account and mirrors the earlier change
made in preserve_anchor_as_reconciliation_if_stale to avoid unnecessary
savepoints.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ee06f244-070f-4bd1-a6ce-eb3ec4860777
📒 Files selected for processing (2)
app/models/account/current_balance_manager.rbtest/models/balance/reverse_calculator_test.rb
|
All the points have been addressed in the last commit (57c9f9f):
|
Overview
Resolves the issue where linked accounts (e.g., Plaid, SnapTrade) lose their historical balance data over time. This PR fixes #1492, fixes #1484.
Previously,
CurrentBalanceManagerdestructively overwrote the singlecurrent_anchoron every sync. Because the reverse calculator had only today's balance to work backward from, any gaps or inaccuracies in the transaction history led to drifted, incorrect historical balances and missing data on the Net Worth chart.This PR implements an "anchor rotation" system to gracefully accumulate daily API-reported balances and uses them as rigid waypoints to correct historical balance drift.
Changes Made
CurrentBalanceManager): Before overwriting acurrent_anchor, we now check if it belongs to a previous day. If so, it is preserved by converting its kind toreconciliation(named "Manual balance update"), and a freshcurrent_anchoris laid down for today.ReverseCalculator): Updated the reverse calculation loop to actively search forreconciliationvaluations. When it hits a waypoint during its reverse walk, it trusts the API-reported valuation and hard-resets the balance, neutralizing any drift caused by missing historical transactions.Rails.logger.infohook inside the conversion block to easily track anchor rotations in production.Architectural Note
The original implementation plan (suggested in #1492) assumed that simply converting the anchors to reconciliations was sufficient to preserve history. However,
Balance::Materializerpurges stale records and recalculates them on sync. Without theReverseCalculatorupdate included in this PR, the system would have ignored the preserved data entirely and overwritten the correct history with drifted transaction flows.Summary by CodeRabbit
Bug Fixes
Tests