Documentation
¶
Overview ¶
Package drives persists derived drive sessions.
Index ¶
- Constants
- Variables
- func HaversineMeters(lat1, lon1, lat2, lon2 float64) float64
- func IsReparking(d Drive) bool
- func ThumbPolyline(encoded string) string
- type ChartDrive
- type Drive
- type Factory
- type LiveOverlap
- type RouteStub
- 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) DeleteBefore(ctx context.Context, cutoff time.Time) (int64, error)
- func (s *Store) EachAll(ctx context.Context, fn func(Drive) error) error
- func (s *Store) EachListed(ctx context.Context, fn func(Drive) error) error
- func (s *Store) Get(ctx context.Context, externalID string) (*Drive, error)
- func (s *Store) ListAll(ctx context.Context) ([]Drive, error)
- func (s *Store) ListAllListed(ctx context.Context) ([]Drive, error)
- func (s *Store) ListChartPoints(ctx context.Context, since, until time.Time, rivianVehicleID string) ([]ChartDrive, error)
- func (s *Store) ListRange(ctx context.Context, from, to time.Time) ([]Drive, error)
- func (s *Store) ListRecent(ctx context.Context, limit int) ([]Drive, error)
- func (s *Store) ListRecentForVehicle(ctx context.Context, rivianVehicleID string, limit int, polyline bool) ([]Drive, error)
- func (s *Store) ListRecentSlim(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) RoutesByIDs(ctx context.Context, ids []string, rivianVehicleID string) ([]RouteStub, error)
- func (s *Store) SetTrailerOverride(ctx context.Context, externalID string, v *bool) (bool, error)
- func (s *Store) Upsert(ctx context.Context, d Drive) error
Constants ¶
const MaxChartRows = 5000
MaxChartRows bounds one chart answer (newest rows win when a window holds more).
const ThumbMaxPoints = 24
ThumbMaxPoints is the most points a thumbnail carries (endpoints included).
Variables ¶
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
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 ¶
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
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
// 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 ¶
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.
func (Drive) HasTrailer ¶ added in v0.38.26
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.
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 RouteStub ¶ added in v0.40.17
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 (*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) DeleteBefore ¶ added in v0.40.16
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
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
EachListed is EachAll without the drives the lists hide: those under a minute.
func (*Store) Get ¶ added in v0.37.6
Get returns one drive by its external id, with its route polyline, or nil when the user has no such drive.
func (*Store) ListAllListed ¶ added in v0.38.0
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
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 ¶
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
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 ¶
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
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 ¶
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.