gazpar2haws 0.3.3__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/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
- ConsumptionPriceArray,
8
+ CompositePriceArray,
9
+ CompositePriceValue,
8
10
  ConsumptionQuantityArray,
9
11
  CostArray,
10
- EnergyTaxesPriceArray,
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
- ) -> CostArray:
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
- consumption_price_array = self.get_consumption_price_array(
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
- consumption_prices=consumption_prices,
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
- subscription_price_array = self.get_subscription_price_array(
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
- subscription_prices=subscription_prices,
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
- subscription_price_array = SubscriptionPriceArray(
96
+ subscription_composite = CompositePriceArray(
108
97
  name="subscription_prices",
109
98
  start_date=start_date,
110
99
  end_date=end_date,
111
- value_unit=price_unit,
112
- base_unit=quantities.base_unit,
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
- transport_price_array = self.get_transport_price_array(
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
- transport_prices=transport_prices,
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
- transport_price_array = TransportPriceArray(
117
+ transport_composite = CompositePriceArray(
125
118
  name="transport_prices",
126
119
  start_date=start_date,
127
120
  end_date=end_date,
128
- value_unit=price_unit,
129
- base_unit=quantities.base_unit,
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
- energy_taxes_price_array = self.get_energy_taxes_price_array(
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
- energy_taxes_prices=energy_taxes,
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
- energy_taxes_price_array = EnergyTaxesPriceArray(
138
+ energy_taxes_composite = CompositePriceArray(
142
139
  name="energy_taxes",
143
140
  start_date=start_date,
144
141
  end_date=end_date,
145
- value_unit=price_unit,
146
- base_unit=quantities.value_unit,
142
+ price_unit=price_unit,
143
+ quantity_unit=quantities.value_unit,
144
+ time_unit=quantities.base_unit,
147
145
  )
148
146
 
149
- res = CostArray(
150
- name="costs",
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
- # Compute pricing formula
158
- 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
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
- return res
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 get_consumption_price_array(
244
+ def get_composite_price_array(
187
245
  cls,
188
246
  start_date: date,
189
247
  end_date: date,
190
- consumption_prices: list[PriceValue[PriceUnit, QuantityUnit]],
248
+ composite_prices: list[CompositePriceValue],
191
249
  vat_rate_array_by_id: dict[str, VatRateArray],
192
- ) -> ConsumptionPriceArray:
193
-
194
- if consumption_prices is None or len(consumption_prices) == 0:
195
- raise ValueError("consumption_prices is None or empty")
250
+ target_price_unit: PriceUnit,
251
+ target_quantity_unit: QuantityUnit,
252
+ target_time_unit: TimeUnit,
253
+ ) -> CompositePriceArray:
196
254
 
197
- first_consumption_price = consumption_prices[0]
255
+ if composite_prices is None or len(composite_prices) == 0:
256
+ raise ValueError("composite_prices is None or empty")
198
257
 
199
- res = ConsumptionPriceArray(
200
- name="consumption_prices",
201
- start_date=start_date,
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
- cls._fill_price_array(res, consumption_prices, vat_rate_array_by_id) # type: ignore
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
- first_subscription_price = subscription_prices[0]
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
- value_unit=first_subscription_price.value_unit,
232
- base_unit=first_subscription_price.base_unit,
233
- vat_id=first_subscription_price.vat_id,
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
- cls._fill_price_array(res, subscription_prices, vat_rate_array_by_id) # type: ignore
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
- first_transport_price = transport_prices[0]
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: