Feature request: model a rate-conditional export limit (curtailment under a price threshold)
Is your feature request related to a problem?
When the export price goes negative (dynamic tariff), exporting PV is a direct cost. Many of us prevent this by curtailing the inverter to net-zero export during negative-price slots. In my case an automation sets the SolarEdge site export limit to 0 when export_price < 0.
The problem is that Predbat has no way to know this curtailment happens. With a static export_limit (mine is 17000 W), Predbat models the export as proceeding normally during those slots. That has two consequences:
- Inaccurate cost/return projection. Predbat books an export cost for negative-price slots that I actually avoid through curtailment, and it models the PV as exported rather than curtailed. The day's projected economics don't match reality.
- PV calibration pollution (separate but related). Because the inverter throttles back PV production during curtailment, measured generation drops. With
metric_pv_calibration_enable on (the default), Predbat treats this as panel underperformance and reduces the forecast. The docs already warn about this and the workaround is to turn calibration off — but that's a workaround, and it means Predbat is flying blind about why the generation is lower.
This is closely related to three existing reports about export-limit / curtailment modelling that are still open:
Those three are all about a static export limit (DNO caps) that should be modelled and optimised around. My case is the same mechanism but rate-conditional (the limit only applies when the price is below a threshold). A single feature would cover all four situations.
Describe the solution you'd like
A rate-conditional export limit in the prediction model. Concretely:
- A new optional config (e.g.
export_limit_rate_threshold and an associated limit value, or an extension of the existing export_limit) that says: when the slot's export rate is below threshold X, model the export limit as Y (e.g. 0) instead of the normal value.
- Predbat does not drive the curtailment, it only models it. The actual curtailment is left to the user's inverter setting or automation. This keeps the feature inverter-agnostic and avoids any new control failure modes.
The building blocks already exist in run_prediction (apps/predbat/prediction.py):
- The per-slot export rate is already available,
export_rate = rate_export.get(minute_absolute, 0).
- The export-limit clipping already happens per-slot in the "Export limit, clip PV output" block, using
export_limit and accumulating clipped_today.
- The cost function already penalises export at a negative rate (
metric -= export_rate * energy).
So the change is small and localised: derive an effective per-slot export limit from the rate before the clipping block, e.g.
# near the top of the slot loop, after export_rate is read
effective_export_limit = export_limit
if export_limit_rate_threshold is not None and export_rate < export_limit_rate_threshold:
effective_export_limit = export_limit_negative # e.g. 0, * step
# in the "Export limit, clip PV output" block, use effective_export_limit
if diff < 0 and abs(diff) > effective_export_limit:
over_limit = abs(diff) - effective_export_limit
...
With that in place, during sub-threshold slots Predbat models net export as capped (PV above house load + battery headroom is curtailed, not exported), the return projection matches reality, and the optimiser sees the stored-PV value of keeping battery headroom ahead of a sunny negative-price window (avoided future import) rather than booking a phantom export cost.
For the static DNO cases (#3442 / #3481 / #3828) the same plumbing applies with the threshold disabled — the limit is just always active. The one extra step those cases additionally want is for the curtailed energy (clipped_today) to carry a cost/lost-value signal into the optimiser so it actively lowers SOC ahead of high-generation days. That's a deeper change to the cost function and could be a follow-up; the rate-conditional limit modelling stands on its own.
The same curtailment signal also fixes PV calibration pollution
There's a second, separate problem with the same root cause, and knowing when curtailment happens fixes both.
pv_calibration in solcast.py calibrates the forecast by comparing ~7 days of measured pv_today against the historical forecast (sensor.<prefix>_pv_forecast_h0), per slot and per day, and derives a scaling factor actual / forecast. It makes no distinction between "panels underperformed" and "the inverter was curtailed", it just sees actual < forecast and scales the forecast down. The lower clamp is PV_CALIBRATION_LOWEST = 0.20, so a curtailed history can drag the forecast down to 20% of its real value (this matches the 42 kWh → ~7 kWh collapse reported in #3373).
So every time export is curtailed during negative-price slots, measured generation for those slots drops, and the calibration reads it as structural panel underperformance and pulls the future forecast down, even though the panels were fine and only the export was withheld.
The current documented workaround is to set metric_pv_calibration_enable off. That's fine for Solcast users, because the docs point them to Solcast's auto-dampening which already accounts for shutoff periods. But Forecast.Solar users have no equivalent. Forecast.Solar is a pure weather forecast with no feedback from actual production, so turning calibration off removes their only feedback layer, leaving only a manual, static pv_scaling knob.
The fix is the same signal: exclude curtailed slots from the calibration input. When building pv_power_hist_by_slot and past_day_actual (around the history-aggregation loop in pv_calibration), skip slots whose historical export rate was below the threshold. Calibration then keeps working on normal days (real shading / forecast-bias correction) but is no longer polluted by deliberate curtailment.
That means one piece of knowledge "when is export curtailed" resolves two independent issues:
- Modelling export correctly instead of booking an avoided cost (the export-limit problem above).
- Keeping PV calibration clean so the forecast isn't dragged down by curtailment, which specifically rescues Forecast.Solar users from the current off-switch trade-off.
That's the argument for treating these together rather than as separate fixes.
Describe alternatives you've considered
- Predbat driving the curtailment (a switch/service that sets the inverter's export limit). Rejected as the primary approach: it would need a per-inverter control implementation, introduces a second actor that can conflict with existing charge/discharge control and user automations, and adds failure modes (limit left hanging if a service call fails). Modelling-only is simpler, safer, and works for everyone. A control hook could be added later as an optional layer on top, via the existing service-API pattern.
- Dynamically updating
export_limit from an entity. Doesn't work: export_limit is a system-wide scalar applied to all forecast slots equally, not a per-slot series. A momentary value would either block all future export or allow it everywhere — it can't represent "export OK now, not in 3 hours."
- Inflating the export rate /
pv_metric10_weight (suggested in Sigenergy — Export limit curtailment cost modelling with FIT generation payment #3442). Indirect and imprecise; doesn't model the physical curtailment.
Additional context
- Setup: 2× SolarEdge SE10K-RWB48BFN4 (leader/follower), 2× SE Home Battery (28.35 kWh total), ~36 kWp across three planes, dynamic tariff. Forecast via Forecast.Solar.
- Predbat v8.39.7
- Happy to work on a PR for the rate-conditional modelling
Feature request: model a rate-conditional export limit (curtailment under a price threshold)
Is your feature request related to a problem?
When the export price goes negative (dynamic tariff), exporting PV is a direct cost. Many of us prevent this by curtailing the inverter to net-zero export during negative-price slots. In my case an automation sets the SolarEdge site export limit to 0 when
export_price < 0.The problem is that Predbat has no way to know this curtailment happens. With a static
export_limit(mine is 17000 W), Predbat models the export as proceeding normally during those slots. That has two consequences:metric_pv_calibration_enableon (the default), Predbat treats this as panel underperformance and reduces the forecast. The docs already warn about this and the workaround is to turn calibration off — but that's a workaround, and it means Predbat is flying blind about why the generation is lower.This is closely related to three existing reports about export-limit / curtailment modelling that are still open:
Those three are all about a static export limit (DNO caps) that should be modelled and optimised around. My case is the same mechanism but rate-conditional (the limit only applies when the price is below a threshold). A single feature would cover all four situations.
Describe the solution you'd like
A rate-conditional export limit in the prediction model. Concretely:
export_limit_rate_thresholdand an associated limit value, or an extension of the existingexport_limit) that says: when the slot's export rate is below threshold X, model the export limit as Y (e.g. 0) instead of the normal value.The building blocks already exist in
run_prediction(apps/predbat/prediction.py):export_rate = rate_export.get(minute_absolute, 0).export_limitand accumulatingclipped_today.metric -= export_rate * energy).So the change is small and localised: derive an effective per-slot export limit from the rate before the clipping block, e.g.
With that in place, during sub-threshold slots Predbat models net export as capped (PV above house load + battery headroom is curtailed, not exported), the return projection matches reality, and the optimiser sees the stored-PV value of keeping battery headroom ahead of a sunny negative-price window (avoided future import) rather than booking a phantom export cost.
For the static DNO cases (#3442 / #3481 / #3828) the same plumbing applies with the threshold disabled — the limit is just always active. The one extra step those cases additionally want is for the curtailed energy (
clipped_today) to carry a cost/lost-value signal into the optimiser so it actively lowers SOC ahead of high-generation days. That's a deeper change to the cost function and could be a follow-up; the rate-conditional limit modelling stands on its own.The same curtailment signal also fixes PV calibration pollution
There's a second, separate problem with the same root cause, and knowing when curtailment happens fixes both.
pv_calibrationinsolcast.pycalibrates the forecast by comparing ~7 days of measuredpv_todayagainst the historical forecast (sensor.<prefix>_pv_forecast_h0), per slot and per day, and derives a scaling factoractual / forecast. It makes no distinction between "panels underperformed" and "the inverter was curtailed", it just sees actual < forecast and scales the forecast down. The lower clamp isPV_CALIBRATION_LOWEST = 0.20, so a curtailed history can drag the forecast down to 20% of its real value (this matches the 42 kWh → ~7 kWh collapse reported in #3373).So every time export is curtailed during negative-price slots, measured generation for those slots drops, and the calibration reads it as structural panel underperformance and pulls the future forecast down, even though the panels were fine and only the export was withheld.
The current documented workaround is to set
metric_pv_calibration_enableoff. That's fine for Solcast users, because the docs point them to Solcast's auto-dampening which already accounts for shutoff periods. But Forecast.Solar users have no equivalent. Forecast.Solar is a pure weather forecast with no feedback from actual production, so turning calibration off removes their only feedback layer, leaving only a manual, staticpv_scalingknob.The fix is the same signal: exclude curtailed slots from the calibration input. When building
pv_power_hist_by_slotandpast_day_actual(around the history-aggregation loop inpv_calibration), skip slots whose historical export rate was below the threshold. Calibration then keeps working on normal days (real shading / forecast-bias correction) but is no longer polluted by deliberate curtailment.That means one piece of knowledge "when is export curtailed" resolves two independent issues:
That's the argument for treating these together rather than as separate fixes.
Describe alternatives you've considered
export_limitfrom an entity. Doesn't work:export_limitis a system-wide scalar applied to all forecast slots equally, not a per-slot series. A momentary value would either block all future export or allow it everywhere — it can't represent "export OK now, not in 3 hours."pv_metric10_weight(suggested in Sigenergy — Export limit curtailment cost modelling with FIT generation payment #3442). Indirect and imprecise; doesn't model the physical curtailment.Additional context