openepd 4.13.1__py3-none-any.whl → 5.1.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.
Files changed (60) hide show
  1. openepd/__version__.py +1 -1
  2. openepd/model/common.py +40 -1
  3. openepd/model/declaration.py +7 -2
  4. openepd/model/geography.py +1 -1
  5. openepd/model/lcia.py +191 -19
  6. openepd/model/pcr.py +2 -2
  7. openepd/model/specs/README.md +34 -8
  8. openepd/model/specs/asphalt.py +2 -2
  9. openepd/model/specs/base.py +15 -5
  10. openepd/model/specs/generated/__init__.py +80 -0
  11. openepd/model/specs/generated/cladding.py +4 -4
  12. openepd/model/specs/generated/concrete.py +8 -7
  13. openepd/model/specs/generated/electrical.py +2 -2
  14. openepd/model/specs/generated/finishes.py +10 -6
  15. openepd/model/specs/generated/masonry.py +6 -2
  16. openepd/model/specs/generated/network_infrastructure.py +7 -2
  17. openepd/model/specs/generated/openings.py +10 -6
  18. openepd/model/specs/generated/sheathing.py +8 -4
  19. openepd/model/specs/generated/steel.py +10 -5
  20. openepd/model/specs/range/__init__.py +101 -0
  21. openepd/model/specs/range/accessories.py +97 -0
  22. openepd/model/specs/range/aggregates.py +57 -0
  23. openepd/model/specs/range/aluminium.py +92 -0
  24. openepd/model/specs/range/asphalt.py +61 -0
  25. openepd/model/specs/range/bulk_materials.py +31 -0
  26. openepd/model/specs/range/cast_decks_and_underlayment.py +34 -0
  27. openepd/model/specs/range/cladding.py +275 -0
  28. openepd/model/specs/range/cmu.py +44 -0
  29. openepd/model/specs/range/concrete.py +179 -0
  30. openepd/model/specs/range/conveying_equipment.py +86 -0
  31. openepd/model/specs/range/electrical.py +422 -0
  32. openepd/model/specs/range/electrical_transmission_and_distribution_equipment.py +96 -0
  33. openepd/model/specs/range/electricity.py +31 -0
  34. openepd/model/specs/range/finishes.py +585 -0
  35. openepd/model/specs/range/fire_and_smoke_protection.py +108 -0
  36. openepd/model/specs/range/furnishings.py +137 -0
  37. openepd/model/specs/range/grouting.py +34 -0
  38. openepd/model/specs/range/manufacturing_inputs.py +190 -0
  39. openepd/model/specs/range/masonry.py +87 -0
  40. openepd/model/specs/range/material_handling.py +50 -0
  41. openepd/model/specs/range/mechanical.py +307 -0
  42. openepd/model/specs/range/mechanical_insulation.py +42 -0
  43. openepd/model/specs/range/network_infrastructure.py +208 -0
  44. openepd/model/specs/range/openings.py +512 -0
  45. openepd/model/specs/range/other_electrical_equipment.py +31 -0
  46. openepd/model/specs/range/other_materials.py +194 -0
  47. openepd/model/specs/range/plumbing.py +200 -0
  48. openepd/model/specs/range/precast_concrete.py +115 -0
  49. openepd/model/specs/range/sheathing.py +86 -0
  50. openepd/model/specs/range/steel.py +332 -0
  51. openepd/model/specs/range/thermal_moisture_protection.py +336 -0
  52. openepd/model/specs/range/utility_piping.py +75 -0
  53. openepd/model/specs/range/wood.py +228 -0
  54. openepd/model/specs/range/wood_joists.py +44 -0
  55. openepd/model/validation/numbers.py +11 -5
  56. openepd/model/validation/quantity.py +469 -58
  57. {openepd-4.13.1.dist-info → openepd-5.1.0.dist-info}/METADATA +6 -1
  58. {openepd-4.13.1.dist-info → openepd-5.1.0.dist-info}/RECORD +60 -25
  59. {openepd-4.13.1.dist-info → openepd-5.1.0.dist-info}/LICENSE +0 -0
  60. {openepd-4.13.1.dist-info → openepd-5.1.0.dist-info}/WHEEL +0 -0
