From 4f339dac238b15035b2cbe40e357baa1cf3e050d Mon Sep 17 00:00:00 2001 From: Trefor Southwell Date: Thu, 7 May 2026 08:54:06 +0100 Subject: [PATCH] New chart for PV accuracy --- apps/predbat/predbat.py | 2 +- apps/predbat/solcast.py | 4 ++++ apps/predbat/web.py | 20 ++++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/apps/predbat/predbat.py b/apps/predbat/predbat.py index 4723d7f56..c1036a03a 100644 --- a/apps/predbat/predbat.py +++ b/apps/predbat/predbat.py @@ -36,7 +36,7 @@ import requests import asyncio -THIS_VERSION = "v8.37.10" +THIS_VERSION = "v8.38.0" from download import predbat_update_move, predbat_update_download, check_install, resolve_predbat_repository, DEFAULT_PREDBAT_REPOSITORY from const import MINUTE_WATT diff --git a/apps/predbat/solcast.py b/apps/predbat/solcast.py index ab2609506..8167063bd 100644 --- a/apps/predbat/solcast.py +++ b/apps/predbat/solcast.py @@ -842,6 +842,7 @@ def publish_pv_stats(self, pv_forecast_data, divide_by, period): "remainingCL": dp2(total_left_todayCL), "detailedForecast": forecast_day[day], }, + app="solar", ) self.dashboard_item( "sensor." + self.prefix + "_pv_forecast_h0", @@ -856,6 +857,7 @@ def publish_pv_stats(self, pv_forecast_data, divide_by, period): "now90": dp2(power_now90), "nowCL": dp2(power_nowCL), }, + app="solar", ) else: day_name = "tomorrow" if day == 1 else "d{}".format(day) @@ -877,6 +879,7 @@ def publish_pv_stats(self, pv_forecast_data, divide_by, period): "totalCL": dp2(total_dayCL[day]), "detailedForecast": forecast_day[day], }, + app="solar", ) def pv_calibration(self, pv_forecast_minute, pv_forecast_minute10, pv_forecast_data, create_pv10, divide_by, max_kwh, forecast_days, period=None): @@ -1167,6 +1170,7 @@ def pack_and_store_forecast(self, pv_forecast_minute, pv_forecast_minute10): "device_class": "power", "state_class": "measurement", }, + app="solar", ) async def fetch_pv_forecast(self): diff --git a/apps/predbat/web.py b/apps/predbat/web.py index f69f0053f..e11f1d143 100644 --- a/apps/predbat/web.py +++ b/apps/predbat/web.py @@ -2958,6 +2958,25 @@ def get_chart(self, chart): {"name": "Forecast CL", "data": pv_today_forecastCL, "opacity": "0.3", "stroke_width": "2", "stroke_curve": "smooth", "chart_type": "area", "color": "#e90a0a"}, ] text += self.render_chart(series_data, "kW", "Solar Forecast", now_str) + elif chart == "PVAccuracy": + # Get pv_today history once and extract total and remaining attributes per timestamp + pv_today_hist = self.get_history_wrapper("sensor." + self.prefix + "_pv_today", 7, required=False) + pv_total_raw = history_attribute(pv_today_hist, attributes=True, state_key="total") + pv_remaining_raw = history_attribute(pv_today_hist, attributes=True, state_key="remaining") + # Compute forecast so far = total - remaining per timestamp + pv_forecast_sofar_raw = {} + for ts, total_val in pv_total_raw.items(): + remaining_val = pv_remaining_raw.get(ts, 0) + pv_forecast_sofar_raw[ts] = dp2(max(total_val - remaining_val, 0)) + pv_forecast_sofar = prune_today(pv_forecast_sofar_raw, self.now_utc, self.midnight_utc, prune=False) + # Get actual PV energy over time + pv_actual_hist = history_attribute(self.get_history_wrapper(self.prefix + ".pv_energy_h0", 7, required=False)) + pv_actual = prune_today(pv_actual_hist, self.now_utc, self.midnight_utc, prune=False) + series_data = [ + {"name": "PV Forecast (so far)", "data": pv_forecast_sofar, "opacity": "1.0", "stroke_width": "2", "stroke_curve": "smooth", "color": "#a8a8a7"}, + {"name": "PV Actual", "data": pv_actual, "opacity": "1.0", "stroke_width": "3", "stroke_curve": "smooth", "color": "#f5c43d"}, + ] + text += self.render_chart(series_data, "kWh", "PV Forecast vs Actual", now_str) elif chart == "LoadML": load_today_history = self.get_history_with_now_attrs("sensor." + self.prefix + "_load_ml_stats", 7) # Get historical load data for last 24 hours @@ -3195,6 +3214,7 @@ async def html_charts(self, request): text += f'InDay' text += f'PV' text += f'PV7' + text += f'PVAccuracy' text += f'Savings' text += f'MarginalCosts' # Only show LoadML chart if ML is enabled