openepd 5.0.0__tar.gz → 5.1.0__tar.gz

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 (142) hide show
  1. {openepd-5.0.0 → openepd-5.1.0}/PKG-INFO +6 -1
  2. {openepd-5.0.0 → openepd-5.1.0}/README.md +5 -0
  3. {openepd-5.0.0 → openepd-5.1.0}/pyproject.toml +3 -2
  4. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/__version__.py +1 -1
  5. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/common.py +39 -0
  6. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/declaration.py +7 -2
  7. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/geography.py +1 -1
  8. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/lcia.py +6 -3
  9. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/pcr.py +2 -2
  10. openepd-5.1.0/src/openepd/model/specs/README.md +45 -0
  11. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/asphalt.py +2 -2
  12. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/base.py +13 -0
  13. openepd-5.1.0/src/openepd/model/specs/generated/__init__.py +95 -0
  14. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/cladding.py +4 -4
  15. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/concrete.py +8 -7
  16. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/electrical.py +2 -2
  17. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/finishes.py +10 -6
  18. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/masonry.py +6 -2
  19. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/network_infrastructure.py +7 -2
  20. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/openings.py +10 -6
  21. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/sheathing.py +8 -4
  22. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/steel.py +10 -5
  23. openepd-5.1.0/src/openepd/model/specs/range/__init__.py +101 -0
  24. openepd-5.1.0/src/openepd/model/specs/range/accessories.py +97 -0
  25. openepd-5.1.0/src/openepd/model/specs/range/aggregates.py +57 -0
  26. openepd-5.1.0/src/openepd/model/specs/range/aluminium.py +92 -0
  27. openepd-5.1.0/src/openepd/model/specs/range/asphalt.py +61 -0
  28. openepd-5.1.0/src/openepd/model/specs/range/bulk_materials.py +31 -0
  29. openepd-5.1.0/src/openepd/model/specs/range/cast_decks_and_underlayment.py +34 -0
  30. openepd-5.1.0/src/openepd/model/specs/range/cladding.py +275 -0
  31. openepd-5.1.0/src/openepd/model/specs/range/cmu.py +44 -0
  32. openepd-5.1.0/src/openepd/model/specs/range/concrete.py +179 -0
  33. openepd-5.1.0/src/openepd/model/specs/range/conveying_equipment.py +86 -0
  34. openepd-5.1.0/src/openepd/model/specs/range/electrical.py +422 -0
  35. openepd-5.1.0/src/openepd/model/specs/range/electrical_transmission_and_distribution_equipment.py +96 -0
  36. openepd-5.1.0/src/openepd/model/specs/range/electricity.py +31 -0
  37. openepd-5.1.0/src/openepd/model/specs/range/finishes.py +585 -0
  38. openepd-5.1.0/src/openepd/model/specs/range/fire_and_smoke_protection.py +108 -0
  39. openepd-5.1.0/src/openepd/model/specs/range/furnishings.py +137 -0
  40. openepd-5.1.0/src/openepd/model/specs/range/grouting.py +34 -0
  41. openepd-5.1.0/src/openepd/model/specs/range/manufacturing_inputs.py +190 -0
  42. openepd-5.1.0/src/openepd/model/specs/range/masonry.py +87 -0
  43. openepd-5.1.0/src/openepd/model/specs/range/material_handling.py +50 -0
  44. openepd-5.1.0/src/openepd/model/specs/range/mechanical.py +307 -0
  45. openepd-5.1.0/src/openepd/model/specs/range/mechanical_insulation.py +42 -0
  46. openepd-5.1.0/src/openepd/model/specs/range/network_infrastructure.py +208 -0
  47. openepd-5.1.0/src/openepd/model/specs/range/openings.py +512 -0
  48. openepd-5.1.0/src/openepd/model/specs/range/other_electrical_equipment.py +31 -0
  49. openepd-5.1.0/src/openepd/model/specs/range/other_materials.py +194 -0
  50. openepd-5.1.0/src/openepd/model/specs/range/plumbing.py +200 -0
  51. openepd-5.1.0/src/openepd/model/specs/range/precast_concrete.py +115 -0
  52. openepd-5.1.0/src/openepd/model/specs/range/sheathing.py +86 -0
  53. openepd-5.1.0/src/openepd/model/specs/range/steel.py +332 -0
  54. openepd-5.1.0/src/openepd/model/specs/range/thermal_moisture_protection.py +336 -0
  55. openepd-5.1.0/src/openepd/model/specs/range/utility_piping.py +75 -0
  56. openepd-5.1.0/src/openepd/model/specs/range/wood.py +228 -0
  57. openepd-5.1.0/src/openepd/model/specs/range/wood_joists.py +44 -0
  58. openepd-5.1.0/src/openepd/model/validation/numbers.py +28 -0
  59. openepd-5.1.0/src/openepd/model/validation/quantity.py +726 -0
  60. openepd-5.0.0/src/openepd/model/specs/README.md +0 -19
  61. openepd-5.0.0/src/openepd/model/validation/__init__.py +0 -15
  62. openepd-5.0.0/src/openepd/model/validation/numbers.py +0 -22
  63. openepd-5.0.0/src/openepd/model/validation/quantity.py +0 -338
  64. {openepd-5.0.0 → openepd-5.1.0}/LICENSE +0 -0
  65. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/__init__.py +0 -0
  66. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/__init__.py +0 -0
  67. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/average_dataset/__init__.py +0 -0
  68. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/average_dataset/generic_estimate_sync_api.py +0 -0
  69. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/average_dataset/industry_epd_sync_api.py +0 -0
  70. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/base_sync_client.py +0 -0
  71. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/category/__init__.py +0 -0
  72. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/category/dto.py +0 -0
  73. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/category/sync_api.py +0 -0
  74. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/common.py +0 -0
  75. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/dto/__init__.py +0 -0
  76. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/dto/base.py +0 -0
  77. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/dto/common.py +0 -0
  78. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/dto/meta.py +0 -0
  79. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/dto/mf.py +0 -0
  80. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/dto/params.py +0 -0
  81. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/epd/__init__.py +0 -0
  82. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/epd/dto.py +0 -0
  83. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/epd/sync_api.py +0 -0
  84. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/errors.py +0 -0
  85. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/pcr/__init__.py +0 -0
  86. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/pcr/sync_api.py +0 -0
  87. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/sync_client.py +0 -0
  88. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/test/__init__.py +0 -0
  89. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/api/utils.py +0 -0
  90. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/bundle/__init__.py +0 -0
  91. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/bundle/base.py +0 -0
  92. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/bundle/model.py +0 -0
  93. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/bundle/reader.py +0 -0
  94. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/bundle/writer.py +0 -0
  95. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/compat/__init__.py +0 -0
  96. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/compat/compat_functional_validators.py +0 -0
  97. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/compat/pydantic.py +0 -0
  98. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/__init__.py +0 -0
  99. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/base.py +0 -0
  100. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/category.py +0 -0
  101. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/epd.py +0 -0
  102. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/factory.py +0 -0
  103. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/generic_estimate.py +0 -0
  104. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/industry_epd.py +0 -0
  105. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/org.py +0 -0
  106. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/__init__.py +0 -0
  107. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/concrete.py +0 -0
  108. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/accessories.py +0 -0
  109. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/aggregates.py +0 -0
  110. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/aluminium.py +0 -0
  111. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/asphalt.py +0 -0
  112. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/bulk_materials.py +0 -0
  113. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/cast_decks_and_underlayment.py +0 -0
  114. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/cmu.py +0 -0
  115. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/common.py +0 -0
  116. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/conveying_equipment.py +0 -0
  117. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/electrical_transmission_and_distribution_equipment.py +0 -0
  118. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/electricity.py +0 -0
  119. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/enums.py +0 -0
  120. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/fire_and_smoke_protection.py +0 -0
  121. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/furnishings.py +0 -0
  122. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/grouting.py +0 -0
  123. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/manufacturing_inputs.py +0 -0
  124. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/material_handling.py +0 -0
  125. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/mechanical.py +0 -0
  126. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/mechanical_insulation.py +0 -0
  127. {openepd-5.0.0/src/openepd/model/specs/generated → openepd-5.1.0/src/openepd/model/specs/generated/mixins}/__init__.py +0 -0
  128. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/mixins/conduit_mixin.py +0 -0
  129. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/other_electrical_equipment.py +0 -0
  130. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/other_materials.py +0 -0
  131. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/plumbing.py +0 -0
  132. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/precast_concrete.py +0 -0
  133. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/thermal_moisture_protection.py +0 -0
  134. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/utility_piping.py +0 -0
  135. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/wood.py +0 -0
  136. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/specs/generated/wood_joists.py +0 -0
  137. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/standard.py +0 -0
  138. {openepd-5.0.0/src/openepd/model/specs/generated/mixins → openepd-5.1.0/src/openepd/model/validation}/__init__.py +0 -0
  139. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/validation/common.py +0 -0
  140. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/model/versioning.py +0 -0
  141. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/patch_pydantic.py +0 -0
  142. {openepd-5.0.0 → openepd-5.1.0}/src/openepd/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openepd
3
- Version: 5.0.0
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/).
@@ -143,6 +143,11 @@ codes, UN m49 codification, and special regions. To update the enums, first upda
143
143
  Windows is not supported for development. You can use WSL2 with Ubuntu 20.04 or higher.
144
144
  Instructions are the same as for regular GNU/Linux installation.
145
145
 
146
+ ### Commit messages
147
+
148
+ Commit messages should follow [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/#specification)
149
+ specification as we use automatic version with [commitizen](https://commitizen-tools.github.io/commitizen/).
150
+
146
151
  # Credits
147
152
 
148
153
  This library has been written and maintained by [C-Change Labs](https://c-change-labs.com/).
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "openepd"
3
- version = "5.0.0"
3
+ version = "5.1.0"
4
4
  license = "Apache-2.0"
5
5
  description = "Python library to work with OpenEPD format"
6
6
  authors = ["C-Change Labs <support@c-change-labs.com>"]
@@ -38,6 +38,7 @@ pytest-subtests = "~=0.4"
38
38
  pytest-cov = "~=4.0"
39
39
  teamcity-messages = ">=1.31"
40
40
  wheel = "~=0.40.0"
41
+ click = "~=8.1.7"
41
42
 
42
43
  # Dev tools
43
44
  black = "~=24.3"
@@ -58,7 +59,7 @@ types-requests = ">=2.0"
58
59
  # Code generation
59
60
  # For list of countries
60
61
  pycountry = ">=24.6.1"
61
- jinja2 = ">=2.10.3"
62
+ jinja2 = ">=3.1.4"
62
63
 
63
64
 
64
65
  [tool.poetry.extras]
@@ -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.0.0"
16
+ VERSION = "5.1.0"
@@ -150,3 +150,42 @@ class OpenEPDUnit(StrEnum):
150
150
  degree_c = "°C"
151
151
  kg_co2 = "kgCO2e"
152
152
  hour = "hour"
153
+
154
+
155
+ class RangeBase(BaseOpenEpdSchema):
156
+ """Base class for range types having min and max and order between them."""
157
+
158
+ @pyd.root_validator
159
+ def _validate_range_bounds(cls, values: dict[str, Any]) -> dict[str, Any]:
160
+ min_boundary = values.get("min")
161
+ max_boundary = values.get("max")
162
+ if min_boundary is not None and max_boundary is not None and min_boundary > max_boundary:
163
+ raise ValueError("Max should be greater than min")
164
+ return values
165
+
166
+
167
+ class RangeFloat(RangeBase):
168
+ """Structure representing a range of floats."""
169
+
170
+ min: float | None = pyd.Field(default=None, example=3.1)
171
+ max: float | None = pyd.Field(default=None, example=5.8)
172
+
173
+
174
+ class RangeInt(RangeBase):
175
+ """Structure representing a range of ints1."""
176
+
177
+ min: int | None = pyd.Field(default=None, example=2)
178
+ max: int | None = pyd.Field(default=None, example=3)
179
+
180
+
181
+ class RangeRatioFloat(RangeFloat):
182
+ """Range of ratios (0-1 to 0-10)."""
183
+
184
+ min: float | None = pyd.Field(default=None, example=0.2, ge=0, le=1)
185
+ max: float | None = pyd.Field(default=None, example=0.65, ge=0, le=1)
186
+
187
+
188
+ class RangeAmount(RangeFloat):
189
+ """Structure representing a range of quantities."""
190
+
191
+ unit: str | None = pyd.Field(default=None, description="Unit of the range.")
@@ -22,6 +22,7 @@ from openepd.model.common import Amount
22
22
  from openepd.model.geography import Geography
23
23
  from openepd.model.org import Org
24
24
  from openepd.model.pcr import Pcr
25
+ from openepd.model.specs.range import SpecsRange
25
26
  from openepd.model.standard import Standard
26
27
  from openepd.model.validation.common import ReferenceStr
27
28
  from openepd.model.validation.quantity import AmountGWP, AmountMass
@@ -141,14 +142,14 @@ class BaseDeclaration(RootDocument, abc.ABC):
141
142
  description="Mass of elemental carbon, per declared unit, contained in the product itself at the manufacturing "
142
143
  "facility gate. Used (among other things) to check a carbon balance or calculate incineration "
143
144
  "emissions. The source of carbon (e.g. biogenic) is not relevant in this field.",
144
- example=Amount(qty=8.76, unit="kgCO2e"),
145
+ example=Amount(qty=8.76, unit="kgCO2e").to_serializable(exclude_unset=True),
145
146
  )
146
147
  kg_C_biogenic_per_declared_unit: AmountGWP | None = pyd.Field(
147
148
  default=None,
148
149
  description="Mass of elemental carbon from biogenic sources, per declared unit, contained in the product "
149
150
  "itself at the manufacturing facility gate. It may be presumed that any biogenic carbon content "
150
151
  "has been accounted for as -44/12 kgCO2e per kg C in stages A1-A3, per EN15804 and ISO 21930.",
151
- example=Amount(qty=8.76, unit="kgCO2e"),
152
+ example=Amount(qty=8.76, unit="kgCO2e").to_serializable(exclude_unset=True),
152
153
  )
153
154
  product_service_life_years: float | None = pyd.Field(
154
155
  gt=0.0009,
@@ -173,6 +174,10 @@ class AverageDatasetMixin(pyd.BaseModel, title="Average Dataset"):
173
174
  "implies global applicability.",
174
175
  )
175
176
 
177
+ specs: SpecsRange | None = pyd.Field(
178
+ default=None, description="Average dataset material performance specifications."
179
+ )
180
+
176
181
 
177
182
  class WithProgramOperatorMixin(pyd.BaseModel):
178
183
  """Object which has a connection to ProgramOperator."""
@@ -279,7 +279,7 @@ class Geography(StrEnum):
279
279
  * ZM: Zambia
280
280
  * ZW: Zimbabwe
281
281
 
282
- USA and Canada subdivisions, see https://en.wikipedia.org/wiki/ISO_3166-1:
282
+ USA states and Canada provinces, see https://en.wikipedia.org/wiki/ISO_3166-1:
283
283
 
284
284
  * CA-AB: Alberta, Canada
285
285
  * CA-BC: British Columbia, Canada
@@ -681,12 +681,15 @@ class WithLciaMixin(BaseOpenEpdSchema):
681
681
  """Mixin for LCIA data."""
682
682
 
683
683
  impacts: Impacts | None = pyd.Field(
684
- description="List of environmental impacts, compiled per one of the standard Impact Assessment methods"
684
+ description="List of environmental impacts, compiled per one of the standard Impact Assessment methods",
685
+ example={"TRACI 2.1": {"gwp": {"A1A2A3": {"mean": 22.4, "unit": "kgCO2e"}}}},
685
686
  )
686
687
  resource_uses: ResourceUseSet | None = pyd.Field(
687
- description="Set of Resource Use Indicators, over various LCA scopes"
688
+ description="Set of Resource Use Indicators, over various LCA scopes",
689
+ example={"RPRe": {"A1A2A3": {"mean": 12, "unit": "MJ", "rsd": 0.12}}},
688
690
  )
689
691
  output_flows: OutputFlowSet | None = pyd.Field(
690
692
  description="Set of Waste and Output Flow indicators which describe the waste categories "
691
- "and other material output flows derived from the LCI."
693
+ "and other material output flows derived from the LCI.",
694
+ example={"hwd": {"A1A2A3": {"mean": 2300, "unit": "kg", "rsd": 0.22}}},
692
695
  )
@@ -91,12 +91,12 @@ class Pcr(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
91
91
  default=None,
92
92
  )
93
93
  date_of_issue: datetime.datetime | None = pyd.Field(
94
- example=datetime.date(day=11, month=2, year=2022),
94
+ example=datetime.datetime(day=11, month=9, year=2019, tzinfo=datetime.timezone.utc),
95
95
  default=None,
96
96
  description="First day on which the document is valid",
97
97
  )
98
98
  valid_until: datetime.datetime | None = pyd.Field(
99
- example=datetime.date(day=11, month=2, year=2024),
99
+ example=datetime.datetime(day=11, month=9, year=2019, tzinfo=datetime.timezone.utc),
100
100
  default=None,
101
101
  description="Last day on which the document is valid",
102
102
  )
@@ -0,0 +1,45 @@
1
+ # Material Extensions
2
+
3
+ This package contains openEPD material extensions. They are used to represent the material properties of the openEPD
4
+ materials, and are a more dynamic, frequently changing part of the standard.
5
+
6
+ ## Versioning
7
+
8
+ Extensions are versioned separately from the openEPD standard or openEPD API.
9
+
10
+ Each material extension is named after the corresponding EC3 product class, and is located in the relevant place
11
+ in the specs tree. For example, `RebarSteel` is nested under `Steel`.
12
+
13
+ Extensions are versioned as Major.Minor, for example "2.4".
14
+
15
+ Rules:
16
+
17
+ 1. Minor versions for the same major version should be backwards compatible.
18
+ 2. Major versions are not compatible between themselves.
19
+ 3. Pydantic models representing versions are named in a pattern SpecNameV1, where 1 is major version and SpecName is
20
+ the name of the material extension.
21
+
22
+ ## Range specs
23
+
24
+ Normal EPDs get singular specs (e.g. `SteelV1`). Single specs can express performance parameters of one concrete
25
+ material/EPD. However, the IndustryEDPs and Generic Estimates often cover a specific segment of the market, and
26
+ include a range of products under the hood, thus demanding for ranges. For example, a single EPD has one `strength_28d`
27
+ of `4000 psi`, but an industry EPD can be applicable to certain concretes in the range of `4000 psi` to `5000 psi`.
28
+
29
+ Range specs are used to express that. Range specs are located in `specs.range` package, and are auto-generated from the
30
+ single specs, please see `make codegen` command and `tools/openepd/codegen/generate_range_spec_models.py`
31
+
32
+ Range specs are created by following general rules:
33
+
34
+ 1. A QuantityStr (such as `QuantityMassKg`) becomes an `AmountRange` of certain type - `AmountRangeMass`
35
+ 2. Float -> RangeFloat, Ratio -> RatioRange, int -> IntRange
36
+ 3. Enums become lists of enums, for example: `cable_trays_material: enums.CableTrayMaterial` in normal spec becomes a
37
+ `cable_trays_material: list[enums.CabeTrayMaterial]` in the range spec.
38
+ 4. Complex objects, strings remain unchanged.
39
+
40
+ This is, however, not always desired. For example, `recarbonation: float` and `recarbonation_z: float` property of CMU
41
+ should not be converted to ranges as these do not make sense.
42
+
43
+ The default rule-base behaviour can be overridden with the
44
+ `CodeGenSpec` class annotation like this: `recarbonation: Annotated[float, CodeGenSpec(override_type=float)]` in single
45
+ spec to make RangeSpec have normal `float` type.
@@ -19,7 +19,7 @@ from openepd.compat.pydantic import pyd
19
19
  from openepd.model.common import OpenEPDUnit
20
20
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
21
21
  from openepd.model.validation.numbers import RatioFloat
22
- from openepd.model.validation.quantity import LengthMmStr, TemperatureCStr, validate_unit_factory
22
+ from openepd.model.validation.quantity import LengthMmStr, TemperatureCStr, validate_quantity_unit_factory
23
23
 
24
24
 
25
25
  class AsphaltMixType(StrEnum):
@@ -79,5 +79,5 @@ class AsphaltV1(BaseOpenEpdHierarchicalSpec):
79
79
  asphalt_pmb: bool | None = pyd.Field(default=None, description="Polymer modified bitumen (PMB)")
80
80
 
81
81
  _aggregate_size_max_validator = pyd.validator("asphalt_aggregate_size_max", allow_reuse=True)(
82
- validate_unit_factory(OpenEPDUnit.m)
82
+ validate_quantity_unit_factory(OpenEPDUnit.m)
83
83
  )
@@ -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
+ import dataclasses
16
17
  from typing import Any
17
18
 
18
19
  from openepd.compat.pydantic import pyd
@@ -51,3 +52,15 @@ class BaseOpenEpdHierarchicalSpec(BaseOpenEpdSpec, WithExtVersionMixin):
51
52
  def setup_external_validators(quantity_validator: QuantityValidator):
52
53
  """Set the implementation unit validator for specs."""
53
54
  ExternalValidationConfig.QUANTITY_VALIDATOR = quantity_validator
55
+
56
+
57
+ @dataclasses.dataclass(kw_only=True)
58
+ class CodegenSpec:
59
+ """
60
+ Specification for codegen when generating RangeSpecs from normal specs.
61
+
62
+ See openepd.mode.specs.README.md for details.
63
+ """
64
+
65
+ exclude_from_codegen: bool = False
66
+ override_type: type
@@ -0,0 +1,95 @@
1
+ #
2
+ # Copyright 2024 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
+
17
+ from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
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 (
30
+ ElectricalTransmissionAndDistributionEquipmentV1,
31
+ )
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(BaseOpenEpdHierarchicalSpec):
57
+ """Material specific specs."""
58
+
59
+ _EXT_VERSION = "1.0"
60
+
61
+ # Nested specs:
62
+ CMU: CMUV1 | None = None
63
+ Masonry: MasonryV1 | None = None
64
+ Steel: SteelV1 | None = None
65
+ NetworkInfrastructure: NetworkInfrastructureV1 | None = None
66
+ Finishes: FinishesV1 | None = None
67
+ ManufacturingInputs: ManufacturingInputsV1 | None = None
68
+ Accessories: AccessoriesV1 | None = None
69
+ ElectricalTransmissionAndDistributionEquipment: ElectricalTransmissionAndDistributionEquipmentV1 | None = None
70
+ Aggregates: AggregatesV1 | None = None
71
+ ThermalMoistureProtection: ThermalMoistureProtectionV1 | None = None
72
+ Mechanical: MechanicalV1 | None = None
73
+ Aluminium: AluminiumV1 | None = None
74
+ Cladding: CladdingV1 | None = None
75
+ FireAndSmokeProtection: FireAndSmokeProtectionV1 | None = None
76
+ PrecastConcrete: PrecastConcreteV1 | None = None
77
+ Asphalt: AsphaltV1 | None = None
78
+ OtherMaterials: OtherMaterialsV1 | None = None
79
+ Plumbing: PlumbingV1 | None = None
80
+ Electrical: ElectricalV1 | None = None
81
+ UtilityPiping: UtilityPipingV1 | None = None
82
+ BulkMaterials: BulkMaterialsV1 | None = None
83
+ CastDecksAndUnderlayment: CastDecksAndUnderlaymentV1 | None = None
84
+ Concrete: ConcreteV1 | None = None
85
+ Sheathing: SheathingV1 | None = None
86
+ Furnishings: FurnishingsV1 | None = None
87
+ Wood: WoodV1 | None = None
88
+ ConveyingEquipment: ConveyingEquipmentV1 | None = None
89
+ MaterialHandling: MaterialHandlingV1 | None = None
90
+ Openings: OpeningsV1 | None = None
91
+ Electricity: ElectricityV1 | None = None
92
+ Grouting: GroutingV1 | None = None
93
+ MechanicalInsulation: MechanicalInsulationV1 | None = None
94
+ OtherElectricalEquipment: OtherElectricalEquipmentV1 | None = None
95
+ WoodJoists: WoodJoistsV1 | None = None
@@ -16,7 +16,7 @@
16
16
  from openepd.compat.pydantic import pyd
17
17
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
18
18
  from openepd.model.specs.generated.enums import CladdingFacingMaterial, CladdingInsulatingMaterial, SidingFormFactor
19
- from openepd.model.validation.quantity import LengthMmStr, LengthMStr, RValueStr, validate_unit_factory
19
+ from openepd.model.validation.quantity import LengthMmStr, LengthMStr, RValueStr, validate_quantity_unit_factory
20
20
 
21
21
 
22
22
  class AluminiumSidingV1(BaseOpenEpdHierarchicalSpec):
@@ -75,7 +75,7 @@ class InsulatedVinylSidingV1(BaseOpenEpdHierarchicalSpec):
75
75
  thickness: LengthMmStr | None = pyd.Field(default=None, description="", example="1 mm")
76
76
 
77
77
  _vinyl_siding_thickness_is_quantity_validator = pyd.validator("thickness", allow_reuse=True)(
78
- validate_unit_factory("m")
78
+ validate_quantity_unit_factory("m")
79
79
  )
80
80
 
81
81
 
@@ -109,7 +109,7 @@ class VinylSidingV1(BaseOpenEpdHierarchicalSpec):
109
109
  thickness: LengthMmStr | None = pyd.Field(default=None, description="", example="5 mm")
110
110
 
111
111
  _vinyl_siding_thickness_is_quantity_validator = pyd.validator("thickness", allow_reuse=True)(
112
- validate_unit_factory("m")
112
+ validate_quantity_unit_factory("m")
113
113
  )
114
114
 
115
115
 
@@ -191,7 +191,7 @@ class CladdingV1(BaseOpenEpdHierarchicalSpec):
191
191
  thickness: LengthMStr | None = pyd.Field(default=None, description="", example="10 mm")
192
192
  facing_material: CladdingFacingMaterial | None = pyd.Field(default=None, description="", example="Steel")
193
193
 
194
- _thickness_is_quantity_validator = pyd.validator("thickness", allow_reuse=True)(validate_unit_factory("m"))
194
+ _thickness_is_quantity_validator = pyd.validator("thickness", allow_reuse=True)(validate_quantity_unit_factory("m"))
195
195
 
196
196
  # Nested specs:
197
197
  Siding: SidingV1 | None = None
@@ -13,10 +13,10 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- from typing import Literal
16
+ from typing import Annotated, Literal
17
17
 
18
18
  from openepd.compat.pydantic import pyd
19
- from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
19
+ from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec, CodegenSpec
20
20
  from openepd.model.specs.concrete import Cementitious, ConcreteTypicalApplication
21
21
  from openepd.model.specs.generated.enums import AciExposureClass, CsaExposureClass, EnExposureClass
22
22
  from openepd.model.validation.numbers import RatioFloat
@@ -25,7 +25,7 @@ from openepd.model.validation.quantity import (
25
25
  LengthMmStr,
26
26
  MassKgStr,
27
27
  PressureMPaStr,
28
- validate_unit_factory,
28
+ validate_quantity_unit_factory,
29
29
  )
30
30
 
31
31
 
@@ -53,7 +53,7 @@ class ConcretePavingV1(BaseOpenEpdHierarchicalSpec):
53
53
  )
54
54
 
55
55
  _concrete_flexion_strength_is_quantity_validator = pyd.validator("flexion_strength", allow_reuse=True)(
56
- validate_unit_factory("MPa")
56
+ validate_quantity_unit_factory("MPa")
57
57
  )
58
58
 
59
59
 
@@ -119,9 +119,10 @@ class ConcreteV1(BaseOpenEpdHierarchicalSpec):
119
119
  description="A strength spec which is to be reached later other 28 days (e.g. 42d)",
120
120
  example="30 MPa",
121
121
  )
122
- strength_other_d: Literal[3, 7, 14, 42, 56, 72, 96, 120] | None = pyd.Field(
123
- default=None, description="Test Day for strength_other", example=42
124
- )
122
+ strength_other_d: Annotated[
123
+ Literal[3, 7, 14, 42, 56, 72, 96, 120] | None,
124
+ CodegenSpec(override_type=Literal[3, 7, 14, 42, 56, 72, 96, 120]),
125
+ ] = pyd.Field(default=None, description="Test Day for strength_other", example=42)
125
126
 
126
127
  slump: LengthInchStr | None = pyd.Field(default=None, description="", example="2 in")
127
128
  min_slump: LengthInchStr | None = pyd.Field(default=None, description="Minimum test slump", example="2 in")
@@ -26,7 +26,7 @@ from openepd.model.validation.quantity import (
26
26
  PowerStr,
27
27
  validate_quantity_ge_factory,
28
28
  validate_quantity_le_factory,
29
- validate_unit_factory,
29
+ validate_quantity_unit_factory,
30
30
  )
31
31
 
32
32
 
@@ -268,7 +268,7 @@ class LightingV1(BaseOpenEpdHierarchicalSpec):
268
268
  validate_quantity_le_factory("1E+04 K")
269
269
  )
270
270
  _typical_utilization_unit_validator = pyd.validator("typical_utilization", allow_reuse=True)(
271
- validate_unit_factory("h / yr")
271
+ validate_quantity_unit_factory("h / yr")
272
272
  )
273
273
  _typical_utilization_quantity_ge_validator = pyd.validator("typical_utilization", allow_reuse=True)(
274
274
  validate_quantity_ge_factory("25 h / yr")
@@ -52,7 +52,7 @@ from openepd.model.validation.quantity import (
52
52
  PressureMPaStr,
53
53
  validate_quantity_ge_factory,
54
54
  validate_quantity_le_factory,
55
- validate_unit_factory,
55
+ validate_quantity_unit_factory,
56
56
  )
57
57
 
58
58
 
@@ -85,10 +85,10 @@ class AccessFlooringV1(BaseOpenEpdHierarchicalSpec):
85
85
 
86
86
  _access_flooring_rolling_load_10_pass_is_quantity_validator = pyd.validator(
87
87
  "rolling_load_10_pass", allow_reuse=True
88
- )(validate_unit_factory("N"))
88
+ )(validate_quantity_unit_factory("N"))
89
89
  _access_flooring_rolling_load_10000_pass_is_quantity_validator = pyd.validator(
90
90
  "rolling_load_10000_pass", allow_reuse=True
91
- )(validate_unit_factory("N"))
91
+ )(validate_quantity_unit_factory("N"))
92
92
 
93
93
 
94
94
  class CarpetV1(BaseOpenEpdHierarchicalSpec):
@@ -114,7 +114,9 @@ class CarpetV1(BaseOpenEpdHierarchicalSpec):
114
114
  gwp_factor_base: GwpKgCo2eStr | None = pyd.Field(default=None, description="", example="1 kgCO2e")
115
115
  gwp_factor_yarn: GwpKgCo2eStr | None = pyd.Field(default=None, description="", example="1 kgCO2e")
116
116
 
117
- _yarn_weight_is_quantity_validator = pyd.validator("yarn_weight", allow_reuse=True)(validate_unit_factory("g / m2"))
117
+ _yarn_weight_is_quantity_validator = pyd.validator("yarn_weight", allow_reuse=True)(
118
+ validate_quantity_unit_factory("g / m2")
119
+ )
118
120
  _yarn_weight_ge_validator = pyd.validator("yarn_weight", allow_reuse=True)(validate_quantity_ge_factory("0 g / m2"))
119
121
 
120
122
 
@@ -348,7 +350,7 @@ class CementBoardV1(BaseOpenEpdHierarchicalSpec):
348
350
  )
349
351
 
350
352
  _cement_board_thickness_is_quantity_validator = pyd.validator("thickness", allow_reuse=True)(
351
- validate_unit_factory("m")
353
+ validate_quantity_unit_factory("m")
352
354
  )
353
355
 
354
356
 
@@ -456,7 +458,9 @@ class GypsumV1(BaseOpenEpdHierarchicalSpec):
456
458
  moisture_resistant: bool | None = pyd.Field(default=None, description="", example=True)
457
459
  abuse_resistant: bool | None = pyd.Field(default=None, description="", example=True)
458
460
 
459
- _gypsum_r_factor_is_quantity_validator = pyd.validator("r_factor", allow_reuse=True)(validate_unit_factory("RSI"))
461
+ _gypsum_r_factor_is_quantity_validator = pyd.validator("r_factor", allow_reuse=True)(
462
+ validate_quantity_unit_factory("RSI")
463
+ )
460
464
 
461
465
  # Nested specs:
462
466
  GypsumSupports: GypsumSupportsV1 | None = None
@@ -15,7 +15,11 @@
15
15
  #
16
16
  from openepd.compat.pydantic import pyd
17
17
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
18
- from openepd.model.validation.quantity import PressureMPaStr, validate_quantity_ge_factory, validate_unit_factory
18
+ from openepd.model.validation.quantity import (
19
+ PressureMPaStr,
20
+ validate_quantity_ge_factory,
21
+ validate_quantity_unit_factory,
22
+ )
19
23
 
20
24
 
21
25
  class GMUV1(BaseOpenEpdHierarchicalSpec):
