Skip to content

Commit da1e97d

Browse files
committed
Scope FSD insights to drive detail
Adds drive_id support for FSD insights with a 7-day bookend lookup and focused attribution, while filtering negligible fidget drives so they do not steal sparse counter deltas. Updates drive detail and list UI to use the drive-scoped hook and omit unknown FSD badges.
1 parent 2b7a9d3 commit da1e97d

13 files changed

Lines changed: 394 additions & 58 deletions

internal/api/fsd/drive_aggregate.go

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ const (
1919
maxObservatoryTimelineEvents = 200
2020
maxObservatoryCommuteStories = 8
2121
minObservatoryCommuteDrives = 2
22-
observatoryHonesty = "Every kilometre here is a reset-safe counter change, not an FSD engagement segment. Unknown and ambiguous distance are shown instead of guessed."
22+
// Tesla will not emit SelfDrivingMilesSinceReset below a 1 mile wire
23+
// delta. Sub-0.1 mi "drives" are pull-in / GPS fidget and must not steal
24+
// overlap from a real commute sitting one minute later.
25+
minFSDAttributionDistanceM = 160.9344
26+
observatoryHonesty = "Every kilometre here is a reset-safe counter change, not an FSD engagement segment. Unknown and ambiguous distance are shown instead of guessed."
2327
)
2428

