openepd 7.1.0__py3-none-any.whl → 7.2.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 (77) hide show
  1. openepd/__version__.py +1 -1
  2. openepd/api/average_dataset/generic_estimate_sync_api.py +2 -1
  3. openepd/api/average_dataset/industry_epd_sync_api.py +2 -1
  4. openepd/api/base_sync_client.py +10 -8
  5. openepd/api/common.py +17 -11
  6. openepd/api/dto/common.py +1 -1
  7. openepd/api/epd/sync_api.py +2 -1
  8. openepd/api/org/__init__.py +15 -0
  9. openepd/api/org/sync_api.py +79 -0
  10. openepd/api/pcr/sync_api.py +35 -0
  11. openepd/api/plant/__init__.py +15 -0
  12. openepd/api/plant/sync_api.py +79 -0
  13. openepd/api/standard/__init__.py +15 -0
  14. openepd/api/standard/sync_api.py +79 -0
  15. openepd/api/sync_client.py +27 -0
  16. openepd/bundle/base.py +5 -4
  17. openepd/bundle/reader.py +13 -7
  18. openepd/bundle/writer.py +11 -6
  19. openepd/m49/__init__.py +2 -0
  20. openepd/m49/const.py +1 -1
  21. openepd/m49/utils.py +16 -10
  22. openepd/model/base.py +20 -15
  23. openepd/model/common.py +10 -5
  24. openepd/model/declaration.py +2 -2
  25. openepd/model/epd.py +2 -1
  26. openepd/model/factory.py +5 -3
  27. openepd/model/generic_estimate.py +4 -0
  28. openepd/model/lcia.py +10 -10
  29. openepd/model/org.py +14 -7
  30. openepd/model/pcr.py +2 -2
  31. openepd/model/specs/__init__.py +37 -0
  32. openepd/model/specs/asphalt.py +3 -3
  33. openepd/model/specs/base.py +2 -1
  34. openepd/model/specs/enums.py +9 -1
  35. openepd/model/specs/range/__init__.py +5 -3
  36. openepd/model/specs/range/accessories.py +1 -1
  37. openepd/model/specs/range/aluminium.py +1 -1
  38. openepd/model/specs/range/cladding.py +10 -10
  39. openepd/model/specs/range/cmu.py +0 -2
  40. openepd/model/specs/range/concrete.py +25 -2
  41. openepd/model/specs/range/conveying_equipment.py +2 -2
  42. openepd/model/specs/range/electrical.py +18 -18
  43. openepd/model/specs/range/electrical_transmission_and_distribution_equipment.py +1 -1
  44. openepd/model/specs/range/exterior_improvements.py +47 -0
  45. openepd/model/specs/range/finishes.py +19 -40
  46. openepd/model/specs/range/fire_and_smoke_protection.py +3 -3
  47. openepd/model/specs/range/furnishings.py +7 -7
  48. openepd/model/specs/range/manufacturing_inputs.py +17 -5
  49. openepd/model/specs/range/masonry.py +1 -1
  50. openepd/model/specs/range/mechanical.py +6 -6
  51. openepd/model/specs/range/mixins/__init__.py +15 -0
  52. openepd/model/specs/range/mixins/access_flooring_mixin.py +43 -0
  53. openepd/model/specs/range/network_infrastructure.py +3 -3
  54. openepd/model/specs/range/openings.py +17 -17
  55. openepd/model/specs/range/other_materials.py +4 -4
  56. openepd/model/specs/range/plumbing.py +5 -5
  57. openepd/model/specs/range/precast_concrete.py +2 -2
  58. openepd/model/specs/range/steel.py +18 -9
  59. openepd/model/specs/range/thermal_moisture_protection.py +12 -12
  60. openepd/model/specs/range/wood.py +4 -6
  61. openepd/model/specs/singular/__init__.py +119 -2
  62. openepd/model/specs/singular/aluminium.py +2 -1
  63. openepd/model/specs/singular/concrete.py +25 -1
  64. openepd/model/specs/singular/deprecated/__init__.py +1 -1
  65. openepd/model/specs/singular/exterior_improvements.py +47 -0
  66. openepd/model/specs/singular/finishes.py +3 -59
  67. openepd/model/specs/singular/manufacturing_inputs.py +13 -1
  68. openepd/model/specs/singular/mixins/access_flooring_mixin.py +55 -0
  69. openepd/model/specs/singular/steel.py +10 -2
  70. openepd/model/validation/common.py +10 -6
  71. openepd/model/validation/enum.py +4 -2
  72. openepd/model/validation/quantity.py +13 -6
  73. openepd/model/versioning.py +8 -6
  74. {openepd-7.1.0.dist-info → openepd-7.2.0.dist-info}/METADATA +3 -4
  75. {openepd-7.1.0.dist-info → openepd-7.2.0.dist-info}/RECORD +77 -66
  76. {openepd-7.1.0.dist-info → openepd-7.2.0.dist-info}/WHEEL +1 -1
  77. {openepd-7.1.0.dist-info → openepd-7.2.0.dist-info}/LICENSE +0 -0
