e-data 1.2.17__tar.gz → 1.2.19__tar.gz
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.
- {e-data-1.2.17/e_data.egg-info → e_data-1.2.19}/PKG-INFO +9 -1
- {e-data-1.2.17 → e_data-1.2.19/e_data.egg-info}/PKG-INFO +9 -1
- {e-data-1.2.17 → e_data-1.2.19}/edata/connectors/datadis.py +83 -61
- {e-data-1.2.17 → e_data-1.2.19}/edata/definitions.py +1 -3
- {e-data-1.2.17 → e_data-1.2.19}/edata/helpers.py +73 -75
- {e-data-1.2.17 → e_data-1.2.19}/setup.py +1 -1
- {e-data-1.2.17 → e_data-1.2.19}/LICENSE +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/MANIFEST.in +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/README.md +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/e_data.egg-info/SOURCES.txt +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/e_data.egg-info/dependency_links.txt +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/e_data.egg-info/requires.txt +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/e_data.egg-info/top_level.txt +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/edata/__init__.py +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/edata/connectors/__init__.py +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/edata/connectors/redata.py +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/edata/const.py +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/edata/processors/__init__.py +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/edata/processors/base.py +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/edata/processors/billing.py +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/edata/processors/consumption.py +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/edata/processors/maximeter.py +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/edata/processors/utils.py +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/edata/storage.py +0 -0
- {e-data-1.2.17 → e_data-1.2.19}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: e-data
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.19
|
|
4
4
|
Summary: Python library for managing spanish energy data from various web providers
|
|
5
5
|
Home-page: https://github.com/uvejota/python-edata
|
|
6
6
|
Author: VMG
|
|
@@ -18,6 +18,14 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
|
18
18
|
Requires-Python: >=3.6.0
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
+
Requires-Dist: dateparser>=1.1.2
|
|
22
|
+
Requires-Dist: freezegun>=1.2.1
|
|
23
|
+
Requires-Dist: holidays>=0.14.2
|
|
24
|
+
Requires-Dist: pytest>=7.1.2
|
|
25
|
+
Requires-Dist: python_dateutil>=2.8.2
|
|
26
|
+
Requires-Dist: requests>=2.28.1
|
|
27
|
+
Requires-Dist: voluptuous>=0.13.1
|
|
28
|
+
Requires-Dist: Jinja2>=3.1.2
|
|
21
29
|
|
|
22
30
|
|
|
23
31
|
[](https://pepy.tech/project/e-data)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: e-data
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.19
|
|
4
4
|
Summary: Python library for managing spanish energy data from various web providers
|
|
5
5
|
Home-page: https://github.com/uvejota/python-edata
|
|
6
6
|
Author: VMG
|
|
@@ -18,6 +18,14 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
|
18
18
|
Requires-Python: >=3.6.0
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
+
Requires-Dist: dateparser>=1.1.2
|
|
22
|
+
Requires-Dist: freezegun>=1.2.1
|
|
23
|
+
Requires-Dist: holidays>=0.14.2
|
|
24
|
+
Requires-Dist: pytest>=7.1.2
|
|
25
|
+
Requires-Dist: python_dateutil>=2.8.2
|
|
26
|
+
Requires-Dist: requests>=2.28.1
|
|
27
|
+
Requires-Dist: voluptuous>=0.13.1
|
|
28
|
+
Requires-Dist: Jinja2>=3.1.2
|
|
21
29
|
|
|
22
30
|
|
|
23
31
|
[](https://pepy.tech/project/e-data)
|
|
@@ -4,17 +4,17 @@ To fetch data from datadis.es private API.
|
|
|
4
4
|
There a few issues that are workarounded:
|
|
5
5
|
- You have to wait 24h between two identical requests.
|
|
6
6
|
- Datadis server does not like ranges greater than 1 month.
|
|
7
|
-
- TODO: gzip header checksum errors under weird circumstances
|
|
8
7
|
"""
|
|
9
8
|
|
|
10
9
|
import contextlib
|
|
10
|
+
from datetime import datetime, timedelta
|
|
11
11
|
import hashlib
|
|
12
12
|
import json
|
|
13
13
|
import logging
|
|
14
|
-
from datetime import datetime, timedelta
|
|
15
14
|
import os
|
|
16
|
-
from dateutil.relativedelta import relativedelta
|
|
17
15
|
|
|
16
|
+
from dateutil.relativedelta import relativedelta
|
|
17
|
+
from filelock import FileLock
|
|
18
18
|
import requests
|
|
19
19
|
|
|
20
20
|
from ..definitions import ConsumptionData, ContractData, MaxPowerData, SupplyData
|
|
@@ -104,50 +104,60 @@ class DatadisConnector:
|
|
|
104
104
|
self._recent_queries_file = DEFAULT_RECENT_QUERIES_FILE
|
|
105
105
|
self._recent_queries_cache_file = DEFAULT_RECENT_QUERIES_CACHE
|
|
106
106
|
self._warned_queries = []
|
|
107
|
+
self._load_recent_cache()
|
|
108
|
+
|
|
109
|
+
def _load_recent_cache(self) -> None:
|
|
110
|
+
"""Load caches."""
|
|
107
111
|
|
|
108
|
-
# load caches (to avoid query spam if the program restarts)
|
|
109
112
|
with contextlib.suppress(FileNotFoundError):
|
|
110
113
|
with open(self._recent_queries_file, encoding="utf8") as dst_file:
|
|
111
|
-
|
|
112
|
-
for query in
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
recent_queries = json.load(dst_file)
|
|
115
|
+
for query in recent_queries:
|
|
116
|
+
recent_queries[query] = datetime.fromisoformat(
|
|
117
|
+
recent_queries[query]
|
|
115
118
|
)
|
|
119
|
+
self._recent_queries.update(recent_queries)
|
|
120
|
+
|
|
116
121
|
with open(self._recent_queries_cache_file, encoding="utf8") as dst_file:
|
|
117
|
-
self._recent_cache
|
|
122
|
+
self._recent_cache.update(json.load(dst_file))
|
|
118
123
|
|
|
119
124
|
def _update_recent_queries(self, query: str, data: dict | None = None) -> None:
|
|
120
125
|
"""Cache a successful query to avoid exceeding query limits."""
|
|
121
126
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
self.
|
|
128
|
-
|
|
129
|
-
# purge old cache
|
|
130
|
-
to_delete = []
|
|
131
|
-
for _query in self._recent_queries:
|
|
132
|
-
if (datetime.now() - self._recent_queries[_query]) > QUERY_LIMIT:
|
|
133
|
-
to_delete.append(_query)
|
|
134
|
-
|
|
135
|
-
for key in to_delete:
|
|
136
|
-
with contextlib.suppress(KeyError):
|
|
137
|
-
self._recent_queries.pop(key, None)
|
|
138
|
-
self._recent_cache.pop(key, None)
|
|
139
|
-
|
|
140
|
-
# dump current cache to disk
|
|
141
|
-
try:
|
|
142
|
-
with open(self._recent_queries_file, "w", encoding="utf8") as dst_file:
|
|
143
|
-
json.dump(utils.serialize_dict(self._recent_queries), dst_file)
|
|
127
|
+
with FileLock(self._recent_queries_file + ".lock"):
|
|
128
|
+
self._load_recent_cache()
|
|
129
|
+
# identify the query by a md5 hash
|
|
130
|
+
hash_query = hashlib.md5(query.encode()).hexdigest()
|
|
131
|
+
# prepare key (hash) and values (timestamp and response)
|
|
132
|
+
self._recent_queries[hash_query] = datetime.now()
|
|
144
133
|
if data is not None:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
134
|
+
self._recent_cache[hash_query] = data
|
|
135
|
+
|
|
136
|
+
# purge old cache
|
|
137
|
+
to_delete = [
|
|
138
|
+
x
|
|
139
|
+
for x in self._recent_queries
|
|
140
|
+
if (datetime.now() - self._recent_queries[x]) > QUERY_LIMIT
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
for key in to_delete:
|
|
144
|
+
with contextlib.suppress(KeyError):
|
|
145
|
+
self._recent_queries.pop(key, None)
|
|
146
|
+
self._recent_cache.pop(key, None)
|
|
147
|
+
|
|
148
|
+
# dump current cache to disk
|
|
149
|
+
try:
|
|
150
|
+
with open(self._recent_queries_file, "w", encoding="utf8") as dst_file:
|
|
151
|
+
json.dump(utils.serialize_dict(self._recent_queries), dst_file)
|
|
152
|
+
if data is not None:
|
|
153
|
+
with open(
|
|
154
|
+
self._recent_queries_cache_file, "w", encoding="utf8"
|
|
155
|
+
) as dst_file:
|
|
156
|
+
json.dump(self._recent_cache, dst_file)
|
|
157
|
+
_LOGGER.debug("Local cache updated")
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
_LOGGER.warning("Unknown error while updating cache: %s", e)
|
|
151
161
|
|
|
152
162
|
def _is_recent_query(self, query: str) -> bool:
|
|
153
163
|
"""Check if a query has been done recently to avoid exceeding query limits."""
|
|
@@ -160,7 +170,7 @@ class DatadisConnector:
|
|
|
160
170
|
def _get_cache_for_query(self, query: str) -> dict:
|
|
161
171
|
"""Return cached response for a query."""
|
|
162
172
|
hash_query = hashlib.md5(query.encode()).hexdigest()
|
|
163
|
-
|
|
173
|
+
_LOGGER.info("Serving '%s' from cache", query)
|
|
164
174
|
return self._recent_cache.get(hash_query, None)
|
|
165
175
|
|
|
166
176
|
def _get_token(self):
|
|
@@ -243,13 +253,15 @@ class DatadisConnector:
|
|
|
243
253
|
_LOGGER.info("Got 200 OK at %s", url + params)
|
|
244
254
|
if reply.json():
|
|
245
255
|
response = reply.json()
|
|
246
|
-
|
|
256
|
+
if not ignore_recent_queries:
|
|
257
|
+
self._update_recent_queries(url + params, response)
|
|
247
258
|
else:
|
|
248
259
|
# this mostly happens when datadis provides an empty response
|
|
249
260
|
_LOGGER.info(
|
|
250
261
|
"Datadis returned an empty response at %s", url + params
|
|
251
262
|
)
|
|
252
|
-
|
|
263
|
+
if not ignore_recent_queries:
|
|
264
|
+
self._update_recent_queries(url + params)
|
|
253
265
|
elif reply.status_code == 401 and not refresh_token:
|
|
254
266
|
# we're here if we were unauthorized so we will refresh the token
|
|
255
267
|
response = self._get(
|
|
@@ -266,7 +278,8 @@ class DatadisConnector:
|
|
|
266
278
|
reply.text,
|
|
267
279
|
url + params,
|
|
268
280
|
)
|
|
269
|
-
|
|
281
|
+
if not ignore_recent_queries:
|
|
282
|
+
self._update_recent_queries(url + params)
|
|
270
283
|
elif is_retry:
|
|
271
284
|
# otherwise, if this was a retried request... warn the user
|
|
272
285
|
if (url + params) not in self._warned_queries:
|
|
@@ -278,7 +291,8 @@ class DatadisConnector:
|
|
|
278
291
|
"Query temporary disabled",
|
|
279
292
|
"Future 500 code errors for this query will be silenced until restart",
|
|
280
293
|
)
|
|
281
|
-
|
|
294
|
+
if not ignore_recent_queries:
|
|
295
|
+
self._update_recent_queries(url + params)
|
|
282
296
|
self._warned_queries.append(url + params)
|
|
283
297
|
else:
|
|
284
298
|
# finally, retry since an unexpected error took place (mostly 500 errors - server fault)
|
|
@@ -302,7 +316,7 @@ class DatadisConnector:
|
|
|
302
316
|
|
|
303
317
|
# Request the resource
|
|
304
318
|
response = self._get(
|
|
305
|
-
URL_GET_SUPPLIES, request_data=data, ignore_recent_queries=
|
|
319
|
+
URL_GET_SUPPLIES, request_data=data, ignore_recent_queries=True
|
|
306
320
|
)
|
|
307
321
|
|
|
308
322
|
# Response is a list of serialized supplies.
|
|
@@ -318,23 +332,27 @@ class DatadisConnector:
|
|
|
318
332
|
SupplyData(
|
|
319
333
|
cups=i["cups"], # the supply identifier
|
|
320
334
|
date_start=datetime.strptime(
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
335
|
+
(
|
|
336
|
+
i["validDateFrom"]
|
|
337
|
+
if i["validDateFrom"] != ""
|
|
338
|
+
else "1970/01/01"
|
|
339
|
+
),
|
|
324
340
|
"%Y/%m/%d",
|
|
325
341
|
), # start date of the supply. 1970/01/01 if unset.
|
|
326
342
|
date_end=datetime.strptime(
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
343
|
+
(
|
|
344
|
+
i["validDateTo"]
|
|
345
|
+
if i["validDateTo"] != ""
|
|
346
|
+
else tomorrow_str
|
|
347
|
+
),
|
|
330
348
|
"%Y/%m/%d",
|
|
331
349
|
), # end date of the supply, tomorrow if unset
|
|
332
350
|
# the following parameters are not crucial, so they can be none
|
|
333
|
-
address=i
|
|
334
|
-
postal_code=i
|
|
335
|
-
province=i
|
|
336
|
-
municipality=i
|
|
337
|
-
distributor=i
|
|
351
|
+
address=i.get("address", None),
|
|
352
|
+
postal_code=i.get("postalCode", None),
|
|
353
|
+
province=i.get("province", None),
|
|
354
|
+
municipality=i.get("municipality", None),
|
|
355
|
+
distributor=i.get("distributor", None),
|
|
338
356
|
# these two are mandatory, we will use them to fetch contracts data
|
|
339
357
|
pointType=i["pointType"],
|
|
340
358
|
distributorCode=i["distributorCode"],
|
|
@@ -355,7 +373,7 @@ class DatadisConnector:
|
|
|
355
373
|
if authorized_nif is not None:
|
|
356
374
|
data["authorizedNif"] = authorized_nif
|
|
357
375
|
response = self._get(
|
|
358
|
-
URL_GET_CONTRACT_DETAIL, request_data=data, ignore_recent_queries=
|
|
376
|
+
URL_GET_CONTRACT_DETAIL, request_data=data, ignore_recent_queries=True
|
|
359
377
|
)
|
|
360
378
|
contracts = []
|
|
361
379
|
tomorrow_str = (datetime.today() + timedelta(days=1)).strftime("%Y/%m/%d")
|
|
@@ -373,12 +391,16 @@ class DatadisConnector:
|
|
|
373
391
|
),
|
|
374
392
|
marketer=i["marketer"],
|
|
375
393
|
distributorCode=distributor_code,
|
|
376
|
-
power_p1=
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
394
|
+
power_p1=(
|
|
395
|
+
i["contractedPowerkW"][0]
|
|
396
|
+
if isinstance(i["contractedPowerkW"], list)
|
|
397
|
+
else None
|
|
398
|
+
),
|
|
399
|
+
power_p2=(
|
|
400
|
+
i["contractedPowerkW"][1]
|
|
401
|
+
if (len(i["contractedPowerkW"]) > 1)
|
|
402
|
+
else None
|
|
403
|
+
),
|
|
382
404
|
)
|
|
383
405
|
)
|
|
384
406
|
else:
|
|
@@ -71,9 +71,7 @@ DEFAULT_BILLING_SURPLUS_FORMULA = (
|
|
|
71
71
|
|
|
72
72
|
# Sum energy and power terms, and substract surplus until 0.
|
|
73
73
|
# An alternative would be "[(energy_term + power_term - surplus_term), 0]|max + others_term"
|
|
74
|
-
DEFAULT_BILLING_MAIN_FORMULA =
|
|
75
|
-
"[(energy_term - surplus_term), 0]|max + power_term + others_term"
|
|
76
|
-
)
|
|
74
|
+
DEFAULT_BILLING_MAIN_FORMULA = "energy_term + power_term + others_term"
|
|
77
75
|
|
|
78
76
|
|
|
79
77
|
class SupplyData(TypedDict):
|
|
@@ -22,6 +22,12 @@ from .storage import check_storage_integrity, dump_storage, load_storage
|
|
|
22
22
|
_LOGGER = logging.getLogger(__name__)
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
def acups(cups):
|
|
26
|
+
"""Print an abbreviated and anonymized CUPS."""
|
|
27
|
+
|
|
28
|
+
return cups[-5:]
|
|
29
|
+
|
|
30
|
+
|
|
25
31
|
class EdataHelper:
|
|
26
32
|
"""Main EdataHelper class."""
|
|
27
33
|
|
|
@@ -53,7 +59,7 @@ class EdataHelper:
|
|
|
53
59
|
self.attributes = {}
|
|
54
60
|
self._storage_dir = storage_dir_path
|
|
55
61
|
self._cups = cups
|
|
56
|
-
self._scups = cups
|
|
62
|
+
self._scups = acups(cups)
|
|
57
63
|
self._authorized_nif = datadis_authorized_nif
|
|
58
64
|
self.last_update = {x: datetime(1970, 1, 1) for x in self.data}
|
|
59
65
|
self._date_from = datetime(1970, 1, 1)
|
|
@@ -74,9 +80,11 @@ class EdataHelper:
|
|
|
74
80
|
self.datadis_api = DatadisConnector(
|
|
75
81
|
datadis_username,
|
|
76
82
|
datadis_password,
|
|
77
|
-
storage_path=
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
storage_path=(
|
|
84
|
+
os.path.join(storage_dir_path, const.PROG_NAME)
|
|
85
|
+
if storage_dir_path is not None
|
|
86
|
+
else None
|
|
87
|
+
),
|
|
80
88
|
)
|
|
81
89
|
self.redata_api = REDataConnector()
|
|
82
90
|
|
|
@@ -111,7 +119,7 @@ class EdataHelper:
|
|
|
111
119
|
date_to: datetime = datetime.today(),
|
|
112
120
|
incremental_update: bool = True,
|
|
113
121
|
):
|
|
114
|
-
"""
|
|
122
|
+
"""Update synchronously."""
|
|
115
123
|
|
|
116
124
|
_LOGGER.info(
|
|
117
125
|
"%s: update triggered",
|
|
@@ -136,9 +144,9 @@ class EdataHelper:
|
|
|
136
144
|
dump_storage(self._cups, self.data, self._storage_dir)
|
|
137
145
|
|
|
138
146
|
def update_supplies(self):
|
|
139
|
-
"""
|
|
147
|
+
"""Update supplies."""
|
|
140
148
|
|
|
141
|
-
_LOGGER.
|
|
149
|
+
_LOGGER.debug("%s: supplies update triggered", self._scups)
|
|
142
150
|
if datetime.today().date() != self.last_update["supplies"].date():
|
|
143
151
|
# if supplies haven't been updated today
|
|
144
152
|
supplies = self.datadis_api.get_supplies(
|
|
@@ -153,9 +161,9 @@ class EdataHelper:
|
|
|
153
161
|
_LOGGER.info("%s: supplies are already updated (skipping)", self._scups)
|
|
154
162
|
|
|
155
163
|
def update_contracts(self, cups: str, distributor_code: str):
|
|
156
|
-
"""
|
|
164
|
+
"""Update contracts."""
|
|
157
165
|
|
|
158
|
-
_LOGGER.
|
|
166
|
+
_LOGGER.debug("%s: contracts update triggered", self._scups)
|
|
159
167
|
if datetime.today().date() != self.last_update["contracts"].date():
|
|
160
168
|
# if contracts haven't been updated today
|
|
161
169
|
contracts = self.datadis_api.get_contract_detail(
|
|
@@ -180,9 +188,9 @@ class EdataHelper:
|
|
|
180
188
|
measurement_type: str,
|
|
181
189
|
point_type: int,
|
|
182
190
|
):
|
|
183
|
-
"""
|
|
191
|
+
"""Update consumptions."""
|
|
184
192
|
|
|
185
|
-
_LOGGER.
|
|
193
|
+
_LOGGER.debug("%s: consumptions update triggered", self._scups)
|
|
186
194
|
if (datetime.now() - self.last_update["consumptions"]) > self.UPDATE_INTERVAL:
|
|
187
195
|
consumptions = self.datadis_api.get_consumption_data(
|
|
188
196
|
cups,
|
|
@@ -194,22 +202,23 @@ class EdataHelper:
|
|
|
194
202
|
authorized_nif=self._authorized_nif,
|
|
195
203
|
)
|
|
196
204
|
if len(consumptions) > 0:
|
|
205
|
+
_LOGGER.info(
|
|
206
|
+
"%s: got consumptions from %s to %s",
|
|
207
|
+
self._scups,
|
|
208
|
+
consumptions[0]["datetime"].isoformat(),
|
|
209
|
+
consumptions[-1]["datetime"].isoformat(),
|
|
210
|
+
)
|
|
197
211
|
self.data["consumptions"] = utils.extend_by_key(
|
|
198
212
|
self.data["consumptions"], consumptions, "datetime"
|
|
199
213
|
)
|
|
200
214
|
self.last_update["consumptions"] = datetime.now()
|
|
201
|
-
_LOGGER.info(
|
|
202
|
-
"%s: consumptions update succeeded (%s new records)",
|
|
203
|
-
self._scups,
|
|
204
|
-
len(consumptions),
|
|
205
|
-
)
|
|
206
215
|
else:
|
|
207
216
|
_LOGGER.info("%s: consumptions are already updated (skipping)", self._scups)
|
|
208
217
|
|
|
209
218
|
def update_maximeter(self, cups, distributor_code, start_date, end_date):
|
|
210
|
-
"""
|
|
219
|
+
"""Update maximeter."""
|
|
211
220
|
|
|
212
|
-
_LOGGER.
|
|
221
|
+
_LOGGER.debug("%s: maximeter update triggered", self._scups)
|
|
213
222
|
if (datetime.now() - self.last_update["maximeter"]) > self.UPDATE_INTERVAL:
|
|
214
223
|
maximeter = self.datadis_api.get_max_power(
|
|
215
224
|
cups,
|
|
@@ -219,15 +228,14 @@ class EdataHelper:
|
|
|
219
228
|
authorized_nif=self._authorized_nif,
|
|
220
229
|
)
|
|
221
230
|
if len(maximeter) > 0:
|
|
231
|
+
_LOGGER.info(
|
|
232
|
+
"%s: maximeter update succeeded",
|
|
233
|
+
self._scups,
|
|
234
|
+
)
|
|
222
235
|
self.data["maximeter"] = utils.extend_by_key(
|
|
223
236
|
self.data["maximeter"], maximeter, "datetime"
|
|
224
237
|
)
|
|
225
238
|
self.last_update["maximeter"] = datetime.now()
|
|
226
|
-
_LOGGER.info(
|
|
227
|
-
"%s: maximeter update succeeded (%s new records)",
|
|
228
|
-
self._scups,
|
|
229
|
-
len(maximeter),
|
|
230
|
-
)
|
|
231
239
|
else:
|
|
232
240
|
_LOGGER.info("%s: maximeter is already updated (skipping)", self._scups)
|
|
233
241
|
|
|
@@ -237,7 +245,7 @@ class EdataHelper:
|
|
|
237
245
|
date_from: datetime = datetime(1970, 1, 1),
|
|
238
246
|
date_to: datetime = datetime.today(),
|
|
239
247
|
):
|
|
240
|
-
"""
|
|
248
|
+
"""Update all data from Datadis."""
|
|
241
249
|
|
|
242
250
|
_LOGGER.info(
|
|
243
251
|
"%s: datadis update triggered (from %s to %s)",
|
|
@@ -262,15 +270,22 @@ class EdataHelper:
|
|
|
262
270
|
if supply is None:
|
|
263
271
|
# return if specified cups seems not valid
|
|
264
272
|
_LOGGER.error(
|
|
265
|
-
"%s: CUPS not found
|
|
273
|
+
"%s: CUPS not found. Got: %s",
|
|
274
|
+
self._scups,
|
|
275
|
+
[acups(x["cups"]) for x in self.data["supplies"]],
|
|
266
276
|
)
|
|
267
277
|
return False
|
|
278
|
+
_LOGGER.info("%s: CUPS found in account", self._scups)
|
|
268
279
|
|
|
269
280
|
# get some supply-related data
|
|
270
281
|
supply_date_start = supply["date_start"]
|
|
271
282
|
distributor_code = supply["distributorCode"]
|
|
272
283
|
point_type = supply["pointType"]
|
|
273
284
|
|
|
285
|
+
_LOGGER.info(
|
|
286
|
+
"%s: CUPS start date is %s", self._scups, supply_date_start.isoformat()
|
|
287
|
+
)
|
|
288
|
+
|
|
274
289
|
# update contracts to get valid periods
|
|
275
290
|
self.update_contracts(cups, distributor_code)
|
|
276
291
|
if len(self.data["contracts"]) == 0:
|
|
@@ -280,7 +295,7 @@ class EdataHelper:
|
|
|
280
295
|
)
|
|
281
296
|
return False
|
|
282
297
|
|
|
283
|
-
# filter consumptions and maximeter, and
|
|
298
|
+
# filter consumptions and maximeter, and log gaps
|
|
284
299
|
def sort_and_filter(dt_from, dt_to):
|
|
285
300
|
self.data["consumptions"], miss_cons = utils.extract_dt_ranges(
|
|
286
301
|
self.data["consumptions"],
|
|
@@ -316,64 +331,47 @@ class EdataHelper:
|
|
|
316
331
|
),
|
|
317
332
|
)
|
|
318
333
|
|
|
319
|
-
|
|
320
|
-
for
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
# update consumptions
|
|
326
|
-
for gap in [
|
|
327
|
-
x
|
|
328
|
-
for x in miss_cons
|
|
329
|
-
if not (
|
|
330
|
-
x["to"] < contract["date_start"] or x["from"] > contract["date_end"]
|
|
331
|
-
)
|
|
332
|
-
]:
|
|
334
|
+
# update consumptions
|
|
335
|
+
for gap in miss_cons:
|
|
336
|
+
if not (
|
|
337
|
+
gap["to"] < supply["date_start"] or gap["from"] > supply["date_end"]
|
|
338
|
+
):
|
|
333
339
|
# fetch consumptions for each consumptions gap in valid periods
|
|
340
|
+
start = max([gap["from"] + timedelta(hours=1), supply["date_start"]])
|
|
341
|
+
end = min([gap["to"], supply["date_end"]])
|
|
342
|
+
_LOGGER.info(
|
|
343
|
+
"%s: request consumptions from %s to %s",
|
|
344
|
+
self._scups,
|
|
345
|
+
start.isoformat(),
|
|
346
|
+
end.isoformat(),
|
|
347
|
+
)
|
|
334
348
|
self.update_consumptions(
|
|
335
349
|
cups,
|
|
336
350
|
distributor_code,
|
|
337
|
-
|
|
338
|
-
|
|
351
|
+
start,
|
|
352
|
+
end,
|
|
339
353
|
"0",
|
|
340
354
|
point_type,
|
|
341
355
|
)
|
|
342
356
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
for x in miss_maxim
|
|
347
|
-
if not (
|
|
348
|
-
date_to < contract["date_start"] or date_from > contract["date_end"]
|
|
349
|
-
)
|
|
350
|
-
]:
|
|
357
|
+
# update maximeter
|
|
358
|
+
for gap in miss_maxim:
|
|
359
|
+
if not (date_to < supply["date_start"] or date_from > supply["date_end"]):
|
|
351
360
|
# fetch maximeter for each maximeter gap in valid periods
|
|
352
361
|
start = max(
|
|
353
|
-
[gap["from"],
|
|
362
|
+
[gap["from"], supply["date_start"] + relativedelta(months=1)]
|
|
354
363
|
)
|
|
355
|
-
end = min([gap["to"],
|
|
364
|
+
end = min([gap["to"], supply["date_end"]])
|
|
356
365
|
start = min([start, end])
|
|
366
|
+
_LOGGER.info(
|
|
367
|
+
"%s: request maximeter from %s to %s",
|
|
368
|
+
self._scups,
|
|
369
|
+
start.isoformat(),
|
|
370
|
+
end.isoformat(),
|
|
371
|
+
)
|
|
357
372
|
self.update_maximeter(cups, distributor_code, start, end)
|
|
358
373
|
|
|
359
|
-
|
|
360
|
-
if oldest_contract != supply_date_start and oldest_contract > max(
|
|
361
|
-
[date_from, supply_date_start]
|
|
362
|
-
):
|
|
363
|
-
_LOGGER.info(
|
|
364
|
-
"%s: supplies and contract start date do not match, exploring non-registered contracts",
|
|
365
|
-
self._scups,
|
|
366
|
-
)
|
|
367
|
-
start = max([supply_date_start, date_from])
|
|
368
|
-
self.update_consumptions(
|
|
369
|
-
cups, distributor_code, start, oldest_contract, "0", point_type
|
|
370
|
-
)
|
|
371
|
-
self.update_maximeter(cups, distributor_code, start, oldest_contract)
|
|
372
|
-
miss_cons, miss_maxim = sort_and_filter(start, date_to)
|
|
373
|
-
else:
|
|
374
|
-
miss_cons, miss_maxim = sort_and_filter(
|
|
375
|
-
max([date_from, oldest_contract]), date_to
|
|
376
|
-
)
|
|
374
|
+
miss_cons, miss_maxim = sort_and_filter(date_from, date_to)
|
|
377
375
|
|
|
378
376
|
return True
|
|
379
377
|
|
|
@@ -679,11 +677,11 @@ class EdataHelper:
|
|
|
679
677
|
for x in self.data["consumptions"]
|
|
680
678
|
if x["datetime"] >= new_data_from
|
|
681
679
|
],
|
|
682
|
-
prices=
|
|
683
|
-
x for x in self.data["pvpc"] if x["datetime"] >= new_data_from
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
680
|
+
prices=(
|
|
681
|
+
[x for x in self.data["pvpc"] if x["datetime"] >= new_data_from]
|
|
682
|
+
if self.is_pvpc
|
|
683
|
+
else None
|
|
684
|
+
),
|
|
687
685
|
rules=self.pricing_rules,
|
|
688
686
|
)
|
|
689
687
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|