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