@@ -15,10 +15,10 @@
15
15
  #
16
16
  __all__ = (
17
17
  "ArchitecturalPrecastRangeV1",
18
- "StructuralPrecastRangeV1",
19
- "UtilityUndergroundPrecastRangeV1",
20
18
  "CivilPrecastRangeV1",
21
19
  "PrecastConcreteRangeV1",
20
+ "StructuralPrecastRangeV1",
21
+ "UtilityUndergroundPrecastRangeV1",
22
22
  )
23
23
 
24
24
  import pydantic
@@ -14,25 +14,27 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  __all__ = (
17
+ "CoilSteelRangeV1",
17
18
  "ColdFormedFramingRangeV1",
19
+ "ColdFormedSteelRangeV1",
20
+ "CrudeSteelRangeV1",
18
21
  "DeckingSteelRangeV1",
19
- "SteelSuspensionAssemblyRangeV1",
20
22
  "HollowSectionsRangeV1",
21
23
  "HotRolledSectionsRangeV1",
22
- "PlateSteelRangeV1",
24
+ "MBQSteelRangeV1",
23
25
  "MetalRailingsRangeV1",
24
26
  "MetalStairsRangeV1",
25
27
  "MiscMetalFabricationRangeV1",
26
28
  "OpenWebMembranesRangeV1",
27
- "MBQSteelRangeV1",
28
- "CoilSteelRangeV1",
29
- "ColdFormedSteelRangeV1",
30
- "StructuralSteelRangeV1",
31
- "PrefabricatedSteelAssembliesRangeV1",
29
+ "OtherSteelRangeV1",
30
+ "PlateSteelRangeV1",
32
31
  "PostTensioningSteelRangeV1",
32
+ "PrefabricatedSteelAssembliesRangeV1",
33
33
  "RebarSteelRangeV1",
34
- "WireMeshSteelRangeV1",
35
34
  "SteelRangeV1",
35
+ "SteelSuspensionAssemblyRangeV1",
36
+ "StructuralSteelRangeV1",
37
+ "WireMeshSteelRangeV1",
36
38
  )
37
39
 
38
40
  from typing import ClassVar
@@ -308,6 +310,12 @@ class OtherSteelRangeV1(BaseOpenEpdHierarchicalSpec):
308
310
  _EXT_VERSION = "1.0"
309
311
 
310
312
 
313
+ class CrudeSteelRangeV1(BaseOpenEpdHierarchicalSpec):
314
+ """Crude steel products."""
315
+
316
+ _EXT_VERSION = "1.0"
317
+
318
+
311
319
  class SteelRangeV1(BaseOpenEpdHierarchicalSpec):
312
320
  """
313
321
  Broad category for construction materials made from steel and its alloys.
@@ -315,7 +323,7 @@ class SteelRangeV1(BaseOpenEpdHierarchicalSpec):
315
323
  Range version.
316
324
  """
317
325
 
318
- _EXT_VERSION: ClassVar[str] = "1.1"
326
+ _EXT_VERSION: ClassVar[str] = "1.2"
319
327
 
