planleg

package
v0.40.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 30, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package planleg caches planner calls and caps how many a user can make.

A multi-day plan is one planner call per day, each ~10 s upstream, and the planner is edited a day at a time: re-planning after a change to day 3 should cost day 3 (and later days whose start SoC moved), not every day. Cache wraps a rivian.TripPlanner so that

  • a call with the same inputs inside 15 minutes is answered from Redis and is free, and
  • every call that does reach the upstream planner counts against the user's 60 per hour; the 61st is refused with a QuotaError.

Every planner is wrapped, not only the live one: a replay install's synthetic plan is a Valhalla route and a charger-archive scan, which a user can also run up. Simulated plans are deterministic for their inputs, so they cache like any other. Redis being down never blocks planning, but never lifts the quota either: the cache then misses and the quota is counted in this process instead, a share of 60 an hour per user (60 divided by the number of replicas that may run at once, FallbackDivisor), so the replicas together stay inside Rivian's per-user budget. An install without Redis has the same per-process quota.

Index

Constants

View Source
const (
	// TTL is how long a planned leg is served from cache.
	TTL = 15 * time.Minute
	// QuotaPerHour is the planner calls a user may send upstream per UTC hour.
	QuotaPerHour = 60
)

Variables

This section is empty.

Functions

This section is empty.

Types

type Cache

type Cache struct {

	// OnFallback, when set, is called for each quota check counted in this process
	// because Redis could not serve it (or there is none).
	OnFallback func()
	// FallbackDivisor is the number of replicas that may run at once: while Redis cannot
	// serve the quota each pod allows ceil(QuotaPerHour / FallbackDivisor) a user (at least
	// one), so the replicas together stay inside the per-user budget. Zero or less: 3.
	FallbackDivisor float64
	// contains filtered or unexported fields
}

Cache is the Redis-backed leg cache and quota. A nil *Cache wraps nothing.

func New

func New(rdb *redis.Client, prefix string, log *slog.Logger) *Cache

New returns a Cache over rdb. prefix keeps installs sharing a Redis out of each other's keys (RIVOLT_REDIS_KEY_PREFIX). With no Redis (rdb nil) there is no leg cache and the quota is counted in this process.

func (*Cache) Wrap

func (c *Cache) Wrap(inner rivian.TripPlanner, uid uuid.UUID) rivian.TripPlanner

Wrap returns inner behind the cache and the quota for uid. On a nil Cache it returns inner unchanged.

type QuotaError

type QuotaError struct {
	// RetryAfter is the time until the quota window resets.
	RetryAfter time.Duration
}

QuotaError is returned when a user is over their hourly planner quota.

func IsQuota

func IsQuota(err error) (*QuotaError, bool)

IsQuota reports whether err is a QuotaError and returns it.

func (*QuotaError) Error

func (e *QuotaError) Error() string

Jump to

Keyboard shortcuts

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