@@ -35,7 +39,7 @@ class AutoclavedAeratedConcreteV1(BaseOpenEpdHierarchicalSpec):
35
39
  white: bool | None = pyd.Field(default=None, description="", example=True)
36
40
 
37
41
  _aac_thermal_conductivity_is_quantity_validator = pyd.validator("thermal_conductivity", allow_reuse=True)(
38
- validate_unit_factory("W / (m * K)")
42
+ validate_quantity_unit_factory("W / (m * K)")
39
43
  )
40
44
 
41
45
  _aac_thermal_conductivity_min_validator = pyd.validator("thermal_conductivity", allow_reuse=True)(
@@ -28,7 +28,12 @@ from openepd.model.specs.generated.enums import (
28
28
  RackType,
29
29
  )
30
30
  from openepd.model.specs.generated.mixins.conduit_mixin import ConduitMixin
31
- from openepd.model.validation.quantity import ElectricalCurrentStr, LengthMmStr, MassKgStr, validate_unit_factory
31
+ from openepd.model.validation.quantity import (
32
+ ElectricalCurrentStr,
33
+ LengthMmStr,
34
+ MassKgStr,
35
+ validate_quantity_unit_factory,
36
+ )
32
37
 
33
38
 
34
39
  class PDUV1(BaseOpenEpdHierarchicalSpec):
@@ -43,7 +48,7 @@ class PDUV1(BaseOpenEpdHierarchicalSpec):
43
48
  pdu_technology: PduTechnology | None = pyd.Field(default=None, description="", example="Basic")
44
49
  pdu_outlets: int | None = pyd.Field(default=None, description="", example=3, le=200)
45
50
 
46
- _amperage_is_quantity_validator = pyd.validator("amperage", allow_reuse=True)(validate_unit_factory("A"))
51
+ _amperage_is_quantity_validator = pyd.validator("amperage", allow_reuse=True)(validate_quantity_unit_factory("A"))
47
52
 
48
53
 
49
54
  class CabinetsRacksAndEnclosuresV1(BaseOpenEpdHierarchicalSpec):
@@ -26,7 +26,7 @@ from openepd.model.specs.generated.enums import (
26
26
  ThermalSeparation,
27
27
  )
28
28
  from openepd.model.validation.numbers import RatioFloat
29
- from openepd.model.validation.quantity import LengthMmStr, PressureMPaStr, SpeedStr, validate_unit_factory
29
+ from openepd.model.validation.quantity import LengthMmStr, PressureMPaStr, SpeedStr, validate_quantity_unit_factory
30
30
 
31
31
 
32
32
  class GlazingIntendedApplication(BaseOpenEpdSchema):
@@ -197,7 +197,7 @@ class FlatGlassPanesV1(BaseOpenEpdHierarchicalSpec):
197
197
  thickness: FlatGlassPanesThickness | None = pyd.Field(default=None, example="12 mm")
198
198
 
199
199
  _flat_glass_panes_thickness_is_quantity_validator = pyd.validator("thickness", allow_reuse=True)(
200
- validate_unit_factory("m")
200
+ validate_quantity_unit_factory("m")
201
201
  )
202
202
 
203
203
 
@@ -380,10 +380,10 @@ class NAFSFenestrationV1(BaseOpenEpdHierarchicalSpec, GlazingOptionsMixin):
380
380
  )
