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

milestones

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

Documentation

Overview

Package milestones notices when a vehicle passes one of a few round numbers (1,000 miles recorded, 1 MWh charged at home, 100 charges, halfway through the lease, and a personal-best efficiency on a long drive), keeps a dismissible card for each, and pushes it to the devices that turned milestones on.

Each milestone is announced once per vehicle, ever: the (account, vehicle, key) row is both the card and the guard. Milestones already passed when this shipped are marked 'backfilled' by a one-off pass and never announced; until that pass is done nothing is announced at all. Pushes go through internal/engage (the monthly budget, quiet hours, the toggle read at send time) and give way when the weekly recap needs the budget: a milestone push is only sent while at least two budget slots are left. Aggregates only: no place, no coordinate.

Index

Constants

View Source
const (
	KindMiles   = "miles_1000"
	KindHomeMWh = "home_mwh_1"
	KindCharges = "charges_100"
	KindLease   = "lease_half"
	KindBestEff = "best_eff" // key best_eff:YYYY-MM
)

Kinds, and their key prefixes. A key is stored per vehicle.

View Source
const (
	MilesThreshold   = 1000.0
	HomeKWhThreshold = 1000.0
	ChargesThreshold = 100
	// A personal best needs a long drive, a history to beat and a clear margin.
	BestEffMinMiles  = 50.0
	BestEffMinPrior  = 5
	BestEffMargin    = 1.05
	StatusAchieved   = "achieved"
	StatusBackfilled = "backfilled"
	CardWindow       = 30 * 24 * time.Hour
	// PushReserve is how many budget slots a milestone push leaves for the weekly
	// recap: it is sent only while at most MonthlyBudget-PushReserve are used.
	PushReserve = 2
)

Thresholds.

View Source
const ReofferWindow = 72 * time.Hour

ReofferWindow is how long a milestone whose push was refused stays eligible for another try; older ones stay card-only.

Variables

This section is empty.

Functions

func PushAllowed

func PushAllowed(used int) bool

PushAllowed says whether a milestone push may use the account's budget now: it leaves PushReserve slots for the weekly recap.

func Text

func Text(kind string, d Detail) (title, body string)

Text is the card's title and body, also the push's: aggregates only, one sentence, no exclamation marks. The wording is growth's.

Types

type Checker

type Checker struct {
	Store Store
	// Input loads the vehicle's whole ledger input.
	Input func(ctx context.Context, uid uuid.UUID, rivianID string) (ledger.Input, error)
	// Lease says how far through its lease the vehicle is; Total 0 when none.
	Lease func(ctx context.Context, uid uuid.UUID, vehicle uuid.UUID) LeaseTerm
	// Push sends the milestone to the account's devices that have it on, through
	// engage; nil sends nothing (the card still appears).
	Push   func(ctx context.Context, uid uuid.UUID, vehicle uuid.UUID, r Row)
	Now    func() time.Time
	Logger *slog.Logger
}

Checker finds new milestones and announces them.

func (*Checker) Backfill

func (c *Checker) Backfill(ctx context.Context) error

Backfill marks every milestone already passed as backfilled (never announced), then marks the feature ready. Idempotent and safe on every replica.

func (*Checker) CatchUp

func (c *Checker) CatchUp(ctx context.Context) error

CatchUp checks the vehicles that drove or charged in the last two days, for a close a restart or a failed hook missed.

func (*Checker) Check

func (c *Checker) Check(ctx context.Context, uid uuid.UUID, rivianID, driveID string)

Check looks for new milestones after a drive or charge closed (driveID names the drive, empty for a charge or the catch-up pass). Until the backfill is done it announces nothing.

func (*Checker) Run

func (c *Checker) Run(ctx context.Context)

Run backfills shortly after start (if it has not been done) and then catches up every six hours, until ctx ends.

type Detail

type Detail struct {
	Miles       float64 `json:"miles,omitempty"`
	MiPerKWh    float64 `json:"mi_per_kwh,omitempty"`
	CostPerMile float64 `json:"cost_per_mile,omitempty"`
	Currency    string  `json:"currency,omitempty"`
	HomeKWh     float64 `json:"home_kwh,omitempty"`
	HomeCost    float64 `json:"home_cost,omitempty"`
	Charges     int     `json:"charges,omitempty"`
	KWhAdded    float64 `json:"kwh_added,omitempty"`
	DriveMiles  float64 `json:"drive_miles,omitempty"`
	DriveMiKWh  float64 `json:"drive_mi_per_kwh,omitempty"`
	LeaseDays   int     `json:"lease_days_elapsed,omitempty"`
	LeaseTotal  int     `json:"lease_days_total,omitempty"`
}

Detail is what a milestone records: numbers only.

type Found

type Found struct {
	Key    string
	Kind   string
	Detail Detail
}

Found is a milestone that has been passed.

func Evaluate

func Evaluate(in ledger.Input, now time.Time, lease LeaseTerm, driveID string) []Found

Evaluate lists the milestones the vehicle has passed: the totals ones, the lease one when it has a lease, and the personal best when driveID names a drive that just closed. It reports every one passed, announced or not; the caller keeps only the new ones.

type LeaseTerm

type LeaseTerm struct{ Elapsed, Total int }

LeaseTerm is how far through its lease a vehicle is; Total 0 means no lease.

type PGStore

type PGStore struct {
	// contains filtered or unexported fields
}

PGStore is the Postgres store; it also serves the card API.

func NewStore

func NewStore(db *sql.DB) *PGStore

NewStore returns the Postgres store.

func (*PGStore) Dismiss

func (s *PGStore) Dismiss(ctx context.Context, uid uuid.UUID, rivianID, key string, now time.Time) (bool, error)

Dismiss hides a card; false when there is no such card for the account.

func (*PGStore) Insert

func (s *PGStore) Insert(ctx context.Context, r Row) (bool, error)

func (*PGStore) List

func (s *PGStore) List(ctx context.Context, uid uuid.UUID, since time.Time) ([]Row, error)

List returns the account's milestone cards: achieved since the cutoff and not dismissed, newest first.

func (*PGStore) MarkReady

func (s *PGStore) MarkReady(ctx context.Context) error

func (*PGStore) PendingPush

func (s *PGStore) PendingPush(ctx context.Context, since time.Time) ([]Row, error)

PendingPush: see Store. The claim key is the one the push is sent under (milestone:<key>:<vehicle id>), so "pushed" is read from engagement_sends and nothing new is stored. Pending means no engagement_sends row at all: engage leaves no row for a refused send, and never re-sends a claimed key (sent, failed or stuck), so a failed push must not hold the account's one re-offer slot.

func (*PGStore) Ready

func (s *PGStore) Ready(ctx context.Context) (bool, error)

func (*PGStore) Vehicle

func (s *PGStore) Vehicle(ctx context.Context, uid uuid.UUID, rivianID string) (uuid.UUID, bool, error)

func (*PGStore) Vehicles

func (s *PGStore) Vehicles(ctx context.Context, active time.Time) ([]VehicleRef, error)

type Row

type Row struct {
	User        uuid.UUID
	Vehicle     uuid.UUID
	RivianID    string
	Key, Kind   string
	Status      string
	AchievedAt  time.Time
	Detail      Detail
	DismissedAt *time.Time
}

Row is a stored milestone.

type Store

type Store interface {
	// Ready reports whether the one-off backfill has finished.
	Ready(ctx context.Context) (bool, error)
	MarkReady(ctx context.Context) error
	// Insert adds a milestone row; false when it already exists.
	Insert(ctx context.Context, r Row) (bool, error)
	// Vehicle maps a Rivian vehicle id to the account's vehicle row.
	Vehicle(ctx context.Context, uid uuid.UUID, rivianID string) (uuid.UUID, bool, error)
	// Vehicles lists accounts' vehicles; active limits it to those with a drive or
	// charge since the given time (the zero time: all).
	Vehicles(ctx context.Context, active time.Time) ([]VehicleRef, error)
	// PendingPush lists achieved, undismissed milestones achieved since the
	// cutoff whose push was never claimed (no engagement_sends row for their
	// claim key, whatever its status), oldest first.
	PendingPush(ctx context.Context, since time.Time) ([]Row, error)
}

Store is the persistence the checker needs.

type VehicleRef

type VehicleRef struct {
	User     uuid.UUID
	ID       uuid.UUID
	RivianID string
}

VehicleRef names a vehicle row and its owner.

Jump to

Keyboard shortcuts

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