See Smart scheduler for the user-facing summary.
30 16 * * * UTC → /api/cron/scheduler (plus Re-optimise now from Planner).
Steps (simplified):
- For each
scheduler_enableduser, repair inverter drift if the live FoxESS schedule ≠ DB plan - Load tomorrow’s Agile export rates for the user’s GSP
- Find peak export window above
minExportRateP(findPeakExportWindow— highest revenue sub-window within contiguous eligible runs, length/start tiebreakers; limited by discharge capacity and max 4 hours) - Skip export if live SoC < 30%
- Insert Force Charge periods for IO cheap window (GraphQL planned dispatches, or settings fallback 23:30–05:30 @ 7p)
- Fill remaining periods with Self-use
- Push
tomorrowScheduleto FoxESS (full next calendar day) - Log run + estimates to Neon (
scheduler_log,is_current)
preserved); else push missed today export; else rebuild tomorrow.
Manual override merge: mergeScheduleWithOverride() inserts a 30-minute period into today’s plan; export interrupt requires confirm (HTTP 409 without it). FoxESS errno 40257 on full-day merge may fall back to a short override that wipes the rest of the day on the inverter.
Drift repair: repairInverterScheduleIfNeeded() on home load (≤1 check / 15 min), cron, and manual re-optimise; FoxESS scheduler/get cached 60s.
Source of truth: lib/domain/scheduler.ts, lib/domain/scheduler-sync.ts, lib/providers/foxess-scheduler.ts.