mirror of
https://github.com/we-promise/sure.git
synced 2026-04-17 19:14:11 +00:00
Implement holdings for lunch flow (#590)
* Implement holdings for lunch flow * Implement holdings function call
This commit is contained in:
167
app/models/assistant/function/get_holdings.rb
Normal file
167
app/models/assistant/function/get_holdings.rb
Normal file
@@ -0,0 +1,167 @@
|
||||
class Assistant::Function::GetHoldings < Assistant::Function
|
||||
include Pagy::Backend
|
||||
|
||||
SUPPORTED_ACCOUNT_TYPES = %w[Investment Crypto].freeze
|
||||
|
||||
class << self
|
||||
def default_page_size
|
||||
50
|
||||
end
|
||||
|
||||
def name
|
||||
"get_holdings"
|
||||
end
|
||||
|
||||
def description
|
||||
<<~INSTRUCTIONS
|
||||
Use this to search user's investment holdings by using various optional filters.
|
||||
|
||||
This function is great for things like:
|
||||
- Finding specific holdings or securities
|
||||
- Getting portfolio composition and allocation
|
||||
- Viewing investment performance and cost basis
|
||||
|
||||
Note: This function only returns holdings from Investment and Crypto accounts.
|
||||
|
||||
Note on pagination:
|
||||
|
||||
This function can be paginated. You can expect the following properties in the response:
|
||||
|
||||
- `total_pages`: The total number of pages of results
|
||||
- `page`: The current page of results
|
||||
- `page_size`: The number of results per page (this will always be #{default_page_size})
|
||||
- `total_results`: The total number of results for the given filters
|
||||
- `total_value`: The total value of all holdings for the given filters
|
||||
|
||||
Simple example (all current holdings):
|
||||
|
||||
```
|
||||
get_holdings({
|
||||
page: 1
|
||||
})
|
||||
```
|
||||
|
||||
More complex example (various filters):
|
||||
|
||||
```
|
||||
get_holdings({
|
||||
page: 1,
|
||||
accounts: ["Brokerage Account"],
|
||||
securities: ["AAPL", "GOOGL"]
|
||||
})
|
||||
```
|
||||
INSTRUCTIONS
|
||||
end
|
||||
end
|
||||
|
||||
def strict_mode?
|
||||
false
|
||||
end
|
||||
|
||||
def params_schema
|
||||
build_schema(
|
||||
required: [ "page" ],
|
||||
properties: {
|
||||
page: {
|
||||
type: "integer",
|
||||
description: "Page number"
|
||||
},
|
||||
accounts: {
|
||||
type: "array",
|
||||
description: "Filter holdings by account name (only Investment and Crypto accounts are supported)",
|
||||
items: { enum: investment_account_names },
|
||||
minItems: 1,
|
||||
uniqueItems: true
|
||||
},
|
||||
securities: {
|
||||
type: "array",
|
||||
description: "Filter holdings by security ticker symbol",
|
||||
items: { enum: family_security_tickers },
|
||||
minItems: 1,
|
||||
uniqueItems: true
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def call(params = {})
|
||||
holdings_query = build_holdings_query(params)
|
||||
|
||||
pagy, paginated_holdings = pagy(
|
||||
holdings_query.includes(:security, :account).order(amount: :desc),
|
||||
page: params["page"] || 1,
|
||||
limit: default_page_size
|
||||
)
|
||||
|
||||
total_value = holdings_query.sum(:amount)
|
||||
|
||||
normalized_holdings = paginated_holdings.map do |holding|
|
||||
{
|
||||
ticker: holding.ticker,
|
||||
name: holding.name,
|
||||
quantity: holding.qty.to_f,
|
||||
price: holding.price.to_f,
|
||||
currency: holding.currency,
|
||||
amount: holding.amount.to_f,
|
||||
formatted_amount: holding.amount_money.format,
|
||||
weight: holding.weight&.round(2),
|
||||
average_cost: holding.avg_cost.to_f,
|
||||
formatted_average_cost: holding.avg_cost.format,
|
||||
account: holding.account.name,
|
||||
date: holding.date
|
||||
}
|
||||
end
|
||||
|
||||
{
|
||||
holdings: normalized_holdings,
|
||||
total_results: pagy.count,
|
||||
page: pagy.page,
|
||||
page_size: default_page_size,
|
||||
total_pages: pagy.pages,
|
||||
total_value: Money.new(total_value, family.currency).format
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
def default_page_size
|
||||
self.class.default_page_size
|
||||
end
|
||||
|
||||
def build_holdings_query(params)
|
||||
accounts = investment_accounts
|
||||
|
||||
if params["accounts"].present?
|
||||
accounts = accounts.where(name: params["accounts"])
|
||||
end
|
||||
|
||||
holdings = Holding.where(account: accounts)
|
||||
.where(
|
||||
id: Holding.where(account: accounts)
|
||||
.select("DISTINCT ON (account_id, security_id) id")
|
||||
.where.not(qty: 0)
|
||||
.order(:account_id, :security_id, date: :desc)
|
||||
)
|
||||
|
||||
if params["securities"].present?
|
||||
security_ids = family.securities.where(ticker: params["securities"]).pluck(:id)
|
||||
holdings = holdings.where(security_id: security_ids)
|
||||
end
|
||||
|
||||
holdings
|
||||
end
|
||||
|
||||
def investment_accounts
|
||||
family.accounts.visible.where(accountable_type: SUPPORTED_ACCOUNT_TYPES)
|
||||
end
|
||||
|
||||
def investment_account_names
|
||||
@investment_account_names ||= investment_accounts.pluck(:name)
|
||||
end
|
||||
|
||||
def family_security_tickers
|
||||
@family_security_tickers ||= Security
|
||||
.where(id: Holding.where(account_id: investment_accounts.select(:id)).select(:security_id))
|
||||
.distinct
|
||||
.pluck(:ticker)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user