From 67e570590567580705aa118d9b748ebf8968ea17 Mon Sep 17 00:00:00 2001 From: Zach Gollwitzer Date: Tue, 20 May 2025 13:52:36 -0400 Subject: [PATCH] Finish account processor --- app/models/account.rb | 6 -- app/models/concerns/enrichable.rb | 6 +- .../investment_balance_processor.rb | 17 +++++ app/models/plaid_account/processor.rb | 64 ++++++++++++++++++- 4 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 app/models/plaid_account/investment_balance_processor.rb diff --git a/app/models/account.rb b/app/models/account.rb index a610171d..4984fb89 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -73,12 +73,6 @@ class Account < ApplicationRecord end end - def set_name(name) - if enrichable?(:name) - self.name = name - end - end - def institution_domain url_string = plaid_account&.plaid_item&.institution_url return nil unless url_string.present? diff --git a/app/models/concerns/enrichable.rb b/app/models/concerns/enrichable.rb index 4c373b01..64937a40 100644 --- a/app/models/concerns/enrichable.rb +++ b/app/models/concerns/enrichable.rb @@ -34,7 +34,11 @@ module Enrichable ActiveRecord::Base.transaction do enrichable_attrs.each do |attr, value| self.send("#{attr}=", value) - log_enrichment(attribute_name: attr, attribute_value: value, source: source, metadata: metadata) + + # If it's a new record, this isn't technically an "enrichment". No logging necessary. + unless self.new_record? + log_enrichment(attribute_name: attr, attribute_value: value, source: source, metadata: metadata) + end end save diff --git a/app/models/plaid_account/investment_balance_processor.rb b/app/models/plaid_account/investment_balance_processor.rb new file mode 100644 index 00000000..d6b65ad0 --- /dev/null +++ b/app/models/plaid_account/investment_balance_processor.rb @@ -0,0 +1,17 @@ +# Plaid Investment balances have a ton of edge cases. This processor is responsible +# for deriving "brokerage cash" vs. "total value" based on Plaid's reported balances and holdings. +class PlaidAccount::InvestmentBalanceProcessor + attr_reader :plaid_account + + def initialize(plaid_account) + @plaid_account = plaid_account + end + + def balance + plaid_account.current_balance || plaid_account.available_balance + end + + def cash_balance + plaid_account.available_balance || 0 + end +end diff --git a/app/models/plaid_account/processor.rb b/app/models/plaid_account/processor.rb index 31c8b8d3..0cf8e026 100644 --- a/app/models/plaid_account/processor.rb +++ b/app/models/plaid_account/processor.rb @@ -1,6 +1,17 @@ class PlaidAccount::Processor attr_reader :plaid_account + UnknownAccountTypeError = Class.new(StandardError) + + # Plaid Account Types -> Accountable Types + TYPE_MAPPING = { + "depository" => Depository, + "credit" => CreditCard, + "loan" => Loan, + "investment" => Investment, + "other" => OtherAsset + } + def initialize(plaid_account) @plaid_account = plaid_account end @@ -11,12 +22,59 @@ class PlaidAccount::Processor plaid_account_id: plaid_account.id ) - account.set_name(plaid_account.name) + # Name is the only attribute a user can override for Plaid accounts + account.enrich_attribute( + :name, + plaid_account.name, + source: "plaid" + ) + + account.assign_attributes( + accountable: accountable, + balance: balance, + currency: plaid_account.currency, + cash_balance: cash_balance + ) + + account.save! end + + PlaidAccount::TransactionsProcessor.new(plaid_account).process + PlaidAccount::InvestmentsProcessor.new(plaid_account).process end - private + private def family plaid_account.plaid_item.family end -end \ No newline at end of file + + def accountable + accountable_class = TYPE_MAPPING[plaid_account.plaid_type] + + raise UnknownAccountTypeError, "Unknown account type: #{plaid_account.plaid_type}" unless accountable_class + + accountable_class.new + end + + def balance + case plaid_account.plaid_type + when "investment" + investment_balance_processor.balance + else + plaid_account.current_balance || plaid_account.available_balance + end + end + + def cash_balance + case plaid_account.plaid_type + when "investment" + investment_balance_processor.cash_balance + else + plaid_account.available_balance || 0 + end + end + + def investment_balance_processor + PlaidAccount::InvestmentBalanceProcessor.new(plaid_account) + end +end