320
328
  yield_tensile_str: AmountRangePressureMpa | None = pydantic.Field(
321
329
  default=None,
@@ -349,3 +357,4 @@ class SteelRangeV1(BaseOpenEpdHierarchicalSpec):
349
357
  RebarSteel: RebarSteelRangeV1 | None = None
350
358
  WireMeshSteel: WireMeshSteelRangeV1 | None = None
351
359
  OtherSteel: OtherSteelRangeV1 | None = None
360
+ CrudeSteel: CrudeSteelRangeV1 | None = None
@@ -14,28 +14,28 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  __all__ = (
17
+ "AirBarriersRangeV1",
17
18
  "BituminousRoofingRangeV1",
18
- "SinglePlyEPDMRangeV1",
19
- "SinglePlyKEERangeV1",
20
- "SinglePlyOtherRangeV1",
21
- "SinglePlyPolyurethaneRangeV1",
22
- "SinglePlyPVCRangeV1",
23
- "SinglePlyTPORangeV1",
24
19
  "BlanketInsulationRangeV1",
25
20
  "BlownInsulationRangeV1",
26
21
  "BoardInsulationRangeV1",
27
- "FoamedInPlaceRangeV1",
28
- "SprayedInsulationRangeV1",
29
- "AirBarriersRangeV1",
30
- "MembraneRoofingRangeV1",
31
- "InsulationRangeV1",
32
22
  "DampproofingAndWaterproofingRangeV1",
33
23
  "FlashingAndSheetMetalRangeV1",
24
+ "FoamedInPlaceRangeV1",
25
+ "InsulationRangeV1",
34
26
  "JointProtectionRangeV1",
27
+ "MembraneRoofingRangeV1",
35
28
  "RoofCoverBoardsRangeV1",
29
+ "SinglePlyEPDMRangeV1",
30
+ "SinglePlyKEERangeV1",
31
+ "SinglePlyOtherRangeV1",
32
+ "SinglePlyPVCRangeV1",
33
+ "SinglePlyPolyurethaneRangeV1",
34
+ "SinglePlyTPORangeV1",
35
+ "SprayedInsulationRangeV1",
36
36
  "SteepSlopeRoofingRangeV1",
37
- "WeatherBarriersRangeV1",
38
37
  "ThermalMoistureProtectionRangeV1",
38
+ "WeatherBarriersRangeV1",
39
39
  )
40
40
 
41
41
  import pydantic
@@ -14,26 +14,24 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  __all__ = (
17
- "WoodDeckingRangeV1",
18
- "WoodFramingRangeV1",
19
- "PrefabricatedWoodInsulatedPanelsRangeV1",
20
- "PrefabricatedWoodTrussRangeV1",
21
17
  "CompositeLumberRangeV1",
22
18
  "DimensionLumberRangeV1",
23
19
  "HeavyTimberRangeV1",
24
20
  "MassTimberRangeV1",
25
21
  "NonStructuralWoodRangeV1",
22
+ "PrefabricatedWoodInsulatedPanelsRangeV1",
26
23
  "PrefabricatedWoodRangeV1",
24
+ "PrefabricatedWoodTrussRangeV1",
27
25
  "SheathingPanelsRangeV1",
28
26
  "UnfinishedWoodRangeV1",
27
+ "WoodDeckingRangeV1",
28
+ "WoodFramingRangeV1",
29
29
  "WoodRangeV1",
30
30
  )
31
31
 
32
32
  # NB! This is a generated code. Do not edit it manually. Please see src/openepd/model/specs/README.md
33
33
 
34
34
 
35
- from builtins import float
36
-
37
35
  import pydantic
38
36
 
39
37
  from openepd.model.org import OrgRef
@@ -13,9 +13,18 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- from typing import Any, ClassVar
16
+ __all__ = [
17
+ "BaseCompatibilitySpec",
18
+ "Specs",
19
+ ]
20
+
21
+
22
+ from collections.abc import Sequence
23
+ import types
24
+ from typing import Any, ClassVar, TypeVar
17
25
 
18
26
  import pydantic
27
+ from pydantic.fields import FieldInfo
19
28
 
20
29
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
21
30
  from openepd.model.specs.singular.accessories import AccessoriesV1
@@ -36,6 +45,7 @@ from openepd.model.specs.singular.electrical_transmission_and_distribution_equip
36
45
  ElectricalTransmissionAndDistributionEquipmentV1,
37
46
  )
38
47
  from openepd.model.specs.singular.electricity import ElectricityV1
48
+ from openepd.model.specs.singular.exterior_improvements import ExteriorImprovementsV1
39
49
  from openepd.model.specs.singular.finishes import FinishesV1
40
50
  from openepd.model.specs.singular.fire_and_smoke_protection import FireAndSmokeProtectionV1
41
51
  from openepd.model.specs.singular.furnishings import FurnishingsV1
@@ -58,7 +68,7 @@ from openepd.model.specs.singular.utility_piping import UtilityPipingV1
58
68
  from openepd.model.specs.singular.wood import WoodV1
