Skip to content

Fix FIT income calculation to account for PV clipping#3

Merged
dandwhelan merged 1 commit into
mainfrom
claude/fix-payment-calculator-qNl8d
Apr 26, 2026
Merged

Fix FIT income calculation to account for PV clipping#3
dandwhelan merged 1 commit into
mainfrom
claude/fix-payment-calculator-qNl8d

Conversation

@dandwhelan

Copy link
Copy Markdown
Owner

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 > 0 AND metric_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:

    • FIT disabled (no income)
    • Generation-only tariff (with metered export)
    • Deemed-only tariff (legacy contracts)
    • Full FIT (generation + deemed export)
    • Clipping impact on generation income
    • Clipping impact on deemed-export income
  • 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

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
@dandwhelan dandwhelan merged commit 36410c5 into main Apr 26, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants