openepd 5.1.0__py3-none-any.whl → 6.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 (81) hide show
  1. openepd/__version__.py +1 -1
  2. openepd/api/average_dataset/generic_estimate_sync_api.py +11 -3
  3. openepd/api/epd/sync_api.py +6 -2
  4. openepd/api/utils.py +40 -0
  5. openepd/bundle/model.py +3 -0
  6. openepd/model/common.py +65 -1
  7. openepd/model/epd.py +1 -1
  8. openepd/model/org.py +0 -1
  9. openepd/model/specs/__init__.py +34 -75
  10. openepd/model/specs/base.py +2 -1
  11. openepd/model/specs/{generated/enums.py → enums.py} +51 -3
  12. openepd/model/specs/range/aggregates.py +2 -2
  13. openepd/model/specs/range/aluminium.py +1 -1
  14. openepd/model/specs/range/asphalt.py +4 -4
  15. openepd/model/specs/range/cladding.py +1 -1
  16. openepd/model/specs/range/cmu.py +6 -5
  17. openepd/model/specs/range/concrete.py +1 -1
  18. openepd/model/specs/range/conveying_equipment.py +1 -1
  19. openepd/model/specs/range/electrical.py +3 -2
  20. openepd/model/specs/range/finishes.py +13 -6
  21. openepd/model/specs/range/fire_and_smoke_protection.py +1 -1
  22. openepd/model/specs/range/furnishings.py +1 -1
  23. openepd/model/specs/range/manufacturing_inputs.py +1 -1
  24. openepd/model/specs/range/masonry.py +2 -2
  25. openepd/model/specs/range/mechanical.py +1 -1
  26. openepd/model/specs/range/mechanical_insulation.py +1 -1
  27. openepd/model/specs/range/network_infrastructure.py +1 -1
  28. openepd/model/specs/range/openings.py +12 -5
  29. openepd/model/specs/range/plumbing.py +1 -1
  30. openepd/model/specs/range/precast_concrete.py +1 -1
  31. openepd/model/specs/range/sheathing.py +3 -3
  32. openepd/model/specs/range/steel.py +10 -5
  33. openepd/model/specs/range/thermal_moisture_protection.py +1 -1
  34. openepd/model/specs/range/utility_piping.py +1 -1
  35. openepd/model/specs/range/wood.py +8 -6
  36. openepd/model/specs/range/wood_joists.py +1 -1
  37. openepd/model/specs/{generated → singular}/__init__.py +37 -35
  38. openepd/model/specs/{generated → singular}/aggregates.py +1 -1
  39. openepd/model/specs/{generated → singular}/aluminium.py +1 -1
  40. openepd/model/specs/{generated → singular}/asphalt.py +6 -4
  41. openepd/model/specs/{generated → singular}/cladding.py +1 -1
  42. openepd/model/specs/{generated → singular}/cmu.py +10 -4
  43. openepd/model/specs/{generated → singular}/concrete.py +12 -1
  44. openepd/model/specs/{generated → singular}/conveying_equipment.py +1 -1
  45. openepd/model/specs/{generated → singular}/electrical.py +4 -3
  46. openepd/model/specs/{generated → singular}/finishes.py +9 -6
  47. openepd/model/specs/{generated → singular}/fire_and_smoke_protection.py +1 -1
  48. openepd/model/specs/{generated → singular}/furnishings.py +1 -1
  49. openepd/model/specs/{generated → singular}/manufacturing_inputs.py +1 -1
  50. openepd/model/specs/{generated → singular}/masonry.py +4 -1
  51. openepd/model/specs/{generated → singular}/mechanical.py +1 -1
  52. openepd/model/specs/{generated → singular}/mechanical_insulation.py +1 -1
  53. openepd/model/specs/{generated → singular}/mixins/conduit_mixin.py +1 -1
  54. openepd/model/specs/{generated → singular}/network_infrastructure.py +2 -2
  55. openepd/model/specs/{generated → singular}/openings.py +10 -4
  56. openepd/model/specs/{generated → singular}/plumbing.py +1 -1
  57. openepd/model/specs/{generated → singular}/sheathing.py +3 -3
  58. openepd/model/specs/{generated → singular}/steel.py +11 -5
  59. openepd/model/specs/{generated → singular}/thermal_moisture_protection.py +1 -1
  60. openepd/model/specs/{generated → singular}/utility_piping.py +1 -1
  61. openepd/model/specs/{generated → singular}/wood.py +13 -7
  62. openepd/model/specs/{generated → singular}/wood_joists.py +2 -2
  63. openepd/model/validation/enum.py +42 -0
  64. openepd/model/validation/quantity.py +179 -0
  65. {openepd-5.1.0.dist-info → openepd-6.1.0.dist-info}/METADATA +1 -1
  66. openepd-6.1.0.dist-info/RECORD +139 -0
  67. openepd-5.1.0.dist-info/RECORD +0 -138
  68. /openepd/model/specs/{generated → singular}/accessories.py +0 -0
  69. /openepd/model/specs/{generated → singular}/bulk_materials.py +0 -0
  70. /openepd/model/specs/{generated → singular}/cast_decks_and_underlayment.py +0 -0
  71. /openepd/model/specs/{generated → singular}/common.py +0 -0
  72. /openepd/model/specs/{generated → singular}/electrical_transmission_and_distribution_equipment.py +0 -0
  73. /openepd/model/specs/{generated → singular}/electricity.py +0 -0
  74. /openepd/model/specs/{generated → singular}/grouting.py +0 -0
  75. /openepd/model/specs/{generated → singular}/material_handling.py +0 -0
  76. /openepd/model/specs/{generated → singular}/mixins/__init__.py +0 -0
  77. /openepd/model/specs/{generated → singular}/other_electrical_equipment.py +0 -0
  78. /openepd/model/specs/{generated → singular}/other_materials.py +0 -0
  79. /openepd/model/specs/{generated → singular}/precast_concrete.py +0 -0
  80. {openepd-5.1.0.dist-info → openepd-6.1.0.dist-info}/LICENSE +0 -0
  81. {openepd-5.1.0.dist-info → openepd-6.1.0.dist-info}/WHEEL +0 -0
