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

demo

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: 28 Imported by: 0

Documentation

Overview

Package demo seeds and identifies the public read-only demo account (B7). It is PREVIEW ONLY: Enabled requires RIVOLT_DEMO=true and RIVIAN_CLIENT=replay and RIVOLT_REPLAY_SOURCE=sample, so a production install (live client) never turns it on even if the variable leaks into its environment.

Index

Constants

View Source
const (
	HomeLat   = 34.1613
	HomeLon   = -118.1676
	HomeLabel = "Rose Bowl parking (sample)"
	// HomeRate is the demo's home $/kWh, USD.
	HomeRate = 0.16
)

The demo truck's "home": the sample generator's public, non- residential stadium parking lot (never anyone's address).

View Source
const DemoOffpeakRate = 0.09

DemoOffpeakRate is the example overnight rate of the demo's schedule, USD per kWh, against HomeRate for every other hour. An example, not any utility's tariff.

View Source
const PresetsDir = "trips"

PresetsDir is where the preset files live, relative to this package.

View Source
const Username = db.DemoUsername

Username is the demo account's username. It has no @, so it can't collide with a real (email-derived) sign-in, and nobody holds a credential for it: it is only ever reached through the anonymous, read-only allowlist.

Variables

View Source
var PresetCostsAt = time.Date(2026, 10, 20, 0, 0, 0, 0, time.UTC)

PresetCostsAt is the date the preset plans' costs were priced: the observed third-party prices in them (dcfcrates, hand-copied from DCFC Tracker) were checked on this day, and the presets are static files, so they are built "as of" it and the test that compares them with the corpus stays deterministic.

REFRESH BY OCTOBER 2027: an observed price is hidden from live plans 12 months after its month, but a preset file keeps showing the figure it was built with (with its month in the label). When the figures in dcfcrates/observed.go are re-checked, move this date, regenerate the preset plans (see presets_gen_test.go) and the sample-trip pages built from them; if they are not re-checked, regenerate with no observed prices, so the demo stops showing a figure the live planner would hide.

View Source
var SampleTripHub = SampleTripPage{
	Title: "Sample Rivian road trips, planned with Rivolt",
	Desc:  "Eight sample trips for a Rivian R1S planned by Rivolt: charging stops, arrival charge, time and estimated cost. Simulated sample data, no sign-in needed.",
	Intro: "These are eight simulated sample plans for a Rivian R1S, shown exactly as Rivolt lays them out: every charging stop with its network, the battery on arrival and departure, charge time, and the estimated cost. " +
		"The figures are estimates from the sample truck's efficiency, the route and the chargers along it; a real trip will differ with weather, traffic and charger speed. " +
		"With your own Rivian connected, the plan comes from Rivian's planner plus Rivolt's adjustment for the forecast weather along the route. " +
		"Pick a trip to see the day-by-day plan, open it in the demo planner, or plan your own.",
}

SampleTripHub is the copy the removed /sample-trips hub page used (growth, DA-approved); kept with the presets.

View Source
var SampleTripPages = []SampleTripPage{
	{
		Slug:     "la-las-vegas-two-days",
		PresetID: "919ca13a-123f-5423-994d-12d7ccd31319",
		Title:    "LA to Las Vegas and back in a Rivian: a two-day sample plan",
		Desc:     "A two-day Rivian R1S trip from Los Angeles to Las Vegas and back: a fast-charge stop at Barstow, a hotel night on Level 2, and the estimated cost per day.",
		Intro: "Two driving days and two nights in Las Vegas, 530 miles in all, from Pasadena and home again. " +
			"Day one stops once, at the Rivian Adventure Network site in Barstow, and ends at a Las Vegas hotel with a Level 2 charger; over the stay, charging at 6 kW adds about 79 kWh and stops at 90%. " +
			"The drive home needs only a short top-up at Primm. " +
			"Times, charge levels and costs are estimates for the sample truck, and the hotel is a sample stop with a Level 2 charger, not a real listing; on a real trip, check a hotel's charger before you rely on it.",
		Blurb: "Los Angeles to Las Vegas and back in two days: a hotel overnight on Level 2, one fast-charge stop each way.",
	},
	{
		Slug:     "pasadena-san-francisco",
		PresetID: "san-francisco",
		Title:    "Pasadena to San Francisco in a Rivian: I-5 with one stop",
		Desc:     "A 381-mile Rivian R1S trip up I-5 from Pasadena to the Ferry Building, planned by Rivolt: one fast-charge stop at Harris Ranch and an estimated cost.",
		Intro: "The classic I-5 run: 381 miles from the Rose Bowl to the Ferry Building in about 5 h 40 min of driving, about 6 h 15 min with the stop. " +
			"Leaving at 90%, the plan makes one fast-charge stop at Harris Ranch, arriving at 18% and charging to 80% in about 36 minutes using a NACS adapter, then reaches San Francisco with 16% left. " +
			"Every figure is the planner's estimate for the sample truck; your truck, the weather and the charger on the day will change them.",
		Blurb: "Pasadena to San Francisco on I-5: 381 miles with one fast-charge stop at Harris Ranch.",
	},
	{
		Slug:     "la-mammoth-lakes",
		PresetID: "mammoth",
		Title:    "LA to Mammoth Lakes in a Rivian: one stop to the mountains",
		Desc:     "A 314-mile Rivian R1S sample trip from Los Angeles to Mammoth Mountain Main Lodge, planned by Rivolt: one fast-charge stop and an estimated arrival charge.",
		Intro: "From Union Station to Mammoth Mountain Main Lodge at about 9,000 feet: 314 miles. " +
			"The plan stops once on the way, at the TA Travel Center fast chargers, charging from 22% to 78% in about 33 minutes with a NACS adapter, and arrives with 16%. " +
			"This sample plan does not model the climb; when you plan a real trip with your Rivian connected, the route comes from Rivian's planner and Rivolt adjusts for the forecast weather along the route, which matters on a winter run to the mountains. " +
			"All figures are estimates for the sample truck.",
		Blurb: "Los Angeles to Mammoth Lakes: 314 miles to the mountains with one fast-charge stop.",
	},
	{
		Slug:     "seattle-portland",
		PresetID: "seattle-portland",
		Zone:     "America/Los_Angeles",
		Title:    "Seattle to Portland in a Rivian: a sample plan, no stops",
		Desc:     "A 182-mile Rivian R1S sample trip down I-5 from Seattle to Portland, planned by Rivolt: no charging stop needed, arrival charge and estimated cost.",
		Intro:    "The I-5 run most Northwest owners know: 182 miles from downtown Seattle to downtown Portland in about 2 h 54 min. Leaving at 90%, the sample plan needs no charging stop and arrives with about 26%. Every figure is an estimate for the sample truck; traffic, weather and your own driving will change them.",
		Blurb:    "Seattle to Portland: 182 miles, no stops.",
	},
	{
		Slug:     "denver-moab",
		PresetID: "denver-moab",
		Zone:     "America/Denver",
		Title:    "Denver to Moab in a Rivian: a sample plan over the Rockies",
		Desc:     "A 355-mile Rivian R1S sample trip from Denver to Moab on I-70, planned by Rivolt: one fast-charge stop at Rifle, arrival charge and estimated cost.",
		Intro:    "Denver to Moab over I-70 and down US-191: 355 miles with real mountain passes on the way, about 5 h 50 min including charging. The sample plan stops once, at the Rifle fast chargers at mile 182, charging from 16% to 75% in about 34 minutes with a NACS adapter, and arrives with about 16%. The sample plan does not model the climb; with your own Rivian connected, the route comes from Rivian's planner and Rivolt adjusts for the forecast weather along the route. All figures are estimates for the sample truck.",
		Blurb:    "Denver to Moab: 355 miles, one stop at Rifle.",
	},
	{
		Slug:     "sf-tahoe",
		PresetID: "sf-tahoe",
		Zone:     "America/Los_Angeles",
		Title:    "San Francisco to Lake Tahoe in a Rivian: a sample plan",
		Desc:     "A 190-mile Rivian R1S sample trip from San Francisco to South Lake Tahoe, planned by Rivolt: a quick top-up in Folsom, arrival charge and estimated cost.",
		Intro:    "The Bay to the lake: 190 miles up I-80 and US-50 to South Lake Tahoe, about 3 h 19 min. Leaving at 90% would arrive just under the 10% target, so the sample plan takes a quick top-up in Folsom at mile 110, 52% to 58% in about 4 minutes with a NACS adapter, and arrives with about 15%. Elevation is not modelled in this sample plan, so a winter run will differ; every figure is an estimate for the sample truck.",
		Blurb:    "San Francisco to Lake Tahoe: 190 miles, a quick top-up in Folsom.",
	},
	{
		Slug:     "la-phoenix",
		PresetID: "la-phoenix",
		Zone:     "America/Phoenix",
		Title:    "Los Angeles to Phoenix in a Rivian: a sample plan on I-10",
		Desc:     "A 374-mile Rivian R1S sample trip from Los Angeles to Phoenix on I-10, planned by Rivolt: two charging stops, arrival charge and estimated cost.",
		Intro:    "374 miles of I-10 across the desert, the drive Southern California owners make for a weekend in Arizona, about 6 h 20 min including charging. Leaving at 90% the sample plan stops twice: the Indio fast chargers at mile 126 (45% to 90%, about 26 minutes, NACS adapter) and a slower 62 kW charger at a dealership in Buckeye at mile 342 (12% to 26%, about 28 minutes), arriving with about 15%. That second stop is what the simple planner picks, not a recommendation. Summer heat lowers range; the sample plan does not model it, and with your own Rivian connected Rivolt adjusts for the forecast weather along the route. Every figure is an estimate for the sample truck.",
		Blurb:    "Los Angeles to Phoenix: 374 miles, two stops.",
	},
	{
		Slug:     "nyc-boston",
		PresetID: "nyc-boston",
		Zone:     "America/New_York",
		Title:    "New York to Boston in a Rivian: a sample plan on I-95",
		Desc:     "A 212-mile Rivian R1S sample trip from Manhattan to Boston on I-95, planned by Rivolt: no charging stop, arrival charge to spare, estimated cost.",
		Intro:    "Manhattan to Boston, 212 miles of I-95 and the turnpikes in about 3 h 42 min, with tolls you can choose to avoid in the planner. Leaving at 90% the sample plan gets there without stopping, with a little to spare: about 12% on arrival against a 10% target. Every figure is an estimate for the sample truck.",
		Blurb:    "New York to Boston: 212 miles, no stops, a little to spare.",
	},
}

SampleTripPages lists the pages in hub order, with growth's copy (sample-trips-copy.md, DA-approved; figures from the fixtures).

Functions

func AppURL added in v0.34.0

func AppURL() string

AppURL is the real app's origin (RIVOLT_APP_URL), where the standalone demo's sign-in and sign-up links point. Only a plain http(s) origin is accepted (no credentials, query or fragment); anything else is "" so the config never carries a link the operator didn't mean. A trailing slash and any path are dropped.

func ApplySampleUserPrices added in v0.33.0

func ApplySampleUserPrices(ctx context.Context, d *sql.DB, store pricer) (int, error)

ApplySampleUserPrices gives the demo truck's hand-priced session (the Las Vegas hotel L2, a flat $15 in the sample corpus) its user price after replay has recorded it. The recorder cannot invent a price only a person enters, so this is the demo's stand-in for that person. It uses the same store path as PATCH /charges/{id}/pricing, which stamps cost_source = user.

Only CLOSED charges with no cost source are touched: an open charge is still being upserted by the recorder, which would overwrite the price. Idempotent; returns how many rows it priced. The corpus loops, so the caller runs it again periodically.

func ApplySampleWeather added in v0.38.4

func ApplySampleWeather(ctx context.Context, d *sql.DB) (int64, error)

ApplySampleWeather fills the sample weather. Idempotent; returns the rows added. The corpus loops and the replay records new drives, so the caller runs it again periodically.

func DemoNetworks added in v0.38.8

func DemoNetworks() []settings.ChargingNetwork

DemoNetworks is the demo user's price book: sample rates the "sample owner" has entered, so the demo's fast-charging sessions (priced from the user's own rate) keep their network estimates. The planner and the ledger no longer price anything from the dated built-in table, which shows no rate until a user enters one; these are fixtures like the rest of the demo, 2 cents off the table so that they read as the user's own (a row equal to the table's number is treated as never edited).

func DemoSchedule added in v0.38.10

func DemoSchedule() offpeak.Schedule

DemoSchedule is the demo owner's example off-peak schedule: weekday nights from 21:00 to 06:00 in the home's time zone (Pasadena, Pacific time). It is what the public demo's savings card is computed from, shown as "Sample truck · example schedule". Normalized (the same checks as a user's schedule); the panic is for a programming error caught by the tests.

func Enabled

func Enabled() bool

Enabled reports whether the demo is on: RIVOLT_DEMO=true on an install running the replay client over the GENERATED sample corpus (RIVOLT_REPLAY_SOURCE=sample). Replaying a recorded vehicle would publish a real person's history anonymously, so anything else fails closed.

func MisconfiguredOnly added in v0.34.0

func MisconfiguredOnly() bool

MisconfiguredOnly reports a half-configured standalone demo: RIVOLT_DEMO_ONLY=true and RIVOLT_DEMO=true, but not the replay sample setup the demo needs. The caller refuses to boot: serving a half-demo install to the public is worse than not starting.

func OnlyEnabled added in v0.34.0

func OnlyEnabled() bool

OnlyEnabled reports whether this install is the standalone public demo (demo.rivolt.dev): RIVOLT_DEMO_ONLY=true on an install where Enabled holds. It is a strict subset of Enabled, so a prod install (live client) can never turn it on even if the variable leaks into its environment. In this mode the process serves nothing but the demo: no sign-in, signup, mail, push or events, and every /api route outside the demo allowlist is a 404.

func PlanFixtures added in v0.37.0

func PlanFixtures() ([]rivian.PlanFixture, error)

PlanFixtures turns the preset trips into stored plans for the fixture planner (rivian.FixturePlanner): each preset's origin, destination and starting SoC with its plan. Presets with intermediate stops are left out, as the fixture planner matches two-point trips only.

func PresetIDs added in v0.34.0

func PresetIDs() []string

PresetIDs lists the preset ids in index order.

func PresetsErr added in v0.34.0

func PresetsErr() error

PresetsErr reports why the embedded presets were rejected, if they were.

func PriceCandidates added in v0.33.0

func PriceCandidates(ctx context.Context, cands []PriceCandidate, store pricer) (int, error)

PriceCandidates prices the candidates the sample corpus says a person priced by hand, and returns how many it priced.

func RunPlans added in v0.34.0

func RunPlans(ctx context.Context, d *sql.DB, sf *settings.Factory, every time.Duration, log func(msg string, args ...any))

RunPlans seeds the saved plans now and then every interval until ctx ends, so they follow the replay onto each loop's trip.

func RunPricing added in v0.33.0

func RunPricing(ctx context.Context, d *sql.DB, store pricer, every time.Duration, log func(msg string, args ...any))

RunPricing applies the sample user prices now and then every interval until ctx ends (replay backfill and corpus loops bring new sessions).

func RunWeather added in v0.38.4

func RunWeather(ctx context.Context, d *sql.DB, every time.Duration, log func(msg string, args ...any))

RunWeather applies the sample weather now and then every interval until ctx ends.

func SampleTripID added in v0.34.0

func SampleTripID(name string) uuid.UUID

SampleTripID is the saved trip id of the sample plan called name: fixed, so the demo's preset trip for it (internal/demo/trips) carries the same id on every install.

func SampleTripIDs added in v0.34.0

func SampleTripIDs() map[string]uuid.UUID

SampleTripIDs maps each sample plan's name to its saved trip id.

func SampleTripsErr added in v0.37.7

func SampleTripsErr() error

SampleTripsErr reports a preset the pages couldn't be built from (tests, startup log).

func Seed

func Seed(ctx context.Context, d Deps) error

Seed creates (or refreshes) the demo user, its settings, its replay session and a few older charges. Idempotent; safe at every boot. It does nothing unless Enabled.

func SeedCharges

func SeedCharges(uid uuid.UUID, now time.Time) []charges.Charge

SeedCharges are the hand-written older sessions that complete the cost-source set (the replay stream supplies home_rate, network_estimate, the Rivian-billed DC fast and, via ApplySampleUserPrices, the user-priced hotel L2): a pending (unpriced) AC session and a home-rate home session. They sit 50-60 days back, before the 45-day replay corpus, so they never overlap a replayed session. Times are day-aligned, so reseeding is stable.

func SeedDCFCharges added in v0.38.4

func SeedDCFCharges(uid uuid.UUID, now time.Time) []charges.Charge

SeedDCFCharges are three DC fast stops at 62, 79 and 96 days back that add 58% of the battery (from 18%), with effective pack figures of 129 to 133 kWh, so the capacity chart has enough qualifying charges over enough days. They are user-priced like the hotel L2 so they don't read as pending.

func SeedDrives added in v0.38.4

func SeedDrives(uid uuid.UUID, now time.Time) []drives.Drive

SeedDrives are short daily-commute drives from 47 to 119 days back, one every other day, 9 to 17 mi at the sample truck's 2.3 mi/kWh. They keep every calendar month in that span above the trend's 50 mi.

func SeedPlans added in v0.34.0

func SeedPlans(ctx context.Context, d *sql.DB, sf *settings.Factory, now time.Time) (int, error)

SeedPlans writes the demo's saved plans for now, and returns how many rows it changed. Idempotent: a row is rewritten only when its plan or inputs changed (the next loop's trip, or new settings).

func StrayOnly added in v0.34.0

func StrayOnly() bool

StrayOnly reports RIVOLT_DEMO_ONLY=true without RIVOLT_DEMO=true: not a demo install at all, just a stray variable. It is ignored (the caller logs a loud warning) rather than refused, so it can never take a real install down.

func TripZone added in v0.37.7

func TripZone() *time.Location

TripZone is the zone the original sample trips' times are shown in.

func UserID

func UserID() uuid.UUID

UserID is the demo account's user id.

func VehicleID

func VehicleID() string

VehicleID is the demo vehicle's Rivian id.

func ZoneOf added in v0.40.9

func ZoneOf(t SampleTrip) *time.Location

ZoneOf is the zone a trip's times are shown in: its own, else Pacific.

Types

type Deps

type Deps struct {
	DB       *sql.DB
	Settings *settings.Factory
	Charges  *charges.Factory
	Drives   *drives.Factory // optional: without it the older history is not seeded
	Secrets  *secrets.Store
	Now      func() time.Time
}

Deps are what Seed writes through.

type Preset added in v0.34.0

type Preset struct {
	ID      string          `json:"id"`
	Name    string          `json:"name"`
	Inputs  json.RawMessage `json:"inputs"`
	Summary PresetSummary   `json:"summary"`
	// RoutePolyline is the route's road shape (Valhalla's, simplified)
	// through every waypoint, as a Google encoded polyline of precision
	// 6, for the map: the demo can't ask Valhalla for it.
	RoutePolyline string `json:"route_polyline"`
}

Preset is one preset trip in index.json.

type PresetBlob added in v0.34.0

type PresetBlob struct {
	Body []byte
	ETag string
}

PresetBlob is a static JSON document with its ETag.

func PresetPlan added in v0.34.0

func PresetPlan(id string) (PresetBlob, bool)

PresetPlan returns the stored plan for a preset id.

func PresetsIndex added in v0.34.0

func PresetsIndex() PresetBlob

PresetsIndex is the preset list document, `{"trips":[...]}`. Empty list when the data is absent or malformed (see PresetsErr).

type PresetIndex added in v0.34.0

type PresetIndex struct {
	Trips []Preset `json:"trips"`
}

PresetIndex is index.json.

type PresetSummary added in v0.34.0

type PresetSummary struct {
	DistanceMi    float64 `json:"distance_mi"`
	DurationMin   float64 `json:"duration_min"`
	ChargeStops   int     `json:"charge_stops"`
	ArrivalSoCPct float64 `json:"arrival_soc_pct"`
}

PresetSummary is what the preset list shows.

type PriceCandidate added in v0.33.0

type PriceCandidate struct {
	ID        string
	StartedAt time.Time
}

PriceCandidate is a closed, unpriced demo charge.

type SamplePlan added in v0.34.0

type SamplePlan struct {
	ID       uuid.UUID // fixed: SampleTripID(Name)
	Name     string
	SavedAt  time.Time
	Inputs   json.RawMessage // the planner's SavedTripInputs
	Plan     json.RawMessage // rivian.TripPlan plus costs, as /api/trips/plan returns it
	DepartAt time.Time
}

SamplePlan is one saved trip as the planner stores it.

func SamplePlans added in v0.34.0

func SamplePlans(now, since time.Time, cfg settings.ChargingConfig, nets []settings.ChargingNetwork) ([]SamplePlan, error)

SamplePlans returns the demo's saved plans for the latest sample road trip over by now that started at or after since (the demo truck's first recorded frame, so the trip is recorded whole), priced with the demo user's charging settings (cfg, nets) as /api/trips/plan prices a plan. Nil when there is no such trip.

type SampleTrip added in v0.37.7

type SampleTrip struct {
	Slug string `json:"slug"`
	// TZ is the IANA zone of the trip's clock times (the page names none; the server's table prints them in it).
	TZ        string          `json:"tz"`
	Title     string          `json:"title"`
	Intro     string          `json:"intro"`
	DemoURL   string          `json:"demo_url"`
	SignupURL string          `json:"signup_url"`
	Currency  string          `json:"currency"`
	Totals    SampleTripTotal `json:"totals"`
	Days      []SampleTripDay `json:"days"`
	// Route is the road shape, [lat, lon] pairs, for the map.
	Route [][2]float64 `json:"route"`
}

SampleTrip is the page model: the server renders it and the page component reads it as JSON (contract agreed with ux).

func SampleTripBySlug added in v0.37.7

func SampleTripBySlug(slug string) (SampleTrip, bool)

SampleTripBySlug returns a page's model, built once from the presets.

type SampleTripDay added in v0.37.7

type SampleTripDay struct {
	Label      string               `json:"label"`
	DepartAt   time.Time            `json:"depart_at"`
	ArriveAt   time.Time            `json:"arrive_at"`
	From       string               `json:"from"`
	To         string               `json:"to"`
	DistanceMi float64              `json:"distance_mi"`
	DriveMin   float64              `json:"drive_min"`
	ArrivalSoC float64              `json:"arrival_soc"`
	Stops      []SampleTripStop     `json:"stops"`
	Overnight  *SampleTripOvernight `json:"overnight"`
}

SampleTripDay is one driving day.

type SampleTripOvernight added in v0.37.7

type SampleTripOvernight struct {
	Name string `json:"name"`
	// Nights is how many nights the stop spans (local dates between arriving
	// and leaving): the Las Vegas preset arrives Saturday and leaves Monday.
	Nights      int     `json:"nights"`
	ParkedHours float64 `json:"parked_hours"`
	L2KW        float64 `json:"l2_kw"`
	AddedKWh    float64 `json:"added_kwh"`
	SoCAfter    float64 `json:"soc_after"`
}

SampleTripOvernight is a slow charge where the trip stops for the night.

type SampleTripPage added in v0.37.7

type SampleTripPage struct {
	Slug     string
	PresetID string
	Title    string // <title> and h1
	Desc     string // meta description
	Intro    string
	Blurb    string // the hub card's one line
	// Zone is the IANA zone the trip's clock times are shown in (where it happens); empty is Pacific, the original three.
	Zone string
}

SampleTripPage is one page: its slug, the preset it shows, and its copy.

type SampleTripStop added in v0.37.7

type SampleTripStop struct {
	Name      string   `json:"name"`
	Lat       float64  `json:"lat"`
	Lon       float64  `json:"lon"`
	Network   string   `json:"network"`
	ArriveSoC float64  `json:"arrive_soc"`
	DepartSoC float64  `json:"depart_soc"`
	ChargeMin float64  `json:"charge_min"`
	MaxKW     float64  `json:"max_kw"`
	CostLow   *float64 `json:"cost_low"`
	CostHigh  *float64 `json:"cost_high"`
	PriceNote string   `json:"price_note,omitempty"`
	// PriceMonth is the observation month of the figure behind CostLow/CostHigh.
	PriceMonth string `json:"price_month,omitempty"`
	// PriceURL is the tracker report the figure was copied from (https on
	// dcfctracker.com only); empty when there is no figure.
	PriceURL string `json:"price_url,omitempty"`
}

