From 8384697b1cc99237b4f60cacf82f2d67ed076bc0 Mon Sep 17 00:00:00 2001 From: Maxime PERALTA Date: Wed, 21 Jan 2026 15:05:43 +0100 Subject: [PATCH 1/2] (update) Add EF3.1 methods to impact methods Enums. Remove deprecated ReCiPe ones. Update unique score weighting and normalization datafiles accordingly. Update tests accordingly. --- apparun/impact_methods.py | 85 +++++++++++++++++-- .../resources/pef31/normalisation_factor.csv | 32 +++---- apparun/resources/pef31/weighting_factor.csv | 32 +++---- .../impact_models/5_indicator_model_ef31.yaml | 67 +++++++++++++++ ...unique_score.py => test_impact_methods.py} | 34 ++++++-- 5 files changed, 206 insertions(+), 44 deletions(-) create mode 100644 tests/data/impact_models/5_indicator_model_ef31.yaml rename tests/functional/{test_unique_score.py => test_impact_methods.py} (81%) diff --git a/apparun/impact_methods.py b/apparun/impact_methods.py index b3ada39..b77656a 100644 --- a/apparun/impact_methods.py +++ b/apparun/impact_methods.py @@ -51,6 +51,45 @@ class MethodFullName(str, Enum): EFV3_PHOTOCHEMICAL_OZONE_FORMATION = "('EF v3.0', 'photochemical oxidant formation: human health', 'tropospheric ozone concentration increase')" EFV3_WATER_USE = "('EF v3.0', 'water use', 'user deprivation potential (deprivation-weighted water consumption)')" + # EF31 + EFV31_ACIDIFICATION = "(‘EF v3.1’, ‘acidification’, ‘accumulated exceedance (AE)’)" + EFV31_CLIMATE_CHANGE = ( + "(‘EF v3.1’, ‘climate change’, ‘global warming potential (GWP100)’)" + ) + EFV31_CLIMATE_CHANGE_BIOGENIC = ( + "(‘EF v3.1’, ‘climate change: biogenic’, ‘global warming potential (GWP100)’)" + ) + EFV31_CLIMATE_CHANGE_FOSSIL = ( + "(‘EF v3.1’, ‘climate change: fossil’, ‘global warming potential (GWP100)’)" + ) + EFV31_CLIMATE_CHANGE_LAND_USE = "(‘EF v3.1’, ‘climate change: land use and land use change’, ‘global warming potential (GWP100)’)" + EFV31_ECOTOXICITY_FRESHWATER = "(‘EF v3.1’, ‘ecotoxicity: freshwater’, ‘comparative toxic unit for ecosystems (CTUe)’)" + EFV31_ECOTOXICITY_FRESHWATER_INORGANICS = "(‘EF v3.1’, ‘ecotoxicity: freshwater, inorganics’, ‘comparative toxic unit for ecosystems (CTUe)’)" + EFV31_ECOTOXICITY_FRESHWATER_ORGANICS = "(‘EF v3.1’, ‘ecotoxicity: freshwater, organics’, ‘comparative toxic unit for ecosystems (CTUe)’)" + EFV31_ENERGY_RESOURCES = "(‘EF v3.1’, ‘energy resources: non-renewable’, ‘abiotic depletion potential (ADP): fossil fuels’)" + EFV31_EUTROPHICATION_FRESHWATER = "(‘EF v3.1’, ‘eutrophication: freshwater’, ‘fraction of nutrients reaching freshwater end compartment (P)’)" + EFV31_EUTROPHICATION_MARINE = "(‘EF v3.1’, ‘eutrophication: marine’, ‘fraction of nutrients reaching marine end compartment (N)’)" + EFV31_EUTROPHICATION_TERRESTRIAL = ( + "(‘EF v3.1’, ‘eutrophication: terrestrial’, ‘accumulated exceedance (AE)’)" + ) + EFV31_HUMAN_TOXICITY_CARCINOGENIC = "(‘EF v3.1’, ‘human toxicity: carcinogenic’, ‘comparative toxic unit for human (CTUh)’)" + EFV31_HUMAN_TOXICITY_CARCINOGENIC_INORGANICS = "(‘EF v3.1’, ‘human toxicity: carcinogenic, inorganics’, ‘comparative toxic unit for human (CTUh)’)" + EFV31_HUMAN_TOXICITY_CARCINOGENIC_ORGANICS = "(‘EF v3.1’, ‘human toxicity: carcinogenic, organics’, ‘comparative toxic unit for human (CTUh)’)" + EFV31_HUMAN_TOXICITY_NON_CARCINOGENIC = "(‘EF v3.1’, ‘human toxicity: non-carcinogenic’, ‘comparative toxic unit for human (CTUh)’)" + EFV31_HUMAN_TOXICITY_NON_CARCINOGENIC_INORGANICS = "(‘EF v3.1’, ‘human toxicity: non-carcinogenic, inorganics’, ‘comparative toxic unit for human (CTUh)’)" + EFV31_HUMAN_TOXICITY_NON_CARCINOGENIC_ORGANICS = "(‘EF v3.1’, ‘human toxicity: non-carcinogenic, organics’, ‘comparative toxic unit for human (CTUh)’)" + EFV31_IONISING_RADIATION = "(‘EF v3.1’, ‘ionising radiation: human health’, ‘human exposure efficiency relative to u235’)" + EFV31_LAND_USE = "(‘EF v3.1’, ‘land use’, ‘soil quality index’)" + EFV31_MATERIAL_RESOURCES = "(‘EF v3.1’, ‘material resources: metals/minerals’, ‘abiotic depletion potential (ADP): elements (ultimate reserves)’)" + EFV31_OZONE_DEPLETION = ( + "(‘EF v3.1’, ‘ozone depletion’, ‘ozone depletion potential (ODP)’)" + ) + EFV31_PARTICULATE_MATTER_FORMATION = ( + "(‘EF v3.1’, ‘particulate matter formation’, ‘impact on human health’)" + ) + EFV31_PHOTOCHEMICAL_OZONE_FORMATION = "(‘EF v3.1’, ‘photochemical oxidant formation: human health’, ‘tropospheric ozone concentration increase’)" + EFV31_WATER_USE = "(‘EF v3.1’, ‘water use’, ‘user deprivation potential (deprivation-weighted water consumption)’)" + def to_short_name(self) -> MethodShortName: """ Convert a full impact name (as specified in Brightway) to its shorter version. @@ -67,13 +106,6 @@ class MethodShortName(str, Enum): """ _settings_ = NoAlias - # RECIPE - RECIPE_MIDPOINT_CLIMATE_CHANGE = "Climate change (GWP100)" - RECIPE_MIDPOINT_TERRESTRIAL_ECOTOXICITY = "Terrestrial ecotoxicity (TETPinf)" - RECIPE_ENDPOINT_ECOSYSTEM_QUALITY = "Damage to ecosystems" - RECIPE_ENDPOINT_RESOURCES = "Damage to resource availability" - RECIPE_ENDPOINT_HUMAN_HEALTH = "Damage to human health" - RECIPE_ENDPOINT_TOTAL = "Total damage" # EFV3 EFV3_ACIDIFICATION = "Acification (AE)" @@ -121,6 +153,45 @@ class MethodShortName(str, Enum): EFV3_PHOTOCHEMICAL_OZONE_FORMATION = "Photochemical ozone formation (kgNMVOCeq)" EFV3_WATER_USE = "Depr.-weighted water cons. (kg world eq. deprived)" + # EFV31 + EFV31_ACIDIFICATION = "Acification (AE)" + EFV31_CLIMATE_CHANGE = "Climate change (GWP100)" + EFV31_CLIMATE_CHANGE_BIOGENIC = "Climate change: biogenic (GWP100)" + EFV31_CLIMATE_CHANGE_FOSSIL = "Climate change: fossil (GWP100)" + EFV31_CLIMATE_CHANGE_LAND_USE = "Climate change: land use/land use change (GWP100)" + EFV31_ECOTOXICITY_FRESHWATER = "Ecotoxicity: freshwater (CTUe)" + EFV31_ECOTOXICITY_FRESHWATER_INORGANICS = ( + "Ecotoxicity: freshwater, inorganics (CTUe)" + ) + EFV31_ECOTOXICITY_FRESHWATER_ORGANICS = "Ecotoxicity: freshwater, organics (CTUe)" + EFV31_EUTROPHICATION_FRESHWATER = "Eutrophication: freshwater (kgPeq)" + EFV31_EUTROPHICATION_MARINE = "Eutrophication: marine (N)" + EFV31_EUTROPHICATION_TERRESTRIAL = "Eutrophication: terrestrial (AE)" + EFV31_HUMAN_TOXICITY_CARCINOGENIC = "Human toxicity: carcinogenic (CTUh)" + EFV31_HUMAN_TOXICITY_CARCINOGENIC_INORGANICS = ( + "Human toxicity: carcinogenic, inorganics (CTUh)" + ) + EFV31_HUMAN_TOXICITY_CARCINOGENIC_ORGANICS = ( + "Human toxicity: carcinogenic, organics (CTUh)" + ) + EFV31_HUMAN_TOXICITY_NON_CARCINOGENIC = "Human toxicity: non-carcinogenic (CTUh)" + EFV31_HUMAN_TOXICITY_NON_CARCINOGENIC_INORGANICS = ( + "Human toxicity: non-carcinogenic, inorganics (CTUh)" + ) + EFV31_HUMAN_TOXICITY_NON_CARCINOGENIC_ORGANICS = ( + "Human toxicity: non-carcinogenic, organics (CTUh)" + ) + EFV31_IONISING_RADIATION = "Ionising radiation: human health (kBqU235)" + EFV31_LAND_USE = "Land use (soil quality index)" + EFV31_MATERIAL_RESOURCES = "Resource use, metals and minerals (kgSbeq)" + EFV31_OZONE_DEPLETION = "Ozone depletion (ODP)" + EFV31_ENERGY_RESOURCES = "Energy use, energy carriers (MJ)" + EFV31_PARTICULATE_MATTER_FORMATION = ( + "Particulate matter formation: impact on human health (disease incidences)" + ) + EFV31_PHOTOCHEMICAL_OZONE_FORMATION = "Photochemical ozone formation (kgNMVOCeq)" + EFV31_WATER_USE = "Depr.-weighted water cons. (kg world eq. deprived)" + def to_full_name(self) -> MethodFullName: """ Convert a short impact name to its full version (as specified in Brightway). diff --git a/apparun/resources/pef31/normalisation_factor.csv b/apparun/resources/pef31/normalisation_factor.csv index 7ade444..55793ad 100644 --- a/apparun/resources/pef31/normalisation_factor.csv +++ b/apparun/resources/pef31/normalisation_factor.csv @@ -1,17 +1,17 @@ method,score -EFV3_EUTROPHICATION_FRESHWATER,1.61e0 -EFV3_CLIMATE_CHANGE,7.55e3 -EFV3_PARTICULATE_MATTER_FORMATION,5.95e-4 -EFV3_ACIDIFICATION,5.56e1 -EFV3_ECOTOXICITY_FRESHWATER,5.67e4 -EFV3_IONISING_RADIATION,4.22e3 -EFV3_PHOTOCHEMICAL_OZONE_FORMATION,4.09e1 -EFV3_EUTROPHICATION_MARINE,1.95e1 -EFV3_EUTROPHICATION_TERRESTRIAL,1.77e2 -EFV3_ENERGY_RESOURCES,6.50e4 -EFV3_HUMAN_TOXICITY_NON_CARCINOGENIC,1.29e-4 -EFV3_OZONE_DEPLETION,5.23e-2 -EFV3_HUMAN_TOXICITY_CARCINOGENIC,1.73e-5 -EFV3_MATERIAL_RESOURCES,6.36e-2 -EFV3_WATER_USE,1.15e4 -EFV3_LAND_USE,8.19e5 +EFV31_EUTROPHICATION_FRESHWATER,1.61e0 +EFV31_CLIMATE_CHANGE,7.55e3 +EFV31_PARTICULATE_MATTER_FORMATION,5.95e-4 +EFV31_ACIDIFICATION,5.56e1 +EFV31_ECOTOXICITY_FRESHWATER,5.67e4 +EFV31_IONISING_RADIATION,4.22e3 +EFV31_PHOTOCHEMICAL_OZONE_FORMATION,4.09e1 +EFV31_EUTROPHICATION_MARINE,1.95e1 +EFV31_EUTROPHICATION_TERRESTRIAL,1.77e2 +EFV31_ENERGY_RESOURCES,6.50e4 +EFV31_HUMAN_TOXICITY_NON_CARCINOGENIC,1.29e-4 +EFV31_OZONE_DEPLETION,5.23e-2 +EFV31_HUMAN_TOXICITY_CARCINOGENIC,1.73e-5 +EFV31_MATERIAL_RESOURCES,6.36e-2 +EFV31_WATER_USE,1.15e4 +EFV31_LAND_USE,8.19e5 diff --git a/apparun/resources/pef31/weighting_factor.csv b/apparun/resources/pef31/weighting_factor.csv index 397b7a4..b865db1 100644 --- a/apparun/resources/pef31/weighting_factor.csv +++ b/apparun/resources/pef31/weighting_factor.csv @@ -1,17 +1,17 @@ method,score -EFV3_EUTROPHICATION_FRESHWATER,2.80 -EFV3_CLIMATE_CHANGE,21.06 -EFV3_PARTICULATE_MATTER_FORMATION,8.96 -EFV3_ACIDIFICATION,6.20 -EFV3_ECOTOXICITY_FRESHWATER,1.92 -EFV3_IONISING_RADIATION,5.01 -EFV3_PHOTOCHEMICAL_OZONE_FORMATION,4.78 -EFV3_EUTROPHICATION_MARINE,2.96 -EFV3_EUTROPHICATION_TERRESTRIAL,3.71 -EFV3_ENERGY_RESOURCES,8.32 -EFV3_HUMAN_TOXICITY_NON_CARCINOGENIC,1.84 -EFV3_OZONE_DEPLETION,6.31 -EFV3_HUMAN_TOXICITY_CARCINOGENIC,2.13 -EFV3_MATERIAL_RESOURCES,7.55 -EFV3_WATER_USE,8.51 -EFV3_LAND_USE,7.94 +EFV31_EUTROPHICATION_FRESHWATER,2.80 +EFV31_CLIMATE_CHANGE,21.06 +EFV31_PARTICULATE_MATTER_FORMATION,8.96 +EFV31_ACIDIFICATION,6.20 +EFV31_ECOTOXICITY_FRESHWATER,1.92 +EFV31_IONISING_RADIATION,5.01 +EFV31_PHOTOCHEMICAL_OZONE_FORMATION,4.78 +EFV31_EUTROPHICATION_MARINE,2.96 +EFV31_EUTROPHICATION_TERRESTRIAL,3.71 +EFV31_ENERGY_RESOURCES,8.32 +EFV31_HUMAN_TOXICITY_NON_CARCINOGENIC,1.84 +EFV31_OZONE_DEPLETION,6.31 +EFV31_HUMAN_TOXICITY_CARCINOGENIC,2.13 +EFV31_MATERIAL_RESOURCES,7.55 +EFV31_WATER_USE,8.51 +EFV31_LAND_USE,7.94 diff --git a/tests/data/impact_models/5_indicator_model_ef31.yaml b/tests/data/impact_models/5_indicator_model_ef31.yaml new file mode 100644 index 0000000..cfd921b --- /dev/null +++ b/tests/data/impact_models/5_indicator_model_ef31.yaml @@ -0,0 +1,67 @@ +metadata: + author: + name: Maxime PERALTA + organization: CEA + mail: maxime.peralta@cea.fr + reviewer: + name: Makan HALTER + organization: CEA + mail: null + report: + link: https://appalca.github.io/ + description: A mock example of Appa LCA's impact model corresponding to STM32F0 + ICs. + date: 24/11/2025 + version: '2' + license: proprietary + appabuild_version: '0.3' +parameters: +- name: test_param + default: 0 + type: float + min: null + max: null + distrib: linear + pm: 0.0 + pm_perc: null +tree: + name: stm32f0 + models: + EFV31_CLIMATE_CHANGE: "4.761292e-01" + EFV31_IONISING_RADIATION: "6.063838e-01" + EFV31_PARTICULATE_MATTER_FORMATION: "1.871372e-08" + EFV31_ECOTOXICITY_FRESHWATER: "23.44453" + EFV31_ACIDIFICATION: "2.912266e-03" + children: + - name: soc + models: + EFV31_CLIMATE_CHANGE: "4.761292e-01" + EFV31_IONISING_RADIATION: "6.063838e-01" + EFV31_PARTICULATE_MATTER_FORMATION: "1.871372e-08" + EFV31_ECOTOXICITY_FRESHWATER: "23.44453" + EFV31_ACIDIFICATION: "2.912266e-03" + children: [] + properties: {} + amount: '1.0' + - name: use + models: + EFV31_CLIMATE_CHANGE: "4.761292e-01" + EFV31_IONISING_RADIATION: "6.063838e-01" + EFV31_PARTICULATE_MATTER_FORMATION: "1.871372e-08" + EFV31_ECOTOXICITY_FRESHWATER: "23.44453" + EFV31_ACIDIFICATION: "2.912266e-03" + children: [] + properties: {} + amount: '1.0' + - name: eol + models: + EFV31_CLIMATE_CHANGE: "4.761292e-01" + EFV31_IONISING_RADIATION: "6.063838e-01" + EFV31_PARTICULATE_MATTER_FORMATION: "1.871372e-08" + EFV31_ECOTOXICITY_FRESHWATER: "23.44453" + EFV31_ACIDIFICATION: "2.912266e-03" + children: [] + properties: {} + amount: '1.0' + properties: {} + amount: '1.0' diff --git a/tests/functional/test_unique_score.py b/tests/functional/test_impact_methods.py similarity index 81% rename from tests/functional/test_unique_score.py rename to tests/functional/test_impact_methods.py index 78335ed..329e322 100644 --- a/tests/functional/test_unique_score.py +++ b/tests/functional/test_impact_methods.py @@ -3,6 +3,7 @@ import pytest from apparun.exceptions import InvalidFileError +from apparun.impact_methods import MethodFullName, MethodShortName, MethodUniqueScore from apparun.impact_model import ImpactModel @@ -16,6 +17,11 @@ def impact_model_5(): return ImpactModel.from_yaml("tests/data/impact_models/5_indicator_model.yaml") +@pytest.fixture() +def impact_model_5_ef31(): + return ImpactModel.from_yaml("tests/data/impact_models/5_indicator_model_ef31.yaml") + + def test_unique_score_exception(impact_model_16): """ Check an exception is raised when impact categories from normalisation @@ -54,17 +60,29 @@ def test_unique_score_exception(impact_model_16): ) -def test_unique_score_values(impact_model_5): +def test_unique_score_values(impact_model_5, impact_model_5_ef31): try: - score = impact_model_5.get_scores().to_unique_score( - is_normalised=True, is_weighted=True + score_30 = impact_model_5.get_scores().to_unique_score( + method=MethodUniqueScore.EF30, is_normalised=True, is_weighted=True ) except Exception: pytest.fail( "Valid factors / reduced number of impact cat. in model must not raise any exception" ) - - assert math.isclose(score.scores.get("UNIQUE_SCORE")[0], 0.00361857, abs_tol=1e-6) + try: + score_31 = impact_model_5_ef31.get_scores().to_unique_score( + method=MethodUniqueScore.EF31, is_normalised=True, is_weighted=True + ) + except Exception: + pytest.fail( + "Valid factors / reduced number of impact cat. in model must not raise any exception" + ) + assert math.isclose( + score_30.scores.get("UNIQUE_SCORE")[0], 0.00361857, abs_tol=1e-6 + ) + assert math.isclose( + score_31.scores.get("UNIQUE_SCORE")[0], 0.00344846, abs_tol=1e-6 + ) def test_unique_score_values_multi_param(impact_model_5): @@ -150,3 +168,9 @@ def test_unique_node_score_values_multi_param(impact_model_5): 0.00361857, abs_tol=1e-6, ) + + +def test_impact_methods_consistency(): + assert {elem.name for elem in MethodFullName} == { + elem.name for elem in MethodShortName + } From f43d8af521dd0b9d889dd6c7c0e68ed397ec506c Mon Sep 17 00:00:00 2001 From: Maxime PERALTA Date: Thu, 22 Jan 2026 09:17:28 +0100 Subject: [PATCH 2/2] (fix) Correct deprecated docstring --- apparun/impact_methods.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apparun/impact_methods.py b/apparun/impact_methods.py index b77656a..d74273f 100644 --- a/apparun/impact_methods.py +++ b/apparun/impact_methods.py @@ -102,7 +102,7 @@ class MethodShortName(str, Enum): """ Short version of impact methods supported by Brightway, to ease readability of figures. - So far, only some ReCiPe methods, and all EF v3.0 impact methods are supported. + So far, only EF v3.0 and EF v3.1 impact methods are supported. """ _settings_ = NoAlias