feat: add /optimize/level-schedule endpoint for solar-first strategy - #98
feat: add /optimize/level-schedule endpoint for solar-first strategy#981va13n7in wants to merge 4 commits into
Conversation
Add CC-CV taper modeling to the optimizer. Batteries can now specify a charge_profile (e.g. lifepo4_conservative) that limits charge power as a function of SoC, matching real-world BMS behavior. The MILP solver uses iterative constraint tightening: solve, extract SoC trajectory, compute per-slot P_max(SoC) from the profile, add upper bound constraints, re-solve until convergence (max 5 iterations). New API fields in BatteryConfig: - charge_profile: named profile (e.g. lifepo4_conservative) - charge_knee: SoC% where taper begins (override) - charge_k: exponential decay constant (override) - charge_c_rate_float: minimum C-rate at float (override) New module: charging_profiles.py with profile definitions and max_charge_power() calculation.
Replace the iterative constraint tightening (3-6 re-solves) with piecewise-linear upper bounds added at model build time. The CC-CV taper curve is convex, so tangent lines at 4 breakpoints (knee to 100% SoC) provide valid linear constraints on charge power as a function of s[i][t-1]. One solve, no stale bounds, bounded wall clock.
Add a new optimization endpoint that finds the optimal flat export ceiling for charging a battery from solar surplus. Instead of minimizing cost (MILP), this maximizes self-use by flattening the grid export curve. The algorithm finds the export level where all active charging slots reduce the forecasted feed-in power by the same amount. Charge power per slot is variable and respects the CC-CV taper profile. The response uses the same format as /optimize/charge-schedule (batteries[].charging_power/discharging_power/state_of_charge), so consumers can process both endpoints with identical code. New endpoint: POST /optimize/level-schedule New function: find_optimal_level() in charging_profiles.py New function: _level_to_milp_format() in app.py
ddf87de to
ac5f77e
Compare
|
Its still unclear to me (can't find in which related issue I've asked but didn't get an answer):
|
|
The dependency on #97 is because find_optimal_level() calls The difference to attenuate_grid_peaks: that is a cost-neutral Solar-first is a different optimization objective entirely. It For flat-rate tariffs both point in the same direction, but
|
I still don't get why we should be doing this. Why would self-consumption be a relevant goal and preferable over economic value plus peak shaving? /cc @premultiply |



Add /optimize/level-schedule endpoint for solar-first strategy.
Finds the optimal flat export ceiling that charges a battery from solar
surplus while minimizing peak grid export. Response uses the same format
as /optimize/charge-schedule for identical downstream processing.
Depends on #97 (charge_profile).
Part of evcc-io/evcc#31963