Skip to content

Commit 3093664

Browse files
author
Greg
committed
Demo: embed a real featured road trip (Livigno → Olomouc)
Five legs and four supercharger stops with genuine GPS traces, city names, and recorded charging curves, exported from a real TeslaMate instance with owner approval. No home locations; timestamps are relative offsets anchored at runtime to five days ago, so the trip stays fresh. Fixed IDs (drives 1500+, charges 2500+) make the selection shareable: ?sel=d1500,d1501,d1502,d1503,d1504,c2500,c2501,c2502,c2503 Charge details serve the embedded real power curve when present, falling back to the synthetic taper otherwise.
1 parent 5f00145 commit 3093664

3 files changed

Lines changed: 94 additions & 15 deletions

File tree

CLAUDE.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ commands take `./src`).
2424
- **No telemetry, no outbound server calls.** The server must not phone home. Browser-side external
2525
requests are basemap tiles (configured style URL) and Google Fonts only.
2626
- **Privacy first.** This is someone's home and movements. Do not log coordinates. Never commit real
27-
data; demo data only.
27+
data; demo data only. (Owner-approved exception: `src/demo_trip.json` is a real road trip —
28+
genuine traces, cities, and charging curves — but it must never contain home locations or real
29+
dates; timestamps are stored as relative offsets and anchored at runtime.)
2830
- **Stay a companion.** Do not modify TeslaMate's schema or write to its tables. Ride alongside.
2931

3032
## Conventions

src/demo.go

Lines changed: 90 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package main
22

33
import (
44
"context"
5+
_ "embed"
6+
"encoding/json"
57
"math"
68
"math/rand"
79
"sort"
@@ -39,6 +41,41 @@ type demoCharge struct {
3941
category string
4042
title string
4143
pt []float64
44+
curve [][]float64 // optional [soc, kw]; synthesized when empty
45+
}
46+
47+
// demoTripJSON is a real multi-leg road trip donated to the demo dataset:
48+
// GPS traces, cities, and charging curves are genuine, but it is anchored to
49+
// a synthetic date and contains no home location.
50+
//
51+
//go:embed demo_trip.json
52+
var demoTripJSON []byte
53+
54+
type demoTrip struct {
55+
Drives []struct {
56+
OffsetMin int `json:"offset_min"`
57+
Km float64 `json:"km"`
58+
DurMin int `json:"dur_min"`
59+
SpeedMax float64 `json:"speed_max"`
60+
From string `json:"from"`
61+
To string `json:"to"`
62+
S0 int `json:"s0"`
63+
S1 int `json:"s1"`
64+
KWh float64 `json:"kwh"`
65+
Coords [][]float64 `json:"coords"`
66+
} `json:"drives"`
67+
Charges []struct {
68+
OffsetMin int `json:"offset_min"`
69+
KWh float64 `json:"kwh"`
70+
DurMin int `json:"dur_min"`
71+
S0 int `json:"s0"`
72+
S1 int `json:"s1"`
73+
PeakKw float64 `json:"peak_kw"`
74+
Name string `json:"name"`
75+
Lng float64 `json:"lng"`
76+
Lat float64 `json:"lat"`
77+
Curve [][]float64 `json:"curve"`
78+
} `json:"charges"`
4279
}
4380

4481
// Centre of the synthetic world (a neutral land point used only for demo
@@ -134,9 +171,37 @@ func newDemoStore(units string) *demoStore {
134171
addDrive(day-1, 10, lake, hills)
135172
addDrive(day-1, 16, hills, home)
136173
}
174+
s.addFeaturedTrip(now)
137175
return s
138176
}
139177

178+
// addFeaturedTrip loads the embedded real road trip, anchored so it departs
179+
// five days ago in the evening. IDs are fixed (drives 1500+, charges 2500+)
180+
// so the trip can be shared with a stable ?sel= URL.
181+
func (s *demoStore) addFeaturedTrip(now time.Time) {
182+
var trip demoTrip
183+
if err := json.Unmarshal(demoTripJSON, &trip); err != nil {
184+
return
185+
}
186+
day := now.AddDate(0, 0, -5)
187+
anchor := time.Date(day.Year(), day.Month(), day.Day(), 21, 10, 0, 0, now.Location())
188+
for i, d := range trip.Drives {
189+
s.drives = append(s.drives, demoDrive{
190+
id: 1500 + i, start: anchor.Add(time.Duration(d.OffsetMin) * time.Minute),
191+
km: d.Km, durMin: d.DurMin, speedMax: d.SpeedMax,
192+
from: d.From, to: d.To, s0: d.S0, s1: d.S1, kwh: d.KWh, coords: d.Coords,
193+
})
194+
}
195+
for i, c := range trip.Charges {
196+
s.charges = append(s.charges, demoCharge{
197+
id: 2500 + i, start: anchor.Add(time.Duration(c.OffsetMin) * time.Minute),
198+
kwh: c.KWh, durMin: c.DurMin, s0: c.S0, s1: c.S1, peakKw: c.PeakKw,
199+
category: "supercharger", title: c.Name,
200+
pt: []float64{c.Lng, c.Lat}, curve: c.Curve,
201+
})
202+
}
203+
}
204+
140205
func (s *demoStore) Cars(ctx context.Context) ([]Car, error) {
141206
return []Car{{ID: 1, Name: "Demo", Model: "Model Y"}}, nil
142207
}
@@ -297,22 +362,33 @@ func (s *demoStore) Detail(ctx context.Context, id string) (*Detail, error) {
297362
continue
298363
}
299364
det := &Detail{Activity: s.chargeActivity(c)}
300-
// Plausible charging taper: flat to ~42% SoC, declining after.
301365
sum, minKw := 0.0, c.peakKw
302-
steps := max(c.s1-c.s0, 1)
303-
for i := 0; i <= steps; i++ {
304-
soc := float64(c.s0 + i)
305-
f := 1.0
306-
if soc < 15 {
307-
f = 0.5 + 0.033*soc
308-
} else if soc > 42 {
309-
f = math.Max(0.1, 1-(soc-42)*0.017)
366+
if len(c.curve) > 0 {
367+
// Real recorded curve embedded with the featured trip.
368+
for _, p := range c.curve {
369+
det.Curve = append(det.Curve, CurvePoint{Soc: p[0], Kw: p[1]})
370+
sum += p[1]
371+
if p[1] < minKw {
372+
minKw = p[1]
373+
}
310374
}
311-
kw := math.Round(c.peakKw * f)
312-
det.Curve = append(det.Curve, CurvePoint{Soc: soc, Kw: kw})
313-
sum += kw
314-
if kw < minKw {
315-
minKw = kw
375+
} else {
376+
// Plausible charging taper: flat to ~42% SoC, declining after.
377+
steps := max(c.s1-c.s0, 1)
378+
for i := 0; i <= steps; i++ {
379+
soc := float64(c.s0 + i)
380+
f := 1.0
381+
if soc < 15 {
382+
f = 0.5 + 0.033*soc
383+
} else if soc > 42 {
384+
f = math.Max(0.1, 1-(soc-42)*0.017)
385+
}
386+
kw := math.Round(c.peakKw * f)
387+
det.Curve = append(det.Curve, CurvePoint{Soc: soc, Kw: kw})
388+
sum += kw
389+
if kw < minKw {
390+
minKw = kw
391+
}
316392
}
317393
}
318394
det.AvgKw = math.Round(sum / float64(len(det.Curve)))

src/demo_trip.json

Lines changed: 1 addition & 0 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)