weekly

package
v0.38.18 Latest Latest
Warning

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

Go to latest
Published: Oct 22, 2026 License: MIT Imports: 13 Imported by: 0

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

View Source
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).

View Source
const Kind = "weekly_recap"

Kind is the engage kind of the recap.

Variables

This section is empty.

Functions

func Due

func Due(now time.Time, loc *time.Location) bool

Due reports whether it is the sending window for a user in loc.

func IdempotencyKey

func IdempotencyKey(uid uuid.UUID, week string) string

IdempotencyKey is a stable, opaque per-(account, week) key; the raw user id never leaves the server.

func Render

func Render(s Summary, appURL, unsubURL string) (subject, text string)

Render writes the subject and the plain-text body.

func Week

func Week(now time.Time, loc *time.Location) (key string, start, end time.Time)

Week returns the previous full local week (Monday to Monday) before now in loc, and its key "weekly:{ISO year}-W{ISO week}".

Types

type Candidate

type Candidate struct {
	User     uuid.UUID
	Email    string
	Timezone string
	Model    string
}

Candidate is an account that turned the recap on.

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.

func (*Job) Run

func (j *Job) Run(ctx context.Context)

Run sweeps shortly after start and then every SweepEvery until ctx ends.

func (*Job) Sweep

func (j *Job) Sweep(ctx context.Context) (Stats, error)

Sweep considers every opted-in account once.

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 Mailer

type Mailer interface {
	Send(ctx context.Context, m email.Message) error
}

Mailer is the part of the email client the job uses.

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.

func NewSource

func NewSource(db *sql.DB, skip uuid.UUID) Source

NewSource reads from Postgres. skip (the demo account) is never a candidate.

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.

func (Summary) Empty

func (s Summary) Empty() bool

Empty is true for a week with no drive and no charge.

type SummaryFunc

type SummaryFunc func(ctx context.Context, c Candidate, start, end time.Time, loc *time.Location) (Summary, error)

SummaryFunc computes the figures for the local week [start, end).

Jump to

Keyboard shortcuts

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