Documentation
¶
Overview ¶
Package drives persists derived drive sessions.
Index ¶
- func IsReparking(d Drive) bool
- type Drive
- type Factory
- type LiveOverlap
- type Store
- func (s *Store) Close() error
- func (s *Store) Count(ctx context.Context) (int, error)
- func (s *Store) Dedupe(ctx context.Context) (int, error)
- func (s *Store) EachAll(ctx context.Context, fn func(Drive) error) error
- func (s *Store) ListAll(ctx context.Context) ([]Drive, error)
- func (s *Store) ListRecent(ctx context.Context, limit int) ([]Drive, 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) Reset(ctx context.Context) (int64, error)
- func (s *Store) Upsert(ctx context.Context, d Drive) error
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func IsReparking ¶
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 ¶
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 ¶
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.
type LiveOverlap ¶ added in v0.32.0
LiveOverlap describes the live drive another recorder wrote over the same time range: where it ends and its end readings.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store wraps the drives table.
func (*Store) Dedupe ¶
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) EachAll ¶ added in v0.36.17
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 ¶
ListRecent returns the most recent N drives, newest first.
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) drive for this vehicle overlaps [start, end). See charges.Store.LiveCovers.
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 drive for this vehicle, other than excludeID, whose time range overlaps [start, end), or nil. See charges.Store.LiveOverlap.
func (*Store) Reset ¶
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.