e-data 2.0.0.dev120__py3-none-any.whl → 2.0.0.dev125__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.
- {e_data-2.0.0.dev120.dist-info → e_data-2.0.0.dev125.dist-info}/METADATA +1 -1
- {e_data-2.0.0.dev120.dist-info → e_data-2.0.0.dev125.dist-info}/RECORD +11 -11
- {e_data-2.0.0.dev120.dist-info → e_data-2.0.0.dev125.dist-info}/WHEEL +1 -1
- edata/database/controller.py +8 -0
- edata/database/queries.py +10 -0
- edata/providers/datadis.py +5 -5
- edata/services/bill_service.py +33 -0
- edata/services/data_service.py +5 -0
- edata/tests/test_services.py +57 -0
- {e_data-2.0.0.dev120.dist-info → e_data-2.0.0.dev125.dist-info}/licenses/LICENSE +0 -0
- {e_data-2.0.0.dev120.dist-info → e_data-2.0.0.dev125.dist-info}/top_level.txt +0 -0
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
e_data-2.0.0.
|
|
1
|
+
e_data-2.0.0.dev125.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
2
2
|
edata/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
edata/cli.py,sha256=0pmlaaXPVfbxPVHMovsuK9qf8PGXl38AV3gtIsk95O4,4010
|
|
4
4
|
edata/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
edata/core/const.py,sha256=X9J2f7lt9jXJnghqz0JFi-J7USa3xlYvbuiki70eHNE,43
|
|
6
6
|
edata/core/utils.py,sha256=P4vEPVnymvBp8W2rwwc_XMvd8_6Vx6N0GKnVx0WxE0I,1938
|
|
7
7
|
edata/database/__init__.py,sha256=HP_pDbGLzeMY9Ufm_Dps5cxzW_3YTonT8w8dstG558c,46
|
|
8
|
-
edata/database/controller.py,sha256=
|
|
8
|
+
edata/database/controller.py,sha256=T3fsW-2Y3NTz9eOVVQjHvYqdMHUg0QmcJseLXALF_tQ,16748
|
|
9
9
|
edata/database/models.py,sha256=4rlRpPErULqYr6QJxZar1NBpJPWTcHftDN1MB3bwGyA,5096
|
|
10
|
-
edata/database/queries.py,sha256=
|
|
10
|
+
edata/database/queries.py,sha256=rYdJ1X_yEY9FDU4NnEFjvanOcz7glZiQ4JrKCzXWXUM,7048
|
|
11
11
|
edata/database/utils.py,sha256=V-FJfyAh8YJBGHB85BbWtG3Ilq09pM9Gl-L86TffB4g,1062
|
|
12
12
|
edata/models/__init__.py,sha256=nMPPPkGjUsF51PBFlChgHyb5C3aQiccTRiVHUdJjNj4,153
|
|
13
13
|
edata/models/bill.py,sha256=-qZ4l9f8BnPFlykFRoNYnmRe6-ELWmuNh6usks_k4X0,2674
|
|
14
14
|
edata/models/data.py,sha256=O1aj0H_G92mnJjm6mYhC8fnl8ZOoatGiokRf8b7ziD8,2388
|
|
15
15
|
edata/models/supply.py,sha256=qM0Q22-7PfBN93-Tu3ljAE58p5gfob0YWw-o5FccjrI,878
|
|
16
16
|
edata/providers/__init__.py,sha256=5xaRKPttYM6EpVE1I_vhUAF25L9pEM_qK_5Ix6xnQe0,104
|
|
17
|
-
edata/providers/datadis.py,sha256=
|
|
17
|
+
edata/providers/datadis.py,sha256=a084ArY83ncvrMQvMSD2BY1rHlhp42dwJJkU8avmleo,20137
|
|
18
18
|
edata/providers/redata.py,sha256=uvxO2TTtSNSfPRWHZifLjbaP7zAr6O7CZ1oRQFNbIhQ,3122
|
|
19
|
-
edata/services/bill_service.py,sha256=
|
|
20
|
-
edata/services/data_service.py,sha256=
|
|
19
|
+
edata/services/bill_service.py,sha256=cZkf-CVA-1I1l1vasB8waVGbS4FkODL_W9x9H_DoXG0,14632
|
|
20
|
+
edata/services/data_service.py,sha256=1oIsQFlepCvpcR1T2WRhgjPafRCoXtvKFcL6eU1EM18,16945
|
|
21
21
|
edata/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
edata/tests/test_datadis_connector.py,sha256=O3mjI5uIOgFz3PzQsApcG6J2yXd8rlEtj8By-9dsDbg,9270
|
|
23
23
|
edata/tests/test_redata_connector.py,sha256=SBvxsma5t-qvz2drxNisNM2P1STTurqEs1TSDgwaPJ8,508
|
|
24
|
-
edata/tests/test_services.py,sha256=
|
|
25
|
-
e_data-2.0.0.
|
|
26
|
-
e_data-2.0.0.
|
|
27
|
-
e_data-2.0.0.
|
|
28
|
-
e_data-2.0.0.
|
|
24
|
+
edata/tests/test_services.py,sha256=K8Jx_LpFAqenupaF0I58whpo5OGUaSWBhN4MwNA2TGc,6179
|
|
25
|
+
e_data-2.0.0.dev125.dist-info/METADATA,sha256=jaRZ4ufDfQhJinChybMh0kJEEYCaIdlG-n3b89hd_T0,48075
|
|
26
|
+
e_data-2.0.0.dev125.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
27
|
+
e_data-2.0.0.dev125.dist-info/top_level.txt,sha256=Ez-fReWtUVTMcFuH0dzCnoKIgMBHgjLaBuba8QiKC7g,6
|
|
28
|
+
e_data-2.0.0.dev125.dist-info/RECORD,,
|
edata/database/controller.py
CHANGED
|
@@ -374,6 +374,14 @@ class EdataDB:
|
|
|
374
374
|
session, queries, items, override=["complete", "confhash"]
|
|
375
375
|
)
|
|
376
376
|
|
|
377
|
+
async def clear_bills(self, cups: str, since: datetime | None = None) -> None:
|
|
378
|
+
"""Delete bill records for a cups, optionally only from a datetime onwards."""
|
|
379
|
+
|
|
380
|
+
await self._ensure_tables()
|
|
381
|
+
async with AsyncSession(self.engine) as session:
|
|
382
|
+
await session.exec(q.delete_bill(cups, since)) # type: ignore[call-overload]
|
|
383
|
+
await session.commit()
|
|
384
|
+
|
|
377
385
|
async def list_supplies(self) -> typing.Sequence[SupplyModel]:
|
|
378
386
|
"""List all supply records."""
|
|
379
387
|
|
edata/database/queries.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import typing
|
|
2
2
|
from datetime import datetime
|
|
3
3
|
|
|
4
|
+
from sqlalchemy import Delete, delete
|
|
4
5
|
from sqlmodel import asc, desc, select
|
|
5
6
|
from sqlmodel.sql.expression import SelectOfScalar
|
|
6
7
|
|
|
@@ -184,6 +185,15 @@ def get_last_bill(
|
|
|
184
185
|
return query
|
|
185
186
|
|
|
186
187
|
|
|
188
|
+
def delete_bill(cups: str, date_from: datetime | None = None) -> Delete:
|
|
189
|
+
"""Query that deletes bill records, optionally from a datetime onwards."""
|
|
190
|
+
|
|
191
|
+
query = delete(BillModel).where(BillModel.cups == cups)
|
|
192
|
+
if date_from is not None:
|
|
193
|
+
query = query.where(BillModel.datetime >= date_from)
|
|
194
|
+
return query
|
|
195
|
+
|
|
196
|
+
|
|
187
197
|
# Queries for "pvpc" table
|
|
188
198
|
def get_pvpc(datetime_: datetime) -> SelectOfScalar[PVPCModel]:
|
|
189
199
|
"""Query that selects a PVPC record."""
|
edata/providers/datadis.py
CHANGED
|
@@ -166,7 +166,7 @@ class DatadisConnector:
|
|
|
166
166
|
refresh_token: bool = False,
|
|
167
167
|
is_retry: bool = False,
|
|
168
168
|
ignore_cache: bool = False,
|
|
169
|
-
) ->
|
|
169
|
+
) -> dict[str, typing.Any]:
|
|
170
170
|
"""Async get request for Datadis API."""
|
|
171
171
|
|
|
172
172
|
if request_data is None:
|
|
@@ -175,7 +175,7 @@ class DatadisConnector:
|
|
|
175
175
|
data = request_data
|
|
176
176
|
|
|
177
177
|
is_valid_token = False
|
|
178
|
-
response =
|
|
178
|
+
response = {}
|
|
179
179
|
if refresh_token:
|
|
180
180
|
is_valid_token = await self._async_get_token()
|
|
181
181
|
if is_valid_token or not refresh_token:
|
|
@@ -201,7 +201,7 @@ class DatadisConnector:
|
|
|
201
201
|
if _cache is not None:
|
|
202
202
|
_LOGGER.info("CACHED %s", url + anonym_params)
|
|
203
203
|
return _cache # type: ignore
|
|
204
|
-
return
|
|
204
|
+
return {}
|
|
205
205
|
|
|
206
206
|
try:
|
|
207
207
|
_LOGGER.info("GET %s", url + anonym_params)
|
|
@@ -272,10 +272,10 @@ class DatadisConnector:
|
|
|
272
272
|
)
|
|
273
273
|
except asyncio.TimeoutError:
|
|
274
274
|
_LOGGER.warning("Timeout at %s", url + anonym_params)
|
|
275
|
-
return
|
|
275
|
+
return {}
|
|
276
276
|
except Exception as e:
|
|
277
277
|
_LOGGER.warning("Exception at %s: %s", url + anonym_params, e)
|
|
278
|
-
return
|
|
278
|
+
return {}
|
|
279
279
|
return response
|
|
280
280
|
|
|
281
281
|
async def async_get_supplies(
|
edata/services/bill_service.py
CHANGED
|
@@ -137,6 +137,39 @@ class BillService:
|
|
|
137
137
|
await self.update_statistics(start, end)
|
|
138
138
|
await self.fix_missing_statistics()
|
|
139
139
|
|
|
140
|
+
async def clear_bills(self, since: datetime | None = None) -> None:
|
|
141
|
+
"""Delete stored bills for this cups, optionally only from a datetime onwards."""
|
|
142
|
+
|
|
143
|
+
await self.db.clear_bills(self._cups, since)
|
|
144
|
+
|
|
145
|
+
async def simulate(
|
|
146
|
+
self,
|
|
147
|
+
billing_rules: BillingRules | PVPCBillingRules | None = None,
|
|
148
|
+
is_pvpc: bool = True,
|
|
149
|
+
start: datetime | None = None,
|
|
150
|
+
end: datetime | None = None,
|
|
151
|
+
) -> list[Bill]:
|
|
152
|
+
"""Compile hourly bills for a date range without persisting them."""
|
|
153
|
+
|
|
154
|
+
if not billing_rules:
|
|
155
|
+
_LOGGER.info("%s non explicit billing rules, assuming PVPC", self._scups)
|
|
156
|
+
billing_rules = PVPCBillingRules()
|
|
157
|
+
is_pvpc = True
|
|
158
|
+
|
|
159
|
+
contracts = await self._get_contracts()
|
|
160
|
+
energy = await self._get_energy(start, end)
|
|
161
|
+
|
|
162
|
+
if is_pvpc:
|
|
163
|
+
pvpc = await self._get_pvpc(start, end)
|
|
164
|
+
billing_rules = PVPCBillingRules(**billing_rules.model_dump())
|
|
165
|
+
return await asyncio.to_thread(
|
|
166
|
+
self.simulate_pvpc, contracts, energy, pvpc, billing_rules
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
return await asyncio.to_thread(
|
|
170
|
+
self.simulate_custom, contracts, energy, billing_rules
|
|
171
|
+
)
|
|
172
|
+
|
|
140
173
|
async def update_statistics(self, start: datetime, end: datetime):
|
|
141
174
|
"""Update the statistics during a period."""
|
|
142
175
|
|
edata/services/data_service.py
CHANGED
|
@@ -109,6 +109,11 @@ class DataService:
|
|
|
109
109
|
data = await self.db.list_statistics(self._cups, type_, start, end, complete)
|
|
110
110
|
return [x.data for x in data]
|
|
111
111
|
|
|
112
|
+
async def get_last_energy_dt(self) -> datetime | None:
|
|
113
|
+
"""Return the timestamp of the latest energy record."""
|
|
114
|
+
|
|
115
|
+
return await self._get_last_energy_dt()
|
|
116
|
+
|
|
112
117
|
async def fix_missing_statistics(self) -> None:
|
|
113
118
|
"""Recompile statistics to fix missing data."""
|
|
114
119
|
|
edata/tests/test_services.py
CHANGED
|
@@ -154,3 +154,60 @@ async def test_bill_service(
|
|
|
154
154
|
assert len(monthly_stats) > 0
|
|
155
155
|
|
|
156
156
|
assert monthly_stats == snapshot
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@pytest.mark.asyncio
|
|
160
|
+
async def test_simulate_does_not_persist(
|
|
161
|
+
populated_data_service, snapshot: SnapshotAssertion, storage_dir
|
|
162
|
+
):
|
|
163
|
+
bs = BillService("ESXXXXXXXXXXXXXXXXTEST", storage_path=storage_dir)
|
|
164
|
+
|
|
165
|
+
before = len(await bs.get_bills())
|
|
166
|
+
|
|
167
|
+
rules = BillingRules(
|
|
168
|
+
p1_kwh_eur=0.30,
|
|
169
|
+
p2_kwh_eur=0.30,
|
|
170
|
+
p3_kwh_eur=0.30,
|
|
171
|
+
p1_kw_year_eur=20,
|
|
172
|
+
p2_kw_year_eur=10,
|
|
173
|
+
)
|
|
174
|
+
simulated = await bs.simulate(billing_rules=rules, is_pvpc=False)
|
|
175
|
+
|
|
176
|
+
after = len(await bs.get_bills())
|
|
177
|
+
|
|
178
|
+
assert len(simulated) > 0
|
|
179
|
+
assert before == after
|
|
180
|
+
|
|
181
|
+
summary = {
|
|
182
|
+
"count": len(simulated),
|
|
183
|
+
"value_eur": round(sum(x.value_eur for x in simulated), 6),
|
|
184
|
+
"energy_term": round(sum(x.energy_term for x in simulated), 6),
|
|
185
|
+
"power_term": round(sum(x.power_term for x in simulated), 6),
|
|
186
|
+
"first": simulated[0],
|
|
187
|
+
"last": simulated[-1],
|
|
188
|
+
}
|
|
189
|
+
assert summary == snapshot
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
@pytest.mark.asyncio
|
|
193
|
+
async def test_clear_bills(populated_data_service, energy, storage_dir):
|
|
194
|
+
bs = BillService("ESXXXXXXXXXXXXXXXXTEST", storage_path=storage_dir)
|
|
195
|
+
|
|
196
|
+
all_energy = sorted(energy, key=lambda x: x.datetime)
|
|
197
|
+
rules = BillingRules(
|
|
198
|
+
p1_kwh_eur=0.20,
|
|
199
|
+
p2_kwh_eur=0.20,
|
|
200
|
+
p3_kwh_eur=0.20,
|
|
201
|
+
p1_kw_year_eur=20,
|
|
202
|
+
p2_kw_year_eur=10,
|
|
203
|
+
)
|
|
204
|
+
await bs.update(
|
|
205
|
+
all_energy[0].datetime, all_energy[-1].datetime, rules, is_pvpc=False
|
|
206
|
+
)
|
|
207
|
+
assert len(await bs.get_bills()) > 0
|
|
208
|
+
|
|
209
|
+
await bs.clear_bills()
|
|
210
|
+
|
|
211
|
+
assert len(await bs.get_bills("hour")) == 0
|
|
212
|
+
assert len(await bs.get_bills(type_="day")) == 0
|
|
213
|
+
assert len(await bs.get_bills(type_="month")) == 0
|
|
File without changes
|
|
File without changes
|