Fix FIT income calculation to account for PV clipping#3
Merged
Conversation
The FIT (Feed-in Tariff) algorithm had several bugs that produced incorrect income figures and biased the optimiser: - FIT income was applied to forecast PV rather than the PV the inverter actually delivered, so it ignored the energy lost to export-limit / inverter-limit clipping. The optimiser then saw no benefit from plans that absorb mid-day excess into the battery, defeating the documented "battery headroom for solar" behaviour. The FIT calculation now runs after both clipping points and is charged on `pv_now - clipped_this_step`. - The grid export rate was unconditionally zeroed whenever a generation tariff was set, which broke users who combine FIT generation tariff with a metered export tariff (e.g. SEG). The export rate is now only zeroed when deemed-export is actually configured. - FIT logic was gated on `metric_fit_generation_rate > 0`, so a deemed-export-only configuration (legacy installs) silently produced no FIT income. The gate now also activates on a non-zero deemed-export rate + percentage. Matching gates are updated in plan.py and output.py. - Removed unused `fit_*_no_pvbat` extractions in output.py. Adds tests/test_fit.py covering: FIT disabled, generation-only with metered export coexistence, deemed-only, full FIT, and clipping reducing income for both generation and deemed branches. https://claude.ai/code/session_012buoJB7DD947JUuDCeYWtr
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.
Summary
This PR fixes the Feed-in Tariff (FIT) income calculation to properly account for PV clipping. Previously, FIT generation and deemed-export income were calculated on the full PV forecast regardless of how much power was actually delivered by the inverter. Now the calculation only charges FIT rates on the PV that actually made it through the inverter after clipping losses.
Key Changes
prediction.py: Moved FIT income calculation to occur after clipping is applied, so it charges generation and deemed-export tariffs only on
pv_delivered(forecast minus clipped amount this step). This makes the optimizer correctly penalize clipped PV as lost FIT income, encouraging battery absorption of mid-day excess.prediction.py: Fixed export rate zeroing logic to only zero the export rate when deemed-export is actually active (both
metric_fit_deemed_export_rate > 0ANDmetric_fit_deemed_export_percentage > 0). This allows generation-only FIT contracts to retain the configured export rate signal.output.py & plan.py: Updated FIT income dashboard publishing conditions to check for either generation tariff OR active deemed-export (instead of only generation tariff), ensuring FIT income is reported for all FIT contract types.
test_fit.py: Added comprehensive test suite with 6 test cases covering:
unit_test.py: Integrated new FIT tests into the test runner.
Implementation Details
The key insight is that FIT income should be calculated on delivered PV, not forecast PV. By moving the FIT calculation after the clipping step and using
pv_delivered = max(pv_now - (clipped_today - clipped_before_step), 0), the optimizer now correctly understands that clipped PV represents lost FIT revenue, which encourages more intelligent battery charging strategies during high-generation periods.https://claude.ai/code/session_012buoJB7DD947JUuDCeYWtr