openepd/__version__.py CHANGED
@@ -13,4 +13,4 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- VERSION = "5.1.0"
16
+ VERSION = "6.1.0"
@@ -21,7 +21,7 @@ from openepd.api.base_sync_client import BaseApiMethodGroup
21
21
  from openepd.api.common import StreamingListResponse, paging_meta_from_v1_api
22
22
  from openepd.api.dto.common import BaseMeta, OpenEpdApiResponse
23
23
  from openepd.api.dto.meta import PagingMetaMixin
24
- from openepd.api.utils import encode_path_param
24
+ from openepd.api.utils import encode_path_param, remove_none_id_fields
25
25
  from openepd.model.generic_estimate import (
26
26
  GenericEstimate,
27
27
  GenericEstimatePreview,
@@ -61,7 +61,11 @@ class GenericEstimateApi(BaseApiMethodGroup):
61
61
  ) -> tuple[GenericEstimate, Response]: ...
62
62
 
63
63
  @overload
64
- def post_with_refs(self, ge: GenericEstimateWithDeps, with_response: Literal[False] = False) -> GenericEstimate: ...
64
+ def post_with_refs(
65
+ self,
66
+ ge: GenericEstimateWithDeps,
67
+ with_response: Literal[False] = False,
68
+ ) -> GenericEstimate: ...
65
69
 
66
70
  def post_with_refs(
67
71
  self, ge: GenericEstimateWithDeps, with_response: bool = False
@@ -71,12 +75,16 @@ class GenericEstimateApi(BaseApiMethodGroup):
71
75
 
72
76
  :param ge: GenericEstimate
73
77
  :param with_response: return the response object togather with the GenericEstimate
78
+ :param exclude_defaults: If True, fields with default values are excluded from the payload
74
79
  :return: GenericEstimate alone, or GenericEstimate with HTTP Response object depending on parameter
75
80
  """
81
+ data = ge.to_serializable(exclude_unset=True, by_alias=True)
82
+ # Remove 'id' fields with None values, as 'id' cannot be None
83
+ data = remove_none_id_fields(data)
76
84
  response = self._client.do_request(
77
85
  "patch",
78
86
  "/generic_estimates/post_with_refs",
79
- json=ge.to_serializable(exclude_unset=True, exclude_defaults=True, by_alias=True),
87
+ json=data,
80
88
  )
81
89
  content = response.json()
82
90
  if with_response:
@@ -20,7 +20,7 @@ from requests import Response
20
20
  from openepd.api.base_sync_client import BaseApiMethodGroup
21
21
  from openepd.api.common import StreamingListResponse
22
22
  from openepd.api.epd.dto import EpdSearchResponse, EpdStatisticsResponse, StatisticsDto
23
- from openepd.api.utils import encode_path_param
23
+ from openepd.api.utils import encode_path_param, remove_none_id_fields
24
24
  from openepd.model.epd import Epd
25
25
 
26
26
 
@@ -117,12 +117,16 @@ class EpdApi(BaseApiMethodGroup):
117
117
 
118
118
  :param epd: EPD
119
119
  :param with_response: return the response object togather with the EPD
120
+ :param exclude_defaults: If True, fields with default values are excluded from the payload
120
121
  :return: EPD or EPD with HTTP Response object depending on parameter
121
122
  """
123
+ epd_data = epd.to_serializable(exclude_unset=True, by_alias=True)
124
+ # Remove 'id' fields with None values, as 'id' cannot be None
125
+ epd_data = remove_none_id_fields(epd_data)
122
126
  response = self._client.do_request(
123
127
  "patch",
124
128
  "/epds/post-with-refs",
125
- json=epd.to_serializable(exclude_unset=True, exclude_defaults=True, by_alias=True),
129
+ json=epd_data,
126
130
  )
127
131
  content = response.json()
128
132
  if with_response:
openepd/api/utils.py CHANGED
@@ -26,3 +26,43 @@ def encode_path_param(value: str) -> str:
26
26
  :return: encoded value
27
27
  """
28
28
  return quote(value, safe="")
29
+
30
+
31
+ def remove_none_id_fields(d: dict) -> dict:
32
+ """
33
+ Remove any key 'id' with a None value from the dictionary, including nested dicts.
34
+
35
+ :param d: the dict which may contain 'id' keys with None values.
36
+ :return: a new dict with 'id' keys that have None values removed.
37
+
38
+ :note:
39
+ - This function does not modify the original dictionary (no side effects).
40
+ - It returns a new dictionary with the necessary modifications applied.
41
+
42
+ :example:
43
+ >>> data = {
44
+ ... "id": None,
45
+ ... "name": "item1",
46
+ ... "details": {
47
+ ... "id": None,
48
+ ... "category": "tools",
49
+ ... "nested": {
50
+ ... "id": None,
51
+ ... "value": 42
52
+ ... }
53
+ ... }
54
+ ... }
55
+ >>> remove_none_id_fields(data)
56
+ {'name': 'item1', 'details': {'category': 'tools', 'nested': {'value': 42}}}
57
+ """
58
+ if not isinstance(d, dict):
59
+ return d
60
+
61
+ cleaned_dict = {}
62
+ for k, v in d.items():
63
+ if isinstance(v, dict):
64
+ v = remove_none_id_fields(v)
65
+ if not (k == "id" and v is None):
66
+ cleaned_dict[k] = v
67
+
68
+ return cleaned_dict
openepd/bundle/model.py CHANGED
@@ -13,6 +13,7 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
+ from datetime import datetime
16
17
  from enum import StrEnum
17
18
 
18
19
  from openepd.compat.pydantic import pyd
@@ -61,6 +62,8 @@ class BundleManifest(BaseOpenEpdSchema):
61
62
  """The generator of the bundle."""
62
63
  assets: BundleManifestAssetsStats = pyd.Field(default_factory=BundleManifestAssetsStats)
63
64
  comment: str | None = pyd.Field(default=None)
65
+ created_at: datetime = pyd.Field(default_factory=datetime.utcnow)
66
+ """The date and time when the bundle was generated."""
64
67
 
65
68
 
66
69
  class AssetInfo(BaseOpenEpdSchema):
openepd/model/common.py CHANGED
@@ -18,6 +18,7 @@ from typing import Annotated, Any
18
18
 
19
19
  from openepd.compat.pydantic import pyd
20
20
  from openepd.model.base import BaseOpenEpdSchema
21
+ from openepd.model.validation.numbers import RatioFloat
21
22
 
22
23
 
23
24
  class Amount(BaseOpenEpdSchema):
@@ -38,6 +39,32 @@ class Amount(BaseOpenEpdSchema):
38
39
  return f"{self.qty or ''} {self.unit or 'str'}".strip()
39
40
 
40
41
 
42
+ class Distribution(StrEnum):
43
+ """
44
+ Distribution of the measured value.
45
+
46
+ * log-normal: Probability distribution of any random parameter whose natural log is normally distributed (the
47
+ PDF is gaussian).
48
+ * normal: Probability distribution of any random parameter whose value is normally distributed around the mean
49
+ (the PDF is gaussian).
50
+ * Continuous uniform probability distribution between minimum value and maximum value and "0" probability beyond
51
+ these.
52
+ * Probability distribution of any random parameter between minimum value and maximum value with the highest
53
+ probability at the average value of minimum plus maximum value. Linear change of probability between minimum,
54
+ maximum and average value.
55
+ * Means Impact is not known, but with >95% certainty the true value is below the declared value.
56
+ So [1e-6,"kgCFC11e",0,"max"] means the ODP was not exactly measured, but it is guaranteed to be below
57
+ 1E-6 kg CO2e. It is acceptable to treat a 'max' distribution a normal or lognormal distribution with variation
58
+ 0.1%. This is conservative, because the 'max' value is usually much greater than the true impact.
59
+ """
60
+
61
+ LOG_NORMAL = "log-normal"
62
+ NORMAL = "normal"
63
+ UNIFORM = "uniform"
64
+ TRIANGULAR = "triangular"
65
+ MAX = "max"
66
+
67
+
41
68
  class Measurement(BaseOpenEpdSchema):
42
69
  """A scientific value with units and uncertainty."""
43
70
 
@@ -46,7 +73,15 @@ class Measurement(BaseOpenEpdSchema):
46
73
  rsd: pyd.PositiveFloat | None = pyd.Field(
47
74
  description="Relative standard deviation, i.e. standard_deviation/mean", default=None
48
75
  )
49
- dist: str | None = pyd.Field(description="Statistical distribution of the measurement error.", default=None)
76
+ dist: Distribution | None = pyd.Field(
77
+ description="Statistical distribution of the measurement error.", default=None
78
+ )
79
+
80
+
81
+ class IngredientEvidenceTypeEnum(StrEnum):
82
+ """Supported types of evidence for indirect ingredient."""
83
+
84
+ PRODUCT_EPD = "Product EPD"
50
85
 
51
86
 
52
87
  class Ingredient(BaseOpenEpdSchema):
@@ -56,6 +91,11 @@ class Ingredient(BaseOpenEpdSchema):
56
91
  The Ingredients list gives the core data references and quantities. This list is used to document supply-chain
57
92
  transparency, such as the EPDs of major components (e.g. cement in concrete, or recycled steel
58
93
  in hot-rolled sections).
94
+
95
+ Since the exact ingredients may be viewed as a proprietary information by Manufacturers, this schema also allows
96
+ to pass some data about ingredient about explicitly saying what it is. Further, this data can be used to
97
+ calculate the supply chain specificity of the product and uncertainty adjusted factor. For this option, use
98
+ gwp_fraction, citation and evidence_type.
59
99
  """
60
100
 
61
101
  qty: float | None = pyd.Field(
@@ -67,6 +107,16 @@ class Ingredient(BaseOpenEpdSchema):
67
107
  default=None,
68
108
  )
69
109
 
110
+ gwp_fraction: RatioFloat | None = pyd.Field(
111
+ default=None,
112
+ description="Fraction of product's A1-A3 GWP this flow represents. This value, along with the specificity of "
113
+ "the reference, are used to caclulate supply chain specificity.",
114
+ )
115
+ evidence_type: IngredientEvidenceTypeEnum | None = pyd.Field(
116
+ default=None, description="Type of evidence used, which can be used to calculate degree of specificity"
117
+ )
118
+ citation: str | None = pyd.Field(default=None, description="Text citation describing the data source ")
119
+
70
120
 
71
121
  class LatLng(BaseOpenEpdSchema):
72
122
  """A latitude and longitude."""
@@ -189,3 +239,17 @@ class RangeAmount(RangeFloat):
189
239
  """Structure representing a range of quantities."""
190
240
 
191
241
  unit: str | None = pyd.Field(default=None, description="Unit of the range.")
242
+
243
+
244
+ class EnumGroupingAware:
245
+ """
246
+ Mixin for enums to support groups.
247
+
248
+ With the groups, enum can group its values into more than one groups, so that the validator higher in code can check
249
+ for mutual exclusivity, for example that only one value from the group is permitted at the same time.
250
+ """
251
+
252
+ @classmethod
253
+ def get_groupings(cls) -> list[list]:
254
+ """Return logical groupings of the values."""
255
+ return []
openepd/model/epd.py CHANGED
@@ -29,7 +29,7 @@ from openepd.model.declaration import (
29
29
  )
30
30
  from openepd.model.lcia import WithLciaMixin
31
31
  from openepd.model.org import Org, Plant
32
- from openepd.model.specs import Specs
32
+ from openepd.model.specs.singular import Specs
33
33
  from openepd.model.versioning import OpenEpdVersions, Version
34
34
 
35
35
  MANUFACTURER_DESCRIPTION = (
openepd/model/org.py CHANGED
@@ -69,7 +69,6 @@ class Plant(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
69
69
  description="Plus code (aka Open Location Code) of plant's location and "
70
70
  "owner's web domain joined with `.`(dot).",
71
71
  example="865P2W3V+3W.interface.com",
72
- alias="pluscode",
73
72
  default=None,
74
73
  )
75
74
  owner: Org | None = pyd.Field(description="Organization that owns the plant", default=None)
@@ -14,80 +14,39 @@
14
14
  # limitations under the License.
15
15
  #
16
16
 
17
- from openepd.model.base import BaseOpenEpdSchema
18
- from openepd.model.specs.generated.accessories import AccessoriesV1
19
- from openepd.model.specs.generated.aggregates import AggregatesV1
20
- from openepd.model.specs.generated.aluminium import AluminiumV1
21
- from openepd.model.specs.generated.asphalt import AsphaltV1
22
- from openepd.model.specs.generated.bulk_materials import BulkMaterialsV1
23
- from openepd.model.specs.generated.cast_decks_and_underlayment import CastDecksAndUnderlaymentV1
24
- from openepd.model.specs.generated.cladding import CladdingV1
25
- from openepd.model.specs.generated.cmu import CMUV1
26
- from openepd.model.specs.generated.concrete import ConcreteV1
27
- from openepd.model.specs.generated.conveying_equipment import ConveyingEquipmentV1
28
- from openepd.model.specs.generated.electrical import ElectricalV1
29
- from openepd.model.specs.generated.electrical_transmission_and_distribution_equipment import (
17
+ from openepd.model.specs.singular.accessories import AccessoriesV1
18
+ from openepd.model.specs.singular.aggregates import AggregatesV1
19
+ from openepd.model.specs.singular.aluminium import AluminiumV1
20
+ from openepd.model.specs.singular.asphalt import AsphaltV1
21
+ from openepd.model.specs.singular.bulk_materials import BulkMaterialsV1
22
+ from openepd.model.specs.singular.cast_decks_and_underlayment import CastDecksAndUnderlaymentV1
23
+ from openepd.model.specs.singular.cladding import CladdingV1
24
+ from openepd.model.specs.singular.cmu import CMUV1
25
+ from openepd.model.specs.singular.concrete import ConcreteV1
26
+ from openepd.model.specs.singular.conveying_equipment import ConveyingEquipmentV1
27
+ from openepd.model.specs.singular.electrical import ElectricalV1
28
+ from openepd.model.specs.singular.electrical_transmission_and_distribution_equipment import (
30
29
  ElectricalTransmissionAndDistributionEquipmentV1,
31
30
  )
32
- from openepd.model.specs.generated.electricity import ElectricityV1
33
- from openepd.model.specs.generated.finishes import FinishesV1
34
- from openepd.model.specs.generated.fire_and_smoke_protection import FireAndSmokeProtectionV1
35
- from openepd.model.specs.generated.furnishings import FurnishingsV1
36
- from openepd.model.specs.generated.grouting import GroutingV1
37
- from openepd.model.specs.generated.manufacturing_inputs import ManufacturingInputsV1
38
- from openepd.model.specs.generated.masonry import MasonryV1
39
- from openepd.model.specs.generated.material_handling import MaterialHandlingV1
40
- from openepd.model.specs.generated.mechanical import MechanicalV1
41
- from openepd.model.specs.generated.mechanical_insulation import MechanicalInsulationV1
42
- from openepd.model.specs.generated.network_infrastructure import NetworkInfrastructureV1
43
- from openepd.model.specs.generated.openings import OpeningsV1
44
- from openepd.model.specs.generated.other_electrical_equipment import OtherElectricalEquipmentV1
45
- from openepd.model.specs.generated.other_materials import OtherMaterialsV1
46
- from openepd.model.specs.generated.plumbing import PlumbingV1
47
- from openepd.model.specs.generated.precast_concrete import PrecastConcreteV1
48
- from openepd.model.specs.generated.sheathing import SheathingV1
49
- from openepd.model.specs.generated.steel import SteelV1
50
- from openepd.model.specs.generated.thermal_moisture_protection import ThermalMoistureProtectionV1
51
- from openepd.model.specs.generated.utility_piping import UtilityPipingV1
52
- from openepd.model.specs.generated.wood import WoodV1
53
- from openepd.model.specs.generated.wood_joists import WoodJoistsV1
54
-
55
-
56
- class Specs(BaseOpenEpdSchema):
57
- """Material specific specs."""
58
-
59
- # Nested specs:
60
- CMU: CMUV1 | None = None
61
- Masonry: MasonryV1 | None = None
62
- Steel: SteelV1 | None = None
63
- NetworkInfrastructure: NetworkInfrastructureV1 | None = None
64
- Finishes: FinishesV1 | None = None
65
- ManufacturingInputs: ManufacturingInputsV1 | None = None
66
- Accessories: AccessoriesV1 | None = None
67
- ElectricalTransmissionAndDistributionEquipment: ElectricalTransmissionAndDistributionEquipmentV1 | None = None
68
- Aggregates: AggregatesV1 | None = None
69
- ThermalMoistureProtection: ThermalMoistureProtectionV1 | None = None
70
- Mechanical: MechanicalV1 | None = None
71
- Aluminium: AluminiumV1 | None = None
72
- Cladding: CladdingV1 | None = None
73
- FireAndSmokeProtection: FireAndSmokeProtectionV1 | None = None
74
- PrecastConcrete: PrecastConcreteV1 | None = None
75
- Asphalt: AsphaltV1 | None = None
76
- OtherMaterials: OtherMaterialsV1 | None = None
77
- Plumbing: PlumbingV1 | None = None
78
- Electrical: ElectricalV1 | None = None
79
- UtilityPiping: UtilityPipingV1 | None = None
80
- BulkMaterials: BulkMaterialsV1 | None = None
81
- CastDecksAndUnderlayment: CastDecksAndUnderlaymentV1 | None = None
82
- Concrete: ConcreteV1 | None = None
83
- Sheathing: SheathingV1 | None = None
84
- Furnishings: FurnishingsV1 | None = None
85
- Wood: WoodV1 | None = None
86
- ConveyingEquipment: ConveyingEquipmentV1 | None = None
87
- MaterialHandling: MaterialHandlingV1 | None = None
88
- Openings: OpeningsV1 | None = None
89
- Electricity: ElectricityV1 | None = None
90
- Grouting: GroutingV1 | None = None
91
- MechanicalInsulation: MechanicalInsulationV1 | None = None
92
- OtherElectricalEquipment: OtherElectricalEquipmentV1 | None = None
93
- WoodJoists: WoodJoistsV1 | None = None
31
+ from openepd.model.specs.singular.electricity import ElectricityV1
32
+ from openepd.model.specs.singular.finishes import FinishesV1
33
+ from openepd.model.specs.singular.fire_and_smoke_protection import FireAndSmokeProtectionV1
34
+ from openepd.model.specs.singular.furnishings import FurnishingsV1
35
+ from openepd.model.specs.singular.grouting import GroutingV1
36
+ from openepd.model.specs.singular.manufacturing_inputs import ManufacturingInputsV1
37
+ from openepd.model.specs.singular.masonry import MasonryV1
38
+ from openepd.model.specs.singular.material_handling import MaterialHandlingV1
39
+ from openepd.model.specs.singular.mechanical import MechanicalV1
40
+ from openepd.model.specs.singular.mechanical_insulation import MechanicalInsulationV1
41
+ from openepd.model.specs.singular.network_infrastructure import NetworkInfrastructureV1
42
+ from openepd.model.specs.singular.openings import OpeningsV1
43
+ from openepd.model.specs.singular.other_electrical_equipment import OtherElectricalEquipmentV1
44
+ from openepd.model.specs.singular.other_materials import OtherMaterialsV1
45
+ from openepd.model.specs.singular.plumbing import PlumbingV1
46
+ from openepd.model.specs.singular.precast_concrete import PrecastConcreteV1
47
+ from openepd.model.specs.singular.sheathing import SheathingV1
48
+ from openepd.model.specs.singular.steel import SteelV1
49
+ from openepd.model.specs.singular.thermal_moisture_protection import ThermalMoistureProtectionV1
50
+ from openepd.model.specs.singular.utility_piping import UtilityPipingV1
51
+ from openepd.model.specs.singular.wood import WoodV1
52
+ from openepd.model.specs.singular.wood_joists import WoodJoistsV1
@@ -14,6 +14,7 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  import dataclasses
17
+ from types import UnionType
17
18
  from typing import Any
18
19
 
19
20
  from openepd.compat.pydantic import pyd
@@ -63,4 +64,4 @@ class CodegenSpec:
63
64
  """
64
65
 
65
66
  exclude_from_codegen: bool = False
66
- override_type: type
67
+ override_type: type | UnionType
@@ -15,6 +15,8 @@
15
15
  #
16
16
  from enum import StrEnum
17
17
 
18
+ from openepd.model.common import EnumGroupingAware
19
+
18
20
  # Enums used
19
21
 
20
22
 
@@ -2182,7 +2184,7 @@ class FloorBoxFloorMaterial(StrEnum):
2182
2184
  OTHER = "Other"
2183
2185
 
2184
2186
 
2185
- class AciExposureClass(StrEnum):
2187
+ class AciExposureClass(EnumGroupingAware, StrEnum):
2186
2188
  """
2187
2189
  American Concrete Institute concrete exposure classes.
2188
2190
 
@@ -2196,6 +2198,7 @@ class AciExposureClass(StrEnum):
2196
2198
  * `aci.S2` - Exposed to <10000 ppm of SO4 in water and <2% SO4 in soil
2197
2199
  * `aci.S3` - Exposed to >10000 ppm of SO4 in water or >2% SO4 in soil
2198
2200
 
2201
+ * `aci.C0` - Concrete dry or protected from moisture.
2199
2202
  * `aci.C1` - Concrete in contact with moisture, but the external source of chloride does not reach it.
2200
2203
  * `aci.C2` - Concrete subjected to moisture and an external source of chlorides such as deicing chemicals,
2201
2204
  salt, brackish water, seawater, or spray from these sources.
@@ -2212,14 +2215,25 @@ class AciExposureClass(StrEnum):
2212
2215
  S1 = "aci.S1"
2213
2216
  S2 = "aci.S2"
2214
2217
  S3 = "aci.S3"
2218
+ C0 = "aci.C1"
2215
2219
  C1 = "aci.C1"
2216
2220
  C2 = "aci.C2"
2217
2221
  W0 = "aci.W0"
2218
2222
  W1 = "aci.W1"
2219
2223
  W2 = "aci.W2"
2220
2224
 
2225
+ @classmethod
2226
+ def get_groupings(cls) -> list[list]:
2227
+ """Return logical groupings of the values."""
2228
+ return [
2229
+ [AciExposureClass.F0, AciExposureClass.F1, AciExposureClass.F2, AciExposureClass.F3],
2230
+ [AciExposureClass.S0, AciExposureClass.S1, AciExposureClass.S2, AciExposureClass.S3],
2231
+ [AciExposureClass.W0, AciExposureClass.W1],
2232
+ [AciExposureClass.C0, AciExposureClass.C1, AciExposureClass.C2],
2233
+ ]
2234
+
2221
2235
 
2222
- class CsaExposureClass(StrEnum):
2236
+ class CsaExposureClass(EnumGroupingAware, StrEnum):
2223
2237
  """
2224
2238
  Canadian Standard Association concrete exposure classes.
2225
2239
 
@@ -2286,8 +2300,20 @@ class CsaExposureClass(StrEnum):
2286
2300
  A_3 = "csa.A-3"
2287
2301
  A_4 = "csa.A-4"
2288
2302
 
2303
+ @classmethod
2304
+ def get_groupings(cls) -> list[list]:
2305
+ """Return logical groupings of the values."""
2306
+ return [
2307
+ [CsaExposureClass.C_XL],
2308
+ [CsaExposureClass.C_1, CsaExposureClass.C_2, CsaExposureClass.C_3, CsaExposureClass.C_4],
2309
+ [CsaExposureClass.F_1, CsaExposureClass.F2],
2310
+ [CsaExposureClass.N],
2311
+ [CsaExposureClass.A_1, CsaExposureClass.A_2, CsaExposureClass.A_3, CsaExposureClass.A_4],
2312
+ [CsaExposureClass.S_1, CsaExposureClass.S_2, CsaExposureClass.S_3],
2313
+ ]
2289
2314
 
2290
- class EnExposureClass(StrEnum):
2315
+
2316
+ class EnExposureClass(EnumGroupingAware, StrEnum):
2291
2317
  """
2292
2318
  EN 206 Class (Europe).
2293
2319
 
@@ -2348,6 +2374,28 @@ class EnExposureClass(StrEnum):
2348
2374
  en206_XA2 = "en206.XA2"
2349
2375
  en206_XA3 = "en206.XA3"
2350
2376
 
2377
+ @classmethod
2378
+ def get_groupings(cls) -> list[list]:
2379
+ """Return logical groupings of the values."""
2380
+ return [
2381
+ [EnExposureClass.en206_X0],
2382
+ [
2383
+ EnExposureClass.en206_XC1,
2384
+ EnExposureClass.en206_XC2,
2385
+ EnExposureClass.en206_XC3,
2386
+ EnExposureClass.en206_XC4,
2387
+ ],
2388
+ [EnExposureClass.en206_XD1, EnExposureClass.en206_XD2, EnExposureClass.en206_XD3],
2389
+ [EnExposureClass.en206_XS1, EnExposureClass.en206_XS2, EnExposureClass.en206_XS3],
2390
+ [
2391
+ EnExposureClass.en206_XF1,
2392
+ EnExposureClass.en206_XF2,
2393
+ EnExposureClass.en206_XF3,
2394
+ EnExposureClass.en206_XF4,
2395
+ ],
2396
+ [EnExposureClass.en206_XA1, EnExposureClass.en206_XA2, EnExposureClass.en206_XA3],
2397
+ ]
2398
+
2351
2399
 
2352
2400
  class AggregateWeightClassification(StrEnum):
2353
2401
  """
@@ -21,8 +21,8 @@ __all__ = ("AggregatesRangeV1",)
21
21
  from openepd.compat.pydantic import pyd
22
22
  from openepd.model.common import RangeRatioFloat
23
23
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
24
- from openepd.model.specs.generated.aggregates import AggregateApplication
25
- from openepd.model.specs.generated.enums import AggregateGradation, AggregateWeightClassification
24
+ from openepd.model.specs.enums import AggregateGradation, AggregateWeightClassification
25
+ from openepd.model.specs.singular.aggregates import AggregateApplication
26
26
  from openepd.model.validation.quantity import AmountRangeLengthMm
27
27
 
28
28
 
@@ -26,7 +26,7 @@ __all__ = (
26
26
 
27
27
  from openepd.compat.pydantic import pyd
28
28
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
29
- from openepd.model.specs.generated.enums import AluminiumAlloy
29
+ from openepd.model.specs.enums import AluminiumAlloy
30
30
 
31
31
 
32
32
  class AluminiumBilletsRangeV1(BaseOpenEpdHierarchicalSpec):
@@ -21,8 +21,8 @@ __all__ = ("AsphaltRangeV1",)
21
21
  from openepd.compat.pydantic import pyd
22
22
  from openepd.model.common import RangeRatioFloat
23
23
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
24
- from openepd.model.specs.generated.enums import AsphaltGradation, AsphaltMixType
25
- from openepd.model.validation.quantity import AmountRangeLengthMm, AmountRangeTemperatureC
24
+ from openepd.model.specs.enums import AsphaltGradation, AsphaltMixType
25
+ from openepd.model.validation.quantity import AmountRangeLengthMm, TemperatureCStr
26
26
 
27
27
 
28
28
  class AsphaltRangeV1(BaseOpenEpdHierarchicalSpec):
@@ -44,11 +44,11 @@ class AsphaltRangeV1(BaseOpenEpdHierarchicalSpec):
44
44
  ground_tire_rubber: RangeRatioFloat | None = pyd.Field(
45
45
  default=None, description="Percent of mixture that has been replaced by ground tire rubber (GTR)."
46
46
  )
47
- max_temperature: AmountRangeTemperatureC | None = pyd.Field(
47
+ max_temperature: TemperatureCStr | None = pyd.Field(
48
48
  default=None,
49
49
  description="The upper threshold temperature to which an asphalt binder can be heated preventing the asphalt mixture from rutting",
50
50
  )
51
- min_temperature: AmountRangeTemperatureC | None = pyd.Field(
51
+ min_temperature: TemperatureCStr | None = pyd.Field(
52
52
  default=None,
53
53
  description="The lower threshold temperature for an asphalt binder to prevent thermal cracking of the asphalt mixture.",
54
54
  )
@@ -40,7 +40,7 @@ __all__ = (
40
40
 
41
41
  from openepd.compat.pydantic import pyd
42
42
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
43
- from openepd.model.specs.generated.enums import CladdingFacingMaterial, CladdingInsulatingMaterial, SidingFormFactor
43
+ from openepd.model.specs.enums import CladdingFacingMaterial, CladdingInsulatingMaterial, SidingFormFactor
44
44
  from openepd.model.validation.quantity import AmountRangeLengthMm, AmountRangeRValue
45
45
 
46
46
 
@@ -18,11 +18,12 @@ __all__ = ("CMURangeV1",)
18
18
  # NB! This is a generated code. Do not edit it manually. Please see src/openepd/model/specs/README.md
19
19
 
20
20
 
21
+ from builtins import float
22
+
21
23
  from openepd.compat.pydantic import pyd
22
- from openepd.model.common import RangeFloat
23
24
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
24
- from openepd.model.specs.generated.enums import CmuBlockType, CmuWeightClassification
25
- from openepd.model.validation.quantity import AmountRangeGWP, AmountRangePressureMpa
25
+ from openepd.model.specs.enums import CmuBlockType, CmuWeightClassification
26
+ from openepd.model.validation.quantity import AmountRangePressureMpa, GwpKgCo2eStr
26
27
 
27
28
 
28
29
  class CMURangeV1(BaseOpenEpdHierarchicalSpec):
@@ -40,5 +41,5 @@ class CMURangeV1(BaseOpenEpdHierarchicalSpec):
40
41
  block_type: list[CmuBlockType] | None = pyd.Field(default=None, description="")
41
42
  insulated: bool | None = pyd.Field(default=None, description="")
42
43
  sound_performance: bool | None = pyd.Field(default=None, description="")
43
- b1_recarbonation: AmountRangeGWP | None = pyd.Field(default=None, description="")
44
- b1_recarbonation_z: RangeFloat | None = pyd.Field(default=None, description="")
44
+ b1_recarbonation: GwpKgCo2eStr | None = pyd.Field(default=None, description="")
45
+ b1_recarbonation_z: float | None = pyd.Field(default=None, description="")
@@ -32,7 +32,7 @@ from openepd.compat.pydantic import pyd
32
32
  from openepd.model.common import RangeRatioFloat
33
33
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
34
34
  from openepd.model.specs.concrete import Cementitious, ConcreteTypicalApplication
35
- from openepd.model.specs.generated.enums import AciExposureClass, CsaExposureClass, EnExposureClass
35
+ from openepd.model.specs.enums import AciExposureClass, CsaExposureClass, EnExposureClass
36
36
  from openepd.model.validation.quantity import (
37
37
  AmountRangeLengthInch,
38
38
  AmountRangeLengthMm,
@@ -24,7 +24,7 @@ __all__ = (
24
24
 
25
25
  from openepd.compat.pydantic import pyd
26
26
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
27
- from openepd.model.specs.generated.enums import ElevatorsBuildingRise, ElevatorsUsageIntensity
27
+ from openepd.model.specs.enums import ElevatorsBuildingRise, ElevatorsUsageIntensity
28
28
  from openepd.model.validation.quantity import (
29
29
  AmountRangeCapacityPerHour,
30
30
  AmountRangeLengthMm,
@@ -51,13 +51,14 @@ __all__ = (
51
51
  from openepd.compat.pydantic import pyd
52
52
  from openepd.model.common import RangeFloat
53
53
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
54
- from openepd.model.specs.generated.enums import CableTraysMaterial, ConduitMaterial, EnergySource, RacewaysMaterial
54
+ from openepd.model.specs.enums import CableTraysMaterial, ConduitMaterial, EnergySource, RacewaysMaterial
55
55
  from openepd.model.validation.quantity import (
56
56
  AmountRangeColorTemperature,
57
57
  AmountRangeLengthMm,
58
58
  AmountRangeLuminosity,
59
59
  AmountRangeMass,
60
60
  AmountRangePower,
61
+ AmountRangeUtilization,
61
62
  )
62
63
 
63
64
 
@@ -376,7 +377,7 @@ class LightingRangeV1(BaseOpenEpdHierarchicalSpec):
376
377
  _EXT_VERSION = "1.0"
377
378
 
378
379
  color_temperature: AmountRangeColorTemperature | None = pyd.Field(default=None, description="")
379
- typical_utilization: str | None = pyd.Field(default=None, description="")
380
+ typical_utilization: AmountRangeUtilization | None = pyd.Field(default=None, description="")
380
381
  luminosity: AmountRangeLuminosity | None = pyd.Field(default=None, description="")
381
382
  wattage: AmountRangePower | None = pyd.Field(default=None, description="")
382
383
  color_rendering_index: RangeFloat | None = pyd.Field(default=None, description="")