59
69
  from openepd.model.specs.singular.wood_joists import WoodJoistsV1
60
70
 
61
- __all__ = ("Specs", "BaseCompatibilitySpec")
71
+ TValue = TypeVar("TValue")
62
72
 
63
73
 
64
74
  class Specs(BaseOpenEpdHierarchicalSpec):
@@ -106,6 +116,7 @@ class Specs(BaseOpenEpdHierarchicalSpec):
106
116
  MechanicalInsulation: MechanicalInsulationV1 | None = None
107
117
  OtherElectricalEquipment: OtherElectricalEquipmentV1 | None = None
108
118
  WoodJoists: WoodJoistsV1 | None = None
119
+ ExteriorImprovements: ExteriorImprovementsV1 | None = None
109
120
 
110
121
  # historical backward-compatible specs
111
122
  concrete: ConcreteOldSpec | None = None
@@ -141,3 +152,109 @@ class Specs(BaseOpenEpdHierarchicalSpec):
141
152
  continue
142
153
  set_safely(values, new_key_spec, old_value)
143
154
  return values
155
+
156
+ def get_by_spec_path(
157
+ self,
158
+ path: str | Sequence[str],
159
+ *,
160
+ delimiter: str = ".",
161
+ asserted_type: type[TValue] | None = None,
162
+ ensure_path: bool = True,
163
+ ) -> TValue | None:
164
+ """
165
+ Access specific property of the spec by a path, which can be string-separated or sequence of keys.
166
+
167
+ :param path: The path to access, either as a string with delimiters (e.g. "Concrete.strength_28d")
168
+ or sequence of keys (e.g. ["Concrete", "strength_28d"])
169
+ :param delimiter: String separator used when path is provided as a string (default ".")
170
+ :param asserted_type: Type to validate the returned value against (optional)
171
+ :param ensure_path: If True, validates that path exists in the spec schema before accessing (default True)
172
+ :return: Value at the specified path if found, None if path resolves to None
173
+ :raises KeyError: If ensure_path=True and path does not exist in schema
174
+ :raises TypeError: If asserted_type is provided and value type doesn't match
175
+ :raises ValueError: If path is not string or sequence
176
+
177
+ Example usage:
178
+ >>> specs.get_by_spec_path("Concrete.strength_28d")
179
+ >>> specs.get_by_spec_path(["Concrete", "strength_28d"])
180
+ >>> specs.get_by_spec_path("Concrete.strength_28d", asserted_type=str)
181
+ """
182
+ keys = self._path_to_keys(path, delimiter)
183
+
184
+ if ensure_path:
185
+ self._ensure_path(keys)
186
+
187
+ result: Any = self
188
+ for key in self._path_to_keys(path, delimiter):
189
+ result = getattr(result, key, None)
190
+ if result is None:
191
+ return None
192
+
193
+ if not asserted_type:
194
+ return result
195
+
196
+ if isinstance(result, asserted_type):
197
+ return result
198
+
199
+ msg = f"Expected {asserted_type} but got {type(result)}"
200
+ raise TypeError(msg)
201
+
202
+ def _ensure_path(self, keys: Sequence[str]) -> None:
203
+ """
204
+ Validate if a sequence of keys exists in the spec schema structure.
205
+
206
+ :param keys: Sequence of string keys representing path in spec schema
207
+ :raises KeyError: If path specified by keys does not exist in schema
208
+
209
+ This internal method walks through the schema structure following the provided keys
210
+ sequence, checking if each key exists at the corresponding level. For UnionType fields,
211
+ it extracts the non-None type and continues validation if it's a BaseModel.
212
+ """
213
+ klass = self.__class__
214
+
215
+ fields: dict[str, FieldInfo] = klass.model_fields
216
+
217
+ field: FieldInfo | None
218
+ for i, key in enumerate(keys, start=1):
219
+ field = fields.get(key)
220
+ if field is None:
221
+ msg = f"Path {'.'.join(keys)} does not exist in {klass.__name__} spec"
222
+ raise KeyError(msg)
223
+
224
+ assert hasattr(field, "annotation"), "Annotation field is required"
225
+ if isinstance(field.annotation, types.UnionType):
226
+ target_type = next(item for item in field.annotation.__args__ if item is not None)
227
+ else:
228
+ target_type = field.annotation
229
+
230
+ if target_type is None:
231
+ msg = f"Path {'.'.join(keys)} does not exist in {klass.__name__} spec"
232
+ raise KeyError(msg)
233
+
234
+ if issubclass(target_type, pydantic.BaseModel):
235
+ fields = target_type.model_fields
236
+ elif i == len(keys):
237
+ return None
238
+ else:
239
+ msg = f"Path {'.'.join(keys)} does not exist in {klass.__name__} spec"
240
+ raise KeyError(msg)
241
+
242
+ return None
243
+
244
+ def _path_to_keys(self, path: str | Sequence[str], delimiter: str) -> Sequence[str]:
245
+ """
246
+ Convert path parameter to sequence of keys.
247
+
248
+ :param path: Path to convert, either string to split by delimiter or sequence of keys
249
+ :param delimiter: String separator used to split path if string is provided
250
+ :return: Sequence of string keys
251
+ :raises ValueError: If a path is not string or sequence
252
+ """
253
+ match path:
254
+ case str():
255
+ return path.split(delimiter)
256
+ case Sequence():
257
+ return path
258
+ case _:
259
+ msg = f"Unsupported path type: {type(path)}"
260
+ raise ValueError(msg)
@@ -26,7 +26,8 @@ class AluminiumBilletsV1(BaseOpenEpdHierarchicalSpec):
26
26
 
