Skip to content

Optimizer: hourly slots beyond 48h also for a self-hosted optimizer - #34833

Merged
andig merged 3 commits into
evcc-io:masterfrom
Thomvh:optimizer-coarsen-after
Oct 15, 2026
Merged

andig merged 3 commits into
evcc-io:masterfrom
Thomvh:optimizer-coarsen-after

Conversation

@Thomvh

@Thomvh Thomvh commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

fixes #34820, follows #34738

As agreed in the issue, the horizon beyond 48h is now merged into clock hour slots for every optimizer, not only the hosted one. Local installs can opt out or move the boundary with an environment variable.

  • coarsenHorizon runs for hosted and self-hosted optimizers, default boundary 48h
  • new env OPTIMIZER_COARSEN_AFTER (whole hours, e.g. 72), only honoured by a self-hosted optimizer; 0 keeps quarter hour slots for the full horizon
  • an invalid or negative value logs a warning and falls back to 48h
  • behaviour change for existing self-hosted users: long horizons now use hourly slots unless they set OPTIMIZER_COARSEN_AFTER=0

TODO

🤖 Generated with Claude Code

Coarsen the horizon for every optimizer, not only the hosted one. A
self-hosted optimizer can move the boundary with OPTIMIZER_COARSEN_AFTER
(a Go duration, 0 keeps quarter hour slots for the full horizon).
@github-actions github-actions Bot added the enhancement New feature or request label Oct 14, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="core/site_optimizer.go" line_range="1388" />
<code_context>
+// coarseSlotsAfter returns where the request switches to clock hour slots. A self-hosted optimizer
+// may move it with OPTIMIZER_COARSEN_AFTER, 0 keeps quarter hour slots for the full horizon.
+func coarseSlotsAfter(hosted bool) (time.Duration, error) {
+	v := os.Getenv("OPTIMIZER_COARSEN_AFTER")
+	if hosted || v == "" {
+		return defaultCoarseSlotsAfter, nil
</code_context>
<issue_to_address>
**Self-hosted users cannot discover override**

When a self-hosted user relies on the optimizer documentation to configure options, the optimizer documentation omits `OPTIMIZER_COARSEN_AFTER`, so users cannot learn how to set it to `0` to preserve quarter-hour slots or move the boundary, and requests retain hourly slots beyond 48 hours unintentionally.

Document `OPTIMIZER_COARSEN_AFTER`, including that `0` preserves quarter-hour slots for the full horizon and how to move the boundary.
</issue_to_address>

### Comment 2
<location path="core/site_optimizer_test.go" line_range="238-239" />
<code_context>
 	after := before
-	coarsenHorizon(&short, &after)
+	coarsenHorizon(&short, &after, defaultCoarseSlotsAfter)
 	assert.Equal(t, 4*47, len(short.TimeSeries.Dt))
 	assert.Equal(t, before, after)
+
+	// an earlier boundary coarsens the same request
</code_context>
<issue_to_address>
**Short-horizon mutations go undetected**

When the coarsener changes slot durations in place without changing the slot count, when the coarsener changes `short.TimeSeries.Dt` values without changing its length, `TestCoarsenHorizon` line 238 still passes and line 239 never compares `Dt`; the test passes despite a short-horizon mutation.

Snapshot and compare `short.TimeSeries.Dt` before and after coarsening.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread core/site_optimizer.go
// coarseSlotsAfter returns where the request switches to clock hour slots. A self-hosted optimizer
// may move it with OPTIMIZER_COARSEN_AFTER, 0 keeps quarter hour slots for the full horizon.
func coarseSlotsAfter(hosted bool) (time.Duration, error) {
v := os.Getenv("OPTIMIZER_COARSEN_AFTER")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Self-hosted users cannot discover override

When a self-hosted user relies on the optimizer documentation to configure options, the optimizer documentation omits OPTIMIZER_COARSEN_AFTER, so users cannot learn how to set it to 0 to preserve quarter-hour slots or move the boundary, and requests retain hourly slots beyond 48 hours unintentionally.

Document OPTIMIZER_COARSEN_AFTER, including that 0 preserves quarter-hour slots for the full horizon and how to move the boundary.

Prompt for AI agents
In `core/site_optimizer.go` at line 1388:

**Self-hosted users cannot discover override**

When a self-hosted user relies on the optimizer documentation to configure options, the optimizer documentation omits `OPTIMIZER_COARSEN_AFTER`, so users cannot learn how to set it to `0` to preserve quarter-hour slots or move the boundary, and requests retain hourly slots beyond 48 hours unintentionally.

Document `OPTIMIZER_COARSEN_AFTER`, including that `0` preserves quarter-hour slots for the full horizon and how to move the boundary.

Comment thread core/site_optimizer_test.go Outdated
@naltatis naltatis added the needs documentation Triggers issue creation in evcc-io/docs label Oct 14, 2026
Comment thread core/site_optimizer.go Outdated
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@andig
andig merged commit 53da181 into evcc-io:master Oct 15, 2026
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request needs documentation Triggers issue creation in evcc-io/docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Optimizer: hourly slots beyond 48 h also for a self-hosted optimizer

3 participants