SampleTripStop is a charging stop. CostLow and CostHigh are the energy at the network's typical observed price (an estimate, a range where the tracker reports a membership and a non-member price); nil when the network has no figure and the price is shown at the charger. PriceNote says where the figure comes from.

type SampleTripTotal added in v0.37.7

type SampleTripTotal struct {
	DistanceMi  float64 `json:"distance_mi"`
	DriveMin    float64 `json:"drive_min"`
	ChargeMin   float64 `json:"charge_min"`
	ChargeStops int     `json:"charge_stops"`
	// DCFCCostLow and DCFCCostHigh are the fast-charging cost over the stops that have
	// a price (an estimate: the third-party typical price, see dcfcrates.ObservedFor),
	// PricedStops of ChargeStops. The other stops' price is shown at the charger.
	DCFCCostLow   float64 `json:"dcfc_cost_low"`
	DCFCCostHigh  float64 `json:"dcfc_cost_high"`
	PricedStops   int     `json:"priced_stops"`
	UnpricedStops int     `json:"unpriced_stops"`
	// PriceMonth is the observation month ("2026-10") of the oldest figure behind
	// the estimate, so every wording of it can name the month.
	PriceMonth string `json:"price_month,omitempty"`
}

SampleTripTotal sums the trip.

Jump to

Keyboard shortcuts

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