27
27
 
28
28
  class AluminiumExtrusionsV1(BaseOpenEpdHierarchicalSpec):
29
- """Extruded aluminum products used in construction.
29
+ """
30
+ Extruded aluminum products used in construction.
30
31
 
31
32
  Includes range of finish options including mill finish, painted, and anodized.
32
33
  """
@@ -99,6 +99,28 @@ class ShotcreteV1(BaseOpenEpdHierarchicalSpec):
99
99
  _EXT_VERSION = "1.0"
100
100
 
101
101
 
102
+ class CellularConcreteV1(BaseOpenEpdHierarchicalSpec):
103
+ """
104
+ Cellular concrete is typically composed of cementitious material, water, and pre-formed foam with air entrainment.
105
+
106
+ Such a product is a homogeneous void or cell structure.
107
+ It is self-compacting and can be pumped over extensive heights and distances.
108
+ """
109
+
110
+ _EXT_VERSION = "1.0"
111
+
112
+
113
+ class OtherConcreteV1(BaseOpenEpdHierarchicalSpec):
114
+ """
115
+ Other Concrete Products.
116
+
117
+ Other types of concrete products that are not captured by existing concrete categories.
118
+ Could include products such as patching concrete or additives
119
+ """
120
+
121
+ _EXT_VERSION = "1.0"
122
+
123
+
102
124
  class ConcreteV1(BaseOpenEpdHierarchicalSpec):
103
125
  """
104
126
  Concrete.
@@ -107,7 +129,7 @@ class ConcreteV1(BaseOpenEpdHierarchicalSpec):
107
129
  hardens over time.
108
130
  """
109
131
 
110
- _EXT_VERSION = "1.0"
132
+ _EXT_VERSION = "1.1"
111
133
 
112
134
  # Own fields:
113
135
  lightweight: bool | None = pydantic.Field(
@@ -208,6 +230,8 @@ class ConcreteV1(BaseOpenEpdHierarchicalSpec):
208
230
  OilPatch: OilPatchV1 | None = None
209
231
  ReadyMix: ReadyMixV1 | None = None
210
232
  Shotcrete: ShotcreteV1 | None = None
233
+ OtherConcrete: OtherConcreteV1 | None = None
234
+ CellularConcrete: CellularConcreteV1 | None = None
211
235
 
212
236
  @pydantic.field_validator("aci_exposure_classes", mode="before", check_fields=False)
213
237
  def _validate_aci_exposure_classes(cls, value):
@@ -50,7 +50,7 @@ def get_safely(d: dict, path: str) -> tuple[bool, Any]:
50
50
  return False, None
51
51
  current = current.get(p)
52
52
  case pydantic.BaseModel() as model:
53
- if not hasattr(current, p) or p not in model.__fields_set__:
53
+ if not hasattr(current, p) or p not in model.model_fields_set:
54
54
  return False, None
55
55
  current = getattr(current, p)
56
56
  case _:
@@ -0,0 +1,47 @@
1
+ #
2
+ # Copyright 2025 by C Change Labs Inc. www.c-change-labs.com
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ import pydantic as pyd
17
+
18
+ from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
19
+ from openepd.model.validation.quantity import AmountPressureMpa
20
+
21
+
22
+ class ConcretePaversV1(BaseOpenEpdHierarchicalSpec):
23
+ """
24
+ Segmental units of concrete of standardized sizes and shapes for use in paving applications.
25
+
26
+ Includes pavers and paving slabs.
27
+ """
28
+
29
+ _EXT_VERSION = "1.0"
30
+
31
+ compressive_strength: AmountPressureMpa | None = pyd.Field(
32
+ default=None,
33
+ description=(
34
+ "Unit strength in MPa, ksi, or psi. "
35
+ "This is the net compressive strength, also known as unit strength, as opposed to f′ₘ (f prime m) which is "
36
+ "the strength of the completed wall module."
37
+ ),
38
+ )
39
+ white_cement: bool | None = pyd.Field(default=None, description="CMU using some portion of white cement")
40
+
41
+
42
+ class ExteriorImprovementsV1(BaseOpenEpdHierarchicalSpec):
43
+ """Products that alter the exterior appearance of a lot or its structures."""
44
+
45
+ _EXT_VERSION = "1.0"
46
+
47
+ ConcretePavers: ConcretePaversV1 | None = None
@@ -17,10 +17,6 @@ import pydantic
17
17
 
18
18
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
19
19
  from openepd.model.specs.enums import (
20
- AccessFlooringCoreMaterial,
21
- AccessFlooringFinishMaterial,
22
- AccessFlooringSeismicRating,
23
- AccessFlooringStringers,
24
20
  AllFabrication,
25
21
  CarpetFormFactor,
26
22
  CarpetIntendedApplication,
@@ -44,13 +40,12 @@ from openepd.model.specs.enums import (
44
40
  WoodFlooringTimberSpecies,
45
41
  )
46
42
  from openepd.model.specs.singular.common import HasForestPracticesCertifiers
43
+ from openepd.model.specs.singular.mixins.access_flooring_mixin import AccessFlooringMixin
47
44
  from openepd.model.validation.quantity import (
48
45
  AreaPerVolumeStr,
49
- ForceNStr,
50
46
  GwpKgCo2eStr,
51
47
  LengthMmStr,
52
48
  LengthMStr,
53
- PressureMPaStr,
54
49
  RFactorStr,
55
50
  YarnWeightStr,
56
51
  validate_quantity_ge_factory,
@@ -59,7 +54,7 @@ from openepd.model.validation.quantity import (
59
54
  )
60
55
 
61
56
 
62
- class AccessFlooringV1(BaseOpenEpdHierarchicalSpec):
57
+ class AccessFlooringV1(BaseOpenEpdHierarchicalSpec, AccessFlooringMixin):
63
58
  """
64
59
  Elevated floor system built on top of concrete slab surface.
65
60
 
@@ -69,57 +64,6 @@ class AccessFlooringV1(BaseOpenEpdHierarchicalSpec):
69
64
 
70
65
  _EXT_VERSION = "1.0"
71
66
 
72
- # Own fields:
73
- core_material: AccessFlooringCoreMaterial | None = pydantic.Field(
74
- default=None, description="", examples=["Cementitious"]
75
- )
76
- finish_material: AccessFlooringFinishMaterial | None = pydantic.Field(
77
- default=None, description="", examples=["Linoleum"]
78
- )
79
- stringers: AccessFlooringStringers | None = pydantic.Field(default=None, description="", examples=["Standard"])
80
- seismic_rating: AccessFlooringSeismicRating | None = pydantic.Field(
81
- default=None, description="", examples=["Type 0"]
82
- )
83
- magnetically_attached_finish: bool | None = pydantic.Field(
84
- default=None,
85
- description="",
86
- examples=[True],
87
- )
88
- permanent_finish: bool | None = pydantic.Field(
89
- default=None,
90
- description="",
91
- examples=[True],
92
- )
93
- drylay: bool | None = pydantic.Field(
94
- default=None,
95
- description="",
96
- examples=[True],
97
- )
98
- adjustable_height: bool | None = pydantic.Field(
99
- default=None,
100
- description="",
101
- examples=[True],
102
- )
103
- fixed_height: bool | None = pydantic.Field(
104
- default=None,
105
- description="",
106
- examples=[True],
107
- )
108
- finished_floor_height: LengthMmStr | None = pydantic.Field(default=None, description="", examples=["1 m"])
109
- panel_thickness: LengthMmStr | None = pydantic.Field(default=None, description="", examples=["1 m"])
110
- concentrated_load: PressureMPaStr | None = pydantic.Field(default=None, description="", examples=["1 MPa"])
111
- uniform_load: PressureMPaStr | None = pydantic.Field(default=None, description="", examples=["1 MPa"])
112
- rolling_load_10_pass: ForceNStr | None = pydantic.Field(default=None, description="", examples=["1 N"])
113
- rolling_load_10000_pass: ForceNStr | None = pydantic.Field(default=None, description="", examples=["1 N"])
114
-
115
- @pydantic.field_validator("rolling_load_10_pass", mode="before", check_fields=False)
116
- def _access_flooring_rolling_load_10_pass_is_quantity_validator(cls, value):
117
- return validate_quantity_unit_factory("N")(cls, value)
118
-
119
- @pydantic.field_validator("rolling_load_10000_pass", mode="before", check_fields=False)
120
- def _access_flooring_rolling_load_10000_pass_is_quantity_validator(cls, value):
121
- return validate_quantity_unit_factory("N")(cls, value)
122
-
123
67
 
124
68
  class CarpetV1(BaseOpenEpdHierarchicalSpec):
125
69
  """Textile Floor Coverings."""
@@ -419,7 +363,7 @@ class BackingAndUnderlayV1(BaseOpenEpdHierarchicalSpec):
419
363
  class CementBoardV1(BaseOpenEpdHierarchicalSpec):
420
364
  """Hard cementitious boards, typically used as a tile backer."""
421
365
 
422
- _EXT_VERSION = "1.0"
366
+ _EXT_VERSION = "1.1"
423
367
 
424
368
  # Own fields:
425
369
  thickness: CementBoardThickness | None = pydantic.Field(
@@ -28,6 +28,7 @@ from openepd.model.specs.enums import (
28
28
  MasonryCementAstmC91Type,
29
29
  TextilesFabricType,
30
30
  )
31
+ from openepd.model.specs.singular.mixins.access_flooring_mixin import AccessFlooringMixin
31
32
 
32
33
 
33
34
  class CementV1(BaseOpenEpdHierarchicalSpec):
@@ -141,6 +142,16 @@ class TextilesV1(BaseOpenEpdHierarchicalSpec):
141
142
  fabric_type: list[TextilesFabricType] | None = pydantic.Field(default=None, description="", examples=[["Leather"]])
142
143
 
143
144
 
145
+ class AccessFlooringPanelsV1(BaseOpenEpdHierarchicalSpec, AccessFlooringMixin):
146
+ """
147
+ Part of an access floor system.
148
+
149
+ Panels are laid on top of an access floor pedestal, creating a finish floor.
150
+ """
151
+
152
+ _EXT_VERSION = "1.0"
153
+
154
+
144
155
  class ManufacturingInputsV1(BaseOpenEpdHierarchicalSpec):
145
156
  """
146
157
  Manufacturing inputs.
@@ -149,7 +160,7 @@ class ManufacturingInputsV1(BaseOpenEpdHierarchicalSpec):
149
160
  a construction.
150
161
  """
151
162
 
152
- _EXT_VERSION = "1.0"
163
+ _EXT_VERSION = "1.1"
153
164
 
154
165
  # Nested specs:
155
166
  AccessFlooringPedestals: AccessFlooringPedestalsV1 | None = None
@@ -158,3 +169,4 @@ class ManufacturingInputsV1(BaseOpenEpdHierarchicalSpec):
158
169
  CementitiousMaterials: CementitiousMaterialsV1 | None = None
159
170
  ConcreteAdmixtures: ConcreteAdmixturesV1 | None = None
160
171
  Textiles: TextilesV1 | None = None
172
+ AccessFlooringPanels: AccessFlooringPanelsV1 | None = None
@@ -0,0 +1,55 @@
1
+ #
2
+ # Copyright 2025 by C Change Labs Inc. www.c-change-labs.com
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ import pydantic as pyd
17
+
18
+ from openepd.model.specs.base import BaseOpenEpdSpec
19
+ from openepd.model.specs.enums import (
20
+ AccessFlooringCoreMaterial,
21
+ AccessFlooringFinishMaterial,
22
+ AccessFlooringSeismicRating,
23
+ AccessFlooringStringers,
24
+ )
25
+ from openepd.model.validation.quantity import ForceNStr, LengthMmStr, PressureMPaStr, validate_quantity_unit_factory
26
+
27
+
28
+ class AccessFlooringMixin(BaseOpenEpdSpec):
29
+ core_material: AccessFlooringCoreMaterial | None = pyd.Field(
30
+ default=None, description="", examples=["Cementitious"]
31
+ )
32
+ finish_material: AccessFlooringFinishMaterial | None = pyd.Field(
33
+ default=None, description="", examples=["Linoleum"]
34
+ )
35
+ stringers: AccessFlooringStringers | None = pyd.Field(default=None, description="", examples=["Standard"])
36
+ seismic_rating: AccessFlooringSeismicRating | None = pyd.Field(default=None, description="", examples=["Type 0"])
37
+ magnetically_attached_finish: bool | None = pyd.Field(default=None, description="", examples=[True])
38
+ permanent_finish: bool | None = pyd.Field(default=None, description="", examples=[True])
39
+ drylay: bool | None = pyd.Field(default=None, description="", examples=[True])
40
+ adjustable_height: bool | None = pyd.Field(default=None, description="", examples=[True])
41
+ fixed_height: bool | None = pyd.Field(default=None, description="", examples=[True])
42
+ finished_floor_height: LengthMmStr | None = pyd.Field(default=None, description="", examples=["1 m"])
43
+ panel_thickness: LengthMmStr | None = pyd.Field(default=None, description="", examples=["1 m"])
44
+ concentrated_load: PressureMPaStr | None = pyd.Field(default=None, description="", examples=["1 MPa"])
45
+ uniform_load: PressureMPaStr | None = pyd.Field(default=None, description="", examples=["1 MPa"])
46
+ rolling_load_10_pass: ForceNStr | None = pyd.Field(default=None, description="", examples=["1 N"])
47
+ rolling_load_10000_pass: ForceNStr | None = pyd.Field(default=None, description="", examples=["1 N"])
48
+
49
+ @pyd.field_validator("rolling_load_10_pass", mode="before", check_fields=False)
50
+ def _access_flooring_rolling_load_10_pass_is_quantity_validator(cls, value):
51
+ return validate_quantity_unit_factory("N")(cls, value)
52
+
53
+ @pyd.field_validator("rolling_load_10000_pass", mode="before", check_fields=False)
54
+ def _access_flooring_rolling_load_10000_pass_is_quantity_validator(cls, value):
55
+ return validate_quantity_unit_factory("N")(cls, value)
@@ -89,7 +89,8 @@ class HotRolledSectionsV1(BaseOpenEpdHierarchicalSpec, SteelFabricatedMixin):
89
89
 
90
90
 
91
91
  class PlateSteelV1(BaseOpenEpdHierarchicalSpec, SteelFabricatedMixin):
92
- """Plate Steels.
92
+ """
93
+ Plate Steels.
93
94
 
94
95
  Flat hot-rolled steel, typically thicker than 'sheet', made by compressing multiple steel
95
96
  layers together into one.
@@ -260,10 +261,16 @@ class OtherSteelV1(BaseOpenEpdHierarchicalSpec):
260
261
  _EXT_VERSION = "1.0"
261
262
 
262
263
 
264
+ class CrudeSteelV1(BaseOpenEpdHierarchicalSpec):
265
+ """Steel ingots, billets, blooms, and slabs for use in manufacturing steel products."""
266
+
267
+ _EXT_VERSION = "1.0"
268
+
269
+
263
270
  class SteelV1(BaseOpenEpdHierarchicalSpec):
264
271
  """Broad category for construction materials made from steel and its alloys."""
265
272
 
266
- _EXT_VERSION = "1.1"
273
+ _EXT_VERSION = "1.2"
267
274
 
268
275
  # Own fields:
269
276
  yield_tensile_str: PressureMPaStr | None = pydantic.Field(
@@ -327,3 +334,4 @@ class SteelV1(BaseOpenEpdHierarchicalSpec):
327
334
  RebarSteel: RebarSteelV1 | None = None
328
335
  WireMeshSteel: WireMeshSteelV1 | None = None
329
336
  OtherSteel: OtherSteelV1 | None = None
337
+ CrudeSteel: CrudeSteelV1 | None = None