1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-24 23:59:40 +02:00

Add Money and Money Series (#505)

* Add Money class

* Standardize creation of money series

* Formatting

* Fix test
This commit is contained in:
Zach Gollwitzer 2024-03-01 17:17:34 -05:00 committed by GitHub
parent 89ea12e9a1
commit 0fe9b6d34a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 228 additions and 161 deletions

View file

@ -28,29 +28,17 @@ class Account < ApplicationRecord
end
def balance_series(period)
filtered_balances = balances.in_period(period).order(:date)
return nil if filtered_balances.empty?
series_data = [ nil, *filtered_balances ].each_cons(2).map do |previous, current|
trend = current&.trend(previous)
{ data: current, trend: { amount: trend&.amount, direction: trend&.direction, percent: trend&.percent } }
end
last_balance = series_data.last[:data]
{
series_data: series_data,
last_balance: last_balance.balance,
trend: last_balance.trend(series_data.first[:data])
}
MoneySeries.new(
balances.in_period(period).order(:date),
{ trend_type: classification }
)
end
def valuation_series
series_data = [ nil, *valuations.order(:date) ].each_cons(2).map do |previous, current|
{ value: current, trend: current&.trend(previous) }
end
series_data.reverse_each
MoneySeries.new(
valuations.order(:date),
{ trend_type: classification, amount_accessor: :value }
)
end
def check_currency

32
app/models/money.rb Normal file
View file

@ -0,0 +1,32 @@
class Money
attr_reader :amount, :currency
def self.from_amount(amount, currency = "USD")
Money.new(amount, currency)
end
def initialize(amount, currency = :USD)
@amount = amount
@currency = currency
end
def cents(precision: nil)
_precision = precision || CURRENCY_OPTIONS[@currency.to_sym][:precision]
return "" unless _precision.positive?
fractional_part = @amount.to_s.split(".")[1] || ""
fractional_part = fractional_part[0, _precision].ljust(_precision, "0")
end
def symbol
CURRENCY_OPTIONS[@currency.to_sym][:symbol]
end
def separator
CURRENCY_OPTIONS[@currency.to_sym][:separator]
end
def precision
CURRENCY_OPTIONS[@currency.to_sym][:precision]
end
end

View file

@ -0,0 +1,60 @@
class MoneySeries
def initialize(series, options = {})
@trend_type = options[:trend_type] || :asset # Defines whether a positive trend is good or bad
@accessor = options[:amount_accessor] || :balance
@series = series
end
def valid?
@series.length > 1
end
def data
[ nil, *@series ].each_cons(2).map do |previous, current|
{
raw: current,
date: current.date,
value: Money.from_amount(current.send(@accessor), current.currency),
trend: Trend.new(
current: current.send(@accessor),
previous: previous&.send(@accessor),
type: @trend_type
)
}
end
end
def trend
return Trend.new(current: 0, type: @trend_type) unless valid?
Trend.new(
current: @series.last.send(@accessor),
previous: @series.first&.send(@accessor),
type: @trend_type
)
end
def serialize_for_d3_chart
{
data: data.map do |datum|
{
date: datum[:date],
amount: datum[:value].amount,
currency: datum[:value].currency,
trend: {
amount: datum[:trend].amount,
percent: datum[:trend].percent,
direction: datum[:trend].direction,
type: datum[:trend].type
}
}
end,
trend: {
amount: trend.amount,
percent: trend.percent,
direction: trend.direction,
type: trend.type
}
}.to_json
end
end

View file

@ -1,26 +1,27 @@
class Trend
attr_reader :current, :previous
attr_reader :current, :previous, :type
def initialize(current, previous)
@current = current
@previous = previous
end
def initialize(current:, previous: nil, type: :asset)
@current = current
@previous = previous
@type = type # :asset means positive trend is good, :liability means negative trend is good
end
def direction
return "flat" unless @previous
return "up" if @current > @previous
return "down" if @current < @previous
"flat"
end
def direction
return "flat" unless @previous
return "up" if @current > @previous
return "down" if @current < @previous
"flat"
end
def amount
return 0 if @previous.nil?
@current - @previous
end
def amount
return 0 if @previous.nil?
@current - @previous
end
def percent
return 0 if @previous.nil?
return Float::INFINITY if @previous == 0
((@current - @previous).abs / @previous.to_f * 100).round(1)
end
def percent
return 0 if @previous.nil?
return Float::INFINITY if @previous == 0
((@current - @previous).abs / @previous.to_f * 100).round(1)
end
end

View file

@ -4,7 +4,7 @@ class Valuation < ApplicationRecord
after_commit :sync_account
def trend(previous)
Trend.new(value, previous&.value)
Trend.new(current: value, previous: previous&.value, type: account.classification)
end
private