@@ -17,12 +17,11 @@ from abc import ABC, abstractmethod
17
17
  from typing import TYPE_CHECKING, Any, Callable, ClassVar
18
18
 
19
19
  from openepd.compat.pydantic import pyd
20
- from openepd.model.common import Amount, OpenEPDUnit
20
+ from openepd.model.base import BaseOpenEpdSchema
21
+ from openepd.model.common import Amount, OpenEPDUnit, RangeAmount
21
22
 
22
23
  if TYPE_CHECKING:
23
- from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
24
-
25
- QuantityValidatorType = Callable[[type[BaseOpenEpdHierarchicalSpec], str], str]
24
+ QuantityValidatorType = Callable[[type, str], str]
26
25
 
27
26
 
28
27
  class QuantityValidator(ABC):
@@ -33,6 +32,19 @@ class QuantityValidator(ABC):
33
32
  and set it with `set_unit_validator` function.
34
33
  """
35
34
 
35
+ @abstractmethod
36
+ def validate_same_dimensionality(self, unit: str | None, dimensionality_unit: str) -> None:
37
+ """
38
+ Validate that a given unit ('kg') has the same dimesnionality as provided dimensionality_unit ('g').
39
+
40
+ :param unit: unit to validate, not quantity
41
+ :param dimensionality_unit: unit to check against
42
+ :raise:
43
+ ValueError if dimensionality is different
44
+ :return: None
45
+ """
46
+ pass
47
+
36
48
  @abstractmethod
37
49
  def validate_unit_correctness(self, value: str, dimensionality: str) -> None:
38
50
  """
@@ -79,46 +91,56 @@ class QuantityValidator(ABC):
79
91
  pass
80
92
 
81
93
 
94
+ class ExternalValidationConfig:
95
+ """
96
+ Configuration holder for external validator.
97
+
98
+ Since openEPD library does not provide any facility for working with quantities/units, users should do this
99
+ by implementing the protocol for this validator and setting it with setup_external_validators function.
100
+ """
101
+
102
+ QUANTITY_VALIDATOR: ClassVar[QuantityValidator | None] = None
103
+
104
+
82
105
  def validate_unit_factory(dimensionality: OpenEPDUnit | str) -> "QuantityValidatorType":
83
- """Create validator for quantity field to check unit matching."""
106
+ """Create validator for units (not quantities) to check for dimensionality."""
84
107
 
85
- def validator(cls: "type[BaseOpenEpdHierarchicalSpec]", value: str) -> str:
86
- if hasattr(cls, "_QUANTITY_VALIDATOR") and cls._QUANTITY_VALIDATOR is not None:
87
- cls._QUANTITY_VALIDATOR.validate_unit_correctness(value, dimensionality)
108
+ def validator(cls: type | None, value: str) -> str:
109
+ if ExternalValidationConfig.QUANTITY_VALIDATOR is not None:
110
+ ExternalValidationConfig.QUANTITY_VALIDATOR.validate_same_dimensionality(value, dimensionality)
88
111
  return value
89
112
 
90
113
  return validator
91
114
 
92
115
 
93
- def validate_quantity_ge_factory(min_value: str) -> "QuantityValidatorType":
94
- """Create validator to check that quantity is greater than or equal to min_value."""
116
+ def validate_quantity_unit_factory(dimensionality: OpenEPDUnit | str) -> "QuantityValidatorType":
117
+ """Create validator for quantity field to check unit matching."""
95
118
 
