You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

e-data

Package Overview
Dependencies
Maintainers
1
Versions
85
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

e-data - pypi Package Compare versions

Comparing version
2.0.0.dev112
to
2.0.0.dev120
+3
-3
e_data.egg-info/PKG-INFO
Metadata-Version: 2.4
Name: e-data
Version: 2.0.0.dev112
Version: 2.0.0.dev120
Summary: Python library for managing spanish energy data from various web providers

@@ -826,3 +826,3 @@ Author-email: VMG <vmayorg@outlook.es>

print(f"Coste total: {total_cost:.2f} €")
print(f"Consumo total: {sum(e.value_kWh for e in energy):.2f} kWh")
print(f"Consumo total: {sum(e.value_kwh for e in energy):.2f} kWh")

@@ -845,3 +845,3 @@ # Ejecutar

# Actualizar facturación con tarifa fija
python -m edata.cli update-bill \
python -m edata.cli update-custom-bill \
--cups ES0000000000000000XX \

@@ -848,0 +848,0 @@ --p1-kw-year-eur 30.67 \

@@ -20,3 +20,3 @@ import asyncio

@app.command()
def show_supplies(username: str):
def show_supplies(username: str) -> None:
"""Show supplies and contracts for a given datadis user."""

@@ -32,3 +32,3 @@

cups = supplies[0].cups
distributor = supplies[0].distributorCode
distributor = supplies[0].distributor_code
contracts = connector.get_contract_detail(cups, distributor)

@@ -46,3 +46,3 @@ typer.echo("\nContracts:")

authorized_nif: str | None = None,
):
) -> None:
"""Download all data for a given datadis account and CUPS."""

@@ -69,3 +69,3 @@

authorized_nif: str | None = None,
):
) -> None:
"""Download all data for a given datadis account and CUPS."""

@@ -84,3 +84,3 @@

meter_month_eur: float,
):
) -> None:
"""Download all data for a given datadis account and CUPS."""

@@ -113,3 +113,3 @@

meter_month_eur: Annotated[float, typer.Option(help="Monthly cost of the meter")],
):
) -> None:
"""Download all data for a given datadis account and CUPS."""

@@ -130,3 +130,3 @@

async def _update_pvpc_bill(cups: str):
async def _update_pvpc_bill(cups: str) -> None:
"""Download all data for a given datadis account and CUPS."""

@@ -141,3 +141,3 @@

cups: Annotated[str, typer.Option(help="The identifier of the Supply")],
):
) -> None:
"""Download all data for a given datadis account and CUPS."""

@@ -144,0 +144,0 @@

@@ -46,3 +46,3 @@ """Collection of utilities."""

def get_contract_for_dt(contracts: list[Contract], date: datetime):
def get_contract_for_dt(contracts: list[Contract], date: datetime) -> Contract | None:
"""Return the active contract for a provided datetime."""

@@ -49,0 +49,0 @@

@@ -52,3 +52,3 @@ import logging

@property
def engine(self):
def engine(self) -> AsyncEngine | None:
"""Return the async database engine."""

@@ -58,3 +58,3 @@

async def _ensure_tables(self):
async def _ensure_tables(self) -> None:
"""Create tables if not already created (lazy init)."""

@@ -89,7 +89,7 @@

query: SelectOfScalar,
data,
data: typing.Any,
commit: bool = True,
overrides: dict[str, typing.Any] | None = None,
) -> T | None: # type: ignore
"""Updates a single record in the database."""
"""Update a single record in the database."""

@@ -246,3 +246,3 @@ result = await session.exec(query)

async def add_energy_list(self, cups: str, energy: list[Energy]):
async def add_energy_list(self, cups: str, energy: list[Energy]) -> None:
"""Add or update a list of energy records."""

@@ -271,3 +271,3 @@

async def add_power_list(self, cups: str, power: list[Power]):
async def add_power_list(self, cups: str, power: list[Power]) -> None:
"""Add or update a list of power records."""

@@ -293,3 +293,3 @@

async def add_pvpc_list(self, pvpc: list[EnergyPrice]):
async def add_pvpc_list(self, pvpc: list[EnergyPrice]) -> None:
"""Add or update a list of pvpc records."""

