openepd 0.5.0__py3-none-any.whl → 0.7.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.
openepd/__version__.py CHANGED
@@ -17,4 +17,4 @@
17
17
  # Charles Pankow Foundation, Microsoft Sustainability Fund, Interface, MKA Foundation, and others.
18
18
  # Find out more at www.BuildingTransparency.org
19
19
  #
20
- VERSION = "0.5.0"
20
+ VERSION = "0.7.0"
openepd/model/common.py CHANGED
@@ -17,9 +17,10 @@
17
17
  # Charles Pankow Foundation, Microsoft Sustainability Fund, Interface, MKA Foundation, and others.
18
18
  # Find out more at www.BuildingTransparency.org
19
19
  #
20
- from typing import Annotated
20
+ from typing import Annotated, Any
21
21
 
22
22
  import pydantic as pyd
23
+ from pydantic import root_validator
23
24
 
24
25
  from openepd.model.base import BaseOpenEpdSchema
25
26
 
@@ -27,8 +28,19 @@ from openepd.model.base import BaseOpenEpdSchema
27
28
  class Amount(BaseOpenEpdSchema):
28
29
  """A value-and-unit pairing for amounts that do not have an uncertainty."""
29
30
 
30
- qty: float | None = pyd.Field(description="How much of this in the amount.")
31
- unit: str = pyd.Field(description="Which unit. SI units are preferred.", example="kg")
31
+ qty: float | None = pyd.Field(description="How much of this in the amount.", default=None)
32
+ unit: str | None = pyd.Field(description="Which unit. SI units are preferred.", example="kg", default=None)
33
+
34
+ @root_validator
35
+ def check_qty_or_unit(cls, values: dict[str, Any]):
36
+ """Ensure that qty or unit is provided."""
37
+ if values["qty"] is None and values["unit"] is None:
38
+ raise ValueError("Either qty or unit must be provided.")
39
+ return values
40
+
41
+ def to_quantity_str(self):
42
+ """Return a string representation of the amount."""
43
+ return f"{self.qty or ''} {self.unit or 'str'}".strip()
32
44
 
33
45
 
34
46
  class Measurement(BaseOpenEpdSchema):
@@ -42,10 +54,29 @@ class Measurement(BaseOpenEpdSchema):
42
54
  dist: str | None = pyd.Field(description="Statistical distribution of the measurement error.", default=None)
43
55
 
44
56
 
57
+ class Ingredient(BaseOpenEpdSchema):
58
+ """
59
+ An ingredient of a product.
60
+
61
+ The Ingredients list gives the core data references and quantities. This list is used to document supply-chain
62
+ transparency, such as the EPDs of major components (e.g. cement in concrete, or recycled steel
63
+ in hot-rolled sections).
64
+ """
65
+
66
+ qty: float | None = pyd.Field(
67
+ description="Number of declared units of this consumed. Negative values indicate an outflow."
68
+ )
69
+ link: pyd.AnyUrl | None = pyd.Field(
70
+ description="Link to this object's OpenEPD declaration. "
71
+ "An OpenIndustryEPD or OpenLCI link is also acceptable.",
72
+ default=None,
73
+ )
74
+
75
+
45
76
  class WithAttachmentsMixin:
46
77
  """Mixin for objects that can have attachments."""
47
78
 
