openepd 4.5.0__py3-none-any.whl → 4.6.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 +1 -1
- openepd/model/declaration.py +113 -0
- openepd/model/epd.py +44 -120
- openepd/model/generic_estimate.py +41 -8
- openepd/model/geography.py +428 -428
- openepd/model/lcia.py +15 -0
- openepd/patch_pydantic.py +7 -0
- {openepd-4.5.0.dist-info → openepd-4.6.0.dist-info}/METADATA +1 -1
- {openepd-4.5.0.dist-info → openepd-4.6.0.dist-info}/RECORD +11 -10
- {openepd-4.5.0.dist-info → openepd-4.6.0.dist-info}/LICENSE +0 -0
- {openepd-4.5.0.dist-info → openepd-4.6.0.dist-info}/WHEEL +0 -0
openepd/__version__.py
CHANGED
@@ -0,0 +1,113 @@
|
|
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
|
+
import abc
|
17
|
+
import datetime
|
18
|
+
|
19
|
+
from openepd.compat.pydantic import pyd
|
20
|
+
from openepd.model.base import RootDocument
|
21
|
+
from openepd.model.common import Amount
|
22
|
+
from openepd.model.pcr import Pcr
|
23
|
+
from openepd.model.standard import Standard
|
24
|
+
|
25
|
+
|
26
|
+
class BaseDeclaration(RootDocument, abc.ABC):
|
27
|
+
"""Base class for declaration-related documents (EPDs, Industry-wide EPDs, Generic Estimates)."""
|
28
|
+
|
29
|
+
# TODO: Add validator for open-xpd-uuid on this field
|
30
|
+
id: str | None = pyd.Field(
|
31
|
+
description="The unique ID for this document. To ensure global uniqueness, should be registered at "
|
32
|
+
"open-xpd-uuid.cqd.io/register or a coordinating registry.",
|
33
|
+
example="1u7zsed8",
|
34
|
+
default=None,
|
35
|
+
)
|
36
|
+
date_of_issue: datetime.datetime | None = pyd.Field(
|
37
|
+
example=datetime.datetime(day=11, month=9, year=2019, tzinfo=datetime.timezone.utc),
|
38
|
+
description="Date the document was issued. This should be the first day on which the document is valid.",
|
39
|
+
)
|
40
|
+
valid_until: datetime.datetime | None = pyd.Field(
|
41
|
+
example=datetime.datetime(day=11, month=9, year=2028, tzinfo=datetime.timezone.utc),
|
42
|
+
description="Last date the document is valid on, including any extensions.",
|
43
|
+
)
|
44
|
+
|
45
|
+
declared_unit: Amount | None = pyd.Field(
|
46
|
+
description="SI declared unit for this document. If a functional unit is "
|
47
|
+
"utilized, the declared unit shall refer to the amount of "
|
48
|
+
"product associated with the A1-A3 life cycle stage."
|
49
|
+
)
|
50
|
+
kg_per_declared_unit: Amount | None = pyd.Field(
|
51
|
+
default=None,
|
52
|
+
description="Mass of the product, in kilograms, per declared unit",
|
53
|
+
example=Amount(qty=12.5, unit="kg"),
|
54
|
+
)
|
55
|
+
compliance: list[Standard] = pyd.Field(
|
56
|
+
description="Standard(s) to which this document is compliant.", default_factory=list
|
57
|
+
)
|
58
|
+
|
59
|
+
# TODO: add product_alt_names? E.g. ILCD has a list of synonymous names
|
60
|
+
product_classes: dict[str, str | list[str]] = pyd.Field(
|
61
|
+
description="List of classifications, including Masterformat and UNSPC", default_factory=dict
|
62
|
+
)
|
63
|
+
|
64
|
+
language: str | None = pyd.Field(
|
65
|
+
min_length=2,
|
66
|
+
max_length=2,
|
67
|
+
strip_whitespace=True,
|
68
|
+
description="Language this document is captured in, as an ISO 639-1 code",
|
69
|
+
example="en",
|
70
|
+
)
|
71
|
+
private: bool = pyd.Field(
|
72
|
+
default=False,
|
73
|
+
description="This document's author does not wish the contents published. "
|
74
|
+
"Useful for draft, partial, or confidential declarations. "
|
75
|
+
"How (or whether) privacy is implemented is up to the receiving system. "
|
76
|
+
"Null is treated as false (public). Private (draft) entries have a reduced "
|
77
|
+
"number of required fields, to allow for multiple systems to coordinate "
|
78
|
+
"incomplete EPDs.",
|
79
|
+
)
|
80
|
+
|
81
|
+
pcr: Pcr | None = pyd.Field(
|
82
|
+
description="JSON object for product category rules. Should point to the "
|
83
|
+
"most-specific PCR that applies; the PCR entry should point to any "
|
84
|
+
"parent PCR.",
|
85
|
+
default=None,
|
86
|
+
)
|
87
|
+
lca_discussion: str | None = pyd.Field(
|
88
|
+
max_length=20000,
|
89
|
+
description="""A rich text description containing information for experts reviewing the document contents.
|
90
|
+
Text descriptions required by ISO 14025, ISO 21930, EN 15804,, relevant PCRs, or program instructions and which do not
|
91
|
+
have specific openEPD fields should be entered here. This field may be large, and may contain multiple sections
|
92
|
+
separated by github flavored markdown formatting.""",
|
93
|
+
example="""# Packaging
|
94
|
+
|
95
|
+
Information on product-specific packaging: type, composition and possible reuse of packaging materials (paper,
|
96
|
+
strapping, pallets, foils, drums, etc.) shall be included in this Section. The EPD shall describe specific packaging
|
97
|
+
scenario assumptions, including disposition pathways for each packaging material by reuse, recycling, or landfill
|
98
|
+
disposal based on packaging type.*
|
99
|
+
|
100
|
+
# Product Installation
|
101
|
+
|
102
|
+
A description of the type of processing, machinery, tools, dust extraction equipment, auxiliary materials, etc.
|
103
|
+
to be used during installation shall be included. Information on industrial and environmental protection may be
|
104
|
+
included in this section. Any waste treatment included within the system boundary of installation waste should be
|
105
|
+
specified.
|
106
|
+
|
107
|
+
# Use Conditions
|
108
|
+
|
109
|
+
Use-stage environmental impacts of flooring products during building operations depend on product cleaning assumptions.
|
110
|
+
Information on cleaning frequency and cleaning products shall be provided based on the manufacturer’s recommendations.
|
111
|
+
In the absence of primary data, cleaning assumptions shall be documented.
|
112
|
+
""",
|
113
|
+
)
|
openepd/model/epd.py
CHANGED
@@ -13,129 +13,33 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
|
-
import datetime
|
17
16
|
from typing import Annotated
|
18
17
|
|
19
18
|
from openepd.compat.pydantic import pyd
|
20
|
-
from openepd.model.base import BaseDocumentFactory, OpenEpdDoctypes
|
19
|
+
from openepd.model.base import BaseDocumentFactory, OpenEpdDoctypes
|
21
20
|
from openepd.model.common import Amount, Ingredient, WithAltIdsMixin, WithAttachmentsMixin
|
22
|
-
from openepd.model.
|
23
|
-
from openepd.model.
|
24
|
-
from openepd.model.
|
21
|
+
from openepd.model.declaration import BaseDeclaration
|
22
|
+
from openepd.model.lcia import WithLciaMixin
|
23
|
+
from openepd.model.org import Org, OrgRef, Plant
|
25
24
|
from openepd.model.specs import Specs
|
26
|
-
from openepd.model.standard import Standard
|
27
25
|
from openepd.model.versioning import OpenEpdVersions, Version
|
28
26
|
|
27
|
+
MANUFACTURER_DESCRIPTION = (
|
28
|
+
'JSON object for declaring Org. Sometimes called the "Declaration Holder" or "Declaration Owner".'
|
29
|
+
)
|
30
|
+
DEVELOPER_DESCRIPTION = "The organization responsible for the underlying LCA (and subsequent summarization as EPD)."
|
31
|
+
PROGRAM_OPERATOR_DESCRIPTION = "JSON object for program operator Org"
|
32
|
+
THIRD_PARTY_VERIFIER_DESCRIPTION = "JSON object for Org that performed a critical review of the EPD data"
|
29
33
|
|
30
|
-
class BaseDeclaration(RootDocument):
|
31
|
-
"""
|
32
|
-
Base class for EPD documents.
|
33
34
|
|
34
|
-
|
35
|
+
class EpdPreviewV0(WithAttachmentsMixin, WithAltIdsMixin, BaseDeclaration, title="EPD (Preview)"):
|
35
36
|
"""
|
37
|
+
EPD preview, used in API list responses and where there is no need for a full object.
|
36
38
|
|
37
|
-
|
38
|
-
description="The unique ID for this EPD. To ensure global uniqueness, should be registered at "
|
39
|
-
"open-xpd-uuid.cqd.io/register or a coordinating registry.",
|
40
|
-
example="1u7zsed8",
|
41
|
-
default=None,
|
42
|
-
)
|
43
|
-
date_of_issue: datetime.datetime | None = pyd.Field(
|
44
|
-
example=datetime.datetime(day=11, month=9, year=2019, tzinfo=datetime.timezone.utc),
|
45
|
-
description="Date the EPD was issued. This should be the first day on which the EPD is valid.",
|
46
|
-
)
|
47
|
-
valid_until: datetime.datetime | None = pyd.Field(
|
48
|
-
example=datetime.datetime(day=11, month=9, year=2028, tzinfo=datetime.timezone.utc),
|
49
|
-
description="Last date the EPD is valid on, including any extensions.",
|
50
|
-
)
|
51
|
-
|
52
|
-
declared_unit: Amount | None = pyd.Field(
|
53
|
-
description="SI declared unit for this EPD. If a functional unit is "
|
54
|
-
"utilized, the declared unit shall refer to the amount of "
|
55
|
-
"product associated with the A1-A3 life cycle stage."
|
56
|
-
)
|
57
|
-
kg_per_declared_unit: Amount | None = pyd.Field(
|
58
|
-
default=None,
|
59
|
-
description="Mass of the product, in kilograms, per declared unit",
|
60
|
-
example=Amount(qty=12.5, unit="kg"),
|
61
|
-
)
|
62
|
-
compliance: list[Standard] = pyd.Field(
|
63
|
-
description="Standard(s) to which this declaration is compliant.", default_factory=list
|
64
|
-
)
|
65
|
-
|
66
|
-
# TODO: add product_alt_names? E.g. ILCD has a list of synonymous names
|
67
|
-
product_classes: dict[str, str | list[str]] = pyd.Field(
|
68
|
-
description="List of classifications, including Masterformat and UNSPC", default_factory=dict
|
69
|
-
)
|
70
|
-
|
71
|
-
language: str | None = pyd.Field(
|
72
|
-
min_length=2,
|
73
|
-
max_length=2,
|
74
|
-
strip_whitespace=True,
|
75
|
-
description="Language this EPD is captured in, as an ISO 639-1 code",
|
76
|
-
example="en",
|
77
|
-
)
|
78
|
-
private: bool = pyd.Field(
|
79
|
-
default=False,
|
80
|
-
description="This document's author does not wish the contents published. "
|
81
|
-
"Useful for draft, partial, or confidential declarations. "
|
82
|
-
"How (or whether) privacy is implemented is up to the receiving system. "
|
83
|
-
"Null is treated as false (public). Private (draft) entries have a reduced "
|
84
|
-
"number of required fields, to allow for multiple systems to coordinate "
|
85
|
-
"incomplete EPDs.",
|
86
|
-
)
|
87
|
-
impacts: Impacts | None = pyd.Field(
|
88
|
-
description="List of environmental impacts, compiled per one of the standard Impact Assessment methods"
|
89
|
-
)
|
90
|
-
resource_uses: ResourceUseSet | None = pyd.Field(
|
91
|
-
description="Set of Resource Use Indicators, over various LCA scopes"
|
92
|
-
)
|
93
|
-
output_flows: OutputFlowSet | None = pyd.Field(
|
94
|
-
description="Set of Waste and Output Flow indicators which describe the waste categories "
|
95
|
-
"and other material output flows derived from the LCI."
|
96
|
-
)
|
97
|
-
|
98
|
-
pcr: Pcr | None = pyd.Field(
|
99
|
-
description="JSON object for product category rules. Should point to the "
|
100
|
-
"most-specific PCR that applies; the PCR entry should point to any "
|
101
|
-
"parent PCR.",
|
102
|
-
default=None,
|
103
|
-
)
|
104
|
-
lca_discussion: str | None = pyd.Field(
|
105
|
-
max_length=20000,
|
106
|
-
description="""A rich text description containing information for experts reviewing the EPD contents.
|
107
|
-
Text descriptions required by ISO 14025, ISO 21930, EN 15804,, relevant PCRs, or program instructions and which do not
|
108
|
-
have specific openEPD fields should be entered here. This field may be large, and may contain multiple sections
|
109
|
-
separated by github flavored markdown formatting.""",
|
110
|
-
example="""# Packaging
|
111
|
-
|
112
|
-
Information on product-specific packaging: type, composition and possible reuse of packaging materials (paper,
|
113
|
-
strapping, pallets, foils, drums, etc.) shall be included in this Section. The EPD shall describe specific packaging
|
114
|
-
scenario assumptions, including disposition pathways for each packaging material by reuse, recycling, or landfill
|
115
|
-
disposal based on packaging type.*
|
116
|
-
|
117
|
-
# Product Installation
|
118
|
-
|
119
|
-
A description of the type of processing, machinery, tools, dust extraction equipment, auxiliary materials, etc.
|
120
|
-
to be used during installation shall be included. Information on industrial and environmental protection may be
|
121
|
-
included in this section. Any waste treatment included within the system boundary of installation waste should be
|
122
|
-
specified.
|
123
|
-
|
124
|
-
# Use Conditions
|
39
|
+
Excludes LCIA data.
|
125
40
|
|
126
|
-
|
127
|
-
Information on cleaning frequency and cleaning products shall be provided based on the manufacturer’s recommendations.
|
128
|
-
In the absence of primary data, cleaning assumptions shall be documented.
|
129
|
-
""",
|
130
|
-
)
|
131
|
-
|
132
|
-
|
133
|
-
class EpdV0(WithAttachmentsMixin, WithAltIdsMixin, BaseDeclaration):
|
134
|
-
"""Represent an EPD."""
|
135
|
-
|
136
|
-
_FORMAT_VERSION = OpenEpdVersions.Version0.as_str()
|
41
|
+
"""
|
137
42
|
|
138
|
-
# TODO: Add validator for open-xpd-uuid on this field
|
139
43
|
product_name: str | None = pyd.Field(
|
140
44
|
max_length=200, description="The name of the product described by this EPD", example="Mix 12345AC", default=None
|
141
45
|
)
|
@@ -162,12 +66,9 @@ class EpdV0(WithAttachmentsMixin, WithAltIdsMixin, BaseDeclaration):
|
|
162
66
|
description="Link to data object on original registrar's site",
|
163
67
|
example="https://epd-online.com/EmbeddedEpdList/Download/6029",
|
164
68
|
)
|
165
|
-
manufacturer:
|
166
|
-
|
167
|
-
|
168
|
-
)
|
169
|
-
epd_developer: Org | None = pyd.Field(
|
170
|
-
description="The organization responsible for the underlying LCA (and subsequent summarization as EPD).",
|
69
|
+
manufacturer: OrgRef | None = pyd.Field(description=MANUFACTURER_DESCRIPTION)
|
70
|
+
epd_developer: OrgRef | None = pyd.Field(
|
71
|
+
description=DEVELOPER_DESCRIPTION,
|
171
72
|
default=None,
|
172
73
|
)
|
173
74
|
epd_developer_email: pyd.EmailStr | None = pyd.Field(
|
@@ -181,16 +82,14 @@ class EpdV0(WithAttachmentsMixin, WithAltIdsMixin, BaseDeclaration):
|
|
181
82
|
description="List of object(s) for one or more plant(s) that this declaration applies to.",
|
182
83
|
default_factory=list,
|
183
84
|
)
|
184
|
-
program_operator:
|
85
|
+
program_operator: OrgRef | None = pyd.Field(description=PROGRAM_OPERATOR_DESCRIPTION)
|
185
86
|
program_operator_doc_id: str | None = pyd.Field(
|
186
87
|
max_length=200, description="Document identifier from Program Operator.", example="123-456.789/b"
|
187
88
|
)
|
188
89
|
program_operator_version: str | None = pyd.Field(
|
189
90
|
max_length=200, description="Document version number from Program Operator.", example="4.3.0"
|
190
91
|
)
|
191
|
-
third_party_verifier:
|
192
|
-
description="JSON object for Org that performed a critical review of the EPD data"
|
193
|
-
)
|
92
|
+
third_party_verifier: OrgRef | None = pyd.Field(description=THIRD_PARTY_VERIFIER_DESCRIPTION)
|
194
93
|
third_party_verification_url: pyd.AnyUrl | None = pyd.Field(
|
195
94
|
description="Optional link to a verification statement.",
|
196
95
|
example="https://we-verify-epds.com/en/letters/123-456.789b.pdf",
|
@@ -268,6 +167,15 @@ class EpdV0(WithAttachmentsMixin, WithAltIdsMixin, BaseDeclaration):
|
|
268
167
|
default_factory=list,
|
269
168
|
)
|
270
169
|
|
170
|
+
|
171
|
+
EpdPreview = EpdPreviewV0
|
172
|
+
|
173
|
+
|
174
|
+
class EpdV0(WithLciaMixin, EpdPreviewV0, title="EPD (Full)"):
|
175
|
+
"""Represent an EPD."""
|
176
|
+
|
177
|
+
_FORMAT_VERSION = OpenEpdVersions.Version0.as_str()
|
178
|
+
|
271
179
|
@classmethod
|
272
180
|
def get_asset_type(cls) -> str | None:
|
273
181
|
"""Return the asset type of this class (see BaseOpenEpdSchema.get_asset_type for details)."""
|
@@ -291,6 +199,22 @@ class EpdV0(WithAttachmentsMixin, WithAltIdsMixin, BaseDeclaration):
|
|
291
199
|
Epd = EpdV0
|
292
200
|
|
293
201
|
|
202
|
+
class EpdWithDepsV0(EpdV0, title="EPD (with Dependencies)"):
|
203
|
+
"""
|
204
|
+
Expanded version of the EPD.
|
205
|
+
|
206
|
+
Contains related entities - orgs - with full fields, to support object matching in implementations.
|
207
|
+
"""
|
208
|
+
|
209
|
+
manufacturer: Org | None = pyd.Field(description=MANUFACTURER_DESCRIPTION)
|
210
|
+
epd_developer: Org | None = pyd.Field(description=DEVELOPER_DESCRIPTION, default=None)
|
211
|
+
program_operator: Org | None = pyd.Field(description=PROGRAM_OPERATOR_DESCRIPTION)
|
212
|
+
third_party_verifier: Org | None = pyd.Field(description=THIRD_PARTY_VERIFIER_DESCRIPTION)
|
213
|
+
|
214
|
+
|
215
|
+
EpdWithDeps = EpdWithDepsV0
|
216
|
+
|
217
|
+
|
294
218
|
class EpdFactory(BaseDocumentFactory[BaseDeclaration]):
|
295
219
|
"""Factory for EPD objects."""
|
296
220
|
|
@@ -18,9 +18,10 @@ from enum import StrEnum
|
|
18
18
|
from openepd.compat.pydantic import pyd
|
19
19
|
from openepd.model.base import BaseDocumentFactory, OpenEpdDoctypes
|
20
20
|
from openepd.model.common import WithAltIdsMixin, WithAttachmentsMixin
|
21
|
-
from openepd.model.
|
21
|
+
from openepd.model.declaration import BaseDeclaration
|
22
22
|
from openepd.model.geography import Geography
|
23
|
-
from openepd.model.
|
23
|
+
from openepd.model.lcia import WithLciaMixin
|
24
|
+
from openepd.model.org import Org, OrgRef
|
24
25
|
from openepd.model.versioning import OpenEpdVersions, Version
|
25
26
|
|
26
27
|
|
@@ -45,27 +46,30 @@ class LicenseTerms(StrEnum):
|
|
45
46
|
"""
|
46
47
|
|
47
48
|
|
48
|
-
class
|
49
|
-
"""
|
49
|
+
class GenericEstimatePreviewV0(WithAttachmentsMixin, BaseDeclaration, title="Generic Estimate (preview)"):
|
50
|
+
"""
|
51
|
+
Generic Estimate preview, used in API list responses and where there is no need for a full object.
|
50
52
|
|
51
|
-
|
53
|
+
Excludes LCIA data.
|
54
|
+
"""
|
52
55
|
|
53
56
|
doctype: str = pyd.Field(
|
54
57
|
description='Describes the type and schema of the document. Must always be "openGenericEstimate"',
|
55
58
|
default="openGenericEstimate",
|
56
59
|
)
|
57
60
|
|
58
|
-
name: str | None = pyd.Field(max_length=200, description="", default=None)
|
61
|
+
name: str | None = pyd.Field(max_length=200, description="Name of the generic estimate", default=None)
|
62
|
+
|
59
63
|
description: str | None = pyd.Field(
|
60
64
|
max_length=2000,
|
61
65
|
description="1-paragraph description of the Generic Estimate. Supports plain text or github flavored markdown.",
|
62
66
|
)
|
63
67
|
|
64
|
-
publisher:
|
68
|
+
publisher: OrgRef | None = pyd.Field(description="Organization that published the LCA results.")
|
65
69
|
reviewer_email: pyd.EmailStr | None = pyd.Field(
|
66
70
|
description="Email address of the third party verifier", example="john.doe@example.com", default=None
|
67
71
|
)
|
68
|
-
reviewer:
|
72
|
+
reviewer: OrgRef | None = pyd.Field(description="Org that performed a critical review of the LCA.")
|
69
73
|
license_terms: LicenseTerms | None = pyd.Field(description="The license terms for use of the data.")
|
70
74
|
geography: list[Geography] | None = pyd.Field(
|
71
75
|
"Jurisdiction(s) in which the LCA result is applicable. An empty array, or absent properties, implies global applicability."
|
@@ -75,9 +79,38 @@ class GenericEstimateV0(WithAttachmentsMixin, WithAltIdsMixin, BaseDeclaration):
|
|
75
79
|
)
|
76
80
|
|
77
81
|
|
82
|
+
GenericEstimatePreview = GenericEstimatePreviewV0
|
83
|
+
|
84
|
+
|
85
|
+
class GenericEstimateV0(GenericEstimatePreviewV0, WithLciaMixin, WithAltIdsMixin, title="Generic Estimate (Full)"):
|
86
|
+
"""
|
87
|
+
Represent a full Generic Estimate object.
|
88
|
+
|
89
|
+
This is considered the most complete valid openEPD object for GenericEstimate. In addition to it, several related
|
90
|
+
models are defined, either with fewer fields (to be used in APIs for list requests) or with more relaxed structure
|
91
|
+
to support related entities matching.
|
92
|
+
"""
|
93
|
+
|
94
|
+
_FORMAT_VERSION = OpenEpdVersions.Version0.as_str()
|
95
|
+
|
96
|
+
|
78
97
|
GenericEstimate = GenericEstimateV0
|
79
98
|
|
80
99
|
|
100
|
+
class GenericEstimateWithDepsV0(GenericEstimateV0, title="Generic Estimate (with Dependencies)"):
|
101
|
+
"""
|
102
|
+
Expanded version of the GenericEstimate.
|
103
|
+
|
104
|
+
Contains related entities - orgs - with full fields, to support object matching in implementations.
|
105
|
+
"""
|
106
|
+
|
107
|
+
publisher: Org | None = pyd.Field(description="Organization that published the LCA results.")
|
108
|
+
reviewer: Org | None = pyd.Field(description="Org that performed a critical review of the LCA.")
|
109
|
+
|
110
|
+
|
111
|
+
GenericEstimateWithDeps = GenericEstimateWithDepsV0
|
112
|
+
|
113
|
+
|
81
114
|
class GenericEstimateFactory(BaseDocumentFactory[GenericEstimate]):
|
82
115
|
"""Factory for EPD objects."""
|
83
116
|
|