[IRRIGOAPI-114] Stop the rain-skip deferring irrigation indefinitely #11

Merged
chris merged 3 commits from bug/IRRIGOAPI-114 into main 2026-08-11 17:22:56 -06:00
Owner

Ticket

IRRIGOAPI-114 — Rain-skip can defer irrigation indefinitely; add credit cap, critical-depletion floor, and probability weighting.

Summary

All three zones went nine days without water (last run 2026-08-02) while sitting above their watering trigger, every night skipped with reason rain-forecast. Actual rainfall over 2026-07-28 → 08-10 was 2.90 mm total; the planner was crediting 26–48 mm of forecast rain each night.

The rain-skip had unbounded forecast credit and no critical-depletion override. Because depletion only ever creeps a few mm past the trigger, the deficit a forecast has to "cover" stays tiny, so almost any prediction defers the night. Even at total soil-moisture exhaustion the deficit is smaller than the forecasts being shown — there was no depletion level at which the system was guaranteed to water, and the nightly re-plan could not self-correct against a persistent wet bias.

Three layered changes, in order of what actually fixes the bug:

  • Critical-depletion floor (RAIN_SKIP_DEPLETION_FLOOR_FRACTION, default 0.7 of total available water). Past this depletion the rain-skip is suppressed outright. Depletion is already a running accumulator of consecutive skips, so this needs no new cross-replan state. For the live zones the floor lands at 34.65 mm, leaving a ~10 mm band above the 24.75 mm trigger for genuine rain delays.
  • Per-day forecast credit cap (RAIN_SKIP_MAX_DAILY_CREDIT_MM, default 10). Stops one speculative convective day dominating the lookahead. Applied per day, so a real multi-day soaker (3 × 8 mm) is still credited in full.
  • Probability weighting. precipitation_probability_max is now fetched, parsed, and persisted; forecast credit is scaled by it. A 66%-likely 32.8 mm day credits ~5.3 mm instead of 26.2 mm. Only the forward-looking lookahead is weighted — the day-by-day soil balance still applies observed rain undiscounted.

Also:

  • planZoneSchedule's 6th positional argument becomes a RainSkipSettings object rather than growing to eight parameters.
  • New depletion-floor decision reason, recorded only when the floor overrode a rain-skip that would otherwise have fired — nights that water normally keep their full-refill / partial-refill reason.
  • Additive nullable migration 0020_whole_wolfsbane.sql; historical rows carry null probability, which reads as "no discount".

Verification

  • bun --cwd=./api run type-check — clean.
  • bun --cwd=./api test — 1021 pass. The 12 failures are pre-existing HA/alert tests that fail identically on main (verified by checking out main and re-running); this branch adds 33 tests and introduces no new failures.
  • Regression coverage reproduces the incident exactly: depletion 30.6 mm against a 24.8 mm trigger with ~48 mm of forecast credit still rain-skips below the floor, and waters once depletion crosses it.

Follow-up

The running daemon needs a restart to pick this up; zones will water on the next evening tick once it does. The duplicate scheduling_decisions rows and concurrent tick loops seen while diagnosing this are tracked separately as IRRIGOAPI-115.

## Ticket [IRRIGOAPI-114](http://192.168.2.100:7123/home/browse/IRRIGOAPI-114/) — Rain-skip can defer irrigation indefinitely; add credit cap, critical-depletion floor, and probability weighting. ## Summary All three zones went nine days without water (last run 2026-08-02) while sitting above their watering trigger, every night skipped with reason `rain-forecast`. Actual rainfall over 2026-07-28 → 08-10 was **2.90 mm total**; the planner was crediting 26–48 mm of forecast rain each night. The rain-skip had **unbounded forecast credit and no critical-depletion override**. Because depletion only ever creeps a few mm past the trigger, the deficit a forecast has to "cover" stays tiny, so almost any prediction defers the night. Even at total soil-moisture exhaustion the deficit is smaller than the forecasts being shown — there was no depletion level at which the system was guaranteed to water, and the nightly re-plan could not self-correct against a persistent wet bias. Three layered changes, in order of what actually fixes the bug: - **Critical-depletion floor** (`RAIN_SKIP_DEPLETION_FLOOR_FRACTION`, default `0.7` of total available water). Past this depletion the rain-skip is suppressed outright. Depletion is already a running accumulator of consecutive skips, so this needs no new cross-replan state. For the live zones the floor lands at 34.65 mm, leaving a ~10 mm band above the 24.75 mm trigger for genuine rain delays. - **Per-day forecast credit cap** (`RAIN_SKIP_MAX_DAILY_CREDIT_MM`, default `10`). Stops one speculative convective day dominating the lookahead. Applied per day, so a real multi-day soaker (3 × 8 mm) is still credited in full. - **Probability weighting.** `precipitation_probability_max` is now fetched, parsed, and persisted; forecast credit is scaled by it. A 66%-likely 32.8 mm day credits ~5.3 mm instead of 26.2 mm. Only the forward-looking lookahead is weighted — the day-by-day soil balance still applies observed rain undiscounted. Also: - `planZoneSchedule`'s 6th positional argument becomes a `RainSkipSettings` object rather than growing to eight parameters. - New `depletion-floor` decision reason, recorded only when the floor overrode a rain-skip that would otherwise have fired — nights that water normally keep their `full-refill` / `partial-refill` reason. - Additive nullable migration `0020_whole_wolfsbane.sql`; historical rows carry `null` probability, which reads as "no discount". ## Verification - `bun --cwd=./api run type-check` — clean. - `bun --cwd=./api test` — **1021 pass**. The 12 failures are pre-existing HA/alert tests that fail identically on `main` (verified by checking out `main` and re-running); this branch adds 33 tests and introduces no new failures. - Regression coverage reproduces the incident exactly: depletion 30.6 mm against a 24.8 mm trigger with ~48 mm of forecast credit still rain-skips below the floor, and waters once depletion crosses it. ## Follow-up The running daemon needs a restart to pick this up; zones will water on the next evening tick once it does. The duplicate `scheduling_decisions` rows and concurrent tick loops seen while diagnosing this are tracked separately as IRRIGOAPI-115.
chris merged commit c8c5b18105 into main 2026-08-11 17:22:56 -06:00
chris deleted branch bug/IRRIGOAPI-114 2026-08-11 17:22:56 -06:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
chris/irrigo!11
No description provided.