96
- def validator(cls: "type[BaseOpenEpdHierarchicalSpec]", value: str) -> str:
97
- if hasattr(cls, "_QUANTITY_VALIDATOR") and cls._QUANTITY_VALIDATOR is not None:
98
- cls._QUANTITY_VALIDATOR.validate_quantity_greater_or_equal(value, min_value)
119
+ def validator(cls: type | None, value: str) -> str:
120
+ if ExternalValidationConfig.QUANTITY_VALIDATOR is not None:
121
+ ExternalValidationConfig.QUANTITY_VALIDATOR.validate_unit_correctness(value, dimensionality)
99
122
  return value
100
123
 
101
124
  return validator
102
125
 
103
126
 
104
- def validate_quantity_le_factory(max_value: str) -> "QuantityValidatorType":
105
- """Create validator to check that quantity is less than or equal to max_value."""
127
+ def validate_quantity_ge_factory(min_value: str) -> "QuantityValidatorType":
128
+ """Create validator to check that quantity is greater than or equal to min_value."""
106
129
 
107
- def validator(cls: "type[BaseOpenEpdHierarchicalSpec]", value: str) -> str:
108
- if hasattr(cls, "_QUANTITY_VALIDATOR") and cls._QUANTITY_VALIDATOR is not None:
109
- cls._QUANTITY_VALIDATOR.validate_quantity_less_or_equal(value, max_value)
130
+ def validator(cls: type | None, value: str) -> str:
131
+ if ExternalValidationConfig.QUANTITY_VALIDATOR is not None:
132
+ ExternalValidationConfig.QUANTITY_VALIDATOR.validate_quantity_greater_or_equal(value, min_value)
110
133
  return value
111
134
 
112
135
  return validator
113
136
 
114
137
 
115
- def validate_quantity_for_new_validator(max_value: str) -> Callable:
138
+ def validate_quantity_le_factory(max_value: str) -> "QuantityValidatorType":
116
139
  """Create validator to check that quantity is less than or equal to max_value."""
117
140
 
118
- def validator(value: str) -> str:
119
- cls = BaseOpenEpdHierarchicalSpec
120
- if hasattr(cls, "_QUANTITY_VALIDATOR") and cls._QUANTITY_VALIDATOR is not None:
121
- cls._QUANTITY_VALIDATOR.validate_quantity_less_or_equal(value, max_value)
141
+ def validator(cls: type | None, value: str) -> str:
142
+ if ExternalValidationConfig.QUANTITY_VALIDATOR is not None:
143
+ ExternalValidationConfig.QUANTITY_VALIDATOR.validate_quantity_less_or_equal(value, max_value)
122
144
  return value
123
145
 
124
146
  return validator
@@ -128,38 +150,6 @@ def validate_quantity_for_new_validator(max_value: str) -> Callable:
128
150
  # todo these types should be replaced by Annotated[str, AfterValidator...] as we move completely to pydantic 2
129
151
 
130
152
 
131
- class AmountWithDimensionality(Amount, ABC):
132
- """Class for dimensionality-validated amounts."""
133
-
134
- dimensionality_unit: ClassVar[str | None] = None
135
-
136
- # Unit for dimensionality to validate against, for example "kg"
137
-
138
- @pyd.root_validator
139
- def check_dimensionality_matches(cls, values: dict[str, Any]) -> dict[str, Any]:
140
- """Check that this amount conforms to the same dimensionality as dimensionality_unit."""
141
- if not cls.dimensionality_unit:
142
- return values
143
-
144
- from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
145
-
146
- str_repr = f"{values['qty']} {values['unit']}"
147
- validate_unit_factory(cls.dimensionality_unit)(BaseOpenEpdHierarchicalSpec, str_repr)
148
- return values
149
-
150
-
151
- class AmountMass(AmountWithDimensionality):
152
- """Amount of mass, measured in kg, t, etc."""
153
-
154
- dimensionality_unit = OpenEPDUnit.kg
155
-
156
-
157
- class AmountGWP(AmountWithDimensionality):
158
- """Amount of Global Warming Potential, measured in kgCO2e."""
159
-
160
- dimensionality_unit = OpenEPDUnit.kg_co2
161
-
162
-
163
153
  class QuantityStr(str):