@@ -360,3 +360,3 @@

bill: list[Bill],
):
) -> None:
"""Add or update a list of bill records."""

@@ -384,3 +384,3 @@

async def list_supplies(self):
async def list_supplies(self) -> typing.Sequence[SupplyModel]:
"""List all supply records."""

@@ -393,3 +393,5 @@

async def list_contracts(self, cups: str | None = None):
async def list_contracts(
self, cups: str | None = None
) -> typing.Sequence[ContractModel]:
"""List all contract records."""

@@ -407,3 +409,3 @@

date_to: datetime | None = None,
):
) -> typing.Sequence[EnergyModel]:
"""List energy records."""

@@ -421,3 +423,3 @@

date_to: datetime | None = None,
):
) -> typing.Sequence[PowerModel]:
"""List power records."""

@@ -434,3 +436,3 @@

date_to: datetime | None = None,
):
) -> typing.Sequence[PVPCModel]:
"""List pvpc records."""

@@ -450,3 +452,3 @@

complete: bool | None = None,
):
) -> typing.Sequence[StatisticsModel]:
"""List statistics records filtered by type ('day' or 'month') and date range."""

@@ -468,3 +470,3 @@

complete: bool | None = None,
):
) -> typing.Sequence[BillModel]:
"""List bill records filtered by type ('hour', 'day' or 'month') and date range."""

@@ -471,0 +473,0 @@

@@ -218,3 +218,5 @@ import typing

def get_bill(cups, type_, datetime_):
def get_bill(
cups: str, type_: typing.Literal["hour", "day", "month"], datetime_: datetime | None
) -> SelectOfScalar[BillModel]:
"""Query that selects a bill."""

@@ -221,0 +223,0 @@

@@ -10,6 +10,3 @@ from typing import Any, Type, TypeVar

class PydanticJSON(TypeDecorator):
"""
Tipo de SQLAlchemy para guardar modelos Pydantic como JSON.
Automáticamente serializa al guardar y deserializa al leer.
"""
"""SQLAlchemy type to guard Pydantic models as JSON."""

@@ -24,2 +21,3 @@ impl = JSON

def process_bind_param(self, value: T | None, dialect: Any) -> Any:
"""Process binding parameter."""
# Python -> Base de Datos

@@ -32,2 +30,3 @@ if value is None:

def process_result_value(self, value: Any, dialect: Any) -> T | None:
"""Process result value."""
# Base de Datos -> Python

@@ -34,0 +33,0 @@ if value is None:

@@ -9,6 +9,6 @@ """Models for billing-related data"""

class EnergyPrice(BaseModel):
"""Data structure to represent pricing data."""
"""Represent pricing data."""
datetime: datetime
value_eur_kWh: float
value_eur_kwh: float
delta_h: float

@@ -18,3 +18,3 @@

class Bill(BaseModel):
"""Data structure to represent a bill during a period."""
"""Represent a bill during a period."""

@@ -31,3 +31,3 @@ datetime: datetime

class BillingRules(BaseModel):
"""Data structure to represent a generic billing rule."""
"""Represent a generic billing rule."""

@@ -56,3 +56,3 @@ p1_kw_year_eur: float

class PVPCBillingRules(BillingRules):
"""Data structure to represent a PVPC billing rule."""
"""Represent a PVPC billing rule."""

@@ -59,0 +59,0 @@ p1_kw_year_eur: float = Field(default=30.67266)

@@ -9,8 +9,10 @@ """Models for telemetry data"""

class Energy(BaseModel):
"""Data structure to represent energy consumption and/or surplus measurements."""
"""Represent energy consumption and/or surplus measurements."""
datetime: datetime
delta_h: float
value_kWh: float
surplus_kWh: float = Field(0)
consumption_kwh: float
surplus_kwh: float = Field(0)
generation_kwh: float = Field(0)
selfconsumption_kwh: float = Field(0)
real: bool

@@ -20,20 +22,70 @@

class Power(BaseModel):
"""Data structure to represent power measurements."""
"""Represent power measurements."""
datetime: datetime
value_kW: float
value_kw: float
class Statistics(BaseModel):
"""Data structure to represent aggregated energy/surplus data."""
"""Represent aggregated energy/surplus data."""
datetime: datetime
delta_h: float = Field(0)
value_kWh: float = Field(0)
value_p1_kWh: float = Field(0)
value_p2_kWh: float = Field(0)
value_p3_kWh: float = Field(0)
surplus_kWh: float = Field(0)
surplus_p1_kWh: float = Field(0)
surplus_p2_kWh: float = Field(0)
surplus_p3_kWh: float = Field(0)
consumption_kwh: float = Field(0)
consumption_by_tariff: list[float] = Field(default_factory=lambda: [0.0, 0.0, 0.0])
surplus_kwh: float = Field(0)
surplus_by_tariff: list[float] = Field(default_factory=lambda: [0.0, 0.0, 0.0])
generation_kwh: float = Field(0)
generation_by_tariff: list[float] = Field(default_factory=lambda: [0.0, 0.0, 0.0])
selfconsumption_kwh: float = Field(0)
selfconsumption_by_tariff: list[float] = Field(
default_factory=lambda: [0.0, 0.0, 0.0]
)
@property
def consumption_p1_kwh(self) -> float:
return self.consumption_by_tariff[0]
@property
def consumption_p2_kwh(self) -> float:
return self.consumption_by_tariff[1]
@property
def consumption_p3_kwh(self) -> float:
return self.consumption_by_tariff[2]
@property
def surplus_p1_kwh(self) -> float:
return self.surplus_by_tariff[0]
@property
def surplus_p2_kwh(self) -> float:
return self.surplus_by_tariff[1]
@property
def surplus_p3_kwh(self) -> float:
return self.surplus_by_tariff[2]
@property
def generation_p1_kwh(self) -> float:
return self.generation_by_tariff[0]
@property
def generation_p2_kwh(self) -> float:
return self.generation_by_tariff[1]
@property
def generation_p3_kwh(self) -> float:
return self.generation_by_tariff[2]
@property
def selfconsumption_p1_kwh(self) -> float:
return self.selfconsumption_by_tariff[0]
@property
def selfconsumption_p2_kwh(self) -> float:
return self.selfconsumption_by_tariff[1]
@property
def selfconsumption_p3_kwh(self) -> float:
return self.selfconsumption_by_tariff[2]

@@ -19,4 +19,4 @@ """Models for contractual data"""

distributor: str | None
pointType: int
distributorCode: str
point_type: int
distributor_code: str

@@ -30,4 +30,13 @@

marketer: str
distributorCode: str
power_p1: float | None
power_p2: float | None
distributor_code: str
power: list[float]
@property
def power_p1(self) -> float | None:
"""Return power P1."""
return self.power[0] if len(self.power) > 0 else None
@property
def power_p2(self) -> float | None:
"""Return power P2."""
return self.power[1] if len(self.power) > 1 else None

@@ -1,9 +0,3 @@

"""Datadis API connector.
"""Datadis API connector."""
To fetch data from datadis.es private API.
There a few issues that are workarounded:
- You have to wait 24h between two identical requests.
- Datadis server does not like ranges greater than 1 month.
"""
import asyncio

@@ -31,3 +25,3 @@ import contextlib

# Supplies-related constants
URL_GET_SUPPLIES = "https://datadis.es/api-private/api/get-supplies"
URL_GET_SUPPLIES = "https://datadis.es/api-private/api/get-supplies-v2"
GET_SUPPLIES_MANDATORY_FIELDS = [

@@ -42,3 +36,3 @@ "cups",

# Contracts-related constants
URL_GET_CONTRACT_DETAIL = "https://datadis.es/api-private/api/get-contract-detail"
URL_GET_CONTRACT_DETAIL = "https://datadis.es/api-private/api/get-contract-detail-v2"
GET_CONTRACT_DETAIL_MANDATORY_FIELDS = [

@@ -52,3 +46,3 @@ "startDate",

# Consumption-related constants
URL_GET_CONSUMPTION_DATA = "https://datadis.es/api-private/api/get-consumption-data"
URL_GET_CONSUMPTION_DATA = "https://datadis.es/api-private/api/get-consumption-data-v2"
GET_CONSUMPTION_DATA_MANDATORY_FIELDS = [

@@ -62,3 +56,3 @@ "time",

# Maximeter-related constants
URL_GET_MAX_POWER = "https://datadis.es/api-private/api/get-max-power"
URL_GET_MAX_POWER = "https://datadis.es/api-private/api/get-max-power-v2"
GET_MAX_POWER_MANDATORY_FIELDS = ["time", "date", "maxPower"]

@@ -75,3 +69,3 @@

def migrate_storage(storage_dir):
def migrate_storage(storage_dir: str) -> None:
"""Migrate storage from older versions."""

@@ -110,5 +104,4 @@ with contextlib.suppress(FileNotFoundError):

def _get_hash(self, item: str):
def _get_hash(self, item: str) -> str:
"""Return a hash."""
return hashlib.md5(item.encode()).hexdigest()

@@ -130,3 +123,3 @@

def _get_cache(self, key: str):
def _get_cache(self, key: str) -> dict | None:
"""Return cached response for a query (diskcache)."""

@@ -139,3 +132,3 @@ hash_query = self._get_hash(key)

async def _async_get_token(self):
async def _async_get_token(self) -> bool:
"""Private async method that fetches a new token if needed."""

@@ -169,7 +162,7 @@ _LOGGER.debug("No token found, fetching a new one")

async def async_login(self):
async def async_login(self) -> bool:
"""Test to login with provided credentials (async)."""
return await self._async_get_token()
def login(self):
def login(self) -> bool:
"""Test to login with provided credentials (sync wrapper)."""

@@ -300,2 +293,3 @@ return asyncio.run(self.async_login())

) -> list[Supply]:
"""Datadis 'get_supplies' query."""
data = {}

@@ -309,3 +303,3 @@ if authorized_nif is not None:

tomorrow_str = (datetime.today() + timedelta(days=1)).strftime("%Y/%m/%d")
for i in response:
for i in response.get("supplies", []):
if all(k in i for k in GET_SUPPLIES_MANDATORY_FIELDS):

@@ -336,4 +330,4 @@ supplies.append(

distributor=i.get("distributor", None),
pointType=i["pointType"],
distributorCode=i["distributorCode"],
point_type=i["pointType"],
distributor_code=i["distributorCode"],
)

@@ -348,3 +342,3 @@ )

def get_supplies(self, authorized_nif: str | None = None):
def get_supplies(self, authorized_nif: str | None = None) -> list[Supply]:
"""Datadis 'get_supplies' query (sync wrapper)."""

@@ -356,2 +350,3 @@ return asyncio.run(self.async_get_supplies(authorized_nif=authorized_nif))

) -> list[Contract]:
"""Datadis 'get_contract_detail' query."""
data = {"cups": cups, "distributorCode": distributor_code}

@@ -365,3 +360,3 @@ if authorized_nif is not None:

tomorrow_str = (datetime.today() + timedelta(days=1)).strftime("%Y/%m/%d")
for i in response:
for i in response.get("contract", []):
if all(k in i for k in GET_CONTRACT_DETAIL_MANDATORY_FIELDS):

@@ -379,13 +374,4 @@ contracts.append(

marketer=i["marketer"],
distributorCode=distributor_code,
power_p1=(
i["contractedPowerkW"][0]
if isinstance(i["contractedPowerkW"], list)
else None
),
power_p2=(
i["contractedPowerkW"][1]
if (len(i["contractedPowerkW"]) > 1)
else None
),
distributor_code=distributor_code,
power=i["contractedPowerkW"],
)

@@ -402,3 +388,3 @@ )

self, cups: str, distributor_code: str, authorized_nif: str | None = None
):
) -> list[Contract]:
"""Datadis get_contract_detail query (sync wrapper)."""

@@ -419,3 +405,3 @@ return asyncio.run(

) -> list[Energy]:
"""Datadis 'get_consumption_data' query."""
data = {

@@ -435,3 +421,3 @@ "cups": cups,

consumptions = []
for i in response:
for i in response.get("timeCurve", []):
if "consumptionKWh" in i:

@@ -445,5 +431,14 @@ if all(k in i for k in GET_CONSUMPTION_DATA_MANDATORY_FIELDS):

continue # skip element if dt is out of range
_surplus = i.get("surplusEnergyKWh", 0)
if _surplus is None:
_surplus = 0
# sanitize these values
_surplus_kwh = i.get("surplusEnergyKWh", 0)
if _surplus_kwh is None:
_surplus_kwh = 0
_generation_kwh = i.get("generationEnergyKWh", 0)
if _generation_kwh is None:
_generation_kwh = 0
_selfconsumption_kwh = i.get("selfConsumptionEnergyKWh", 0)
if _selfconsumption_kwh is None:
_selfconsumption_kwh = 0
consumptions.append(

@@ -453,4 +448,6 @@ Energy(

delta_h=1,
value_kWh=i["consumptionKWh"],
surplus_kWh=_surplus,
consumption_kwh=i["consumptionKWh"],
surplus_kwh=_surplus_kwh,
generation_kwh=_generation_kwh,
selfconsumption_kwh=_selfconsumption_kwh,
real=i["obtainMethod"] == "Real",

@@ -475,3 +472,3 @@ )

authorized_nif: str | None = None,
):
) -> list[Energy]:
"""Datadis get_consumption_data query (sync wrapper)."""

@@ -498,2 +495,3 @@ return asyncio.run(

) -> list[Power]:
"""Datadis 'get_max_power' query."""
data = {

@@ -509,3 +507,3 @@ "cups": cups,

maxpower_values = []
for i in response:
for i in response.get("maxPower", []):
if all(k in i for k in GET_MAX_POWER_MANDATORY_FIELDS):

@@ -517,3 +515,3 @@ maxpower_values.append(

),
value_kW=i["maxPower"],
value_kw=i["maxPower"],
)

@@ -535,3 +533,3 @@ )

authorized_nif: str | None = None,
):
) -> list[Power]:
"""Datadis get_max_power query (sync wrapper)."""

@@ -538,0 +536,0 @@ return asyncio.run(

@@ -65,3 +65,3 @@ """A REData API connector"""

),
value_eur_kWh=element["value"] / 1000,
value_eur_kwh=element["value"] / 1000,
delta_h=1,

@@ -83,3 +83,3 @@ )

self, dt_from: dt.datetime, dt_to: dt.datetime, is_ceuta_melilla: bool = False
) -> list:
) -> list[EnergyPrice]:
"""GET query to fetch realtime pvpc prices, historical data is limited to current month (sync wrapper)"""

