gazpar2haws 0.3.2__py3-none-any.whl → 0.4.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/datetime_utils.py +104 -0
- gazpar2haws/gazpar.py +127 -19
- gazpar2haws/haws.py +113 -1
- gazpar2haws/model.py +83 -20
- gazpar2haws/pricer.py +309 -143
- {gazpar2haws-0.3.2.dist-info → gazpar2haws-0.4.0.dist-info}/METADATA +234 -72
- gazpar2haws-0.4.0.dist-info/RECORD +16 -0
- {gazpar2haws-0.3.2.dist-info → gazpar2haws-0.4.0.dist-info}/WHEEL +1 -1
- gazpar2haws-0.3.2.dist-info/RECORD +0 -15
- {gazpar2haws-0.3.2.dist-info → gazpar2haws-0.4.0.dist-info/licenses}/LICENSE +0 -0
gazpar2haws/pricer.py
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import calendar
|
|
2
2
|
from datetime import date, timedelta
|
|
3
|
-
from typing import Optional, Tuple, overload
|
|
3
|
+
from typing import Callable, Optional, Tuple, overload
|
|
4
4
|
|
|
5
|
+
from gazpar2haws.date_array import DateArray
|
|
5
6
|
from gazpar2haws.model import (
|
|
6
7
|
BaseUnit,
|
|
7
|
-
|
|
8
|
+
CompositePriceArray,
|
|
9
|
+
CompositePriceValue,
|
|
8
10
|
ConsumptionQuantityArray,
|
|
9
11
|
CostArray,
|
|
10
|
-
|
|
12
|
+
CostBreakdown,
|
|
11
13
|
PriceUnit,
|
|
12
14
|
PriceValue,
|
|
13
15
|
Pricing,
|
|
14
16
|
QuantityUnit,
|
|
15
|
-
SubscriptionPriceArray,
|
|
16
17
|
TimeUnit,
|
|
17
|
-
TransportPriceArray,
|
|
18
18
|
Value,
|
|
19
19
|
ValueArray,
|
|
20
20
|
ValueUnit,
|
|
@@ -36,7 +36,7 @@ class Pricer:
|
|
|
36
36
|
# ----------------------------------
|
|
37
37
|
def compute( # pylint: disable=too-many-branches
|
|
38
38
|
self, quantities: ConsumptionQuantityArray, price_unit: PriceUnit
|
|
39
|
-
) ->
|
|
39
|
+
) -> CostBreakdown:
|
|
40
40
|
|
|
41
41
|
if quantities is None:
|
|
42
42
|
raise ValueError("quantities is None")
|
|
@@ -62,24 +62,6 @@ class Pricer:
|
|
|
62
62
|
|
|
63
63
|
quantity_array = quantities.value_array
|
|
64
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
65
|
# Transform to the vectorized form.
|
|
84
66
|
if self._pricing.vat is not None and len(self._pricing.vat) > 0:
|
|
85
67
|
vat_rate_array_by_id = self.get_vat_rate_array_by_id(
|
|
@@ -88,76 +70,152 @@ class Pricer:
|
|
|
88
70
|
else:
|
|
89
71
|
vat_rate_array_by_id = dict[str, VatRateArray]()
|
|
90
72
|
|
|
91
|
-
|
|
73
|
+
# Use get_composite_price_array for all price types (conversion happens inside)
|
|
74
|
+
consumption_composite = self.get_composite_price_array(
|
|
92
75
|
start_date=start_date,
|
|
93
76
|
end_date=end_date,
|
|
94
|
-
|
|
77
|
+
composite_prices=self._pricing.consumption_prices,
|
|
95
78
|
vat_rate_array_by_id=vat_rate_array_by_id,
|
|
79
|
+
target_price_unit=price_unit,
|
|
80
|
+
target_quantity_unit=quantities.value_unit,
|
|
81
|
+
target_time_unit=quantities.base_unit,
|
|
96
82
|
)
|
|
97
83
|
|
|
98
84
|
# Subscription price is optional.
|
|
99
|
-
if subscription_prices is not None and len(subscription_prices) > 0:
|
|
100
|
-
|
|
85
|
+
if self._pricing.subscription_prices is not None and len(self._pricing.subscription_prices) > 0:
|
|
86
|
+
subscription_composite = self.get_composite_price_array(
|
|
101
87
|
start_date=start_date,
|
|
102
88
|
end_date=end_date,
|
|
103
|
-
|
|
89
|
+
composite_prices=self._pricing.subscription_prices,
|
|
104
90
|
vat_rate_array_by_id=vat_rate_array_by_id,
|
|
91
|
+
target_price_unit=price_unit,
|
|
92
|
+
target_quantity_unit=quantities.value_unit,
|
|
93
|
+
target_time_unit=quantities.base_unit,
|
|
105
94
|
)
|
|
106
95
|
else:
|
|
107
|
-
|
|
96
|
+
subscription_composite = CompositePriceArray(
|
|
108
97
|
name="subscription_prices",
|
|
109
98
|
start_date=start_date,
|
|
110
99
|
end_date=end_date,
|
|
111
|
-
|
|
112
|
-
|
|
100
|
+
price_unit=price_unit,
|
|
101
|
+
quantity_unit=quantities.value_unit,
|
|
102
|
+
time_unit=quantities.base_unit,
|
|
113
103
|
)
|
|
114
104
|
|
|
115
105
|
# Transport price is optional.
|
|
116
|
-
if transport_prices is not None and len(transport_prices) > 0:
|
|
117
|
-
|
|
106
|
+
if self._pricing.transport_prices is not None and len(self._pricing.transport_prices) > 0:
|
|
107
|
+
transport_composite = self.get_composite_price_array(
|
|
118
108
|
start_date=start_date,
|
|
119
109
|
end_date=end_date,
|
|
120
|
-
|
|
110
|
+
composite_prices=self._pricing.transport_prices,
|
|
121
111
|
vat_rate_array_by_id=vat_rate_array_by_id,
|
|
112
|
+
target_price_unit=price_unit,
|
|
113
|
+
target_quantity_unit=quantities.value_unit,
|
|
114
|
+
target_time_unit=quantities.base_unit,
|
|
122
115
|
)
|
|
123
116
|
else:
|
|
124
|
-
|
|
117
|
+
transport_composite = CompositePriceArray(
|
|
125
118
|
name="transport_prices",
|
|
126
119
|
start_date=start_date,
|
|
127
120
|
end_date=end_date,
|
|
128
|
-
|
|
129
|
-
|
|
121
|
+
price_unit=price_unit,
|
|
122
|
+
quantity_unit=quantities.value_unit,
|
|
123
|
+
time_unit=quantities.base_unit,
|
|
130
124
|
)
|
|
131
125
|
|
|
132
126
|
# Energy taxes are optional.
|
|
133
|
-
if energy_taxes is not None and len(energy_taxes) > 0:
|
|
134
|
-
|
|
127
|
+
if self._pricing.energy_taxes is not None and len(self._pricing.energy_taxes) > 0:
|
|
128
|
+
energy_taxes_composite = self.get_composite_price_array(
|
|
135
129
|
start_date=start_date,
|
|
136
130
|
end_date=end_date,
|
|
137
|
-
|
|
131
|
+
composite_prices=self._pricing.energy_taxes,
|
|
138
132
|
vat_rate_array_by_id=vat_rate_array_by_id,
|
|
133
|
+
target_price_unit=price_unit,
|
|
134
|
+
target_quantity_unit=quantities.value_unit,
|
|
135
|
+
target_time_unit=quantities.base_unit,
|
|
139
136
|
)
|
|
140
137
|
else:
|
|
141
|
-
|
|
138
|
+
energy_taxes_composite = CompositePriceArray(
|
|
142
139
|
name="energy_taxes",
|
|
143
140
|
start_date=start_date,
|
|
144
141
|
end_date=end_date,
|
|
145
|
-
|
|
146
|
-
|
|
142
|
+
price_unit=price_unit,
|
|
143
|
+
quantity_unit=quantities.value_unit,
|
|
144
|
+
time_unit=quantities.base_unit,
|
|
147
145
|
)
|
|
148
146
|
|
|
149
|
-
|
|
150
|
-
|
|
147
|
+
# Create individual cost arrays for each component
|
|
148
|
+
consumption_cost = CostArray(
|
|
149
|
+
name="consumption_cost",
|
|
151
150
|
start_date=start_date,
|
|
152
151
|
end_date=end_date,
|
|
153
152
|
value_unit=price_unit,
|
|
154
153
|
base_unit=quantities.base_unit,
|
|
155
154
|
)
|
|
155
|
+
consumption_cost.value_array = (
|
|
156
|
+
quantity_array * consumption_composite.quantity_value_array # type: ignore
|
|
157
|
+
+ consumption_composite.time_value_array # type: ignore
|
|
158
|
+
)
|
|
156
159
|
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
subscription_cost = CostArray(
|
|
161
|
+
name="subscription_cost",
|
|
162
|
+
start_date=start_date,
|
|
163
|
+
end_date=end_date,
|
|
164
|
+
value_unit=price_unit,
|
|
165
|
+
base_unit=quantities.base_unit,
|
|
166
|
+
)
|
|
167
|
+
subscription_cost.value_array = (
|
|
168
|
+
quantity_array * subscription_composite.quantity_value_array # type: ignore
|
|
169
|
+
+ subscription_composite.time_value_array # type: ignore
|
|
170
|
+
)
|
|
159
171
|
|
|
160
|
-
|
|
172
|
+
transport_cost = CostArray(
|
|
173
|
+
name="transport_cost",
|
|
174
|
+
start_date=start_date,
|
|
175
|
+
end_date=end_date,
|
|
176
|
+
value_unit=price_unit,
|
|
177
|
+
base_unit=quantities.base_unit,
|
|
178
|
+
)
|
|
179
|
+
transport_cost.value_array = (
|
|
180
|
+
quantity_array * transport_composite.quantity_value_array # type: ignore
|
|
181
|
+
+ transport_composite.time_value_array # type: ignore
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
energy_taxes_cost = CostArray(
|
|
185
|
+
name="energy_taxes_cost",
|
|
186
|
+
start_date=start_date,
|
|
187
|
+
end_date=end_date,
|
|
188
|
+
value_unit=price_unit,
|
|
189
|
+
base_unit=quantities.base_unit,
|
|
190
|
+
)
|
|
191
|
+
energy_taxes_cost.value_array = (
|
|
192
|
+
quantity_array * energy_taxes_composite.quantity_value_array # type: ignore
|
|
193
|
+
+ energy_taxes_composite.time_value_array # type: ignore
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# Calculate total cost
|
|
197
|
+
total_cost = CostArray(
|
|
198
|
+
name="total_cost",
|
|
199
|
+
start_date=start_date,
|
|
200
|
+
end_date=end_date,
|
|
201
|
+
value_unit=price_unit,
|
|
202
|
+
base_unit=quantities.base_unit,
|
|
203
|
+
)
|
|
204
|
+
total_cost.value_array = (
|
|
205
|
+
consumption_cost.value_array # type: ignore
|
|
206
|
+
+ subscription_cost.value_array # type: ignore
|
|
207
|
+
+ transport_cost.value_array # type: ignore
|
|
208
|
+
+ energy_taxes_cost.value_array # type: ignore
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Return detailed breakdown
|
|
212
|
+
return CostBreakdown(
|
|
213
|
+
consumption=consumption_cost,
|
|
214
|
+
subscription=subscription_cost,
|
|
215
|
+
transport=transport_cost,
|
|
216
|
+
energy_taxes=energy_taxes_cost,
|
|
217
|
+
total=total_cost,
|
|
218
|
+
)
|
|
161
219
|
|
|
162
220
|
# ----------------------------------
|
|
163
221
|
@classmethod
|
|
@@ -183,113 +241,43 @@ class Pricer:
|
|
|
183
241
|
|
|
184
242
|
# ----------------------------------
|
|
185
243
|
@classmethod
|
|
186
|
-
def
|
|
244
|
+
def get_composite_price_array(
|
|
187
245
|
cls,
|
|
188
246
|
start_date: date,
|
|
189
247
|
end_date: date,
|
|
190
|
-
|
|
248
|
+
composite_prices: list[CompositePriceValue],
|
|
191
249
|
vat_rate_array_by_id: dict[str, VatRateArray],
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
250
|
+
target_price_unit: PriceUnit,
|
|
251
|
+
target_quantity_unit: QuantityUnit,
|
|
252
|
+
target_time_unit: TimeUnit,
|
|
253
|
+
) -> CompositePriceArray:
|
|
196
254
|
|
|
197
|
-
|
|
255
|
+
if composite_prices is None or len(composite_prices) == 0:
|
|
256
|
+
raise ValueError("composite_prices is None or empty")
|
|
198
257
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
end_date=end_date,
|
|
203
|
-
value_unit=first_consumption_price.value_unit,
|
|
204
|
-
base_unit=first_consumption_price.base_unit,
|
|
205
|
-
vat_id=first_consumption_price.vat_id,
|
|
258
|
+
# Convert all composite prices to target units
|
|
259
|
+
composite_prices_converted = cls.convert(
|
|
260
|
+
composite_prices, (target_price_unit, target_quantity_unit, target_time_unit)
|
|
206
261
|
)
|
|
207
262
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
return res
|
|
211
|
-
|
|
212
|
-
# ----------------------------------
|
|
213
|
-
@classmethod
|
|
214
|
-
def get_subscription_price_array(
|
|
215
|
-
cls,
|
|
216
|
-
start_date: date,
|
|
217
|
-
end_date: date,
|
|
218
|
-
subscription_prices: list[PriceValue[PriceUnit, TimeUnit]],
|
|
219
|
-
vat_rate_array_by_id: dict[str, VatRateArray],
|
|
220
|
-
) -> SubscriptionPriceArray:
|
|
221
|
-
|
|
222
|
-
if subscription_prices is None or len(subscription_prices) == 0:
|
|
223
|
-
raise ValueError("subscription_prices is None or empty")
|
|
263
|
+
# Get vat_id from first element (after conversion it should all be the same)
|
|
264
|
+
first_composite_price = composite_prices_converted[0]
|
|
224
265
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
res = SubscriptionPriceArray(
|
|
228
|
-
name="subscription_prices",
|
|
266
|
+
res = CompositePriceArray(
|
|
267
|
+
name="composite_prices",
|
|
229
268
|
start_date=start_date,
|
|
230
269
|
end_date=end_date,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
270
|
+
price_unit=target_price_unit,
|
|
271
|
+
quantity_unit=target_quantity_unit,
|
|
272
|
+
time_unit=target_time_unit,
|
|
273
|
+
vat_id=first_composite_price.vat_id,
|
|
234
274
|
)
|
|
235
275
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return res
|
|
239
|
-
|
|
240
|
-
# ----------------------------------
|
|
241
|
-
@classmethod
|
|
242
|
-
def get_transport_price_array(
|
|
243
|
-
cls,
|
|
244
|
-
start_date: date,
|
|
245
|
-
end_date: date,
|
|
246
|
-
transport_prices: list[PriceValue[PriceUnit, TimeUnit]],
|
|
247
|
-
vat_rate_array_by_id: dict[str, VatRateArray],
|
|
248
|
-
) -> TransportPriceArray:
|
|
249
|
-
|
|
250
|
-
if transport_prices is None or len(transport_prices) == 0:
|
|
251
|
-
raise ValueError("transport_prices is None or empty")
|
|
276
|
+
# Fill the quantity component array (if present in the composite prices)
|
|
277
|
+
cls._fill_composite_quantity_array(res, composite_prices_converted, vat_rate_array_by_id)
|
|
252
278
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
res = TransportPriceArray(
|
|
256
|
-
name="transport_prices",
|
|
257
|
-
start_date=start_date,
|
|
258
|
-
end_date=end_date,
|
|
259
|
-
value_unit=first_transport_price.value_unit,
|
|
260
|
-
base_unit=first_transport_price.base_unit,
|
|
261
|
-
vat_id=first_transport_price.vat_id,
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
cls._fill_price_array(res, transport_prices, vat_rate_array_by_id) # type: ignore
|
|
265
|
-
|
|
266
|
-
return res
|
|
267
|
-
|
|
268
|
-
# ----------------------------------
|
|
269
|
-
@classmethod
|
|
270
|
-
def get_energy_taxes_price_array(
|
|
271
|
-
cls,
|
|
272
|
-
start_date: date,
|
|
273
|
-
end_date: date,
|
|
274
|
-
energy_taxes_prices: list[PriceValue[PriceUnit, QuantityUnit]],
|
|
275
|
-
vat_rate_array_by_id: dict[str, VatRateArray],
|
|
276
|
-
) -> EnergyTaxesPriceArray:
|
|
277
|
-
|
|
278
|
-
if energy_taxes_prices is None or len(energy_taxes_prices) == 0:
|
|
279
|
-
raise ValueError("energy_taxes_prices is None or empty")
|
|
280
|
-
|
|
281
|
-
first_energy_taxes_price = energy_taxes_prices[0]
|
|
282
|
-
|
|
283
|
-
res = EnergyTaxesPriceArray(
|
|
284
|
-
name="energy_taxes",
|
|
285
|
-
start_date=start_date,
|
|
286
|
-
end_date=end_date,
|
|
287
|
-
value_unit=first_energy_taxes_price.value_unit,
|
|
288
|
-
base_unit=first_energy_taxes_price.base_unit,
|
|
289
|
-
vat_id=first_energy_taxes_price.vat_id,
|
|
290
|
-
)
|
|
291
|
-
|
|
292
|
-
cls._fill_price_array(res, energy_taxes_prices, vat_rate_array_by_id) # type: ignore
|
|
279
|
+
# Fill the time component array (if present in the composite prices)
|
|
280
|
+
cls._fill_composite_time_array(res, composite_prices_converted, vat_rate_array_by_id)
|
|
293
281
|
|
|
294
282
|
return res
|
|
295
283
|
|
|
@@ -418,6 +406,127 @@ class Pricer:
|
|
|
418
406
|
value_array[current_date] = (vat_value + 1) * value.value # type: ignore
|
|
419
407
|
current_date += timedelta(days=1)
|
|
420
408
|
|
|
409
|
+
# ----------------------------------
|
|
410
|
+
@classmethod
|
|
411
|
+
def _fill_composite_component_array( # pylint: disable=too-many-branches, too-many-statements
|
|
412
|
+
cls,
|
|
413
|
+
out_composite_array: CompositePriceArray,
|
|
414
|
+
in_composite_values: list[CompositePriceValue],
|
|
415
|
+
vat_rate_array_by_id: dict[str, VatRateArray],
|
|
416
|
+
get_array: Callable[[CompositePriceArray], DateArray],
|
|
417
|
+
get_value: Callable[[CompositePriceValue], Optional[float]],
|
|
418
|
+
) -> None:
|
|
419
|
+
"""Generic method to fill either quantity or time component array of a CompositePriceArray."""
|
|
420
|
+
|
|
421
|
+
if out_composite_array is None:
|
|
422
|
+
raise ValueError("out_composite_array is None")
|
|
423
|
+
|
|
424
|
+
if out_composite_array.start_date is None:
|
|
425
|
+
raise ValueError("out_composite_array.start_date is None")
|
|
426
|
+
|
|
427
|
+
start_date = out_composite_array.start_date
|
|
428
|
+
|
|
429
|
+
if out_composite_array.end_date is None:
|
|
430
|
+
raise ValueError("out_composite_array.end_date is None")
|
|
431
|
+
|
|
432
|
+
end_date = out_composite_array.end_date
|
|
433
|
+
|
|
434
|
+
component_array = get_array(out_composite_array)
|
|
435
|
+
if component_array is None:
|
|
436
|
+
raise ValueError("component_array is None")
|
|
437
|
+
|
|
438
|
+
if in_composite_values is None or len(in_composite_values) == 0:
|
|
439
|
+
raise ValueError("in_composite_values is None or empty")
|
|
440
|
+
|
|
441
|
+
first_value = in_composite_values[0]
|
|
442
|
+
last_value = in_composite_values[-1]
|
|
443
|
+
|
|
444
|
+
if first_value.start_date > end_date:
|
|
445
|
+
# Fully before first value period.
|
|
446
|
+
component_value = get_value(first_value)
|
|
447
|
+
if component_value is not None:
|
|
448
|
+
if vat_rate_array_by_id is not None and first_value.vat_id in vat_rate_array_by_id:
|
|
449
|
+
vat_value = vat_rate_array_by_id[first_value.vat_id].value_array[start_date : end_date + timedelta(1)] # type: ignore
|
|
450
|
+
else:
|
|
451
|
+
vat_value = 0.0
|
|
452
|
+
component_array[start_date : end_date + timedelta(1)] = (vat_value + 1) * component_value # type: ignore
|
|
453
|
+
elif last_value.end_date is not None and last_value.end_date < start_date:
|
|
454
|
+
# Fully after last value period.
|
|
455
|
+
component_value = get_value(last_value)
|
|
456
|
+
if component_value is not None:
|
|
457
|
+
if vat_rate_array_by_id is not None and last_value.vat_id in vat_rate_array_by_id:
|
|
458
|
+
vat_value = vat_rate_array_by_id[last_value.vat_id].value_array[start_date : end_date + timedelta(1)] # type: ignore
|
|
459
|
+
else:
|
|
460
|
+
vat_value = 0.0
|
|
461
|
+
component_array[start_date : end_date + timedelta(1)] = (vat_value + 1) * component_value # type: ignore
|
|
462
|
+
else:
|
|
463
|
+
if start_date < first_value.start_date:
|
|
464
|
+
# Partially before first value period.
|
|
465
|
+
component_value = get_value(first_value)
|
|
466
|
+
if component_value is not None:
|
|
467
|
+
if vat_rate_array_by_id is not None and first_value.vat_id in vat_rate_array_by_id:
|
|
468
|
+
vat_value = vat_rate_array_by_id[first_value.vat_id].value_array[start_date : first_value.start_date + timedelta(1)] # type: ignore
|
|
469
|
+
else:
|
|
470
|
+
vat_value = 0.0
|
|
471
|
+
component_array[start_date : first_value.start_date + timedelta(1)] = (vat_value + 1) * component_value # type: ignore
|
|
472
|
+
if last_value.end_date is not None and end_date > last_value.end_date:
|
|
473
|
+
# Partially after last value period.
|
|
474
|
+
component_value = get_value(last_value)
|
|
475
|
+
if component_value is not None:
|
|
476
|
+
if vat_rate_array_by_id is not None and last_value.vat_id in vat_rate_array_by_id:
|
|
477
|
+
vat_value = vat_rate_array_by_id[last_value.vat_id].value_array[last_value.end_date : end_date + timedelta(1)] # type: ignore
|
|
478
|
+
else:
|
|
479
|
+
vat_value = 0.0
|
|
480
|
+
component_array[last_value.end_date : end_date + timedelta(1)] = (vat_value + 1) * component_value # type: ignore
|
|
481
|
+
# Inside value periods.
|
|
482
|
+
for value in in_composite_values:
|
|
483
|
+
component_value = get_value(value)
|
|
484
|
+
if component_value is not None:
|
|
485
|
+
latest_start = max(value.start_date, start_date)
|
|
486
|
+
earliest_end = min(value.end_date if value.end_date is not None else end_date, end_date)
|
|
487
|
+
current_date = latest_start
|
|
488
|
+
while current_date <= earliest_end:
|
|
489
|
+
if vat_rate_array_by_id is not None and value.vat_id in vat_rate_array_by_id:
|
|
490
|
+
vat_value = vat_rate_array_by_id[value.vat_id].value_array[current_date] # type: ignore
|
|
491
|
+
else:
|
|
492
|
+
vat_value = 0.0
|
|
493
|
+
component_array[current_date] = (vat_value + 1) * component_value # type: ignore
|
|
494
|
+
current_date += timedelta(days=1)
|
|
495
|
+
|
|
496
|
+
# ----------------------------------
|
|
497
|
+
@classmethod
|
|
498
|
+
def _fill_composite_quantity_array(
|
|
499
|
+
cls,
|
|
500
|
+
out_composite_array: CompositePriceArray,
|
|
501
|
+
in_composite_values: list[CompositePriceValue],
|
|
502
|
+
vat_rate_array_by_id: dict[str, VatRateArray],
|
|
503
|
+
) -> None:
|
|
504
|
+
"""Fill the quantity component array of a CompositePriceArray."""
|
|
505
|
+
cls._fill_composite_component_array(
|
|
506
|
+
out_composite_array,
|
|
507
|
+
in_composite_values,
|
|
508
|
+
vat_rate_array_by_id,
|
|
509
|
+
lambda arr: arr.quantity_value_array, # type: ignore
|
|
510
|
+
lambda val: val.quantity_value,
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
# ----------------------------------
|
|
514
|
+
@classmethod
|
|
515
|
+
def _fill_composite_time_array(
|
|
516
|
+
cls,
|
|
517
|
+
out_composite_array: CompositePriceArray,
|
|
518
|
+
in_composite_values: list[CompositePriceValue],
|
|
519
|
+
vat_rate_array_by_id: dict[str, VatRateArray],
|
|
520
|
+
) -> None:
|
|
521
|
+
"""Fill the time component array of a CompositePriceArray."""
|
|
522
|
+
cls._fill_composite_component_array(
|
|
523
|
+
out_composite_array,
|
|
524
|
+
in_composite_values,
|
|
525
|
+
vat_rate_array_by_id,
|
|
526
|
+
lambda arr: arr.time_value_array, # type: ignore
|
|
527
|
+
lambda val: val.time_value,
|
|
528
|
+
)
|
|
529
|
+
|
|
421
530
|
# ----------------------------------
|
|
422
531
|
@classmethod
|
|
423
532
|
def get_time_unit_convertion_factor(cls, from_time_unit: TimeUnit, to_time_unit: TimeUnit, dt: date) -> float:
|
|
@@ -542,12 +651,24 @@ class Pricer:
|
|
|
542
651
|
)
|
|
543
652
|
|
|
544
653
|
# ----------------------------------
|
|
654
|
+
@overload
|
|
545
655
|
@classmethod
|
|
546
656
|
def convert(
|
|
547
657
|
cls,
|
|
548
658
|
price_values: list[PriceValue[ValueUnit, BaseUnit]],
|
|
549
659
|
to_unit: Tuple[ValueUnit, BaseUnit],
|
|
550
|
-
) -> list[PriceValue[ValueUnit, BaseUnit]]:
|
|
660
|
+
) -> list[PriceValue[ValueUnit, BaseUnit]]: ...
|
|
661
|
+
|
|
662
|
+
@overload
|
|
663
|
+
@classmethod
|
|
664
|
+
def convert(
|
|
665
|
+
cls,
|
|
666
|
+
composite_prices: list[CompositePriceValue],
|
|
667
|
+
to_unit: Tuple[PriceUnit, QuantityUnit, TimeUnit],
|
|
668
|
+
) -> list[CompositePriceValue]: ...
|
|
669
|
+
|
|
670
|
+
@classmethod
|
|
671
|
+
def convert(cls, price_values, to_unit):
|
|
551
672
|
|
|
552
673
|
if price_values is None or len(price_values) == 0:
|
|
553
674
|
raise ValueError("price_values is None or empty")
|
|
@@ -555,6 +676,51 @@ class Pricer:
|
|
|
555
676
|
if to_unit is None:
|
|
556
677
|
raise ValueError("to_unit is None")
|
|
557
678
|
|
|
679
|
+
# Check if input is CompositePriceValue list (has 3-tuple with QuantityUnit and TimeUnit)
|
|
680
|
+
if isinstance(price_values[0], CompositePriceValue):
|
|
681
|
+
# to_unit is (PriceUnit, QuantityUnit, TimeUnit)
|
|
682
|
+
target_price_unit = to_unit[0]
|
|
683
|
+
target_quantity_unit = to_unit[1]
|
|
684
|
+
target_time_unit = to_unit[2]
|
|
685
|
+
|
|
686
|
+
res = list[CompositePriceValue]()
|
|
687
|
+
for composite_price in price_values:
|
|
688
|
+
converted_quantity_value = None
|
|
689
|
+
converted_time_value = None
|
|
690
|
+
|
|
691
|
+
# Convert quantity component if present
|
|
692
|
+
if composite_price.quantity_value is not None and composite_price.quantity_unit is not None:
|
|
693
|
+
quantity_conversion_factor = cls.get_convertion_factor(
|
|
694
|
+
(composite_price.price_unit, composite_price.quantity_unit),
|
|
695
|
+
(target_price_unit, target_quantity_unit),
|
|
696
|
+
composite_price.start_date,
|
|
697
|
+
)
|
|
698
|
+
converted_quantity_value = composite_price.quantity_value * quantity_conversion_factor
|
|
699
|
+
|
|
700
|
+
# Convert time component if present
|
|
701
|
+
if composite_price.time_value is not None and composite_price.time_unit is not None:
|
|
702
|
+
time_conversion_factor = cls.get_convertion_factor(
|
|
703
|
+
(composite_price.price_unit, composite_price.time_unit),
|
|
704
|
+
(target_price_unit, target_time_unit),
|
|
705
|
+
composite_price.start_date,
|
|
706
|
+
)
|
|
707
|
+
converted_time_value = composite_price.time_value * time_conversion_factor
|
|
708
|
+
|
|
709
|
+
res.append(
|
|
710
|
+
CompositePriceValue(
|
|
711
|
+
start_date=composite_price.start_date,
|
|
712
|
+
end_date=composite_price.end_date,
|
|
713
|
+
price_unit=target_price_unit,
|
|
714
|
+
quantity_value=converted_quantity_value,
|
|
715
|
+
quantity_unit=target_quantity_unit,
|
|
716
|
+
time_value=converted_time_value,
|
|
717
|
+
time_unit=target_time_unit,
|
|
718
|
+
vat_id=composite_price.vat_id,
|
|
719
|
+
)
|
|
720
|
+
)
|
|
721
|
+
return res
|
|
722
|
+
|
|
723
|
+
# Original PriceValue conversion logic (2-tuple)
|
|
558
724
|
res = list[PriceValue[ValueUnit, BaseUnit]]()
|
|
559
725
|
for price_value in price_values:
|
|
560
726
|
if price_value.value_unit is None:
|