Reject an unknown strategy instead of silently dropping it - #134
Open
andig wants to merge 1 commit into
Open
Conversation
Strategy names are read straight off the request and matched against the branches in the objective, so a name that matches none of them reaches no branch at all and the request is solved without any strategy. Nothing in the response says so: the schedule is cost optimal, the status says Optimal, and only the shape of the grid profile gives it away, which is exactly how #132 presents - an attenuate_feedin_peaks request answered with the raw PV surplus as its export peak. openapi.yaml has carried the enum since the strategies were added, but the flask-restx input model declared a plain string, so nothing validated it. Declare the accepted names on the model instead, listed next to the code that reads them so the two cannot drift, and let the existing payload validation return the 400 with the offending value and the accepted set. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Member
Author
|
@ekkea is this the correct approach for API validation? |
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.
Found while analysing #132.
What happens today
Optimizermatches the strategy names off the request against the branches that build the objective:A name that matches none of them reaches no branch, so the request is solved without any strategy. Nothing in the response says so — the schedule is cost optimal for the request,
statusisOptimal,objective_valueis right, and only the shape of the grid profile gives it away.That is what a request answered like #132 looks like. Replaying that request on
mainwith the strategy string intact levels grid export to a flat 658 W plateau; replaying it with the strategy misspelled reproduces the reported export peak of 1179.2 W exactly — the rawmax(ft - gt)of the request, i.e. no levelling at all, at an identical bill.openapi.yamlhas carried the enum since the strategies were added:but the flask-restx input model declared a plain
fields.String, so nothing validated it. The documented contract and the enforced one had drifted apart.The change
Declare the accepted names on the input model, listed in
optimizer.pynext to the code that reads them so the list and the branches cannot drift:The existing
@api.expect(..., validate=True)then does the rest, and names the offending value and the accepted set:{ "message": "Input payload validation failed", "details": { "strategy.charging_strategy": "'attenuate_fedin_peaks' is not one of ['none', 'charge_before_export', 'attenuate_demand_peaks', 'attenuate_feedin_peaks', 'attenuate_grid_peaks']" } }An absent
strategyobject, an absent field, andnoneare all unchanged. A charging strategy passed in the discharging slot is now rejected too — the two sets are disjoint and were never interchangeable.Breaking change
A caller sending a name outside the documented enum gets a 400 where it used to get a schedule. That is the point: today it gets a schedule that quietly ignores what it asked for. Worth calling out for the release notes, since evcc is the caller.
Verification
uv run pytestgreen, 98 tests.uv run ruff checkclean.tests/test_app.py::test_unknown_strategy_is_rejectedpins both halves: three rejected names, and every documented charging × discharging combination plus an absent strategy still solving.Not in scope
This does not change any schedule, and it is not a fix for the optimisation reported in #132 — that request levels correctly on current
main. It removes the failure mode that makes a dropped strategy indistinguishable from a working one.🤖 Generated with Claude Code