Skip to content

Commit 738af35

Browse files
committed
feat(custom): updated intelligent rate adjustments to use started dispatches (1.5 hour dev time)
1 parent 89bc8b9 commit 738af35

File tree

14 files changed

+590
-86
lines changed

14 files changed

+590
-86
lines changed

_docs/entities/intelligent.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ This sensor is used to determine if you're currently in a planned dispatch perio
3030
|-----------|------|-------------|
3131
| `planned_dispatches` | `array` | An array of the dispatches that are currently planned by Octopus Energy. |
3232
| `completed_dispatches` | `array` | An array of the dispatches that have been completed by Octopus Energy. This will only store up to the last 3 days worth of completed dispatches. |
33+
| `started_dispatches` | `array` | An array of the dispatches that have been planned by Octopus Energy and upon API refresh are still planned when the current 30 minute period started and is not in a boosting state. A planned dispatch will be added one 30 minute period at a time. This will only store up to the last 3 days worth of started dispatches. This is used to determine historic off peak rates. For example if you have a planned dispatch of `2025-04-01T10:00:00`-`2025-04-01T11:00:00`, at `2025-04-01T10:01:00` if the planned dispatch is still available the period of `2025-04-01T10:00:00`-`2025-04-01T10:30:00` will be added. |
3334
| `provider` | `string` | The provider of the intelligent features |
3435
| `vehicle_battery_size_in_kwh` | `float` | The size of the target vehicle battery in kWh. |
3536
| `charge_point_power_in_kw` | `float` | The power of the charge point battery in kW. |
@@ -45,9 +46,16 @@ Each item in `planned_dispatch` or `completed_dispatches` have the following att
4546
| `start` | `datetime` | The start date/time of the dispatch |
4647
| `end` | `datetime` | The end date/time of the dispatch |
4748
| `charge_in_kwh` | `float` | The amount to be charged within the dispatch period. |
48-
| `source` | `string` | Determines what has caused the dispatch to be generated. Will be `smart-charge` or `bump-charge`. |
49+
| `source` | `string` | Determines what has caused the dispatch to be generated. Will be `smart-charge`, `bump-charge` or None. |
4950
| `location` | `string` | The location of the smart charge |
5051

52+
Each item in `started_dispatch` have the following attributes
53+
54+
| Attribute | Type | Description |
55+
|-----------|------|-------------|
56+
| `start` | `datetime` | The start date/time of the dispatch |
57+
| `end` | `datetime` | The end date/time of the dispatch |
58+
5159
!!! info
5260

