gazpar2haws 0.2.1__py3-none-any.whl → 0.3.0b15__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 +11 -18
- gazpar2haws/bridge.py +11 -29
- gazpar2haws/config_utils.py +4 -3
- gazpar2haws/configuration.py +28 -0
- gazpar2haws/date_array.py +236 -0
- gazpar2haws/gazpar.py +198 -138
- gazpar2haws/haws.py +10 -28
- gazpar2haws/model.py +234 -0
- gazpar2haws/pricer.py +571 -0
- {gazpar2haws-0.2.1.dist-info → gazpar2haws-0.3.0b15.dist-info}/METADATA +259 -1
- gazpar2haws-0.3.0b15.dist-info/RECORD +15 -0
- gazpar2haws-0.2.1.dist-info/RECORD +0 -11
- {gazpar2haws-0.2.1.dist-info → gazpar2haws-0.3.0b15.dist-info}/LICENSE +0 -0
- {gazpar2haws-0.2.1.dist-info → gazpar2haws-0.3.0b15.dist-info}/WHEEL +0 -0
gazpar2haws/pricer.py
ADDED
@@ -0,0 +1,571 @@
|
|
1
|
+
import calendar
|
2
|
+
from datetime import date, timedelta
|
3
|
+
from typing import Optional, Tuple, overload
|
4
|
+
|
5
|
+
from gazpar2haws.model import (
|
6
|
+
BaseUnit,
|
7
|
+
ConsumptionPriceArray,
|
8
|
+
ConsumptionQuantityArray,
|
9
|
+
CostArray,
|
10
|
+
EnergyTaxesPriceArray,
|
11
|
+
PriceUnit,
|
12
|
+
PriceValue,
|
13
|
+
Pricing,
|
14
|
+
QuantityUnit,
|
15
|
+
SubscriptionPriceArray,
|
16
|
+
TimeUnit,
|
17
|
+
TransportPriceArray,
|
18
|
+
Value,
|
19
|
+
ValueArray,
|
20
|
+
ValueUnit,
|
21
|
+
VatRate,
|
22
|
+
VatRateArray,
|
23
|
+
)
|
24
|
+
|
25
|
+
|
26
|
+
class Pricer:
|
27
|
+
|
28
|
+
# ----------------------------------
|
29
|
+
def __init__(self, pricing: Pricing):
|
30
|
+
self._pricing = pricing
|
31
|
+
|
32
|
+
# ----------------------------------
|
33
|
+
def pricing_data(self) -> Pricing:
|
34
|
+
return self._pricing
|
35
|
+
|
36
|
+
# ----------------------------------
|
37
|
+
def compute( # pylint: disable=too-many-branches
|
38
|
+
self, quantities: ConsumptionQuantityArray, price_unit: PriceUnit
|
39
|
+
) -> CostArray:
|
40
|
+
|
41
|
+
if quantities is None:
|
42
|
+
raise ValueError("quantities is None")
|
43
|
+
|
44
|
+
if quantities.start_date is None:
|
45
|
+
raise ValueError("quantities.start_date is None")
|
46
|
+
|
47
|
+
start_date = quantities.start_date
|
48
|
+
|
49
|
+
if quantities.end_date is None:
|
50
|
+
raise ValueError("quantities.end_date is None")
|
51
|
+
|
52
|
+
end_date = quantities.end_date
|
53
|
+
|
54
|
+
if quantities.value_array is None:
|
55
|
+
raise ValueError("quantities.value_array is None")
|
56
|
+
|
57
|
+
if quantities.value_unit is None:
|
58
|
+
raise ValueError("quantities.value_unit is None")
|
59
|
+
|
60
|
+
if quantities.base_unit is None:
|
61
|
+
raise ValueError("quantities.base_unit is None")
|
62
|
+
|
63
|
+
quantity_array = quantities.value_array
|
64
|
+
|
65
|
+
# Convert all pricing data to the same unit as the quantities.
|
66
|
+
consumption_prices = Pricer.convert(self._pricing.consumption_prices, (price_unit, quantities.value_unit))
|
67
|
+
|
68
|
+
if self._pricing.subscription_prices is not None and len(self._pricing.subscription_prices) > 0:
|
69
|
+
subscription_prices = Pricer.convert(self._pricing.subscription_prices, (price_unit, quantities.base_unit))
|
70
|
+
else:
|
71
|
+
subscription_prices = None
|
72
|
+
|
73
|
+
if self._pricing.transport_prices is not None and len(self._pricing.transport_prices) > 0:
|
74
|
+
transport_prices = Pricer.convert(self._pricing.transport_prices, (price_unit, quantities.base_unit))
|
75
|
+
else:
|
76
|
+
transport_prices = None
|
77
|
+
|
78
|
+
if self._pricing.energy_taxes is not None and len(self._pricing.energy_taxes) > 0:
|
79
|
+
energy_taxes = Pricer.convert(self._pricing.energy_taxes, (price_unit, quantities.value_unit))
|
80
|
+
else:
|
81
|
+
energy_taxes = None
|
82
|
+
|
83
|
+
# Transform to the vectorized form.
|
84
|
+
if self._pricing.vat is not None and len(self._pricing.vat) > 0:
|
85
|
+
vat_rate_array_by_id = self.get_vat_rate_array_by_id(
|
86
|
+
start_date=start_date, end_date=end_date, vat_rates=self._pricing.vat
|
87
|
+
)
|
88
|
+
else:
|
89
|
+
vat_rate_array_by_id = dict[str, VatRateArray]()
|
90
|
+
|
91
|
+
consumption_price_array = self.get_consumption_price_array(
|
92
|
+
start_date=start_date,
|
93
|
+
end_date=end_date,
|
94
|
+
consumption_prices=consumption_prices,
|
95
|
+
vat_rate_array_by_id=vat_rate_array_by_id,
|
96
|
+
)
|
97
|
+
|
98
|
+
# Subscription price is optional.
|
99
|
+
if subscription_prices is not None and len(subscription_prices) > 0:
|
100
|
+
subscription_price_array = self.get_subscription_price_array(
|
101
|
+
start_date=start_date,
|
102
|
+
end_date=end_date,
|
103
|
+
subscription_prices=subscription_prices,
|
104
|
+
vat_rate_array_by_id=vat_rate_array_by_id,
|
105
|
+
)
|
106
|
+
else:
|
107
|
+
subscription_price_array = SubscriptionPriceArray(
|
108
|
+
start_date=start_date,
|
109
|
+
end_date=end_date,
|
110
|
+
value_unit=price_unit,
|
111
|
+
base_unit=quantities.base_unit,
|
112
|
+
)
|
113
|
+
|
114
|
+
# Transport price is optional.
|
115
|
+
if transport_prices is not None and len(transport_prices) > 0:
|
116
|
+
transport_price_array = self.get_transport_price_array(
|
117
|
+
start_date=start_date,
|
118
|
+
end_date=end_date,
|
119
|
+
transport_prices=transport_prices,
|
120
|
+
vat_rate_array_by_id=vat_rate_array_by_id,
|
121
|
+
)
|
122
|
+
else:
|
123
|
+
transport_price_array = TransportPriceArray(
|
124
|
+
start_date=start_date,
|
125
|
+
end_date=end_date,
|
126
|
+
value_unit=price_unit,
|
127
|
+
base_unit=quantities.base_unit,
|
128
|
+
)
|
129
|
+
|
130
|
+
# Energy taxes are optional.
|
131
|
+
if energy_taxes is not None and len(energy_taxes) > 0:
|
132
|
+
energy_taxes_price_array = self.get_energy_taxes_price_array(
|
133
|
+
start_date=start_date,
|
134
|
+
end_date=end_date,
|
135
|
+
energy_taxes_prices=energy_taxes,
|
136
|
+
vat_rate_array_by_id=vat_rate_array_by_id,
|
137
|
+
)
|
138
|
+
else:
|
139
|
+
energy_taxes_price_array = EnergyTaxesPriceArray(
|
140
|
+
start_date=start_date,
|
141
|
+
end_date=end_date,
|
142
|
+
value_unit=price_unit,
|
143
|
+
base_unit=quantities.value_unit,
|
144
|
+
)
|
145
|
+
|
146
|
+
res = CostArray(
|
147
|
+
start_date=start_date,
|
148
|
+
end_date=end_date,
|
149
|
+
value_unit=price_unit,
|
150
|
+
base_unit=quantities.base_unit,
|
151
|
+
)
|
152
|
+
|
153
|
+
# Compute pricing formula
|
154
|
+
res.value_array = quantity_array * (consumption_price_array.value_array + energy_taxes_price_array.value_array) + subscription_price_array.value_array + transport_price_array.value_array # type: ignore
|
155
|
+
|
156
|
+
return res
|
157
|
+
|
158
|
+
# ----------------------------------
|
159
|
+
@classmethod
|
160
|
+
def get_vat_rate_array_by_id(
|
161
|
+
cls, start_date: date, end_date: date, vat_rates: list[VatRate]
|
162
|
+
) -> dict[str, VatRateArray]:
|
163
|
+
|
164
|
+
if vat_rates is None or len(vat_rates) == 0:
|
165
|
+
raise ValueError("vat_rates is None or empty")
|
166
|
+
|
167
|
+
res = dict[str, VatRateArray]()
|
168
|
+
vat_rate_by_id = dict[str, list[VatRate]]()
|
169
|
+
for vat_rate in vat_rates:
|
170
|
+
res[vat_rate.id] = VatRateArray(id=vat_rate.id, start_date=start_date, end_date=end_date)
|
171
|
+
if vat_rate.id not in vat_rate_by_id:
|
172
|
+
vat_rate_by_id[vat_rate.id] = list[VatRate]()
|
173
|
+
vat_rate_by_id[vat_rate.id].append(vat_rate)
|
174
|
+
|
175
|
+
for vat_id, vat_rate_list in vat_rate_by_id.items():
|
176
|
+
cls._fill_value_array(res[vat_id], vat_rate_list) # type: ignore
|
177
|
+
|
178
|
+
return res
|
179
|
+
|
180
|
+
# ----------------------------------
|
181
|
+
@classmethod
|
182
|
+
def get_consumption_price_array(
|
183
|
+
cls,
|
184
|
+
start_date: date,
|
185
|
+
end_date: date,
|
186
|
+
consumption_prices: list[PriceValue[PriceUnit, QuantityUnit]],
|
187
|
+
vat_rate_array_by_id: dict[str, VatRateArray],
|
188
|
+
) -> ConsumptionPriceArray:
|
189
|
+
|
190
|
+
if consumption_prices is None or len(consumption_prices) == 0:
|
191
|
+
raise ValueError("consumption_prices is None or empty")
|
192
|
+
|
193
|
+
first_consumption_price = consumption_prices[0]
|
194
|
+
|
195
|
+
res = ConsumptionPriceArray(
|
196
|
+
start_date=start_date,
|
197
|
+
end_date=end_date,
|
198
|
+
value_unit=first_consumption_price.value_unit,
|
199
|
+
base_unit=first_consumption_price.base_unit,
|
200
|
+
vat_id=first_consumption_price.vat_id,
|
201
|
+
)
|
202
|
+
|
203
|
+
cls._fill_price_array(res, consumption_prices, vat_rate_array_by_id) # type: ignore
|
204
|
+
|
205
|
+
return res
|
206
|
+
|
207
|
+
# ----------------------------------
|
208
|
+
@classmethod
|
209
|
+
def get_subscription_price_array(
|
210
|
+
cls,
|
211
|
+
start_date: date,
|
212
|
+
end_date: date,
|
213
|
+
subscription_prices: list[PriceValue[PriceUnit, TimeUnit]],
|
214
|
+
vat_rate_array_by_id: dict[str, VatRateArray],
|
215
|
+
) -> SubscriptionPriceArray:
|
216
|
+
|
217
|
+
if subscription_prices is None or len(subscription_prices) == 0:
|
218
|
+
raise ValueError("subscription_prices is None or empty")
|
219
|
+
|
220
|
+
first_subscription_price = subscription_prices[0]
|
221
|
+
|
222
|
+
res = SubscriptionPriceArray(
|
223
|
+
start_date=start_date,
|
224
|
+
end_date=end_date,
|
225
|
+
value_unit=first_subscription_price.value_unit,
|
226
|
+
base_unit=first_subscription_price.base_unit,
|
227
|
+
vat_id=first_subscription_price.vat_id,
|
228
|
+
)
|
229
|
+
|
230
|
+
cls._fill_price_array(res, subscription_prices, vat_rate_array_by_id) # type: ignore
|
231
|
+
|
232
|
+
return res
|
233
|
+
|
234
|
+
# ----------------------------------
|
235
|
+
@classmethod
|
236
|
+
def get_transport_price_array(
|
237
|
+
cls,
|
238
|
+
start_date: date,
|
239
|
+
end_date: date,
|
240
|
+
transport_prices: list[PriceValue[PriceUnit, TimeUnit]],
|
241
|
+
vat_rate_array_by_id: dict[str, VatRateArray],
|
242
|
+
) -> TransportPriceArray:
|
243
|
+
|
244
|
+
if transport_prices is None or len(transport_prices) == 0:
|
245
|
+
raise ValueError("transport_prices is None or empty")
|
246
|
+
|
247
|
+
first_transport_price = transport_prices[0]
|
248
|
+
|
249
|
+
res = TransportPriceArray(
|
250
|
+
start_date=start_date,
|
251
|
+
end_date=end_date,
|
252
|
+
value_unit=first_transport_price.value_unit,
|
253
|
+
base_unit=first_transport_price.base_unit,
|
254
|
+
vat_id=first_transport_price.vat_id,
|
255
|
+
)
|
256
|
+
|
257
|
+
cls._fill_price_array(res, transport_prices, vat_rate_array_by_id) # type: ignore
|
258
|
+
|
259
|
+
return res
|
260
|
+
|
261
|
+
# ----------------------------------
|
262
|
+
@classmethod
|
263
|
+
def get_energy_taxes_price_array(
|
264
|
+
cls,
|
265
|
+
start_date: date,
|
266
|
+
end_date: date,
|
267
|
+
energy_taxes_prices: list[PriceValue[PriceUnit, QuantityUnit]],
|
268
|
+
vat_rate_array_by_id: dict[str, VatRateArray],
|
269
|
+
) -> EnergyTaxesPriceArray:
|
270
|
+
|
271
|
+
if energy_taxes_prices is None or len(energy_taxes_prices) == 0:
|
272
|
+
raise ValueError("energy_taxes_prices is None or empty")
|
273
|
+
|
274
|
+
first_energy_taxes_price = energy_taxes_prices[0]
|
275
|
+
|
276
|
+
res = EnergyTaxesPriceArray(
|
277
|
+
start_date=start_date,
|
278
|
+
end_date=end_date,
|
279
|
+
value_unit=first_energy_taxes_price.value_unit,
|
280
|
+
base_unit=first_energy_taxes_price.base_unit,
|
281
|
+
vat_id=first_energy_taxes_price.vat_id,
|
282
|
+
)
|
283
|
+
|
284
|
+
cls._fill_price_array(res, energy_taxes_prices, vat_rate_array_by_id) # type: ignore
|
285
|
+
|
286
|
+
return res
|
287
|
+
|
288
|
+
# ----------------------------------
|
289
|
+
@classmethod
|
290
|
+
def _fill_value_array(cls, out_value_array: ValueArray, in_values: list[Value]) -> None:
|
291
|
+
|
292
|
+
if out_value_array is None:
|
293
|
+
raise ValueError("out_value_array is None")
|
294
|
+
|
295
|
+
if out_value_array.start_date is None:
|
296
|
+
raise ValueError("out_value_array.start_date is None")
|
297
|
+
|
298
|
+
start_date = out_value_array.start_date
|
299
|
+
|
300
|
+
if out_value_array.end_date is None:
|
301
|
+
raise ValueError("out_value_array.end_date is None")
|
302
|
+
|
303
|
+
end_date = out_value_array.end_date
|
304
|
+
|
305
|
+
if out_value_array.value_array is None:
|
306
|
+
raise ValueError("out_value_array.value_array is None")
|
307
|
+
|
308
|
+
value_array = out_value_array.value_array
|
309
|
+
|
310
|
+
if in_values is None or len(in_values) == 0:
|
311
|
+
raise ValueError("in_values is None or empty")
|
312
|
+
|
313
|
+
first_value = in_values[0]
|
314
|
+
last_value = in_values[-1]
|
315
|
+
|
316
|
+
if first_value.start_date > end_date:
|
317
|
+
# Fully before first value period.
|
318
|
+
value_array[start_date:end_date] = first_value.value # type: ignore
|
319
|
+
elif last_value.end_date is not None and last_value.end_date < start_date:
|
320
|
+
# Fully after last value period.
|
321
|
+
value_array[start_date:end_date] = last_value.value # type: ignore
|
322
|
+
else:
|
323
|
+
if start_date < first_value.start_date:
|
324
|
+
# Partially before first value period.
|
325
|
+
value_array[start_date : first_value.start_date] = first_value.value # type: ignore
|
326
|
+
if last_value.end_date is not None and end_date > last_value.end_date:
|
327
|
+
# Partially after last value period.
|
328
|
+
value_array[last_value.end_date : end_date] = last_value.value # type: ignore
|
329
|
+
# Inside value periods.
|
330
|
+
for value in in_values:
|
331
|
+
latest_start = max(value.start_date, start_date)
|
332
|
+
earliest_end = min(value.end_date if value.end_date is not None else end_date, end_date)
|
333
|
+
current_date = latest_start
|
334
|
+
while current_date <= earliest_end:
|
335
|
+
value_array[current_date] = value.value
|
336
|
+
current_date += timedelta(days=1)
|
337
|
+
|
338
|
+
# ----------------------------------
|
339
|
+
@classmethod
|
340
|
+
def _fill_price_array( # pylint: disable=too-many-branches
|
341
|
+
cls,
|
342
|
+
out_value_array: ValueArray,
|
343
|
+
in_values: list[PriceValue],
|
344
|
+
vat_rate_array_by_id: dict[str, VatRateArray],
|
345
|
+
) -> None:
|
346
|
+
|
347
|
+
if out_value_array is None:
|
348
|
+
raise ValueError("out_value_array is None")
|
349
|
+
|
350
|
+
if out_value_array.start_date is None:
|
351
|
+
raise ValueError("out_value_array.start_date is None")
|
352
|
+
|
353
|
+
start_date = out_value_array.start_date
|
354
|
+
|
355
|
+
if out_value_array.end_date is None:
|
356
|
+
raise ValueError("out_value_array.end_date is None")
|
357
|
+
|
358
|
+
end_date = out_value_array.end_date
|
359
|
+
|
360
|
+
if out_value_array.value_array is None:
|
361
|
+
raise ValueError("out_value_array.value_array is None")
|
362
|
+
|
363
|
+
value_array = out_value_array.value_array
|
364
|
+
|
365
|
+
if in_values is None or len(in_values) == 0:
|
366
|
+
raise ValueError("in_values is None or empty")
|
367
|
+
|
368
|
+
first_value = in_values[0]
|
369
|
+
last_value = in_values[-1]
|
370
|
+
|
371
|
+
if first_value.start_date > end_date:
|
372
|
+
# Fully before first value period.
|
373
|
+
if vat_rate_array_by_id is not None and first_value.vat_id in vat_rate_array_by_id:
|
374
|
+
vat_value = vat_rate_array_by_id[first_value.vat_id].value_array[start_date:end_date] # type: ignore
|
375
|
+
else:
|
376
|
+
vat_value = 0.0
|
377
|
+
value_array[start_date:end_date] = first_value.value * (1 + vat_value) # type: ignore
|
378
|
+
elif last_value.end_date is not None and last_value.end_date < start_date:
|
379
|
+
# Fully after last value period.
|
380
|
+
if vat_rate_array_by_id is not None and last_value.vat_id in vat_rate_array_by_id:
|
381
|
+
vat_value = vat_rate_array_by_id[last_value.vat_id].value_array[start_date:end_date] # type: ignore
|
382
|
+
else:
|
383
|
+
vat_value = 0.0
|
384
|
+
value_array[start_date:end_date] = last_value.value * (1 + vat_value) # type: ignore
|
385
|
+
else:
|
386
|
+
if start_date < first_value.start_date:
|
387
|
+
# Partially before first value period.
|
388
|
+
if vat_rate_array_by_id is not None and first_value.vat_id in vat_rate_array_by_id:
|
389
|
+
vat_value = vat_rate_array_by_id[first_value.vat_id].value_array[start_date : first_value.start_date] # type: ignore
|
390
|
+
else:
|
391
|
+
vat_value = 0.0
|
392
|
+
value_array[start_date : first_value.start_date] = first_value.value * (1 + vat_value) # type: ignore
|
393
|
+
if last_value.end_date is not None and end_date > last_value.end_date:
|
394
|
+
# Partially after last value period.
|
395
|
+
if vat_rate_array_by_id is not None and last_value.vat_id in vat_rate_array_by_id:
|
396
|
+
vat_value = vat_rate_array_by_id[last_value.vat_id].value_array[last_value.end_date : end_date] # type: ignore
|
397
|
+
else:
|
398
|
+
vat_value = 0.0
|
399
|
+
value_array[last_value.end_date : end_date] = last_value.value * (1 + vat_value) # type: ignore
|
400
|
+
# Inside value periods.
|
401
|
+
for value in in_values:
|
402
|
+
latest_start = max(value.start_date, start_date)
|
403
|
+
earliest_end = min(value.end_date if value.end_date is not None else end_date, end_date)
|
404
|
+
current_date = latest_start
|
405
|
+
while current_date <= earliest_end:
|
406
|
+
if vat_rate_array_by_id is not None and value.vat_id in vat_rate_array_by_id:
|
407
|
+
vat_value = vat_rate_array_by_id[value.vat_id].value_array[current_date] # type: ignore
|
408
|
+
else:
|
409
|
+
vat_value = 0.0
|
410
|
+
value_array[current_date] = value.value * (1 + vat_value) # type: ignore
|
411
|
+
current_date += timedelta(days=1)
|
412
|
+
|
413
|
+
# ----------------------------------
|
414
|
+
@classmethod
|
415
|
+
def get_time_unit_convertion_factor(cls, from_time_unit: TimeUnit, to_time_unit: TimeUnit, dt: date) -> float:
|
416
|
+
|
417
|
+
if from_time_unit == to_time_unit:
|
418
|
+
return 1.0
|
419
|
+
|
420
|
+
def days_in_month(year: int, month: int) -> int:
|
421
|
+
return calendar.monthrange(year, month)[1]
|
422
|
+
|
423
|
+
def days_in_year(year: int) -> int:
|
424
|
+
return 366 if calendar.isleap(year) else 365
|
425
|
+
|
426
|
+
if TimeUnit.MONTH in (from_time_unit, to_time_unit):
|
427
|
+
switcher = {
|
428
|
+
TimeUnit.DAY: days_in_month(dt.year, dt.month),
|
429
|
+
TimeUnit.WEEK: days_in_month(dt.year, dt.month) / 7.0,
|
430
|
+
TimeUnit.MONTH: 1.0,
|
431
|
+
TimeUnit.YEAR: 1.0 / 12.0,
|
432
|
+
}
|
433
|
+
else:
|
434
|
+
switcher = {
|
435
|
+
TimeUnit.DAY: 1.0,
|
436
|
+
TimeUnit.WEEK: 1 / 7.0,
|
437
|
+
TimeUnit.MONTH: 1 / days_in_month(dt.year, dt.month),
|
438
|
+
TimeUnit.YEAR: 1 / days_in_year(dt.year),
|
439
|
+
}
|
440
|
+
|
441
|
+
if from_time_unit not in switcher:
|
442
|
+
raise ValueError(f"Invalid 'from' time unit: {from_time_unit}")
|
443
|
+
|
444
|
+
if to_time_unit not in switcher:
|
445
|
+
raise ValueError(f"Invalid 'to' time unit: {to_time_unit}")
|
446
|
+
|
447
|
+
return switcher[to_time_unit] / switcher[from_time_unit]
|
448
|
+
|
449
|
+
# ----------------------------------
|
450
|
+
@classmethod
|
451
|
+
def get_price_unit_convertion_factor(cls, from_price_unit: PriceUnit, to_price_unit: PriceUnit) -> float:
|
452
|
+
|
453
|
+
if from_price_unit == to_price_unit:
|
454
|
+
return 1.0
|
455
|
+
|
456
|
+
switcher = {
|
457
|
+
PriceUnit.EURO: 1.0,
|
458
|
+
PriceUnit.CENT: 100.0,
|
459
|
+
}
|
460
|
+
|
461
|
+
if from_price_unit not in switcher:
|
462
|
+
raise ValueError(f"Invalid 'from' price unit: {from_price_unit}")
|
463
|
+
|
464
|
+
if to_price_unit not in switcher:
|
465
|
+
raise ValueError(f"Invalid 'to' price unit: {to_price_unit}")
|
466
|
+
|
467
|
+
return switcher[to_price_unit] / switcher[from_price_unit]
|
468
|
+
|
469
|
+
# ----------------------------------
|
470
|
+
@classmethod
|
471
|
+
def get_quantity_unit_convertion_factor(
|
472
|
+
cls, from_quantity_unit: QuantityUnit, to_quantity_unit: QuantityUnit
|
473
|
+
) -> float:
|
474
|
+
|
475
|
+
if from_quantity_unit == to_quantity_unit:
|
476
|
+
return 1.0
|
477
|
+
|
478
|
+
switcher = {
|
479
|
+
QuantityUnit.WH: 1.0,
|
480
|
+
QuantityUnit.KWH: 0.001,
|
481
|
+
QuantityUnit.MWH: 0.000001,
|
482
|
+
}
|
483
|
+
|
484
|
+
if from_quantity_unit not in switcher:
|
485
|
+
raise ValueError(f"Invalid 'from' quantity unit: {from_quantity_unit}")
|
486
|
+
|
487
|
+
if to_quantity_unit not in switcher:
|
488
|
+
raise ValueError(f"Invalid 'to' quantity unit: {to_quantity_unit}")
|
489
|
+
|
490
|
+
return switcher[to_quantity_unit] / switcher[from_quantity_unit]
|
491
|
+
|
492
|
+
# ----------------------------------
|
493
|
+
@overload
|
494
|
+
@classmethod
|
495
|
+
def get_convertion_factor(
|
496
|
+
cls,
|
497
|
+
from_unit: Tuple[PriceUnit, QuantityUnit],
|
498
|
+
to_unit: Tuple[PriceUnit, QuantityUnit],
|
499
|
+
dt: Optional[date] = None,
|
500
|
+
) -> float: ...
|
501
|
+
|
502
|
+
@overload
|
503
|
+
@classmethod
|
504
|
+
def get_convertion_factor(
|
505
|
+
cls,
|
506
|
+
from_unit: Tuple[PriceUnit, TimeUnit],
|
507
|
+
to_unit: Tuple[PriceUnit, TimeUnit],
|
508
|
+
dt: Optional[date] = None,
|
509
|
+
) -> float: ...
|
510
|
+
|
511
|
+
@classmethod
|
512
|
+
def get_convertion_factor(cls, from_unit, to_unit, dt: Optional[date] = None) -> float:
|
513
|
+
if type(from_unit) is not type(to_unit):
|
514
|
+
raise ValueError(f"from_unit {from_unit} and to_unit {to_unit} must be of the same type")
|
515
|
+
if (
|
516
|
+
isinstance(from_unit, tuple)
|
517
|
+
and isinstance(from_unit[0], PriceUnit)
|
518
|
+
and isinstance(from_unit[1], QuantityUnit)
|
519
|
+
):
|
520
|
+
return cls.get_price_unit_convertion_factor(
|
521
|
+
from_unit[0], to_unit[0]
|
522
|
+
) / cls.get_quantity_unit_convertion_factor(from_unit[1], to_unit[1])
|
523
|
+
if isinstance(from_unit, tuple) and isinstance(from_unit[0], PriceUnit) and isinstance(from_unit[1], TimeUnit):
|
524
|
+
if dt is None:
|
525
|
+
raise ValueError(
|
526
|
+
f"dt must not be None when from_unit {from_unit} and to_unit {to_unit} are of type Tuple[PriceUnit, TimeUnit]"
|
527
|
+
)
|
528
|
+
return cls.get_price_unit_convertion_factor(from_unit[0], to_unit[0]) / cls.get_time_unit_convertion_factor(
|
529
|
+
from_unit[1], to_unit[1], dt
|
530
|
+
)
|
531
|
+
|
532
|
+
raise ValueError(
|
533
|
+
f"from_unit {from_unit} and to_unit {to_unit} must be of type Tuple[PriceUnit, QuantityUnit] or Tuple[PriceUnit, TimeUnit]"
|
534
|
+
)
|
535
|
+
|
536
|
+
# ----------------------------------
|
537
|
+
@classmethod
|
538
|
+
def convert(
|
539
|
+
cls,
|
540
|
+
price_values: list[PriceValue[ValueUnit, BaseUnit]],
|
541
|
+
to_unit: Tuple[ValueUnit, BaseUnit],
|
542
|
+
) -> list[PriceValue[ValueUnit, BaseUnit]]:
|
543
|
+
|
544
|
+
if price_values is None or len(price_values) == 0:
|
545
|
+
raise ValueError("price_values is None or empty")
|
546
|
+
|
547
|
+
if to_unit is None:
|
548
|
+
raise ValueError("to_unit is None")
|
549
|
+
|
550
|
+
res = list[PriceValue[ValueUnit, BaseUnit]]()
|
551
|
+
for price_value in price_values:
|
552
|
+
if price_value.value_unit is None:
|
553
|
+
raise ValueError("price_value.value_unit is None")
|
554
|
+
if price_value.base_unit is None:
|
555
|
+
raise ValueError("price_value.base_unit is None")
|
556
|
+
|
557
|
+
res.append(
|
558
|
+
PriceValue(
|
559
|
+
start_date=price_value.start_date,
|
560
|
+
end_date=price_value.end_date,
|
561
|
+
value=price_value.value
|
562
|
+
* cls.get_convertion_factor(
|
563
|
+
(price_value.value_unit, price_value.base_unit), to_unit, price_value.start_date # type: ignore
|
564
|
+
),
|
565
|
+
value_unit=to_unit[0],
|
566
|
+
base_unit=to_unit[1],
|
567
|
+
vat_id=price_value.vat_id,
|
568
|
+
)
|
569
|
+
)
|
570
|
+
|
571
|
+
return res
|