177 lines
6.6 KiB
Go
177 lines
6.6 KiB
Go
package domain
|
|
|
|
// Money is an exact decimal string bounded to signed 64-bit ten-thousandths.
|
|
type Money string
|
|
|
|
// Quantity is an exact decimal string bounded to signed 64-bit hundred-millionths.
|
|
// Broker share counts are fractional: savings plans and reinvested distributions
|
|
// settle in eight decimal places, which Money cannot represent.
|
|
type Quantity string
|
|
|
|
// Account kinds. An empty kind is a cash account: the field was added after the
|
|
// journal format, and absent means the original behaviour.
|
|
const (
|
|
AccountCash = "cash"
|
|
AccountInvestment = "investment"
|
|
)
|
|
|
|
type Account struct {
|
|
ID string `json:"id"`
|
|
DisplayName string `json:"display_name"`
|
|
Institution string `json:"institution"`
|
|
Currency string `json:"currency"`
|
|
// Kind is "cash" or "investment". An investment account also holds
|
|
// positions, and its facts never reach the sign-based classification
|
|
// fallback.
|
|
Kind string `json:"kind,omitempty"`
|
|
ExternalAccountID string `json:"external_account_id,omitempty"`
|
|
IBAN string `json:"iban,omitempty"`
|
|
// ReferenceIBAN is the counterpart this account settles cash against: a
|
|
// broker exports no counterparty column, so deposits and withdrawals carry
|
|
// this IBAN instead and pair with the funding account like any transfer.
|
|
ReferenceIBAN string `json:"reference_iban,omitempty"`
|
|
Active bool `json:"active"`
|
|
}
|
|
|
|
func (a Account) Investing() bool { return a.Kind == AccountInvestment }
|
|
|
|
// Investment events. Cash events move money only; buy, sell and reinvest move
|
|
// both money and position; corporate actions and position transfers move
|
|
// position only and must never touch cash.
|
|
const (
|
|
EventDeposit = "deposit"
|
|
EventWithdrawal = "withdrawal"
|
|
EventFee = "fee"
|
|
EventInterest = "interest"
|
|
EventDistribution = "distribution"
|
|
EventBuy = "buy"
|
|
EventSell = "sell"
|
|
EventReinvest = "reinvest"
|
|
EventCorporateAction = "corporate_action"
|
|
EventPositionTransfer = "position_transfer"
|
|
)
|
|
|
|
// Investment is the broker-native leg of an imported fact. Cash movement always
|
|
// stays in Facts.Amount, so a position-only event has a zero amount; Gross,
|
|
// Fee and Tax record the broker's own figures the amount was derived from.
|
|
//
|
|
// Quantity is signed: positive adds to the holding, negative removes it. The
|
|
// export signs corporate actions and position transfers in its share column but
|
|
// leaves buys and sells unsigned, so the sign is resolved at import, once.
|
|
type Investment struct {
|
|
Event string `json:"event"`
|
|
InstrumentID string `json:"instrument_id,omitempty"`
|
|
Quantity Quantity `json:"quantity,omitempty"`
|
|
Price Money `json:"price,omitempty"`
|
|
Gross Money `json:"gross,omitempty"`
|
|
Fee Money `json:"fee,omitempty"`
|
|
Tax Money `json:"tax,omitempty"`
|
|
}
|
|
|
|
// CashOnly reports an event that moves money without moving a position.
|
|
func (i Investment) CashOnly() bool {
|
|
switch i.Event {
|
|
case EventDeposit, EventWithdrawal, EventFee, EventInterest, EventDistribution:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// PositionOnly reports an event that moves a position without moving money.
|
|
func (i Investment) PositionOnly() bool {
|
|
return i.Event == EventCorporateAction || i.Event == EventPositionTransfer
|
|
}
|
|
|
|
// Settling reports an event that moves money and position together.
|
|
func (i Investment) Settling() bool {
|
|
switch i.Event {
|
|
case EventBuy, EventSell, EventReinvest:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Instrument is a security held in an investment account, identified by ISIN.
|
|
// The broker's description for one ISIN changes over time, so Name is editable
|
|
// display text and never an identity.
|
|
type Instrument struct {
|
|
ID string `json:"id"`
|
|
ISIN string `json:"isin"`
|
|
Name string `json:"name"`
|
|
Currency string `json:"currency"`
|
|
}
|
|
|
|
type Facts struct {
|
|
ID string `json:"id"`
|
|
Source string `json:"source"`
|
|
AccountID string `json:"account_id"`
|
|
BookingDate string `json:"booking_date"`
|
|
ValueDate string `json:"value_date,omitempty"`
|
|
Amount Money `json:"amount"`
|
|
Currency string `json:"currency"`
|
|
RawDescription string `json:"raw_description"`
|
|
ExternalID string `json:"external_id,omitempty"`
|
|
Fingerprint string `json:"fingerprint"`
|
|
Counterparty string `json:"counterparty,omitempty"`
|
|
CounterpartyIBAN string `json:"counterparty_iban,omitempty"`
|
|
// Investment is present exactly on facts imported from an investment
|
|
// account. It is bank fact data and therefore immutable.
|
|
Investment *Investment `json:"investment,omitempty"`
|
|
}
|
|
type Provenance struct {
|
|
Source string `json:"source"`
|
|
Model string `json:"model,omitempty"`
|
|
Confidence string `json:"confidence,omitempty"`
|
|
Timestamp string `json:"timestamp,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
type Enrichment struct {
|
|
Kind string `json:"kind"`
|
|
MerchantID string `json:"merchant_id,omitempty"`
|
|
CategoryID string `json:"category_id,omitempty"`
|
|
TagIDs []string `json:"tag_ids"`
|
|
TransferPeerID string `json:"transfer_peer_id,omitempty"`
|
|
Classification Provenance `json:"classification"`
|
|
}
|
|
type Transaction struct {
|
|
Facts Facts `json:"facts"`
|
|
Enrichment Enrichment `json:"enrichment"`
|
|
}
|
|
type Category struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
ParentID string `json:"parent_id,omitempty"`
|
|
Kind string `json:"kind"`
|
|
Hint string `json:"hint,omitempty"`
|
|
}
|
|
type Tag struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Hint string `json:"hint,omitempty"`
|
|
}
|
|
type Merchant struct {
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
Aliases []string `json:"aliases"`
|
|
DefaultCategoryID string `json:"default_category_id,omitempty"`
|
|
DefaultTagIDs []string `json:"default_tag_ids"`
|
|
UseDefaults bool `json:"use_defaults"`
|
|
}
|
|
type Dataset struct {
|
|
Accounts []Account `json:"accounts"`
|
|
Categories []Category `json:"categories"`
|
|
Tags []Tag `json:"tags"`
|
|
Merchants []Merchant `json:"merchants"`
|
|
Instruments []Instrument `json:"instruments"`
|
|
Transactions []Transaction `json:"transactions"`
|
|
}
|
|
|
|
const ExpenseFallback = "cat_expenses_unclassified"
|
|
const IncomeFallback = "cat_income_unclassified"
|
|
|
|
// KindInvestment is the enrichment kind for broker facts. Like a transfer it
|
|
// carries no category or merchant and never reaches spending analytics: money
|
|
// moving between your own cash and your own positions is not income or
|
|
// spending, and the AI must never see it.
|
|
const KindInvestment = "investment"
|