Files
s1d3sw1ped a6bf8632ce fix: parse MCP result payloads and drop place idempotency_key
Typed result structs replace empty envelopes. Equity place sends
ref_id only so live additionalProperties:false schemas accept the call.
2026-09-01 14:18:39 -05:00

191 lines
4.5 KiB
Go

package accounts
import (
"encoding/json"
decimal "github.com/alpacahq/alpacadecimal"
"s1d3sw1ped/robinhood-agentic-mcp/internal/wire"
)
func (r *RealizedPnLResult) UnmarshalJSON(b []byte) error {
var wrap struct {
Total any `json:"total"`
TotalGain any `json:"total_gain"`
Percent any `json:"percent"`
TotalPct any `json:"total_percent"`
Buckets []struct {
Label string `json:"label"`
Name string `json:"name"`
Amount any `json:"amount"`
Gain any `json:"gain"`
Percent any `json:"percent"`
Trades int `json:"trades"`
Count int `json:"count"`
} `json:"buckets"`
Results []struct {
Label string `json:"label"`
Amount any `json:"amount"`
Percent any `json:"percent"`
Trades int `json:"trades"`
} `json:"results"`
}
if err := json.Unmarshal(wire.Unwrap(b), &wrap); err != nil {
return err
}
var err error
if r.Total, err = firstDec(wrap.Total, wrap.TotalGain); err != nil {
return err
}
if r.Percent, err = firstDec(wrap.Percent, wrap.TotalPct); err != nil {
return err
}
src := wrap.Buckets
if len(src) == 0 {
for _, row := range wrap.Results {
src = append(src, struct {
Label string `json:"label"`
Name string `json:"name"`
Amount any `json:"amount"`
Gain any `json:"gain"`
Percent any `json:"percent"`
Trades int `json:"trades"`
Count int `json:"count"`
}{Label: row.Label, Amount: row.Amount, Percent: row.Percent, Trades: row.Trades})
}
}
r.Buckets = make([]PnLBucket, 0, len(src))
for _, row := range src {
label := row.Label
if label == "" {
label = row.Name
}
amt, err := firstDec(row.Amount, row.Gain)
if err != nil {
return err
}
pct, err := firstDec(row.Percent)
if err != nil {
return err
}
n := row.Trades
if n == 0 {
n = row.Count
}
r.Buckets = append(r.Buckets, PnLBucket{Label: label, Amount: amt, Percent: pct, Trades: n})
}
return nil
}
func (r *PnLTradeHistoryResult) UnmarshalJSON(b []byte) error {
type row struct {
Symbol string `json:"symbol"`
Side string `json:"side"`
Quantity any `json:"quantity"`
Price any `json:"price"`
PnL any `json:"realized_gain"`
Gain any `json:"gain"`
}
rows, next, err := wire.UnmarshalRows[row](b, "trades", "results")
if err != nil {
return err
}
r.NextCursor = next
r.Trades = make([]PnLTrade, 0, len(rows))
for _, row := range rows {
qty, err := firstDec(row.Quantity)
if err != nil {
return err
}
px, err := firstDec(row.Price)
if err != nil {
return err
}
pnl, err := firstDec(row.PnL, row.Gain)
if err != nil {
return err
}
r.Trades = append(r.Trades, PnLTrade{Symbol: row.Symbol, Side: row.Side, Quantity: qty, Price: px, PnL: pnl})
}
return nil
}
func (r *UpgradeInfoResult) UnmarshalJSON(b []byte) error {
var wrap struct {
URL string `json:"url"`
WebURL string `json:"web_url"`
MobileURL string `json:"mobile_url"`
Links struct {
Web string `json:"web"`
Mobile string `json:"mobile"`
} `json:"links"`
}
if err := json.Unmarshal(wire.Unwrap(b), &wrap); err != nil {
return err
}
r.URL = wrap.URL
r.WebURL = wrap.WebURL
if r.WebURL == "" {
r.WebURL = wrap.Links.Web
}
r.MobileURL = wrap.MobileURL
if r.MobileURL == "" {
r.MobileURL = wrap.Links.Mobile
}
if r.URL == "" {
r.URL = r.WebURL
}
return nil
}
func (r *OnboardingInfoResult) UnmarshalJSON(b []byte) error {
var wrap struct {
URL string `json:"url"`
WebURL string `json:"web_url"`
}
if err := json.Unmarshal(wire.Unwrap(b), &wrap); err != nil {
return err
}
r.URL = wrap.URL
r.WebURL = wrap.WebURL
if r.URL == "" {
r.URL = r.WebURL
}
return nil
}
func (r *SearchResult) UnmarshalJSON(b []byte) error {
type row struct {
Symbol string `json:"symbol"`
Name string `json:"name"`
ID string `json:"id"`
InstrumentID string `json:"instrument_id"`
}
rows, _, err := wire.UnmarshalRows[row](b, "results", "instruments")
if err != nil {
return err
}
r.Results = make([]SearchHit, 0, len(rows))
for _, row := range rows {
r.Results = append(r.Results, SearchHit{
Symbol: row.Symbol,
Name: row.Name,
ID: row.ID,
InstrumentID: row.InstrumentID,
})
}
return nil
}
func firstDec(vs ...any) (decimal.Decimal, error) {
for _, v := range vs {
if v == nil {
continue
}
if s, ok := v.(string); ok && s == "" {
continue
}
return wire.Dec(v)
}
return decimal.Zero, nil
}