Documentation
¶
Overview ¶
Package receipts syncs what Rivian actually billed for each charging session (its Charging history) next to our own estimate.
Index ¶
- Constants
- func PaidRates(rs []Receipt, now time.Time, currency string) map[string]PaidRate
- type ChargeRef
- type PaidRate
- type Pair
- type Receipt
- type Result
- type Store
- func (s *Store) Apply(ctx context.Context, uid uuid.UUID, pairs []Pair) error
- func (s *Store) CandidateCharges(ctx context.Context, uid uuid.UUID, from, to time.Time) ([]ChargeRef, error)
- func (s *Store) ClaimDetails(ctx context.Context, uid uuid.UUID, minInterval time.Duration) (bool, error)
- func (s *Store) ClaimSummaries(ctx context.Context, uid uuid.UUID, minInterval time.Duration) (bool, error)
- func (s *Store) ListAll(ctx context.Context, uid uuid.UUID) ([]Receipt, error)
- func (s *Store) MarkResult(ctx context.Context, uid uuid.UUID, runErr error) error
- func (s *Store) NeedDetail(ctx context.Context, uid uuid.UUID, limit int) ([]string, error)
- func (s *Store) PaidRates(ctx context.Context, uid uuid.UUID, now time.Time, currency string) (map[string]PaidRate, error)
- func (s *Store) Purge(ctx context.Context, uid uuid.UUID) error
- func (s *Store) PurgeNotOptedIn(ctx context.Context) (int64, error)
- func (s *Store) RefreshMatched(ctx context.Context, uid uuid.UUID) error
- func (s *Store) SaveDetail(ctx context.Context, uid uuid.UUID, txn string, c *rivian.SessionCharger) error
- func (s *Store) Unmatched(ctx context.Context, uid uuid.UUID) ([]Receipt, error)
- func (s *Store) UpsertReceipts(ctx context.Context, uid uuid.UUID, in []rivian.SessionSummary) error
- type Syncer
- type UserFencer
Constants ¶
const ( // PaidMinSessions is how many usable receipts a network needs before a figure is // shown; fewer is noise (one off-peak session, one promotion). PaidMinSessions = 3 // PaidMaxSessions caps it at the most recent sessions, so an old price does not // outweigh the current one. PaidMaxSessions = 10 // PaidMaxAge drops receipts older than this: a price paid long ago is not // today's price. PaidMaxAge = 365 * 24 * time.Hour )
Variables ¶
This section is empty.
Functions ¶
func PaidRates ¶ added in v0.38.7
PaidRates computes the figures from a user's receipts: per network (the vendor and charger name matched to the network table), over the most recent PaidMaxSessions receipts that are within PaidMaxAge, not at a home charger, in the given currency, with a positive amount and energy. A network with fewer than PaidMinSessions such receipts has no entry. Pure.
Types ¶
type PaidRate ¶ added in v0.38.7
type PaidRate struct {
Slug string
Currency string
// PerKWh is total paid over total energy across Sessions receipts (energy-weighted,
// not an average of per-session prices).
PerKWh float64
Sessions int
}
PaidRate is one network's figure for one user.
type Pair ¶
Pair says which charge a receipt belongs to.
func Match ¶
Match pairs unmatched receipts with unmatched charges one to one.
A receipt and a charge are candidates when both their starts and their ends are within 5 minutes, or when one of the two is within 5 minutes and the energies agree (a long trickle session can end at different moments on the two clocks). When both energies are known and disagree the pair is never a candidate, however close the times. Candidates are taken best first by |start gap| + |end gap|.
type Receipt ¶
type Receipt struct {
ID uuid.UUID
TransactionID string
StartedAt time.Time
EndedAt time.Time
TotalCost *float64
Currency string
EnergyKWh *float64
RangeAddedKm *float64
Vendor string
City string
ChargerName string
IsHome *bool
DetailFetched bool
ChargeID *uuid.UUID
}
Receipt is one Rivian charging-history row.
type Result ¶
type Result struct {
Skipped bool // claimed by another run within the interval, or not this pod's user
NotOwner bool // skipped because another pod owns the user's group (lead mode)
Fetched int // summaries returned
Matched int // new receipt-to-charge links
Details int // detail calls that succeeded
DetailsLeft bool
}
Result is what one Sync did, for logs and tests.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the Postgres side: rivian_receipts, rivian_receipt_sync and the charges.receipt_* columns (migration 0055). It never touches a charge's cost, cost_source or price_per_kwh.
func (*Store) Apply ¶
Apply links each pair: the receipt gets charge_id, the charge gets the billed amount in receipt_*. A charge's cost, cost_source and price_per_kwh are left alone.
func (*Store) CandidateCharges ¶
func (s *Store) CandidateCharges(ctx context.Context, uid uuid.UUID, from, to time.Time) ([]ChargeRef, error)
CandidateCharges lists the user's charges without a receipt whose window touches [from, to].
func (*Store) ClaimDetails ¶
func (s *Store) ClaimDetails(ctx context.Context, uid uuid.UUID, minInterval time.Duration) (bool, error)
ClaimDetails is the same for a details-only pass (no summaries call).
func (*Store) ClaimSummaries ¶
func (s *Store) ClaimSummaries(ctx context.Context, uid uuid.UUID, minInterval time.Duration) (bool, error)
ClaimSummaries records an attempt to fetch the user's summaries and reports whether this caller won it: no attempt in the last minInterval, across pods and restarts.
func (*Store) MarkResult ¶
MarkResult stores the outcome of a claimed attempt. The error text is kept short and is only ever our own classification, never a response body.
func (*Store) NeedDetail ¶
NeedDetail returns up to limit transaction ids of matched receipts whose charger details were never fetched.
func (*Store) PaidRates ¶ added in v0.38.7
func (s *Store) PaidRates(ctx context.Context, uid uuid.UUID, now time.Time, currency string) (map[string]PaidRate, error)
PaidRates reads the user's receipts and returns their paid $/kWh per network. Empty for a user without receipts (they must have opted in for any to exist).
func (*Store) Purge ¶ added in v0.36.10
Purge removes everything the receipts sync stored for a user: the receipts, the sync clock, and the receipt_* columns on their charges. The estimate on the charge is untouched. Used when the user opts out.
func (*Store) PurgeNotOptedIn ¶ added in v0.36.10
PurgeNotOptedIn is the daily backstop for Purge: it removes the receipts of every user who is not opted in, catching a pass that saved rows after the user opted out. It returns how many receipts it deleted.
func (*Store) RefreshMatched ¶
RefreshMatched copies a changed amount or currency from a receipt to the charge it is linked to.
func (*Store) SaveDetail ¶
func (s *Store) SaveDetail(ctx context.Context, uid uuid.UUID, txn string, c *rivian.SessionCharger) error
SaveDetail stores the charger fields. For a home charger only the flag is kept: its name can be the owner's own place. A nil charger still marks the row fetched so it is not asked again.
func (*Store) UpsertReceipts ¶
func (s *Store) UpsertReceipts(ctx context.Context, uid uuid.UUID, in []rivian.SessionSummary) error
UpsertReceipts stores the summaries, refreshing amounts of rows we already have (a receipt can finalize later) and keeping detail and match state.
type Syncer ¶
type Syncer struct {
// contains filtered or unexported fields
}
Syncer pulls receipts for users whose flag is on.
func NewSyncer ¶
func NewSyncer(st *Store, accounts rivian.AccountRegistry, enabled func(uuid.UUID) bool, users func(context.Context) ([]uuid.UUID, error), logger *slog.Logger) *Syncer
NewSyncer wires a Syncer. enabled gates a user (the rivian_receipts flag); users lists the users with a Rivian session for the sweep.
func (*Syncer) OnChargeClose ¶
OnChargeClose schedules a sync for the user after closeDelay. One timer per user: further closes while it is pending are absorbed.
func (*Syncer) Run ¶
Run sweeps until ctx ends: every sweepEvery it syncs each enabled user once a day (summaries) and gives users with unfetched details a details-only pass, one user at a time, userPace apart.
func (*Syncer) SetFence ¶ added in v0.40.13
func (s *Syncer) SetFence(f UserFencer)
SetFence makes the sync run only on the pod that owns the user's group, with its writes guarded by that lease (lead mode); with lead mode off the store says Off and nothing changes.
func (*Syncer) Sync ¶
func (s *Syncer) Sync(ctx context.Context, uid uuid.UUID, minInterval time.Duration, detailsOnly bool) (Result, error)
Sync runs one pass for a user: unless detailsOnly, fetch the summaries, store them and match them to charges; then fetch up to maxDetailPerRun charger details for matched receipts. minInterval is the claim window.