381
381
 
382
382
  _assembly_u_factor_is_quantity_validator = pyd.validator("assembly_u_factor", allow_reuse=True)(
383
- validate_unit_factory("USI")
383
+ validate_quantity_unit_factory("USI")
384
384
  )
385
385
  _nafs_performance_grade_is_quantity_validator = pyd.validator("performance_grade", allow_reuse=True)(
386
- validate_unit_factory("psf")
386
+ validate_quantity_unit_factory("psf")
387
387
  )
388
388
 
389
389
  # Nested specs:
@@ -424,8 +424,12 @@ class InsulatingGlazingUnitsV1(BaseOpenEpdHierarchicalSpec, GlazingOptionsMixin)
424
424
  default=None, description="Spacer material for Integrated Glass Unit.", example="Aluminium"
425
425
  )
426
426
 
427
- _dp_rating_is_quantity_validator = pyd.validator("dp_rating", allow_reuse=True)(validate_unit_factory("MPa"))
428
- _cog_u_factor_is_quantity_validator = pyd.validator("cog_u_factor", allow_reuse=True)(validate_unit_factory("USI"))
427
+ _dp_rating_is_quantity_validator = pyd.validator("dp_rating", allow_reuse=True)(
428
+ validate_quantity_unit_factory("MPa")
429
+ )
430
+ _cog_u_factor_is_quantity_validator = pyd.validator("cog_u_factor", allow_reuse=True)(
431
+ validate_quantity_unit_factory("USI")
432
+ )
429
433
 
430
434
 
431
435
  class CurtainWallsV1(BaseOpenEpdHierarchicalSpec):
@@ -16,7 +16,7 @@
16
16
  from openepd.compat.pydantic import pyd
17
17
  from openepd.model.specs.base import BaseOpenEpdHierarchicalSpec
18
18
  from openepd.model.specs.generated.enums import GypsumFacing, GypsumFireRating, GypsumThickness
19
- from openepd.model.validation.quantity import LengthMmStr, validate_unit_factory
19
+ from openepd.model.validation.quantity import LengthMmStr, validate_quantity_unit_factory
20
20
 
21
21
 
22
22
  class CementitiousSheathingBoardV1(BaseOpenEpdHierarchicalSpec):
@@ -33,7 +33,7 @@ class CementitiousSheathingBoardV1(BaseOpenEpdHierarchicalSpec):
33
33
  cement_board_thickness: LengthMmStr | None = pyd.Field(default=None, description="", example="10 mm")
34
34
 
35
35
  _cement_board_thickness_is_quantity_validator = pyd.validator("cement_board_thickness", allow_reuse=True)(
36
- validate_unit_factory("m")
36
+ validate_quantity_unit_factory("m")
37
37
  )
38
38
 
39
39
 
@@ -65,8 +65,12 @@ class GypsumSheathingBoardV1(BaseOpenEpdHierarchicalSpec):
65
65
  moisture_resistant: bool | None = pyd.Field(default=None, description="", example=True)
66
66
  abuse_resistant: bool | None = pyd.Field(default=None, description="", example=True)
67
67
 
68
- _gypsum_thickness_is_quantity_validator = pyd.validator("thickness", allow_reuse=True)(validate_unit_factory("m"))
69
- _gypsum_r_factor_is_quantity_validator = pyd.validator("r_factor", allow_reuse=True)(validate_unit_factory("RSI"))
68
+ _gypsum_thickness_is_quantity_validator = pyd.validator("thickness", allow_reuse=True)(
69
+ validate_quantity_unit_factory("m")
70
+ )
71
+ _gypsum_r_factor_is_quantity_validator = pyd.validator("r_factor", allow_reuse=True)(
72
+ validate_quantity_unit_factory("RSI")
73
+ )
70
74
 
71
75
 
72
76
  class SheathingV1(BaseOpenEpdHierarchicalSpec):