Documentation
¶
Overview ¶
Package weekly is the opt-in weekly recap email: on Monday morning, last week's miles, energy, cost and efficiency, and lease pace if the user tracks a lease. Plain text, no images, no tracking, a signed one-click unsubscribe. A week with no drive and no charge sends nothing.
Every send goes through internal/engage (toggle, budget, once per account and week), so it is safe on every replica and counts toward the monthly budget.
Index ¶
- Constants
- func Due(now time.Time, loc *time.Location) bool
- func IdempotencyKey(uid uuid.UUID, week string) string
- func Render(s Summary, appURL, unsubURL string) (subject, text string)
- func Week(now time.Time, loc *time.Location) (key string, start, end time.Time)
- type Candidate
- type Job
- type LeasePace
- type Mailer
- type Source
- type Stats
- type Summary
- type SummaryFunc
Constants ¶
const ( SendFromHour = 8 SendUntilHour = 12 SweepEvery = 30 * time.Minute // BestDriveMinMiles keeps a short hop from being the "best drive". BestDriveMinMiles = 5.0 )
The recap goes out on Monday between SendFromHour and SendUntilHour local: a few hours' grace so a restart or an outage at 08:00 does not cost the week (the claim still makes it once).
const Kind = "weekly_recap"
Kind is the engage kind of the recap.
Variables ¶
This section is empty.
Functions ¶
func IdempotencyKey ¶
IdempotencyKey is a stable, opaque per-(account, week) key; the raw user id never leaves the server.
Types ¶
type Job ¶
type Job struct {
Source Source
Summarize SummaryFunc
Mailer Mailer
Signer *statement.Signer
BaseURL string
Engage *engage.Engage
Now func() time.Time
Logger *slog.Logger
// Pause is called between sends (the throttle); nil uses 500 ms.
Pause func(ctx context.Context)
}
Job sends the recaps.
type LeasePace ¶
type LeasePace struct {
// MilesOver is positive when they are over pace, negative when under.
MilesOver float64
}
LeasePace is how the user stands against the lease allowance.
type Source ¶
type Source interface {
Candidates(ctx context.Context) ([]Candidate, error)
// Enabled reads the live toggle.
Enabled(ctx context.Context, uid uuid.UUID) (bool, error)
}
Source reads who gets the recap and whether they still want it.
type Stats ¶
type Stats struct{ Considered, Due, Sent, Skipped, Failed int }
Stats is what one sweep did.
type Summary ¶
type Summary struct {
Model string
Currency string
Miles float64
Drives int
KWhUsed float64 // 0: unknown
MiPerKWh float64 // 0: unknown
KWhCharged float64
Cost float64
// HomePct is the share of charged energy done at home; nil when no home
// place is set or nothing was charged.
HomePct *int
// BestMiles and BestMiPerKWh describe the most efficient drive; zero when the
// week had fewer than two drives.
BestMiles, BestMiPerKWh float64
Lease *LeasePace
// Charges counts charging sessions in the week.
Charges int
}
Summary is one week's figures.