diff --git a/apps/predbat/predbat.py b/apps/predbat/predbat.py
index 3aceb34ca..4b0efa9b9 100644
--- a/apps/predbat/predbat.py
+++ b/apps/predbat/predbat.py
@@ -5441,6 +5441,7 @@ def rate_replicate(self, rates, rate_io={}, is_import=True, is_gas=False):
# Add 48 extra hours to make sure the whole cycle repeats another day
while minute < (self.forecast_minutes + 48 * 60):
if minute not in rates:
+ adjust_type = "copy"
# Take 24-hours previous if missing rate
if (minute >= 24 * 60) and ((minute - 24 * 60) in rates):
minute_mod = minute - 24 * 60
@@ -5466,6 +5467,7 @@ def rate_replicate(self, rates, rate_io={}, is_import=True, is_gas=False):
):
prev_rate = rate_offset
rate_offset = rate_offset - self.future_energy_rates_import[minute_mod] + self.future_energy_rates_import[minute]
+ adjust_type = "future"
elif (
(not is_import)
and (not is_gas)
@@ -5474,18 +5476,22 @@ def rate_replicate(self, rates, rate_io={}, is_import=True, is_gas=False):
and (minute_mod in self.future_energy_rates_export)
):
rate_offset = max(rate_offset - self.future_energy_rates_export[minute_mod] + self.future_energy_rates_export[minute], 0)
+ adjust_type = "future"
elif is_import:
rate_offset = rate_offset + self.metric_future_rate_offset_import
+ if self.metric_future_rate_offset_import:
+ adjust_type = "offset"
elif (not is_import) and (not is_gas):
rate_offset = max(rate_offset + self.metric_future_rate_offset_export, 0)
+ if self.metric_future_rate_offset_export:
+ adjust_type = "offset"
adjusted_rates[minute] = True
rates[minute] = rate_offset
- replicated_rates[minute] = True
+ replicated_rates[minute] = adjust_type
else:
rate_last = rates[minute]
- replicated_rates[minute] = False
minute += 1
return rates, replicated_rates
@@ -5553,7 +5559,7 @@ def find_charge_window(self, rates, minute, threshold_rate, find_high):
rate_low_average = self.dp2(rate_low_average / rate_low_count)
return rate_low_start, rate_low_end, rate_low_average
- def basic_rates(self, info, rtype, prev=None):
+ def basic_rates(self, info, rtype, prev=None, rate_replicate={}):
"""
Work out the energy rates based on user supplied time periods
works on a 24-hour period only and then gets replicated later for future days
@@ -5655,8 +5661,10 @@ def basic_rates(self, info, rtype, prev=None):
while minute_index < max_minute:
if rate_increment:
rates[minute_index] = rates.get(minute_index, 0.0) + rate
+ rate_replicate[minute_index] = "increment"
else:
rates[minute_index] = rate
+ rate_replicate[minute_index] = "user"
if load_scaling is not None:
self.load_scaling_dynamic[minute_index] = load_scaling
if date or not prev:
@@ -5782,7 +5790,7 @@ def add_now_to_octopus_slot(self, octopus_slots, now_utc):
self.log("Car is charging now - added new IO slot {}".format(slot))
return octopus_slots
- def load_saving_slot(self, octopus_saving_slots, export=False):
+ def load_saving_slot(self, octopus_saving_slots, export=False, rate_replicate={}):
"""
Load octopus saving session slot
"""
@@ -5819,6 +5827,7 @@ def load_saving_slot(self, octopus_saving_slots, export=False):
else:
self.rate_import[minute] += rate
self.load_scaling_dynamic[minute] = self.load_scaling_saving
+ rate_replicate[minute] = "saving"
def load_octopus_slots(self, octopus_slots):
"""
@@ -6438,6 +6447,32 @@ def publish_rates_import(self):
attributes={"friendly_name": "Next+1 low rate cost", "state_class": "measurement", "unit_of_measurement": "p", "icon": "mdi:currency-usd"},
)
+ def adjust_symbol(self, adjust_type):
+ """
+ Returns an HTML symbol based on the adjust rate type.
+
+ Parameters:
+ - adjust_type (str): The type of adjustment.
+
+ Returns:
+ - symbol (str): The symbol corresponding to the adjust_type.
+ """
+ symbol = ""
+ if adjust_type:
+ if adjust_type == "offset":
+ symbol = "? ⅆ"
+ elif adjust_type == "future":
+ symbol = "? ⚖"
+ elif adjust_type == "user":
+ symbol = "="
+ elif adjust_type == "increment":
+ symbol = "±"
+ elif adjust_type == "saving":
+ symbol = "$"
+ else:
+ symbol = "?"
+ return symbol
+
def publish_html_plan(self, pv_forecast_minute_step, load_minutes_step, end_record):
"""
Publish the current plan in HTML format
@@ -6630,10 +6665,12 @@ def publish_html_plan(self, pv_forecast_minute_step, load_minutes_step, end_reco
state += " ⅎ"
# Import and export rates -> to string
- if self.rate_import_replicated.get(minute, False):
- rate_str_import = "%02.02f ?" % (rate_value_import)
+ adjust_type = self.rate_import_replicated.get(minute, None)
+ adjust_symbol = self.adjust_symbol(adjust_type)
+ if adjust_symbol:
+ rate_str_import = "%02.02f %s" % (rate_value_import, adjust_symbol)
else:
- rate_str_import = "%02.02f" % rate_value_import
+ rate_str_import = "%02.02f" % (rate_value_import)
if plan_debug:
rate_str_import += " (%02.02f)" % (rate_value_import / self.battery_loss / self.inverter_loss + self.metric_battery_cycle)
@@ -6641,10 +6678,12 @@ def publish_html_plan(self, pv_forecast_minute_step, load_minutes_step, end_reco
if charge_window_n >= 0:
rate_str_import = "" + rate_str_import + ""
- if self.rate_export_replicated.get(minute, False):
- rate_str_export = "%02.02f ?" % (rate_value_export)
+ adjust_type = self.rate_export_replicated.get(minute, None)
+ adjust_symbol = self.adjust_symbol(adjust_type)
+ if adjust_symbol:
+ rate_str_export = "%02.02f %s" % (rate_value_export, adjust_symbol)
else:
- rate_str_export = "%2.02f" % (rate_value_export)
+ rate_str_export = "%02.02f" % (rate_value_export)
if plan_debug:
rate_str_export += " (%02.02f)" % (rate_value_export * self.battery_loss_discharge * self.inverter_loss - self.metric_battery_cycle)
@@ -10323,9 +10362,9 @@ def fetch_sensor_data(self):
self.rate_import = self.rate_scan(self.rate_import, print=False)
self.rate_import, self.rate_import_replicated = self.rate_replicate(self.rate_import, self.io_adjusted, is_import=True)
self.rate_import = self.rate_add_io_slots(self.rate_import, self.octopus_slots)
- self.load_saving_slot(octopus_saving_slots, export=False)
+ self.load_saving_slot(octopus_saving_slots, export=False, rate_replicate=self.rate_import_replicated)
if "rates_import_override" in self.args:
- self.rate_import = self.basic_rates(self.get_arg("rates_import_override", [], indirect=False), "import", self.rate_import)
+ self.rate_import = self.basic_rates(self.get_arg("rates_import_override", [], indirect=False), "import", self.rate_import, self.rate_import_replicated)
self.rate_import = self.rate_scan(self.rate_import, print=True)
else:
self.log("Warning: No import rate data provided")
@@ -10337,9 +10376,9 @@ def fetch_sensor_data(self):
self.rate_export, self.rate_export_replicated = self.rate_replicate(self.rate_export, is_import=False)
# For export tariff only load the saving session if enabled
if self.rate_export_max > 0:
- self.load_saving_slot(octopus_saving_slots, export=True)
+ self.load_saving_slot(octopus_saving_slots, export=True, rate_replicate=self.rate_export_replicated)
if "rates_export_override" in self.args:
- self.rate_export = self.basic_rates(self.get_arg("rates_export_override", [], indirect=False), "export", self.rate_export)
+ self.rate_export = self.basic_rates(self.get_arg("rates_export_override", [], indirect=False), "export", self.rate_export, self.rate_export_replicated)
self.rate_export = self.rate_scan_export(self.rate_export, print=True)
else:
self.log("Warning: No export rate data provided")
diff --git a/docs/predbat-plan-card.md b/docs/predbat-plan-card.md
index 4944b9344..222de87ba 100644
--- a/docs/predbat-plan-card.md
+++ b/docs/predbat-plan-card.md
@@ -43,6 +43,23 @@ For every half hour period (slot) that Predbat has planned for (the *forecast_ho
- The forecast cost for the half hour slot
- A running total cost
+Rate symbols (import and export):
+
+- ? ⅆ - Rate that has been modified based on **input_number.predbat_metric_future_rate_offset_import** or **input_number.predbat_metric_future_rate_offset_export**
+- ? ⚖ - Rate that has been estimated using future rate estimation data (e.g. Nordpool)
+- = - Rate that has been overridden by the users apps.yaml
+- ± - Rate that has been adjusted with a rate offset in the users apps.yaml
+- $ - Rate that has been adjusted for an Octopus Saving session
+- ? - Rate that has not yet been defined and the previous days data was used instead
+Battery SOC symbols:
+- → - Current SOC expected to remain level
+- ↘ - Current SOC expected to fall
+- ↗ - Current SOC expected to rise
+Cost symbols:
+- → - Current cost expected to remain level
+- ↘ - Current cost expected to fall (due to export)
+- ↗ - Current cost expected to rise (due to import or standing charge)
+
Explaining each column in the Predbat plan in more detail:
- **Time** - Predbat plans your home, solar and battery load in 30 minute slots, on the :00 and :30 minutes past each hour.