e-data 2.0.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
edata/utils.py ADDED
@@ -0,0 +1,164 @@
1
+ """Utility functions for edata package."""
2
+
3
+ import contextlib
4
+ import json
5
+ import logging
6
+ import math
7
+ from copy import deepcopy
8
+ from datetime import date, datetime, timedelta
9
+ from json import JSONEncoder
10
+ from typing import Any, Dict, List, Optional
11
+
12
+ import holidays
13
+
14
+ _LOGGER = logging.getLogger(__name__)
15
+
16
+ # PVPC tariff constants
17
+ HOURS_P1 = [10, 11, 12, 13, 18, 19, 20, 21]
18
+ HOURS_P2 = [8, 9, 14, 15, 16, 17, 22, 23]
19
+ WEEKDAYS_P3 = [5, 6]
20
+
21
+
22
+ def get_pvpc_tariff(a_datetime: datetime) -> str:
23
+ """Evaluate the PVPC tariff for a given datetime.
24
+
25
+ Args:
26
+ a_datetime: The datetime to evaluate
27
+
28
+ Returns:
29
+ The tariff period: "p1", "p2", or "p3"
30
+ """
31
+ hdays = holidays.country_holidays("ES")
32
+ hour = a_datetime.hour
33
+ weekday = a_datetime.weekday()
34
+
35
+ if weekday in WEEKDAYS_P3 or a_datetime.date() in hdays:
36
+ return "p3"
37
+ elif hour in HOURS_P1:
38
+ return "p1"
39
+ elif hour in HOURS_P2:
40
+ return "p2"
41
+ else:
42
+ return "p3"
43
+
44
+
45
+ def extend_by_key(
46
+ old_lst: List[Dict[str, Any]], new_lst: List[Dict[str, Any]], key: str
47
+ ) -> List[Dict[str, Any]]:
48
+ """Extend a list of dicts by key."""
49
+ lst = deepcopy(old_lst)
50
+ temp_list = []
51
+ for new_element in new_lst:
52
+ for old_element in lst:
53
+ if new_element[key] == old_element[key]:
54
+ for i in old_element:
55
+ old_element[i] = new_element[i]
56
+ break
57
+ else:
58
+ temp_list.append(new_element)
59
+ lst.extend(temp_list)
60
+ return lst
61
+
62
+
63
+ def extract_dt_ranges(
64
+ lst: List[Dict[str, Any]],
65
+ dt_from: datetime,
66
+ dt_to: datetime,
67
+ gap_interval: timedelta = timedelta(hours=1),
68
+ ) -> tuple:
69
+ """Filter a list of dicts between two datetimes."""
70
+ new_lst = []
71
+ missing = []
72
+ oldest_dt = None
73
+ newest_dt = None
74
+ last_dt = None
75
+ if len(lst) > 0:
76
+ sorted_lst = sorted(lst, key=lambda i: i["datetime"])
77
+ last_dt = dt_from
78
+ for i in sorted_lst:
79
+ if dt_from <= i["datetime"] <= dt_to:
80
+ if (i["datetime"] - last_dt) > gap_interval:
81
+ missing.append({"from": last_dt, "to": i["datetime"]})
82
+ if i.get("value_kWh", 1) > 0:
83
+ if oldest_dt is None or i["datetime"] < oldest_dt:
84
+ oldest_dt = i["datetime"]
85
+ if newest_dt is None or i["datetime"] > newest_dt:
86
+ newest_dt = i["datetime"]
87
+ if i["datetime"] != last_dt: # remove duplicates
88
+ new_lst.append(i)
89
+ last_dt = i["datetime"]
90
+ if dt_to > last_dt:
91
+ missing.append({"from": last_dt, "to": dt_to})
92
+ _LOGGER.debug("found data from %s to %s", oldest_dt, newest_dt)
93
+ else:
94
+ missing.append({"from": dt_from, "to": dt_to})
95
+ return new_lst, missing
96
+
97
+
98
+ def get_by_key(
99
+ lst: List[Dict[str, Any]], key: str, value: Any
100
+ ) -> Optional[Dict[str, Any]]:
101
+ """Obtain an element of a list of dicts by key=value."""
102
+ for i in lst:
103
+ if i[key] == value:
104
+ return i
105
+ return None
106
+
107
+
108
+ def serialize_dict(data: dict) -> dict:
109
+ """Serialize dicts as json."""
110
+
111
+ class DateTimeEncoder(JSONEncoder):
112
+ """Replace datetime objects with ISO strings."""
113
+
114
+ def default(self, o):
115
+ if isinstance(o, (date, datetime)):
116
+ return o.isoformat()
117
+
118
+ return json.loads(json.dumps(data, cls=DateTimeEncoder))
119
+
120
+
121
+ def deserialize_dict(serialized_dict: dict) -> dict:
122
+ """Deserializes a json replacing ISOTIME strings into datetime."""
123
+
124
+ def datetime_parser(json_dict):
125
+ """Parse JSON while converting ISO strings into datetime objects."""
126
+ for key, value in json_dict.items():
127
+ if "date" in key:
128
+ with contextlib.suppress(Exception):
129
+ json_dict[key] = datetime.fromisoformat(value)
130
+ return json_dict
131
+
132
+ return json.loads(json.dumps(serialized_dict), object_hook=datetime_parser)
133
+
134
+
135
+ def percentile(N: List, percent: float, key=lambda x: x):
136
+ """Find the percentile of a list of values."""
137
+ if not N:
138
+ return None
139
+ k = (len(N) - 1) * percent
140
+ f = math.floor(k)
141
+ c = math.ceil(k)
142
+ if f == c:
143
+ return key(N[int(k)])
144
+ d0 = key(N[int(f)]) * (c - k)
145
+ d1 = key(N[int(c)]) * (k - f)
146
+ return d0 + d1
147
+
148
+
149
+ def extend_and_filter(
150
+ old_lst: List[Dict[str, Any]],
151
+ new_lst: List[Dict[str, Any]],
152
+ key: str,
153
+ dt_from: datetime,
154
+ dt_to: datetime,
155
+ ) -> List[Dict[str, Any]]:
156
+ """Extend and filter data by datetime range."""
157
+ data = extend_by_key(old_lst, new_lst, key)
158
+ data, _ = extract_dt_ranges(
159
+ data,
160
+ dt_from,
161
+ dt_to,
162
+ gap_interval=timedelta(days=365), # trick
163
+ )
164
+ return data