Documentation
¶
Overview ¶
Package dcfcrates owns the DCFC network table used by trip planning. It lives in its own package so the settings layer (which stores user-edited overrides) and the tripadvice layer (which consumes both) can share it without the settings -> tripadvice -> rivian -> settings import cycle that the older single-package layout produced.
Index ¶
- Constants
- Variables
- func DisplayNetwork(known string) string
- func IsDCOnlyNode(poi *PoiLike) bool
- func NetworkKey(parts ...string) string
- func StationQuery(nrelNetwork, stationName string) string
- type Candidate
- type DetectInput
- type Detection
- type MembershipTerm
- type Network
- type NetworkOverride
- type ObservedPrice
- type PinChoice
- type PoiLike
- type ResolvedRate
- type ShownMembershipTerm
- type SiteMemory
- type StopNetwork
Constants ¶
const ( StateEstimated = "estimated" StateNeedsConfirmation = "needs_confirmation" StateConfirmed = "confirmed" )
Verify states (charges.network_state).
const ( SiteMemoryRadiusM = 100.0 // a verified charge of the user's own at the same site NearRadiusM = 150.0 // one network this close: confident SiteRadiusM = 250.0 // stations this close count as the site )
Detection radii, metres.
const ( KeyTesla = "tesla" KeyTeslaDestination = "tesla_destination" KeyRivian = "rivian" KeyRivianWaypoints = "rivian_waypoints" KeyElectrifyAmerica = "electrify_america" KeyEVgo = "evgo" KeyChargePoint = "chargepoint" KeyIONNA = "ionna" KeyShellRecharge = "shell_recharge" )
Network keys. "" is "no network recognised".
const ConfidentAt = 0.85
ConfidentAt is the confidence an estimate needs to stand without asking the user (state estimated rather than needs_confirmation).
const ObservedSource = "DCFC Tracker"
ObservedSource is the tracker's name, shown with every figure.
Variables ¶
var Networks = []Network{ { Slug: "ea", DisplayName: "Electrify America", MatchPatterns: []string{"electrify america"}, GuestRate: 0.48, MemberRate: f64(0.36), MemberPlan: "Pass+ - $7/mo", RivianIDs: []string{"10027"}, }, { Slug: "tesla_sc", DisplayName: "Tesla Supercharger", MatchPatterns: []string{"[tesla]", "tesla supercharger", "supercharger"}, GuestRate: 0.55, MemberRate: f64(0.40), MemberPlan: "Supercharging Membership - $12.99/mo", RivianIDs: []string{"10050"}, }, { Slug: "ran", DisplayName: "Rivian Adventure Network", MatchPatterns: []string{"rivian adventure", "adventure network", "rivian ran"}, GuestRate: 0.60, MemberRate: f64(0.54), MemberPlan: "Rivian owner discount", RivianIDs: []string{"10001", "10002"}, DefaultMemberActive: true, }, { Slug: "evgo", DisplayName: "EVgo", MatchPatterns: []string{"evgo"}, GuestRate: 0.42, MemberRate: f64(0.34), MemberPlan: "EVgo Plus - $6.99/mo", RivianIDs: []string{"10031"}, }, { Slug: "shell_recharge", DisplayName: "Shell Recharge", MatchPatterns: []string{"shell recharge", "shell ev", "greenlots"}, GuestRate: 0.43, MemberRate: f64(0.40), MemberPlan: "Recharge Rewards - free tier", RivianIDs: []string{"10045"}, }, { Slug: "chargepoint", DisplayName: "ChargePoint", MatchPatterns: []string{"chargepoint"}, GuestRate: 0.45, MemberRate: nil, MemberPlan: "", RivianIDs: []string{"10025"}, }, { Slug: "ionna", DisplayName: "Ionna", MatchPatterns: []string{"ionna"}, GuestRate: 0.40, MemberRate: nil, MemberPlan: "", RivianIDs: []string{"10056"}, }, { Slug: "flo", DisplayName: "Flo", MatchPatterns: []string{"[flo]", "flo charging", " flo "}, GuestRate: 0.35, MemberRate: nil, MemberPlan: "", RivianIDs: []string{"10032", "10033"}, }, { Slug: "ev_connect", DisplayName: "EV Connect", MatchPatterns: []string{"ev connect", "evconnect"}, GuestRate: 0.40, MemberRate: nil, MemberPlan: "", RivianIDs: []string{"10030"}, }, }
Networks is the lookup table, ordered most-specific-first so the substring matcher returns the right hit. Last entry (slug "unmatched") is the fallthrough - its patterns never match, but MatchNetwork returns it when nothing else does.
var UnmatchedNetwork = Network{ Slug: "unmatched", DisplayName: "Unknown DCFC", GuestRate: 0.46, MemberRate: nil, MemberPlan: "", }
UnmatchedNetwork is the fallback when MatchNetwork can't pin a stop to a known operator. Pinned at "unknown" rather than guessing - every per-stop quote should be traceable to a row in this file.
Functions ¶
func DisplayNetwork ¶ added in v0.33.0
DisplayNetwork is a friendly canonical name, else the string itself.
func IsDCOnlyNode ¶ added in v0.33.0
IsDCOnlyNode reports a node an AC session can't be at.
func NetworkKey ¶ added in v0.33.0
NetworkKey reads a free-form network, operator, brand or name string.
func StationQuery ¶ added in v0.32.0
StationQuery turns an NREL station's network code and name into the text ResolveRate matches, so a recorded DCFC session priced from the station it happened at resolves the same rows a planned stop does.
Types ¶
type Candidate ¶ added in v0.33.0
type Candidate struct {
Name string // station name
Network string // the archive's network field (NREL ev_network, renamed)
Operator string // the archive's operator field (often a URL)
DistM float64 // from the charge position
MaxPowerKW float64 // 0 when unknown
}
Candidate is an archive DC fast charging station near the charge.
type DetectInput ¶ added in v0.33.0
type DetectInput struct {
RivianCharger bool // Rivian reported isRivianCharger for this DC session
Site *SiteMemory // nil when none within SiteMemoryRadiusM
Candidates []Candidate // DC stations within SiteRadiusM, any order
PeakKW float64 // the session's peak power, 0 when unknown
}
DetectInput is everything Detect looks at.
type Detection ¶ added in v0.33.0
type Detection struct {
Query string
Network string // display name of the detected network ("" when none)
State string
Confidence float64
Evidence json.RawMessage
}
Detection is Detect's verdict. Query is what to price with (ResolveRate(Query, user overrides)); empty Query means nothing to price: the charge stays unpriced and needs the user.
func Detect ¶ added in v0.33.0
func Detect(in DetectInput, pos string) Detection
Detect classifies a DC fast charge Rivian didn't bill. Signals, first decisive one wins: Rivian's isRivianCharger, the user's own verified charge at the same site, then archive stations by distance. At a site with more than one network the nearest is the estimate, a peak only one candidate could deliver can pick another, and either way the user is asked. pos labels where the charge position came from ("open" or "last") for the evidence.
type MembershipTerm ¶ added in v0.38.10
type MembershipTerm struct {
Slug string // the network's slug in Networks
Plan string
Terms string // as the operator states it
SourceURL string // the operator page
// ReadVia says how the text was read: "page" (the operator's page was fetched) or
// "search snippet" (the page itself could not be fetched; the text is what the
// operator's own page showed in search results).
ReadVia string
Checked string // YYYY-MM-DD
}
MembershipTerm is one plan of one network.
type Network ¶
type Network struct {
Slug string
DisplayName string
// MatchPatterns are substrings checked (case-insensitive) against
// the charger name returned by Rivian's planTrip waypoint.
// Order matters: more specific patterns must come first. The
// Tesla / Tesla Supercharger pair is the canonical example -
// "tesla supercharger" appears before "tesla" so destination
// chargers don't accidentally claim the Supercharger rate.
MatchPatterns []string
GuestRate float64
// MemberRate is the rate after paying for the network's
// subscription. Nil = no membership tier exists (ChargePoint,
// Francis, etc.) or the user is automatically a member (RAN -
// every Rivolt user is by definition a Rivian owner).
MemberRate *float64
// MemberPlan is the human-readable plan name + monthly cost
// surfaced in the cost-strip tooltip later. Empty when no plan.
MemberPlan string
// RivianIDs are the values passed to Rivian's planTrip2
// networkPreferences[].networkId field. Some networks span
// multiple operator IDs (RAN = Adventure + Wayfinder; FLO =
// FLOC + FLOU for Canada/US), so this is a list. Mapping of the
// network ids Rivian reports:
//
// 10001, 10002 → RAN (Adventure + Wayfinder, "RAN, RWN")
// 10025 → ChargePoint
// 10027 → Electrify America
// 10030 → EV Connect
// 10031 → EVgo
// 10032, 10033 → FLO ("FLOC, FLOU" — Canada + US)
// 10045 → Shell Recharge
// 10050 → Tesla
// 10056 → IONNA
//
// Networks outside that enum (Blink, bp pulse, Francis Energy)
// leave RivianIDs empty — Rivian's planner doesn't recognise
// them, so sending an arbitrary ID would risk being treated as
// "exclude everything else." Custom user-added rows also stay
// empty.
RivianIDs []string
// DefaultMemberActive is the initial MemberActive state for a
// freshly-seeded ChargingNetwork row. True for networks every
// Rivolt user qualifies for by definition (RAN — every user
// owns a Rivian). False for opt-in subscriptions (EA Pass+,
// Tesla SC Membership, EVgo Plus).
DefaultMemberActive bool
}
Network is one DCFC operator with a guest rate and, where it exists, a member rate. The "is the user a member?" question is deliberately not modeled here - we show BOTH totals on the planner and let the user do the comparison. Keeps the data model trivial (no settings panel, no migration) and the savings line reads as a one-glance number.
Rates are USD/kWh for US deployments at time of writing (mid-2025). They will go stale - Rivolt's user-history pricing (deferred slice C) is the long-term fix; this table is the best-effort fallback when we have no per-user data for that network yet.
func MatchNetwork ¶
MatchNetwork returns the network entry whose first MatchPattern appears (case-insensitive) in the charger name. Falls back to UnmatchedNetwork - caller never has to nil-check.
The match is on the bare charger name as Rivian's planner returned it (e.g. "Electrify America - Pflugerville Crossing"). Whitespace around the user-visible name varies; lowercasing both sides plus padding the haystack with a space on each end lets " flo "-style boundary patterns work without a regex.
func (Network) MemberRateOrGuest ¶
MemberRateOrGuest returns the network's MemberRate when set, otherwise its GuestRate. Used by the cost estimator's "with-all-memberships" total - networks that don't have a membership simply contribute their guest rate to both totals.
type NetworkOverride ¶
type NetworkOverride struct {
Slug string
Name string
GuestRate float64
MemberRate float64 // 0 = no member tier
MemberActive bool
MemberPlan string
}
NetworkOverride is the bridge type the user-settings layer passes to the cost estimator so we can wire user-edited rates + per-membership-toggle into trip planning without tripadvice having to import the settings package. Slug ties the override to a row in the built-in Networks table so the match-pattern lookup keeps working; Name is the fallback substring for user-added custom rows that have no slug.
type ObservedPrice ¶ added in v0.38.7
type ObservedPrice struct {
Slug string
// Low and High are USD per kWh (see the note above).
Low, High float64
// Month is the observation month, "2006-01" form (the month of the tracker report).
Month string
// URL is the tracker page the figure was copied from.
URL string
// Note says what Low and High are, for whoever re-checks the row.
Note string
// LowNote and HighNote say what each end is, for the UI, when Low and High differ.
LowNote, HighNote string
}
ObservedPrice is one network's hand-copied observation.
func ObservedFor ¶ added in v0.38.7
func ObservedFor(slug string, now time.Time) (ObservedPrice, bool)
ObservedFor returns the observed price for a network slug when there is one and it is not over 12 months old at now. The month counts from its first day, so a figure observed in 2026-10 stops showing on 2027-10-01: earlier than the day it was copied, never later.
func ObservedRateAt ¶ added in v0.38.8
func ObservedRateAt(slug string, sessionStart time.Time, member bool) (rate float64, o ObservedPrice, ok bool)
ObservedRateAt is the figure a past or live session may be priced at: the observed price for slug when the session started on or after the observation month's first day and before 12 months later (a session before the report is not covered by it; a session after the 12 months is not either). The rate is the higher end of the range (what a non-member pays, where the tracker reports both) unless member is true, then the lower end.
func (ObservedPrice) Label ¶ added in v0.38.7
func (o ObservedPrice) Label() string
Label is the exact wording for the figure.
type PinChoice ¶ added in v0.33.0
PinChoice is what a charge's pin says: Snap uses the node as-is; otherwise the charge's own location is labelled.
func ChoosePin ¶ added in v0.33.0
ChoosePin decides whether a nearby node agrees with the known network (Snap) and what to call the charge. Never a wrong brand:
- a non-DC session never snaps to a DC-only node;
- unknown network: snap with the node's name, or a generic label;
- the node agrees: snap;
- it disagrees, or can't be verified (no network info): the known network at the charge's own location.
type PoiLike ¶ added in v0.33.0
type PoiLike struct {
Name string `json:"name,omitempty"`
Operator string `json:"operator,omitempty"`
Network string `json:"network,omitempty"`
Brand string `json:"brand,omitempty"`
MaxPowerKW float64 `json:"maxPowerKW,omitempty"`
}
PoiLike is a charging node's descriptive fields (OSM or the archive).
type ResolvedRate ¶
type ResolvedRate struct {
Slug string
DisplayName string
GuestRate float64
UserRate float64 // honours the user's MemberActive toggle
AllMemberRate float64 // hypothetical "you have every plan"
MemberPlan string
}
ResolvedRate is what the cost estimator works with per stop. Three rates so the strip can show guest / your-memberships / max-if-you-joined-everything; DisplayName + Slug feed the breakdown attribution line.
func ResolveRate ¶
func ResolveRate(stopName string, overrides []NetworkOverride) ResolvedRate
ResolveRate picks the right rate for a planned stop. First the user's overrides (slug-keyed against the built-in match patterns, falling back to substring against Name for custom rows), then the built-in Networks defaults, then UnmatchedNetwork.
func ResolveRateBySlug ¶ added in v0.33.2
func ResolveRateBySlug(slug string, overrides []NetworkOverride) (ResolvedRate, bool)
ResolveRateBySlug is ResolveRate for a network the caller already identified by slug (the user picked it, or it came from detection evidence): the user's override for that slug first, then the built-in default. ok is false for a slug that names no built-in network and has no override row.
type ShownMembershipTerm ¶ added in v0.38.10
type ShownMembershipTerm struct {
MembershipTerm
Network string
Label string
}
ShownMembershipTerm is a row as the API gives it: the term, the network's display name, and the label every row carries.
func MembershipTermsAt ¶ added in v0.38.10
func MembershipTermsAt(now time.Time) []ShownMembershipTerm
MembershipTermsAt returns the rows that are still shown at now: checked less than 12 months ago. Each carries the label "As listed by <network>; may be out of date; last checked <date>.".
type SiteMemory ¶ added in v0.33.0
SiteMemory is the nearest DC fast charge of the user's own whose network they confirmed or set themselves.
type StopNetwork ¶ added in v0.38.7
type StopNetwork struct {
Slug string
DisplayName string
MemberPlan string
// UserPerKWh is what the user's saved price book says they pay (their active
// membership applied), and ok only when it is THEIR number: a custom row, or a
// built-in row whose rate differs from the built-in default. A row still holding
// the default (the seed the Settings page offers) is the table figure, not theirs.
UserPerKWh float64
UserSet bool
}
StopNetwork is the network a planned stop belongs to, and the price the USER set for it, if they did. The built-in table's rates are deliberately not here: they are dated estimates (mid-2025) and the planner no longer shows them as dollar figures.
func ResolveStop ¶ added in v0.38.7
func ResolveStop(stopName string, overrides []NetworkOverride) StopNetwork
ResolveStop identifies a stop's network and the user's own price for it.