charges

package
v0.30.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package charges persists derived charge sessions.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsOpenLiveFinalState

func IsOpenLiveFinalState(s string) bool

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
}

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.

func (*Factory) For

func (f *Factory) For(userID uuid.UUID) *Store

For returns the *Store for userID, constructing one on first sight.

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 OpenStore

func OpenStore(d *sql.DB, userID uuid.UUID, v *db.VehicleResolver) (*Store, error)

OpenStore binds a pooled connection to a user + vehicle resolver.

func (*Store) AbandonOpenLive

func (s *Store) AbandonOpenLive(ctx context.Context, externalID string) (int, error)

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) Close

func (s *Store) Close() error

Close is a no-op; the pool is managed by main.

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

func (s *Store) CloseStaleOpenLiveBefore(ctx context.Context, before time.Time) (int, error)

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) Count

func (s *Store) Count(ctx context.Context) (int, error)

Count returns the total number of charges for this user.

func (*Store) Dedupe

func (s *Store) Dedupe(ctx context.Context) (int, error)

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

func (s *Store) DeleteByExternalID(ctx context.Context, externalID string) (int64, error)

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) LatestOpenLive

func (s *Store) LatestOpenLive(ctx context.Context, rivianVehicleID string) (*Charge, error)

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) ListAll

func (s *Store) ListAll(ctx context.Context) ([]Charge, error)

ListAll returns every charge for this user, newest first. Used by the Settings → Backup endpoint.

func (*Store) ListRecent

func (s *Store) ListRecent(ctx context.Context, limit int) ([]Charge, error)

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) RefreshOpenLive

func (s *Store) RefreshOpenLive(ctx context.Context, externalID, finalState string) (int, error)

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) Reset

func (s *Store) Reset(ctx context.Context) (int64, error)

Reset deletes every charge for this store's user. See drives.Store.Reset.

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.

func (*Store) Upsert

func (s *Store) Upsert(ctx context.Context, c Charge) error

Upsert inserts or replaces a charge by external_id within the (user_id, vehicle_id) scope.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL