gazpar2haws 0.2.0b1__py3-none-any.whl → 0.3.0__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.
- gazpar2haws/__main__.py +14 -18
- gazpar2haws/bridge.py +19 -16
- gazpar2haws/config_utils.py +10 -6
- gazpar2haws/configuration.py +28 -0
- gazpar2haws/date_array.py +259 -0
- gazpar2haws/gazpar.py +246 -106
- gazpar2haws/haws.py +10 -28
- gazpar2haws/model.py +235 -0
- gazpar2haws/pricer.py +579 -0
- gazpar2haws-0.3.0.dist-info/METADATA +541 -0
- gazpar2haws-0.3.0.dist-info/RECORD +15 -0
- {gazpar2haws-0.2.0b1.dist-info → gazpar2haws-0.3.0.dist-info}/WHEEL +1 -1
- gazpar2haws-0.2.0b1.dist-info/METADATA +0 -278
- gazpar2haws-0.2.0b1.dist-info/RECORD +0 -11
- {gazpar2haws-0.2.0b1.dist-info → gazpar2haws-0.3.0.dist-info}/LICENSE +0 -0
gazpar2haws/model.py
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
from datetime import date
|
2
|
+
from enum import Enum
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Generic, Optional, TypeVar
|
5
|
+
|
6
|
+
from pydantic import BaseModel, DirectoryPath, EmailStr, SecretStr, model_validator
|
7
|
+
from pydantic_extra_types.timezone_name import TimeZoneName
|
8
|
+
|
9
|
+
from gazpar2haws.date_array import DateArray
|
10
|
+
|
11
|
+
|
12
|
+
# ----------------------------------
|
13
|
+
class LoggingLevel(str, Enum):
|
14
|
+
DEBUG = "debug"
|
15
|
+
INFO = "info"
|
16
|
+
WARNING = "warning"
|
17
|
+
ERROR = "error"
|
18
|
+
CRITICAL = "critical"
|
19
|
+
|
20
|
+
|
21
|
+
# ----------------------------------
|
22
|
+
class TimeUnit(str, Enum):
|
23
|
+
DAY = "day"
|
24
|
+
WEEK = "week"
|
25
|
+
MONTH = "month"
|
26
|
+
YEAR = "year"
|
27
|
+
|
28
|
+
|
29
|
+
# ----------------------------------
|
30
|
+
class PriceUnit(str, Enum):
|
31
|
+
EURO = "€"
|
32
|
+
CENT = "¢"
|
33
|
+
|
34
|
+
|
35
|
+
# ----------------------------------
|
36
|
+
class QuantityUnit(str, Enum):
|
37
|
+
MWH = "MWh"
|
38
|
+
KWH = "kWh"
|
39
|
+
WH = "Wh"
|
40
|
+
M3 = "m³"
|
41
|
+
LITER = "l"
|
42
|
+
|
43
|
+
|
44
|
+
# ----------------------------------
|
45
|
+
class Logging(BaseModel):
|
46
|
+
file: str
|
47
|
+
console: bool
|
48
|
+
level: LoggingLevel
|
49
|
+
format: str
|
50
|
+
|
51
|
+
|
52
|
+
# ----------------------------------
|
53
|
+
class Device(BaseModel):
|
54
|
+
name: str
|
55
|
+
data_source: str = "json"
|
56
|
+
tmp_dir: DirectoryPath = DirectoryPath("/tmp")
|
57
|
+
as_of_date: date = date.today()
|
58
|
+
username: Optional[EmailStr] = None
|
59
|
+
password: Optional[SecretStr] = None
|
60
|
+
pce_identifier: Optional[SecretStr] = None
|
61
|
+
timezone: TimeZoneName = TimeZoneName("Europe/Paris")
|
62
|
+
last_days: int = 365
|
63
|
+
reset: bool = False
|
64
|
+
|
65
|
+
@model_validator(mode="after")
|
66
|
+
def validate_properties(self):
|
67
|
+
if self.data_source not in ["json", "excel", "test"]:
|
68
|
+
raise ValueError(f"Invalid data_source{self.data_source} (expected values: json, excel, test)")
|
69
|
+
if self.data_source != "test" and self.username is None:
|
70
|
+
raise ValueError("Missing username")
|
71
|
+
if self.data_source != "test" and self.password is None:
|
72
|
+
raise ValueError("Missing password")
|
73
|
+
if self.data_source != "test" and self.pce_identifier is None:
|
74
|
+
raise ValueError("Missing pce_identifier")
|
75
|
+
if self.data_source == "excel" and self.tmp_dir is None or not Path(self.tmp_dir).is_dir():
|
76
|
+
raise ValueError(f"Invalid tmp_dir {self.tmp_dir}")
|
77
|
+
return self
|
78
|
+
|
79
|
+
|
80
|
+
# ----------------------------------
|
81
|
+
class Grdf(BaseModel):
|
82
|
+
scan_interval: Optional[int] = 480
|
83
|
+
devices: list[Device]
|
84
|
+
|
85
|
+
|
86
|
+
# ----------------------------------
|
87
|
+
class HomeAssistant(BaseModel):
|
88
|
+
host: str
|
89
|
+
port: int
|
90
|
+
endpoint: str = "/api/websocket"
|
91
|
+
token: SecretStr
|
92
|
+
|
93
|
+
|
94
|
+
# ----------------------------------
|
95
|
+
class Period(BaseModel):
|
96
|
+
start_date: date
|
97
|
+
end_date: Optional[date] = None
|
98
|
+
|
99
|
+
|
100
|
+
# ----------------------------------
|
101
|
+
class Value(Period):
|
102
|
+
value: float
|
103
|
+
|
104
|
+
|
105
|
+
# ----------------------------------
|
106
|
+
class ValueArray(Period):
|
107
|
+
name: Optional[str] = None
|
108
|
+
value_array: Optional[DateArray] = None
|
109
|
+
|
110
|
+
@model_validator(mode="after")
|
111
|
+
def set_value_array(self):
|
112
|
+
if self.value_array is None:
|
113
|
+
self.value_array = DateArray(
|
114
|
+
name=self.name, start_date=self.start_date, end_date=self.end_date
|
115
|
+
) # pylint: disable=attribute-defined-outside-init
|
116
|
+
return self
|
117
|
+
|
118
|
+
|
119
|
+
# ----------------------------------
|
120
|
+
class Vat(BaseModel):
|
121
|
+
id: str
|
122
|
+
|
123
|
+
|
124
|
+
# ----------------------------------
|
125
|
+
class VatRate(Vat, Value):
|
126
|
+
pass
|
127
|
+
|
128
|
+
|
129
|
+
# ----------------------------------
|
130
|
+
class VatRateArray(Vat, ValueArray):
|
131
|
+
pass
|
132
|
+
|
133
|
+
|
134
|
+
# ----------------------------------
|
135
|
+
# Define type variables
|
136
|
+
ValueUnit = TypeVar("ValueUnit")
|
137
|
+
BaseUnit = TypeVar("BaseUnit")
|
138
|
+
|
139
|
+
|
140
|
+
# ----------------------------------
|
141
|
+
class Unit(BaseModel, Generic[ValueUnit, BaseUnit]):
|
142
|
+
value_unit: Optional[ValueUnit] = None
|
143
|
+
base_unit: Optional[BaseUnit] = None
|
144
|
+
|
145
|
+
|
146
|
+
# ----------------------------------
|
147
|
+
class Price(Unit[ValueUnit, BaseUnit]): # pylint: disable=too-few-public-methods
|
148
|
+
vat_id: Optional[str] = None
|
149
|
+
|
150
|
+
|
151
|
+
# ----------------------------------
|
152
|
+
class PriceValue(Price[ValueUnit, BaseUnit], Value):
|
153
|
+
pass
|
154
|
+
|
155
|
+
|
156
|
+
# ----------------------------------
|
157
|
+
class PriceValueArray(Price[ValueUnit, BaseUnit], ValueArray):
|
158
|
+
pass
|
159
|
+
|
160
|
+
|
161
|
+
# ----------------------------------
|
162
|
+
class ConsumptionPriceArray(PriceValueArray[PriceUnit, QuantityUnit]): # pylint: disable=too-few-public-methods
|
163
|
+
pass
|
164
|
+
|
165
|
+
|
166
|
+
# ----------------------------------
|
167
|
+
class SubscriptionPriceArray(PriceValueArray[PriceUnit, TimeUnit]): # pylint: disable=too-few-public-methods
|
168
|
+
pass
|
169
|
+
|
170
|
+
|
171
|
+
# ----------------------------------
|
172
|
+
class TransportPriceArray(PriceValueArray[PriceUnit, TimeUnit]): # pylint: disable=too-few-public-methods
|
173
|
+
pass
|
174
|
+
|
175
|
+
|
176
|
+
# ----------------------------------
|
177
|
+
class EnergyTaxesPriceArray(PriceValueArray[PriceUnit, QuantityUnit]): # pylint: disable=too-few-public-methods
|
178
|
+
pass
|
179
|
+
|
180
|
+
|
181
|
+
# ----------------------------------
|
182
|
+
class Pricing(BaseModel):
|
183
|
+
vat: Optional[list[VatRate]] = None
|
184
|
+
consumption_prices: list[PriceValue[PriceUnit, QuantityUnit]]
|
185
|
+
subscription_prices: Optional[list[PriceValue[PriceUnit, TimeUnit]]] = None
|
186
|
+
transport_prices: Optional[list[PriceValue[PriceUnit, TimeUnit]]] = None
|
187
|
+
energy_taxes: Optional[list[PriceValue[PriceUnit, QuantityUnit]]] = None
|
188
|
+
|
189
|
+
@model_validator(mode="before")
|
190
|
+
@classmethod
|
191
|
+
def propagates_properties(cls, values):
|
192
|
+
for price_list in [
|
193
|
+
"consumption_prices",
|
194
|
+
"subscription_prices",
|
195
|
+
"transport_prices",
|
196
|
+
"energy_taxes",
|
197
|
+
]:
|
198
|
+
prices = values.get(price_list, [])
|
199
|
+
|
200
|
+
if len(prices) == 0:
|
201
|
+
continue
|
202
|
+
|
203
|
+
if "start_date" not in prices[0]:
|
204
|
+
raise ValueError(f"Missing start_date in first element of {price_list}")
|
205
|
+
if "value_unit" not in prices[0]:
|
206
|
+
prices[0]["value_unit"] = "€"
|
207
|
+
if "base_unit" not in prices[0]:
|
208
|
+
if price_list in ["consumption_prices", "energy_taxes"]:
|
209
|
+
prices[0]["base_unit"] = "kWh"
|
210
|
+
else:
|
211
|
+
raise ValueError(
|
212
|
+
"Missing base_unit in first element of ['transport_prices', 'subscription_prices']"
|
213
|
+
)
|
214
|
+
|
215
|
+
for i in range(len(prices) - 1):
|
216
|
+
if "end_date" not in prices[i]:
|
217
|
+
prices[i]["end_date"] = prices[i + 1]["start_date"]
|
218
|
+
if "value_unit" not in prices[i + 1]:
|
219
|
+
prices[i + 1]["value_unit"] = prices[i]["value_unit"]
|
220
|
+
if "base_unit" not in prices[i + 1]:
|
221
|
+
prices[i + 1]["base_unit"] = prices[i]["base_unit"]
|
222
|
+
if "vat_id" not in prices[i + 1] and "vat_id" in prices[i]:
|
223
|
+
prices[i + 1]["vat_id"] = prices[i]["vat_id"]
|
224
|
+
|
225
|
+
return values
|
226
|
+
|
227
|
+
|
228
|
+
# ----------------------------------
|
229
|
+
class ConsumptionQuantityArray(Unit[QuantityUnit, TimeUnit], ValueArray):
|
230
|
+
pass
|
231
|
+
|
232
|
+
|
233
|
+
# ----------------------------------
|
234
|
+
class CostArray(Unit[PriceUnit, TimeUnit], ValueArray):
|
235
|
+
pass
|