Documentation
¶
Overview ¶
Package charges persists derived charge sessions.
Index ¶
- Variables
- func IsOpenLiveFinalState(s string) bool
- type Charge
- type Factory
- type LiveOverlap
- type RestampOptions
- type RestampResult
- type StaleOpenCandidate
- type Store
- func (s *Store) AbandonOpenLive(ctx context.Context, externalID string) (int, error)
- func (s *Store) Close() error
- func (s *Store) CloseStaleOpenLive(ctx context.Context, rivianVehicleID, keepID string) (int, error)
- func (s *Store) CloseStaleOpenLiveBefore(ctx context.Context, before time.Time) (int, error)
- func (s *Store) ConfirmNetwork(ctx context.Context, externalID string) (int64, error)
- func (s *Store) Count(ctx context.Context) (int, error)
- func (s *Store) Dedupe(ctx context.Context) (int, error)
- func (s *Store) DeleteByExternalID(ctx context.Context, externalID string) (int64, error)
- func (s *Store) EachAll(ctx context.Context, fn func(Charge) error) error
- func (s *Store) GetByExternalID(ctx context.Context, externalID string) (*Charge, error)
- func (s *Store) LatestOpenLive(ctx context.Context, rivianVehicleID string) (*Charge, error)
- func (s *Store) ListAll(ctx context.Context) ([]Charge, error)
- func (s *Store) ListRecent(ctx context.Context, limit int) ([]Charge, error)
- func (s *Store) ListStaleOpenLive(ctx context.Context, before time.Time) ([]StaleOpenCandidate, error)
- func (s *Store) LiveCovers(ctx context.Context, rivianVehicleID string, start, end time.Time) (bool, error)
- func (s *Store) LiveOverlap(ctx context.Context, rivianVehicleID, excludeID string, start, end time.Time) (*LiveOverlap, error)
- func (s *Store) MarkFree(ctx context.Context, externalID, currency string) (int64, error)
- func (s *Store) NearestVerifiedNetwork(ctx context.Context, lat, lon, radiusM float64) (VerifiedNetwork, bool, error)
- func (s *Store) NetworkToConfirm(ctx context.Context) (toConfirm, needsAttention int, err error)
- func (s *Store) RefreshOpenLive(ctx context.Context, externalID, finalState string) (int, error)
- func (s *Store) Reset(ctx context.Context) (int64, error)
- func (s *Store) RestampPlace(ctx context.Context, o RestampOptions) (RestampResult, error)
- func (s *Store) SetNetworkPrice(ctx context.Context, externalID, network, currency string, pricePerKWh float64) (int64, error)
- func (s *Store) UndoRestamp(ctx context.Context, restampID *uuid.UUID) (UndoResult, error)
- func (s *Store) UpdatePricing(ctx context.Context, externalID string, cost float64, currency string, ...) (int64, error)
- func (s *Store) Upsert(ctx context.Context, c Charge) error
- type UndoResult
- type VerifiedNetwork
Constants ¶
This section is empty.
Variables ¶
var ErrNoRestamp = errors.New("no restamp to undo")
ErrNoRestamp is returned by UndoRestamp when there's nothing to undo.
var ErrRestampNotLatest = errors.New("only the latest restamp can be undone")
ErrRestampNotLatest is returned by UndoRestamp for a correction that is still undoable but isn't the user's latest: undoing it would put back places a newer correction already changed. Undo the newer ones first.
Functions ¶
func IsOpenLiveFinalState ¶
IsOpenLiveFinalState reports whether s names a charges.final_state value that means the session is physically still in progress. Kept in sync with the inline IN-list in LatestOpenLive et al.
Types ¶
type Charge ¶
type Charge struct {
ID string
VehicleID string
StartedAt time.Time
EndedAt time.Time
StartSoCPct float64
EndSoCPct float64
EnergyAddedKWh float64
MilesAdded float64
MaxPowerKW float64
AvgPowerKW float64
FinalState string
Lat, Lon float64
Source string
Cost float64
Currency string
PricePerKWh float64
// ThermalKWh is the energy the BMS spent on pack heating / cooling
// during the session, surfaced by Rivian's Parallax
// ChargingSessionLiveData protobuf. Nullable: legacy rows
// recorded before this column existed leave it nil so the UI
// can render "—" instead of misleading "0 kWh thermal".
ThermalKWh *float64
// ActiveSeconds is the wall-clock time the session actually spent
// charging (power above the idle floor), as opposed to
// EndedAt-StartedAt which spans the whole plugged-in period
// (including charging_ready / battery-conditioning idle). Zero on
// legacy rows and non-live sources; the UI falls back to the wall
// span in that case.
ActiveSeconds float64
// CostSource says where Cost came from: "rivian" (billed),
// "home_rate", "network_estimate" (DCFC priced from the matched
// network's rate) or "user". Empty on legacy rows.
CostSource string
// Network is the matched DCFC network's display name for
// network_estimate rows; empty otherwise.
Network string
// Place is where the session happened relative to the user's home
// when it was recorded: "home", "away", or "" (unknown: no saved
// home or no position). Migration 0044.
Place string
// NetworkState is DC fast charge auto-detection's verify state
// (migration 0051): "estimated", "needs_confirmation", "confirmed",
// or "" (not auto-detected: AC, Rivian-billed, user-priced, legacy).
NetworkState string
// NetworkConfidence is the detection's confidence, 0..1; nil when
// there was no detection.
NetworkConfidence *float64
// NetworkEvidence is what the detection saw (JSON, contract v1);
// nil when there was no detection.
NetworkEvidence json.RawMessage
// RivianCharger is Rivian's isRivianCharger for the session: nil
// when not reported. Written by the live recorder; a later upsert
// without it keeps the stored value. Not read back yet. Migration
// 0050.
RivianCharger *bool
// ReceiptCost is what Rivian billed for the session (its Charging
// history receipt), kept next to Cost (our estimate), which a receipt
// never overwrites. Nil when no receipt matched. ReceiptCurrency and
// ReceiptTxnID go with it. Migration 0055; written only by the
// receipts sync.
ReceiptCost *float64
ReceiptCurrency string
ReceiptTxnID string
}
Charge is a single charging session. ID is the stable external id ("live_<vid>_c_<unix>", "electrafi_<vid>_c_<unix>") and maps to the charges.external_id column. VehicleID is the Rivian gateway string.
type Factory ¶
type Factory struct {
// contains filtered or unexported fields
}
Factory hands out per-user *Store instances. See drives.Factory for the rationale; this is the same shape, scoped to the charges table.
func NewFactory ¶
func NewFactory(d *sql.DB, resolvers *db.VehicleResolverFactory) *Factory
NewFactory wraps the shared pool + resolver factory.
type LiveOverlap ¶ added in v0.32.0
LiveOverlap describes the live session another recorder wrote over the same time range: where it ends and what it already covers.
type RestampOptions ¶ added in v0.32.0
type RestampOptions struct {
HomeLat, HomeLon float64
RadiusM float64
// HomeRate and Currency price AC charges that become home and are
// unpriced; HomeRate <= 0 prices nothing.
HomeRate float64
Currency string
// IsDCFC is the shared fast-charging rule (rivian.IsDCFCSession);
// fast charges are never priced at the home rate. nil treats every
// session as AC.
IsDCFC func(peakKW, energyKWh, activeSeconds float64, span time.Duration) bool
DryRun bool
}
RestampOptions configures RestampPlace.
type RestampResult ¶ added in v0.32.0
type RestampResult struct {
DryRun bool `json:"dry_run"`
Changed int `json:"changed"`
ToHome int `json:"to_home"`
ToAway int `json:"to_away"`
Unchanged int `json:"unchanged"`
SkippedUserPriced int `json:"skipped_user_priced"`
WouldPriceAtHomeRate int `json:"would_price_at_home_rate"`
TotalCostDelta float64 `json:"total_cost_delta"`
Currency string `json:"currency"`
RestampID *uuid.UUID `json:"restamp_id"`
}
RestampResult counts what RestampPlace changed (or would change).
type StaleOpenCandidate ¶
type StaleOpenCandidate struct {
ExternalID string
RivianVehicleID string
FinalState string
EndedAt time.Time
}
StaleOpenCandidate is one row the janitor is considering for abandonment. The rivian-side vehicle ID is denormalised here so the caller can REST-poll the gateway without re-resolving from the internal vehicle UUID.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store wraps the charges table.
func (*Store) AbandonOpenLive ¶
AbandonOpenLive closes one specific open live row as abandoned. The smart janitor calls this only after a REST poll has confirmed the vehicle is no longer in a charging state. Same guarded WHERE as RefreshOpenLive so we never abandon a row that's already been closed legitimately by a terminal-state push between the list and the close.
func (*Store) CloseStaleOpenLive ¶
func (s *Store) CloseStaleOpenLive(ctx context.Context, rivianVehicleID, keepID string) (int, error)
CloseStaleOpenLive marks every live-sourced charge for the vehicle whose external_id is NOT keepID and whose final_state is still a non-terminal "charging_*" as abandoned. Returns rows updated.
func (*Store) CloseStaleOpenLiveBefore ¶
CloseStaleOpenLiveBefore is the unattended janitor variant: marks every open live charge for THIS user whose ended_at is older than `before` as abandoned. Used by the recorder's periodic sweep to self-heal sessions left open by a previous process that died mid-charge and never came back. Returns rows updated.
func (*Store) ConfirmNetwork ¶ added in v0.33.0
ConfirmNetwork records that the user agreed with an auto-detected DC fast charge network: network_state becomes 'confirmed'; cost, network and cost_source stay. Only an estimate can be confirmed (cost_source 'network_estimate' in an open state); a charge nothing matched has no estimate to confirm, so the user picks a network instead. Returns rows affected: 0 means not found for this user or not confirmable.
func (*Store) Dedupe ¶
Dedupe is a no-op on Postgres — UNIQUE (vehicle_id, external_id) prevents the SQLite-era duplicates the old code had to clean up.
func (*Store) DeleteByExternalID ¶
DeleteByExternalID removes a single charge row owned by this user. Returns the number of rows deleted (0 if no match — caller should treat that as 404). Scoped by user_id so a malicious external_id can't reach into another user's data.
func (*Store) EachAll ¶ added in v0.36.17
ListAll returns every charge for this user, newest first. Used by the Settings → Backup endpoint.
func (*Store) GetByExternalID ¶ added in v0.33.2
GetByExternalID returns one of this user's charges, or (nil, nil).
func (*Store) LatestOpenLive ¶
LatestOpenLive returns the most recent live-sourced charge for this vehicle whose final_state is still a non-terminal "charging_*" — i.e. the recorder was mid-session when the process stopped.
func (*Store) ListRecent ¶
ListRecent returns the most recent N charges, newest first.
func (*Store) ListStaleOpenLive ¶
func (s *Store) ListStaleOpenLive(ctx context.Context, before time.Time) ([]StaleOpenCandidate, error)
ListStaleOpenLive returns every open live-sourced charge row whose ended_at is older than `before`. Same WHERE shape as CloseStaleOpenLiveBefore but read-only — used by the smarter janitor that wants to REST-confirm the vehicle is actually no longer charging before tagging the row abandoned. WS feeds for long L1/L2 sessions can go silent for hours without the charge having ended.
func (*Store) LiveCovers ¶ added in v0.32.0
func (s *Store) LiveCovers(ctx context.Context, rivianVehicleID string, start, end time.Time) (bool, error)
LiveCovers reports whether a live (recorder) charge for this vehicle overlaps [start, end). Importers use it to leave sessions the recorder already captured alone instead of double-counting them.
func (*Store) LiveOverlap ¶ added in v0.32.0
func (s *Store) LiveOverlap(ctx context.Context, rivianVehicleID, excludeID string, start, end time.Time) (*LiveOverlap, error)
LiveOverlap returns the latest-ending live charge for this vehicle, other than excludeID, whose time range overlaps [start, end), or nil. The recorder uses it when the overlap guard (migration 0041) rejected one of its sessions: the session's tail after that row is still worth writing if the other recorder stopped.
func (*Store) MarkFree ¶ added in v0.32.0
MarkFree records that the user paid nothing for a session: cost and price_per_kwh are stored as 0 (not NULL), cost_source = 'user'. It is deliberately separate from UpdatePricing, where a zero still means "clear the price". Upsert keeps user-priced rows, so a session marked free while the recorder still has it open stays free. currency may be empty. Returns rows affected (0 → not found for this user).
func (*Store) NearestVerifiedNetwork ¶ added in v0.33.0
func (s *Store) NearestVerifiedNetwork(ctx context.Context, lat, lon, radiusM float64) (VerifiedNetwork, bool, error)
NearestVerifiedNetwork returns the user's nearest DC fast charge within radiusM of (lat, lon) whose network is settled: confirmed, or a user price that names a network. It is auto-detection's site memory, so a co-located site the user already resolved isn't asked about again. ok=false when there is none (or no position).
func (*Store) NetworkToConfirm ¶ added in v0.33.0
NetworkToConfirm counts this user's auto-detected charges still open for verification: toConfirm covers both open states, needsAttention only 'needs_confirmation'.
func (*Store) RefreshOpenLive ¶
RefreshOpenLive bumps ended_at = NOW() on a single open live row and optionally advances its final_state. Used by the smart janitor when a REST poll confirms the vehicle is still actively charging — keeps the row live for another sweep window without abandoning it. The WHERE clause is intentionally narrow (still open, still live) so a concurrent terminal-state push that closed the row legitimately can't be undone here.
func (*Store) RestampPlace ¶ added in v0.32.0
func (s *Store) RestampPlace(ctx context.Context, o RestampOptions) (RestampResult, error)
RestampPlace is the "my old home pin was wrong" correction: it recomputes the recorded place ('home' within RadiusM of the home, else 'away') on this user's charges that have a position, and prices the AC charges that become home and were unpriced (no cost, no cost source, some energy) at the home rate with cost_source 'home_rate', as the recorder would have with the right pin. Rows the user priced are never touched; no other price changes. One transaction; each changed row is logged in charge_restamps under a new restamp id so UndoRestamp can revert it. DryRun counts without writing.
func (*Store) SetNetworkPrice ¶ added in v0.33.2
func (s *Store) SetNetworkPrice(ctx context.Context, externalID, network, currency string, pricePerKWh float64) (int64, error)
SetNetworkPrice records the user's own choice of DC fast charge network: cost = pricePerKWh × the session's energy, cost_source 'user', network_state 'confirmed' (the user picked it; confidence and evidence stay for audit). Returns rows affected (0: not found for this user, or the session has no energy to price).
func (*Store) UndoRestamp ¶ added in v0.32.0
UndoRestamp reverts the user's latest correction not yet undone (restampID, if given, must be that one: ErrRestampNotLatest for an older pending one, ErrNoRestamp for an unknown or undone one): every logged charge gets its previous place back, and a home-rate price the correction added is removed only if the row still carries exactly that price (a price the user set or edited since is kept). One transaction; a correction can be undone once.
func (*Store) UpdatePricing ¶
func (s *Store) UpdatePricing(ctx context.Context, externalID string, cost float64, currency string, pricePerKWh float64) (int64, error)
UpdatePricing overwrites the cost / currency / price-per-kWh of a single charge row, scoped to the current user. Used when the user edits a row in the UI to correct a missing or wrong DCFC price (e.g. a session paid outside the Rivian app). Zeros clear the stored value and let the API-layer fallbacks (home rate, etc.) take over again.
type UndoResult ¶ added in v0.32.0
type UndoResult struct {
RestampID uuid.UUID `json:"restamp_id"`
RestoredPlace int `json:"restored_place"`
Unpriced int `json:"unpriced"`
}
UndoResult reports what UndoRestamp reverted.
type VerifiedNetwork ¶ added in v0.33.0
VerifiedNetwork is a DC fast charge of the user's own whose network they confirmed or set themselves, and how far it is from a point.