Help us shape the future of Go! Take the 2026 Go Developer Survey

drives

package
v0.40.22 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package drives persists derived drive sessions.

Index

Constants

View Source
const MaxChartRows = 5000

MaxChartRows bounds one chart answer (newest rows win when a window holds more).

View Source
const ThumbMaxPoints = 24

ThumbMaxPoints is the most points a thumbnail carries (endpoints included).

Variables

View Source
var ErrRangeTooLarge = errors.New("window holds more rows than one answer carries")

ErrRangeTooLarge: a window holds more rows than one answer may carry.

Functions

func HaversineMeters added in v0.38.16

func HaversineMeters(lat1, lon1, lat2, lon2 float64) float64

HaversineMeters is the great-circle distance between two lat/lon points; a missing fix (the 0,0 sentinel) is +Inf, so a pair with an unknown location never merges.

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.

func ThumbPolyline added in v0.40.17

func ThumbPolyline(encoded string) string

ThumbPolyline cuts a stored route (a precision-5 polyline) to at most ThumbMaxPoints points that keep its shape, as a polyline again. Empty when the route has fewer than two points.

Types

type ChartDrive added in v0.38.16

type ChartDrive struct {
	StartedAt   time.Time `json:"started_at"`
	EndedAt     time.Time `json:"ended_at"`
	DistanceMi  float64   `json:"distance_mi"`
	StartSoCPct float64   `json:"start_soc_pct"`
	EndSoCPct   float64   `json:"end_soc_pct"`
}

ChartDrive is the part of a drive the iPhone's charts need: when, how far and the charge at each end. No ids, places or coordinates.

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
	// Thinned is set by Get and the lists (not by the backup/export iterators):
	// the drive's month of raw telemetry has been thinned (sample_tiers) and is not
	// currently held at full detail by an admin restore, so the detail the drive
	// page draws from it is the lighter recording. Absent when not. The month is
	// UTC, as the partitions are; a drive that crosses a month counts as thinned
	// when either of its months is.
	Thinned bool `json:"thinned,omitempty"`
	// Trailer is what the recorder saw: the trailer connector was present for at least half of the time its state was
	// known, absent for more of it, or nil when it was never known. The connector, not the load.
	Trailer *bool
	// TrailerOverride is the user's own correction, nil when they have made none. Backup and restore carry both.
	TrailerOverride *bool
	// LocationUnavailable: the drive moved but its truck sent no usable live location, so it has no route (a truck whose
	// location sharing is off, or whose GPS is not updating). Set by the live recorder; kept in drives.metadata.
	LocationUnavailable bool
}

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.

func (Drive) HasTrailer added in v0.38.26

func (d Drive) HasTrailer() *bool

HasTrailer is the effective value: the user's correction when there is one, else what the recorder saw.

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 LiveOverlap added in v0.32.0

type LiveOverlap struct {
	EndedAt       time.Time
	EndSoCPct     float64
	EndOdometerMi float64
}

LiveOverlap describes the live drive another recorder wrote over the same time range: where it ends and its end readings.

type RouteStub added in v0.40.17

type RouteStub struct {
	ID            string
	StartedAt     time.Time
	RoutePolyline string
}

RouteStub is what a thumbnail needs of a drive: its id, when it started (for the free plan's history window) and its stored route.

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) DeleteBefore added in v0.40.16

func (s *Store) DeleteBefore(ctx context.Context, cutoff time.Time) (int64, error)

DeleteBefore deletes this user's drives that started before cutoff, with the per-drive rows that hang off them (see Reset), in one transaction. Returns the number of drives deleted.

func (*Store) EachAll added in v0.36.17

func (s *Store) EachAll(ctx context.Context, fn func(Drive) error) error

EachAll streams EVERY stored drive of this user, newest first, with no display filter: the backup and the data export must hold every row, including the sub-minute ones the lists hide. Callers that show drives (efficiency, weather backfill) use EachListed.

func (*Store) EachListed added in v0.38.0

func (s *Store) EachListed(ctx context.Context, fn func(Drive) error) error

EachListed is EachAll without the drives the lists hide: those under a minute.

func (*Store) Get added in v0.37.6

func (s *Store) Get(ctx context.Context, externalID string) (*Drive, error)

Get returns one drive by its external id, with its route polyline, or nil when the user has no such drive.

func (*Store) ListAll

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

ListAll returns every stored row; use EachAll for large result sets.

func (*Store) ListAllListed added in v0.38.0

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

ListAllListed returns the drives the lists show (a minute or longer).

func (*Store) ListChartPoints added in v0.38.16

func (s *Store) ListChartPoints(ctx context.Context, since, until time.Time, rivianVehicleID string) ([]ChartDrive, error)

ListChartPoints returns the user's drives that started in [since, until), oldest first, for the charts: the same rows the list shows (at least a minute, no re-parking rows), narrowed to ChartDrive. rivianVehicleID "" means every truck; at most MaxChartRows, the newest if there are more.

func (*Store) ListRange added in v0.40.14

func (s *Store) ListRange(ctx context.Context, from, to time.Time) ([]Drive, error)

ListRange returns the user's listed drives (as ListRecent: a minute or more) that started in [from, to), newest first, from every truck, without the route. At most maxRangeRows; a window with more returns ErrRangeTooLarge rather than a partial list, for totals that must not be short.

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) ListRecentForVehicle added in v0.38.16

func (s *Store) ListRecentForVehicle(ctx context.Context, rivianVehicleID string, limit int, polyline bool) ([]Drive, error)

ListRecentForVehicle is ListRecent for one of the user's trucks, by its Rivian vehicle id; polyline false leaves out the route as ListRecentSlim. An id the user doesn't have returns no rows.

func (*Store) ListRecentSlim added in v0.37.6

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

ListRecentSlim is ListRecent without the route polyline (RoutePolyline is empty): the lists don't draw maps, and the polyline is most of a row's size. Get returns the full row.

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

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

Reset deletes every drive for this store's user, and the per-drive rows derived from them. 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.

The recap, efficiency and weather rows are keyed (user_id, drive_id = drives.external_id) with no foreign key to drives, so deleting the drives does not remove them. They hold the drive's coarse start location and hour (weather) and generated text (recaps), so "delete your history" removes them in the same transaction. Returns the number of drives deleted.

func (*Store) RoutesByIDs added in v0.40.17

func (s *Store) RoutesByIDs(ctx context.Context, ids []string, rivianVehicleID string) ([]RouteStub, error)

RoutesByIDs returns the user's drives among ids (external ids), each with its stored route; an id the user does not have, or that has no route, is simply absent. rivianVehicleID, when set, keeps one truck's (the free plan's one-vehicle limit). It reads only this user's rows, like every other read of the store.

func (*Store) SetTrailerOverride added in v0.38.26

func (s *Store) SetTrailerOverride(ctx context.Context, externalID string, v *bool) (bool, error)

SetTrailerOverride sets the user's own correction on one of their drives (by external id): true or false, or nil to go back to what was recorded. Reports whether the user has such a drive.

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. With a fence token in the context (the live recorder of a pod that holds the group lease) the write is guarded by the lease in the same statement and returns fence.ErrFenced when the lease is gone.

Jump to

Keyboard shortcuts

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