@@ -86,0 +86,0 @@ return asyncio.run(

@@ -118,3 +118,3 @@ import asyncio

bills = await asyncio.to_thread(
self._compile_pvpc, contracts, energy, pvpc, billing_rules
self.simulate_pvpc, contracts, energy, pvpc, billing_rules
)

@@ -124,3 +124,3 @@ confighash = f"pvpc-{hash(billing_rules.model_dump_json())}"

bills = await asyncio.to_thread(
self._compile_j2, contracts, energy, billing_rules
self.simulate_custom, contracts, energy, billing_rules
)

@@ -148,3 +148,3 @@ confighash = f"custom-{hash(billing_rules.model_dump_json())}"

async def _update_daily_statistics(self, start: datetime, end: datetime):
async def _update_daily_statistics(self, start: datetime, end: datetime) -> None:
"""Update daily statistics within a date range."""

@@ -177,3 +177,3 @@

async def _update_monthly_statistics(self, start: datetime, end: datetime):
async def _update_monthly_statistics(self, start: datetime, end: datetime) -> None:
"""Update monthly statistics within a date range."""

@@ -209,3 +209,3 @@

async def _find_missing_stats(self):
async def _find_missing_stats(self) -> list[datetime]:
"""Return the list of days that are missing billing data."""

@@ -255,3 +255,3 @@

async def fix_missing_statistics(self):
async def fix_missing_statistics(self) -> None:
"""Recompile statistics to fix missing data."""

@@ -273,3 +273,3 @@

def _compile_pvpc(
def simulate_pvpc(
self,

@@ -316,4 +316,4 @@ contracts: list[Contract],

* rules.iva_tax
* p[dt].value_eur_kWh
* e[dt].value_kWh
* p[dt].value_eur_kwh
* e[dt].consumption_kwh
)

@@ -337,3 +337,3 @@ bill.power_term = (

def _compile_j2(
def simulate_custom(
self,

@@ -343,3 +343,3 @@ contracts: list[Contract],

rules: BillingRules,
):
) -> list[Bill]:
"""Compile bills from custom rules."""

@@ -371,3 +371,3 @@

params["p2_kw"] = p2_kw
params["kwh"] = e[dt].value_kWh
params["kwh"] = e[dt].consumption_kwh

@@ -402,3 +402,3 @@ tariff = get_tariff(dt)

async def _get_contracts(self) -> list[Contract]:
"""Get contracts."""
res = await self.db.list_contracts(self._cups)

@@ -410,2 +410,3 @@ return [x.data for x in res]

) -> list[Energy]:
"""Get energy."""
res = await self.db.list_energy(self._cups, start, end)

@@ -417,2 +418,3 @@ return [x.data for x in res]

) -> list[EnergyPrice]:
"""Get PVPC."""
res = await self.db.list_pvpc(start, end)

@@ -423,5 +425,4 @@ return [x.data for x in res]

"""Return the timestamp of the latest bill record."""
last_record = await self.db.get_last_bill(self._cups)
if last_record:
return last_record.datetime

@@ -112,3 +112,3 @@ """Definition of a service for telemetry data handling."""

async def fix_missing_statistics(self):
async def fix_missing_statistics(self) -> None:
"""Recompile statistics to fix missing data."""

@@ -240,3 +240,3 @@

async def login(self):
async def login(self) -> bool:
"""Test login at Datadis."""

@@ -246,3 +246,3 @@

async def update_supplies(self):
async def update_supplies(self) -> None:
"""Update the list of supplies for the configured user."""

@@ -261,3 +261,3 @@

self._contracts = await self.datadis.async_get_contract_detail(
cups, supply.distributorCode, self._authorized_nif
cups, supply.distributor_code, self._authorized_nif
)

@@ -270,3 +270,3 @@ for c in self._contracts:

async def update_energy(self, start: datetime, end: datetime):
async def update_energy(self, start: datetime, end: datetime) -> bool:
"""Update the list of energy consumptions for the selected cups."""

@@ -279,7 +279,7 @@

cups,
supply.distributorCode,
supply.distributor_code,
start,
end,
self._measurement_type,
supply.pointType,
supply.point_type,
self._authorized_nif,

@@ -292,3 +292,3 @@ )

async def update_power(self, start: datetime, end: datetime):
async def update_power(self, start: datetime, end: datetime) -> bool:
"""Update the list of power peaks for the selected cups."""

@@ -300,3 +300,3 @@ cups = self._cups

cups,
supply.distributorCode,
supply.distributor_code,
start,

@@ -311,3 +311,3 @@ end,

async def update_pvpc(self, start: datetime, end: datetime):
async def update_pvpc(self, start: datetime, end: datetime) -> bool:
"""Update recent pvpc prices."""

@@ -336,3 +336,3 @@

async def update_statistics(self, start: datetime, end: datetime):
async def update_statistics(self, start: datetime, end: datetime) -> None:
"""Update the statistics during a period."""

@@ -343,3 +343,3 @@

async def _update_daily_statistics(self, start: datetime, end: datetime):
async def _update_daily_statistics(self, start: datetime, end: datetime) -> None:
"""Update daily statistics within a date range."""

@@ -372,3 +372,3 @@

async def _update_monthly_statistics(self, start: datetime, end: datetime):
async def _update_monthly_statistics(self, start: datetime, end: datetime) -> None:
"""Update monthly statistics within a date range."""

@@ -435,10 +435,10 @@

delta_h=0,
value_kWh=0,
value_p1_kWh=0,
value_p2_kWh=0,
value_p3_kWh=0,
surplus_kWh=0,
surplus_p1_kWh=0,
surplus_p2_kWh=0,
surplus_p3_kWh=0,
value_kwh=0,
consumption_by_tariff=[0.0, 0.0, 0.0],
surplus_kwh=0,
surplus_by_tariff=[0.0, 0.0, 0.0],
generation_kwh=0,
generation_by_tariff=[0.0, 0.0, 0.0],
selfconsumption_kwh=0,
selfconsumption_by_tariff=[0.0, 0.0, 0.0],
)

@@ -448,17 +448,18 @@

ref.delta_h += item.delta_h
ref.value_kWh += item.value_kWh
ref.surplus_kWh += item.surplus_kWh
if 1 == tariff:
ref.value_p1_kWh += item.value_kWh
ref.surplus_p1_kWh += item.surplus_kWh
elif 2 == tariff:
ref.value_p2_kWh += item.value_kWh
ref.surplus_p2_kWh += item.surplus_kWh
elif 3 == tariff:
ref.value_p3_kWh += item.value_kWh
ref.surplus_p3_kWh += item.surplus_kWh
ref.consumption_kwh += item.consumption_kwh
ref.surplus_kwh += item.surplus_kwh
ref.generation_kwh += item.generation_kwh
ref.selfconsumption_kwh += item.selfconsumption_kwh
if 1 <= tariff <= 3:
idx = tariff - 1
ref.consumption_by_tariff[idx] += item.consumption_kwh
ref.surplus_by_tariff[idx] += item.surplus_kwh
ref.generation_by_tariff[idx] += item.generation_kwh
ref.selfconsumption_by_tariff[idx] += item.selfconsumption_kwh
return [agg_data[x] for x in agg_data]
async def _find_missing_stats(self, agg: typing.Literal["day", "month"] = "day"):
async def _find_missing_stats(
self, agg: typing.Literal["day", "month"] = "day"
) -> list[datetime]:
"""Return the list of days that are missing energy data."""

@@ -490,3 +491,3 @@

async def _sync(self):
async def _sync(self) -> None:
"""Load state."""

@@ -493,0 +494,0 @@

@@ -11,61 +11,69 @@ """Tests for DatadisConnector (offline)."""

SUPPLIES_RESPONSE = [
{
"cups": "ESXXXXXXXXXXXXXXXXTEST",
"validDateFrom": "2022/03/09",
"validDateTo": "2022/10/28",
"address": "-",
"postalCode": "-",
"province": "-",
"municipality": "-",
"distributor": "-",
"pointType": 5,
"distributorCode": "2",
}
]
SUPPLIES_RESPONSE = {
"supplies": [
{
"cups": "ESXXXXXXXXXXXXXXXXTEST",
"validDateFrom": "2022/03/09",
"validDateTo": "2022/10/28",
"address": "-",
"postalCode": "-",
"province": "-",
"municipality": "-",
"distributor": "-",
"pointType": 5,
"distributorCode": "2",
}
]
}
CONTRACTS_RESPONSE = [
{
"startDate": "2022/03/09",
"endDate": "2022/10/28",
"marketer": "MARKETER",
"distributorCode": "2",
"contractedPowerkW": [4.4, 4.4],
}
]
CONTRACTS_RESPONSE = {
"contract": [
{
"startDate": "2022/03/09",
"endDate": "2022/10/28",
"marketer": "MARKETER",
"distributorCode": "2",
"contractedPowerkW": [4.4, 4.4],
}
]
}
CONSUMPTIONS_RESPONSE = [
{
"date": "2022/10/22",
"time": "01:00",
"consumptionKWh": 0.203,
"surplusEnergyKWh": 0,
"obtainMethod": "Real",
},
{
"date": "2022/10/22",
"time": "02:00",
"consumptionKWh": 0.163,
"surplusEnergyKWh": 0,
"obtainMethod": "Real",
},
]
CONSUMPTIONS_RESPONSE = {
"timeCurve": [
{
"date": "2022/10/22",
"time": "01:00",
"consumptionKWh": 0.203,
"surplusEnergyKWh": 0,
"obtainMethod": "Real",
},
{
"date": "2022/10/22",
"time": "02:00",
"consumptionKWh": 0.163,
"surplusEnergyKWh": 0,
"obtainMethod": "Real",
},
]
}
MAXIMETER_RESPONSE = [
{
"date": "2022/03/10",
"time": "14:15",
"maxPower": 2.436,
},
{
"date": "2022/03/14",
"time": "13:15",
"maxPower": 3.008,
},
{
"date": "2022/03/27",
"time": "10:30",
"maxPower": 3.288,
},
]
MAXIMETER_RESPONSE = {
"maxPower": [
{
"date": "2022/03/10",
"time": "14:15",
"maxPower": 2.436,
},
{
"date": "2022/03/14",
"time": "13:15",
"maxPower": 3.008,
},
{
"date": "2022/03/27",
"time": "10:30",
"maxPower": 3.288,
},
]
}

