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 ¶
This section is empty.
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 (10% off)", RivianIDs: []string{"10001", "10002"}, DefaultMemberActive: true, }, { Slug: "evgo", DisplayName: "EVgo", MatchPatterns: []string{"evgo"}, GuestRate: 0.42, MemberRate: f64(0.34), MemberPlan: "Rewards+ - $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: "GO+ - free", 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 ¶
This section is empty.
Types ¶
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 is
// lifted from the Android app's InternalNetwork enum
// (apk decompile, 2026-05-18):
//
// 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 Rewards+).
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 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.