1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-08-05 05:25:24 +02:00

Additional cache columns on balances for activity view breakdowns (#2505)

* Initial schema iteration

* Add new balance components

* Add existing data migrator to backfill components

* Update calculator test assertions for new balance components

* Update flow assertions for forward calculator

* Update reverse calculator flows assumptions

* Forward calculator tests passing

* Get all calculator tests passing

* Assert flows factor
This commit is contained in:
Zach Gollwitzer 2025-07-23 10:06:25 -04:00 committed by GitHub
parent 347c0a7906
commit da2045dbd8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1159 additions and 177 deletions

View file

@ -0,0 +1,72 @@
class AddStartEndColumnsToBalances < ActiveRecord::Migration[7.2]
def up
# Add new columns for balance tracking
add_column :balances, :start_cash_balance, :decimal, precision: 19, scale: 4, null: false, default: 0.0
add_column :balances, :start_non_cash_balance, :decimal, precision: 19, scale: 4, null: false, default: 0.0
# Flow tracking columns (absolute values)
add_column :balances, :cash_inflows, :decimal, precision: 19, scale: 4, null: false, default: 0.0
add_column :balances, :cash_outflows, :decimal, precision: 19, scale: 4, null: false, default: 0.0
add_column :balances, :non_cash_inflows, :decimal, precision: 19, scale: 4, null: false, default: 0.0
add_column :balances, :non_cash_outflows, :decimal, precision: 19, scale: 4, null: false, default: 0.0
# Market value changes
add_column :balances, :net_market_flows, :decimal, precision: 19, scale: 4, null: false, default: 0.0
# Manual adjustments from valuations
add_column :balances, :cash_adjustments, :decimal, precision: 19, scale: 4, null: false, default: 0.0
add_column :balances, :non_cash_adjustments, :decimal, precision: 19, scale: 4, null: false, default: 0.0
# Flows factor determines *how* the flows affect the balance.
# Inflows increase asset accounts, while inflows decrease liability accounts (reducing debt via "payment")
add_column :balances, :flows_factor, :integer, null: false, default: 1
# Add generated columns
change_table :balances do |t|
t.virtual :start_balance, type: :decimal, precision: 19, scale: 4, stored: true,
as: "start_cash_balance + start_non_cash_balance"
t.virtual :end_cash_balance, type: :decimal, precision: 19, scale: 4, stored: true,
as: "start_cash_balance + ((cash_inflows - cash_outflows) * flows_factor) + cash_adjustments"
t.virtual :end_non_cash_balance, type: :decimal, precision: 19, scale: 4, stored: true,
as: "start_non_cash_balance + ((non_cash_inflows - non_cash_outflows) * flows_factor) + net_market_flows + non_cash_adjustments"
# Postgres doesn't support generated columns depending on other generated columns,
# but we want the integrity of the data to happen at the DB level, so this is the full formula.
# Formula: (cash components) + (non-cash components)
t.virtual :end_balance, type: :decimal, precision: 19, scale: 4, stored: true,
as: <<~SQL.squish
(
start_cash_balance +
((cash_inflows - cash_outflows) * flows_factor) +
cash_adjustments
) + (
start_non_cash_balance +
((non_cash_inflows - non_cash_outflows) * flows_factor) +
net_market_flows +
non_cash_adjustments
)
SQL
end
end
def down
# Remove generated columns first (PostgreSQL requirement)
remove_column :balances, :start_balance
remove_column :balances, :end_cash_balance
remove_column :balances, :end_non_cash_balance
remove_column :balances, :end_balance
# Remove new columns
remove_column :balances, :start_cash_balance
remove_column :balances, :start_non_cash_balance
remove_column :balances, :cash_inflows
remove_column :balances, :cash_outflows
remove_column :balances, :non_cash_inflows
remove_column :balances, :non_cash_outflows
remove_column :balances, :net_market_flows
remove_column :balances, :cash_adjustments
remove_column :balances, :non_cash_adjustments
end
end

16
db/schema.rb generated
View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.2].define(version: 2025_07_18_120146) do
ActiveRecord::Schema[7.2].define(version: 2025_07_19_121103) do
# These are extensions that must be enabled in order to support this database
enable_extension "pgcrypto"
enable_extension "plpgsql"
@ -115,6 +115,20 @@ ActiveRecord::Schema[7.2].define(version: 2025_07_18_120146) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.decimal "cash_balance", precision: 19, scale: 4, default: "0.0"
t.decimal "start_cash_balance", precision: 19, scale: 4, default: "0.0", null: false
t.decimal "start_non_cash_balance", precision: 19, scale: 4, default: "0.0", null: false
t.decimal "cash_inflows", precision: 19, scale: 4, default: "0.0", null: false
t.decimal "cash_outflows", precision: 19, scale: 4, default: "0.0", null: false
t.decimal "non_cash_inflows", precision: 19, scale: 4, default: "0.0", null: false
t.decimal "non_cash_outflows", precision: 19, scale: 4, default: "0.0", null: false
t.decimal "net_market_flows", precision: 19, scale: 4, default: "0.0", null: false
t.decimal "cash_adjustments", precision: 19, scale: 4, default: "0.0", null: false
t.decimal "non_cash_adjustments", precision: 19, scale: 4, default: "0.0", null: false
t.integer "flows_factor", default: 1, null: false
t.virtual "start_balance", type: :decimal, precision: 19, scale: 4, as: "(start_cash_balance + start_non_cash_balance)", stored: true
t.virtual "end_cash_balance", type: :decimal, precision: 19, scale: 4, as: "((start_cash_balance + ((cash_inflows - cash_outflows) * (flows_factor)::numeric)) + cash_adjustments)", stored: true
t.virtual "end_non_cash_balance", type: :decimal, precision: 19, scale: 4, as: "(((start_non_cash_balance + ((non_cash_inflows - non_cash_outflows) * (flows_factor)::numeric)) + net_market_flows) + non_cash_adjustments)", stored: true
t.virtual "end_balance", type: :decimal, precision: 19, scale: 4, as: "(((start_cash_balance + ((cash_inflows - cash_outflows) * (flows_factor)::numeric)) + cash_adjustments) + (((start_non_cash_balance + ((non_cash_inflows - non_cash_outflows) * (flows_factor)::numeric)) + net_market_flows) + non_cash_adjustments))", stored: true
t.index ["account_id", "date", "currency"], name: "index_account_balances_on_account_id_date_currency_unique", unique: true
t.index ["account_id", "date"], name: "index_balances_on_account_id_and_date", order: { date: :desc }
t.index ["account_id"], name: "index_balances_on_account_id"