5361
You can use the [data_last_retrieved sensor](./diagnostics.md#intelligent-dispatches-data-last-retrieved) to determine when the underlying data was last retrieved from the OE servers.

_docs/services.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,10 @@ This service is only available for the following sensors
339339

340340
Due to limitations with Home Assistant entities, this service will only refresh data for the associated statistic ids used for the recommended approach in the [energy dashboard](./setup/energy_dashboard.md#previous-day-consumption). This will not update the history of the entities themselves.
341341

342+
!!! warn
343+
344+
If you are on intelligent, the cost data will not be correct for charges outside of the normal off peak times. This is because this data isn't available.
345+
342346
### octopus_energy.register_rate_weightings
343347

344348
Allows you to configure weightings against rates at given times using factors external to the integration. These are applied when calculating [target rates](./setup/target_rate.md#external-rate-weightings) or [rolling target rates](./setup/rolling_target_rate.md#external-rate-weightings).

_docs/setup/account.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,25 +53,23 @@ By default, intelligent dispatches are retrieved [periodically](../faq.md#how-of
5353

5454
## Intelligent rates mode
5555

56-
If you are on an intelligent tariff then it's possible for you to get cheaper rates outside of your normal off peak periods if Octopus Energy schedules the charges and your car accepts the charges. The rate information provides by Octopus Energy doesn't take these periods into account, so the integration has to use the pending/completed dispatch information to adjust the rates appropriately. Due to the quality of the available data, this _can_ be off sometimes. This feature allows how the rate information is adjusted in these scenarios.
56+
If you are on an intelligent tariff then it's possible for you to get cheaper rates outside of your normal off peak periods if Octopus Energy schedules the charges and your car accepts the charges. The rate information provides by Octopus Energy doesn't take these periods into account, so the integration has to use the planned/completed dispatch information to adjust the rates appropriately. Due to the quality of the available data, this _can_ be off sometimes. This feature allows how the rate information is adjusted in these scenarios.
5757

58-
### Pending and completed dispatches will turn into off peak rates
58+
### Planned and started dispatches will turn into off peak rates
5959

60-
This is the default behaviour. In this scenario, all pending dispatches will be assumed to be converted into successful off peak charges by the car and therefore all rates during these periods will be converted into the off peak rate. This will be indicated by the `is_intelligent_adjusted` property. This is useful when planning other devices to turn on in the future during these cheap periods (e.g. by using a [target rate sensor](./target_rate.md)).
60+
This is the default behaviour. In this scenario, all planned dispatches will be assumed to be converted into successful off peak charges by the car and therefore all rates during these periods will be converted into the off peak rate. This will be indicated by the `is_intelligent_adjusted` property. This is useful when planning other devices to turn on in the future during these cheap periods (e.g. by using a [target rate sensor](./target_rate.md)).
6161

6262
!!! warning
6363

64-
One side effect of this is around cost sensors, where if a pending dispatch does not turn into a completed dispatch, the cost sensor can increase in value when the pending dispatch is removed.
64+
One side effect of this is around cost sensors, where if a planned dispatch does not turn into a started dispatch, the cost sensor can increase in value when the planned dispatch is removed.
6565

66-
### Only completed dispatches will turn into off peak rates
66+
### Only started dispatches will turn into off peak rates
6767

68-
In this scenario only completed dispatches will be taken into account for adjustments meaning all rates during only completed dispatch periods will be converted into the off peak rate. This will be indicated by the `is_intelligent_adjusted` property. This means no future planning can be made to take advantage of these cheap periods by rates alone.
68+
In this scenario only started dispatches will be taken into account for adjustments meaning all rates during only started dispatch periods will be converted into the off peak rate. This will be indicated by the `is_intelligent_adjusted` property. This means no future planning can be made to take advantage of these cheap periods by rates alone.
6969

7070
!!! warning
7171

72-
One side effect of this is around cost sensors, where when a completed dispatch arrives the cost sensor will decrease in value.
73-
74-
There have also been reports of some successful charges not resolving into completed dispatches.
72+
One side effect of this is around cost sensors, where when a started dispatch arrives the cost sensor will decrease in value.
7573

7674
## Home Pro
7775

custom_components/octopus_energy/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
CONFIG_MAIN_HOME_PRO_API_KEY,
5454
CONFIG_MAIN_INTELLIGENT_MANUAL_DISPATCHES,
5555
CONFIG_MAIN_INTELLIGENT_RATE_MODE,
56-
CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_COMPLETED_DISPATCHES,
56+
CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES,
5757
CONFIG_MAIN_OLD_API_KEY,
5858
CONFIG_VERSION,
5959
DATA_DISCOVERY_MANAGER,
@@ -504,7 +504,7 @@ async def async_setup_dependencies(hass, config):
504504
is_smart_meter,
505505
is_export_meter,
506506
planned_dispatches_supported,
507-
config[CONFIG_MAIN_INTELLIGENT_RATE_MODE] if CONFIG_MAIN_INTELLIGENT_RATE_MODE in config else CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_COMPLETED_DISPATCHES,
507+
config[CONFIG_MAIN_INTELLIGENT_RATE_MODE] if CONFIG_MAIN_INTELLIGENT_RATE_MODE in config else CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES,
508508
tariff_override)
509509

510510
mock_heat_pump = account_debug_override.mock_heat_pump if account_debug_override is not None else False

custom_components/octopus_energy/config_flow.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
CONFIG_MAIN_HOME_PRO_API_KEY,
2828
CONFIG_MAIN_INTELLIGENT_MANUAL_DISPATCHES,
2929
CONFIG_MAIN_INTELLIGENT_RATE_MODE,
30-
CONFIG_MAIN_INTELLIGENT_RATE_MODE_COMPLETED_DISPATCHES_ONLY,
31-
CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_COMPLETED_DISPATCHES,
30+
CONFIG_MAIN_INTELLIGENT_RATE_MODE_STARTED_DISPATCHES_ONLY,
31+
CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES,
3232
CONFIG_ROLLING_TARGET_HOURS_LOOK_AHEAD,
3333
CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE,
3434
CONFIG_TARGET_TARGET_TIMES_EVALUATION_MODE_ALL_IN_FUTURE_OR_PAST,
@@ -840,8 +840,8 @@ async def __async_setup_main_schema__(self, config, errors):
840840
vol.Required(CONFIG_MAIN_INTELLIGENT_RATE_MODE): selector.SelectSelector(
841841
selector.SelectSelectorConfig(
842842
options=[
843-
selector.SelectOptionDict(value=CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_COMPLETED_DISPATCHES, label="Pending and completed dispatches will turn into off peak rates"),
844-
selector.SelectOptionDict(value=CONFIG_MAIN_INTELLIGENT_RATE_MODE_COMPLETED_DISPATCHES_ONLY, label="Only completed dispatches will turn into off peak rates"),
843+
selector.SelectOptionDict(value=CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES, label="Planned and started dispatches will turn into off peak rates"),
844+
selector.SelectOptionDict(value=CONFIG_MAIN_INTELLIGENT_RATE_MODE_STARTED_DISPATCHES_ONLY, label="Only stared dispatches will turn into off peak rates"),
845845
],
846846
mode=selector.SelectSelectorMode.DROPDOWN,
847847
)
@@ -860,7 +860,7 @@ async def __async_setup_main_schema__(self, config, errors):
860860
CONFIG_MAIN_GAS_PRICE_CAP: config[CONFIG_MAIN_GAS_PRICE_CAP] if CONFIG_MAIN_GAS_PRICE_CAP in config else None,
861861
CONFIG_MAIN_FAVOUR_DIRECT_DEBIT_RATES: config[CONFIG_MAIN_FAVOUR_DIRECT_DEBIT_RATES] if CONFIG_MAIN_FAVOUR_DIRECT_DEBIT_RATES in config else True,
862862
CONFIG_MAIN_INTELLIGENT_MANUAL_DISPATCHES: config[CONFIG_MAIN_INTELLIGENT_MANUAL_DISPATCHES] if CONFIG_MAIN_INTELLIGENT_MANUAL_DISPATCHES in config else False,
863-
CONFIG_MAIN_INTELLIGENT_RATE_MODE: config[CONFIG_MAIN_INTELLIGENT_RATE_MODE] if CONFIG_MAIN_INTELLIGENT_RATE_MODE in config else CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_COMPLETED_DISPATCHES,
863+
CONFIG_MAIN_INTELLIGENT_RATE_MODE: config[CONFIG_MAIN_INTELLIGENT_RATE_MODE] if CONFIG_MAIN_INTELLIGENT_RATE_MODE in config else CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES,
864864
CONFIG_MAIN_AUTO_DISCOVER_COST_TRACKERS: config[CONFIG_MAIN_AUTO_DISCOVER_COST_TRACKERS] if CONFIG_MAIN_AUTO_DISCOVER_COST_TRACKERS in config else False,
865865
}
866866
),

custom_components/octopus_energy/const.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@
4545
CONFIG_MAIN_INTELLIGENT_MANUAL_DISPATCHES = "intelligent_manual_dispatches"
4646
CONFIG_MAIN_AUTO_DISCOVER_COST_TRACKERS = "auto_discover_cost_trackers"
4747
CONFIG_MAIN_INTELLIGENT_RATE_MODE = "intelligent_rate_mode"
48-
CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_COMPLETED_DISPATCHES = "pending_and_completed_dispatches"
49-
CONFIG_MAIN_INTELLIGENT_RATE_MODE_COMPLETED_DISPATCHES_ONLY = "completed_dispatches_only"
48+
CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES = "pending_and_started_dispatches"
49+
CONFIG_MAIN_INTELLIGENT_RATE_MODE_STARTED_DISPATCHES_ONLY = "started_dispatches_only"
5050

5151
CONFIG_DEFAULT_LIVE_ELECTRICITY_CONSUMPTION_REFRESH_IN_MINUTES = 1
5252
CONFIG_DEFAULT_LIVE_GAS_CONSUMPTION_REFRESH_IN_MINUTES = 2
@@ -202,8 +202,8 @@
202202
vol.Required(CONFIG_MAIN_INTELLIGENT_RATE_MODE): selector.SelectSelector(
203203
selector.SelectSelectorConfig(
204204
options=[
205-
selector.SelectOptionDict(value=CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_COMPLETED_DISPATCHES, label="Pending and completed dispatches will turn into off peak rates"),
206-
selector.SelectOptionDict(value=CONFIG_MAIN_INTELLIGENT_RATE_MODE_COMPLETED_DISPATCHES_ONLY, label="Only completed dispatches will turn into off peak rates"),
205+
selector.SelectOptionDict(value=CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES, label="Planned and started dispatches will turn into off peak rates"),
206+
selector.SelectOptionDict(value=CONFIG_MAIN_INTELLIGENT_RATE_MODE_STARTED_DISPATCHES_ONLY, label="Only started dispatches will turn into off peak rates"),
207207
],
208208
mode=selector.SelectSelectorMode.DROPDOWN,
209209
)

custom_components/octopus_energy/coordinators/electricity_rates.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from homeassistant.helpers import issue_registry as ir
1010

1111
from ..const import (
12-
CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_COMPLETED_DISPATCHES,
12+
CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES,
1313
COORDINATOR_REFRESH_IN_SECONDS,
1414
DATA_ACCOUNT_COORDINATOR,
1515
DATA_INTELLIGENT_DEVICE,
@@ -67,7 +67,7 @@ async def async_refresh_electricity_rates_data(
6767
unique_rates_changed: Callable[[Tariff, int], Awaitable[None]] = None,
6868
raise_no_active_rate: Callable[[], Awaitable[None]] = None,
6969
remove_no_active_rate: Callable[[], Awaitable[None]] = None,
70-
intelligent_rate_mode: str = CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_COMPLETED_DISPATCHES
70+
intelligent_rate_mode: str = CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES
7171
) -> ElectricityRatesCoordinatorResult:
7272
if (account_info is not None):
7373
period_from = as_utc((current - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0))
@@ -122,7 +122,7 @@ async def async_refresh_electricity_rates_data(
122122
if dispatches_result is not None and dispatches_result.dispatches is not None and is_export_meter == False:
123123
new_rates = adjust_intelligent_rates(new_rates,
124124
dispatches_result.dispatches.planned if planned_dispatches_supported else [],
125-
dispatches_result.dispatches.completed,
125+
dispatches_result.dispatches.started,
126126
intelligent_rate_mode)
127127

128128
_LOGGER.debug(f"Rates adjusted: {new_rates}; dispatches: {dispatches_result.dispatches}")
@@ -189,7 +189,7 @@ async def async_refresh_electricity_rates_data(
189189
dispatches_result.last_evaluated > existing_rates_result.rates_last_adjusted):
190190
new_rates = adjust_intelligent_rates(existing_rates_result.original_rates,
191191
dispatches_result.dispatches.planned,
192-
dispatches_result.dispatches.completed,
192+
dispatches_result.dispatches.started,
193193
intelligent_rate_mode)
194194

195195
_LOGGER.debug(f"Rates adjusted: {new_rates}; dispatches: {dispatches_result.dispatches}")

custom_components/octopus_energy/coordinators/previous_consumption_and_rates.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from homeassistant.helpers import storage
1212

1313
from ..const import (
14-
CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_COMPLETED_DISPATCHES,
14+
CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES,
1515
COORDINATOR_REFRESH_IN_SECONDS,
1616
DATA_ACCOUNT,
1717
DATA_INTELLIGENT_DEVICE,
@@ -243,7 +243,7 @@ async def async_fetch_consumption_and_rates(
243243
intelligent_device: IntelligentDevice | None = None,
244244
intelligent_dispatches: IntelligentDispatches | None = None,
245245
tariff_override: Tariff = None,
246-
intelligent_rate_mode: str = CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_COMPLETED_DISPATCHES
246+
intelligent_rate_mode: str = CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES
247247

248248
):
249249
"""Fetch the previous consumption and rates"""
@@ -293,7 +293,7 @@ async def async_fetch_consumption_and_rates(
293293
_LOGGER.debug(f"Adjusting rate data based on intelligent tariff; dispatches: {intelligent_dispatches}")
294294
rate_data = adjust_intelligent_rates(rate_data,
295295
intelligent_dispatches.planned,
296-
intelligent_dispatches.completed,
296+
intelligent_dispatches.started,
297297
intelligent_rate_mode)
298298
else:
299299
consumption_data = await client.async_get_gas_consumption(identifier, serial_number, page_size=52)

custom_components/octopus_energy/intelligent/__init__.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88

99
from ..utils import get_active_tariff
1010

11-
from ..const import CONFIG_MAIN_INTELLIGENT_RATE_MODE_COMPLETED_DISPATCHES_ONLY, CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_COMPLETED_DISPATCHES, INTELLIGENT_SOURCE_BUMP_CHARGE, INTELLIGENT_SOURCE_SMART_CHARGE, REFRESH_RATE_IN_MINUTES_INTELLIGENT
11+
from ..const import CONFIG_MAIN_INTELLIGENT_RATE_MODE_STARTED_DISPATCHES_ONLY, CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES, INTELLIGENT_SOURCE_BUMP_CHARGE, INTELLIGENT_SOURCE_SMART_CHARGE, REFRESH_RATE_IN_MINUTES_INTELLIGENT
1212

1313
from ..api_client.intelligent_settings import IntelligentSettings
14-
from ..api_client.intelligent_dispatches import IntelligentDispatchItem, IntelligentDispatches
14+
from ..api_client.intelligent_dispatches import IntelligentDispatchItem, IntelligentDispatches, SimpleIntelligentDispatchItem
1515
from ..api_client.intelligent_device import IntelligentDevice
1616

1717
mock_intelligent_data_key = "MOCK_INTELLIGENT_DATA"
@@ -89,6 +89,10 @@ def mock_intelligent_dispatches() -> IntelligentDispatches:
8989
current_state = "SMART_CONTROL_IN_PROGRESS"
9090
elif dispatch.source == INTELLIGENT_SOURCE_BUMP_CHARGE:
9191
current_state = "BOOSTING"
92+
else:
93+
# If there is one without a source, then don't push it to completed dispatch to simulate
94+
# a planned dispatch not turning into a completed dispatch
95+
continue
9296

9397
if (dispatch.end > utcnow()):
9498
planned.append(dispatch)
@@ -151,7 +155,7 @@ def __get_dispatch(rate, dispatches: list[IntelligentDispatchItem], expected_sou
151155

152156
def adjust_intelligent_rates(rates,
153157
planned_dispatches: list[IntelligentDispatchItem],
154-
completed_dispatches: list[IntelligentDispatchItem],
158+
started_dispatches: list[SimpleIntelligentDispatchItem],
155159
mode: str):
156160
off_peak_rate = min(rates, key = lambda x: x["value_inc_vat"])
157161
adjusted_rates = []
@@ -162,10 +166,10 @@ def adjust_intelligent_rates(rates,
162166
continue
163167

164168
is_planned_dispatch = __get_dispatch(rate, planned_dispatches, INTELLIGENT_SOURCE_SMART_CHARGE) is not None
165-
is_completed_dispatch = __get_dispatch(rate, completed_dispatches, None) is not None
169+
is_started_dispatch = __get_dispatch(rate, started_dispatches, None) is not None
166170

167-
if ((mode == CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_COMPLETED_DISPATCHES and (is_planned_dispatch or is_completed_dispatch)) or
168-
(mode == CONFIG_MAIN_INTELLIGENT_RATE_MODE_COMPLETED_DISPATCHES_ONLY and is_completed_dispatch)):
171+
if ((mode == CONFIG_MAIN_INTELLIGENT_RATE_MODE_PENDING_AND_STARTED_DISPATCHES and (is_planned_dispatch or is_started_dispatch)) or
172+
(mode == CONFIG_MAIN_INTELLIGENT_RATE_MODE_STARTED_DISPATCHES_ONLY and is_started_dispatch)):
169173
adjusted_rates.append({
170174
"start": rate["start"],
171175
"end": rate["end"],

0 commit comments

Comments
 (0)