drives

package
v0.18.13 Latest Latest
Warning

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

Go to latest
Published: May 12, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package drives persists derived drive sessions.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsReparking

func IsReparking(d Drive) bool

IsReparking reports whether a drive looks like an ignition cycle or in-place gear bounce rather than real travel: zero or near-zero distance with no SoC delta. The recorder still emits these so the raw timeline is preserved; the listing endpoints filter them out so the user doesn't see noise rows alongside real drives.

Threshold: 0.05 mi (~80 m) covers the GPS jitter envelope of a vehicle parked at a charger stall while still letting any real short drive (turn around the block, ~0.1 mi minimum) through.

Types

type Drive

type Drive struct {
	ID                 string
	VehicleID          string
	StartedAt          time.Time
	EndedAt            time.Time
	StartSoCPct        float64
	EndSoCPct          float64
	StartOdometerMi    float64
	EndOdometerMi      float64
	DistanceMi         float64
	StartLat, StartLon float64
	EndLat, EndLon     float64
	MaxSpeedMph        float64
	AvgSpeedMph        float64
	// EnergyUsedKWh is the pack-side energy consumed over the drive,
	// derived from (startSoC - endSoC)/100 × usable pack capacity.
	// Zero on legacy rows imported before migration 0002.
	EnergyUsedKWh float64
	Source        string
	// RoutePolyline is the drive's GPS path encoded with the
	// Google polyline algorithm at precision 5 (~1m resolution).
	// Empty for legacy ElectraFi imports and pre-migration live
	// drives -- consumers fall back to a straight start->end
	// line when this is empty.
	RoutePolyline string
}

Drive is a single drive session. ID is the importer/recorder's stable external id ("electrafi_<hash>_d_<unix>", "live_<vid>_d_<unix>"); it maps to the drives.external_id column. VehicleID is the Rivian gateway string id; the store resolves it to the internal UUID.

func CollapseRoundTrips

func CollapseRoundTrips(ds []Drive, radiusMeters float64, maxGap time.Duration) []Drive

CollapseRoundTrips merges chains of consecutive drives that form a closed loop into a single row. A chain [D1 … Dn] qualifies when:

  • every consecutive gap (Di.EndedAt → Di+1.StartedAt) ≤ maxGap, and
  • the chain's last drive ends within radiusMeters of the first drive's start point.

The greedy algorithm scans ascending and, at each position i, extends the chain as far as the gap constraint allows, then picks the farthest j for which the endpoint lands within radius of D[i].Start. This handles 2-leg round trips (gym-and-back), 3-leg chains (A→B→C→A), and arbitrary multi-stop trips in a single O(n²) pass — no iteration needed. The input is expected in DESC order (ListRecent contract) and the output preserves that order.

Pure function — DB rows are never mutated, so raw data is preserved and the pairing rule can be tuned / replayed at will.

func FilterReparking

func FilterReparking(ds []Drive) []Drive

FilterReparking returns ds with reparking rows removed. Order is preserved; the slice is rebuilt rather than compacted in place so callers don't need to worry about reusing the input.

type Factory

type Factory struct {
	// contains filtered or unexported fields
}

Factory hands out per-user *Store instances. Each Store is a thin wrapper around (db, userID, vehicleResolver) — building one per request is cheap, but the Factory caches them anyway so the vehicle-resolver in-memory cache stays warm across calls.

Safe for concurrent use.

func NewFactory

func NewFactory(d *sql.DB, resolvers *db.VehicleResolverFactory) *Factory

NewFactory wraps the shared pool + resolver factory. main.go owns the pool; the factory does not close it.

func (*Factory) For

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

For returns the *Store for userID, constructing one on first sight. Returns nil when userID is the zero UUID.

type Store

type Store struct {
	// contains filtered or unexported fields
}

Store wraps the drives 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) Close

func (s *Store) Close() error

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

func (*Store) Count

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

Count returns the total number of drives for this user.

func (*Store) Dedupe

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

Dedupe is retained for API compatibility but is a no-op on Postgres — UNIQUE (vehicle_id, external_id) makes the SQLite "same session written with different ids" condition impossible here.

func (*Store) ListAll

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

ListAll returns every drive for this user, newest first. Used by the Settings → Backup endpoint; /api/drives keeps the capped ListRecent path for the dashboard.

func (*Store) ListRecent

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

ListRecent returns the most recent N drives, newest first.

func (*Store) Reset

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

Reset deletes every drive for this store's user. Intended for the Settings → Danger zone reset button; the session external_id is derived from the parsed timestamp, so changing importer inputs (tz, pack size) creates parallel rows instead of ON CONFLICT upserts. Clearing lets a re-import land on a clean slate. Returns the number of rows deleted.

func (*Store) Upsert

func (s *Store) Upsert(ctx context.Context, d Drive) error

Upsert inserts or replaces a drive 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