Skip to content

docs(planner): no-demand-window replay and price-block target design - #1052

Merged
jackmcintyre merged 2 commits into
mainfrom
claude/charging-strategy-no-demand-6d5d6b
Sep 8, 2026
Merged

jackmcintyre merged 2 commits into
mainfrom
claude/charging-strategy-no-demand-6d5d6b

Conversation

@jackmcintyre

Copy link
Copy Markdown
Owner

Summary

The tariff's demand charge is seasonal; the planner's demand window is a clock window applied every day. This PR lands the offline evidence for what the planner does without it, and the design that follows. No planner behaviour changes.

Related Issue

None yet — design PR. Slice 1 will get its own issue.

Changes

  • scripts/replay_no_dw.py: replays captured real days with the demand window removed, plus diagnostic arms (cheap gate open, hurdle open, gateless). Seeds forecast_horizon_hours before the first pass (see harness note in the README).
  • simulations/replay-nodw/README.md: results over 2026-08-29..09-07. Nine sunny days identical with or without the window; on the one deficit day removing it costs +$0.88 because the DW water level was the only funder of a 7c-morning-for-18c-evening pre-charge. Gateless with a zero hurdle reproduces the DW plan to the cent; no arm charged overnight at any hurdle.
  • simulations/replay-nodw/2026-09-07.json: the deficit day, committed so recorder retention cannot lose it. Other captures gitignored.
  • docs/PRICE_BLOCK_TARGET.md (+ INDEX link): split the window into a charge_window (demand charge, Amber's per-slot flag, drives only the import ban) and a forecast-derived target_block (expensive evening, drives deadline/target/useful-period all year), load-sized target off-season, separate block_min_spread knob default $0.08 (decided 2026-09-08). Three vertical slices; slice 1 gated on this replay plus a flap test, live in dry run first.

Testing

  • Tested in worktree — uv run scripts/replay_no_dw.py --arms dw,no_dw,gateless_mcs25,gateless_mcs10,gateless_mcs0 reproduces the README table; ruff clean.
  • CI passes

Checklist

  • Code follows project conventions
  • No type errors
  • Tests added/updated for new functionality — n/a, analysis script and docs only

The tariff's demand charge is seasonal and the shoulder season has just
started, but the planner's demand window is a clock window applied every
day. Before changing that, measure what the planner does without it.

scripts/replay_no_dw.py replays captured real days through the optimizer
with the demand window removed from slot flagging, plus diagnostic arms
that open the cheap-price gate and the min-cycle-saving hurdle, and a
gateless arm where only the DP cost function decides. Results over ten
days (2026-08-29..09-07) in simulations/replay-nodw/README.md:

- nine sunny days are byte-identical with or without the window
- on the one solar-deficit day (09-07) removing the window costs +$0.88:
  the demand-window water level was the only path admitting 7-8c morning
  slots for a 16-19c evening; the cheap-price gate cannot see the peak
  and the $0.25 hurdle rejects a 9c/kWh net spread
- gateless with the hurdle at zero reproduces the demand-window plan to
  the cent, and no arm charged overnight at any hurdle (no #800 return)

docs/PRICE_BLOCK_TARGET.md is the design that follows: split the window
into a charge_window (demand charge, Amber's per-slot flag, drives only
the import ban) and a forecast-derived target_block (the expensive
evening, drives the deadline/target and useful period all year), with a
load-sized target out of season and a separate block spread knob
(default $0.08/kWh, decided 2026-09-08). Sliced vertically; slice 1 is
gated on this replay plus a flap test and runs live in dry run first.

Harness note: a fresh CoordinatorData carries forecast_horizon_hours=0,
which the cheap-price calculation reads before the facade writes the
real span, scaling the percentile by 0.1 on the first pass and sticking
through the EMA hysteresis. The harness seeds the horizon before pass 1.

The 2026-09-07 capture is committed as the load-bearing deficit day;
the other captures are gitignored and regenerable while the recorder
retains them.
@coderabbitai

coderabbitai Bot commented Sep 8, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 894ab36e-7733-4c08-a95d-6337bd79ea2f


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…t slice 1

Three tasks for docs/PRICE_BLOCK_TARGET.md slice 1: the pure detector
module and its knobs, the planner wiring behind a default-off switch,
and the replay-harness block arm plus flap gate. Launch with
/routed-build-auto {"backlogFile": ..., "stack": true} once this PR
has merged; scope and gates are in the file.
@jackmcintyre
jackmcintyre merged commit 699c8f9 into main Sep 8, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant