Documentation
¶
Overview ¶
Package ledger builds the server-side money statement (the Money tab and, eventually, every cost/energy total in the SPA) from recorded charges and drives.
Everything here is read-only and pure: Build takes rows already loaded from Postgres (see Load) and returns the response. Three rules keep the numbers honest:
- Charges are the ledger. "Spend" is what was paid for energy; driving cost is spend ÷ miles, never a sum of per-drive guesses.
- Overlapping duplicates (two recorders on one vehicle, or an import overlapping live data) are resolved at query time and excluded from every total. No rows are modified.
- Unknown stays unknown: unpriced sessions, missing energy and inferred provenance are counted and surfaced, not papered over.
The wire contract lives in the Money tab's ledger-contract.md (v1).
Index ¶
- Constants
- Variables
- func CostSource(c Charge, cfg Config) (string, bool)
- func CountToConfirm(ctx context.Context, d *sql.DB, userID uuid.UUID) (open, attention int, err error)
- func EffectiveCharges(cs []Charge) ([]Charge, []Superseded)
- func Load(ctx context.Context, d *sql.DB, userID uuid.UUID, vehicleID string, ...) ([]Charge, []Drive, error)
- func LoadWindow(ps ...Period) (time.Time, time.Time)
- func PlanWindow(p Plan) (time.Time, time.Time)
- type Charge
- type Classification
- type Config
- type DataQuality
- type Drive
- type Driving
- type Energy
- type Input
- type KindLine
- type LatLon
- type MonthSummary
- type Monthly
- type Period
- type Plan
- type PlanCandidate
- type PlanComparison
- type PlanLeg
- type PlanMetric
- type PlanReason
- type PlanSignals
- type PlanStopActual
- type PlanStopPair
- type PlanStopPlanned
- type PlannedStop
- type Range
- type Receipt
- type ReceiptSession
- type Spend
- type Superseded
- type Trip
- type TripPlan
- type TripsResponse
Constants ¶
const ( SourceRivian = "rivian" SourceHomeRate = "home_rate" SourceNetworkEstimate = "network_estimate" SourceUser = "user" SourceHomeRateMisapplied = "home_rate_misapplied" SourceUnknown = "unknown" SourceMixed = "mixed" )
Cost sources. Rows closed by the pricing-aware recorder persist one of rivian / home_rate / network_estimate / user in charges.cost_source (migration 0040). Legacy rows (NULL) are derived at query time, which adds home_rate_misapplied: the old recorder's home rate stamped on a DC fast session, shown as "estimate pending" and kept out of spend until `rivolt reprice` backfills it.
const ( FlagFragmentsFolded = "fragments_folded" FlagHomeRateOnDCFC = "home_rate_on_dcfc" FlagHomeRateAway = "home_rate_away" FlagUnpriced = "unpriced" FlagNullEnergy = "null_energy" FlagOpenSession = "open_session" FlagAbandoned = "abandoned" FlagContinuesNextMonth = "continues_next_month" FlagContinuedFromPrev = "continued_from_previous_month" FlagCurrencyMismatch = "currency_mismatch" FlagEstimatePending = "estimate_pending" // FlagSuperseded marks a row the ledger drops as an overlapping // duplicate of another (EffectiveCharges); it isn't in any total. FlagSuperseded = "superseded" // FlagFree marks a session the user priced at $0 (cost_source=user, // cost 0): priced, counted in spend at zero, shown as "Free". FlagFree = "free" )
Flags on receipts and sessions.
const CrumbKWh = 0.05
CrumbKWh is the energy below which a charge session is a crumb (a plug-in blip, an abandoned stub): nothing to price, counted as null_energy rather than "needs a price". Seen on preview: a 4.7e-7 kWh abandoned row counted as unpriced.
const FastMinKW = 50.0
FastMinKW is the session peak at or above which a charge is DC fast charging. Same threshold as analytics.FastMinKW (the Charges page's "Fast" bucket) so the two surfaces agree.
const HomeRadiusKM = rivian.HomeRadiusM / 1000
HomeRadiusKM is how close to the saved home location a session must start to count as "home". Matches the ~200 m DBSCAN epsilon the Charges page uses, with a little GPS slack.
const MinMilesForCostPerMile = 100.0
MinMilesForCostPerMile is the driven distance below which driving.cost_per_mile is null: with only a few miles, one charge dominates and the ratio is meaningless (D2 F6).
const TripMaxParked = 12 * time.Hour
TripMaxParked is the longest a car can sit parked at home (or where we can't tell) inside one outing; a longer park ends it. Away from home a multi-night stay stays one trip (arriving home ends it), up to TripMaxParkedAway as a safety valve.
const TripMaxParkedAway = 14 * 24 * time.Hour
TripMaxParkedAway caps a park away from home inside one trip.
const TripMinKMFromHome = 150.0
TripMinKMFromHome is how far from home an outing must go to count as a trip (when the home location is known).
const Version = 1
Version is the response contract version. Additive changes keep it; breaking changes bump it.
Variables ¶
var ErrPlanUnusable = errors.New("ledger: saved trip has no origin and destination")
ErrPlanUnusable is returned by ParsePlan when the saved trip has neither an origin and destination in its inputs nor a planned route to take them from.
Functions ¶
func CostSource ¶
CostSource reports where a session's cost came from and whether the answer was derived at query time rather than read from the row.
- A persisted rivian / network_estimate / user source wins (a row with no cost is still unknown).
- Legacy rows (NULL source) with no cost are unknown.
- A persisted home_rate wins, except AC away from home → unknown (owner decision 2026-10-13: away AC is unknown until priced).
- A legacy rate within homeRateTolerance of the user's home rate: DC fast charging → home_rate_misapplied (estimate pending); AC away from home → unknown, needs a price; otherwise → home_rate.
- Any other legacy price → rivian (billed, or a manual edit / an older home rate).
func CountToConfirm ¶ added in v0.33.2
func CountToConfirm(ctx context.Context, d *sql.DB, userID uuid.UUID) (open, attention int, err error)
CountToConfirm counts the user's auto-detected charges still open for verification, across all time and vehicles: open is both open states, attention only needs_confirmation. Zero on a database without migration 0051.
func EffectiveCharges ¶
func EffectiveCharges(cs []Charge) ([]Charge, []Superseded)
EffectiveCharges resolves overlapping duplicates per vehicle. Sessions are accepted in priority order (live over imports, then has energy, has a cost, longer, created earlier, id); a session overlapping one already accepted is superseded by it. Output is sorted by start.
func Load ¶
func Load(ctx context.Context, d *sql.DB, userID uuid.UUID, vehicleID string, from, to time.Time) ([]Charge, []Drive, error)
Load reads the user's charges and drives whose time range intersects [from, to), optionally narrowed to one Rivian vehicle id. Read-only: plain SELECTs filtered by user_id (RLS is the backstop, not the filter). Rows keep NULLs as nil so the ledger can tell "not recorded" from zero.
func LoadWindow ¶
LoadWindow is the [from, to) range Load must cover for the periods, padded so boundary-straddling sessions are seen.
func PlanWindow ¶ added in v0.33.0
PlanWindow is the [from, to) to Load the ledger Input over for p: the candidate window padded by the longest an outing can stay away on both sides, so every outing whose leg starts in the window is loaded whole. A partly loaded outing would be named after its first loaded drive, a trip_id that doesn't match /api/ledger/trips and moves with the window (a return-leg plan's outing can have left days earlier).
Types ¶
type Charge ¶
type Charge struct {
ID string // charges.external_id, the id /api/charges exposes
VehicleID string // Rivian vehicle id
StartedAt time.Time
EndedAt time.Time
StartSoC *float64
EndSoC *float64
EnergyKWh *float64
PeakKW *float64
Cost *float64
Currency string
PricePerKWh *float64
FinalState string
Source string
Lat, Lon *float64
CreatedAt time.Time
// ActiveSeconds is charges.active_seconds (time actually drawing
// power); nil when not recorded. Feeds the shared DCFC rule.
ActiveSeconds *float64
// Place is charges.place ("home" / "away") recorded with the row
// (migration 0044); "" when not recorded.
Place string
// CostSource is charges.cost_source; "" when NULL (legacy row) or
// the column doesn't exist yet.
CostSource string
// Network is charges.network (e.g. "Electrify America") for
// network-estimated sessions; "" when unknown.
Network string
// NetworkState/-Confidence/-Evidence are DC fast charge
// auto-detection's verify record (migration 0051); empty/nil when
// the charge wasn't auto-detected.
NetworkState string
NetworkConfidence *float64
NetworkEvidence json.RawMessage
// Billed* is what Rivian billed for the session (its charging-history
// receipt, matched by the receipts sync; migration 0055), beside Cost,
// which stays our own figure. BilledCost is nil without a match.
// BilledVendor / BilledCharger are the network and charger name;
// BilledHome is Rivian's isHomeCharger (vendor and charger are then
// never surfaced).
BilledCost *float64
BilledCurrency string
BilledVendor string
BilledCharger string
BilledHome bool
}
Charge is the projection of a charges row the ledger needs. Pointer fields are nullable columns: nil means "not recorded", which the ledger keeps distinct from zero.
func FromCharge ¶
FromCharge converts a charges.Charge as the store lists it (zeros for NULL columns) into the ledger's nullable shape. A zero cost stays 0 only on user-priced rows, where it means Free.
type Classification ¶
type Classification struct {
CostSource string `json:"cost_source_effective"`
CostSourceDerived bool `json:"cost_source_derived"`
Kind string `json:"kind"`
Place string `json:"place"`
KMFromHome *float64 `json:"km_from_home"`
// CountsInSpend is true when the session's cost is part of
// spend.total (priced, same currency).
CountsInSpend bool `json:"counts_in_spend"`
Flags []string `json:"ledger_flags"`
}
Classification is the ledger's view of one charge: the same cost source, kind, place and flags the Money tab uses, for other surfaces (the Charges list, the charge page, the Activity feed) so they don't re-derive them with their own rules.
func Classify ¶
func Classify(c Charge, cfg Config) Classification
Classify returns the ledger's classification of c under cfg.
func ClassifyAll ¶
func ClassifyAll(cs []Charge, cfg Config) []Classification
ClassifyAll is Classify over a list, with the ledger's dedupe applied: a row EffectiveCharges drops as an overlapping duplicate gets CountsInSpend=false and the superseded flag, so a list built from it agrees with the Money tab about what's in spend. Results are in the input order.
type Config ¶
type Config struct {
// Currency is the user's home currency (ISO-4217). Sessions in
// another currency are excluded from spend and flagged.
Currency string
// HomeRate is the user's configured home $/kWh; 0 = not set.
HomeRate float64
// Home is the saved home location; nil = not set.
Home *LatLon
// Billed turns on the billed_* fields (Rivian receipts) for this user:
// the rivian_receipts flag. Off, none of them appear.
Billed bool
}
Config is the per-user context the statement is priced in.
type DataQuality ¶
type DataQuality struct {
// CostSourceInferred is true when any session in the month had its
// cost_source derived (legacy NULL); CostSourceDerivedSessions counts
// them.
CostSourceInferred bool `json:"cost_source_inferred"`
CostSourceDerivedSessions int `json:"cost_source_derived_sessions"`
SupersededExcluded int `json:"superseded_excluded"`
Superseded []Superseded `json:"superseded,omitempty"`
DrivesSupersededExcluded int `json:"drives_superseded_excluded"`
OpenSessions int `json:"open_sessions"`
HomeRateOnDCFC int `json:"home_rate_on_dcfc"`
HomeRateAway int `json:"home_rate_away"`
CurrencyMismatchExcluded int `json:"currency_mismatch_excluded"`
}
DataQuality carries the counts behind the Money tab's quality notes.
type Drive ¶
type Drive struct {
ID string
VehicleID string
StartedAt time.Time
EndedAt time.Time
DistanceMi *float64
EnergyKWh *float64
Source string
CreatedAt time.Time
// Start and end positions; nil when not recorded. Used only to
// measure distance from home (trips); never returned.
StartLat, StartLon *float64
EndLat, EndLon *float64
// Start and end state of charge (%); nil when not recorded. Used
// for plan vs actual arrival SoC; never returned.
StartSoC, EndSoC *float64
}
Drive is the projection of a drives row the ledger needs.
type Driving ¶
type Driving struct {
Drives int `json:"drives"`
Miles float64 `json:"miles"`
MilesWithEnergy float64 `json:"miles_with_energy"`
KWhUsedKnown float64 `json:"kwh_used_known"`
Coverage *float64 `json:"coverage"`
CostPerMile *float64 `json:"cost_per_mile"`
CostPerMileBasis string `json:"cost_per_mile_basis"`
}
Driving summarises drives and the per-mile allocation of spend.
type Energy ¶
type Energy struct {
Sessions int `json:"sessions"`
KWhAdded float64 `json:"kwh_added"`
NullEnergySessions int `json:"null_energy_sessions"`
ByKind map[string]KindLine `json:"by_kind"`
ByPlace map[string]KindLine `json:"by_place"`
// ByPlaceKind is the place × kind cross-tab of the same per-session
// classification: summed over places it equals ByKind, over kinds
// ByPlace. All six cells are always present.
ByPlaceKind map[string]map[string]KindLine `json:"by_place_kind"`
}
Energy is energy added by charging.
type Input ¶
Input is everything Build needs for one or more months: rows loaded over LoadWindow(periods...).
type KindLine ¶
type KindLine struct {
Sessions int `json:"sessions"`
KWh float64 `json:"kwh"`
Cost float64 `json:"cost"`
PeakKW *float64 `json:"peak_kw"`
}
KindLine is one by_kind / by_place bucket. Sessions are classified one by one (session peak for kind, session location for place), so each map's lines sum to the Energy totals.
type MonthSummary ¶
MonthSummary is a Monthly without receipts, for GET /api/ledger/summary. The outer Receipts field shadows the embedded one in JSON and is always empty, so the key is omitted.
type Monthly ¶
type Monthly struct {
Version int `json:"version"`
Period string `json:"period"`
TZ string `json:"tz"`
TZSource string `json:"tz_source"`
Range Range `json:"range"`
Currency string `json:"currency"`
VehicleID *string `json:"vehicle_id"`
IsCurrentMonth bool `json:"is_current_month"`
FirstActivityAt *time.Time `json:"first_activity_at"`
LastActivityAt *time.Time `json:"last_activity_at"`
// BilledSessions / EstimatedSessions split the month's sessions (with
// energy) into those with a Rivian receipt and those without. Present
// only when Config.Billed.
BilledSessions *int `json:"billed_sessions,omitempty"`
EstimatedSessions *int `json:"estimated_sessions,omitempty"`
Spend Spend `json:"spend"`
Energy Energy `json:"energy"`
Driving Driving `json:"driving"`
Receipts []Receipt `json:"receipts"`
DataQuality DataQuality `json:"data_quality"`
}
Monthly is the GET /api/ledger/monthly response.
type Period ¶
type Period struct {
Month string // YYYY-MM
Loc *time.Location
Start time.Time // inclusive, UTC instant
End time.Time // exclusive, UTC instant
}
Period is one statement month in a location.
func LastMonths ¶
LastMonths returns the n months ending with the one containing now, oldest first.
type Plan ¶ added in v0.33.0
type Plan struct {
CreatedAt time.Time
Departure *time.Time // planned departure; nil when not set
Origin LatLon
Destination LatLon
Stops []PlannedStop // charging stops, in route order
EnergyKWh *float64 // route energy
DistanceMi *float64
DurationMin *float64
ArrivalSoC *float64
Cost *float64 // DC fast spend at the user's own member rates
Currency string
}
Plan is a saved trip plan, parsed from saved_trips.inputs and .plan (or a link-time snapshot of them).
type PlanCandidate ¶ added in v0.33.0
type PlanCandidate struct {
TripID string `json:"trip_id"`
StartedAt time.Time `json:"started_at"`
EndedAt time.Time `json:"ended_at"`
Miles float64 `json:"miles"`
Confidence float64 `json:"confidence"`
Band string `json:"band"`
// InProgress: the outing is still under way (not back home, last
// activity within TripMaxParked) and hasn't reached the plan's
// destination yet. Its band is at most medium.
InProgress bool `json:"in_progress"`
Signals PlanSignals `json:"signals"`
}
PlanCandidate is one outing that may have carried out the plan (§4).
func MatchPlan ¶ added in v0.33.0
func MatchPlan(p Plan, in Input) []PlanCandidate
MatchPlan scores the outings in `in` against p and returns those with confidence ≥ 0.50, best first (ties: earlier start), at most 3.
type PlanComparison ¶ added in v0.33.0
type PlanComparison struct {
// Status is "in_progress" while the outing hasn't reached the
// plan's destination or is still under way (not home yet), else
// "complete". Metrics are what's known so far either way.
Status string `json:"status"`
Currency string `json:"currency"`
PlanFrom string `json:"plan_from"`
Leg PlanLeg `json:"leg"`
Metrics []PlanMetric `json:"metrics"`
Stops []PlanStopPair `json:"stops"`
}
PlanComparison is the planned-vs-actual comparison of one linked (or suggested) outing (contract §5). PlanFrom is set by the caller ("snapshot" for a link, "current" for a suggestion).
func ComparePlan ¶ added in v0.33.0
func ComparePlan(p Plan, in Input, tripID string) (PlanComparison, bool)
ComparePlan compares p with the outing tripID in `in` (§5). False when no outing with that id is in `in`.
type PlanLeg ¶ added in v0.33.0
type PlanLeg struct {
StartedAt time.Time `json:"started_at"`
// ArrivedAt is when the leg reached the destination; the time of
// the latest drive's end while still on the way.
ArrivedAt time.Time `json:"arrived_at"`
Miles float64 `json:"miles"`
DriveIDs []string `json:"drive_ids"`
ChargeIDs []string `json:"charge_ids"`
}
PlanLeg is the part of the outing compared: from leaving through the first arrival at the plan's destination.
type PlanMetric ¶ added in v0.33.0
type PlanMetric struct {
Metric string `json:"metric"`
Unit string `json:"unit"`
Planned *float64 `json:"planned"`
Actual *float64 `json:"actual"`
Delta *float64 `json:"delta"`
ActualComplete *bool `json:"actual_complete,omitempty"`
ActualKnown *float64 `json:"actual_known,omitempty"`
UnpricedSessions *int `json:"unpriced_sessions,omitempty"`
ActualSources map[string]float64 `json:"actual_sources,omitempty"`
Reasons []PlanReason `json:"reasons"`
}
PlanMetric is one planned/actual pair. Delta is actual − planned; any of the three is null when unknown. The cost-only fields are omitted on other metrics.
type PlanReason ¶ added in v0.33.0
type PlanReason struct {
Code string `json:"code"`
Amount *float64 `json:"amount"`
Detail string `json:"detail"`
Network string `json:"network,omitempty"` // the actual session's network
PlanNetwork string `json:"plan_network,omitempty"` // the planned stop's network
Stop string `json:"stop,omitempty"` // planned stop label
Rate *float64 `json:"rate,omitempty"` // actual price per kWh
PlanRate *float64 `json:"plan_rate,omitempty"`
Estimated bool `json:"estimated,omitempty"` // Rate is a network estimate, not a bill
KWh *float64 `json:"kwh,omitempty"`
PlanKWh *float64 `json:"plan_kwh,omitempty"`
Count *int `json:"count,omitempty"` // unpriced sessions, extra or skipped stops
Percent *float64 `json:"percent,omitempty"` // drove_further, less_efficient
Miles *float64 `json:"miles,omitempty"`
PlanMiles *float64 `json:"plan_miles,omitempty"`
MiPerKWh *float64 `json:"mi_per_kwh,omitempty"`
PlanMiPerKWh *float64 `json:"plan_mi_per_kwh,omitempty"`
}
PlanReason explains part of a difference. Amount (cost metric only) is that part of the cost delta, in the comparison currency.
Detail is ready-to-show copy (plan-vs-actual-copy.md); the structured fields, set where they apply, let a client word it itself. Stop and network names are public chargers only, never a home.
type PlanSignals ¶ added in v0.33.0
type PlanSignals struct {
Destination float64 `json:"destination"`
Origin float64 `json:"origin"`
Time float64 `json:"time"`
RouteOverlap *float64 `json:"route_overlap"`
DestinationKM float64 `json:"destination_km"`
OriginKM *float64 `json:"origin_km"`
StartOffsetMin *float64 `json:"start_offset_min"`
}
PlanSignals are the per-signal scores (0..1) behind a confidence.
type PlanStopActual ¶ added in v0.33.0
type PlanStopActual struct {
ChargeID string `json:"charge_id"`
Kind string `json:"kind"` // dcfc | ac
Network string `json:"network"`
KWh float64 `json:"kwh"`
Cost *float64 `json:"cost"` // null when not priced
CostSource string `json:"cost_source"`
StartedAt time.Time `json:"started_at"`
Minutes float64 `json:"minutes"`
}
PlanStopActual is the actual session at a stop.
type PlanStopPair ¶ added in v0.33.0
type PlanStopPair struct {
Match string `json:"match"`
Planned *PlanStopPlanned `json:"planned"`
Actual *PlanStopActual `json:"actual"`
}
PlanStopPair pairs a planned stop with the actual session there. Match is same_station / same_network / other_network / nearby (paired, network unknown on one side) / skipped / extra.
type PlanStopPlanned ¶ added in v0.33.0
type PlanStopPlanned struct {
Name string `json:"name"`
Network string `json:"network"`
Lat float64 `json:"lat"`
Lon float64 `json:"lon"`
ArrivalSoC *float64 `json:"arrival_soc"`
ChargeMin *float64 `json:"charge_min"`
EstKWh *float64 `json:"est_kwh"`
EstCost *float64 `json:"est_cost"`
}
PlanStopPlanned is the planned side of a stop.
type PlannedStop ¶ added in v0.33.0
type PlannedStop struct {
Name string
Network string // display name; "" when unknown
Lat, Lon float64
ArrivalSoC *float64
ChargeMin *float64
EstKWh *float64 // from the plan's cost breakdown
EstRate *float64 // the user's rate there, per kWh
EstCost *float64
}
PlannedStop is one charging stop on the planned route.
type Receipt ¶
type Receipt struct {
ReceiptID string `json:"receipt_id"`
VehicleID string `json:"vehicle_id"`
Kind string `json:"kind"`
Place string `json:"place"`
KMFromHome *float64 `json:"km_from_home"`
StartedAt time.Time `json:"started_at"`
EndedAt time.Time `json:"ended_at"`
LocalDate string `json:"local_date"`
SpansDays int `json:"spans_days"`
SoCStart *float64 `json:"soc_start"`
SoCEnd *float64 `json:"soc_end"`
KWh float64 `json:"kwh"`
PeakKW *float64 `json:"peak_kw"`
Cost *float64 `json:"cost"`
CostSource string `json:"cost_source"`
Network *string `json:"network"`
// NetworkState is the most urgent verify state among the receipt's
// sessions (needs_confirmation > estimated > confirmed); the
// confidence and evidence are that session's. null when none.
NetworkState *string `json:"network_state"`
NetworkConfidence *float64 `json:"network_confidence"`
NetworkEvidence json.RawMessage `json:"network_evidence"`
Rate *float64 `json:"rate"`
SessionCount int `json:"session_count"`
UnpricedSessions int `json:"unpriced_sessions"`
Flags []string `json:"flags"`
Sessions []ReceiptSession `json:"sessions"`
// Billed*: what Rivian billed for the plug-in (Config.Billed only).
// BilledCost is the sum of the sessions' billed amounts and is set only
// when every session with energy has one; vendor and charger come from
// the first session that has them and are never set for a home charger.
BilledCost *float64 `json:"billed_cost,omitempty"`
BilledCurrency *string `json:"billed_currency,omitempty"`
BilledVendor *string `json:"billed_vendor,omitempty"`
BilledCharger *string `json:"billed_charger,omitempty"`
BilledSessions *int `json:"billed_sessions,omitempty"`
EstimatedSessions *int `json:"estimated_sessions,omitempty"`
}
Receipt is one plug-in: consecutive charges with no drive between.
type ReceiptSession ¶
type ReceiptSession struct {
ChargeID string `json:"charge_id"`
StartedAt time.Time `json:"started_at"`
EndedAt time.Time `json:"ended_at"`
SoCStart *float64 `json:"soc_start"`
SoCEnd *float64 `json:"soc_end"`
KWh *float64 `json:"kwh"`
PeakKW *float64 `json:"peak_kw"`
Cost *float64 `json:"cost"`
PricePerKWh *float64 `json:"price_per_kwh"`
CostSource string `json:"cost_source"`
// CostSourceDerived is true when cost_source was derived at query
// time (legacy row) rather than persisted by the recorder.
CostSourceDerived bool `json:"cost_source_derived"`
Network *string `json:"network"`
NetworkState *string `json:"network_state"`
NetworkConfidence *float64 `json:"network_confidence"`
NetworkEvidence json.RawMessage `json:"network_evidence"`
Kind string `json:"kind"`
Place string `json:"place"`
FinalState string `json:"final_state"`
Source string `json:"source"`
Flags []string `json:"flags"`
// Billed*: Rivian's receipt for this session (Config.Billed and a
// match only); vendor and charger are never set for a home charger.
BilledCost *float64 `json:"billed_cost,omitempty"`
BilledCurrency *string `json:"billed_currency,omitempty"`
BilledVendor *string `json:"billed_vendor,omitempty"`
BilledCharger *string `json:"billed_charger,omitempty"`
}
ReceiptSession is one charge inside a receipt.
type Spend ¶
type Spend struct {
Total float64 `json:"total"`
BySource map[string]float64 `json:"by_source"`
PricedSessions int `json:"priced_sessions"`
UnpricedSessions int `json:"unpriced_sessions"`
UnpricedKWh float64 `json:"unpriced_kwh"`
UnpricedHomeRateEstimate *float64 `json:"unpriced_home_rate_estimate"`
EstimatePendingSessions int `json:"estimate_pending_sessions"`
EstimatePendingKWh float64 `json:"estimate_pending_kwh"`
// BilledTotal is what Rivian billed over the month's sessions that have
// a receipt in the statement currency; EstimatedTotal is our own cost
// over the other sessions counted in Total. With Config.Billed each
// session counts once, as its receipt when it has one, so
// Total == BilledTotal + EstimatedTotal. Present only when Config.Billed;
// off, Total is our own cost per session as before.
BilledTotal *float64 `json:"billed_total,omitempty"`
EstimatedTotal *float64 `json:"estimated_total,omitempty"`
}
Spend is money paid for energy.
type Superseded ¶
type Superseded struct {
ChargeID string `json:"charge_id"`
SupersededBy string `json:"superseded_by"`
}
Superseded names a duplicate excluded in favour of its winner.
type Trip ¶
type Trip struct {
TripID string `json:"trip_id"`
VehicleID string `json:"vehicle_id"`
StartedAt time.Time `json:"started_at"`
EndedAt time.Time `json:"ended_at"`
Miles float64 `json:"miles"`
DriveIDs []string `json:"drive_ids"`
ChargeIDs []string `json:"charge_ids"`
FastSessions int `json:"fast_sessions"`
MaxKMFromHome *float64 `json:"max_km_from_home"`
KWhUsed float64 `json:"kwh_used"`
KWhAdded float64 `json:"kwh_added"`
Cost float64 `json:"cost"`
CostSourceMix map[string]float64 `json:"cost_source_mix"`
UnpricedSessions int `json:"unpriced_sessions"`
EfficiencyMiKWh *float64 `json:"efficiency_mi_per_kwh"`
Plan *TripPlan `json:"plan"`
}
Trip is one auto-detected trip.
type TripPlan ¶
type TripPlan struct {
PlanID string `json:"plan_id"`
PlannedCost float64 `json:"planned_cost"`
PlannedArrivalAt time.Time `json:"planned_arrival_at"`
PlannedMiles float64 `json:"planned_miles"`
}
TripPlan links a saved planned trip. Always null in v1.
type TripsResponse ¶
type TripsResponse struct {
Version int `json:"version"`
TZ string `json:"tz"`
Currency string `json:"currency"`
Trips []Trip `json:"trips"`
}
TripsResponse is the GET /api/trips body.
func BuildTrips ¶
BuildTrips detects trips among the effective (deduped) drives and charges in in, returning those that start in [from, to), newest first. in should be loaded over a window padded beyond [from, to) so a trip straddling the edges is seen whole.