2529
type driveAttributionState struct {
@@ -80,11 +84,19 @@ func BuildDriveAnalytics(
8084
}
8185

8286
currentDrives := drivesFullyContained(input.Drives, current.Period.StartAt, current.Period.EndAt)
87+
if input.FocusDriveID != 0 {
88+
currentDrives = focusedDrive(input.Drives, input.FocusDriveID)
89+
} else {
90+
currentDrives = significantDrives(currentDrives, 0)
91+
}
8392
currentDriveIDs := make(map[int64]struct{}, len(currentDrives))
8493
for _, drive := range currentDrives {
8594
currentDriveIDs[drive.ID] = struct{}{}
8695
}
87-
allDrives := drivesOverlapping(input.Drives, previous.Period.StartAt, current.Period.EndAt)
96+
allDrives := significantDrives(
97+
drivesOverlapping(input.Drives, previous.Period.StartAt, current.Period.EndAt),
98+
input.FocusDriveID,
99+
)
88100
states := make(map[int64]*driveAttributionState, len(allDrives))
89101
for _, drive := range allDrives {
90102
drive.DistanceM = finiteNonNegativePointer(drive.DistanceM)
@@ -399,6 +411,43 @@ func drivesFullyContained(drives []DriveRecord, start, end time.Time) []DriveRec
399411
return filtered
400412
}
401413

414+
func focusedDrive(drives []DriveRecord, driveID int64) []DriveRecord {
415+
for _, drive := range drives {
416+
if drive.ID == driveID {
417+
return []DriveRecord{drive}
418+
}
419+
}
420+
return nil
421+
}
422+
423+
func isNegligibleDrive(drive DriveRecord) bool {
424+
if drive.DistanceM != nil && *drive.DistanceM >= minFSDAttributionDistanceM {
425+
return false
426+
}
427+
if drive.DistanceM != nil && *drive.DistanceM > 0 {
428+
return true
429+
}
430+
if drive.EndedAt == nil {
431+
return false
432+
}
433+
return drive.EndedAt.Sub(drive.StartedAt) < time.Minute
434+
}
435+
436+
func significantDrives(drives []DriveRecord, keepID int64) []DriveRecord {
437+
filtered := make([]DriveRecord, 0, len(drives))
438+
for _, drive := range drives {
439+
if keepID != 0 && drive.ID == keepID {
440+
filtered = append(filtered, drive)
441+
continue
442+
}
443+
if isNegligibleDrive(drive) {
444+
continue
445+
}
446+
filtered = append(filtered, drive)
447+
}
448+
return filtered
449+
}
450+
402451
func trustedCounterObservations(samples []Sample, field string) []counterObservation {
403452
ordered := make([]Sample, 0, len(samples)/2+1)
404453
for _, sample := range samples {

internal/api/fsd/drive_aggregate_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,81 @@ func TestBuildDriveAnalytics_DriveDetailLookaroundIncludesSparseBookend(t *testi
141141
}
142142
}
143143

144+
func TestBuildDriveAnalytics_FidgetDriveDoesNotStealCommuteDelta(t *testing.T) {
145+
start := at(t, "2026-09-07T00:00:00Z")
146+
end := at(t, "2026-09-08T00:00:00Z")
147+
fidgetStart := at(t, "2026-09-07T00:51:00Z")
148+
fidgetEnd := at(t, "2026-09-07T00:52:00Z")
149+
driveStart := at(t, "2026-09-07T00:55:00Z")
150+
driveEndAt := at(t, "2026-09-07T01:19:00Z")
151+
fidgetDistance := 32.0
152+
commuteDistance := 13260.0
153+
samples := []Sample{
154+
trustedSample(SignalFSDDistance, at(t, "2026-09-07T00:50:00Z"), 10000),
155+
trustedSample(SignalDrivingDistance, at(t, "2026-09-07T00:50:00Z"), 50000),
156+
trustedSample(SignalFSDDistance, at(t, "2026-09-07T01:18:00Z"), 22874.752),
157+
trustedSample(SignalDrivingDistance, at(t, "2026-09-07T01:18:00Z"), 63260),
158+
}
159+
current := responseForRange(7, start, end, samples)
160+
previous := responseForRange(7, start.Add(-24*time.Hour), start, samples)
161+
162+
analytics := BuildDriveAnalytics(current, previous, AnalyticsInput{
163+
CounterSamples: samples,
164+
Drives: []DriveRecord{
165+
{ID: 349, StartedAt: fidgetStart, EndedAt: &fidgetEnd, DistanceM: &fidgetDistance},
166+
{ID: 350, StartedAt: driveStart, EndedAt: &driveEndAt, DistanceM: &commuteDistance},
167+
},
168+
}, time.UTC, true)
169+
170+
if len(analytics.ContributingDrives) != 1 {
171+
t.Fatalf("drives = %d, want 1 commute (fidget excluded)", len(analytics.ContributingDrives))
172+
}
173+
drive := analytics.ContributingDrives[0]
174+
if drive.DriveID != 350 {
175+
t.Fatalf("drive id = %d, want 350", drive.DriveID)
176+
}
177+
if drive.Confidence != ConfidenceEstimated {
178+
t.Errorf("confidence = %q, want estimated", drive.Confidence)
179+
}
180+
wantMeasured(t, drive.FSDDistanceM, 12874.752, "commute FSD after ignoring fidget overlap")
181+
}
182+
183+
func TestBuildDriveAnalytics_FocusDriveIDReportsOnlyThatDrive(t *testing.T) {
184+
start := at(t, "2026-09-07T00:00:00Z")
185+
end := at(t, "2026-09-08T00:00:00Z")
186+
firstStart := at(t, "2026-09-07T01:00:00Z")
187+
firstEnd := at(t, "2026-09-07T01:20:00Z")
188+
secondStart := at(t, "2026-09-07T03:00:00Z")
189+
secondEnd := at(t, "2026-09-07T03:20:00Z")
190+
distance := 8000.0
191+
samples := []Sample{
192+
trustedSample(SignalFSDDistance, at(t, "2026-09-07T00:59:00Z"), 1000),
193+
trustedSample(SignalDrivingDistance, at(t, "2026-09-07T00:59:00Z"), 10000),
194+
trustedSample(SignalFSDDistance, at(t, "2026-09-07T01:19:00Z"), 2609.344),
195+
trustedSample(SignalDrivingDistance, at(t, "2026-09-07T01:19:00Z"), 18000),
196+
trustedSample(SignalFSDDistance, at(t, "2026-09-07T02:59:00Z"), 2609.344),
197+
trustedSample(SignalDrivingDistance, at(t, "2026-09-07T02:59:00Z"), 18000),
198+
trustedSample(SignalFSDDistance, at(t, "2026-09-07T03:19:00Z"), 4218.688),
199+
trustedSample(SignalDrivingDistance, at(t, "2026-09-07T03:19:00Z"), 26000),
200+
}
201+
current := responseForRange(7, start, end, samples)
202+
previous := responseForRange(7, start.Add(-24*time.Hour), start, samples)
203+
204+
analytics := BuildDriveAnalytics(current, previous, AnalyticsInput{
205+
CounterSamples: samples,
206+
Drives: []DriveRecord{
207+
{ID: 1, StartedAt: firstStart, EndedAt: &firstEnd, DistanceM: &distance},
208+
{ID: 2, StartedAt: secondStart, EndedAt: &secondEnd, DistanceM: &distance},
209+
},
210+
FocusDriveID: 1,
211+
}, time.UTC, true)
212+
213+
if len(analytics.ContributingDrives) != 1 || analytics.ContributingDrives[0].DriveID != 1 {
214+
t.Fatalf("drives = %+v, want only drive 1", analytics.ContributingDrives)
215+
}
216+
wantMeasured(t, analytics.ContributingDrives[0].FSDDistanceM, 1609.344, "focused drive FSD")
217+
}
218+
144219
func TestBuildDriveAnalytics_SparseIntervalAcrossDrivesIsAmbiguous(t *testing.T) {
145220
start := at(t, "2026-03-03T08:00:00Z")
146221
end := at(t, "2026-03-03T13:00:00Z")

internal/api/fsd/drive_repo.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package fsd
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"time"
78
)
89

10+
// ErrDriveNotFound is returned by DriveByID when the vehicle has no such drive.
11+
var ErrDriveNotFound = errors.New("fsd drive not found")
12+
913
const analyticsCounterSamplesSQL = `
1014
WITH baseline AS (
1115
SELECT DISTINCT ON (field)
@@ -194,6 +198,20 @@ SELECT id,
194198
AND COALESCE(ended_at, $3) > $2
195199
ORDER BY started_at ASC, id ASC`
196200

201+
const analyticsDriveByIDSQL = `
202+
SELECT id,
203+
started_at,
204+
ended_at,
205+
NULLIF(BTRIM(start_place), ''),
206+
NULLIF(BTRIM(end_place), ''),
207+
start_geofence_id,
208+
end_geofence_id,
209+
distance_m,
210+
energy_used_wh
211+
FROM drives
212+
WHERE vehicle_id = $1
213+
AND id = $2`
214+
197215
const analyticsVersionSamplesSQL = `
198216
WITH baseline AS (
199217
SELECT ts, str_value, normalization_version
@@ -372,3 +390,36 @@ func (r *Repo) LoadAnalyticsInput(
372390

373391
return input, nil
374392
}
393+
394+
// DriveByID loads one drive for vehicle-scoped FSD focus queries.
395+
func (r *Repo) DriveByID(ctx context.Context, vehicleID, driveID int64) (DriveRecord, error) {
396+
var drive DriveRecord
397+
if r == nil || r.pool == nil {
398+
return drive, fmt.Errorf("load FSD drive %d: database pool is nil", driveID)
399+
}
400+
rows, err := r.pool.Query(ctx, analyticsDriveByIDSQL, vehicleID, driveID)
401+
if err != nil {
402+
return drive, fmt.Errorf("query FSD drive %d: %w", driveID, err)
403+
}
404+
defer rows.Close()
405+
if !rows.Next() {
406+
if err := rows.Err(); err != nil {
407+
return drive, fmt.Errorf("iterate FSD drive %d: %w", driveID, err)
408+
}
409+
return drive, fmt.Errorf("load FSD drive %d: %w", driveID, ErrDriveNotFound)
410+
}
411+
if err := rows.Scan(
412+
&drive.ID,
413+
&drive.StartedAt,
414+
&drive.EndedAt,
415+
&drive.StartPlace,
416+
&drive.EndPlace,
417+
&drive.StartGeofenceID,
418+
&drive.EndGeofenceID,
419+
&drive.DistanceM,
420+
&drive.EnergyUsedWh,
421+
); err != nil {
422+
return drive, fmt.Errorf("scan FSD drive %d: %w", driveID, err)
423+
}
424+
return drive, nil
425+
}

internal/api/fsd/drive_types.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ type AnalyticsInput struct {
4444
CounterSamples []Sample
4545
VersionSamples []VersionSample
4646
Drives []DriveRecord
47+
// FocusDriveID, when non-zero, reports only that drive as a current-period
48+
// contributor. Neighboring drives still participate in overlap so a
49+
// sparse counter interval that spans two real trips stays ambiguous.
50+
FocusDriveID int64
4751
}
4852

4953
// EvidenceInterval identifies the time span in which a cumulative FSD counter
@@ -110,13 +114,13 @@ type CounterResetEvent struct {
110114

111115
// CommuteMonthShare is one calendar month of a commute identity.
112116
type CommuteMonthShare struct {
113-
Month string `json:"month"`
114-
DriveCount int `json:"drive_count"`
115-
FSDDistanceM *float64 `json:"fsd_distance_m"`
117+
Month string `json:"month"`
118+
DriveCount int `json:"drive_count"`
119+
FSDDistanceM *float64 `json:"fsd_distance_m"`
116120
DrivingDistanceM float64 `json:"driving_distance_m"`
117-
FSDSharePct *float64 `json:"fsd_share_pct"`
118-
Confidence AttributionConfidence `json:"confidence"`
119-
UnknownDays int `json:"unknown_days"`
121+
FSDSharePct *float64 `json:"fsd_share_pct"`
122+
Confidence AttributionConfidence `json:"confidence"`
123+
UnknownDays int `json:"unknown_days"`
120124
}
121125

122126
// CommuteIdentity compares the same route and time-of-day window this month

internal/api/fsd/handler.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ const (
3939
// pool connection. The pool's per-connection statement_timeout remains
4040
// the backstop underneath this.
4141
insightsQueryBudget = 5 * time.Second
42+
// Bookend SelfDrivingMilesSinceReset samples for one drive. Tesla's
43+
// minimum_delta is 1 mile, so a commute's opener may sit hours earlier.
44+
driveFocusLookaround = 7 * 24 * time.Hour
4245
)
4346

4447
type daysLimitError struct {
@@ -61,6 +64,7 @@ type driveAnalyticsRepository interface {
6164
vehicleID int64,
6265
from, split, to time.Time,
6366
) (AnalyticsInput, error)
67+
DriveByID(ctx context.Context, vehicleID, driveID int64) (DriveRecord, error)
6468
}
6569

6670
// clock is injected so handler tests can pin the period boundary;
@@ -98,6 +102,7 @@ type request struct {
98102
endAt *time.Time
99103
explicitRange bool
100104
includeEvidence bool
105+
driveID int64
101106
}
102107

103108
// parseRequest validates every query parameter. Returns ok=false after
@@ -164,8 +169,30 @@ func parseRequest(w http.ResponseWriter, r *http.Request) (request, bool) {
164169
includeEvidence = value
165170
}
166171

172+
var driveID int64
173+
if raw := q.Get("drive_id"); raw != "" {
174+
value, err := strconv.ParseInt(raw, 10, 64)
175+
if err != nil || value <= 0 {
176+
httpx.WriteError(w, http.StatusBadRequest, "drive_id must be a positive integer")
177+
return request{}, false
178+
}
179+
driveID = value
180+
}
181+
167182
startRaw := q.Get("start")
168183
endRaw := q.Get("end")
184+
if driveID != 0 {
185+
if startRaw != "" || endRaw != "" || q.Get("days") != "" {
186+
httpx.WriteError(w, http.StatusBadRequest, "drive_id cannot be combined with days, start, or end")
187+
return request{}, false
188+
}
189+
return request{
190+
vehicleID: vehicleID,
191+
loc: loc,
192+
includeEvidence: includeEvidence,
193+
driveID: driveID,
194+
}, true
195+
}
169196
if startRaw != "" || endRaw != "" {
170197
if startRaw == "" || endRaw == "" {
171198
httpx.WriteError(w, http.StatusBadRequest, "start and end must be provided together")
@@ -247,6 +274,7 @@ func (h *Handler) Insights(w http.ResponseWriter, r *http.Request) {
247274
attribute.Int64("vehicle_id", req.vehicleID),
248275
attribute.Int("fsd.days", req.days),
249276
attribute.String("fsd.timezone", req.loc.String()),
277+
attribute.Int64("drive_id", req.driveID),
250278
)
251279

252280
fields := counterFields()
@@ -262,8 +290,41 @@ func (h *Handler) Insights(w http.ResponseWriter, r *http.Request) {
262290

263291
var resp Response
264292
if analyticsRepo, ok := h.repo.(driveAnalyticsRepository); ok {
293+
var focusDriveID int64
294+
if req.driveID != 0 {
295+
drive, err := analyticsRepo.DriveByID(readCtx, req.vehicleID, req.driveID)
296+
if err != nil {
297+
if errors.Is(err, ErrDriveNotFound) {
298+
httpx.WriteError(w, http.StatusNotFound, "drive not found")
299+
return
300+
}
301+
span.RecordError(err)
302+
log.Error().Err(err).
303+
Int64("vehicle_id", req.vehicleID).
304+
Int64("drive_id", req.driveID).
305+
Str("trace_id", traceID).
306+
Msg("fsd.insights: drive lookup failed")
307+
httpx.WriteError(w, http.StatusInternalServerError, "failed to load FSD insights")
308+
return
309+
}
310+
focusDriveID = drive.ID
311+
start = drive.StartedAt
312+
if drive.EndedAt != nil && drive.EndedAt.After(drive.StartedAt) {
313+
now = drive.EndedAt.Add(driveFocusLookaround)
314+
} else {
315+
now = h.now()
316+
if !now.After(start) {
317+
now = start.Add(time.Millisecond)
318+
}
319+
}
320+
req.days = inclusiveCivilDayCount(start, now.Add(-time.Nanosecond), req.loc)
321+
}
265322
previousEnd := start
266323
previousStart := start.Add(-now.Sub(start))
324+
if req.driveID != 0 {
325+
previousStart = start.Add(-driveFocusLookaround)
326+
previousEnd = start
327+
}
267328
input, err := analyticsRepo.LoadAnalyticsInput(
268329
readCtx,
269330
req.vehicleID,
@@ -281,6 +342,7 @@ func (h *Handler) Insights(w http.ResponseWriter, r *http.Request) {
281342
httpx.WriteError(w, http.StatusInternalServerError, "failed to load FSD insights")
282343
return
283344
}
345+
input.FocusDriveID = focusDriveID
284346

285347
resp = Aggregate(AggregateParams{
286348
VehicleID: req.vehicleID,
@@ -304,6 +366,9 @@ func (h *Handler) Insights(w http.ResponseWriter, r *http.Request) {
304366
Samples: input.PreviousCounterSamples,
305367
})
306368
resp.Analytics = BuildDriveAnalytics(resp, previous, input, req.loc, req.includeEvidence)
369+
} else if req.driveID != 0 {
370+
httpx.WriteError(w, http.StatusInternalServerError, "failed to load FSD insights")
371+
return
307372
} else {
308373
baselines, err := h.repo.BaselineSamples(readCtx, req.vehicleID, fields, start)
309374
if err != nil {

0 commit comments

Comments
 (0)