* Fix SimpleFIN inverting Loan account balances
SimplefinAccount::Processor#process_account! routes every liability
through OverpaymentAnalyzer + normalize_liability_balance. That path
is built around credit-like liabilities, where transaction history
distinguishes debt vs. credit. For a Loan account with only the
opening anchor (no payment history), the analyzer returns :unknown
and the fallback negates the observed value:
def normalize_liability_balance(observed, bal, avail)
...
-observed
end
That's wrong for loans: the bank reports the principal outstanding
as a positive number from its own books. Negating it stores the loan
balance as negative, so BalanceSheet#net_worth = assets - liabilities
ends up _adding_ the loan instead of subtracting it (off by 2× the
loan amount). Example with a hypothetical mortgage:
raw_balance = 100000.00 (positive — bank's own report)
Sure stored = -100000.00 (negated by the fallback)
Net worth shown = inflated by 2 × 100000
Short-circuit Loan accountables straight to observed.abs and skip the
analyzer/fallback entirely. Loans don't have credit-vs-debt
ambiguity — if the loan is paid off the balance is 0, not negative.
Credit cards still go through the existing heuristic.
* Add observability for the SimpleFIN loan sign branch
Mirrors the logging + Sentry breadcrumb the credit-card branches emit
when the OverpaymentAnalyzer classifies as :credit / :debt, so the
loan short-circuit shows up in production traces too. Per CodeRabbit
review on #1574.
* Test that positive bank-reported loan balances are preserved
The existing "inverts negative balance for loan liabilities" test only
covers a bank that reports the loan as negative — both the old (buggy)
fallback and the new short-circuit produce the same +50000 there, which
is why the inversion bug went undetected. Add a sibling test where the
bank reports +50000 (the common mortgage convention); under the old
code that became -50000 and inflated net worth.
* Redact monetary amounts from SimpleFIN liability info logs
Move raw observed/stored amounts and metric totals from `Rails.logger.info`
and `Sentry.add_breadcrumb` payloads to a `Rails.logger.debug` line.
The info-level message and breadcrumb data now carry only identifiers
(`sfa_id`) plus the classification (`loan` / `credit` / `debt` /
`unknown`) and `tx_count`, so log aggregators and Sentry no longer
receive raw monetary values for any of the four liability branches.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Added a new test to validate how dormant credit cards with zero balance and negative available balance are processed.
- Updated processor logic to ensure `current_balance` takes precedence when explicitly set to zero, preventing incorrect usage of `available_balance`.
Co-authored-by: Josh Waldrep <joshua.waldrep5+github@gmail.com>
* Add liability balance normalization logic with comprehensive tests
- Updated `SimplefinAccount::Processor` to normalize liability balances based on observed values, ensuring correct handling of debts and overpayments.
- Enhanced `SimplefinItem::Importer` to apply similar normalization rules during imports, improving consistency.
- Added multiple test cases in `SimplefinAccountProcessorTest` to validate edge cases for liabilities and mixed-sign scenarios.
- Introduced helper methods (`to_decimal`, `same_sign?`) to simplify numeric operations in normalization logic.
* Add overpayment detection for liabilities with heuristic-based classification
- Introduced `SimplefinAccount::Liabilities::OverpaymentAnalyzer` to classify liability balances as credit, debt, or unknown using transaction history.
- Updated `SimplefinAccount::Processor` and `SimplefinItem::Importer` to integrate heuristic-based balance normalization with fallback logic for ambiguous cases.
- Added comprehensive unit tests in `OverpaymentAnalyzerTest` to validate classification logic and edge cases.
- Enhanced logging and observability around classification results and fallback scenarios.
* Refactor liability handling for better fallback consistency
- Updated `sticky_key` method in `OverpaymentAnalyzer` to handle missing `@sfa.id` with a default value.
- Enhanced `SimplefinAccount::Processor` to use `with_indifferent_access` for `raw_payload` and `org_data`, improving robustness in liability type inference.
* Extract numeric helper methods into `SimplefinNumericHelpers` concern and apply across models
- Moved `to_decimal` and `same_sign?` methods into a new `SimplefinNumericHelpers` concern for reuse.
- Updated `OverpaymentAnalyzer`, `Processor`, and `Importer` to include the concern and remove redundant method definitions.
- Added empty fixtures for `simplefin_accounts` and `simplefin_items` to ensure test isolation.
- Refactored `OverpaymentAnalyzerTest` to reduce fixture dependencies and ensure cleanup of created records.
* Refactor overpayment detection logic for clarity and fallback consistency
- Simplified `enabled?` method in `OverpaymentAnalyzer` for clearer precedence order (Setting > ENV > default).
- Added `parse_bool` helper to streamline boolean parsing.
- Enhanced error handling with detailed logging for transaction gathering failures.
- Improved `sticky_key` method to use a temporary object ID fallback when `@sfa.id` is missing.
---------
Co-authored-by: Josh Waldrep <joshua.waldrep5+github@gmail.com>
* Add tests and logic for Simplefin account balance normalization
- Introduced `SimplefinAccountProcessorTest` to verify balance normalization logic.
- Updated `SimplefinAccount::Processor` to invert negative balances for liability accounts (credit cards and loans) while keeping asset balances unchanged.
- Added comments to clarify balance conventions and sign normalization rules.
* Refactor balances-only sync logic and improve tests for edge cases
- Updated `SimplefinItem::Importer` and `SimplefinItem::Syncer` to ensure `last_synced_at` remains nil during balances-only runs, preserving chunked-history behavior for full syncs.
- Introduced additional comments to clarify balances-only implications and syncing logic.
- Added test case in `SimplefinAccountProcessorTest` to verify correct handling of overpayment for credit card liabilities.
- Refined balance normalization in `SimplefinAccount::Processor` to always invert liability balances for consistency.
---------
Co-authored-by: Josh Waldrep <joshua.waldrep5+github@gmail.com>