e-data 2.0.0.dev120__tar.gz → 2.0.0.dev125__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.
Files changed (34) hide show
  1. {e_data-2.0.0.dev120/e_data.egg-info → e_data-2.0.0.dev125}/PKG-INFO +1 -1
  2. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125/e_data.egg-info}/PKG-INFO +1 -1
  3. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/database/controller.py +8 -0
  4. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/database/queries.py +10 -0
  5. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/providers/datadis.py +5 -5
  6. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/services/bill_service.py +33 -0
  7. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/services/data_service.py +5 -0
  8. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/tests/test_services.py +57 -0
  9. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/pyproject.toml +1 -1
  10. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/LICENSE +0 -0
  11. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/MANIFEST.in +0 -0
  12. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/README.md +0 -0
  13. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/e_data.egg-info/SOURCES.txt +0 -0
  14. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/e_data.egg-info/dependency_links.txt +0 -0
  15. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/e_data.egg-info/requires.txt +0 -0
  16. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/e_data.egg-info/top_level.txt +0 -0
  17. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/__init__.py +0 -0
  18. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/cli.py +0 -0
  19. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/core/__init__.py +0 -0
  20. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/core/const.py +0 -0
  21. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/core/utils.py +0 -0
  22. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/database/__init__.py +0 -0
  23. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/database/models.py +0 -0
  24. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/database/utils.py +0 -0
  25. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/models/__init__.py +0 -0
  26. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/models/bill.py +0 -0
  27. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/models/data.py +0 -0
  28. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/models/supply.py +0 -0
  29. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/providers/__init__.py +0 -0
  30. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/providers/redata.py +0 -0
  31. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/tests/__init__.py +0 -0
  32. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/tests/test_datadis_connector.py +0 -0
  33. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/edata/tests/test_redata_connector.py +0 -0
  34. {e_data-2.0.0.dev120 → e_data-2.0.0.dev125}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: e-data
3
- Version: 2.0.0.dev120
3
+ Version: 2.0.0.dev125
4
4
  Summary: Python library for managing spanish energy data from various web providers
5
5
  Author-email: VMG <vmayorg@outlook.es>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: e-data
3
- Version: 2.0.0.dev120
3
+ Version: 2.0.0.dev125
4
4
  Summary: Python library for managing spanish energy data from various web providers
5
5
  Author-email: VMG <vmayorg@outlook.es>
6
6
  License: GNU GENERAL PUBLIC LICENSE
@@ -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
 
@@ -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."""
@@ -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
- ) -> list[dict[str, typing.Any]]:
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(
@@ -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
 
@@ -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
 
@@ -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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "e-data"
7
- version = "2.0.0.dev120"
7
+ version = "2.0.0.dev125"
8
8
  description = "Python library for managing spanish energy data from various web providers"
9
9
  readme = "README.md"
10
10
  authors = [
File without changes
File without changes
File without changes
File without changes