openepd 0.5.0__tar.gz → 0.7.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.
- {openepd-0.5.0 → openepd-0.7.0}/PKG-INFO +1 -1
- {openepd-0.5.0 → openepd-0.7.0}/pyproject.toml +2 -2
- {openepd-0.5.0 → openepd-0.7.0}/src/openepd/__version__.py +1 -1
- {openepd-0.5.0 → openepd-0.7.0}/src/openepd/model/common.py +35 -4
- {openepd-0.5.0 → openepd-0.7.0}/src/openepd/model/epd.py +88 -4
- {openepd-0.5.0 → openepd-0.7.0}/src/openepd/model/lcia.py +32 -0
- {openepd-0.5.0 → openepd-0.7.0}/LICENSE +0 -0
- {openepd-0.5.0 → openepd-0.7.0}/README.md +0 -0
- {openepd-0.5.0 → openepd-0.7.0}/src/openepd/__init__.py +0 -0
- {openepd-0.5.0 → openepd-0.7.0}/src/openepd/model/__init__.py +0 -0
- {openepd-0.5.0 → openepd-0.7.0}/src/openepd/model/base.py +0 -0
- {openepd-0.5.0 → openepd-0.7.0}/src/openepd/model/org.py +0 -0
- {openepd-0.5.0 → openepd-0.7.0}/src/openepd/model/pcr.py +0 -0
- {openepd-0.5.0 → openepd-0.7.0}/src/openepd/model/specs/__init__.py +0 -0
- {openepd-0.5.0 → openepd-0.7.0}/src/openepd/model/specs/concrete.py +0 -0
- {openepd-0.5.0 → openepd-0.7.0}/src/openepd/model/standard.py +0 -0
- {openepd-0.5.0 → openepd-0.7.0}/src/openepd/py.typed +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "openepd"
|
3
|
-
version = "0.
|
3
|
+
version = "0.7.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>"]
|
@@ -56,7 +56,7 @@ types-deprecated = ">=1.2.9"
|
|
56
56
|
|
57
57
|
|
58
58
|
[tool.commitizen]
|
59
|
-
version = "0.
|
59
|
+
version = "0.7.0"
|
60
60
|
bump_version = "bump: version $current_version → $new_version"
|
61
61
|
update_changelog_on_bump = true
|
62
62
|
pre_bump_hooks = []
|
@@ -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.
|
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/",
|
@@ -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
|
-
|
108
|
-
description="
|
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.
|
@@ -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")
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|