> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pilotwatt.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Scheduler logic

> How tomorrow's FoxESS schedule is built from Agile rates.

<Note>
  See [Smart scheduler](/features/smart-scheduler) for the user-facing summary.
</Note>

**Trigger:** Vercel cron `30 16 * * *` UTC → `/api/cron/scheduler` (plus **Re-optimise now** from Planner).

**Steps (simplified):**

1. For each `scheduler_enabled` user, repair inverter drift if the live FoxESS schedule ≠ DB plan
2. Load tomorrow's Agile export rates for the user's GSP
3. 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)
4. Skip export if live SoC \< 30%
5. Insert Force Charge periods for IO cheap window (GraphQL planned dispatches, or settings fallback 23:30–05:30 @ 7p)
6. Fill remaining periods with Self-use
7. Push **`tomorrowSchedule`** to FoxESS (full next calendar day)
8. Log run + estimates to Neon (`scheduler_log`, `is_current`)

**Manual re-optimise:** preserve pending cron export when inverter matches plan (`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`.
