From 16f2d563440139388842258b356ddae0909bace9 Mon Sep 17 00:00:00 2001 From: Bert Scholten Date: Wed, 24 Apr 2024 12:01:18 +0200 Subject: [PATCH] AER-2827 animal housing domain, validation and emission calculation (#261) * Deprecated existing farm lodging domain classes These are to be replaced by the farm animal housing classes that will be introduced. * Domain objects for farm animal housing Including json roundtrip with (I think) all of the aspects covered. * Implemented emission calculator and validator for animal housing Also added the necessary methods in EmissionSourceVisitor and such, with a default implementation, so we can go ahead with working on this without having to adjust something in the main branch for calculator. --- .../aerius/gml/v5_1/togml/Source2GML.java | 8 + .../aerius/gml/v6_0/togml/Source2GML.java | 8 + .../test/TestValidationAndEmissionHelper.java | 137 ++++- .../domain/v2/source/EmissionSource.java | 1 + .../domain/v2/source/EmissionSourceType.java | 7 + .../v2/source/EmissionSourceVisitor.java | 10 +- .../FarmAnimalHousingEmissionSource.java | 47 ++ .../v2/source/FarmLodgingEmissionSource.java | 4 + .../source/farm/AdditionalHousingSystem.java | 39 ++ .../source/farm/AdditionalLodgingSystem.java | 4 + .../farm/CustomAdditionalHousingSystem.java | 55 ++ .../source/farm/CustomFarmAnimalHousing.java | 55 ++ .../v2/source/farm/CustomFarmLodging.java | 4 + .../v2/source/farm/FarmAnimalHousing.java | 71 +++ .../domain/v2/source/farm/FarmLodging.java | 4 + .../v2/source/farm/LodgingFodderMeasure.java | 4 + .../domain/v2/source/farm/LodgingSystem.java | 4 + .../source/farm/ReductiveLodgingSystem.java | 4 + .../farm/StandardAdditionalHousingSystem.java | 36 ++ .../farm/StandardFarmAnimalHousing.java | 44 ++ .../v2/source/farm/StandardFarmLodging.java | 4 + .../emissions/EmissionFactorSupplier.java | 4 + .../shared/emissions/EmissionsCalculator.java | 10 + ...rmAnimalHousingEmissionFactorSupplier.java | 54 ++ .../FarmAnimalHousingEmissionsCalculator.java | 203 +++++++ .../FarmLodgingEmissionFactorSupplier.java | 4 + .../FarmLodgingEmissionsCalculator.java | 4 + .../exception/ImaerExceptionReason.java | 21 + .../shared/domain/JsonRoundTripTest.java | 1 + .../emissions/EmissionsCalculatorTest.java | 19 +- ...mAnimalHousingEmissionsCalculatorTest.java | 403 ++++++++++++++ .../json/FarmAnimalHousingEmissionSource.json | 63 +++ .../FarmAnimalHousingValidationHelper.java | 35 ++ .../FarmAnimalHousingValidator.java | 140 +++++ .../FarmLodgingValidationHelper.java | 4 + .../validation/FarmLodgingValidator.java | 4 + .../aerius/validation/ValidationHelper.java | 4 + .../aerius/validation/ValidationVisitor.java | 6 + .../FarmAnimalHousingValidatorTest.java | 494 ++++++++++++++++++ 39 files changed, 2011 insertions(+), 12 deletions(-) create mode 100644 source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/FarmAnimalHousingEmissionSource.java create mode 100644 source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/AdditionalHousingSystem.java create mode 100644 source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/CustomAdditionalHousingSystem.java create mode 100644 source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/CustomFarmAnimalHousing.java create mode 100644 source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/FarmAnimalHousing.java create mode 100644 source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/StandardAdditionalHousingSystem.java create mode 100644 source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/StandardFarmAnimalHousing.java create mode 100644 source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmAnimalHousingEmissionFactorSupplier.java create mode 100644 source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmAnimalHousingEmissionsCalculator.java create mode 100644 source/imaer-shared/src/test/java/nl/overheid/aerius/shared/emissions/FarmAnimalHousingEmissionsCalculatorTest.java create mode 100644 source/imaer-shared/src/test/resources/json/FarmAnimalHousingEmissionSource.json create mode 100644 source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmAnimalHousingValidationHelper.java create mode 100644 source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmAnimalHousingValidator.java create mode 100644 source/imaer-util/src/test/java/nl/overheid/aerius/validation/FarmAnimalHousingValidatorTest.java diff --git a/source/imaer-gml/src/main/java/nl/overheid/aerius/gml/v5_1/togml/Source2GML.java b/source/imaer-gml/src/main/java/nl/overheid/aerius/gml/v5_1/togml/Source2GML.java index f59a9cae..b813616f 100644 --- a/source/imaer-gml/src/main/java/nl/overheid/aerius/gml/v5_1/togml/Source2GML.java +++ b/source/imaer-gml/src/main/java/nl/overheid/aerius/gml/v5_1/togml/Source2GML.java @@ -37,6 +37,7 @@ import nl.overheid.aerius.shared.domain.v2.source.EmissionSource; import nl.overheid.aerius.shared.domain.v2.source.EmissionSourceFeature; import nl.overheid.aerius.shared.domain.v2.source.EmissionSourceVisitor; +import nl.overheid.aerius.shared.domain.v2.source.FarmAnimalHousingEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.FarmLodgingEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.FarmlandEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.GenericEmissionSource; @@ -171,6 +172,13 @@ public nl.overheid.aerius.gml.v5_1.source.EmissionSource visit(final FarmLodging return new Farm2GML().convert(emissionSource); } + @Override + public nl.overheid.aerius.gml.v5_1.source.EmissionSource visit(final FarmAnimalHousingEmissionSource emissionSource, final IsFeature feature) + throws AeriusException { + // In this version the animal housing is not implemented, just export as a generic source (other option would be an exception). + return new nl.overheid.aerius.gml.v5_1.source.EmissionSource(); + } + @Override public nl.overheid.aerius.gml.v5_1.source.EmissionSource visit(final FarmlandEmissionSource emissionSource, final IsFeature feature) throws AeriusException { diff --git a/source/imaer-gml/src/main/java/nl/overheid/aerius/gml/v6_0/togml/Source2GML.java b/source/imaer-gml/src/main/java/nl/overheid/aerius/gml/v6_0/togml/Source2GML.java index 54c85fd3..d7563d62 100644 --- a/source/imaer-gml/src/main/java/nl/overheid/aerius/gml/v6_0/togml/Source2GML.java +++ b/source/imaer-gml/src/main/java/nl/overheid/aerius/gml/v6_0/togml/Source2GML.java @@ -34,6 +34,7 @@ import nl.overheid.aerius.shared.domain.v2.source.EmissionSource; import nl.overheid.aerius.shared.domain.v2.source.EmissionSourceFeature; import nl.overheid.aerius.shared.domain.v2.source.EmissionSourceVisitor; +import nl.overheid.aerius.shared.domain.v2.source.FarmAnimalHousingEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.FarmLodgingEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.FarmlandEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.GenericEmissionSource; @@ -166,6 +167,13 @@ public nl.overheid.aerius.gml.v6_0.source.EmissionSource visit(final FarmLodging return new Farm2GML().convert(emissionSource); } + @Override + public nl.overheid.aerius.gml.v6_0.source.EmissionSource visit(final FarmAnimalHousingEmissionSource emissionSource, final IsFeature feature) + throws AeriusException { + // TODO: actual IMAER part (AER-2828) + return new nl.overheid.aerius.gml.v6_0.source.EmissionSource(); + } + @Override public nl.overheid.aerius.gml.v6_0.source.EmissionSource visit(final FarmlandEmissionSource emissionSource, final IsFeature feature) throws AeriusException { diff --git a/source/imaer-gml/src/test/java/nl/overheid/aerius/test/TestValidationAndEmissionHelper.java b/source/imaer-gml/src/test/java/nl/overheid/aerius/test/TestValidationAndEmissionHelper.java index fc3d3c89..6a49bc3c 100644 --- a/source/imaer-gml/src/test/java/nl/overheid/aerius/test/TestValidationAndEmissionHelper.java +++ b/source/imaer-gml/src/test/java/nl/overheid/aerius/test/TestValidationAndEmissionHelper.java @@ -40,6 +40,7 @@ import nl.overheid.aerius.shared.domain.v2.source.shipping.maritime.ShippingMovementType; import nl.overheid.aerius.shared.emissions.ColdStartEmissionFactorSupplier; import nl.overheid.aerius.shared.emissions.EmissionFactorSupplier; +import nl.overheid.aerius.shared.emissions.FarmAnimalHousingEmissionFactorSupplier; import nl.overheid.aerius.shared.emissions.FarmEmissionFactorType; import nl.overheid.aerius.shared.emissions.FarmLodgingEmissionFactorSupplier; import nl.overheid.aerius.shared.emissions.FarmlandEmissionFactorSupplier; @@ -53,6 +54,7 @@ import nl.overheid.aerius.shared.emissions.shipping.ShippingLaden; import nl.overheid.aerius.shared.exception.AeriusException; import nl.overheid.aerius.validation.ColdStartValidationHelper; +import nl.overheid.aerius.validation.FarmAnimalHousingValidationHelper; import nl.overheid.aerius.validation.FarmLodgingValidationHelper; import nl.overheid.aerius.validation.FarmlandValidationHelper; import nl.overheid.aerius.validation.InlandShippingValidationHelper; @@ -66,20 +68,21 @@ * Test data for validation. */ public class TestValidationAndEmissionHelper implements ValidationHelper, EmissionFactorSupplier, - FarmLodgingEmissionFactorSupplier, FarmlandEmissionFactorSupplier, ManureStorageEmissionFactorSupplier, OffRoadMobileEmissionFactorSupplier, - ColdStartEmissionFactorSupplier, RoadEmissionFactorSupplier, InlandShippingEmissionFactorSupplier, MaritimeShippingEmissionFactorSupplier, - FarmLodgingValidationHelper, FarmlandValidationHelper, ManureStorageValidationHelper, OffRoadValidationHelper, ColdStartValidationHelper, - RoadValidationHelper, InlandShippingValidationHelper, MaritimeShippingValidationHelper { + FarmLodgingEmissionFactorSupplier, FarmAnimalHousingEmissionFactorSupplier, FarmlandEmissionFactorSupplier, ManureStorageEmissionFactorSupplier, + OffRoadMobileEmissionFactorSupplier, ColdStartEmissionFactorSupplier, RoadEmissionFactorSupplier, InlandShippingEmissionFactorSupplier, + MaritimeShippingEmissionFactorSupplier, FarmLodgingValidationHelper, FarmAnimalHousingValidationHelper, FarmlandValidationHelper, + ManureStorageValidationHelper, OffRoadValidationHelper, ColdStartValidationHelper, RoadValidationHelper, InlandShippingValidationHelper, + MaritimeShippingValidationHelper { private static final List FARM_LODGING_CATEGORIES = Arrays.asList( new FarmConstructHelper("A1.4", 9.2, false), new FarmConstructHelper("B1.100", 0.7, false), new FarmConstructHelper("C1.100", 1.9, false), - new FarmConstructHelper("D3.1", 4.5, false, "BWL2001.21"), + new FarmConstructHelper("D3.1", 4.5, false), new FarmConstructHelper("D1.3.3", 2.5, false), new FarmConstructHelper("D3.2.7.2.1", 1.5, false), new FarmConstructHelper("F4.4", 0.2, true), - new FarmConstructHelper("A4.2", 1.1, true, "BWL2011.12"), + new FarmConstructHelper("A4.2", 1.1, true), new FarmConstructHelper("A3.100", 4.4, false), new FarmConstructHelper("A2.100", 4.1, false), new FarmConstructHelper("A1.1", 5.7, false), @@ -99,6 +102,36 @@ public class TestValidationAndEmissionHelper implements ValidationHelper, Emissi new FarmFodderConstructHelper("PAS2015.05-01", 0.2, 0.2, 0.2, 0.3, 0.7, true), new FarmFodderConstructHelper("PAS2015.04-01", 0.1, 0.1, 0.1, 0.3, 0.7, false)); + private static final List FARM_ANIMAL_CATEGORIES = Arrays.asList( + "HA1", + "HA2", + "HA3", + "HA4", + "HB1", + "HC1", + "HD1", + "HD3", + "HF1"); + + private static final List FARM_ANIMAL_HOUSING_CATEGORIES = Arrays.asList( + new FarmConstructHelper("HA1.1", 5.7), + new FarmConstructHelper("HA1.28", 9.9), + new FarmConstructHelper("HA1.100", 13), + new FarmConstructHelper("HA2.100", 4.4), + new FarmConstructHelper("HA3.2", 1.9), + new FarmConstructHelper("HA4.100", 4.1), + new FarmConstructHelper("HB1.100", 0.7), + new FarmConstructHelper("HC1.100", 1.9), + new FarmConstructHelper("HD1.3.2", 0.25), + new FarmConstructHelper("HD3.1", 2.4), + new FarmConstructHelper("HD3.8.2", 2.5), + new FarmConstructHelper("HF1.4", 0.2)); + + private static final List FARM_ADDITIONAL_HOUSING_SYSTEM_CATEGORIES = Arrays.asList( + new FarmConstructHelper("LW1.1", 0.7, true), + new FarmConstructHelper("LW2.9", 0.7, true), + new FarmConstructHelper("AV1.1", 0.4, false)); + private static final List FARMLAND_CATEGORIES = Arrays.asList( "PASTURE", "MANURE", @@ -403,13 +436,15 @@ private static class FarmConstructHelper { final String code; final double emissionFactor; final boolean scrubber; - final List systemDefinitions; - FarmConstructHelper(final String code, final double emissionFactor, final boolean scrubber, final String... systemDefinitions) { + FarmConstructHelper(final String code, final double emissionFactor) { + this(code, emissionFactor, false); + } + + FarmConstructHelper(final String code, final double emissionFactor, final boolean scrubber) { this.code = code; this.emissionFactor = emissionFactor; this.scrubber = scrubber; - this.systemDefinitions = Arrays.asList(systemDefinitions); } } @@ -486,6 +521,11 @@ public FarmLodgingEmissionFactorSupplier farmLodging() { return this; } + @Override + public FarmAnimalHousingEmissionFactorSupplier farmAnimalHousing() { + return this; + } + @Override public FarmlandEmissionFactorSupplier farmland() { return this; @@ -526,6 +566,11 @@ public FarmLodgingValidationHelper farmLodgingValidation() { return this; } + @Override + public FarmAnimalHousingValidationHelper farmAnimalHousingValidation() { + return this; + } + @Override public FarmlandValidationHelper farmlandValidation() { return this; @@ -573,6 +618,11 @@ public FarmEmissionFactorType getLodgingEmissionFactorType(final String lodgingC return FarmEmissionFactorType.PER_ANIMAL_PER_YEAR; } + @Override + public FarmEmissionFactorType getAnimalHousingEmissionFactorType(final String animalHousingCode) { + return FarmEmissionFactorType.PER_ANIMAL_PER_YEAR; + } + @Override public Map getFarmSourceEmissionFactors(final String farmSourceCategoryCode) { return Map.of(Substance.NH3, 0.104); @@ -706,6 +756,57 @@ public boolean isValidFarmLodgingFodderMeasureCode(final String fodderMeasureCod return farmFodderMeasure(fodderMeasureCode).isPresent(); } + @Override + public Map getAnimalHousingEmissionFactors(final String animalHousingCode) { + return farmAnimalHousing(animalHousingCode) + .map(c -> Map.of(Substance.NH3, c.emissionFactor)) + .orElse(Map.of()); + } + + @Override + public boolean isAdditionalHousingSystemAirScrubber(final String additionalSystemCode) { + return farmAnimalHousing(additionalSystemCode) + .map(c -> c.scrubber) + .orElse(false); + } + + @Override + public String getAnimalBasicHousingCode(final String animalCode) { + return animalCode + ".100"; + } + + @Override + public Map getAdditionalHousingSystemReductionFractions(final String additionalSystemCode, final String animalCode) { + return farmAnimalHousing(additionalSystemCode) + .map(c -> Map.of(Substance.NH3, c.emissionFactor)) + .orElse(Map.of()); + } + + @Override + public boolean isValidFarmAnimalCode(final String animalCode) { + return farmAnimal(animalCode).isPresent(); + } + + @Override + public boolean isValidFarmAnimalHousingCode(final String animalHousingCode) { + return farmAnimalHousing(animalHousingCode).isPresent(); + } + + @Override + public boolean isValidFarmAnimalHousingCombination(final String animalCode, final String animalHousingCode) { + return animalHousingCode.startsWith(animalCode); + } + + @Override + public boolean isValidFarmAdditionalSystemCode(final String systemCode) { + return farmAdditionalHousingSystem(systemCode).isPresent(); + } + + @Override + public boolean isValidFarmAdditionalSystemCombination(final String animalCode, final String systemCode) { + return animalCode.startsWith("HA"); + } + @Override public boolean isValidFarmlandActivityCode(final String activityCode) { return FARMLAND_CATEGORIES.stream() @@ -942,6 +1043,24 @@ private Optional farmFodderMeasure(final String fodde .findFirst(); } + private Optional farmAnimal(final String farmAnimalCode) { + return FARM_ANIMAL_CATEGORIES.stream() + .filter(c -> c.equalsIgnoreCase(farmAnimalCode)) + .findFirst(); + } + + private Optional farmAnimalHousing(final String farmAnimalHousingCode) { + return FARM_ANIMAL_HOUSING_CATEGORIES.stream() + .filter(c -> c.code.equalsIgnoreCase(farmAnimalHousingCode)) + .findFirst(); + } + + private Optional farmAdditionalHousingSystem(final String farmAdditionalSystemCode) { + return FARM_ADDITIONAL_HOUSING_SYSTEM_CATEGORIES.stream() + .filter(c -> c.code.equalsIgnoreCase(farmAdditionalSystemCode)) + .findFirst(); + } + private Optional offRoad(final String offRoadMobileSourceCode) { return OFF_ROAD_MOBILE_SOURCE_CATEGORIES.stream() .filter(c -> c.code.equalsIgnoreCase(offRoadMobileSourceCode)) diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/EmissionSource.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/EmissionSource.java index 796f0788..8319b00a 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/EmissionSource.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/EmissionSource.java @@ -36,6 +36,7 @@ @JsonSubTypes({ @Type(value = GenericEmissionSource.class, name = EmissionSourceType.Names.GENERIC), @Type(value = FarmLodgingEmissionSource.class, name = EmissionSourceType.Names.FARM_LODGE), + @Type(value = FarmAnimalHousingEmissionSource.class, name = EmissionSourceType.Names.FARM_ANIMAL_HOUSING), @Type(value = FarmlandEmissionSource.class, name = EmissionSourceType.Names.FARMLAND), @Type(value = ManureStorageEmissionSource.class, name = EmissionSourceType.Names.MANURE_STORAGE), @Type(value = OffRoadMobileEmissionSource.class, name = EmissionSourceType.Names.OFFROAD_MOBILE), diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/EmissionSourceType.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/EmissionSourceType.java index 3a5301ac..6b846416 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/EmissionSourceType.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/EmissionSourceType.java @@ -22,8 +22,14 @@ public enum EmissionSourceType { /** * Farm Lodge emission values. + * @Deprecated Replaced by Animal Housing approach */ + @Deprecated(forRemoval = true) FARM_LODGE(Names.FARM_LODGE), + /** + * Farm animal housing emission values. + */ + FARM_ANIMAL_HOUSING(Names.FARM_ANIMAL_HOUSING), /** * Farmland emission values. */ @@ -83,6 +89,7 @@ public enum EmissionSourceType { public static final class Names { public static final String FARM_LODGE = "FARM_LODGE"; + public static final String FARM_ANIMAL_HOUSING = "FARM_ANIMAL_HOUSING"; public static final String FARMLAND = "FARMLAND"; public static final String MANURE_STORAGE = "MANURE_STORAGE"; public static final String GENERIC = "GENERIC"; diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/EmissionSourceVisitor.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/EmissionSourceVisitor.java index 6a23a2d3..2331f461 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/EmissionSourceVisitor.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/EmissionSourceVisitor.java @@ -25,11 +25,19 @@ */ public interface EmissionSourceVisitor { + /** + * @Deprecated Replaced by animal housing, will be removed in the future. + */ + @Deprecated(forRemoval = true) T visit(FarmLodgingEmissionSource emissionSource, IsFeature feature) throws AeriusException; + default T visit(final FarmAnimalHousingEmissionSource emissionSource, final IsFeature feature) throws AeriusException { + return null; + } + T visit(FarmlandEmissionSource emissionSource, IsFeature feature) throws AeriusException; - T visit(final ManureStorageEmissionSource emissionSource, final IsFeature feature) throws AeriusException; + T visit(ManureStorageEmissionSource emissionSource, IsFeature feature) throws AeriusException; T visit(OffRoadMobileEmissionSource emissionSource, IsFeature feature) throws AeriusException; diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/FarmAnimalHousingEmissionSource.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/FarmAnimalHousingEmissionSource.java new file mode 100644 index 00000000..16b62dad --- /dev/null +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/FarmAnimalHousingEmissionSource.java @@ -0,0 +1,47 @@ +/* + * Copyright the State of the Netherlands + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package nl.overheid.aerius.shared.domain.v2.source; + +import java.time.LocalDate; + +import nl.overheid.aerius.shared.domain.v2.geojson.IsFeature; +import nl.overheid.aerius.shared.domain.v2.source.farm.FarmAnimalHousing; +import nl.overheid.aerius.shared.exception.AeriusException; + +/** + * Emission source for farm animal housing. + */ +public class FarmAnimalHousingEmissionSource extends EmissionSourceWithSubSources { + + private static final long serialVersionUID = 1L; + + private LocalDate established; + + public LocalDate getEstablished() { + return established; + } + + public void setEstablished(final LocalDate established) { + this.established = established; + } + + @Override + T accept(final EmissionSourceVisitor visitor, final IsFeature feature) throws AeriusException { + return visitor.visit(this, feature); + } + +} diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/FarmLodgingEmissionSource.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/FarmLodgingEmissionSource.java index c4405141..e2a735b7 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/FarmLodgingEmissionSource.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/FarmLodgingEmissionSource.java @@ -22,6 +22,10 @@ import nl.overheid.aerius.shared.domain.v2.source.farm.FarmLodging; import nl.overheid.aerius.shared.exception.AeriusException; +/** + * @Deprecated Replaced by Animal Housing approach + */ +@Deprecated(forRemoval = true) public class FarmLodgingEmissionSource extends EmissionSourceWithSubSources { private static final long serialVersionUID = 1L; diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/AdditionalHousingSystem.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/AdditionalHousingSystem.java new file mode 100644 index 00000000..094b10ac --- /dev/null +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/AdditionalHousingSystem.java @@ -0,0 +1,39 @@ +/* + * Copyright the State of the Netherlands + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package nl.overheid.aerius.shared.domain.v2.source.farm; + +import java.io.Serializable; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; + +/** + * Base class for farm animal additional housing systems. + * These additional systems can be used to reduce emissions. + */ +@JsonTypeInfo(property = "additionalSystemType", use = Id.NAME) +@JsonSubTypes({ + @Type(value = StandardAdditionalHousingSystem.class, name = "STANDARD"), + @Type(value = CustomAdditionalHousingSystem.class, name = "CUSTOM"), +}) +public abstract class AdditionalHousingSystem implements Serializable { + + private static final long serialVersionUID = 1L; + +} diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/AdditionalLodgingSystem.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/AdditionalLodgingSystem.java index 47ea543c..3f9d9b0c 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/AdditionalLodgingSystem.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/AdditionalLodgingSystem.java @@ -16,6 +16,10 @@ */ package nl.overheid.aerius.shared.domain.v2.source.farm; +/** + * @Deprecated Replaced by Animal Housing approach + */ +@Deprecated(forRemoval = true) public class AdditionalLodgingSystem extends LodgingSystem { private static final long serialVersionUID = 1L; diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/CustomAdditionalHousingSystem.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/CustomAdditionalHousingSystem.java new file mode 100644 index 00000000..c82f415f --- /dev/null +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/CustomAdditionalHousingSystem.java @@ -0,0 +1,55 @@ +/* + * Copyright the State of the Netherlands + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package nl.overheid.aerius.shared.domain.v2.source.farm; + +import java.util.HashMap; +import java.util.Map; + +import nl.overheid.aerius.shared.domain.Substance; + +/** + * Custom farm additional animal housing system, based on user supplied emission reduction factors. + */ +public class CustomAdditionalHousingSystem extends AdditionalHousingSystem { + + private static final long serialVersionUID = 1L; + + private String description; + private boolean airScrubber; + private final Map emissionReductionFactors = new HashMap<>(); + + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } + + public boolean isAirScrubber() { + return airScrubber; + } + + public void setAirScrubber(final boolean airScrubber) { + this.airScrubber = airScrubber; + } + + public Map getEmissionReductionFactors() { + return emissionReductionFactors; + } + +} diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/CustomFarmAnimalHousing.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/CustomFarmAnimalHousing.java new file mode 100644 index 00000000..6bb46e82 --- /dev/null +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/CustomFarmAnimalHousing.java @@ -0,0 +1,55 @@ +/* + * Copyright the State of the Netherlands + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package nl.overheid.aerius.shared.domain.v2.source.farm; + +import java.util.HashMap; +import java.util.Map; + +import nl.overheid.aerius.shared.domain.Substance; +import nl.overheid.aerius.shared.emissions.FarmEmissionFactorType; + +/** + * Custom farm animal housing definition, based on user supplied emission factors. + */ +public class CustomFarmAnimalHousing extends FarmAnimalHousing { + + private static final long serialVersionUID = 1L; + + private String description; + private FarmEmissionFactorType farmEmissionFactorType; + private Map emissionFactors = new HashMap<>(); + + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } + + public FarmEmissionFactorType getFarmEmissionFactorType() { + return farmEmissionFactorType; + } + + public void setFarmEmissionFactorType(final FarmEmissionFactorType farmEmissionFactorType) { + this.farmEmissionFactorType = farmEmissionFactorType; + } + + public Map getEmissionFactors() { + return emissionFactors; + } +} diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/CustomFarmLodging.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/CustomFarmLodging.java index 679953fe..acb5a95c 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/CustomFarmLodging.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/CustomFarmLodging.java @@ -22,6 +22,10 @@ import nl.overheid.aerius.shared.domain.Substance; import nl.overheid.aerius.shared.emissions.FarmEmissionFactorType; +/** + * @Deprecated Replaced by Animal Housing approach + */ +@Deprecated(forRemoval = true) public class CustomFarmLodging extends FarmLodging { private static final long serialVersionUID = 2L; diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/FarmAnimalHousing.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/FarmAnimalHousing.java new file mode 100644 index 00000000..b089c9e0 --- /dev/null +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/FarmAnimalHousing.java @@ -0,0 +1,71 @@ +/* + * Copyright the State of the Netherlands + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package nl.overheid.aerius.shared.domain.v2.source.farm; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; + +import nl.overheid.aerius.shared.domain.v2.source.base.AbstractSubSource; +import nl.overheid.aerius.shared.emissions.IsFarmEmissionFactorTypeObject; + +/** + * Base class for farm animal housing sub sources. + * Animal housing represents a number of animals of a certain animal type that are housed together. + */ +@JsonTypeInfo(property = "animalHousingType", use = Id.NAME) +@JsonSubTypes({ + @Type(value = CustomFarmAnimalHousing.class, name = "CUSTOM"), + @Type(value = StandardFarmAnimalHousing.class, name = "STANDARD"), +}) +public abstract class FarmAnimalHousing extends AbstractSubSource implements IsFarmEmissionFactorTypeObject { + + private static final long serialVersionUID = 1L; + + private int numberOfAnimals; + private Integer numberOfDays; + private String animalTypeCode; + + @Override + public Integer getNumberOfAnimals() { + return numberOfAnimals; + } + + public void setNumberOfAnimals(final int numberOfAnimals) { + this.numberOfAnimals = numberOfAnimals; + } + + @Override + public Integer getNumberOfDays() { + return numberOfDays; + } + + @Override + public void setNumberOfDays(final Integer numberOfDays) { + this.numberOfDays = numberOfDays; + } + + public String getAnimalTypeCode() { + return animalTypeCode; + } + + public void setAnimalTypeCode(final String animalTypeCode) { + this.animalTypeCode = animalTypeCode; + } + +} diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/FarmLodging.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/FarmLodging.java index 2fbc1f88..ac421be2 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/FarmLodging.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/FarmLodging.java @@ -24,6 +24,10 @@ import nl.overheid.aerius.shared.domain.v2.source.base.AbstractSubSource; import nl.overheid.aerius.shared.emissions.IsFarmEmissionFactorTypeObject; +/** + * @Deprecated Replaced by Animal Housing approach + */ +@Deprecated(forRemoval = true) @JsonTypeInfo(property = "farmLodgingType", use = Id.NAME) @JsonSubTypes({ @Type(value = CustomFarmLodging.class, name = "CUSTOM"), diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/LodgingFodderMeasure.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/LodgingFodderMeasure.java index d0871738..74120bc9 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/LodgingFodderMeasure.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/LodgingFodderMeasure.java @@ -18,6 +18,10 @@ import java.io.Serializable; +/** + * @Deprecated Replaced by Animal Housing approach + */ +@Deprecated(forRemoval = true) public class LodgingFodderMeasure implements Serializable { private static final long serialVersionUID = 1L; diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/LodgingSystem.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/LodgingSystem.java index b341a4e3..67a803ca 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/LodgingSystem.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/LodgingSystem.java @@ -23,6 +23,10 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo.Id; +/** + * @Deprecated Replaced by Animal Housing approach + */ +@Deprecated(forRemoval = true) @JsonTypeInfo(property = "lodgingSystemType", use = Id.NAME) @JsonSubTypes({ @Type(value = ReductiveLodgingSystem.class, name = "REDUCTIVE"), diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/ReductiveLodgingSystem.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/ReductiveLodgingSystem.java index dc191b97..aed40406 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/ReductiveLodgingSystem.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/ReductiveLodgingSystem.java @@ -16,6 +16,10 @@ */ package nl.overheid.aerius.shared.domain.v2.source.farm; +/** + * @Deprecated Replaced by Animal Housing approach + */ +@Deprecated(forRemoval = true) public class ReductiveLodgingSystem extends LodgingSystem { private static final long serialVersionUID = 1L; diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/StandardAdditionalHousingSystem.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/StandardAdditionalHousingSystem.java new file mode 100644 index 00000000..6144cb8a --- /dev/null +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/StandardAdditionalHousingSystem.java @@ -0,0 +1,36 @@ +/* + * Copyright the State of the Netherlands + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package nl.overheid.aerius.shared.domain.v2.source.farm; + +/** + * Standard farm additional animal housing system, based on a known housing code. + */ +public class StandardAdditionalHousingSystem extends AdditionalHousingSystem { + + private static final long serialVersionUID = 1L; + + private String additionalSystemCode; + + public String getAdditionalSystemCode() { + return additionalSystemCode; + } + + public void setAdditionalSystemCode(final String additionalSystemCode) { + this.additionalSystemCode = additionalSystemCode; + } + +} diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/StandardFarmAnimalHousing.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/StandardFarmAnimalHousing.java new file mode 100644 index 00000000..8c3b4e58 --- /dev/null +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/StandardFarmAnimalHousing.java @@ -0,0 +1,44 @@ +/* + * Copyright the State of the Netherlands + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package nl.overheid.aerius.shared.domain.v2.source.farm; + +import java.util.ArrayList; +import java.util.List; + +/** + * Standard farm animal housing definition, based on a known housing code. + */ +public class StandardFarmAnimalHousing extends FarmAnimalHousing { + + private static final long serialVersionUID = 1L; + + private String animalHousingCode; + private final List additionalSystems = new ArrayList<>(); + + public String getAnimalHousingCode() { + return animalHousingCode; + } + + public void setAnimalHousingCode(final String animalHousingCode) { + this.animalHousingCode = animalHousingCode; + } + + public List getAdditionalSystems() { + return additionalSystems; + } + +} diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/StandardFarmLodging.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/StandardFarmLodging.java index fdbd86d2..1bde658f 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/StandardFarmLodging.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/domain/v2/source/farm/StandardFarmLodging.java @@ -19,6 +19,10 @@ import java.util.ArrayList; import java.util.List; +/** + * @Deprecated Replaced by Animal Housing approach + */ +@Deprecated(forRemoval = true) public class StandardFarmLodging extends FarmLodging { private static final long serialVersionUID = 1L; diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/EmissionFactorSupplier.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/EmissionFactorSupplier.java index 5c309140..d85eac4b 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/EmissionFactorSupplier.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/EmissionFactorSupplier.java @@ -23,6 +23,10 @@ public interface EmissionFactorSupplier { FarmLodgingEmissionFactorSupplier farmLodging(); + default FarmAnimalHousingEmissionFactorSupplier farmAnimalHousing() { + return null; + } + FarmlandEmissionFactorSupplier farmland(); ManureStorageEmissionFactorSupplier manureStorage(); diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/EmissionsCalculator.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/EmissionsCalculator.java index 8ad4b8eb..0cbf4fa7 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/EmissionsCalculator.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/EmissionsCalculator.java @@ -23,6 +23,7 @@ import nl.overheid.aerius.shared.domain.v2.source.ADMSRoadEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.ColdStartEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.EmissionSourceVisitor; +import nl.overheid.aerius.shared.domain.v2.source.FarmAnimalHousingEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.FarmLodgingEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.FarmlandEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.GenericEmissionSource; @@ -40,6 +41,7 @@ class EmissionsCalculator implements EmissionSourceVisitor> { private final FarmLodgingEmissionsCalculator farmLodgingEmissionsCalculator; + private final FarmAnimalHousingEmissionsCalculator farmAnimalHousingEmissionsCalculator; private final FarmlandEmissionsCalculator farmlandEmissionsCalculator; private final ManureStorageEmissionsCalculator manureStorageEmissionsCalculator; private final OffRoadMobileEmissionsCalculator offRoadMobileEmissionsCalculator; @@ -51,6 +53,7 @@ class EmissionsCalculator implements EmissionSourceVisitor visit(final FarmLodgingEmissionSource emissionSour return farmLodgingEmissionsCalculator.calculateEmissions(emissionSource); } + @Override + public Map visit(final FarmAnimalHousingEmissionSource emissionSource, final IsFeature feature) throws AeriusException { + return farmAnimalHousingEmissionsCalculator.calculateEmissions(emissionSource); + } + @Override public Map visit(final FarmlandEmissionSource emissionSource, final IsFeature feature) throws AeriusException { return farmlandEmissionsCalculator.updateEmissions(emissionSource); diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmAnimalHousingEmissionFactorSupplier.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmAnimalHousingEmissionFactorSupplier.java new file mode 100644 index 00000000..9efe6fbd --- /dev/null +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmAnimalHousingEmissionFactorSupplier.java @@ -0,0 +1,54 @@ +/* + * Copyright the State of the Netherlands + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package nl.overheid.aerius.shared.emissions; + +import java.util.Map; + +import nl.overheid.aerius.shared.domain.Substance; + +/** + * Emission factor supplier for farm animal housing. + */ +public interface FarmAnimalHousingEmissionFactorSupplier { + + /** + * Obtain emission factors per animal for farm animal housing. + * This should be the flat emission factor. + */ + Map getAnimalHousingEmissionFactors(String animalHousingCode); + + /** + * Determine the emission factor type for the animal housing. + */ + FarmEmissionFactorType getAnimalHousingEmissionFactorType(String animalHousingCode); + + /** + * Determine if the additional housing system is in fact a scrubber. + */ + boolean isAdditionalHousingSystemAirScrubber(String additionalSystemCode); + + /** + * Get the code of the basic housing system for an animal type. + */ + String getAnimalBasicHousingCode(String animalCode); + + /** + * Obtain the remaining fraction of emission for additonal system for animal housing. + */ + Map getAdditionalHousingSystemReductionFractions(String additionalSystemCode, String animalCode); + +} diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmAnimalHousingEmissionsCalculator.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmAnimalHousingEmissionsCalculator.java new file mode 100644 index 00000000..41aeec61 --- /dev/null +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmAnimalHousingEmissionsCalculator.java @@ -0,0 +1,203 @@ +/* + * Copyright the State of the Netherlands + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package nl.overheid.aerius.shared.emissions; + +import java.math.BigDecimal; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import nl.overheid.aerius.shared.domain.Substance; +import nl.overheid.aerius.shared.domain.v2.source.FarmAnimalHousingEmissionSource; +import nl.overheid.aerius.shared.domain.v2.source.farm.AdditionalHousingSystem; +import nl.overheid.aerius.shared.domain.v2.source.farm.CustomAdditionalHousingSystem; +import nl.overheid.aerius.shared.domain.v2.source.farm.CustomFarmAnimalHousing; +import nl.overheid.aerius.shared.domain.v2.source.farm.FarmAnimalHousing; +import nl.overheid.aerius.shared.domain.v2.source.farm.StandardAdditionalHousingSystem; +import nl.overheid.aerius.shared.domain.v2.source.farm.StandardFarmAnimalHousing; +import nl.overheid.aerius.shared.exception.AeriusException; +import nl.overheid.aerius.shared.exception.ImaerExceptionReason; + +/** + * Emissions calculator for farm animal housing. + */ +public class FarmAnimalHousingEmissionsCalculator { + + /** + * Fraction that main system emission reduction is constrained by if there are any scrubbers. + */ + private static final BigDecimal SCRUBBER_CONSTRAIN_FRACTION = BigDecimal.valueOf(0.3); + + private final FarmAnimalHousingEmissionFactorSupplier emissionFactorSupplier; + + public FarmAnimalHousingEmissionsCalculator(final FarmAnimalHousingEmissionFactorSupplier emissionFactorSupplier) { + this.emissionFactorSupplier = emissionFactorSupplier; + } + + public Map calculateEmissions(final FarmAnimalHousingEmissionSource animalHousingSource) throws AeriusException { + final Map summed = new EnumMap<>(Substance.class); + for (final FarmAnimalHousing animalHousing : animalHousingSource.getSubSources()) { + final Map emissionsForAnimalHousing = calculateEmissions(animalHousing); + + emissionsForAnimalHousing.forEach((key, value) -> animalHousing.getEmissions().put(key, value.doubleValue())); + emissionsForAnimalHousing.forEach((key, value) -> summed.merge(key, value, (v1, v2) -> v1.add(v2))); + } + final Map result = new EnumMap<>(Substance.class); + + summed.forEach((key, value) -> result.put(key, value.doubleValue())); + return result; + } + + private Map calculateEmissions(final FarmAnimalHousing animalHousing) throws AeriusException { + final Map emissions; + + if (animalHousing instanceof CustomFarmAnimalHousing) { + emissions = calculateEmissions((CustomFarmAnimalHousing) animalHousing); + } else if (animalHousing instanceof StandardFarmAnimalHousing) { + emissions = calculateEmissions((StandardFarmAnimalHousing) animalHousing); + } else { + throw new AeriusException(ImaerExceptionReason.INTERNAL_ERROR, "Unknown animal housing type"); + } + + return emissions; + } + + Map calculateEmissions(final CustomFarmAnimalHousing customAnimalHousing) { + final Map results = new EnumMap<>(Substance.class); + final BigDecimal numberOfAnimals = BigDecimal.valueOf(customAnimalHousing.getNumberOfAnimals()); + final BigDecimal periodFactor = customAnimalHousing.getFarmEmissionFactorType() == FarmEmissionFactorType.PER_ANIMAL_PER_DAY + ? BigDecimal.valueOf(customAnimalHousing.getNumberOfDays()) + : BigDecimal.ONE; + + customAnimalHousing.getEmissionFactors().forEach( + (key, value) -> results.put(key, BigDecimal.valueOf(value).multiply(numberOfAnimals).multiply(periodFactor))); + return results; + } + + Map calculateEmissions(final StandardFarmAnimalHousing standardAnimalHousing) throws AeriusException { + // Get emission of base lodging type + final Map emissions = getFlatEmission(standardAnimalHousing); + + // Reduce emissions based on additional systems + processAdditionalSystems(emissions, standardAnimalHousing.getAdditionalSystems(), standardAnimalHousing.getAnimalTypeCode()); + + final BigDecimal periodFactor = + emissionFactorSupplier + .getAnimalHousingEmissionFactorType(standardAnimalHousing.getAnimalHousingCode()) == FarmEmissionFactorType.PER_ANIMAL_PER_DAY + ? BigDecimal.valueOf(standardAnimalHousing.getNumberOfDays()) + : BigDecimal.ONE; + + emissions.forEach((key, value) -> emissions.put(key, value.multiply(periodFactor))); + return emissions; + } + + private Map getFlatEmission(final StandardFarmAnimalHousing standardAnimalHousing) { + final Map emissions = new EnumMap<>(Substance.class); + final BigDecimal numberOfAnimals = BigDecimal.valueOf(standardAnimalHousing.getNumberOfAnimals()); + final Map emissionFactors = determineEmissionFactorsAnimalHousing(standardAnimalHousing); + + emissionFactors.forEach((key, value) -> emissions.put(key, BigDecimal.valueOf(value).multiply(numberOfAnimals))); + return emissions; + } + + private Map determineEmissionFactorsAnimalHousing(final StandardFarmAnimalHousing standardAnimalHousing) { + final String animalHousingCode = standardAnimalHousing.getAnimalHousingCode(); + final Map housingEmissionFactors = emissionFactorSupplier.getAnimalHousingEmissionFactors(animalHousingCode); + + if (isAnyAdditionalSystemScrubber(standardAnimalHousing)) { + final String basicHousingCode = emissionFactorSupplier.getAnimalBasicHousingCode(standardAnimalHousing.getAnimalTypeCode()); + return basicHousingCode == null || basicHousingCode.equals(animalHousingCode) + ? housingEmissionFactors + : obtainConstrainedEmissionFactors(housingEmissionFactors, emissionFactorSupplier.getAnimalHousingEmissionFactors(basicHousingCode)); + } else { + return housingEmissionFactors; + } + } + + private boolean isAnyAdditionalSystemScrubber(final StandardFarmAnimalHousing standardLodging) { + final boolean hasAdditionalStandardScrubber = standardLodging.getAdditionalSystems().stream() + .filter(StandardAdditionalHousingSystem.class::isInstance) + .map(StandardAdditionalHousingSystem.class::cast) + .map(StandardAdditionalHousingSystem::getAdditionalSystemCode) + .anyMatch(emissionFactorSupplier::isAdditionalHousingSystemAirScrubber); + + final boolean hasAdditionalCustomScrubber = standardLodging.getAdditionalSystems().stream() + .filter(CustomAdditionalHousingSystem.class::isInstance) + .map(CustomAdditionalHousingSystem.class::cast) + .anyMatch(CustomAdditionalHousingSystem::isAirScrubber); + + return hasAdditionalStandardScrubber || hasAdditionalCustomScrubber; + } + + /** + * Dutch calculation rule: + * When an air scrubber is applied as an additional system in combination with a housing system + * where the emission factor is lower than 30% of the emission factor for ammonia for a basic housing system: + * ammonia emission = ammonia emissionfactor basic housing system x (100% - reductionpercentage ammonia scrubber) x 0.3 + * + * In essence this means the ammonia emissionfactor of the housing system is constrained to a maximum of 30% of the associated + * basic housing system. + */ + private Map obtainConstrainedEmissionFactors(final Map systemEmissionFactors, + final Map basicEmissionFactors) { + final Map constrained = new EnumMap<>(Substance.class); + systemEmissionFactors.forEach((substance, factor) -> { + if (substance == Substance.NH3) { + final BigDecimal basicFactor = BigDecimal.valueOf(basicEmissionFactors.getOrDefault(substance, factor)).multiply(SCRUBBER_CONSTRAIN_FRACTION); + constrained.put(substance, Math.max(factor, basicFactor.doubleValue())); + } else { + constrained.put(substance, factor); + } + }); + return constrained; + } + + private void processAdditionalSystems(final Map emissions, final List additionalSystems, + final String animalTypeCode) throws AeriusException { + for (final AdditionalHousingSystem additionalSystem : additionalSystems) { + final Map reductionFractions; + if (additionalSystem instanceof StandardAdditionalHousingSystem) { + reductionFractions = toBigDecimalMap(emissionFactorSupplier.getAdditionalHousingSystemReductionFractions( + ((StandardAdditionalHousingSystem) additionalSystem).getAdditionalSystemCode(), animalTypeCode)); + } else if (additionalSystem instanceof CustomAdditionalHousingSystem) { + reductionFractions = toBigDecimalMap( + ((CustomAdditionalHousingSystem) additionalSystem).getEmissionReductionFactors()); + } else { + throw new AeriusException(ImaerExceptionReason.INTERNAL_ERROR, "Unknown animal additional housing type"); + } + + for (final Entry entry : emissions.entrySet()) { + final Substance substance = entry.getKey(); + + if (reductionFractions.containsKey(substance)) { + // Remaining emission = original emission * remaining fraction = original emission * (1 - reduction fraction) + final BigDecimal reducedValue = entry.getValue().multiply(BigDecimal.ONE.subtract(reductionFractions.get(substance))); + entry.setValue(reducedValue); + } + } + } + } + + private static Map toBigDecimalMap(final Map original) { + final Map result = new EnumMap<>(Substance.class); + + original.forEach((key, value) -> result.put(key, BigDecimal.valueOf(value))); + return result; + } + +} diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmLodgingEmissionFactorSupplier.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmLodgingEmissionFactorSupplier.java index 1d5955af..3b71570b 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmLodgingEmissionFactorSupplier.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmLodgingEmissionFactorSupplier.java @@ -20,6 +20,10 @@ import nl.overheid.aerius.shared.domain.Substance; +/** + * @Deprecated Replaced by Animal Housing approach + */ +@Deprecated(forRemoval = true) public interface FarmLodgingEmissionFactorSupplier { /** diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmLodgingEmissionsCalculator.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmLodgingEmissionsCalculator.java index e6110cca..5a4af188 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmLodgingEmissionsCalculator.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/emissions/FarmLodgingEmissionsCalculator.java @@ -34,6 +34,10 @@ import nl.overheid.aerius.shared.exception.AeriusException; import nl.overheid.aerius.shared.exception.ImaerExceptionReason; +/** + * @Deprecated Replaced by Animal Housing approach + */ +@Deprecated(forRemoval = true) public class FarmLodgingEmissionsCalculator { /** diff --git a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/exception/ImaerExceptionReason.java b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/exception/ImaerExceptionReason.java index 36092204..7cfad780 100644 --- a/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/exception/ImaerExceptionReason.java +++ b/source/imaer-shared/src/main/java/nl/overheid/aerius/shared/exception/ImaerExceptionReason.java @@ -499,6 +499,27 @@ public enum ImaerExceptionReason implements Reason { * @param 1 cold start type category */ GML_UNKNOWN_COLD_START_CATEGORY(5239), + /** + * GML contains a unknown animal housing code. + * + * @param 0 the id of the object containing the error. + * @param 1 The code that is unknown. + */ + GML_UNKNOWN_ANIMAL_HOUSING_CODE(5260), + /** + * GML contains a unknown animal housing code. + * + * @param 0 the id of the object containing the error. + * @param 1 The first code that does not combine. + * @param 2 The second code that does not combine. + */ + GML_UNSUPPORTED_ANIMAL_HOUSING_COMBINATION(5261), + /** + * GML contains incorrect custom factors. + * + * @param 0 the id of the object containing the error. + */ + GML_INCORRECT_CUSTOM_FACTORS(5262), // Cohesion (between files) errors. diff --git a/source/imaer-shared/src/test/java/nl/overheid/aerius/shared/domain/JsonRoundTripTest.java b/source/imaer-shared/src/test/java/nl/overheid/aerius/shared/domain/JsonRoundTripTest.java index 1bfbffad..abcacc08 100644 --- a/source/imaer-shared/src/test/java/nl/overheid/aerius/shared/domain/JsonRoundTripTest.java +++ b/source/imaer-shared/src/test/java/nl/overheid/aerius/shared/domain/JsonRoundTripTest.java @@ -72,6 +72,7 @@ void testConvertGeometries(final String fileName) throws IOException { "GenericEmissionSource.json", "GenericADMSEmissionSource.json", "FarmLodgingEmissionSource.json", + "FarmAnimalHousingEmissionSource.json", "FarmlandEmissionSource.json", "OffRoadMobileEmissionSource.json", "SRM1RoadEmissionSource.json", diff --git a/source/imaer-shared/src/test/java/nl/overheid/aerius/shared/emissions/EmissionsCalculatorTest.java b/source/imaer-shared/src/test/java/nl/overheid/aerius/shared/emissions/EmissionsCalculatorTest.java index d1db6483..f019d11d 100644 --- a/source/imaer-shared/src/test/java/nl/overheid/aerius/shared/emissions/EmissionsCalculatorTest.java +++ b/source/imaer-shared/src/test/java/nl/overheid/aerius/shared/emissions/EmissionsCalculatorTest.java @@ -31,6 +31,7 @@ import nl.overheid.aerius.shared.domain.Substance; import nl.overheid.aerius.shared.domain.v2.geojson.Geometry; import nl.overheid.aerius.shared.domain.v2.geojson.IsFeature; +import nl.overheid.aerius.shared.domain.v2.source.FarmAnimalHousingEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.FarmLodgingEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.FarmlandEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.GenericEmissionSource; @@ -48,6 +49,7 @@ class EmissionsCalculatorTest { @Mock FarmLodgingEmissionsCalculator farmLodgingCalculator; + @Mock FarmAnimalHousingEmissionsCalculator farmAnimalHousingCalculator; @Mock FarmlandEmissionsCalculator farmlandCalculator; @Mock ManureStorageEmissionsCalculator manureStorageCalculator; @Mock OffRoadMobileEmissionsCalculator offRoadMobileCalculator; @@ -62,8 +64,9 @@ class EmissionsCalculatorTest { @BeforeEach void beforeEach() throws AeriusException { emissionsCalculator = - new EmissionsCalculator(farmLodgingCalculator, farmlandCalculator, manureStorageCalculator, offRoadMobileCalculator, - coldStartEmissionsCalculator, srmRoadCalculator, admsRoadCalculator, inlandShippingCalculator, maritimeShippingCalculator); + new EmissionsCalculator(farmLodgingCalculator, farmAnimalHousingCalculator, farmlandCalculator, manureStorageCalculator, + offRoadMobileCalculator, coldStartEmissionsCalculator, srmRoadCalculator, admsRoadCalculator, inlandShippingCalculator, + maritimeShippingCalculator); } @Test @@ -78,6 +81,18 @@ void testVisitFarmLodging() throws AeriusException { "Calculated emission values from FarmLodgingEmissionsCalculator should be returned for farm lodging source"); } + @Test + void testVisitFarmAnimalHousing() throws AeriusException { + final Map calculatedEmissions = Map.of(Substance.NH3, 324.0); + final FarmAnimalHousingEmissionSource emissionSource = mock(FarmAnimalHousingEmissionSource.class); + when(farmAnimalHousingCalculator.calculateEmissions(emissionSource)).thenReturn(calculatedEmissions); + + final Map result = emissionsCalculator.visit(emissionSource, null); + + assertEquals(calculatedEmissions, result, + "Calculated emission values from FarmAnimalHousingEmissionSource should be returned for farm animal housing source"); + } + @Test void testVisitFarmland() throws AeriusException { final Map calculatedEmissions = Map.of(Substance.NH3, 324.0); diff --git a/source/imaer-shared/src/test/java/nl/overheid/aerius/shared/emissions/FarmAnimalHousingEmissionsCalculatorTest.java b/source/imaer-shared/src/test/java/nl/overheid/aerius/shared/emissions/FarmAnimalHousingEmissionsCalculatorTest.java new file mode 100644 index 00000000..bf72f7a0 --- /dev/null +++ b/source/imaer-shared/src/test/java/nl/overheid/aerius/shared/emissions/FarmAnimalHousingEmissionsCalculatorTest.java @@ -0,0 +1,403 @@ +/* + * Copyright the State of the Netherlands + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package nl.overheid.aerius.shared.emissions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; + +import java.math.BigDecimal; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import nl.overheid.aerius.shared.domain.Substance; +import nl.overheid.aerius.shared.domain.v2.source.FarmAnimalHousingEmissionSource; +import nl.overheid.aerius.shared.domain.v2.source.farm.AdditionalHousingSystem; +import nl.overheid.aerius.shared.domain.v2.source.farm.CustomAdditionalHousingSystem; +import nl.overheid.aerius.shared.domain.v2.source.farm.CustomFarmAnimalHousing; +import nl.overheid.aerius.shared.domain.v2.source.farm.FarmAnimalHousing; +import nl.overheid.aerius.shared.domain.v2.source.farm.StandardAdditionalHousingSystem; +import nl.overheid.aerius.shared.domain.v2.source.farm.StandardFarmAnimalHousing; +import nl.overheid.aerius.shared.exception.AeriusException; +import nl.overheid.aerius.shared.exception.ImaerExceptionReason; + +@ExtendWith(MockitoExtension.class) +class FarmAnimalHousingEmissionsCalculatorTest { + + @Mock FarmAnimalHousingEmissionFactorSupplier emissionFactorSupplier; + + FarmAnimalHousingEmissionsCalculator emissionsCalculator; + + @BeforeEach + void beforeEach() throws AeriusException { + emissionsCalculator = new FarmAnimalHousingEmissionsCalculator(emissionFactorSupplier); + } + + @Test + void testCalculateEmissions() throws AeriusException { + final FarmAnimalHousingEmissionSource emissionSource = new FarmAnimalHousingEmissionSource(); + final CustomFarmAnimalHousing housing1 = new CustomFarmAnimalHousing(); + housing1.getEmissionFactors().put(Substance.NOX, 44.3); + housing1.setNumberOfAnimals(2); + emissionSource.getSubSources().add(housing1); + final CustomFarmAnimalHousing housing2 = new CustomFarmAnimalHousing(); + housing2.getEmissionFactors().put(Substance.NOX, 5.4); + housing2.getEmissionFactors().put(Substance.NH3, 7.9); + housing2.setNumberOfAnimals(3); + emissionSource.getSubSources().add(housing2); + final StandardFarmAnimalHousing housing3 = createTestObjectBase(); + addAdditionalSystem1(housing3); + addAdditionalSystem2(housing3); + emissionSource.getSubSources().add(housing3); + + // Ensure emissions per subsource are unknown at start + for (final FarmAnimalHousing housing : emissionSource.getSubSources()) { + assertTrue(housing.getEmissions().isEmpty()); + } + + final Map results = emissionsCalculator.calculateEmissions(emissionSource); + + // Check total emissions + assertEquals(104.8, results.get(Substance.NOX)); + assertEquals(23.7 + 1710.0, results.get(Substance.NH3)); + // Check emissions per subsource (should be set during calculation) + assertEquals(88.6, housing1.getEmissions().get(Substance.NOX)); + assertEquals(16.2, housing2.getEmissions().get(Substance.NOX)); + assertNull(housing3.getEmissions().get(Substance.NOX)); + assertNull(housing1.getEmissions().get(Substance.NH3)); + assertEquals(23.7, housing2.getEmissions().get(Substance.NH3)); + assertEquals(1710.0, housing3.getEmissions().get(Substance.NH3)); + } + + @Test + void testCalculateEmissionsWithNumberOfDays() throws AeriusException { + final FarmAnimalHousingEmissionSource emissionSource = + getCustomFarmAnimalHousingTestCase(0.034567, FarmEmissionFactorType.PER_ANIMAL_PER_DAY, 100); + final Map results = emissionsCalculator.calculateEmissions(emissionSource); + + assertEquals(193.5752, results.get(Substance.NOX)); + } + + @Test + void testCalculateEmissionsWithMinNumberOfDays() throws AeriusException { + final FarmAnimalHousingEmissionSource emissionSource = getCustomFarmAnimalHousingTestCase(12.34567, FarmEmissionFactorType.PER_ANIMAL_PER_DAY, 0); + final Map results = emissionsCalculator.calculateEmissions(emissionSource); + + assertEquals(0, results.get(Substance.NOX)); + } + + @Test + void testCalculateEmissionsWithNumberOfDaysAndEmissionFactorPerYear() throws AeriusException { + final FarmAnimalHousingEmissionSource emissionSource = + getCustomFarmAnimalHousingTestCase(12.34567, FarmEmissionFactorType.PER_ANIMAL_PER_YEAR, 100); + final Map results = emissionsCalculator.calculateEmissions(emissionSource); + + assertEquals(691.35752, results.get(Substance.NOX)); + } + + @Test + void testCalculateEmissionsUnknownImplementation() throws AeriusException { + final FarmAnimalHousingEmissionSource emissionSource = new FarmAnimalHousingEmissionSource(); + emissionSource.getSubSources().add(new FarmAnimalHousing() { + private static final long serialVersionUID = 1L; + }); + + final AeriusException thrown = assertThrows(AeriusException.class, () -> emissionsCalculator.calculateEmissions(emissionSource)); + + assertEquals(ImaerExceptionReason.INTERNAL_ERROR, thrown.getReason()); + } + + private FarmAnimalHousingEmissionSource getCustomFarmAnimalHousingTestCase(final double noxEmissionFactor, + final FarmEmissionFactorType farmEmissionFactorType, + final int numberOfDays) { + final CustomFarmAnimalHousing housing = new CustomFarmAnimalHousing(); + housing.getEmissionFactors().put(Substance.NOX, noxEmissionFactor); + housing.setNumberOfAnimals(56); + housing.setFarmEmissionFactorType(farmEmissionFactorType); + housing.setNumberOfDays(numberOfDays); + + final FarmAnimalHousingEmissionSource emissionSource = new FarmAnimalHousingEmissionSource(); + emissionSource.getSubSources().add(housing); + return emissionSource; + } + + @Test + void testCalculateEmissionsCustomFarmAnimalHousing() { + final CustomFarmAnimalHousing housing = new CustomFarmAnimalHousing(); + housing.getEmissionFactors().put(Substance.NOX, 12.34567); + housing.setNumberOfAnimals(56); + + final Map results = emissionsCalculator.calculateEmissions(housing); + + assertEquals(BigDecimal.valueOf(691.35752), results.get(Substance.NOX)); + } + + @Test + void testCalculateEmissionsStandardFarmAnimalHousing() throws AeriusException { + final StandardFarmAnimalHousing housing = createTestObjectBase(); + + final Map results = emissionsCalculator.calculateEmissions(housing); + + // 1000 * 3 + assertEquals(BigDecimal.valueOf(3000.0), results.get(Substance.NH3)); + } + + @Test + void testCalculateEmissionsStandardFarmAnimalHousingPerDay() throws AeriusException { + final StandardFarmAnimalHousing housing = createStandardHousingPerDay(); + + final Map results = emissionsCalculator.calculateEmissions(housing); + + // 1000 * 0.02 * 175 + assertEquals(new BigDecimal("3500.00"), results.get(Substance.NH3)); + } + + @Test + void testCalculateEmissionsStandardFarmAnimalHousingOneAdditional() throws AeriusException { + final StandardFarmAnimalHousing housing = createTestObjectBase(); + addAdditionalSystem1(housing); + + final Map results = emissionsCalculator.calculateEmissions(housing); + + // 1000 * 3 * (1 - 0.4) + assertEquals(new BigDecimal("1800.00"), results.get(Substance.NH3)); + } + + @Test + void testCalculateEmissionsStandardFarmAnimalHousingOneAdditionalScrubber() throws AeriusException { + final StandardFarmAnimalHousing housing = createTestObjectBase(); + addAdditionalSystem1(housing, true); + + final Map results = emissionsCalculator.calculateEmissions(housing); + + // Animal housing is not constrained, so same value. + assertEquals(new BigDecimal("1800.00"), results.get(Substance.NH3)); + } + + @Test + void testCalculateEmissionsStandardFarmAnimalHousingTwoAdditionals() throws AeriusException { + final StandardFarmAnimalHousing housing = createTestObjectBase(); + addAdditionalSystem1(housing); + addAdditionalSystem2(housing); + + final Map results = emissionsCalculator.calculateEmissions(housing); + + // 1000 * 3 * (1 - 0.4) * (1 - 0.05) + assertEquals(new BigDecimal("1710.0000"), results.get(Substance.NH3)); + assertEquals(1710.0, results.get(Substance.NH3).doubleValue()); + } + + @Test + void testCalculateEmissionsStandardFarmAnimalHousingAdditionalCustom() throws AeriusException { + final StandardFarmAnimalHousing housing = createTestObjectBase(); + addAdditionalSystemCustom(housing, false); + + final Map results = emissionsCalculator.calculateEmissions(housing); + + // 1000 * 3 * (1 - 0.77) + assertEquals(new BigDecimal("690.000"), results.get(Substance.NH3)); + assertEquals(690.0, results.get(Substance.NH3).doubleValue()); + } + + @Test + void testCalculateEmissionsStandardFarmAnimalHousingMixed() throws AeriusException { + final StandardFarmAnimalHousing housing = createTestObjectBase(); + addAdditionalSystem1(housing); + addAdditionalSystem2(housing); + addAdditionalSystemCustom(housing, false); + + final Map results = emissionsCalculator.calculateEmissions(housing); + + // 1000 * 3 * (1 - 0.4) * (1 - 0.05) * (1 - 0.77) + assertEquals(new BigDecimal("393.300000"), results.get(Substance.NH3)); + assertEquals(393.3, results.get(Substance.NH3).doubleValue()); + } + + @Test + void testCalculateEmissionsAdditionalUnknownImplementation() throws AeriusException { + final StandardFarmAnimalHousing housing = createTestObjectBase(); + housing.getAdditionalSystems().add(new AdditionalHousingSystem() { + private static final long serialVersionUID = 1L; + }); + + final AeriusException thrown = assertThrows(AeriusException.class, () -> emissionsCalculator.calculateEmissions(housing)); + + assertEquals(ImaerExceptionReason.INTERNAL_ERROR, thrown.getReason()); + } + + @Test + void testCalculateEmissionsStandardFarmAnimalHousingConstrained() throws AeriusException { + final StandardFarmAnimalHousing housing = createTestObjectBaseConstrained(); + + final Map results = emissionsCalculator.calculateEmissions(housing); + + assertEquals(BigDecimal.valueOf(1000.0), results.get(Substance.NH3)); + assertEquals(BigDecimal.valueOf(10000.0), results.get(Substance.PM10)); + } + + @Test + void testCalculateEmissionsStandardFarmAnimalHousingConstrainedOneAdditionalNonScrubber() throws AeriusException { + final StandardFarmAnimalHousing housing = createTestObjectBaseConstrained(); + addAdditionalSystem1(housing); + + final Map results = emissionsCalculator.calculateEmissions(housing); + + // Unconstrained = 1000 * 1.0 * (1 - 0.4) + assertEquals(new BigDecimal("600.00"), results.get(Substance.NH3)); + // PM10: 1000 * 10.0 * (1 - 0.6) + assertEquals(new BigDecimal("4000.00"), results.get(Substance.PM10)); + } + + @Test + void testCalculateEmissionsStandardFarmAnimalHousingConstrainedOneAdditionalScrubber() throws AeriusException { + final StandardFarmAnimalHousing housing = createTestObjectBaseConstrained(); + addAdditionalSystem1(housing, true); + + final Map results = emissionsCalculator.calculateEmissions(housing); + + // Constrained = 1000 * 8.0 * 0.3 * (1 - 0.4) + assertEquals(new BigDecimal("1440.00"), results.get(Substance.NH3)); + // Constrained only works for NH3 + assertEquals(new BigDecimal("4000.00"), results.get(Substance.PM10)); + } + + @Test + void testCalculateEmissionsStandardFarmAnimalHousingConstrainedCustomNonScrubber() throws AeriusException { + final StandardFarmAnimalHousing housing = createTestObjectBaseConstrained(); + addAdditionalSystemCustom(housing, false); + + final Map results = emissionsCalculator.calculateEmissions(housing); + + // Unconstrained = 1000 * 1.0 * (1 - 0.77) + assertEquals(new BigDecimal("230.000"), results.get(Substance.NH3)); + } + + @Test + void testCalculateEmissionsStandardFarmAnimalHousingConstrainedCustomScrubber() throws AeriusException { + final StandardFarmAnimalHousing housing = createTestObjectBaseConstrained(); + addAdditionalSystemCustom(housing, true); + + final Map results = emissionsCalculator.calculateEmissions(housing); + + // Constrained = 1000 * 8.0 * 0.3 * (1 - 0.77) + assertEquals(new BigDecimal("552.000"), results.get(Substance.NH3)); + } + + @Test + void testCalculateEmissionsStandardFarmAnimalHousingConstrainedMixed() throws AeriusException { + final StandardFarmAnimalHousing housing = createTestObjectBaseConstrained(); + addAdditionalSystem1(housing, true); + addAdditionalSystem2(housing); + addAdditionalSystem1(housing, true); + addAdditionalSystemCustom(housing, false); + + final Map results = emissionsCalculator.calculateEmissions(housing); + + // Constrained = 1000 * 8.0 * 0.3 * (1 - 0.4) * (1 - 0.05) * (1 - 0.4) * (1 - 0.77) + assertEquals(new BigDecimal("188.7840000"), results.get(Substance.NH3)); + assertEquals(188.784, results.get(Substance.NH3).doubleValue()); + } + + private StandardFarmAnimalHousing createTestObjectBase() { + final StandardFarmAnimalHousing housing = new StandardFarmAnimalHousing(); + housing.setNumberOfAnimals(1000); + final String housingCode = "ABC"; + housing.setAnimalHousingCode(housingCode); + housing.setAnimalTypeCode("OWL"); + + final Map emissionFactors = Map.of(Substance.NH3, 3.0); + when(emissionFactorSupplier.getAnimalHousingEmissionFactors(housingCode)).thenReturn(emissionFactors); + + return housing; + } + + private StandardFarmAnimalHousing createTestObjectBaseConstrained() { + final StandardFarmAnimalHousing housing = new StandardFarmAnimalHousing(); + housing.setNumberOfAnimals(1000); + final String housingCode = "ABC"; + final String basicCode = "DEF"; + final String animalCode = "OWL"; + housing.setAnimalHousingCode(housingCode); + housing.setAnimalTypeCode(animalCode); + + final Map emissionFactors = Map.of(Substance.NH3, 1.0, Substance.PM10, 10.0); + final Map emissionFactorsConstrained = Map.of(Substance.NH3, 8.0, Substance.PM10, 80.0); + lenient().when(emissionFactorSupplier.getAnimalHousingEmissionFactors(housingCode)).thenReturn(emissionFactors); + lenient().when(emissionFactorSupplier.getAnimalBasicHousingCode(animalCode)).thenReturn(basicCode); + lenient().when(emissionFactorSupplier.getAnimalHousingEmissionFactors(basicCode)).thenReturn(emissionFactorsConstrained); + + return housing; + } + + private void addAdditionalSystem1(final StandardFarmAnimalHousing housing) { + addAdditionalSystem1(housing, false); + } + + private void addAdditionalSystem1(final StandardFarmAnimalHousing housing, final boolean airScrubber) { + final StandardAdditionalHousingSystem additionalSystem = new StandardAdditionalHousingSystem(); + final String systemCode = "ADD1"; + additionalSystem.setAdditionalSystemCode(systemCode); + + final Map emissionFactors = Map.of(Substance.NH3, 0.4, Substance.PM10, 0.6); + when(emissionFactorSupplier.getAdditionalHousingSystemReductionFractions(systemCode, housing.getAnimalTypeCode())).thenReturn(emissionFactors); + lenient().when(emissionFactorSupplier.isAdditionalHousingSystemAirScrubber(systemCode)).thenReturn(airScrubber); + + housing.getAdditionalSystems().add(additionalSystem); + } + + private void addAdditionalSystem2(final StandardFarmAnimalHousing housing) { + final StandardAdditionalHousingSystem additionalSystem = new StandardAdditionalHousingSystem(); + final String systemCode = "ADD2"; + additionalSystem.setAdditionalSystemCode(systemCode); + + final Map emissionFactors = Map.of(Substance.NH3, 0.05); + when(emissionFactorSupplier.getAdditionalHousingSystemReductionFractions(systemCode, housing.getAnimalTypeCode())).thenReturn(emissionFactors); + + housing.getAdditionalSystems().add(additionalSystem); + } + + private void addAdditionalSystemCustom(final StandardFarmAnimalHousing housing, final boolean airScrubber) { + final CustomAdditionalHousingSystem additionalSystem = new CustomAdditionalHousingSystem(); + additionalSystem.setAirScrubber(airScrubber); + additionalSystem.getEmissionReductionFactors().put(Substance.NH3, 0.77); + + housing.getAdditionalSystems().add(additionalSystem); + } + + private StandardFarmAnimalHousing createStandardHousingPerDay() { + final StandardFarmAnimalHousing housing = new StandardFarmAnimalHousing(); + housing.setNumberOfAnimals(1000); + housing.setNumberOfDays(175); + final String housingCode = "ABC"; + housing.setAnimalHousingCode(housingCode); + housing.setAnimalTypeCode("OWL"); + + final Map emissionFactors = Map.of(Substance.NH3, 0.02); + when(emissionFactorSupplier.getAnimalHousingEmissionFactors(housingCode)).thenReturn(emissionFactors); + when(emissionFactorSupplier.getAnimalHousingEmissionFactorType(housingCode)).thenReturn(FarmEmissionFactorType.PER_ANIMAL_PER_DAY); + + return housing; + } +} diff --git a/source/imaer-shared/src/test/resources/json/FarmAnimalHousingEmissionSource.json b/source/imaer-shared/src/test/resources/json/FarmAnimalHousingEmissionSource.json new file mode 100644 index 00000000..99b6799a --- /dev/null +++ b/source/imaer-shared/src/test/resources/json/FarmAnimalHousingEmissionSource.json @@ -0,0 +1,63 @@ +{ + "type" : "Feature", + "id" : "ES.1", + "geometry" : { + "type" : "Point", + "coordinates" : [ 136558.0, 455251.0 ] + }, + "properties" : { + "emissionSourceType" : "FARM_ANIMAL_HOUSING", + "sectorId" : 4110, + "label" : "SomeSource", + "characteristics" : { + "type" : "OPS", + "heatContent" : 564.584, + "emissionHeight" : 5.0 + }, + "emissions" : { + "NH3" : 1923.1321 + }, + "subSources" : [ { + "animalHousingType" : "STANDARD", + "numberOfAnimals" : 300, + "animalTypeCode" : "A", + "animalHousingCode" : "FIEH_1", + "additionalSystems" : [ { + "additionalSystemType" : "STANDARD", + "additionalSystemCode" : "ADD_4_2" + }, { + "additionalSystemType" : "CUSTOM", + "airScrubber" : false, + "emissionReductionFactors" : { + "NH3" : 0.3 + } + } ] + }, { + "animalHousingType" : "STANDARD", + "numberOfAnimals" : 100, + "numberOfDays" : 365, + "animalTypeCode" : "C", + "animalHousingCode" : "FAH_5" + }, { + "animalHousingType" : "CUSTOM", + "numberOfAnimals" : 324, + "numberOfDays" : 365, + "animalTypeCode" : "A", + "description" : "Koetje", + "farmEmissionFactorType" : "PER_ANIMAL_PER_DAY", + "emissionFactors" : { + "NOX" : 3000.0, + "NH3" : 1000.0 + } + }, { + "animalHousingType" : "CUSTOM", + "numberOfAnimals" : 908, + "animalTypeCode" : "B", + "description" : "Schaap", + "emissionFactors" : { + "NH3" : 2000.0 + } + } ], + "established" : "1981-02-23" + } +} diff --git a/source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmAnimalHousingValidationHelper.java b/source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmAnimalHousingValidationHelper.java new file mode 100644 index 00000000..f4df279e --- /dev/null +++ b/source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmAnimalHousingValidationHelper.java @@ -0,0 +1,35 @@ +/* + * Copyright the State of the Netherlands + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package nl.overheid.aerius.validation; + +import nl.overheid.aerius.shared.emissions.FarmEmissionFactorType; + +public interface FarmAnimalHousingValidationHelper { + + boolean isValidFarmAnimalCode(String animalCode); + + boolean isValidFarmAnimalHousingCode(String animalHousingCode); + + boolean isValidFarmAnimalHousingCombination(String animalCode, String animalHousingCode); + + boolean isValidFarmAdditionalSystemCode(String systemCode); + + boolean isValidFarmAdditionalSystemCombination(String animalCode, String systemCode); + + FarmEmissionFactorType getAnimalHousingEmissionFactorType(final String animalHousingCode); + +} diff --git a/source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmAnimalHousingValidator.java b/source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmAnimalHousingValidator.java new file mode 100644 index 00000000..ec634bec --- /dev/null +++ b/source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmAnimalHousingValidator.java @@ -0,0 +1,140 @@ +/* + * Copyright the State of the Netherlands + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package nl.overheid.aerius.validation; + +import java.util.EnumSet; +import java.util.List; +import java.util.Set; + +import nl.overheid.aerius.shared.domain.v2.source.FarmAnimalHousingEmissionSource; +import nl.overheid.aerius.shared.domain.v2.source.farm.AdditionalHousingSystem; +import nl.overheid.aerius.shared.domain.v2.source.farm.CustomAdditionalHousingSystem; +import nl.overheid.aerius.shared.domain.v2.source.farm.CustomFarmAnimalHousing; +import nl.overheid.aerius.shared.domain.v2.source.farm.FarmAnimalHousing; +import nl.overheid.aerius.shared.domain.v2.source.farm.StandardAdditionalHousingSystem; +import nl.overheid.aerius.shared.domain.v2.source.farm.StandardFarmAnimalHousing; +import nl.overheid.aerius.shared.emissions.FarmEmissionFactorType; +import nl.overheid.aerius.shared.exception.AeriusException; +import nl.overheid.aerius.shared.exception.ImaerExceptionReason; + +class FarmAnimalHousingValidator extends SourceValidator { + + private static final Set EXPECTED_EMISSION_FACTOR_TYPES = + EnumSet.of(FarmEmissionFactorType.PER_ANIMAL_PER_DAY, FarmEmissionFactorType.PER_ANIMAL_PER_YEAR); + + private final FarmAnimalHousingValidationHelper validationHelper; + + FarmAnimalHousingValidator(final List errors, final List warnings, + final FarmAnimalHousingValidationHelper validationHelper) { + super(errors, warnings); + this.validationHelper = validationHelper; + } + + @Override + boolean validate(final FarmAnimalHousingEmissionSource source) { + boolean valid = true; + for (final FarmAnimalHousing subSource : source.getSubSources()) { + if (subSource instanceof final StandardFarmAnimalHousing standardSubSource) { + valid = validateStandardAnimalHousing(standardSubSource, source.getGmlId()) && valid; + } else if (subSource instanceof final CustomFarmAnimalHousing customSubSource) { + valid = validateCustomAnimalHousing(customSubSource, source.getGmlId()) && valid; + } else { + getErrors().add(new AeriusException(ImaerExceptionReason.INTERNAL_ERROR, "Unexpected animal housing implementation")); + valid = false; + } + } + return valid; + } + + private boolean validateStandardAnimalHousing(final StandardFarmAnimalHousing subSource, final String sourceId) { + final String housingCode = subSource.getAnimalHousingCode(); + final String animalCode = subSource.getAnimalTypeCode(); + boolean valid = true; + if (!validationHelper.isValidFarmAnimalCode(animalCode)) { + getErrors().add(new AeriusException(ImaerExceptionReason.GML_UNKNOWN_ANIMAL_HOUSING_CODE, sourceId, animalCode)); + valid = false; + } else if (!validationHelper.isValidFarmAnimalHousingCode(housingCode)) { + getErrors().add(new AeriusException(ImaerExceptionReason.GML_UNKNOWN_ANIMAL_HOUSING_CODE, sourceId, housingCode)); + valid = false; + } else if (!validationHelper.isValidFarmAnimalHousingCombination(animalCode, housingCode)) { + getErrors().add(new AeriusException(ImaerExceptionReason.GML_UNSUPPORTED_ANIMAL_HOUSING_COMBINATION, sourceId, animalCode, housingCode)); + valid = false; + } else { + valid = validateNumberOfDays(subSource, sourceId) && valid; + valid = validateAdditionalSystems(subSource.getAdditionalSystems(), animalCode, sourceId) && valid; + } + return valid; + } + + private boolean validateNumberOfDays(final StandardFarmAnimalHousing subSource, final String sourceId) { + final String code = subSource.getAnimalHousingCode(); + final FarmEmissionFactorType emissionFactorType = validationHelper.getAnimalHousingEmissionFactorType(code); + return FarmEmissionFactorTypeValidatorUtil.validate(EXPECTED_EMISSION_FACTOR_TYPES, emissionFactorType, subSource, sourceId, getErrors()); + } + + private boolean validateAdditionalSystems(final List additionalSystems, final String animalCode, final String sourceId) { + boolean valid = true; + for (final AdditionalHousingSystem additionalSystem : additionalSystems) { + if (additionalSystem instanceof final StandardAdditionalHousingSystem standardSystem) { + valid = validateStandardAdditionalSystem(standardSystem, animalCode, sourceId) && valid; + } else if (additionalSystem instanceof final CustomAdditionalHousingSystem customSystem) { + valid = validateCustomAdditionalSystem(customSystem, sourceId) && valid; + } else { + getErrors().add(new AeriusException(ImaerExceptionReason.INTERNAL_ERROR, "Unexpected additional animal housing system implementation")); + valid = false; + } + } + return valid; + } + + private boolean validateStandardAdditionalSystem(final StandardAdditionalHousingSystem standardSystem, final String animalCode, + final String sourceId) { + final String code = standardSystem.getAdditionalSystemCode(); + boolean valid = true; + if (!validationHelper.isValidFarmAdditionalSystemCode(code)) { + getErrors().add(new AeriusException(ImaerExceptionReason.GML_UNKNOWN_ANIMAL_HOUSING_CODE, sourceId, code)); + valid = false; + } else if (!validationHelper.isValidFarmAdditionalSystemCombination(animalCode, code)) { + getErrors().add(new AeriusException(ImaerExceptionReason.GML_UNSUPPORTED_ANIMAL_HOUSING_COMBINATION, sourceId, animalCode, code)); + valid = false; + } + return valid; + } + + private boolean validateCustomAdditionalSystem(final CustomAdditionalHousingSystem customSystem, final String sourceId) { + boolean valid = true; + if (customSystem.getEmissionReductionFactors().isEmpty() + || customSystem.getEmissionReductionFactors().values().stream().anyMatch(x -> x < 0)) { + getErrors().add(new AeriusException(ImaerExceptionReason.GML_INCORRECT_CUSTOM_FACTORS, sourceId)); + valid = false; + } + return valid; + } + + private boolean validateCustomAnimalHousing(final CustomFarmAnimalHousing subSource, final String sourceId) { + final FarmEmissionFactorType emissionFactorType = subSource.getFarmEmissionFactorType(); + boolean valid = + FarmEmissionFactorTypeValidatorUtil.validate(EXPECTED_EMISSION_FACTOR_TYPES, emissionFactorType, subSource, sourceId, getErrors()); + if (subSource.getEmissionFactors().isEmpty() + || subSource.getEmissionFactors().values().stream().anyMatch(x -> x < 0)) { + getErrors().add(new AeriusException(ImaerExceptionReason.GML_INCORRECT_CUSTOM_FACTORS, sourceId)); + valid = false; + } + return valid; + } + +} diff --git a/source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmLodgingValidationHelper.java b/source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmLodgingValidationHelper.java index 9cbfef4a..f78938fe 100644 --- a/source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmLodgingValidationHelper.java +++ b/source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmLodgingValidationHelper.java @@ -18,6 +18,10 @@ import nl.overheid.aerius.shared.emissions.FarmEmissionFactorType; +/** + * @Deprecated Replaced by Animal Housing approach + */ +@Deprecated(forRemoval = true) public interface FarmLodgingValidationHelper { boolean isValidFarmLodgingCode(String lodgingCode); diff --git a/source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmLodgingValidator.java b/source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmLodgingValidator.java index c4477e08..c25951d7 100644 --- a/source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmLodgingValidator.java +++ b/source/imaer-util/src/main/java/nl/overheid/aerius/validation/FarmLodgingValidator.java @@ -31,6 +31,10 @@ import nl.overheid.aerius.shared.exception.AeriusException; import nl.overheid.aerius.shared.exception.ImaerExceptionReason; +/** + * @Deprecated Replaced by Animal Housing approach + */ +@Deprecated(forRemoval = true) class FarmLodgingValidator extends SourceValidator { private static final Set EXPECTED_EMISSION_FACTOR_TYPES = diff --git a/source/imaer-util/src/main/java/nl/overheid/aerius/validation/ValidationHelper.java b/source/imaer-util/src/main/java/nl/overheid/aerius/validation/ValidationHelper.java index acd6204a..b3f59865 100644 --- a/source/imaer-util/src/main/java/nl/overheid/aerius/validation/ValidationHelper.java +++ b/source/imaer-util/src/main/java/nl/overheid/aerius/validation/ValidationHelper.java @@ -23,6 +23,10 @@ public interface ValidationHelper { FarmLodgingValidationHelper farmLodgingValidation(); + default FarmAnimalHousingValidationHelper farmAnimalHousingValidation() { + return null; + } + FarmlandValidationHelper farmlandValidation(); ManureStorageValidationHelper manureStorageValidation(); diff --git a/source/imaer-util/src/main/java/nl/overheid/aerius/validation/ValidationVisitor.java b/source/imaer-util/src/main/java/nl/overheid/aerius/validation/ValidationVisitor.java index b0c75591..0164f1e9 100644 --- a/source/imaer-util/src/main/java/nl/overheid/aerius/validation/ValidationVisitor.java +++ b/source/imaer-util/src/main/java/nl/overheid/aerius/validation/ValidationVisitor.java @@ -24,6 +24,7 @@ import nl.overheid.aerius.shared.domain.v2.source.ColdStartEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.EmissionSourceFeature; import nl.overheid.aerius.shared.domain.v2.source.EmissionSourceVisitor; +import nl.overheid.aerius.shared.domain.v2.source.FarmAnimalHousingEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.FarmLodgingEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.FarmlandEmissionSource; import nl.overheid.aerius.shared.domain.v2.source.GenericEmissionSource; @@ -76,6 +77,11 @@ public Boolean visit(final FarmLodgingEmissionSource emissionSource, final IsFea return new FarmLodgingValidator(errors, warnings, validationHelper.farmLodgingValidation()).validate(emissionSource, feature); } + @Override + public Boolean visit(final FarmAnimalHousingEmissionSource emissionSource, final IsFeature feature) throws AeriusException { + return new FarmAnimalHousingValidator(errors, warnings, validationHelper.farmAnimalHousingValidation()).validate(emissionSource, feature); + } + @Override public Boolean visit(final FarmlandEmissionSource emissionSource, final IsFeature feature) throws AeriusException { return new FarmlandValidator(errors, warnings, validationHelper.farmlandValidation()).validate(emissionSource, feature); diff --git a/source/imaer-util/src/test/java/nl/overheid/aerius/validation/FarmAnimalHousingValidatorTest.java b/source/imaer-util/src/test/java/nl/overheid/aerius/validation/FarmAnimalHousingValidatorTest.java new file mode 100644 index 00000000..2125a0ec --- /dev/null +++ b/source/imaer-util/src/test/java/nl/overheid/aerius/validation/FarmAnimalHousingValidatorTest.java @@ -0,0 +1,494 @@ +/* + * Copyright the State of the Netherlands + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ +package nl.overheid.aerius.validation; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import nl.overheid.aerius.shared.domain.Substance; +import nl.overheid.aerius.shared.domain.v2.source.FarmAnimalHousingEmissionSource; +import nl.overheid.aerius.shared.domain.v2.source.farm.AdditionalHousingSystem; +import nl.overheid.aerius.shared.domain.v2.source.farm.CustomAdditionalHousingSystem; +import nl.overheid.aerius.shared.domain.v2.source.farm.CustomFarmAnimalHousing; +import nl.overheid.aerius.shared.domain.v2.source.farm.FarmAnimalHousing; +import nl.overheid.aerius.shared.domain.v2.source.farm.StandardAdditionalHousingSystem; +import nl.overheid.aerius.shared.domain.v2.source.farm.StandardFarmAnimalHousing; +import nl.overheid.aerius.shared.emissions.FarmEmissionFactorType; +import nl.overheid.aerius.shared.exception.AeriusException; +import nl.overheid.aerius.shared.exception.ImaerExceptionReason; + +@ExtendWith(MockitoExtension.class) +class FarmAnimalHousingValidatorTest { + + private static final String SOURCE_ID = "OurSourceId"; + + @Mock FarmAnimalHousingValidationHelper validationHelper; + + @Test + void testValidCustomHousing() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final CustomFarmAnimalHousing subSource = new CustomFarmAnimalHousing(); + subSource.setFarmEmissionFactorType(FarmEmissionFactorType.PER_ANIMAL_PER_DAY); + subSource.setNumberOfDays(5); + subSource.getEmissionFactors().put(Substance.NH3, 1.2); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertValidCase(valid, errors, warnings); + } + + @Test + void testInvalidCustomHousingMissingEmissionFactorType() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final CustomFarmAnimalHousing subSource = new CustomFarmAnimalHousing(); + subSource.setNumberOfDays(5); + subSource.getEmissionFactors().put(Substance.NH3, 1.2); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertInvalidCase(valid, errors, warnings, ImaerExceptionReason.GML_UNKNOWN_FARM_EMISSION_FACTOR_TYPE, new Object[] { + SOURCE_ID, "null" + }); + } + + @Test + void testInvalidCustomHousingMissingDays() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final CustomFarmAnimalHousing subSource = new CustomFarmAnimalHousing(); + subSource.setFarmEmissionFactorType(FarmEmissionFactorType.PER_ANIMAL_PER_DAY); + subSource.getEmissionFactors().put(Substance.NH3, 1.2); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertInvalidCase(valid, errors, warnings, ImaerExceptionReason.MISSING_NUMBER_OF_DAYS, new Object[] { + SOURCE_ID + }); + } + + @Test + void testInvalidCustomHousingMissingEmissionFactors() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final CustomFarmAnimalHousing subSource = new CustomFarmAnimalHousing(); + subSource.setFarmEmissionFactorType(FarmEmissionFactorType.PER_ANIMAL_PER_DAY); + subSource.setNumberOfDays(5); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertInvalidCase(valid, errors, warnings, ImaerExceptionReason.GML_INCORRECT_CUSTOM_FACTORS, new Object[] { + SOURCE_ID + }); + } + + @Test + void testInvalidCustomHousingNegativeEmissionFactors() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final CustomFarmAnimalHousing subSource = new CustomFarmAnimalHousing(); + subSource.setFarmEmissionFactorType(FarmEmissionFactorType.PER_ANIMAL_PER_DAY); + subSource.setNumberOfDays(5); + subSource.getEmissionFactors().put(Substance.NH3, -1.2); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertInvalidCase(valid, errors, warnings, ImaerExceptionReason.GML_INCORRECT_CUSTOM_FACTORS, new Object[] { + SOURCE_ID + }); + } + + @Test + void testValidStandardHousing() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final StandardFarmAnimalHousing subSource = mockValidStandardHousing("SomeAnimalCode"); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertValidCase(valid, errors, warnings); + } + + @Test + void testInvalidStandardHousingIncorrectAnimalCode() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final StandardFarmAnimalHousing subSource = new StandardFarmAnimalHousing(); + final String animalCode = "SomeAnimalCode"; + subSource.setAnimalTypeCode(animalCode); + final String housingCode = "SomeStandardHouse"; + mockHousingCategory(housingCode, animalCode); + subSource.setAnimalHousingCode(housingCode); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertInvalidCase(valid, errors, warnings, ImaerExceptionReason.GML_UNKNOWN_ANIMAL_HOUSING_CODE, new Object[] { + SOURCE_ID, animalCode + }); + } + + @Test + void testInvalidStandardHousingIncorrectHousingCode() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final StandardFarmAnimalHousing subSource = new StandardFarmAnimalHousing(); + final String animalCode = "SomeAnimalCode"; + mockAnimalCategory(animalCode); + subSource.setAnimalTypeCode(animalCode); + final String housingCode = "SomeStandardHouse"; + subSource.setAnimalHousingCode(housingCode); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertInvalidCase(valid, errors, warnings, ImaerExceptionReason.GML_UNKNOWN_ANIMAL_HOUSING_CODE, new Object[] { + SOURCE_ID, housingCode + }); + } + + @Test + void testInvalidStandardHousingIncorrectCombination() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final StandardFarmAnimalHousing subSource = new StandardFarmAnimalHousing(); + final String animalCode = "SomeAnimalCode"; + mockAnimalCategory(animalCode); + subSource.setAnimalTypeCode(animalCode); + final String housingCode = "SomeStandardHouse"; + mockHousingCategory(housingCode, "otherAnimalCode"); + subSource.setAnimalHousingCode(housingCode); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertInvalidCase(valid, errors, warnings, ImaerExceptionReason.GML_UNSUPPORTED_ANIMAL_HOUSING_COMBINATION, new Object[] { + SOURCE_ID, animalCode, housingCode + }); + } + + @Test + void testValidStandardAdditionalSystem() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final String animalCode = "SomeAnimalCode"; + final StandardFarmAnimalHousing subSource = mockValidStandardHousing(animalCode); + + final String additionalSystemCode = "SomeAdditionalCode"; + final StandardAdditionalHousingSystem additionalSystem = new StandardAdditionalHousingSystem(); + mockAdditionalSystemCategory(additionalSystemCode, animalCode); + additionalSystem.setAdditionalSystemCode(additionalSystemCode); + subSource.getAdditionalSystems().add(additionalSystem); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertValidCase(valid, errors, warnings); + } + + @Test + void testInvalidStandardAdditionalSystemIncorrectCode() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final String animalCode = "SomeAnimalCode"; + final StandardFarmAnimalHousing subSource = mockValidStandardHousing(animalCode); + + final String additionalSystemCode = "SomeAdditionalCode"; + final StandardAdditionalHousingSystem additionalSystem = new StandardAdditionalHousingSystem(); + additionalSystem.setAdditionalSystemCode(additionalSystemCode); + subSource.getAdditionalSystems().add(additionalSystem); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertInvalidCase(valid, errors, warnings, ImaerExceptionReason.GML_UNKNOWN_ANIMAL_HOUSING_CODE, new Object[] { + SOURCE_ID, additionalSystemCode + }); + } + + @Test + void testInvalidStandardAdditionalSystemIncorrectCombination() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final String animalCode = "SomeAnimalCode"; + final StandardFarmAnimalHousing subSource = mockValidStandardHousing(animalCode); + + final String additionalSystemCode = "SomeAdditionalCode"; + mockAdditionalSystemCategory(additionalSystemCode, "OtherAnimalCode"); + final StandardAdditionalHousingSystem additionalSystem = new StandardAdditionalHousingSystem(); + additionalSystem.setAdditionalSystemCode(additionalSystemCode); + subSource.getAdditionalSystems().add(additionalSystem); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertInvalidCase(valid, errors, warnings, ImaerExceptionReason.GML_UNSUPPORTED_ANIMAL_HOUSING_COMBINATION, new Object[] { + SOURCE_ID, animalCode, additionalSystemCode + }); + } + + private StandardFarmAnimalHousing mockValidStandardHousing(final String animalCode) { + final StandardFarmAnimalHousing subSource = new StandardFarmAnimalHousing(); + mockAnimalCategory(animalCode); + subSource.setAnimalTypeCode(animalCode); + final String housingCode = "SomeStandardHouse"; + mockHousingCategory(housingCode, animalCode); + subSource.setAnimalHousingCode(housingCode); + return subSource; + } + + @Test + void testValidStandardAnimalHousingWithDays() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final StandardFarmAnimalHousing subSource = new StandardFarmAnimalHousing(); + final String animalCode = "SomeAnimalCode"; + mockAnimalCategory(animalCode); + subSource.setAnimalTypeCode(animalCode); + final String housingCode = "SomeStandardHouse"; + mockHousingCategory(housingCode, animalCode); + when(validationHelper.getAnimalHousingEmissionFactorType(housingCode)).thenReturn(FarmEmissionFactorType.PER_ANIMAL_PER_DAY); + subSource.setAnimalHousingCode(housingCode); + subSource.setNumberOfDays(40); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertValidCase(valid, errors, warnings); + } + + @Test + void testInvalidStandardAnimalHousingMissingDays() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final StandardFarmAnimalHousing subSource = new StandardFarmAnimalHousing(); + final String animalCode = "SomeAnimalCode"; + mockAnimalCategory(animalCode); + subSource.setAnimalTypeCode(animalCode); + final String housingCode = "SomeStandardHouse"; + mockHousingCategory(housingCode, animalCode); + when(validationHelper.getAnimalHousingEmissionFactorType(housingCode)).thenReturn(FarmEmissionFactorType.PER_ANIMAL_PER_DAY); + subSource.setAnimalHousingCode(housingCode); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertInvalidCase(valid, errors, warnings, ImaerExceptionReason.MISSING_NUMBER_OF_DAYS, new Object[] { + SOURCE_ID + }); + } + + @Test + void testValidCustomAdditionalSystem() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final String animalCode = "SomeAnimalCode"; + final StandardFarmAnimalHousing subSource = mockValidStandardHousing(animalCode); + + final CustomAdditionalHousingSystem additionalSystem = new CustomAdditionalHousingSystem(); + additionalSystem.getEmissionReductionFactors().put(Substance.NH3, 0.7); + subSource.getAdditionalSystems().add(additionalSystem); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertValidCase(valid, errors, warnings); + } + + @Test + void testInvalidCustomAdditionalSystemNoFactors() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final String animalCode = "SomeAnimalCode"; + final StandardFarmAnimalHousing subSource = mockValidStandardHousing(animalCode); + + final CustomAdditionalHousingSystem additionalSystem = new CustomAdditionalHousingSystem(); + subSource.getAdditionalSystems().add(additionalSystem); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertInvalidCase(valid, errors, warnings, ImaerExceptionReason.GML_INCORRECT_CUSTOM_FACTORS, new Object[] { + SOURCE_ID + }); + } + + @Test + void testInvalidCustomAdditionalSystemNegativeFactors() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final String animalCode = "SomeAnimalCode"; + final StandardFarmAnimalHousing subSource = mockValidStandardHousing(animalCode); + + final CustomAdditionalHousingSystem additionalSystem = new CustomAdditionalHousingSystem(); + additionalSystem.getEmissionReductionFactors().put(Substance.NH3, -0.7); + subSource.getAdditionalSystems().add(additionalSystem); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertInvalidCase(valid, errors, warnings, ImaerExceptionReason.GML_INCORRECT_CUSTOM_FACTORS, new Object[] { + SOURCE_ID + }); + } + + @Test + void testInvalidHousingUnknown() { + final FarmAnimalHousingEmissionSource source = constructSource(); + source.getSubSources().add(new FarmAnimalHousing() { + private static final long serialVersionUID = 1L; + }); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertInvalidCase(valid, errors, warnings, ImaerExceptionReason.INTERNAL_ERROR, new Object[] { + "Unexpected animal housing implementation" + }); + } + + @Test + void testInvalidAdditionalHousingUnknown() { + final FarmAnimalHousingEmissionSource source = constructSource(); + final StandardFarmAnimalHousing subSource = mockValidStandardHousing("SomeAnimalCode"); + subSource.getAdditionalSystems().add(new AdditionalHousingSystem() { + private static final long serialVersionUID = 1L; + }); + source.getSubSources().add(subSource); + + final List errors = new ArrayList<>(); + final List warnings = new ArrayList<>(); + final FarmAnimalHousingValidator validator = new FarmAnimalHousingValidator(errors, warnings, validationHelper); + + final boolean valid = validator.validate(source); + + assertInvalidCase(valid, errors, warnings, ImaerExceptionReason.INTERNAL_ERROR, new Object[] { + "Unexpected additional animal housing system implementation" + }); + } + + private FarmAnimalHousingEmissionSource constructSource() { + final FarmAnimalHousingEmissionSource source = new FarmAnimalHousingEmissionSource(); + source.setGmlId(SOURCE_ID); + source.setLabel("Source label"); + return source; + } + + private void mockAnimalCategory(final String code) { + when(validationHelper.isValidFarmAnimalCode(code)).thenReturn(true); + } + + private void mockHousingCategory(final String code, final String animalCode) { + lenient().when(validationHelper.isValidFarmAnimalHousingCode(code)).thenReturn(true); + lenient().when(validationHelper.isValidFarmAnimalHousingCombination(animalCode, code)).thenReturn(true); + lenient().when(validationHelper.getAnimalHousingEmissionFactorType(code)).thenReturn(FarmEmissionFactorType.PER_ANIMAL_PER_YEAR); + } + + private void mockAdditionalSystemCategory(final String code, final String animalCode) { + lenient().when(validationHelper.isValidFarmAdditionalSystemCode(code)).thenReturn(true); + lenient().when(validationHelper.isValidFarmAdditionalSystemCombination(animalCode, code)).thenReturn(true); + } + + private static void assertValidCase(final boolean valid, final List errors, final List warnings) { + assertTrue(valid, "Valid test case"); + assertEquals(List.of(), errors, "No errors"); + assertEquals(List.of(), warnings, "No warnings"); + } + + private static void assertInvalidCase(final boolean valid, final List errors, final List warnings, + final ImaerExceptionReason expectedReason, final Object[] expectedArguments) { + assertFalse(valid, "Invalid test case"); + assertEquals(1, errors.size(), "Nr of errors"); + assertEquals(expectedReason, errors.get(0).getReason(), "Error reason"); + assertArrayEquals(expectedArguments, errors.get(0).getArgs(), "Arguments"); + assertEquals(List.of(), warnings, "No warnings"); + } + +}