Break near-tie plan comparison toward the less fragmented plan#4301
Open
springfall2008 wants to merge 1 commit into
Open
Break near-tie plan comparison toward the less fragmented plan#4301springfall2008 wants to merge 1 commit into
springfall2008 wants to merge 1 commit into
Conversation
… plan metric_min_improvement_plan keeps the incumbent plan whenever a freshly optimised plan is not better by the threshold. Under flat export rates a fragmented (split) export staircase and a clean single block are near-cost equal, so once a split forms it is locked in forever even though every recompute produces the cleaner block. Add plan_fragmentation() (count of contiguous active charge/export segments, split by idle gaps or charge<->export mode changes) and should_replace_plan(). On a near-tie the new plan is adopted only when it is no worse on cost (improvement >= 0) and strictly less fragmented, so a cleaner block can replace a locked-in staircase without churning the plan for tiny cost changes. The existing "clearly better by the threshold" path and the anti-jitter hysteresis for equally-fragmented plans are unchanged. Add unit tests for the fragmentation count and every decision branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Introduces a near-tie tie-break in the planning pipeline to prevent “locked-in” fragmented export schedules under flat export rates by preferring the less-fragmented plan when the freshly optimised plan is not meaningfully better but is no worse on the optimisation metric.
Changes:
- Added
Plan.plan_fragmentation(...)andPlan.should_replace_plan(...)helpers to quantify plan fragmentation and decide near-tie plan replacement. - Wired the tie-break into
Plan.calculate_plan()when comparing the incumbent vs recomputed plan. - Added and registered a focused unit test module covering fragmentation counting and tie-break decision branches.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| apps/predbat/plan.py | Adds fragmentation scoring + near-tie replacement logic and integrates it into the plan comparison branch. |
| apps/predbat/tests/test_plan_tiebreak.py | Adds regression/unit tests for fragmentation counting and plan replacement decisions. |
| apps/predbat/unit_test.py | Registers the new plan_tiebreak test in the test runner. |
| elif (metric_prev - metric) >= self.metric_min_improvement_plan: | ||
| self.log("New plan metric is significantly better from previous plan, using new plan") | ||
| else: | ||
| self.log("New plan is a cost-neutral improvement but less fragmented ({} vs {} segments), using new plan".format(fragmentation_new, fragmentation_prev)) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
metric_min_improvement_plan(default 2.0p) keeps the incumbent plan whenever a freshly optimised plan is not better than it by that margin. This is deliberate anti-jitter hysteresis, but it has a side effect: under flat export rates, a fragmented (split) export staircase and a clean single export block are near cost-equal, so once a split plan forms it gets locked in permanently — every 5-minute recompute finds the cleaner merged block but discards it because the improvement is under the threshold.Observed on a real system: evening export shown as a staircase (
20:50–22:30 @28%· gap ·22:50–23:30 @7%· gap ·23:45 @6%) instead of a single20:50–23:30 @0%block. Log confirmed:The merged plan was 1.79p better but under the 2.0p floor, so the split persisted.
Change
Two pure helpers on the
Planclass, wired into the plan-comparison branch ofcalculate_plan:plan_fragmentation(...)— counts contiguous active segments (battery discharge where export limit < 99, or charge above reserve). Adjacent same-mode slots merge into one segment; an idle time gap or a charge↔export mode change starts a new one. Fewer = cleaner.should_replace_plan(metric_prev, metric_new, frag_prev, frag_new):improvement >= metric_min_improvement_plan→ adopt new (unchanged behaviour)improvement >= 0andfrag_new < frag_prev→ adopt new (cleaner, never costlier)The
improvement >= 0guard means a cleaner plan is never adopted if it costs anything more.Why it can't thrash
The tie-break only ever moves toward fewer segments inside the cost "don't-care" band, so it's monotone — once on the merged plan the next recompute reproduces it and there's nothing to switch. Genuine forecast jitter (fresh plan differs but is equally fragmented) leaves
frag_new == frag_prev, so the previous plan is kept and the anti-jitter hysteresis is fully preserved.Testing
tests/test_plan_tiebreak.py(registered asplan_tiebreak) — 13 assertions covering the fragmentation count (gaps, mode changes, freeze/off inactive, depth-agnostic) and every decision branch, including cleaner-but-costlier → rejected. Written test-first.calculate_plannow logs "cost-neutral improvement but less fragmented (11 vs 12 segments), using new plan" and replaces the staircase with the merged block.debug_cases, andoptimise_windows/optimise_windows_kernelall pass; pre-commit (ruff/black/cspell) clean.🤖 Generated with Claude Code