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
- func PushAllowed(used int) bool
- func Text(kind string, d Detail) (title, body string)
- type Checker
- type Detail
- type Found
- type LeaseTerm
- type PGStore
- func (s *PGStore) Dismiss(ctx context.Context, uid uuid.UUID, rivianID, key string, now time.Time) (bool, error)
- func (s *PGStore) Insert(ctx context.Context, r Row) (bool, error)
- func (s *PGStore) List(ctx context.Context, uid uuid.UUID, since time.Time) ([]Row, error)
- func (s *PGStore) MarkReady(ctx context.Context) error
- func (s *PGStore) PendingPush(ctx context.Context, since time.Time) ([]Row, error)
- func (s *PGStore) Ready(ctx context.Context) (bool, error)
- func (s *PGStore) Vehicle(ctx context.Context, uid uuid.UUID, rivianID string) (uuid.UUID, bool, error)
- func (s *PGStore) Vehicles(ctx context.Context, active time.Time) ([]VehicleRef, error)
- type Row
- type Store
- type VehicleRef
Constants ¶
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.
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.
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 ¶
PushAllowed says whether a milestone push may use the account's budget now: it leaves PushReserve slots for the weekly recap.
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 ¶
Backfill marks every milestone already passed as backfilled (never announced), then marks the feature ready. Idempotent and safe on every replica.
func (*Checker) CatchUp ¶
CatchUp checks the vehicles that drove or charged in the last two days, for a close a restart or a failed hook missed.
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 ¶
Found is a milestone that has been passed.
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 (*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) List ¶
List returns the account's milestone cards: achieved since the cutoff and not dismissed, newest first.
func (*PGStore) PendingPush ¶
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.
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.