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.