openepd 0.10.0__tar.gz → 0.11.1__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.
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openepd
3
- Version: 0.10.0
3
+ Version: 0.11.1
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
7
7
  Author: C-Change Labs
8
8
  Author-email: support@c-change-labs.com
9
9
  Maintainer: C-Change Labs
10
- Maintainer-email: support@c-change-labs.com
10
+ Maintainer-email: open-source@c-change-labs.com
11
11
  Requires-Python: >=3.11,<4.0
12
12
  Classifier: Development Status :: 3 - Alpha
13
13
  Classifier: Intended Audience :: Developers
@@ -1,10 +1,10 @@
1
1
  [tool.poetry]
2
2
  name = "openepd"
3
- version = "0.10.0"
3
+ version = "0.11.1"
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>"]
7
- maintainers = ["C-Change Labs <support@c-change-labs.com>"]
7
+ maintainers = ["C-Change Labs <open-source@c-change-labs.com>"]
8
8
  repository = "https://github.com/cchangelabs/openepd"
9
9
  keywords = []
10
10
  classifiers = [
@@ -34,9 +34,9 @@ pytest = "~=7.2"
34
34
  pytest-subtests = "~=0.4"
35
35
  pytest-cov = "~=4.0"
36
36
  teamcity-messages = ">=1.31"
37
+ wheel = "~=0.40.0"
37
38
 
38
39
  # Dev tools
39
- #virtualenv = "<=20.23.0" # to avoid conflict with flake plugins
40
40
  black = "~=22.3"
41
41
  licenseheaders = "~=0.8"
42
42
  flake8 = "~=4.0"
@@ -56,7 +56,7 @@ types-deprecated = ">=1.2.9"
56
56
 
57
57
 
58
58
  [tool.commitizen]
59
- version = "0.10.0"
59
+ version = "0.11.1"
60
60
  bump_version = "bump: version $current_version → $new_version"
61
61
  update_changelog_on_bump = true
62
62
  pre_bump_hooks = []
@@ -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.10.0"
20
+ VERSION = "0.11.1"
@@ -18,7 +18,8 @@
18
18
  # Find out more at www.BuildingTransparency.org
19
19
  #
20
20
  import abc
21
- from typing import Optional, Type, TypeVar
21
+ import json
22
+ from typing import Any, Optional, Type, TypeVar
22
23
 
23
24
  import pydantic
24
25
  from pydantic.generics import GenericModel
@@ -38,6 +39,14 @@ class BaseOpenEpdSchema(pydantic.BaseModel):
38
39
  allow_population_by_field_name = True
39
40
  use_enum_values = True
40
41
 
42
+ def to_serializable(self, *args, **kwargs) -> dict[str, Any]:
43
+ """
44
+ Return a serializable dict representation of the DTO.
45
+
46
+ It expects the same arguments as the pydantic.BaseModel.json() method.
47
+ """
48
+ return json.loads(self.json(*args, **kwargs))
49
+
41
50
  def has_values(self) -> bool:
42
51
  """Return True if the model has any values."""
43
52
  return len(self.dict(exclude_unset=True, exclude_none=True)) > 0
@@ -72,6 +81,8 @@ class BaseOpenEpdSchema(pydantic.BaseModel):
72
81
  :raise ValueError: if the value cannot be converted to the target type.
73
82
  """
74
83
  value = self.get_ext_field(key, default)
84
+ if value is None:
85
+ return None # type: ignore
75
86
  if issubclass(target_type, pydantic.BaseModel) and isinstance(value, dict):
76
87
  return target_type.construct(**value) # type: ignore
77
88
  elif isinstance(value, target_type):
@@ -73,6 +73,25 @@ class Ingredient(BaseOpenEpdSchema):
73
73
  )
74
74
 
75
75
 
76
+ class LatLng(BaseOpenEpdSchema):
77
+ """A latitude and longitude."""
78
+
79
+ lat: float = pyd.Field(description="Latitude", example=47.6062)
80
+ lng: float = pyd.Field(description="Longitude", example=-122.3321)
81
+
82
+
83
+ class Location(BaseOpenEpdSchema):
84
+ """A location on the Earth's surface."""
85
+
86
+ pluscode: str | None = pyd.Field(default=None, description="Open Location code of this location")
87
+ latlng: LatLng | None = pyd.Field(default=None, description="Latitude and longitude of this location")
88
+ address: str | None = pyd.Field(default=None, description="Text address, preferably geocoded")
89
+ country: str | None = pyd.Field(default=None, description="2-alpha country code")
90
+ jurisdiction: str | None = pyd.Field(
91
+ default=None, description="Province, State, or similar subdivision below the level of a country"
92
+ )
93
+
94
+
76
95
  class WithAttachmentsMixin(BaseModel):
77
96
  """Mixin for objects that can have attachments."""
78
97
 
@@ -18,7 +18,7 @@
18
18
  # Find out more at www.BuildingTransparency.org
19
19
  #
20
20
  import datetime
21
- from typing import Annotated, Literal
21
+ from typing import Annotated
22
22
 
23
23
  import pydantic as pyd
24
24
 
@@ -35,16 +35,17 @@ class Epd(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
35
35
  """Represent an EPD."""
36
36
 
37
37
  # TODO: Add validator for open-xpd-uuid on this field
38
- id: str = pyd.Field(
38
+ id: str | None = pyd.Field(
39
39
  description="The unique ID for this EPD. To ensure global uniqueness, should be registered at "
40
40
  "open-xpd-uuid.cqd.io/register or a coordinating registry.",
41
41
  example="1u7zsed8",
42
+ default=None,
42
43
  )
43
- doctype: Literal["OpenEPD", "ILCD_EPD"] = pyd.Field(
44
+ doctype: str = pyd.Field(
44
45
  description='Describes the type and schema of the document. Must always always read "openEPD".',
45
46
  default="OpenEPD",
46
47
  )
47
- product_name: str = pyd.Field(
48
+ product_name: str | None = pyd.Field(
48
49
  max_length=200, description="The name of the product described by this EPD", example="Mix 12345AC"
49
50
  )
50
51
  product_sku: str | None = pyd.Field(
@@ -64,10 +65,11 @@ class Epd(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
64
65
  product_image: pyd.AnyUrl | None = pyd.Field(
65
66
  description="pointer to image illustrating the product no more than 10MB", default=None
66
67
  )
67
- version: pyd.PositiveInt = pyd.Field(
68
+ version: pyd.PositiveInt | None = pyd.Field(
68
69
  description="Version of this document. The document's issuer should increment it anytime even a single "
69
70
  "character changes, as this value is used to determine the most recent version.",
70
71
  example=1,
72
+ default=None,
71
73
  )
72
74
  language: str | None = pyd.Field(
73
75
  min_length=2,
@@ -89,11 +91,20 @@ class Epd(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
89
91
  description="Link to data object on original registrar's site",
90
92
  example="https://epd-online.com/EmbeddedEpdList/Download/6029",
91
93
  )
92
- # ilcd_uuid: str | None = pyd.Field(description="An optional UUID (for use with ILCD and similar systems)")
93
94
  manufacturer: Org | None = pyd.Field(
94
95
  description="JSON object for declaring Org. Sometimes called the "
95
96
  '"Declaration Holder" or "Declaration Owner".'
96
97
  )
98
+ epd_developer: Org | None = pyd.Field(
99
+ description="The organization responsible for the underlying LCA (and subsequent summarization as EPD).",
100
+ default=None,
101
+ )
102
+ epd_developer_email: pyd.EmailStr | None = pyd.Field(
103
+ default=None,
104
+ example="john.doe@we-do-lca.com",
105
+ description="Email contact for inquiries about development of this EPD. "
106
+ "This must be an email which can be publicly shared.",
107
+ )
97
108
  plants: list[Plant] = pyd.Field(
98
109
  max_items=32,
99
110
  description="List of object(s) for one or more plant(s) that this declaration applies to.",
@@ -113,6 +124,9 @@ class Epd(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
113
124
  description="Optional link to a verification statement.",
114
125
  example="https://we-verify-epds.com/en/letters/123-456.789b.pdf",
115
126
  )
127
+ third_party_verifier_email: pyd.EmailStr | None = pyd.Field(
128
+ description="Email address of the third party verifier", example="john.doe@example.com", default=None
129
+ )
116
130
  date_of_issue: datetime.date | None = pyd.Field(
117
131
  example=datetime.date(day=11, month=9, year=2019),
118
132
  description="Date the EPD was issued. This should be the first day on which the EPD is valid.",
@@ -212,6 +226,7 @@ class Epd(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
212
226
  max_items=255,
213
227
  description="List of JSON objects pointing to product components. "
214
228
  "Each one should be an EPD or digitized LCI process.",
229
+ default_factory=list,
215
230
  )
216
231
  lca_discussion: str | None = pyd.Field(
217
232
  max_length=20000,
@@ -42,14 +42,28 @@ class EolScenario(BaseOpenEpdSchema):
42
42
  description="The weigting of this scenario used in the C1 .. C4 dataset. For example, the overall C1 shoudl be "
43
43
  "equal to weighted sum of C1 from all the scenarios, weighted by likelihood.",
44
44
  example=0.33,
45
+ default=None,
46
+ )
47
+ C1: Measurement | None = pyd.Field(
48
+ description="Deconstruction and Demolition under this scenario",
49
+ default=None,
50
+ )
51
+ C2: Measurement | None = pyd.Field(
52
+ description="Transport to waste processing or disposal under this scenario.",
53
+ default=None,
54
+ )
55
+ C3: Measurement | None = pyd.Field(
56
+ description="Waste Processing under this scenario",
57
+ default=None,
58
+ )
59
+ C4: Measurement | None = pyd.Field(
60
+ description="Disposal under this scenario",
61
+ default=None,
45
62
  )
46
- C1: Measurement | None = pyd.Field(description="Deconstruction and Demolition under this scenario")
47
- C2: Measurement | None = pyd.Field(description="Transport to waste processing or disposal under this scenario.")
48
- C3: Measurement | None = pyd.Field(description="Waste Processing under this scenario")
49
- C4: Measurement | None = pyd.Field(description="Disposal under this scenario")
50
63
  D: Measurement | None = pyd.Field(
51
64
  description="Potential net benefits from reuse, recycling, and/or energy recovery beyond "
52
- "the system boundary under this scenario"
65
+ "the system boundary under this scenario",
66
+ default=None,
53
67
  )
54
68
 
55
69
 
@@ -61,50 +75,128 @@ class ScopeSet(BaseOpenEpdSchema):
61
75
  The 'unit' field must be consistent across all scopes in a single scopeset.
62
76
  """
63
77
 
64
- A1A2A3: Measurement | None = pyd.Field(description="Sum of A1..A3")
65
- A1: Measurement | None = pyd.Field(description="Raw Material Supply")
66
- A2: Measurement | None = pyd.Field(description="Transport to Manufacturing")
67
- A3: Measurement | None = pyd.Field(description="Manufacturing")
68
- A4: Measurement | None = pyd.Field(description="Transport to Construction")
69
- A5: Measurement | None = pyd.Field(description="Construction")
70
- B1: Measurement | None = pyd.Field(description="Use impacts over Reference Service Life (Predicted)")
78
+ A1A2A3: Measurement | None = pyd.Field(
79
+ description="Sum of A1..A3",
80
+ default=None,
81
+ )
82
+ A1: Measurement | None = pyd.Field(
83
+ description="Raw Material Supply",
84
+ default=None,
85
+ )
86
+ A2: Measurement | None = pyd.Field(
87
+ description="Transport to Manufacturing",
88
+ default=None,
89
+ )
90
+ A3: Measurement | None = pyd.Field(
91
+ description="Manufacturing",
92
+ default=None,
93
+ )
94
+ A4: Measurement | None = pyd.Field(
95
+ description="Transport to Construction",
96
+ default=None,
97
+ )
98
+ A5: Measurement | None = pyd.Field(
99
+ description="Construction",
100
+ default=None,
101
+ )
102
+ B1: Measurement | None = pyd.Field(
103
+ description="Use impacts over Reference Service Life (Predicted)",
104
+ default=None,
105
+ )
71
106
  B1_years: float | None = pyd.Field(
72
107
  gt=0,
73
108
  lt=11,
74
109
  description="Timeframe over which B1 is evaluated, in years. "
75
110
  "For example, an impact of 1.23 kgCO2e per year for ten years could be B1=1.23kgCO2e, "
76
111
  "B1_years=1.0 or B1=12.3kgCO2e, B1_years=10.0",
112
+ default=None,
113
+ )
114
+ B2: Measurement | None = pyd.Field(
115
+ description="Predicted Maintenance Impacts over Reference Service Life",
116
+ default=None,
77
117
  )
78
- B2: Measurement | None = pyd.Field(description="Predicted Maintenance Impacts over Reference Service Life")
79
118
  B2_years: float | None = pyd.Field(
80
- gt=0, lt=11, description="Predicted Maintenance Impacts over Reference Service Life"
119
+ gt=0,
120
+ lt=11,
121
+ description="Predicted Maintenance Impacts over Reference Service Life",
122
+ default=None,
123
+ )
124
+ B3: Measurement | None = pyd.Field(
125
+ description="Predicted Repair impacts over Reference Service Life",
126
+ default=None,
127
+ )
128
+ B3_years: float | None = pyd.Field(
129
+ gt=0,
130
+ lt=11,
131
+ description="Timeframe over which B3 is evaluated, in years",
132
+ default=None,
81
133
  )
82
- B3: Measurement | None = pyd.Field(description="Predicted Repair impacts over Reference Service Life")
83
- B3_years: float | None = pyd.Field(gt=0, lt=11, description="Timeframe over which B3 is evaluated, in years")
84
134
  B4: Measurement | None = pyd.Field(
85
135
  description="Predicted Replacement Impacts over the Building lifetime "
86
- "('Estimated Construction Works lifespan') specified in the PCR."
136
+ "('Estimated Construction Works lifespan') specified in the PCR.",
137
+ default=None,
138
+ )
139
+ B4_years: float | None = pyd.Field(
140
+ gt=0,
141
+ lt=11,
142
+ description="Timeframe over which B4 is evaluated, in years",
143
+ default=None,
87
144
  )
88
- B4_years: float | None = pyd.Field(gt=0, lt=11, description="Timeframe over which B4 is evaluated, in years")
89
145
  B5: Measurement | None = pyd.Field(
90
146
  description="Predicted Refurbishment Impacts over the Building lifetime "
91
- "('Estimated Construction Works lifespan') specified in the PCR."
147
+ "('Estimated Construction Works lifespan') specified in the PCR.",
148
+ default=None,
149
+ )
150
+ B5_years: float | None = pyd.Field(
151
+ gt=0,
152
+ lt=11,
153
+ description="Timeframe over which B5 is evaluated, in years",
154
+ default=None,
155
+ )
156
+ B6: Measurement | None = pyd.Field(
157
+ description="Predicted Impacts related to Operational Energy Use",
158
+ default=None,
159
+ )
160
+ B6_years: float | None = pyd.Field(
161
+ gt=0,
162
+ lt=11,
163
+ description="Timeframe over which B6 is evaluated, in years",
164
+ default=None,
165
+ )
166
+ B7: Measurement | None = pyd.Field(
167
+ description="Predicted Impacts related to Operational Water Use",
168
+ default=None,
169
+ )
170
+ B7_years: float | None = pyd.Field(
171
+ gt=0,
172
+ lt=11,
173
+ description="Timeframe over which B7 is evaluated, in years",
174
+ default=None,
92
175
  )
93
- B5_years: float | None = pyd.Field(gt=0, lt=11, description="Timeframe over which B5 is evaluated, in years")
94
- B6: Measurement | None = pyd.Field(description="Predicted Impacts related to Operational Energy Use")
95
- B6_years: float | None = pyd.Field(gt=0, lt=11, description="Timeframe over which B6 is evaluated, in years")
96
- B7: Measurement | None = pyd.Field(description="Predicted Impacts related to Operational Water Use")
97
- B7_years: float | None = pyd.Field(gt=0, lt=11, description="Timeframe over which B7 is evaluated, in years")
98
176
  C_scenarios: list[EolScenario] | None = pyd.Field(
99
177
  description="A list of possible end-of-life scenarios, "
100
- "for use in analyses where the end-of-life can be predicted."
178
+ "for use in analyses where the end-of-life can be predicted.",
179
+ default=None,
180
+ )
181
+ C1: Measurement | None = pyd.Field(
182
+ description="Deconstruction and Demolition",
183
+ default=None,
184
+ )
185
+ C2: Measurement | None = pyd.Field(
186
+ description="Transport to waste processing or disposal.",
187
+ default=None,
188
+ )
189
+ C3: Measurement | None = pyd.Field(
190
+ description="Waste Processing",
191
+ default=None,
192
+ )
193
+ C4: Measurement | None = pyd.Field(
194
+ description="Disposal",
195
+ default=None,
101
196
  )
102
- C1: Measurement | None = pyd.Field(description="Deconstruction and Demolition")
103
- C2: Measurement | None = pyd.Field(description="Transport to waste processing or disposal.")
104
- C3: Measurement | None = pyd.Field(description="Waste Processing")
105
- C4: Measurement | None = pyd.Field(description="Disposal")
106
197
  D: Measurement | None = pyd.Field(
107
- description="Potential net benefits from reuse, recycling, and/or energy recovery beyond the system boundary."
198
+ default=None,
199
+ description="Potential net benefits from reuse, recycling, and/or energy recovery beyond the system boundary.",
108
200
  )
109
201
 
110
202
 
@@ -112,26 +204,28 @@ class ImpactSet(BaseOpenEpdSchema):
112
204
  """A set of impacts, such as GWP, ODP, AP, EP, POCP, EP-marine, EP-terrestrial, EP-freshwater, etc."""
113
205
 
114
206
  gwp: ScopeSet | None = pyd.Field(
207
+ default=None,
115
208
  description="GWP100, calculated per IPCC guidelines. If any CO2 removals are "
116
209
  "part of this figure, the gwp-fossil, gwp-bioganic, gwp-luluc, an "
117
210
  "gwp-nonCO2 fields are required, as is "
118
- "kg_C_biogenic_per_declared_unit."
211
+ "kg_C_biogenic_per_declared_unit.",
119
212
  )
120
- odp: ScopeSet | None = pyd.Field(description="Ozone Depletion Potential")
121
- ap: ScopeSet | None = pyd.Field(description="Acidification Potential")
213
+ odp: ScopeSet | None = pyd.Field(default=None, description="Ozone Depletion Potential")
214
+ ap: ScopeSet | None = pyd.Field(default=None, description="Acidification Potential")
122
215
  ep: ScopeSet | None = pyd.Field(
123
- description="Eutrophication Potential in Marine Ecosystems. Has the same meaning as ep-marine."
216
+ default=None, description="Eutrophication Potential in Marine Ecosystems. Has the same meaning as ep-marine."
124
217
  )
125
- pocp: ScopeSet | None = pyd.Field(description="Photochemical Smog (Ozone) creation potential")
126
- ep_marine: ScopeSet | None = pyd.Field(alias="ep-marine", description="Has the same meaning as 'ep'")
218
+ pocp: ScopeSet | None = pyd.Field(default=None, description="Photochemical Smog (Ozone) creation potential")
219
+ ep_marine: ScopeSet | None = pyd.Field(alias="ep-marine", default=None, description="Has the same meaning as 'ep'")
127
220
  ep_fresh: ScopeSet | None = pyd.Field(
128
- alias="ep-fresh", description="Eutrophication Potential in Freshwater Ecosystems"
221
+ alias="ep-fresh", default=None, description="Eutrophication Potential in Freshwater Ecosystems"
129
222
  )
130
223
  ep_terr: ScopeSet | None = pyd.Field(
131
- alias="ep-terr", description="Eutrophication Potential in Terrestrial Ecosystems"
224
+ alias="ep-terr", default=None, description="Eutrophication Potential in Terrestrial Ecosystems"
132
225
  )
133
226
  gwp_biogenic: ScopeSet | None = pyd.Field(
134
227
  alias="gwp-biogenic",
228
+ default=None,
135
229
  description="Net GWP from removals of atmospheric CO2 into biomass and emissions of CO2 from biomass sources. "
136
230
  "To be counted as negative, CO2 removals must be closely linked to production in "
137
231
  "operational control (i.e. no purchased offsets), time (within three years of production) and "
@@ -140,6 +234,7 @@ class ImpactSet(BaseOpenEpdSchema):
140
234
  )
141
235
  gwp_luluc: ScopeSet | None = pyd.Field(
142
236
  alias="gwp-luluc",
237
+ default=None,
143
238
  description="Climate change effects related to land use and land use change, for example biogenic carbon "
144
239
  "exchanges resulting from deforestation or other soil activities (including soil carbon "
145
240
  "emissions). All related emissions for native forests are included under this category. "
@@ -147,10 +242,12 @@ class ImpactSet(BaseOpenEpdSchema):
147
242
  )
148
243
  gwp_nonCO2: ScopeSet | None = pyd.Field(
149
244
  alias="gwp-nonCO2",
245
+ default=None,
150
246
  description="GWP from non-CO2, non-fossil sources, such as livestock-sourced CH4 and agricultural N2O.",
151
247
  )
152
248
  gwp_fossil: ScopeSet | None = pyd.Field(
153
249
  alias="gwp-fossil",
250
+ default=None,
154
251
  description="Climate change effects due to greenhouse gas emissions originating from the oxidation or "
155
252
  "reduction of fossil fuels or materials containing fossil carbon. [Source: EN15804]",
156
253
  )
@@ -199,12 +296,14 @@ class LCIAMethod(StrEnum):
199
296
  return cls.UNKNOWN
200
297
 
201
298
 
202
- class Impacts(dict[LCIAMethod, ImpactSet]):
299
+ class Impacts(pyd.BaseModel):
203
300
  """List of environmental impacts, compiled per one of the standard Impact Assessment methods."""
204
301
 
302
+ __root__: dict[LCIAMethod, ImpactSet]
303
+
205
304
  def set_unknown_lcia(self, impact_set: ImpactSet):
206
305
  """Set the impact set as an unknown LCIA method."""
207
- self[LCIAMethod.UNKNOWN] = impact_set
306
+ self.__root__[LCIAMethod.UNKNOWN] = impact_set
208
307
 
209
308
  def set_impact_set(self, lcia_method: LCIAMethod | str | None, impact_set: ImpactSet):
210
309
  """
@@ -217,11 +316,25 @@ class Impacts(dict[LCIAMethod, ImpactSet]):
217
316
  else:
218
317
  if isinstance(lcia_method, str):
219
318
  lcia_method = LCIAMethod.get_by_name(lcia_method)
220
- self[lcia_method] = impact_set
319
+ self.__root__[lcia_method] = impact_set
320
+
321
+ def get_impact_set(
322
+ self, lcia_method: LCIAMethod | str | None, default_val: ImpactSet | None = None
323
+ ) -> ImpactSet | None:
324
+ """Return the impact set for the given LCIA method."""
325
+ if lcia_method is None:
326
+ return self.__root__.get(LCIAMethod.UNKNOWN, default_val)
327
+ if isinstance(lcia_method, str):
328
+ lcia_method = LCIAMethod.get_by_name(lcia_method)
329
+ return self.__root__.get(lcia_method, default_val)
221
330
 
222
331
  def available_methods(self) -> set[LCIAMethod]:
223
332
  """Return a list of available LCIA methods."""
224
- return set(self.keys())
333
+ return set(self.__root__.keys())
334
+
335
+ def as_dict(self) -> dict[LCIAMethod, ImpactSet]:
336
+ """Return the impacts as a dictionary."""
337
+ return self.__root__
225
338
 
226
339
 
227
340
  class ResourceUseSet(BaseOpenEpdSchema):
@@ -230,47 +343,121 @@ class ResourceUseSet(BaseOpenEpdSchema):
230
343
  RPRec: ScopeSet | None = pyd.Field(
231
344
  description="Renewable primary resources used as energy carrier (fuel). "
232
345
  "First use bio-based materials used as an energy source. Hydropower, solar and wind power used "
233
- "in the technosphere are also included in this indicator"
346
+ "in the technosphere are also included in this indicator",
347
+ default=None,
234
348
  )
235
349
  RPRm: ScopeSet | None = pyd.Field(
236
350
  description="Renewable primary resources with energy content used as material. "
237
- "First use biobased materials used as materials (e.g. wood, hemp, etc.)."
238
- )
239
- rpre: ScopeSet | None = pyd.Field(description="Renewable primary energy resources as energy")
240
- nrpre: ScopeSet | None = pyd.Field(description="Non-renewable primary resources as energy (fuel)")
241
- nrprm: ScopeSet | None = pyd.Field(description="Non-renewable primary resources as material")
242
- fw: ScopeSet | None = pyd.Field(description="Use of net fresh water")
243
- sm: ScopeSet | None = pyd.Field(description="Use of secondary materials")
244
- rsf: ScopeSet | None = pyd.Field(description="Use of renewable secondary materials")
245
- nrsf: ScopeSet | None = pyd.Field(description="Use of non-renewable secondary fuels")
246
- re: ScopeSet | None = pyd.Field(description="Renewable energy resources")
351
+ "First use biobased materials used as materials (e.g. wood, hemp, etc.).",
352
+ default=None,
353
+ )
354
+ rpre: ScopeSet | None = pyd.Field(
355
+ description="Renewable primary energy resources as energy",
356
+ default=None,
357
+ )
358
+ nrpre: ScopeSet | None = pyd.Field(
359
+ description="Non-renewable primary resources as energy (fuel)",
360
+ default=None,
361
+ )
362
+ nrprm: ScopeSet | None = pyd.Field(
363
+ description="Non-renewable primary resources as material",
364
+ default=None,
365
+ )
366
+ fw: ScopeSet | None = pyd.Field(
367
+ description="Use of net fresh water",
368
+ default=None,
369
+ )
370
+ sm: ScopeSet | None = pyd.Field(
371
+ description="Use of secondary materials",
372
+ default=None,
373
+ )
374
+ rsf: ScopeSet | None = pyd.Field(
375
+ description="Use of renewable secondary materials",
376
+ default=None,
377
+ )
378
+ nrsf: ScopeSet | None = pyd.Field(
379
+ description="Use of non-renewable secondary fuels",
380
+ default=None,
381
+ )
382
+ re: ScopeSet | None = pyd.Field(
383
+ description="Renewable energy resources",
384
+ default=None,
385
+ )
247
386
  pere: ScopeSet | None = pyd.Field(
248
- description="Use of renewable primary energy excluding renewable primary energy resources used as raw materials"
387
+ description="Use of renewable primary energy excluding renewable primary energy resources used as raw materials",
388
+ default=None,
389
+ )
390
+ perm: ScopeSet | None = pyd.Field(
391
+ description="Use of renewable primary energy resources used as raw materials",
392
+ default=None,
393
+ )
394
+ pert: ScopeSet | None = pyd.Field(
395
+ description="Total use of renewable primary energy resources",
396
+ default=None,
249
397
  )
250
- perm: ScopeSet | None = pyd.Field(description="Use of renewable primary energy resources used as raw materials")
251
- pert: ScopeSet | None = pyd.Field(description="Total use of renewable primary energy resources")
252
398
  penre: ScopeSet | None = pyd.Field(
253
399
  description="Use of non-renewable primary energy excluding "
254
- "non-renewable primary energy resources used as raw materials"
400
+ "non-renewable primary energy resources used as raw materials",
401
+ default=None,
255
402
  )
256
403
  penrm: ScopeSet | None = pyd.Field(
257
- description="Use of non-renewable primary energy resources used as raw materials"
404
+ description="Use of non-renewable primary energy resources used as raw materials",
405
+ default=None,
406
+ )
407
+ penrt: ScopeSet | None = pyd.Field(
408
+ description="Total use of non-renewable primary energy resources",
409
+ default=None,
258
410
  )
259
- penrt: ScopeSet | None = pyd.Field(description="Total use of non-renewable primary energy resources")
260
411
 
261
412
 
262
413
  class OutputFlowSet(BaseOpenEpdSchema):
263
414
  """A set of output flows, such as waste, emissions, etc."""
264
415
 
265
- twd: ScopeSet | None = pyd.Field(description="Total waste disposed")
266
- hwd: ScopeSet | None = pyd.Field(description="Hazardous waste disposed")
267
- nhwd: ScopeSet | None = pyd.Field(description="Non-hazardous waste disposed")
268
- rwd: ScopeSet | None = pyd.Field(description="Radioactive waste disposed")
269
- hlrw: ScopeSet | None = pyd.Field(description="High level radioactive waste disposed")
270
- illrw: ScopeSet | None = pyd.Field(description="Intermediate level radioactive waste disposed")
271
- cru: ScopeSet | None = pyd.Field(description="Components for re-use")
272
- mr: ScopeSet | None = pyd.Field(description="Recycled materials")
273
- mfr: ScopeSet | None = pyd.Field(description="Materials for recycling")
274
- mer: ScopeSet | None = pyd.Field(description="Materials for energy recovery")
275
- ee: ScopeSet | None = pyd.Field(description="Exported energy")
276
- eh: ScopeSet | None = pyd.Field(description="Exported heat")
416
+ twd: ScopeSet | None = pyd.Field(
417
+ description="Total waste disposed",
418
+ default=None,
419
+ )
420
+ hwd: ScopeSet | None = pyd.Field(
421
+ description="Hazardous waste disposed",
422
+ default=None,
423
+ )
424
+ nhwd: ScopeSet | None = pyd.Field(
425
+ description="Non-hazardous waste disposed",
426
+ default=None,
427
+ )
428
+ rwd: ScopeSet | None = pyd.Field(
429
+ description="Radioactive waste disposed",
430
+ default=None,
431
+ )
432
+ hlrw: ScopeSet | None = pyd.Field(
433
+ description="High level radioactive waste disposed",
434
+ default=None,
435
+ )
436
+ illrw: ScopeSet | None = pyd.Field(
437
+ description="Intermediate level radioactive waste disposed",
438
+ default=None,
439
+ )
440
+ cru: ScopeSet | None = pyd.Field(
441
+ description="Components for re-use",
442
+ default=None,
443
+ )
444
+ mr: ScopeSet | None = pyd.Field(
445
+ description="Recycled materials",
446
+ default=None,
447
+ )
448
+ mfr: ScopeSet | None = pyd.Field(
449
+ description="Materials for recycling",
450
+ default=None,
451
+ )
452
+ mer: ScopeSet | None = pyd.Field(
453
+ description="Materials for energy recovery",
454
+ default=None,
455
+ )
456
+ ee: ScopeSet | None = pyd.Field(
457
+ description="Exported energy",
458
+ default=None,
459
+ )
460
+ eh: ScopeSet | None = pyd.Field(
461
+ description="Exported heat",
462
+ default=None,
463
+ )
@@ -22,62 +22,66 @@ from typing import Annotated, Optional
22
22
  import pydantic as pyd
23
23
 
24
24
  from openepd.model.base import BaseOpenEpdSchema
25
- from openepd.model.common import WithAltIdsMixin, WithAttachmentsMixin
26
-
27
-
28
- class Contact(BaseOpenEpdSchema): # TODO: NEW Object, not in the spec
29
- """Contact information of a person or organization."""
30
-
31
- email: pyd.EmailStr | None = pyd.Field(description="Email", example="contact@c-change-labs.com", default=None)
32
- phone: str | None = pyd.Field(description="Phone number", example="+15263327352", default=None)
33
- website: pyd.AnyUrl | None = pyd.Field(
34
- description="Url of the website", example="http://buildingtransparency.org", default=None
35
- )
36
- address: str | None = pyd.Field(description="Address", example="123 Main St, San Francisco, CA 94105", default=None)
25
+ from openepd.model.common import Location, WithAltIdsMixin, WithAttachmentsMixin
37
26
 
38
27
 
39
28
  class Org(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
40
29
  """Represent an organization."""
41
30
 
42
- web_domain: str = pyd.Field(
43
- description="A web domain owned by organization. Typically is the org's home website address",
31
+ web_domain: str | None = pyd.Field(
32
+ description="A web domain owned by organization. Typically is the org's home website address", default=None
33
+ )
34
+ name: str | None = pyd.Field(
35
+ max_length=200,
36
+ description="Common name for organization",
37
+ example="C Change Labs",
38
+ default=None,
44
39
  )
45
- name: str = pyd.Field(max_length=200, description="Common name for organization", example="C Change Labs")
46
40
  alt_names: Annotated[list[str], pyd.conlist(pyd.constr(max_length=200), max_items=255)] | None = pyd.Field(
47
41
  description="List of other names for organization",
48
42
  example=["C-Change Labs", "C-Change Labs inc."],
49
43
  default=None,
50
44
  )
51
45
  # TODO: NEW field, not in the spec
52
- contacts: Contact | None = pyd.Field(description="Contact information of the organization", default=None)
53
46
  owner: Optional["Org"] = pyd.Field(description="Organization that controls this organization", default=None)
54
47
  subsidiaries: Annotated[list[str], pyd.conlist(pyd.constr(max_length=200), max_items=255)] | None = pyd.Field(
55
48
  description="Organizations controlled by this organization",
56
49
  example=["cqd.io", "supplychaincarbonpricing.org"],
57
50
  default=None,
58
51
  )
52
+ hq_location: Location | None = pyd.Field(
53
+ default=None,
54
+ description="Location of a place of business, prefereably the corporate headquarters.",
55
+ )
59
56
 
60
57
 
61
58
  class Plant(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
62
59
  """Represent a manufacturing plant."""
63
60
 
64
61
  # TODO: Add proper validator
65
- id: str = pyd.Field(
62
+ id: str | None = pyd.Field(
66
63
  description="Plus code (aka Open Location Code) of plant's location and "
67
64
  "owner's web domain joined with `.`(dot).",
68
65
  example="865P2W3V+3W.interface.com",
69
66
  alias="pluscode",
67
+ default=None,
70
68
  )
71
69
  owner: Org | None = pyd.Field(description="Organization that owns the plant", default=None)
72
- name: str = pyd.Field(
73
- max_length=200, description="Manufacturer's name for plant. Recommended < 40 chars", example="Dalton, GA"
70
+ name: str | None = pyd.Field(
71
+ max_length=200,
72
+ description="Manufacturer's name for plant. Recommended < 40 chars",
73
+ example="Dalton, GA",
74
+ default=None,
74
75
  )
75
- address: str = pyd.Field(
76
+ address: str | None = pyd.Field(
76
77
  max_length=200,
78
+ default=None,
77
79
  description="Text address, preferably geocoded",
78
80
  example="1503 Orchard Hill Rd, LaGrange, GA 30240, United States",
79
81
  )
80
- contact_email: pyd.EmailStr | None = pyd.Field(description="Email contact", example="info@interface.com")
82
+ contact_email: pyd.EmailStr | None = pyd.Field(
83
+ description="Email contact", example="info@interface.com", default=None
84
+ )
81
85
 
82
86
  class Config(BaseOpenEpdSchema.Config):
83
87
  allow_population_by_field_name = True
@@ -30,44 +30,50 @@ from openepd.model.org import Org
30
30
  class Pcr(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
31
31
  """Represent a PCR (Product Category Rules)."""
32
32
 
33
- id: str = pyd.Field(
33
+ id: str | None = pyd.Field(
34
34
  description="The unique ID for this PCR. To ensure global uniqueness, should be registered "
35
35
  "at open-xpd-uuid.cqd.io/register or a coordinating registry.",
36
36
  example="ec3xpgq2",
37
+ default=None,
37
38
  )
38
39
  issuer: Org | None = pyd.Field(description="Organization issuing this PCR", default=None)
39
40
  issuer_doc_id: str | None = pyd.Field(
40
41
  max_length=40,
42
+ default=None,
41
43
  description="Document ID or code created by issuer",
42
44
  example="c-PCR-003",
43
45
  )
44
46
  name: str | None = pyd.Field(
45
47
  max_length=200,
48
+ default=None,
46
49
  description="Full document name as listed in source document",
47
50
  example="c-PCR-003 Concrete and concrete elements (EN 16757)",
48
51
  )
49
52
  short_name: str | None = pyd.Field(
50
53
  max_length=40,
54
+ default=None,
51
55
  description="A shortened name without boilerplate text.",
52
56
  example="Concrete and Concrete Elements",
53
57
  )
54
- version: pyd.PositiveInt = pyd.Field(
58
+ version: str | None = pyd.Field(
55
59
  description="Document version, as expressed in document.",
56
60
  example="1.0.2",
61
+ default=None,
57
62
  )
58
63
  date_of_issue: datetime.date | None = pyd.Field(
59
64
  example=datetime.date(day=11, month=2, year=2022),
65
+ default=None,
60
66
  description="First day on which the document is valid",
61
67
  )
62
68
  valid_until: datetime.date | None = pyd.Field(
63
69
  example=datetime.date(day=11, month=2, year=2024),
70
+ default=None,
64
71
  description="Last day on which the document is valid",
65
72
  )
66
73
  parent: Optional["Pcr"] = pyd.Field(
67
74
  description="The parent PCR, base PCR, `Part A` PCR",
68
75
  default=None,
69
76
  )
70
- # TODO: why plural?
71
77
  product_classes: dict[str, str | list[str]] = pyd.Field(
72
78
  description="List of classifications, including Masterformat and UNSPC", default_factory=dict
73
79
  )
File without changes
File without changes
File without changes