openepd 6.4.0__py3-none-any.whl → 6.4.2__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
@@ -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 = "6.4.0"
16
+ VERSION = "6.4.2"
openepd/model/common.py CHANGED
@@ -24,7 +24,7 @@ from openepd.model.validation.numbers import RatioFloat
24
24
  class Amount(BaseOpenEpdSchema):
25
25
  """A value-and-unit pairing for amounts that do not have an uncertainty."""
26
26
 
27
- qty: float | None = pyd.Field(description="How much of this in the amount.", default=None)
27
+ qty: float | None = pyd.Field(description="How much of this in the amount.", ge=0, default=None)
28
28
  unit: str | None = pyd.Field(description="Which unit. SI units are preferred.", example="kg", default=None)
29
29
 
30
30
  @pyd.root_validator
@@ -117,6 +117,18 @@ class Ingredient(BaseOpenEpdSchema):
117
117
  )
118
118
  citation: str | None = pyd.Field(default=None, description="Text citation describing the data source ")
119
119
 
120
+ @pyd.root_validator(skip_on_failure=True)
121
+ def _validate_evidence(cls, values: dict[str, Any]) -> dict[str, Any]:
122
+ # gwp_fraction should be backed by some type of evidence (fraction coming from product EPD etc) to be accounted
123
+ # for in the calculation of uncertainty
124
+ if values.get("gwp_fraction"):
125
+ if not values.get("evidence_type"):
126
+ raise ValueError("evidence_type is required if gwp_fraction is provided")
127
+ if not (values.get("citation") or values.get("link")):
128
+ raise ValueError("link or citation is required if gwp_fraction is provided")
129
+
130
+ return values
131
+
120
132
 
121
133
  class LatLng(BaseOpenEpdSchema):
122
134
  """A latitude and longitude."""
openepd/model/epd.py CHANGED
@@ -38,6 +38,7 @@ MANUFACTURER_DESCRIPTION = (
38
38
  'JSON object for declaring Org. Sometimes called the "Declaration Holder" or "Declaration Owner".'
39
39
  )
40
40
 
41
+ PLANT_DESCRIPTION = "List of object(s) for one or more plant(s) that this declaration applies to."
41
42
 
42
43
  #
43
44
  # Copyright 2024 by C Change Labs Inc. www.c-change-labs.com
