Automatic Error Correction for Solar Forecast based upon last 7 days

I use my Solar forecast from SolCast PV to automate my SolaX inverter and battery settings. Basically, my system has three modes: Full Sun, Partial Sun, or No Sun. I have then created three Scenes that reflect the settings I want in these three scenarios, based upon tomorrows Solar forecast.

The issue I have, is that the Solar forecast isn’t always accurate and I end up overriding the scene based upon a local weather forecast that is more right than wrong for my area. This is sometimes a good idea and sometime I should have just it left alone!

So I decided to create an automation that reviews the accuracy between the Forecast Solar and Actual Solar for the past 7 days, and creates a % Error Correction value for ongoing estimates. I have to admit, my coding ability has, shall we say, degraded since I moved into a management role, and then retired! :rofl:

So I thought that I would combine this challenge with my other intellectual project, which is getting to know ChatGPT, Google Gemini, and Claude in detail and what the strengths and weaknesses are of each. :nerd_face:

Google Gemini couldn’t generate YAML code that Home Assistant could understand, nothing even close. ChatGPT, however was on a different level and was very fast.

Basically, I have used/created these entities and helpers.

  • sensor.solcast_pv_forecast_forecast_today: Today’s solar forecast (kWh).
  • sensor.brenchley_today_s_solar_energy: Today’s actual solar generation (kWh).
  • sensor.solcast_pv_forecast_forecast_tomorrow: Tomorrow’s forecast (kWh).
  • input_text.forecast_error_history: Stores the last 7 daily % errors.
  • input_number.adjusted_solar_forecast: Stores the final corrected forecast.

(YAML code available, if anyone wants further detail.)

Then, I’ve (ChatGPT!) created this automation:

alias: Adjust Tomorrow's Solar Forecast With Learning
description: >
  Adjusts tomorrow’s solar forecast using a 7-day rolling average of the %
  difference between actual and forecasted solar. Caps each day's contribution
  to ±10% to avoid distortion.

trigger:
  - platform: time
    at: "20:00:00"  # Run every evening at 8 PM

variables:
  # Retrieve today's forecasted solar generation (in kWh)
  forecast_today: "{{ states('sensor.solcast_pv_forecast_forecast_today') | float(0) }}"

  # Retrieve today's actual measured solar generation (in kWh)
  actual_today: "{{ states('sensor.brenchley_today_s_solar_energy') | float(0) }}"

  # Retrieve tomorrow's forecasted solar generation (in kWh)
  forecast_tomorrow: "{{ states('sensor.solcast_pv_forecast_forecast_tomorrow') | float(0) }}"

  # Calculate today's percentage error between forecast and actual
  # Capped at ±10% to reduce influence of outliers
  percent_error_today: |-
    {% if forecast_today > 0 %}
      {% set raw_error = (actual_today - forecast_today) / forecast_today * 100 %}
      {% if raw_error > 10 %}
        10
      {% elif raw_error < -10 %}
        -10
      {% else %}
        {{ raw_error }}
      {% endif %}
    {% else %}
      0  # Avoid divide-by-zero or meaningless result
    {% endif %}

  # Get previous percentage errors from text helper
  history_raw: "{{ states('input_text.forecast_error_history') }}"

  # Parse historical values into float list, filtering out invalid entries
  history_list: >-
    {% set raw = history_raw.split(',') if history_raw else [] %}
    {{ raw | select('match', '^-?[0-9.]+$') | map('float') | list }}

  # Add today's error and trim the list to retain only the last 7 entries
  updated_list: >-
    {% set new_list = (history_list + [percent_error_today | float])[-7:] %}
    {{ new_list }}

  # Calculate average percentage error over the stored days
  error_avg: "{{ updated_list | sum / updated_list | length }}"

  # Apply the averaged % error to tomorrow's forecast to produce an adjusted value
  adjusted_forecast: "{{ forecast_tomorrow * (1 + error_avg / 100) }}"

action:
  # Update the forecast error history helper with the new 7-day list
  - service: input_text.set_value
    target:
      entity_id: input_text.forecast_error_history
    data:
      value: "{{ updated_list | map('round', 2) | join(',') }}"

  # Save the adjusted forecast value into a number helper for downstream use
  - service: input_number.set_value
    target:
      entity_id: input_number.adjusted_solar_forecast
    data:
      value: "{{ adjusted_forecast | round(2) }}"

mode: single  # Prevent overlapping runs

It all appears to work, although I’m only on day2!!!

EDIT: I’ve amended the code after asking ChatGPT to add comments to aid code maintenance.

1 Like

Hi! Thanks for sharing. Do you have any feedback after some time of use? Is the correction factor as expected? Is the prediction actually closer to the production?
Thanks again!

Hi

Thanks for asking, I’ve found a massive improvement in the solar accuracy. I have the ability to adjust the number of days that the automation monitors the solar forecast against actual, I’ve selected 7 days initially but next year I may increase that to 14 days.

The advantage with the shorter duration is that it adjusts to variances alot quicker but the adjustments are larger per day. To try and compensate for this, I have placed limits on the maximum adjustment per day which has stopped some of the more erratic changes when 50kWh of solar was forecast but the cloud didn’t clear and only 10kWh was recorded.

1 Like