48
- attachments: dict[Annotated[str, pyd.constr(max_length=200)], pyd.AnyUrl] | None = pyd.Field(
79
+ attachments: dict[Annotated[str, pyd.Field(max_length=200)], pyd.AnyUrl] | None = pyd.Field(
49
80
  description="Dict of URLs relevant to this entry",
50
81
  example={
51
82
  "Contact Us": "https://www.c-change-labs.com/en/contact-us/",
openepd/model/epd.py CHANGED
@@ -18,14 +18,15 @@
18
18
  # Find out more at www.BuildingTransparency.org
19
19
  #
20
20
  import datetime
21
- from typing import Literal
21
+ from typing import Annotated, Literal
22
22
 
23
23
  import pydantic as pyd
24
24
 
25
25
  from openepd.model.base import BaseOpenEpdSchema
26
- from openepd.model.common import Amount
26
+ from openepd.model.common import Amount, Ingredient
27
27
  from openepd.model.lcia import ImpactSet, OutputFlowSet, ResourceUseSet
28
28
  from openepd.model.org import Org, Plant
29
+ from openepd.model.pcr import Pcr
29
30
  from openepd.model.specs import Specs
30
31
  from openepd.model.standard import Standard
31
32
 
@@ -46,7 +47,23 @@ class Epd(BaseOpenEpdSchema):
46
47
  product_name: str = pyd.Field(
47
48
  max_length=200, description="The name of the product described by this EPD", example="Mix 12345AC"
48
49
  )
50
+ product_sku: str | None = pyd.Field(
51
+ max_length=200, description="Unique stock keeping identifier assigned by manufacturer"
52
+ )
53
+ product_description: str | None = pyd.Field(
54
+ max_length=2000,
55
+ description="1-paragraph description of product. " "Supports plain text or github flavored markdown.",
56
+ )
49
57
  # TODO: add product_alt_names? E.g. ILCD has a list of synonymous names
58
+ product_classes: dict[str, str] = pyd.Field(
59
+ description="List of classifications, including Masterformat and UNSPC", default_factory=dict
60
+ )
61
+ product_image_small: pyd.AnyUrl | None = pyd.Field(
62
+ description="Pointer to image illustrating the product, which is no more than 200x200 pixels", default=None
63
+ )
64
+ product_image: pyd.AnyUrl | None = pyd.Field(
65
+ description="pointer to image illustrating the product no more than 10MB", default=None
66
+ )
50
67
  version: pyd.PositiveInt = pyd.Field(
51
68
  description="Version of this document. The document's issuer should increment it anytime even a single "
52
69
  "character changes, as this value is used to determine the most recent version.",
@@ -104,14 +121,76 @@ class Epd(BaseOpenEpdSchema):
104
121
  example=datetime.date(day=11, month=9, year=2028),
105
122
  description="Last date the EPD is valid on, including any extensions.",
106
123
  )
107
- product_class: dict[str, str] = pyd.Field(
108
- description="List of classifications, including Masterformat and UNSPC", default_factory=dict
124
+ pcr: Pcr | None = pyd.Field(
125
+ description="JSON object for product category rules. Should point to the "
126
+ "most-specific PCR that applies; the PCR entry should point to any "
127
+ "parent PCR.",
128
+ default=None,
109
129
  )
110
130
  declared_unit: Amount | None = pyd.Field(
111
131
  description="SI declared unit for this EPD. If a functional unit is "
112
132
  "utilized, the declared unit shall refer to the amount of "
113
133
  "product associated with the A1-A3 life cycle stage."
114
134
  )
135
+ kg_per_declared_unit: Amount | None = pyd.Field(
136
+ default=None,
137
+ description="Mass of the product, in kilograms, per declared unit",
138
+ example=Amount(qty=12.5, unit="kg"),
139
+ )
140
+ kg_C_per_declared_unit: Amount | None = pyd.Field(
141
+ default=None,
142
+ description="Mass of elemental carbon, per declared unit, contained in the product itself at the manufacturing "
143
+ "facility gate. Used (among other things) to check a carbon balance or calculate incineration "
144
+ "emissions. The source of carbon (e.g. biogenic) is not relevant in this field.",
145
+ example=Amount(qty=8.76, unit="kg"),
146
+ )
147
+ kg_C_biogenic_per_declared_unit: Amount | None = pyd.Field(
148
+ default=None,
149
+ description="Mass of elemental carbon from biogenic sources, per declared unit, contained in the product "
150
+ "itself at the manufacturing facility gate. It may be presumed that any biogenic carbon content "
151
+ "has been accounted for as -44/12 kgCO2e per kg C in stages A1-A3, per EN15804 and ISO 21930.",
152
+ example=Amount(qty=8.76, unit="kg"),
153
+ )
154
+ product_service_life_years: float | None = pyd.Field(
155
+ gt=0.0009,
156
+ lt=101,
157
+ description="Reference service life of the product, in years. Serves as a maximum for replacement interval, "
158
+ "which may also be constrained by usage or the service life of what the product goes into "
159
+ "(e.g. a building).",
160
+ example=50.0,
161
+ )
162
+ annual_production: float | None = pyd.Field(
163
+ gt=0,
164
+ default=None,
165
+ description="Approximate annual production volume, in declared units, of product covered by this EPD. "
166
+ "This value is intended to be used for weighting of averages. "
167
+ "Providing this data is optional, and it is acceptable to round or obfuscate it downwards "
168
+ "(but not upwards) by any amount desired to protect confidentiality. For example, if the "
169
+ "product volume is 123,456 m3, a value of 120,000, 100,000 or even 87,654 would be acceptable.",
170
+ example=10000,
171
+ )
172
+ applicable_in: list[Annotated[str, pyd.Field(min_length=2, max_length=2)]] | None = pyd.Field(
173
+ max_items=100,
174
+ default=None,
175
+ description="Jurisdiction(s) in which EPD is applicable. An empty array, or absent properties, "
176
+ "implies global applicability.",
177
+ example=["US", "CA", "MX"],
178
+ )
179
+ product_usage_description: str | None = pyd.Field(
180
+ default=None,
181
+ description="Text description of how product is typically used. Can be used to describe accessories "
182
+ "like fasteners, adhesives, etc. Supports plain text or github flavored markdown.",
183
+ )
184
+ product_usage_image: pyd.AnyUrl | None = pyd.Field(
185
+ description="Pointer (url) to image illustrating how the product is used. No more than 10MB.", default=None
186
+ )
187
+ manufacturing_description: str | None = pyd.Field(
188
+ default=None,
189
+ description="Text description of manufacturing process. Supports plain text or github flavored markdown.",
190
+ )
191
+ manufacturing_image: pyd.AnyUrl | None = pyd.Field(
192
+ description="Pointer (url) to an image illustrating the manufacturing process. No more than 10MB.", default=None
193
+ )
115
194
  impacts: ImpactSet | None = pyd.Field(
116
195
  description="List of environmental impacts, compiled per one of the standard Impact Assessment methods"
117
196
  )
@@ -129,6 +208,11 @@ class Epd(BaseOpenEpdSchema):
129
208
  default_factory=Specs,
130
209
  description="Data structure(s) describing performance specs of product. Unique for each material type.",
131
210
  )
211
+ includes: list[Ingredient] = pyd.Field(
212
+ max_items=255,
213
+ description="List of JSON objects pointing to product components. "
214
+ "Each one should be an EPD or digitized LCI process.",
215
+ )
132
216
  lca_discussion: str | None = pyd.Field(
133
217
  max_length=20000,
134
218
  description="""A rich text description containing information for experts reviewing the EPD contents.
openepd/model/lcia.py CHANGED
@@ -161,9 +161,41 @@ class ResourceUseSet(BaseOpenEpdSchema):
161
161
  description="Renewable primary resources with energy content used as material. "
162
162
  "First use biobased materials used as materials (e.g. wood, hemp, etc.)."
163
163
  )
164
+ rpre: ScopeSet | None = pyd.Field(description="Renewable primary energy resources as energy")
165
+ nrpre: ScopeSet | None = pyd.Field(description="Non-renewable primary resources as energy (fuel)")
166
+ nrprm: ScopeSet | None = pyd.Field(description="Non-renewable primary resources as material")
167
+ fw: ScopeSet | None = pyd.Field(description="Use of net fresh water")
168
+ sm: ScopeSet | None = pyd.Field(description="Use of secondary materials")
169
+ rsf: ScopeSet | None = pyd.Field(description="Use of renewable secondary materials")
170
+ nrsf: ScopeSet | None = pyd.Field(description="Use of non-renewable secondary fuels")
171
+ re: ScopeSet | None = pyd.Field(description="Renewable energy resources")
172
+ pere: ScopeSet | None = pyd.Field(
173
+ description="Use of renewable primary energy excluding renewable primary energy resources used as raw materials"
174
+ )
175
+ perm: ScopeSet | None = pyd.Field(description="Use of renewable primary energy resources used as raw materials")
176
+ pert: ScopeSet | None = pyd.Field(description="Total use of renewable primary energy resources")
177
+ penre: ScopeSet | None = pyd.Field(
178
+ description="Use of non-renewable primary energy excluding "
179
+ "non-renewable primary energy resources used as raw materials"
180
+ )
181
+ penrm: ScopeSet | None = pyd.Field(
182
+ description="Use of non-renewable primary energy resources used as raw materials"
183
+ )
184
+ penrt: ScopeSet | None = pyd.Field(description="Total use of non-renewable primary energy resources")
164
185
 
165
186
 
166
187
  class OutputFlowSet(BaseOpenEpdSchema):
167
188
  """A set of output flows, such as waste, emissions, etc."""
168
189
 
190
+ twd: ScopeSet | None = pyd.Field(description="Total waste disposed")
169
191
  hwd: ScopeSet | None = pyd.Field(description="Hazardous waste disposed")
192
+ nhwd: ScopeSet | None = pyd.Field(description="Non-hazardous waste disposed")
193
+ rwd: ScopeSet | None = pyd.Field(description="Radioactive waste disposed")
194
+ hlrw: ScopeSet | None = pyd.Field(description="High level radioactive waste disposed")
195
+ illrw: ScopeSet | None = pyd.Field(description="Intermediate level radioactive waste disposed")
196
+ cru: ScopeSet | None = pyd.Field(description="Components for re-use")
197
+ mr: ScopeSet | None = pyd.Field(description="Recycled materials")
198
+ mfr: ScopeSet | None = pyd.Field(description="Materials for recycling")
199
+ mer: ScopeSet | None = pyd.Field(description="Materials for energy recovery")
200
+ ee: ScopeSet | None = pyd.Field(description="Exported energy")
201
+ eh: ScopeSet | None = pyd.Field(description="Exported heat")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openepd
3
- Version: 0.5.0
3
+ Version: 0.7.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
@@ -1,17 +1,17 @@
1
1
  openepd/__init__.py,sha256=7_QNx3x9RCC7Lj9fHDD3nzgq6wV4Fmou7OJNWkCPnEw,837
2
- openepd/__version__.py,sha256=W-cUOz3Kd4Y0fNfP8od85NJYsHvb_CEOB5cpCkfgfKc,855
2
+ openepd/__version__.py,sha256=EWxgjDC9N7_wuwhU2q4zPuk76h4G-bp8tM3RuSg6hCI,855
3
3
  openepd/model/__init__.py,sha256=7_QNx3x9RCC7Lj9fHDD3nzgq6wV4Fmou7OJNWkCPnEw,837
4
4
  openepd/model/base.py,sha256=CO6EVmeHV5uOfLdfjApwOSS9gkCgul-CEDScVzfgXI8,2613
5
- openepd/model/common.py,sha256=DomuW6IRVyOpbo-wJbrPpWa7ig65OzAncSzA63Imyy8,2178
6
- openepd/model/epd.py,sha256=_BZoLHOz8uDL0lDqHAYgPxOiJ39hHfWCbVAdXqIhWWk,7763
7
- openepd/model/lcia.py,sha256=N_2TukAXKCz5ztWHRdOrw2UVmAhLn9WN9x9y6_BJ6Uw,8887
5
+ openepd/model/common.py,sha256=SBapGDMDNbxwdcJa69TXS72FfesiVQbKJGJqKLj9Jp0,3365
6
+ openepd/model/epd.py,sha256=ghctWN5_jiz6TbLFAlj3jef2Up-oM23fCUXwihoWDn8,12201
7
+ openepd/model/lcia.py,sha256=UHKi676voHLluY4iLqVIMD-B0UxQrgssyEKMneatD6c,11266
8
8
  openepd/model/org.py,sha256=e9acmGS6p1M77gC6mrn-8ErADs7xvpLWOltWOx0AFOg,3811
9
9
  openepd/model/pcr.py,sha256=RmOB9YLkNJIU1RAvHSDzOOIL3ux3FmJo60oCkXXu_08,2855
10
10
  openepd/model/specs/__init__.py,sha256=JmbvfzwO0QmNcrxGqYE_KjvYHBmc2bnDm7cyZsaMG78,1137
11
11
  openepd/model/specs/concrete.py,sha256=v9Jyi6NiaoMA2ELX_wMckgTAtRplEOaIkZrd2qEdiN4,3342
12
12
  openepd/model/standard.py,sha256=Nv_H3Lhu79y4waWzzKpwODCPPGrlWwRIl6eZBMSc9Qg,1519
13
13
  openepd/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- openepd-0.5.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
15
- openepd-0.5.0.dist-info/METADATA,sha256=1qIqE18NQmX4Qwn1ih_Q_85F-kwGPbxkLvRN5o3QxdM,3723
16
- openepd-0.5.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
17
- openepd-0.5.0.dist-info/RECORD,,
14
+ openepd-0.7.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
15
+ openepd-0.7.0.dist-info/METADATA,sha256=TiziIco25lC8TV3aBmA9Ivc-yLtbeThSiO90QxWAqoI,3723
16
+ openepd-0.7.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
17
+ openepd-0.7.0.dist-info/RECORD,,