Skip to content

Commit b65ca55

Browse files
committed
fix: Fixed cost trackers handling low powered devices (1 hour dev time)
1 parent f219012 commit b65ca55

File tree

4 files changed

+423
-6
lines changed

4 files changed

+423
-6
lines changed

_docs/setup/cost_tracker.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ Each item within the `tracked_changes` and `untracked_changes` have the followin
8888
| `rate` | `float` | The rate the consumption is charged at. This is in pounds and pence (e.g. 1.01 = £1.01) |
8989
| `consumption` | `float` | The consumption value of the specified period. This will be in `kwh`. |
9090
| `cost` | `float` | The cost of the consumption at the specified rate. This is in pounds and pence (e.g. 1.01 = £1.01) |
91+
| `cost_raw` | `float` | The raw cost of the consumption at the specified rate. This is in pounds and pence, but not rounded. This is to account for low cost devices |
9192

9293
#### Variants
9394

custom_components/octopus_energy/cost_tracker/__init__.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
from datetime import datetime, timedelta
2+
import logging
23

34
from homeassistant.components.sensor import (
45
SensorStateClass,
56
)
67

78
from homeassistant.helpers.entity import DeviceInfo
89

10+
from ..utils.conversions import pence_to_pounds_pence, round_pounds, value_inc_vat_to_pounds
11+
from ..utils.cost import consumption_cost_in_pence
12+
13+
_LOGGER = logging.getLogger(__name__)
14+
915
def get_device_info_from_device_entry(device_entry):
1016
if device_entry is None:
1117
return None
@@ -152,4 +158,82 @@ def accumulate_cost(current: datetime, accumulative_data: list, new_cost: float,
152158

153159
return AccumulativeCostTrackerResult(new_accumulative_data, total_consumption, total_cost)
154160

155-
161+
def __get_to(item):
162+
return (item["end"].timestamp(), item["end"].fold)
163+
164+
def __sort_consumption(consumption_data):
165+
sorted = consumption_data.copy()
166+
sorted.sort(key=__get_to)
167+
return sorted
168+
169+
def calculate_consumption_and_cost(
170+
consumption_data,
171+
rate_data,
172+
standing_charge,
173+
last_reset,
174+
minimum_consumption_records = 0,
175+
target_rate = None
176+
):
177+
if (consumption_data is not None and len(consumption_data) >= minimum_consumption_records and rate_data is not None and len(rate_data) > 0 and standing_charge is not None):
178+
179+
sorted_consumption_data = __sort_consumption(consumption_data)
180+
181+
# Only calculate our consumption if our data has changed
182+
if (last_reset is None or last_reset < sorted_consumption_data[0]["start"]):
183+
184+
charges = []
185+
total_cost = 0
186+
total_consumption = 0
187+
188+
for consumption in sorted_consumption_data:
189+
consumption_value = consumption["consumption"]
190+
consumption_from = consumption["start"]
191+
consumption_to = consumption["end"]
192+
193+
try:
194+
rate = next(r for r in rate_data if r["start"] == consumption_from and r["end"] == consumption_to)
195+
except StopIteration:
196+
raise Exception(f"Failed to find rate for consumption between {consumption_from} and {consumption_to}")
197+
198+
value = rate["value_inc_vat"]
199+
200+
if target_rate is not None and value != target_rate:
201+
continue
202+
203+
total_consumption = total_consumption + consumption_value
204+
cost = pence_to_pounds_pence(consumption_cost_in_pence(consumption_value, value))
205+
cost_raw = (consumption_value * value) / 100
206+
total_cost = total_cost + cost_raw
207+
208+
current_charge = {
209+
"start": rate["start"],
210+
"end": rate["end"],
211+
"rate": value_inc_vat_to_pounds(value),
212+
"consumption": consumption_value,
213+
"cost": cost,
214+
"cost_raw": cost_raw,
215+
}
216+
217+
charges.append(current_charge)
218+
219+
total_cost = round_pounds(total_cost)
220+
total_cost_plus_standing_charge = total_cost + pence_to_pounds_pence(standing_charge)
221+
222+
last_reset = sorted_consumption_data[0]["start"] if len(sorted_consumption_data) > 0 else None
223+
last_calculated_timestamp = sorted_consumption_data[-1]["end"] if len(sorted_consumption_data) > 0 else None
224+
225+
result = {
226+
"standing_charge": pence_to_pounds_pence(standing_charge),
227+
"total_cost_without_standing_charge": total_cost,
228+
"total_cost": total_cost_plus_standing_charge,
229+
"total_consumption": total_consumption,
230+
"last_reset": last_reset,
231+
"last_evaluated": last_calculated_timestamp,
232+
"charges": charges,
233+
}
234+
235+
return result
236+
else:
237+
_LOGGER.debug(f'Skipping consumption and cost calculation as last reset has not changed - last_reset: {last_reset}; consumption start: {sorted_consumption_data[0]["start"]}')
238+
else:
239+
_LOGGER.debug(f'Skipping consumption and cost calculation due to lack of data; consumption: {len(consumption_data) if consumption_data is not None else 0}; rates: {len(rate_data) if rate_data is not None else 0}; standing_charge: {standing_charge}')

custom_components/octopus_energy/cost_tracker/cost_tracker.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141

4242
from ..coordinators.electricity_rates import ElectricityRatesCoordinatorResult
4343
from . import add_consumption, get_device_info_from_device_entry
44-
from ..electricity import calculate_electricity_consumption_and_cost
44+
from ..cost_tracker import calculate_consumption_and_cost
4545
from ..utils.rate_information import get_rate_index, get_unique_rates
4646
from ..utils.attributes import dict_to_typed_dict
4747

@@ -283,7 +283,7 @@ def _recalculate_cost(self, current: datetime, tracked_consumption_data: list, u
283283
unique_rate_index = get_rate_index(len(unique_rates), self._peak_type)
284284
target_rate = unique_rates[unique_rate_index] if unique_rate_index is not None else None
285285

286-
tracked_result = calculate_electricity_consumption_and_cost(
286+
tracked_result = calculate_consumption_and_cost(
287287
tracked_consumption_data,
288288
rates,
289289
0,
@@ -292,7 +292,7 @@ def _recalculate_cost(self, current: datetime, tracked_consumption_data: list, u
292292
target_rate=target_rate
293293
)
294294

295-
untracked_result = calculate_electricity_consumption_and_cost(
295+
untracked_result = calculate_consumption_and_cost(
296296
untracked_consumption_data,
297297
rates,
298298
0,
@@ -309,15 +309,17 @@ def _recalculate_cost(self, current: datetime, tracked_consumption_data: list, u
309309
"end": charge["end"],
310310
"rate": charge["rate"],
311311
"consumption": charge["consumption"],
312-
"cost": charge["cost"]
312+
"cost": charge["cost"],
313+
"cost_raw": charge["cost_raw"]
313314
}, tracked_result["charges"]))
314315

315316
self._attributes["untracked_charges"] = list(map(lambda charge: {
316317
"start": charge["start"],
317318
"end": charge["end"],
318319
"rate": charge["rate"],
319320
"consumption": charge["consumption"],
320-
"cost": charge["cost"]
321+
"cost": charge["cost"],
322+
"cost_raw": charge["cost_raw"]
321323
}, untracked_result["charges"]))
322324

323325
self._attributes["total_consumption"] = tracked_result["total_consumption"] + untracked_result["total_consumption"]

0 commit comments

Comments
 (0)