@@ -161,3 +169,3 @@

mock_response.text = AsyncMock(return_value="text")
mock_response.json = AsyncMock(return_value=[])
mock_response.json = AsyncMock(return_value={"supplies": []})
mock_get.return_value.__aenter__.return_value = mock_response

@@ -174,3 +182,3 @@ connector = DatadisConnector(MOCK_USERNAME, MOCK_PASSWORD)

"""Test get_supplies with malformed response (missing required fields, syrupy snapshot)."""
malformed = [{"validDateFrom": "2022/03/09"}] # missing 'cups', etc.
malformed = {"supplies": [{"validDateFrom": "2022/03/09"}]} # missing 'cups', etc.
mock_response = MagicMock()

@@ -191,6 +199,6 @@ mock_response.status = 200

"""Test get_supplies with partial valid/invalid response."""
partial = [
SUPPLIES_RESPONSE[0],
partial = {"supplies": [
SUPPLIES_RESPONSE["supplies"][0],
{"validDateFrom": "2022/03/09"}, # invalid
]
]}
mock_response = MagicMock()

@@ -251,16 +259,18 @@ mock_response.status = 200

"""Test get_supplies with optional fields as None."""
response = [
{
"cups": "ESXXXXXXXXXXXXXXXXTEST",
"validDateFrom": "2022/03/09",
"validDateTo": "2022/10/28",
"address": None,
"postalCode": None,
"province": None,
"municipality": None,
"distributor": None,
"pointType": 5,
"distributorCode": "2",
}
]
response = {
"supplies": [
{
"cups": "ESXXXXXXXXXXXXXXXXTEST",
"validDateFrom": "2022/03/09",
"validDateTo": "2022/10/28",
"address": None,
"postalCode": None,
"province": None,
"municipality": None,
"distributor": None,
"pointType": 5,
"distributorCode": "2",
}
]
}
mock_response = MagicMock()

@@ -267,0 +277,0 @@ mock_response.status = 200

Metadata-Version: 2.4
Name: e-data
Version: 2.0.0.dev112
Version: 2.0.0.dev120
Summary: Python library for managing spanish energy data from various web providers

@@ -826,3 +826,3 @@ Author-email: VMG <vmayorg@outlook.es>

print(f"Coste total: {total_cost:.2f} €")
print(f"Consumo total: {sum(e.value_kWh for e in energy):.2f} kWh")
print(f"Consumo total: {sum(e.value_kwh for e in energy):.2f} kWh")

@@ -845,3 +845,3 @@ # Ejecutar

# Actualizar facturación con tarifa fija
python -m edata.cli update-bill \
python -m edata.cli update-custom-bill \
--cups ES0000000000000000XX \

@@ -848,0 +848,0 @@ --p1-kw-year-eur 30.67 \

@@ -7,3 +7,3 @@ [build-system]

name = "e-data"
version = "2.0.0.dev112"
version = "2.0.0.dev120"
description = "Python library for managing spanish energy data from various web providers"

@@ -10,0 +10,0 @@ readme = "README.md"

@@ -122,3 +122,3 @@ [![Downloads](https://pepy.tech/badge/e-data)](https://pepy.tech/project/e-data)

print(f"Coste total: {total_cost:.2f} €")
print(f"Consumo total: {sum(e.value_kWh for e in energy):.2f} kWh")
print(f"Consumo total: {sum(e.value_kwh for e in energy):.2f} kWh")

@@ -141,3 +141,3 @@ # Ejecutar

# Actualizar facturación con tarifa fija
python -m edata.cli update-bill \
python -m edata.cli update-custom-bill \
--cups ES0000000000000000XX \

@@ -144,0 +144,0 @@ --p1-kw-year-eur 30.67 \