@@ -161,7 +162,7 @@ class EpdPreviewV0(
161
162
  manufacturer: Org | None = pyd.Field(description=MANUFACTURER_DESCRIPTION)
162
163
  plants: list[Plant] = pyd.Field(
163
164
  max_items=32,
164
- description="List of object(s) for one or more plant(s) that this declaration applies to.",
165
+ description=PLANT_DESCRIPTION,
165
166
  default_factory=list,
166
167
  )
167
168
 
openepd/model/lcia.py CHANGED
@@ -206,9 +206,17 @@ class ScopeSet(BaseOpenEpdSchema):
206
206
  if isinstance(v, Measurement):
207
207
  all_units.add(v.unit)
208
208
 
209
- # units should be the same across all measurements (textually)
210
- if len(all_units) > 1:
211
- raise ValueError("All scopes and measurements should be expressed in the same unit.")
209
+ if not cls.allowed_units:
210
+ # For unknown units - only units should be the same across all measurements (textually)
211
+ if len(all_units) > 1:
212
+ raise ValueError("All scopes and measurements should be expressed in the same unit.")
213
+ else:
214
+ # might be multiple variations of the same unit (kgCFC-11e, kgCFC11e)
215
+ if len(all_units) > 1 and ExternalValidationConfig.QUANTITY_VALIDATOR:
216
+ all_units_list = list(all_units)
217
+ first = all_units_list[0]
218
+ for unit in all_units_list[1:]:
219
+ ExternalValidationConfig.QUANTITY_VALIDATOR.validate_same_dimensionality(first, unit)
212
220
 
213
221
  # can correctly validate unit
214
222
  if cls.allowed_units is not None and len(all_units) == 1 and ExternalValidationConfig.QUANTITY_VALIDATOR:
@@ -357,6 +365,30 @@ class ScopeSetDiseaseIncidence(ScopeSet):
357
365
  allowed_units = "AnnualPerCapita"
358
366
 
359
367
 
368
+ class ScopeSetMass(ScopeSet):
369
+ """ScopeSet measuring mass in kg."""
370
+
371
+ allowed_units = "kg"
372
+
373
+
374
+ class ScopeSetVolume(ScopeSet):
375
+ """ScopeSet measuring mass in kg."""
376
+
377
+ allowed_units = "m3"
378
+
379
+
380
+ class ScopeSetMassOrVolume(ScopeSet):
381
+ """ScopeSet measuring mass in kg OR volume in m3, example: radioactive waste."""
382
+
383
+ allowed_units = ("kg", "m3")
384
+
385
+
386
+ class ScopeSetEnergy(ScopeSet):
387
+ """ScopeSet measuring mass in kg."""
388
+
389
+ allowed_units = "MJ"
390
+
391
+
360
392
  class ImpactSet(ScopesetByNameBase):
361
393
  """A set of impacts, such as GWP, ODP, AP, EP, POCP, EP-marine, EP-terrestrial, EP-freshwater, etc."""
362
394
 
@@ -554,71 +586,71 @@ class Impacts(pyd.BaseModel):
554
586
  class ResourceUseSet(ScopesetByNameBase):
555
587
  """A set of resource use indicators, such as RPRec, RPRm, etc."""
556
588
 
557
- RPRec: ScopeSet | None = pyd.Field(
589
+ RPRec: ScopeSetEnergy | None = pyd.Field(
558
590
  description="Renewable primary resources used as energy carrier (fuel). "
559
591
  "First use bio-based materials used as an energy source. Hydropower, solar and wind power used "
560
592
  "in the technosphere are also included in this indicator",
561
593
  default=None,
562
594
  )
563
- RPRm: ScopeSet | None = pyd.Field(
595
+ RPRm: ScopeSetEnergy | None = pyd.Field(
564
596
  description="Renewable primary resources with energy content used as material. "
565
597
  "First use biobased materials used as materials (e.g. wood, hemp, etc.).",
566
598
  default=None,
567
599
  )
568
- rpre: ScopeSet | None = pyd.Field(
600
+ rpre: ScopeSetEnergy | None = pyd.Field(
569
601
  description="Renewable primary energy resources as energy",
570
602
  default=None,
571
603
  )
572
- nrpre: ScopeSet | None = pyd.Field(
604
+ nrpre: ScopeSetEnergy | None = pyd.Field(
573
605
  description="Non-renewable primary resources as energy (fuel)",
574
606
  default=None,
575
607
  )
576
- nrprm: ScopeSet | None = pyd.Field(
608
+ nrprm: ScopeSetEnergy | None = pyd.Field(
577
609
  description="Non-renewable primary resources as material",
578
610
  default=None,
579
611
  )
580
- fw: ScopeSet | None = pyd.Field(
612
+ fw: ScopeSetVolume | None = pyd.Field(
581
613
  description="Use of net fresh water",
582
614
  default=None,
583
615
  )
584
- sm: ScopeSet | None = pyd.Field(
616
+ sm: ScopeSetMass | None = pyd.Field(
585
617
  description="Use of secondary materials",
586
618
  default=None,
587
619
  )
588
- rsf: ScopeSet | None = pyd.Field(
620
+ rsf: ScopeSetEnergy | None = pyd.Field(
589
621
  description="Use of renewable secondary materials",
590
622
  default=None,
591
623
  )
592
- nrsf: ScopeSet | None = pyd.Field(
624
+ nrsf: ScopeSetEnergy | None = pyd.Field(
593
625
  description="Use of non-renewable secondary fuels",
594
626
  default=None,
595
627
  )
596
- re: ScopeSet | None = pyd.Field(
628
+ re: ScopeSetEnergy | None = pyd.Field(
597
629
  description="Renewable energy resources",
598
630
  default=None,
599
631
  )
600
- pere: ScopeSet | None = pyd.Field(
632
+ pere: ScopeSetEnergy | None = pyd.Field(
601
633
  description="Use of renewable primary energy excluding renewable primary energy resources used as raw materials",
602
634
  default=None,
603
635
  )
604
- perm: ScopeSet | None = pyd.Field(
636
+ perm: ScopeSetEnergy | None = pyd.Field(
605
637
  description="Use of renewable primary energy resources used as raw materials",
606
638
  default=None,
607
639
  )
608
- pert: ScopeSet | None = pyd.Field(
640
+ pert: ScopeSetEnergy | None = pyd.Field(
609
641
  description="Total use of renewable primary energy resources",
610
642
  default=None,
611
643
  )
612
- penre: ScopeSet | None = pyd.Field(
644
+ penre: ScopeSetEnergy | None = pyd.Field(
613
645
  description="Use of non-renewable primary energy excluding "
614
646
  "non-renewable primary energy resources used as raw materials",
615
647
  default=None,
616
648
  )
617
- penrm: ScopeSet | None = pyd.Field(
649
+ penrm: ScopeSetEnergy | None = pyd.Field(
618
650
  description="Use of non-renewable primary energy resources used as raw materials",
619
651
  default=None,
620
652
  )
621
- penrt: ScopeSet | None = pyd.Field(
653
+ penrt: ScopeSetEnergy | None = pyd.Field(
622
654
  description="Total use of non-renewable primary energy resources",
623
655
  default=None,
624
656
  )
@@ -627,51 +659,51 @@ class ResourceUseSet(ScopesetByNameBase):
627
659
  class OutputFlowSet(ScopesetByNameBase):
628
660
  """A set of output flows, such as waste, emissions, etc."""
629
661
 
630
- twd: ScopeSet | None = pyd.Field(
662
+ twd: ScopeSetMass | None = pyd.Field(
631
663
  description="Total waste disposed",
632
664
  default=None,
633
665
  )
634
- hwd: ScopeSet | None = pyd.Field(
666
+ hwd: ScopeSetMass | None = pyd.Field(
635
667
  description="Hazardous waste disposed",
636
668
  default=None,
637
669
  )
638
- nhwd: ScopeSet | None = pyd.Field(
670
+ nhwd: ScopeSetMass | None = pyd.Field(
639
671
  description="Non-hazardous waste disposed",
640
672
  default=None,
641
673
  )
642
- rwd: ScopeSet | None = pyd.Field(
674
+ rwd: ScopeSetMass | None = pyd.Field(
643
675
  description="Radioactive waste disposed",
644
676
  default=None,
645
677
  )
646
- hlrw: ScopeSet | None = pyd.Field(
678
+ hlrw: ScopeSetMassOrVolume | None = pyd.Field(
647
679
  description="High level radioactive waste disposed",
648
680
  default=None,
649
681
  )
650
- illrw: ScopeSet | None = pyd.Field(
682
+ illrw: ScopeSetMassOrVolume | None = pyd.Field(
651
683
  description="Intermediate level radioactive waste disposed",
652
684
  default=None,
653
685
  )
654
- cru: ScopeSet | None = pyd.Field(
686
+ cru: ScopeSetMass | None = pyd.Field(
655
687
  description="Components for re-use",
656
688
  default=None,
657
689
  )
658
- mr: ScopeSet | None = pyd.Field(
690
+ mr: ScopeSetMass | None = pyd.Field(
659
691
  description="Recycled materials",
660
692
  default=None,
661
693
  )
662
- mfr: ScopeSet | None = pyd.Field(
694
+ mfr: ScopeSetMass | None = pyd.Field(
663
695
  description="Materials for recycling",
664
696
  default=None,
665
697
  )
666
- mer: ScopeSet | None = pyd.Field(
698
+ mer: ScopeSetMass | None = pyd.Field(
667
699
  description="Materials for energy recovery",
668
700
  default=None,
669
701
  )
670
- ee: ScopeSet | None = pyd.Field(
702
+ ee: ScopeSetEnergy | None = pyd.Field(
671
703
  description="Exported energy",
672
704
  default=None,
673
705
  )
674
- eh: ScopeSet | None = pyd.Field(
706
+ eh: ScopeSetEnergy | None = pyd.Field(
675
707
  description="Exported heat",
676
708
  default=None,
677
709
  )
openepd/model/org.py CHANGED
@@ -66,7 +66,6 @@ class Org(WithAttachmentsMixin, WithAltIdsMixin, OrgRef):
66
66
  class Plant(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
67
67
  """Represent a manufacturing plant."""
68
68
 
69
- # TODO: Add proper validator
70
69
  id: str | None = pyd.Field(
71
70
  description="Plus code (aka Open Location Code) of plant's location and "
72
71
  "owner's web domain joined with `.`(dot).",
@@ -110,6 +109,8 @@ class Plant(WithAttachmentsMixin, WithAltIdsMixin, BaseOpenEpdSchema):
110
109
 
111
110
  @pyd.validator("id")
112
111
  def _validate_id(cls, v: str) -> str:
112
+ if not v:
113
+ return v
113
114
  try:
114
115
  pluscode, web_domain = v.split(".", maxsplit=1)
115
116
  except ValueError as e:
@@ -2215,7 +2215,7 @@ class AciExposureClass(EnumGroupingAware, StrEnum):
2215
2215
  S1 = "aci.S1"
2216
2216
  S2 = "aci.S2"
2217
2217
  S3 = "aci.S3"
2218
- C0 = "aci.C1"
2218
+ C0 = "aci.C0"
2219
2219
  C1 = "aci.C1"
2220
2220
  C2 = "aci.C2"
2221
2221
  W0 = "aci.W0"
@@ -33,7 +33,7 @@ class QuantityValidator(ABC):
33
33
  """
34
34
 
35
35
  @abstractmethod
36
- def validate_same_dimensionality(self, unit: str | None, dimensionality_unit: str) -> None:
36
+ def validate_same_dimensionality(self, unit: str | None, dimensionality_unit: str | None) -> None:
37
37
  """
38
38
  Validate that a given unit ('kg') has the same dimesnionality as provided dimensionality_unit ('g').
39
39
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: openepd
3
- Version: 6.4.0
3
+ Version: 6.4.2
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,5 +1,5 @@
1
1
  openepd/__init__.py,sha256=Shkfh0Kun0YRhmRDw7LkUj2eQL3X-HnP55u2THOEALw,794
2
- openepd/__version__.py,sha256=O6qOKVfb9pn636JTuJTFAz0fRVsGULoVFAA8im6tjMQ,638
2
+ openepd/__version__.py,sha256=LyifoozbkojKiw7ry9hn1dHTlFfXjPvebpjBZDdw6uU,638
3
3
  openepd/api/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,620
4
4
  openepd/api/average_dataset/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,620
5
5
  openepd/api/average_dataset/generic_estimate_sync_api.py,sha256=mxWwDokEGMe87Px8C_aHvIdVKZVHrEAuVtaSA1zJchU,7953
@@ -35,22 +35,22 @@ openepd/compat/pydantic.py,sha256=DOjSixsylLqMtFAIARu50sGcT4VPXN_c473q_2JwZQ0,11
35
35
  openepd/model/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPoHTIo-lg,620
36
36
  openepd/model/base.py,sha256=OEYNFUTL4BivBNAt_LGowTlDuUjvKMHgf5U5ZBncZwQ,9805
37
37
  openepd/model/category.py,sha256=IQXNGQFQmFZ_H9PRONloX_UOSf1sTMDq1rM1yz8JR0Y,1639
38
- openepd/model/common.py,sha256=yx3m9Tqb7WgqW3SaHB3WftQaShD80dxTys2r63rswy8,9700
38
+ openepd/model/common.py,sha256=4iZAdmQy5UvLKrtmO8OnXsH1HD8jl20c6cv8MUZKW-M,10356
39
39
  openepd/model/declaration.py,sha256=oBJ_v_ESoQhybQIH5S5tmYVkgkoX3gwe3nvFyPqb4uk,13832
40
- openepd/model/epd.py,sha256=AAksfqbpxBiipwsphPil5MvQouCBDMVcAzvIYkeq5OQ,12070
40
+ openepd/model/epd.py,sha256=6G-5cpwaXx5BC6gQns2qlbqmsiEuXrsNmIK4mk1aJaQ,12108
41
41
  openepd/model/factory.py,sha256=XP7eeQNW5tqwX_4hfuEb3lK6BFQDb4KB0fSN0r8-lCU,2656
42
42
  openepd/model/generic_estimate.py,sha256=bbU0cR4izSqjZcfxUHNbdO4pllqqd8OaUFikrEgCFoA,3992
43
43
  openepd/model/geography.py,sha256=G3Oz3QBw5n-RiSCAv-vAGxrOZBhwIT5rASnPlo9dkcs,42095
44
44
  openepd/model/industry_epd.py,sha256=rgXhCUDAgzZ9eGio7ExqE3ymP3zTXnrrwcIDvg5YP1A,3285
45
- openepd/model/lcia.py,sha256=EChQO_zcc9ZV0cYHCsjjO24pn5oi8d3WMeUxB1Cj3Ww,24390
46
- openepd/model/org.py,sha256=gLzbFt4LNFYLhkAMQUrrAgSu-zC6x5NZ0UiNSQqQ0sc,5043
45
+ openepd/model/lcia.py,sha256=a0OZQzZBtXNx_6O1jMnC1WQrlU3rdFrDN-Y3UchfgGM,25507
46
+ openepd/model/org.py,sha256=PtG4-EgjKvNSt7446lV60HhDC3YyRx2E-yJ8MMs5raE,5049
47
47
  openepd/model/pcr.py,sha256=QknLtTn6Y14JORWKQ1qBqGgKnZpbKgqNiYF3Axl4U4c,5494
48
48
  openepd/model/specs/README.md,sha256=UGhSiFJ9hOxT1mZl-5ZrhkOrPKf1W_gcu5CI9hzV7LU,2430
49
49
  openepd/model/specs/__init__.py,sha256=IAevXqqYrCWlTH4z4Fy9o77vaOLinX56G05iIJJfm0M,3094
50
50
  openepd/model/specs/asphalt.py,sha256=V-N7NbrxyNnjgNXQbJPj17ZFM3Q-qxTxxjqpx61wEfA,3350
51
51
  openepd/model/specs/base.py,sha256=JgXvfy8K1Jpcai1eCUDZ-ndsLYkL-dcDyH2hQls6Znw,2652
52
52
  openepd/model/specs/concrete.py,sha256=spMvijIv79c87PXT_YV7coG851AkJ1ILl9EHCxN2-6E,9593
53
- openepd/model/specs/enums.py,sha256=rh35d0CZUIp1GDYopldu-dUJ3YQtXlKzJuMpyFLKT2w,63602
53
+ openepd/model/specs/enums.py,sha256=r7Ik8tgL60McK_5ACQn0qhIUTmxjnCQlGvHtFE6m8xM,63602
54
54
  openepd/model/specs/range/__init__.py,sha256=LmBClxzTNpWzGwS1zvFoAxbKILrMRMz22mJXKYu2u8I,4502
55
55
  openepd/model/specs/range/accessories.py,sha256=T3z-5jLoIVIIGeQi2cc5fMP3a7uGgvoW2E_SVmr3c30,2498
56
56
  openepd/model/specs/range/aggregates.py,sha256=nxk6iZHzs0CIoUMWSw4IfB-D4tGd4prh9O2HXnC9u70,2549
@@ -129,11 +129,11 @@ openepd/model/validation/__init__.py,sha256=UGmZGEyMnASrYwEBPHuXmVzHiuCUskUsJEPo
129
129
  openepd/model/validation/common.py,sha256=FLYqK8gYFagx08LCkS0jy3qo4-Zq9VAv5i8ZwF2svkc,2435
130
130
  openepd/model/validation/enum.py,sha256=06Fi2_sdjVhOElavtgvNXvkKU0NQ-bcAFx5_5MiDzdU,1793
131
131
  openepd/model/validation/numbers.py,sha256=J2rVMwJTPr8uC1DrU80xpq2SZGMXvVKx7v74SgOVi0U,851
132
- openepd/model/validation/quantity.py,sha256=UP6x2nUj1nP7G8e2rpi-HigHx9rwKeCuIDh_XWFRU6U,20187
132
+ openepd/model/validation/quantity.py,sha256=1z-G46dlryJEm4W-O0QiEJuFYICz7CPTM6gLhQKstgM,20194
133
133
  openepd/model/versioning.py,sha256=R_zm6rCrgF3vlJQYbpyWhirdS_Oek16cv_mvZmpuE8I,4473
134
134
  openepd/patch_pydantic.py,sha256=xrkzblatmU9HBzukWkp1cPq9ZSuohoz1p0pQqVKSlKs,4122
135
135
  openepd/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
136
- openepd-6.4.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
137
- openepd-6.4.0.dist-info/METADATA,sha256=KZPeu2OsyQ-aYGRDN6-NpqSvTfPgeCoG-hAezksY5wc,9038
138
- openepd-6.4.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
139
- openepd-6.4.0.dist-info/RECORD,,
136
+ openepd-6.4.2.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
137
+ openepd-6.4.2.dist-info/METADATA,sha256=5kTEsZy4PiO9EBGPxbl5Lu7pqAwDaJOjqkpRkVKUbas,9038
138
+ openepd-6.4.2.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
139
+ openepd-6.4.2.dist-info/RECORD,,