From 8dd2d7c02f6dfe6a1a2aae138708c1a4c8d1332b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20N=C3=A4slund?= Date: Fri, 9 Oct 2026 14:38:50 +0200 Subject: [PATCH 01/10] Add generic create method for metrics Adds a method that allow creation a metric based on a type and a provided dictionary of the factors. Usefull for adapters and similar to more easily generate the correct metric on the fly, without the need to hard-code for each type. --- .../EnvironmentalMetric.cs | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EnvironmentalMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EnvironmentalMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EnvironmentalMetric.cs new file mode 100644 index 0000000..3f53351 --- /dev/null +++ b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EnvironmentalMetric.cs @@ -0,0 +1,125 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using BH.Engine.Base; +using BH.oM.Base.Attributes; +using BH.oM.LifeCycleAssessment; +using BH.oM.LifeCycleAssessment.MaterialFragments; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace BH.Engine.LifeCycleAssessment +{ + public static partial class Create + { + /***************************************************/ + /**** Public Methods ****/ + /***************************************************/ + + [Description("Creates an IEnvironmentalMetric of a type matched to the provided MetricType, with the provided values per module stored as its indicators.")] + [Input("metricType", "The type of metric to create. Decides which type of IEnvironmentalMetric is returned, e.g. Acidification will return an AcidificationMetric.")] + [Input("indicators", "The values to be stored on the metric per each module.")] + [Output("metric", "The created IEnvironmentalMetric.")] + public static IEnvironmentalMetric EnvironmentalMetric(MetricType metricType, Dictionary indicators) + { + if (indicators == null) + { + Base.Compute.RecordError($"Cannot create a {nameof(IEnvironmentalMetric)} from null indicators."); + return null; + } + + //Get the pre-compiled default constructor for the metric corresponding to the provided type + Func cst = EnvironmentalMetricConstructor(metricType); + + if (cst == null) + return null; + + IEnvironmentalMetric metric = cst(new object[0]); + + //Indicators is get-only on the interface. Fill the dictionary of the new metric, which also means the provided dictionary is not shared with the metric. + foreach (KeyValuePair indicator in indicators) + { + metric.Indicators[indicator.Key] = indicator.Value; + } + + return metric; + } + + /***************************************************/ + /**** Private Methods ****/ + /***************************************************/ + + [Description("Gets a function corresponding to the default constructor for the IEnvironmentalMetric corresponding to the provided MetricType.")] + [Input("metricType", "The type of metric to find a matching constructor for.")] + [Output("cstFunc", "The function corresponding to the constructor of the IEnvironmentalMetric related to the MetricType.")] + private static Func EnvironmentalMetricConstructor(this MetricType metricType) + { + Func cstFunc; + + //Try get cached constructor func + if (!m_EnvironmentalMetricConstructors.TryGetValue(metricType, out cstFunc)) + { + System.Reflection.ConstructorInfo constructor = GetEnvironmentalMetricConstructorInfo(metricType); + if (constructor != null) + { + //Pre-compile the constructor info to a function to increase performance + Func genericFunc = constructor.ToFunc(); + cstFunc = x => (IEnvironmentalMetric)genericFunc(x); + } + else + cstFunc = null; + + m_EnvironmentalMetricConstructors[metricType] = cstFunc; + } + + if (cstFunc == null) + Base.Compute.RecordError($"Unable to find a constructor for a type of {nameof(IEnvironmentalMetric)} based on provided type {metricType}"); + + return cstFunc; + } + + /***************************************************/ + + [Description("Gets the default ConstructorInfo from an IEnvironmentalMetric type matching the provided MetricType.")] + private static System.Reflection.ConstructorInfo GetEnvironmentalMetricConstructorInfo(MetricType metricType) + { + Type metricClassType = BH.Engine.Base.Query.BHoMTypeList().Where(x => typeof(IEnvironmentalMetric).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract).FirstOrDefault(x => x.Name == metricType.ToString() + "Metric"); + + if (metricClassType == null) + return null; + + return metricClassType.GetConstructor(Type.EmptyTypes); + } + + /***************************************************/ + /**** Private Fields ****/ + /***************************************************/ + + //Storage of the pre-compiled functions for future usage + private static ConcurrentDictionary> m_EnvironmentalMetricConstructors = new ConcurrentDictionary>(); + + /***************************************************/ + } +} From 3130cd078b1b1030aa0fb40c1a16cf553fac3134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20N=C3=A4slund?= Date: Fri, 9 Oct 2026 15:32:48 +0200 Subject: [PATCH 02/10] Add method for creating a full CLimateChange EPD based on fossil, luluc and biogenic metrics and set of modules Usefull for manual creation of EPDs based on table --- ...teChangeEnvironmentalProductDeclaration.cs | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 LifeCycleAssessment_Engine/Create/ClimateChangeEnvironmentalProductDeclaration.cs diff --git a/LifeCycleAssessment_Engine/Create/ClimateChangeEnvironmentalProductDeclaration.cs b/LifeCycleAssessment_Engine/Create/ClimateChangeEnvironmentalProductDeclaration.cs new file mode 100644 index 0000000..71eae84 --- /dev/null +++ b/LifeCycleAssessment_Engine/Create/ClimateChangeEnvironmentalProductDeclaration.cs @@ -0,0 +1,111 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using BH.oM.Base.Attributes; +using BH.oM.LifeCycleAssessment; +using BH.oM.LifeCycleAssessment.MaterialFragments; +using BH.oM.Quantities.Attributes; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace BH.Engine.LifeCycleAssessment +{ + public static partial class Create + { + /***************************************************/ + /**** Public Methods ****/ + /***************************************************/ + + [Description("Creates an EnvironmentalProductDeclaration containing the climate change metrics, given the fossil, biogenic and land use and land use change (LULUC) factors per module. " + + "The ClimateChangeTotal (fossil + biogenic + LULUC) and ClimateChangeTotalNoBiogenic (fossil + LULUC) metrics are computed per module from the provided factors for each module.")] + [Input("modules", "The modules the provided factors correspond to. The factor lists need to be of the same length as this list, with the factor at each index corresponding to the module at the same index.")] + [Input("fossil", "Climate change fossil factors per module, in the same order as the provided modules.", typeof(ClimateChangePerQuantity))] + [Input("biogenic", "Climate change biogenic factors per module, in the same order as the provided modules.", typeof(ClimateChangePerQuantity))] + [Input("luluc", "Climate change land use and land use change (LULUC) factors per module, in the same order as the provided modules.", typeof(ClimateChangePerQuantity))] + [Input("quantityType", "The quantity type the factors are given per, for example Mass for factors given per kg.")] + [Input("epdType", "The type of the EnvironmentalProductDeclaration.")] + [Input("name", "Name of the created EnvironmentalProductDeclaration.")] + [Output("epd", "The created EnvironmentalProductDeclaration containing the fossil, biogenic, LULUC, total and total without biogenic climate change metrics.")] + public static EnvironmentalProductDeclaration ClimateChangeEnvironmentalProductDeclaration(List modules, List fossil, List biogenic, List luluc, QuantityType quantityType = QuantityType.Mass, EPDType epdType = EPDType.Sector, string name = "") + { + if (modules == null || fossil == null || biogenic == null || luluc == null) + { + Base.Compute.RecordError("Cannot create a climate change EnvironmentalProductDeclaration from null modules or factors."); + return null; + } + + if (fossil.Count != modules.Count || biogenic.Count != modules.Count || luluc.Count != modules.Count) + { + Base.Compute.RecordError($"The number of fossil ({fossil.Count}), biogenic ({biogenic.Count}) and LULUC ({luluc.Count}) factors need to match the number of modules ({modules.Count})."); + return null; + } + + if (modules.Distinct().Count() != modules.Count) + { + Base.Compute.RecordError("The provided modules contain duplicates. Each module can only be provided once."); + return null; + } + + if (fossil.Concat(biogenic).Concat(luluc).Any(double.IsNaN)) + { + Base.Compute.RecordError("The fossil, biogenic and LULUC factors need to be provided for all modules. NaN values are not allowed."); + return null; + } + + Dictionary fossilFactors = new Dictionary(); + Dictionary biogenicFactors = new Dictionary(); + Dictionary lulucFactors = new Dictionary(); + Dictionary totalFactors = new Dictionary(); + Dictionary totalNoBiogenicFactors = new Dictionary(); + + for (int i = 0; i < modules.Count; i++) + { + Module module = modules[i]; + fossilFactors[module] = fossil[i]; + biogenicFactors[module] = biogenic[i]; + lulucFactors[module] = luluc[i]; + totalFactors[module] = fossil[i] + biogenic[i] + luluc[i]; + totalNoBiogenicFactors[module] = fossil[i] + luluc[i]; + } + + EnvironmentalProductDeclaration epd = new EnvironmentalProductDeclaration() + { + Name = name, + Type = epdType, + QuantityType = quantityType, + EnvironmentalMetrics = new List() + { + new ClimateChangeFossilMetric() { Indicators = fossilFactors }, + new ClimateChangeBiogenicMetric() { Indicators = biogenicFactors }, + new ClimateChangeLandUseMetric() { Indicators = lulucFactors }, + new ClimateChangeTotalMetric() { Indicators = totalFactors }, + new ClimateChangeTotalNoBiogenicMetric() { Indicators = totalNoBiogenicFactors } + } + }; + + return epd; + } + + /***************************************************/ + } +} From 91d0bc93db0ec24427b40bcb10bcd7c4f5b232de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20N=C3=A4slund?= Date: Tue, 13 Oct 2026 13:20:15 +0200 Subject: [PATCH 03/10] Tweak default values and update descriptions --- .../ClimateChangeEnvironmentalProductDeclaration.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/LifeCycleAssessment_Engine/Create/ClimateChangeEnvironmentalProductDeclaration.cs b/LifeCycleAssessment_Engine/Create/ClimateChangeEnvironmentalProductDeclaration.cs index 71eae84..1dca73e 100644 --- a/LifeCycleAssessment_Engine/Create/ClimateChangeEnvironmentalProductDeclaration.cs +++ b/LifeCycleAssessment_Engine/Create/ClimateChangeEnvironmentalProductDeclaration.cs @@ -39,14 +39,14 @@ public static partial class Create [Description("Creates an EnvironmentalProductDeclaration containing the climate change metrics, given the fossil, biogenic and land use and land use change (LULUC) factors per module. " + "The ClimateChangeTotal (fossil + biogenic + LULUC) and ClimateChangeTotalNoBiogenic (fossil + LULUC) metrics are computed per module from the provided factors for each module.")] [Input("modules", "The modules the provided factors correspond to. The factor lists need to be of the same length as this list, with the factor at each index corresponding to the module at the same index.")] - [Input("fossil", "Climate change fossil factors per module, in the same order as the provided modules.", typeof(ClimateChangePerQuantity))] - [Input("biogenic", "Climate change biogenic factors per module, in the same order as the provided modules.", typeof(ClimateChangePerQuantity))] - [Input("luluc", "Climate change land use and land use change (LULUC) factors per module, in the same order as the provided modules.", typeof(ClimateChangePerQuantity))] + [Input("fossil", "Climate change fossil factors per module, in the same order as the provided modules. Values given should be per quantity set by the quantityType.", typeof(ClimateChangePerQuantity))] + [Input("biogenic", "Climate change biogenic factors per module, in the same order as the provided modules. Values given should be per quantity set by the quantityType.", typeof(ClimateChangePerQuantity))] + [Input("luluc", "Climate change land use and land use change (LULUC) factors per module, in the same order as the provided modules. Values given should be per quantity set by the quantityType.", typeof(ClimateChangePerQuantity))] [Input("quantityType", "The quantity type the factors are given per, for example Mass for factors given per kg.")] [Input("epdType", "The type of the EnvironmentalProductDeclaration.")] [Input("name", "Name of the created EnvironmentalProductDeclaration.")] [Output("epd", "The created EnvironmentalProductDeclaration containing the fossil, biogenic, LULUC, total and total without biogenic climate change metrics.")] - public static EnvironmentalProductDeclaration ClimateChangeEnvironmentalProductDeclaration(List modules, List fossil, List biogenic, List luluc, QuantityType quantityType = QuantityType.Mass, EPDType epdType = EPDType.Sector, string name = "") + public static EnvironmentalProductDeclaration ClimateChangeEnvironmentalProductDeclaration(List modules, List fossil, List biogenic, List luluc, QuantityType quantityType, EPDType epdType = EPDType.Undefined, string name = "") { if (modules == null || fossil == null || biogenic == null || luluc == null) { From 16d1e897d1317d84ce37a9253560400fe26b5e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20N=C3=A4slund?= Date: Tue, 13 Oct 2026 16:29:25 +0200 Subject: [PATCH 04/10] Add unit tests for the methods --- ...ngeEnvironmentalProductDeclarationTests.cs | 192 ++++++++++++++++++ .../CreateEnvironmentalMetricTests.cs | 145 +++++++++++++ 2 files changed, 337 insertions(+) create mode 100644 .ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateClimateChangeEnvironmentalProductDeclarationTests.cs create mode 100644 .ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateEnvironmentalMetricTests.cs diff --git a/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateClimateChangeEnvironmentalProductDeclarationTests.cs b/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateClimateChangeEnvironmentalProductDeclarationTests.cs new file mode 100644 index 0000000..a6f0f63 --- /dev/null +++ b/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateClimateChangeEnvironmentalProductDeclarationTests.cs @@ -0,0 +1,192 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using BH.Engine.LifeCycleAssessment; +using BH.oM.Base.Debugging; +using BH.oM.LifeCycleAssessment; +using BH.oM.LifeCycleAssessment.MaterialFragments; +using BH.oM.Test.NUnit; +using FluentAssertions; +using NUnit.Framework; +using System.Collections.Generic; +using System.Linq; +using LCAModule = BH.oM.LifeCycleAssessment.Module; + +namespace BH.Tests.Engine.LifeCycleAssessment +{ + public class CreateClimateChangeEnvironmentalProductDeclarationTests : NUnitTest + { + /***************************************************/ + /**** Public Methods ****/ + /***************************************************/ + + [Test] + [Description("Tests that the ClimateChangeEnvironmentalProductDeclaration create method assigns the provided properties and creates the five climate change metrics.")] + public void ClimateChangeEPDProperties() + { + EnvironmentalProductDeclaration epd = BH.Engine.LifeCycleAssessment.Create.ClimateChangeEnvironmentalProductDeclaration( + new List { LCAModule.A1toA3 }, + new List { 1 }, + new List { 2 }, + new List { 3 }, + QuantityType.Volume, + EPDType.Sector, + "Concrete"); + + epd.Should().NotBeNull(); + epd.Name.Should().Be("Concrete"); + epd.Type.Should().Be(EPDType.Sector); + epd.QuantityType.Should().Be(QuantityType.Volume); + epd.EnvironmentalMetrics.Select(x => x.GetType()).Should().BeEquivalentTo(new[] + { + typeof(ClimateChangeFossilMetric), + typeof(ClimateChangeBiogenicMetric), + typeof(ClimateChangeLandUseMetric), + typeof(ClimateChangeTotalMetric), + typeof(ClimateChangeTotalNoBiogenicMetric), + }); + } + + /***************************************************/ + + [Test] + [Description("Tests that the ClimateChangeEnvironmentalProductDeclaration create method uses Undefined EPDType and an empty name by default.")] + public void ClimateChangeEPDDefaults() + { + EnvironmentalProductDeclaration epd = BH.Engine.LifeCycleAssessment.Create.ClimateChangeEnvironmentalProductDeclaration( + new List { LCAModule.A1toA3 }, + new List { 1 }, + new List { 2 }, + new List { 3 }, + QuantityType.Mass); + + epd.Type.Should().Be(EPDType.Undefined); + epd.Name.Should().BeEmpty(); + } + + /***************************************************/ + + [Test] + [Description("Tests that the ClimateChangeEnvironmentalProductDeclaration create method stores fossil, biogenic and LULUC values per module and computes the totals correctly.")] + public void ClimateChangeEPDValues() + { + List modules = new List { LCAModule.A1toA3, LCAModule.C3toC4, LCAModule.D }; + List fossil = new List { 10, 1, -2 }; + List biogenic = new List { -4, 0.5, 0 }; + List luluc = new List { 0.25, 0.1, 0.05 }; + + EnvironmentalProductDeclaration epd = BH.Engine.LifeCycleAssessment.Create.ClimateChangeEnvironmentalProductDeclaration(modules, fossil, biogenic, luluc, QuantityType.Mass); + + double tol = 1e-12; + for (int i = 0; i < modules.Count; i++) + { + LCAModule m = modules[i]; + MetricIndicators(epd, MetricType.ClimateChangeFossil)[m].Should().BeApproximately(fossil[i], tol); + MetricIndicators(epd, MetricType.ClimateChangeBiogenic)[m].Should().BeApproximately(biogenic[i], tol); + MetricIndicators(epd, MetricType.ClimateChangeLandUse)[m].Should().BeApproximately(luluc[i], tol); + MetricIndicators(epd, MetricType.ClimateChangeTotal)[m].Should().BeApproximately(fossil[i] + biogenic[i] + luluc[i], tol); + MetricIndicators(epd, MetricType.ClimateChangeTotalNoBiogenic)[m].Should().BeApproximately(fossil[i] + luluc[i], tol); + } + + foreach (IEnvironmentalMetric metric in epd.EnvironmentalMetrics) + metric.Indicators.Keys.Should().BeEquivalentTo(modules); + } + + /***************************************************/ + + [Test] + [Description("Tests that the ClimateChangeEnvironmentalProductDeclaration create method returns an EPD with empty metrics for empty module lists.")] + public void ClimateChangeEPDNoModules() + { + EnvironmentalProductDeclaration epd = BH.Engine.LifeCycleAssessment.Create.ClimateChangeEnvironmentalProductDeclaration( + new List(), new List(), new List(), new List(), QuantityType.Mass); + + epd.Should().NotBeNull(); + epd.EnvironmentalMetrics.Should().HaveCount(5); + epd.EnvironmentalMetrics.Should().OnlyContain(x => x.Indicators.Count == 0); + } + + /***************************************************/ + + [TestCaseSource(nameof(FailingClimateChangeEPDInputs))] + [Description("Tests that the ClimateChangeEnvironmentalProductDeclaration create method returns null and records an error for invalid inputs, such as null lists, mismatched list lengths, duplicate modules and NaN factors.")] + public void ClimateChangeEPDFailingCases(List modules, List fossil, List biogenic, List luluc) + { + BH.Engine.Base.Compute.ClearCurrentEvents(); + + EnvironmentalProductDeclaration epd = BH.Engine.LifeCycleAssessment.Create.ClimateChangeEnvironmentalProductDeclaration(modules, fossil, biogenic, luluc, QuantityType.Mass); + + epd.Should().BeNull(); + ErrorCount().Should().Be(1); + } + + /***************************************************/ + /**** Test Case Sources ****/ + /***************************************************/ + + private static IEnumerable FailingClimateChangeEPDInputs() + { + List oneModule = new List { LCAModule.A1toA3 }; + List one = new List { 1 }; + List two = new List { 1, 2 }; + List nan = new List { double.NaN }; + List empty = new List(); + + yield return new TestCaseData(null, one, one, one).SetArgDisplayNames("Failing case: Null modules"); + yield return new TestCaseData(oneModule, null, one, one).SetArgDisplayNames("Failing case: Null fossil"); + yield return new TestCaseData(oneModule, one, null, one).SetArgDisplayNames("Failing case: Null biogenic"); + yield return new TestCaseData(oneModule, one, one, null).SetArgDisplayNames("Failing case: Null LULUC"); + + yield return new TestCaseData(oneModule, empty, one, one).SetArgDisplayNames("Failing case: Fewer fossil than modules"); + yield return new TestCaseData(oneModule, one, empty, one).SetArgDisplayNames("Failing case: Fewer biogenic than modules"); + yield return new TestCaseData(oneModule, one, one, empty).SetArgDisplayNames("Failing case: Fewer LULUC than modules"); + yield return new TestCaseData(oneModule, two, one, one).SetArgDisplayNames("Failing case: More fossil than modules"); + yield return new TestCaseData(oneModule, one, two, one).SetArgDisplayNames("Failing case: More biogenic than modules"); + yield return new TestCaseData(oneModule, one, one, two).SetArgDisplayNames("Failing case: More LULUC than modules"); + + yield return new TestCaseData(new List { LCAModule.A1toA3, LCAModule.A1toA3 }, two, two, two).SetArgDisplayNames("Failing case: Duplicate modules"); + + yield return new TestCaseData(oneModule, nan, one, one).SetArgDisplayNames("Failing case: NaN fossil"); + yield return new TestCaseData(oneModule, one, nan, one).SetArgDisplayNames("Failing case: NaN biogenic"); + yield return new TestCaseData(oneModule, one, one, nan).SetArgDisplayNames("Failing case: NaN LULUC"); + } + + /***************************************************/ + /**** Private Methods ****/ + /***************************************************/ + + private static Dictionary MetricIndicators(EnvironmentalProductDeclaration epd, MetricType metricType) + { + return epd.EnvironmentalMetrics.Single(x => x.IMetricType() == metricType).Indicators; + } + + /***************************************************/ + + private static int ErrorCount() + { + return BH.Engine.Base.Query.CurrentEvents().Count(x => x.Type == EventType.Error); + } + + /***************************************************/ + } +} + diff --git a/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateEnvironmentalMetricTests.cs b/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateEnvironmentalMetricTests.cs new file mode 100644 index 0000000..632997e --- /dev/null +++ b/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateEnvironmentalMetricTests.cs @@ -0,0 +1,145 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using BH.Engine.LifeCycleAssessment; +using BH.oM.Base.Debugging; +using BH.oM.LifeCycleAssessment; +using BH.oM.LifeCycleAssessment.MaterialFragments; +using BH.oM.Test.NUnit; +using FluentAssertions; +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using LCAModule = BH.oM.LifeCycleAssessment.Module; + +namespace BH.Tests.Engine.LifeCycleAssessment +{ + public class CreateEnvironmentalMetricTests : NUnitTest + { + /***************************************************/ + /**** Public Methods ****/ + /***************************************************/ + + [TestCaseSource(nameof(DefinedMetricTypes))] + [Description("Tests that the EnvironmentalMetric create method returns a metric of the type matching the provided MetricType, with the provided indicators stored on it.")] + public void EnvironmentalMetricMatchesMetricType(MetricType metricType) + { + Dictionary indicators = new Dictionary + { + { LCAModule.A1toA3, 1.5 }, + { LCAModule.C3toC4, 2.5 }, + }; + + IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(metricType, indicators); + + metric.Should().NotBeNull(); + metric.IMetricType().Should().Be(metricType); + metric.Indicators.Should().BeEquivalentTo(indicators); + } + + /***************************************************/ + + [Test] + [Description("Tests that the EnvironmentalMetric create method creates the expected concrete type.")] + public void EnvironmentalMetricConcreteType() + { + IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Acidification, new Dictionary()); + metric.Should().BeOfType(); + } + + /***************************************************/ + + [Test] + [Description("Tests that the EnvironmentalMetric create method does not share the provided indicators dictionary with the created metric.")] + public void EnvironmentalMetricDoesNotShareIndicators() + { + Dictionary indicators = new Dictionary { { LCAModule.A1toA3, 1 } }; + + IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Acidification, indicators); + + indicators[LCAModule.A4] = 5; + metric.Indicators.Should().NotContainKey(LCAModule.A4); + + metric.Indicators[LCAModule.C1] = 7; + indicators.Should().NotContainKey(LCAModule.C1); + } + + /***************************************************/ + + [Test] + [Description("Tests that the EnvironmentalMetric create method returns a metric with no indicators for an empty dictionary.")] + public void EnvironmentalMetricEmptyIndicators() + { + IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.OzoneDepletion, new Dictionary()); + + metric.Should().NotBeNull(); + metric.Indicators.Should().BeEmpty(); + } + + /***************************************************/ + + [Test] + [Description("Tests that the EnvironmentalMetric create method returns null and records an error for null indicators.")] + public void EnvironmentalMetricNullIndicators() + { + BH.Engine.Base.Compute.ClearCurrentEvents(); + + IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Acidification, null); + + metric.Should().BeNull(); + ErrorCount().Should().Be(1); + } + + /***************************************************/ + + [Test] + [Description("Tests that the EnvironmentalMetric create method returns null and records an error for the Undefined MetricType, which has no matching metric.")] + public void EnvironmentalMetricUndefinedType() + { + BH.Engine.Base.Compute.ClearCurrentEvents(); + + IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Undefined, new Dictionary()); + + metric.Should().BeNull(); + ErrorCount().Should().Be(1); + } + + /***************************************************/ + /**** Private Methods ****/ + /***************************************************/ + + private static IEnumerable DefinedMetricTypes() + { + return Enum.GetValues(typeof(MetricType)).Cast().Where(x => x != MetricType.Undefined); + } + + /***************************************************/ + private static int ErrorCount() + { + return BH.Engine.Base.Query.CurrentEvents().Count(x => x.Type == EventType.Error); + } + + /***************************************************/ + } +} + From 416a6a8b84146bad3eaa3526f67e994a7ecce1d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20N=C3=A4slund?= Date: Wed, 14 Oct 2026 09:51:33 +0200 Subject: [PATCH 05/10] Correct the LCA module using --- .../CreateEnvironmentalMetricTests.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateEnvironmentalMetricTests.cs b/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateEnvironmentalMetricTests.cs index 632997e..64f06f4 100644 --- a/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateEnvironmentalMetricTests.cs +++ b/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateEnvironmentalMetricTests.cs @@ -30,7 +30,7 @@ using System; using System.Collections.Generic; using System.Linq; -using LCAModule = BH.oM.LifeCycleAssessment.Module; + namespace BH.Tests.Engine.LifeCycleAssessment { @@ -44,10 +44,10 @@ public class CreateEnvironmentalMetricTests : NUnitTest [Description("Tests that the EnvironmentalMetric create method returns a metric of the type matching the provided MetricType, with the provided indicators stored on it.")] public void EnvironmentalMetricMatchesMetricType(MetricType metricType) { - Dictionary indicators = new Dictionary + Dictionary indicators = new Dictionary { - { LCAModule.A1toA3, 1.5 }, - { LCAModule.C3toC4, 2.5 }, + { Module.A1toA3, 1.5 }, + { Module.C3toC4, 2.5 }, }; IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(metricType, indicators); @@ -63,7 +63,7 @@ public void EnvironmentalMetricMatchesMetricType(MetricType metricType) [Description("Tests that the EnvironmentalMetric create method creates the expected concrete type.")] public void EnvironmentalMetricConcreteType() { - IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Acidification, new Dictionary()); + IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Acidification, new Dictionary()); metric.Should().BeOfType(); } @@ -73,15 +73,15 @@ public void EnvironmentalMetricConcreteType() [Description("Tests that the EnvironmentalMetric create method does not share the provided indicators dictionary with the created metric.")] public void EnvironmentalMetricDoesNotShareIndicators() { - Dictionary indicators = new Dictionary { { LCAModule.A1toA3, 1 } }; + Dictionary indicators = new Dictionary { { Module.A1toA3, 1 } }; IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Acidification, indicators); - indicators[LCAModule.A4] = 5; - metric.Indicators.Should().NotContainKey(LCAModule.A4); + indicators[Module.A4] = 5; + metric.Indicators.Should().NotContainKey(Module.A4); - metric.Indicators[LCAModule.C1] = 7; - indicators.Should().NotContainKey(LCAModule.C1); + metric.Indicators[Module.C1] = 7; + indicators.Should().NotContainKey(Module.C1); } /***************************************************/ @@ -90,7 +90,7 @@ public void EnvironmentalMetricDoesNotShareIndicators() [Description("Tests that the EnvironmentalMetric create method returns a metric with no indicators for an empty dictionary.")] public void EnvironmentalMetricEmptyIndicators() { - IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.OzoneDepletion, new Dictionary()); + IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.OzoneDepletion, new Dictionary()); metric.Should().NotBeNull(); metric.Indicators.Should().BeEmpty(); @@ -118,7 +118,7 @@ public void EnvironmentalMetricUndefinedType() { BH.Engine.Base.Compute.ClearCurrentEvents(); - IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Undefined, new Dictionary()); + IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Undefined, new Dictionary()); metric.Should().BeNull(); ErrorCount().Should().Be(1); From 5d73de5fe77f612b328be39bc241de61c68db138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20N=C3=A4slund?= Date: Wed, 14 Oct 2026 09:52:35 +0200 Subject: [PATCH 06/10] Slight improvement to constructor delegate and improved input descriptions for EnvironmentalMetric create method --- .../EnvironmentalMetric.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EnvironmentalMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EnvironmentalMetric.cs index 3f53351..aff9738 100644 --- a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EnvironmentalMetric.cs +++ b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EnvironmentalMetric.cs @@ -29,6 +29,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Linq.Expressions; namespace BH.Engine.LifeCycleAssessment { @@ -40,7 +41,7 @@ public static partial class Create [Description("Creates an IEnvironmentalMetric of a type matched to the provided MetricType, with the provided values per module stored as its indicators.")] [Input("metricType", "The type of metric to create. Decides which type of IEnvironmentalMetric is returned, e.g. Acidification will return an AcidificationMetric.")] - [Input("indicators", "The values to be stored on the metric per each module.")] + [Input("indicators", "The values to be stored on the metric per each module. Values should be per quantity corresponding to the QuantityType on the epd that this metric is associated with.")] [Output("metric", "The created IEnvironmentalMetric.")] public static IEnvironmentalMetric EnvironmentalMetric(MetricType metricType, Dictionary indicators) { @@ -51,12 +52,12 @@ public static IEnvironmentalMetric EnvironmentalMetric(MetricType metricType, Di } //Get the pre-compiled default constructor for the metric corresponding to the provided type - Func cst = EnvironmentalMetricConstructor(metricType); + Func cst = EnvironmentalMetricConstructor(metricType); if (cst == null) return null; - IEnvironmentalMetric metric = cst(new object[0]); + IEnvironmentalMetric metric = cst(); //Indicators is get-only on the interface. Fill the dictionary of the new metric, which also means the provided dictionary is not shared with the metric. foreach (KeyValuePair indicator in indicators) @@ -74,9 +75,9 @@ public static IEnvironmentalMetric EnvironmentalMetric(MetricType metricType, Di [Description("Gets a function corresponding to the default constructor for the IEnvironmentalMetric corresponding to the provided MetricType.")] [Input("metricType", "The type of metric to find a matching constructor for.")] [Output("cstFunc", "The function corresponding to the constructor of the IEnvironmentalMetric related to the MetricType.")] - private static Func EnvironmentalMetricConstructor(this MetricType metricType) + private static Func EnvironmentalMetricConstructor(this MetricType metricType) { - Func cstFunc; + Func cstFunc; //Try get cached constructor func if (!m_EnvironmentalMetricConstructors.TryGetValue(metricType, out cstFunc)) @@ -84,9 +85,9 @@ private static Func EnvironmentalMetricConstruct System.Reflection.ConstructorInfo constructor = GetEnvironmentalMetricConstructorInfo(metricType); if (constructor != null) { - //Pre-compile the constructor info to a function to increase performance - Func genericFunc = constructor.ToFunc(); - cstFunc = x => (IEnvironmentalMetric)genericFunc(x); + //Pre-compile "() => new XMetric()" to a parameterless function to increase performance. + //No object[] input or cast wrapper is needed as the constructor is known to have no parameters. + cstFunc = Expression.Lambda>(Expression.New(constructor)).Compile(); } else cstFunc = null; @@ -118,7 +119,7 @@ private static System.Reflection.ConstructorInfo GetEnvironmentalMetricConstruct /***************************************************/ //Storage of the pre-compiled functions for future usage - private static ConcurrentDictionary> m_EnvironmentalMetricConstructors = new ConcurrentDictionary>(); + private static ConcurrentDictionary> m_EnvironmentalMetricConstructors = new ConcurrentDictionary>(); /***************************************************/ } From 465be9a3aa3bab31d49dea90391cb0e728bf9317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20N=C3=A4slund?= Date: Thu, 15 Oct 2026 11:55:55 +0200 Subject: [PATCH 07/10] Add more overloads for creating IEnvironmentalMetric Removing the need to create a dictionary first, as well as allowing to create an empty one if downstream processes wants to handle the indicator assignments somewhere else. Also adding a 2-list input that allows for providing a list of modules as well as a list of factors --- .../EnvironmentalMetric.cs | 66 +++++++++++++++++-- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EnvironmentalMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EnvironmentalMetric.cs index aff9738..255869d 100644 --- a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EnvironmentalMetric.cs +++ b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EnvironmentalMetric.cs @@ -39,15 +39,15 @@ public static partial class Create /**** Public Methods ****/ /***************************************************/ - [Description("Creates an IEnvironmentalMetric of a type matched to the provided MetricType, with the provided values per module stored as its indicators.")] + [Description("Creates an IEnvironmentalMetric of a type matched to the provided MetricType without any indicator values set.")] [Input("metricType", "The type of metric to create. Decides which type of IEnvironmentalMetric is returned, e.g. Acidification will return an AcidificationMetric.")] - [Input("indicators", "The values to be stored on the metric per each module. Values should be per quantity corresponding to the QuantityType on the epd that this metric is associated with.")] + [Input("name", "The name to give the created metric. Defaults to an empty string.")] [Output("metric", "The created IEnvironmentalMetric.")] - public static IEnvironmentalMetric EnvironmentalMetric(MetricType metricType, Dictionary indicators) + public static IEnvironmentalMetric EnvironmentalMetric(MetricType metricType, string name = "") { - if (indicators == null) + if (metricType == MetricType.Undefined) { - Base.Compute.RecordError($"Cannot create a {nameof(IEnvironmentalMetric)} from null indicators."); + Base.Compute.RecordError($"Cannot create a {nameof(IEnvironmentalMetric)} from an undefined {nameof(MetricType)}."); return null; } @@ -58,6 +58,30 @@ public static IEnvironmentalMetric EnvironmentalMetric(MetricType metricType, Di return null; IEnvironmentalMetric metric = cst(); + metric.Name = name ?? ""; + return metric; + } + + /***************************************************/ + + [Description("Creates an IEnvironmentalMetric of a type matched to the provided MetricType, with the provided values per module stored as its indicators.")] + [Input("metricType", "The type of metric to create. Decides which type of IEnvironmentalMetric is returned, e.g. Acidification will return an AcidificationMetric.")] + [Input("indicators", "The values to be stored on the metric per each module. Values should be per quantity corresponding to the QuantityType on the epd that this metric is associated with.")] + [Input("name", "The name to give the created metric. Defaults to an empty string.")] + [Output("metric", "The created IEnvironmentalMetric.")] + public static IEnvironmentalMetric EnvironmentalMetric(MetricType metricType, Dictionary indicators, string name = "") + { + if (indicators == null) + { + Base.Compute.RecordError($"Cannot create a {nameof(IEnvironmentalMetric)} from null indicators."); + return null; + } + + //Call the create method that only takes the MetricType to get the correct type of metric created. + IEnvironmentalMetric metric = EnvironmentalMetric(metricType, name); + + if (metric == null) + return null; //Indicators is get-only on the interface. Fill the dictionary of the new metric, which also means the provided dictionary is not shared with the metric. foreach (KeyValuePair indicator in indicators) @@ -68,6 +92,38 @@ public static IEnvironmentalMetric EnvironmentalMetric(MetricType metricType, Di return metric; } + /***************************************************/ + + [Description("Creates an IEnvironmentalMetric of a type matched to the provided MetricType, with the provided values stored as its indicators for the corresponding modules.")] + [Input("metricType", "The type of metric to create. Decides which type of IEnvironmentalMetric is returned, e.g. Acidification will return an AcidificationMetric.")] + [Input("modules", "The modules to store indicator values for. Needs to be the same length as indicators, where each module is matched to the indicator at the same index.")] + [Input("indicators", "The values to be stored on the metric per each module. Needs to be the same length as modules. Values should be per quantity corresponding to the QuantityType on the epd that this metric is associated with.")] + [Input("name", "The name to give the created metric. Defaults to an empty string.")] + [Output("metric", "The created IEnvironmentalMetric.")] + public static IEnvironmentalMetric EnvironmentalMetric(MetricType metricType, List modules, List indicators, string name = "") + { + if (modules == null || indicators == null) + { + Base.Compute.RecordError($"Cannot create a {nameof(IEnvironmentalMetric)} from null modules or indicators."); + return null; + } + + if (modules.Count != indicators.Count) + { + Base.Compute.RecordError($"Cannot create a {nameof(IEnvironmentalMetric)} from modules and indicators of different lengths."); + return null; + } + + Dictionary indicatorsDict = new Dictionary(); + + for (int i = 0; i < modules.Count; i++) + { + indicatorsDict[modules[i]] = indicators[i]; + } + + return EnvironmentalMetric(metricType, indicatorsDict, name); + } + /***************************************************/ /**** Private Methods ****/ /***************************************************/ From f4cc30bdbf32c91c0905a5c4b256383df5ff2415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20N=C3=A4slund?= Date: Thu, 15 Oct 2026 12:09:18 +0200 Subject: [PATCH 08/10] Update unit-tests to handle the new cases --- .../CreateEnvironmentalMetricTests.cs | 95 ++++++++++++------- 1 file changed, 63 insertions(+), 32 deletions(-) diff --git a/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateEnvironmentalMetricTests.cs b/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateEnvironmentalMetricTests.cs index 64f06f4..ea6df7a 100644 --- a/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateEnvironmentalMetricTests.cs +++ b/.ci/unit-tests/LifeCycleAssessment_Engine_Tests/CreateEnvironmentalMetricTests.cs @@ -41,7 +41,7 @@ public class CreateEnvironmentalMetricTests : NUnitTest /***************************************************/ [TestCaseSource(nameof(DefinedMetricTypes))] - [Description("Tests that the EnvironmentalMetric create method returns a metric of the type matching the provided MetricType, with the provided indicators stored on it.")] + [Description("Tests that all overloads of the EnvironmentalMetric create method return a metric of the type matching the provided MetricType, with the provided indicators stored on it and an empty name by default.")] public void EnvironmentalMetricMatchesMetricType(MetricType metricType) { Dictionary indicators = new Dictionary @@ -50,11 +50,41 @@ public void EnvironmentalMetricMatchesMetricType(MetricType metricType) { Module.C3toC4, 2.5 }, }; - IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(metricType, indicators); + IEnvironmentalMetric typeOnly = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(metricType); + IEnvironmentalMetric fromDictionary = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(metricType, indicators); + IEnvironmentalMetric fromLists = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(metricType, indicators.Keys.ToList(), indicators.Values.ToList()); - metric.Should().NotBeNull(); - metric.IMetricType().Should().Be(metricType); - metric.Indicators.Should().BeEquivalentTo(indicators); + typeOnly.Indicators.Should().BeEmpty(); + fromDictionary.Indicators.Should().BeEquivalentTo(indicators); + fromLists.Indicators.Should().BeEquivalentTo(indicators); + + foreach (IEnvironmentalMetric metric in new[] { typeOnly, fromDictionary, fromLists }) + { + metric.Should().NotBeNull(); + metric.IMetricType().Should().Be(metricType); + metric.Name.Should().Be(""); + } + } + + /***************************************************/ + + [Test] + [Description("Tests that all overloads of the EnvironmentalMetric create method set the provided name, and fall back to an empty name for null.")] + public void EnvironmentalMetricName() + { + Dictionary indicators = new Dictionary { { Module.A1toA3, 1 } }; + + IEnvironmentalMetric[] metrics = new[] + { + BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Acidification, "My metric"), + BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Acidification, indicators, "My metric"), + BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Acidification, indicators.Keys.ToList(), indicators.Values.ToList(), "My metric"), + }; + + metrics.Select(x => x.Name).Should().OnlyContain(x => x == "My metric"); + + //Cast needed to resolve the overload, as null is also a valid indicators input + BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Acidification, (string)null).Name.Should().Be(""); } /***************************************************/ @@ -86,54 +116,55 @@ public void EnvironmentalMetricDoesNotShareIndicators() /***************************************************/ - [Test] - [Description("Tests that the EnvironmentalMetric create method returns a metric with no indicators for an empty dictionary.")] - public void EnvironmentalMetricEmptyIndicators() - { - IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.OzoneDepletion, new Dictionary()); - - metric.Should().NotBeNull(); - metric.Indicators.Should().BeEmpty(); - } - - /***************************************************/ - - [Test] - [Description("Tests that the EnvironmentalMetric create method returns null and records an error for null indicators.")] - public void EnvironmentalMetricNullIndicators() + [TestCaseSource(nameof(InvalidInputs))] + [Description("Tests that the EnvironmentalMetric create method returns null and records a single error for invalid inputs, without throwing.")] + public void EnvironmentalMetricInvalidInputs(Func create) { BH.Engine.Base.Compute.ClearCurrentEvents(); - IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Acidification, null); + IEnvironmentalMetric metric = null; + create.Invoking(x => metric = x()).Should().NotThrow(); metric.Should().BeNull(); ErrorCount().Should().Be(1); } + /***************************************************/ + /**** Private Methods ****/ /***************************************************/ - [Test] - [Description("Tests that the EnvironmentalMetric create method returns null and records an error for the Undefined MetricType, which has no matching metric.")] - public void EnvironmentalMetricUndefinedType() + private static IEnumerable DefinedMetricTypes() { - BH.Engine.Base.Compute.ClearCurrentEvents(); + return Enum.GetValues(typeof(MetricType)).Cast().Where(x => x != MetricType.Undefined); + } - IEnvironmentalMetric metric = BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Undefined, new Dictionary()); + /***************************************************/ - metric.Should().BeNull(); - ErrorCount().Should().Be(1); + private static IEnumerable InvalidInputs() + { + Dictionary indicators = new Dictionary { { Module.A1toA3, 1 } }; + List modules = new List { Module.A1toA3 }; + List values = new List { 1 }; + + //Cast needed to resolve the overload, as null is also a valid name + yield return Case("Undefined type", () => BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Undefined)); + yield return Case("Undefined type with indicators", () => BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Undefined, indicators)); + yield return Case("Undefined type with modules and indicators", () => BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Undefined, modules, values)); + yield return Case("Null indicators", () => BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Acidification, (Dictionary)null)); + yield return Case("Null modules", () => BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Acidification, null, values)); + yield return Case("Null indicators with modules", () => BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Acidification, modules, (List)null)); + yield return Case("Modules and indicators of different lengths", () => BH.Engine.LifeCycleAssessment.Create.EnvironmentalMetric(MetricType.Acidification, new List { Module.A1toA3, Module.C3toC4 }, values)); } - /***************************************************/ - /**** Private Methods ****/ /***************************************************/ - private static IEnumerable DefinedMetricTypes() + private static TestCaseData Case(string name, Func create) { - return Enum.GetValues(typeof(MetricType)).Cast().Where(x => x != MetricType.Undefined); + return new TestCaseData(create).SetArgDisplayNames(name); } /***************************************************/ + private static int ErrorCount() { return BH.Engine.Base.Query.CurrentEvents().Count(x => x.Type == EventType.Error); From d6b5b3e865f05c919b43670b862a27b2cf65140c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20N=C3=A4slund?= Date: Thu, 15 Oct 2026 12:11:27 +0200 Subject: [PATCH 09/10] Put EPD create method in EPD folder --- .../ClimateChangeEnvironmentalProductDeclaration.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename LifeCycleAssessment_Engine/Create/{ => EnvironmentalProductDeclaration}/ClimateChangeEnvironmentalProductDeclaration.cs (100%) diff --git a/LifeCycleAssessment_Engine/Create/ClimateChangeEnvironmentalProductDeclaration.cs b/LifeCycleAssessment_Engine/Create/EnvironmentalProductDeclaration/ClimateChangeEnvironmentalProductDeclaration.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/ClimateChangeEnvironmentalProductDeclaration.cs rename to LifeCycleAssessment_Engine/Create/EnvironmentalProductDeclaration/ClimateChangeEnvironmentalProductDeclaration.cs From 8de7500ec2ad9828c6e41d42d0ac1bcd80553934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Isak=20N=C3=A4slund?= Date: Thu, 15 Oct 2026 12:13:03 +0200 Subject: [PATCH 10/10] Rename folder for all create methods for metrics Folder was referencing an old interface name. Folder name should match the base interface for the types returned. This has no impact on the code as the namespace stays as it was --- .../AbioticDepletionFossilResourcesMetric.cs | 0 .../AbioticDepletionMineralsAndMetalsMetric.cs | 0 .../AcidificationMetric.cs | 0 .../ClimateChangeBiogenicMetric.cs | 0 .../ClimateChangeFossilMetric.cs | 0 .../ClimateChangeLandUseMetric.cs | 0 .../ClimateChangeTotalMetric.cs | 0 .../ClimateChangeTotalNoBiogenicMetric.cs | 0 .../EnvironmentalMetric.cs | 0 .../EutrophicationAquaticFreshwaterMetric.cs | 0 .../EutrophicationAquaticMarineMetric.cs | 0 .../EutrophicationCMLMetric.cs | 0 .../EutrophicationTRACIMetric.cs | 0 .../EutrophicationTerrestrialMetric.cs | 0 .../FactorsDictionary.cs | 0 .../OzoneDepletionMetric.cs | 0 .../PhotochemicalOzoneCreationCMLMetric.cs | 0 .../PhotochemicalOzoneCreationMetric.cs | 0 .../PhotochemicalOzoneCreationTRACIMetric.cs | 0 .../WaterDeprivationMetric.cs | 0 20 files changed, 0 insertions(+), 0 deletions(-) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/AbioticDepletionFossilResourcesMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/AbioticDepletionMineralsAndMetalsMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/AcidificationMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/ClimateChangeBiogenicMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/ClimateChangeFossilMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/ClimateChangeLandUseMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/ClimateChangeTotalMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/ClimateChangeTotalNoBiogenicMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/EnvironmentalMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/EutrophicationAquaticFreshwaterMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/EutrophicationAquaticMarineMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/EutrophicationCMLMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/EutrophicationTRACIMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/EutrophicationTerrestrialMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/FactorsDictionary.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/OzoneDepletionMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/PhotochemicalOzoneCreationCMLMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/PhotochemicalOzoneCreationMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/PhotochemicalOzoneCreationTRACIMetric.cs (100%) rename LifeCycleAssessment_Engine/Create/{IEnvironmentalMetricFactors => IEnvironmentalMetric}/WaterDeprivationMetric.cs (100%) diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/AbioticDepletionFossilResourcesMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/AbioticDepletionFossilResourcesMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/AbioticDepletionFossilResourcesMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/AbioticDepletionFossilResourcesMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/AbioticDepletionMineralsAndMetalsMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/AbioticDepletionMineralsAndMetalsMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/AbioticDepletionMineralsAndMetalsMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/AbioticDepletionMineralsAndMetalsMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/AcidificationMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/AcidificationMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/AcidificationMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/AcidificationMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/ClimateChangeBiogenicMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/ClimateChangeBiogenicMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/ClimateChangeBiogenicMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/ClimateChangeBiogenicMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/ClimateChangeFossilMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/ClimateChangeFossilMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/ClimateChangeFossilMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/ClimateChangeFossilMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/ClimateChangeLandUseMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/ClimateChangeLandUseMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/ClimateChangeLandUseMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/ClimateChangeLandUseMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/ClimateChangeTotalMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/ClimateChangeTotalMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/ClimateChangeTotalMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/ClimateChangeTotalMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/ClimateChangeTotalNoBiogenicMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/ClimateChangeTotalNoBiogenicMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/ClimateChangeTotalNoBiogenicMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/ClimateChangeTotalNoBiogenicMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EnvironmentalMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/EnvironmentalMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EnvironmentalMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/EnvironmentalMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EutrophicationAquaticFreshwaterMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/EutrophicationAquaticFreshwaterMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EutrophicationAquaticFreshwaterMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/EutrophicationAquaticFreshwaterMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EutrophicationAquaticMarineMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/EutrophicationAquaticMarineMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EutrophicationAquaticMarineMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/EutrophicationAquaticMarineMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EutrophicationCMLMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/EutrophicationCMLMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EutrophicationCMLMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/EutrophicationCMLMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EutrophicationTRACIMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/EutrophicationTRACIMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EutrophicationTRACIMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/EutrophicationTRACIMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EutrophicationTerrestrialMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/EutrophicationTerrestrialMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/EutrophicationTerrestrialMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/EutrophicationTerrestrialMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/FactorsDictionary.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/FactorsDictionary.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/FactorsDictionary.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/FactorsDictionary.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/OzoneDepletionMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/OzoneDepletionMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/OzoneDepletionMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/OzoneDepletionMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/PhotochemicalOzoneCreationCMLMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/PhotochemicalOzoneCreationCMLMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/PhotochemicalOzoneCreationCMLMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/PhotochemicalOzoneCreationCMLMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/PhotochemicalOzoneCreationMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/PhotochemicalOzoneCreationMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/PhotochemicalOzoneCreationMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/PhotochemicalOzoneCreationMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/PhotochemicalOzoneCreationTRACIMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/PhotochemicalOzoneCreationTRACIMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/PhotochemicalOzoneCreationTRACIMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/PhotochemicalOzoneCreationTRACIMetric.cs diff --git a/LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/WaterDeprivationMetric.cs b/LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/WaterDeprivationMetric.cs similarity index 100% rename from LifeCycleAssessment_Engine/Create/IEnvironmentalMetricFactors/WaterDeprivationMetric.cs rename to LifeCycleAssessment_Engine/Create/IEnvironmentalMetric/WaterDeprivationMetric.cs