164
154
  """
165
155
  Quantity string type.
@@ -173,8 +163,10 @@ class QuantityStr(str):
173
163
 
174
164
  @classmethod
175
165
  def __get_validators__(cls):
176
- yield validate_unit_factory(cls.unit)
177
- yield validate_quantity_ge_factory(f"0 {cls.unit}")
166
+ unit = getattr(cls, "unit", None)
167
+ if unit:
168
+ yield validate_quantity_unit_factory(cls.unit)
169
+ yield validate_quantity_ge_factory(f"0 {cls.unit}")
178
170
 
179
171
  @classmethod
180
172
  def __modify_schema__(cls, field_schema):
@@ -222,7 +214,7 @@ class LengthMmStr(QuantityStr):
222
214
  class LengthInchStr(QuantityStr):
223
215
  """Length (inch) quantity type."""
224
216
 
225
- unit = OpenEPDUnit.m
217
+ unit = "inch"
226
218
 
227
219
  @classmethod
228
220
  def __modify_schema__(cls, field_schema):
@@ -313,3 +305,422 @@ class AreaPerVolumeStr(QuantityStr):
313
305
  """Area per unit of volume quantity type."""
314
306
 
315
307
  unit = "m2 / l"
308
+
309
+
310
+ class WithDimensionalityMixin(BaseOpenEpdSchema):
311
+ """Class for dimensionality-validated amounts."""
312
+
313
+ dimensionality_unit: ClassVar[str | None] = None
314
+
315
+ # Unit for dimensionality to validate against, for example "kg"
316
+
317
+ @pyd.root_validator
318
+ def check_dimensionality_matches(cls, values: dict[str, Any]) -> dict[str, Any]:
319
+ """Check that this amount conforms to the same dimensionality as dimensionality_unit."""
320
+ if not cls.dimensionality_unit:
321
+ return values
322
+
323
+ validate_unit_factory(cls.dimensionality_unit)(BaseOpenEpdSchema, values.get("unit")) # type:ignore [arg-type]
324
+ return values
325
+
326
+
327
+ class AmountRangeWithDimensionality(RangeAmount, WithDimensionalityMixin):
328
+ """Mass amount, range."""
329
+
330
+ class Config:
331
+ """Pydantic config."""
332
+
333
+ @staticmethod
334
+ def schema_extra(schema: dict[str, Any], model: type["AmountRangeWithDimensionality"]) -> None:
335
+ """Modify json schema."""
336
+ schema["example"] = {"min": 1.2, "max": 3.4, "unit": str(model.dimensionality_unit) or None}
337
+
338
+
339
+ class WithMassKgMixin(WithDimensionalityMixin):
340
+ """Unit validation mixin."""
341
+
342
+ dimensionality_unit = MassKgStr.unit
343
+
344
+
345
+ class AmountMass(Amount, WithMassKgMixin):
346
+ """Amount of mass, measured in kg, t, etc."""
347
+
348
+ pass
349
+
350
+
351
+ class AmountRangeMass(AmountRangeWithDimensionality, WithMassKgMixin):
352
+ """Range of masses."""
353
+
354
+ pass
355
+
356
+
357
+ class WithGwpMixin(WithDimensionalityMixin):
358
+ """Unit validation mixin."""
359
+
360
+ dimensionality_unit = GwpKgCo2eStr.unit
361
+
362
+
363
+ class AmountGWP(Amount, WithGwpMixin):
364
+ """Amount of Global Warming Potential, measured in kgCO2e."""
365
+
366
+ pass
367
+
368
+
369
+ class AmountRangeGWP(AmountRangeWithDimensionality, WithGwpMixin):
370
+ """Range of masses."""
371
+
372
+ pass
373
+
374
+
375
+ class WithLengthMMixin(WithDimensionalityMixin):
376
+ """Unit validation mixin."""
377
+
378
+ pass
379
+
380
+
381
+ class AmountRangeLengthM(AmountRangeWithDimensionality, WithLengthMMixin):
382
+ """Range of lengths (m)."""
383
+
384
+ pass
385
+
386
+
387
+ class AmountLengthM(Amount, WithLengthMMixin):
388
+ """Length (m)."""
389
+
390
+ pass
391
+
392
+
393
+ class WithLengthMmMixin(WithDimensionalityMixin):
394
+ """Unit validation mixin."""
395
+
396
+ dimensionality_unit = LengthMmStr.unit
397
+
398
+
399
+ class AmountLengthMm(Amount, WithLengthMMixin):
400
+ """Length (mm)."""
401
+
402
+ pass
403
+
404
+
405
+ class AmountRangeLengthMm(AmountRangeWithDimensionality, WithLengthMmMixin):
406
+ """Range of lengths (mm)."""
407
+
408
+ pass
409
+
410
+
411
+ class WithPressureMpaMixin(WithDimensionalityMixin):
412
+ """Unit validation mixin."""
413
+
414
+ dimensionality_unit = PressureMPaStr.unit
415
+
416
+
417
+ class AmountPressureMpa(Amount, WithPressureMpaMixin):
418
+ """Length (mm)."""
419
+
420
+ pass
421
+
422
+
423
+ class AmountRangePressureMpa(AmountRangeWithDimensionality, WithPressureMpaMixin):
424
+ """Range of lengths (mm)."""
425
+
426
+ pass
427
+
428
+
429
+ class WithAreaM2Mixin(WithDimensionalityMixin):
430
+ """Unit validation mixin."""
431
+
432
+ dimensionality_unit = AreaM2Str.unit
433
+
434
+
435
+ class AmountAreaM2(Amount, WithAreaM2Mixin):
436
+ """Area (m2)."""
437
+
438
+ pass
439
+
440
+
441
+ class AmountRangeAreaM2(AmountRangeWithDimensionality, WithAreaM2Mixin):
442
+ """Range of Area (m2)."""
443
+
444
+ pass
445
+
446
+
447
+ class WithLengthInchStr(WithDimensionalityMixin):
448
+ """Unit validation mixin."""
449
+
450
+ dimensionality_unit = LengthInchStr.unit
451
+
452
+
453
+ class AmountLengthInch(Amount, WithLengthInchStr):
454
+ """Length (inch)."""
455
+
456
+ pass
457
+
458
+
459
+ class AmountRangeLengthInch(AmountRangeWithDimensionality, WithLengthInchStr):
460
+ """Range of Length (inch)."""
461
+
462
+ pass
463
+
464
+
465
+ class WithTemperatureCMixin(WithDimensionalityMixin):
466
+ """Unit validation mixin."""
467
+
468
+ dimensionality_unit = TemperatureCStr.unit
469
+
470
+
471
+ class AmountTemperatureC(Amount, WithTemperatureCMixin):
472
+ """Temperature (degrees C)."""
473
+
474
+ pass
475
+
476
+
477
+ class AmountRangeTemperatureC(AmountRangeWithDimensionality, WithTemperatureCMixin):
478
+ """Range of Temperature (degrees C)."""
479
+
480
+ pass
481
+
482
+
483
+ class WithCapacityPerHourMixin(WithDimensionalityMixin):
484
+ """Unit validation mixin."""
485
+
486
+ dimensionality_unit = CapacityPerHourStr.unit
487
+
488
+
489
+ class AmountCapacityPerHour(Amount, WithCapacityPerHourMixin):
490
+ """Capacity per hour."""
491
+
492
+ pass
493
+
494
+
495
+ class AmountRangeCapacityPerHour(AmountRangeWithDimensionality, WithCapacityPerHourMixin):
496
+ """Capacity per hour range."""
497
+
498
+ pass
499
+
500
+
501
+ class WithRValueMixin(WithDimensionalityMixin):
502
+ """Unit validation mixin."""
503
+
504
+ dimensionality_unit = RValueStr.unit
505
+
506
+
507
+ class AmountRValue(Amount, WithRValueMixin):
508
+ """R-Value."""
509
+
510
+ pass
511
+
512
+
513
+ class AmountRangeRValue(AmountRangeWithDimensionality, WithRValueMixin):
514
+ """R-Value range."""
515
+
516
+ pass
517
+
518
+
519
+ class WithSpeedMixin(WithDimensionalityMixin):
520
+ """Unit validation mixin."""
521
+
522
+ dimensionality_unit = SpeedStr.unit
523
+
524
+
525
+ class AmountSpeed(Amount, WithSpeedMixin):
526
+ """Speed."""
527
+
528
+ pass
529
+
530
+
531
+ class AmountRangeSpeed(AmountRangeWithDimensionality, WithSpeedMixin):
532
+ """Speed range."""
533
+
534
+ pass
535
+
536
+
537
+ class WithColorTemperatureMixin(WithDimensionalityMixin):
538
+ """Unit validation mixin."""
539
+
540
+ dimensionality_unit = ColorTemperatureStr.unit
541
+
542
+
543
+ class AmountColorTemperature(Amount, WithColorTemperatureMixin):
544
+ """Color temperature."""
545
+
546
+ pass
547
+
548
+
549
+ class AmountRangeColorTemperature(AmountRangeWithDimensionality, WithColorTemperatureMixin):
550
+ """Color temperature range."""
551
+
552
+ pass
553
+
554
+
555
+ class WithLuminosityMixin(WithDimensionalityMixin):
556
+ """Unit validation mixin."""
557
+
558
+ dimensionality_unit = LuminosityStr.unit
559
+
560
+
561
+ class AmountLuminosity(Amount, WithLuminosityMixin):
562
+ """Luminosity."""
563
+
564
+ pass
565
+
566
+
567
+ class AmountRangeLuminosity(AmountRangeWithDimensionality, WithLuminosityMixin):
568
+ """Luminosity range."""
569
+
570
+ pass
571
+
572
+
573
+ class WithPowerMixin(WithDimensionalityMixin):
574
+ """Unit validation mixin."""
575
+
576
+ dimensionality_unit = PowerStr.unit
577
+
578
+
579
+ class AmountPower(Amount, WithPowerMixin):
580
+ """Power."""
581
+
582
+ pass
583
+
584
+
585
+ class AmountRangePower(AmountRangeWithDimensionality, WithPowerMixin):
586
+ """Power range."""
587
+
588
+ pass
589
+
590
+
591
+ class WithElectricalCurrentMixin(WithDimensionalityMixin):
592
+ """Unit validation mixin."""
593
+
594
+ dimensionality_unit = ElectricalCurrentStr.unit
595
+
596
+
597
+ class AmountElectricalCurrent(Amount, WithElectricalCurrentMixin):
598
+ """Current."""
599
+
600
+ pass
601
+
602
+
603
+ class AmountRangeElectricalCurrent(AmountRangeWithDimensionality, WithElectricalCurrentMixin):
604
+ """Current range."""
605
+
606
+ pass
607
+
608
+
609
+ class WithVolumeMixin(WithDimensionalityMixin):
610
+ """Unit validation mixin."""
611
+
612
+ dimensionality_unit = VolumeStr.unit
613
+
614
+
615
+ class AmountVolume(Amount, WithVolumeMixin):
616
+ """Volume."""
617
+
618
+ pass
619
+
620
+
621
+ class AmountRangeVolume(AmountRangeWithDimensionality, WithVolumeMixin):
622
+ """Volume range."""
623
+
624
+ pass
625
+
626
+
627
+ class WithAirflowMixin(WithDimensionalityMixin):
628
+ """Unit validation mixin."""
629
+
630
+ dimensionality_unit = AirflowStr.unit
631
+
632
+
633
+ class AmountAirflow(Amount, WithAirflowMixin):
634
+ """Airflow."""
635
+
636
+ pass
637
+
638
+
639
+ class AmountRangeAirflow(AmountRangeWithDimensionality, WithAirflowMixin):
640
+ """Airflow range."""
641
+
642
+ pass
643
+
644
+
645
+ class WithFlowRateMixin(WithDimensionalityMixin):
646
+ """Unit validation mixin."""
647
+
648
+ dimensionality_unit = FlowRateStr.unit
649
+
650
+
651
+ class AmountFlowRate(Amount, WithFlowRateMixin):
652
+ """Flow Rate."""
653
+
654
+ pass
655
+
656
+
657
+ class AmountRangeFlowRate(AmountRangeWithDimensionality, WithFlowRateMixin):
658
+ """Flow Rate range."""
659
+
660
+ pass
661
+
662
+
663
+ class WithMassPerLengthMixin(WithDimensionalityMixin):
664
+ """Unit validation mixin."""
665
+
666
+ dimensionality_unit = MassPerLengthStr.unit
667
+
668
+
669
+ class AmountMassPerLength(Amount, WithFlowRateMixin):
670
+ """Mass per length."""
671
+
672
+ pass
673
+
674
+
675
+ class AmountRangeMassPerLength(AmountRangeWithDimensionality, WithFlowRateMixin):
676
+ """Mass per length range."""
677
+
678
+ pass
679
+
680
+
681
+ class WithAreaPerVolumeMixin(WithDimensionalityMixin):
682
+ """Unit validation mixin."""
683
+
684
+ dimensionality_unit = AreaPerVolumeStr.unit
685
+
686
+
687
+ class AmountAreaPerVolume(Amount, WithFlowRateMixin):
688
+ """Area per volume."""
689
+
690
+ pass
691
+
692
+
693
+ class AmountRangeAreaPerVolume(AmountRangeWithDimensionality, WithFlowRateMixin):
694
+ """Area per volume range."""
695
+
696
+ pass
697
+
698
+
699
+ # known range amounts
700
+ SUPPORTED_RANGE_TYPES: tuple[type[AmountRangeWithDimensionality], ...] = (
701
+ AmountRangeMass,
702
+ AmountRangeGWP,
703
+ AmountRangeLengthM,
704
+ AmountRangeLengthMm,
705
+ AmountRangePressureMpa,
706
+ AmountRangeAreaM2,
707
+ AmountRangeLengthInch,
708
+ AmountRangeTemperatureC,
709
+ AmountRangeCapacityPerHour,
710
+ AmountRangeRValue,
711
+ AmountRangeSpeed,
712
+ AmountRangeColorTemperature,
713
+ AmountRangeLuminosity,
714
+ AmountRangePower,
715
+ AmountRangeElectricalCurrent,
716
+ AmountRangeVolume,
717
+ AmountRangeAirflow,
718
+ AmountRangeFlowRate,
719
+ AmountRangeMassPerLength,
720
+ AmountRangeAreaPerVolume,
721
+ )
722
+
723
+ # known range amount mapping by unit
724
+ SUPPORTED_RANGES_BY_UNIT: dict[str, type[AmountRangeWithDimensionality]] = {
725
+ str(t.dimensionality_unit): t for t in SUPPORTED_RANGE_TYPES
726
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openepd
3
- Version: 4.13.1
3
+ Version: 5.1.0
4
4
  Summary: Python library to work with OpenEPD format
5
5
  Home-page: https://github.com/cchangelabs/openepd
6
6
  License: Apache-2.0
@@ -170,6 +170,11 @@ codes, UN m49 codification, and special regions. To update the enums, first upda
170
170
  Windows is not supported for development. You can use WSL2 with Ubuntu 20.04 or higher.
171
171
  Instructions are the same as for regular GNU/Linux installation.
172
172
 
173
+ ### Commit messages
174
+
175
+ Commit messages should follow [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/#specification)
176
+ specification as we use automatic version with [commitizen](https://commitizen-tools.github.io/commitizen/).
177
+
173
178
  # Credits
174
179
 
175
180
  This library has been written and maintained by [C-Change Labs](https://c-change-labs.com/).