@hestia-earth/data-validation 0.37.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.coveragerc +14 -0
- package/.dockerignore +19 -0
- package/.eslintignore +17 -0
- package/.eslintrc.js +11 -0
- package/.flake8 +5 -0
- package/.gitlab/issue_templates/new validation.md +82 -0
- package/.gitlab-ci.yml +216 -0
- package/.readthedocs.yml +24 -0
- package/CODEOWNERS +11 -0
- package/Dockerfile +13 -0
- package/LICENSE +21 -0
- package/MANIFEST.in +2 -0
- package/bin/hestia-validate-data +80 -0
- package/build_mocking.py +14 -0
- package/commitlint.config.js +1 -0
- package/docs/Makefile +20 -0
- package/docs/_static/styles.css +4 -0
- package/docs/_templates/custom-class-template.rst +34 -0
- package/docs/_templates/custom-module-template.rst +66 -0
- package/docs/_templates/layout.html +4 -0
- package/docs/conf.py +74 -0
- package/docs/index.rst +42 -0
- package/docs/make.bat +35 -0
- package/docs/requirements.txt +13 -0
- package/envs/.develop.env +1 -0
- package/envs/.master.env +1 -0
- package/guide-assets/.gitkeep +0 -0
- package/hestia_earth/validation/README.md +5 -0
- package/hestia_earth/validation/__init__.py +32 -0
- package/hestia_earth/validation/distribution.py +22 -0
- package/hestia_earth/validation/gee.py +162 -0
- package/hestia_earth/validation/log.py +44 -0
- package/hestia_earth/validation/models.py +141 -0
- package/hestia_earth/validation/preload_requests.py +61 -0
- package/hestia_earth/validation/terms.py +88 -0
- package/hestia_earth/validation/utils.py +444 -0
- package/hestia_earth/validation/validators/__init__.py +141 -0
- package/hestia_earth/validation/validators/aggregated_cycle.py +32 -0
- package/hestia_earth/validation/validators/aggregated_shared.py +37 -0
- package/hestia_earth/validation/validators/animal.py +88 -0
- package/hestia_earth/validation/validators/completeness.py +252 -0
- package/hestia_earth/validation/validators/cycle.py +1123 -0
- package/hestia_earth/validation/validators/distribution.py +86 -0
- package/hestia_earth/validation/validators/emission.py +109 -0
- package/hestia_earth/validation/validators/impact_assessment.py +138 -0
- package/hestia_earth/validation/validators/indicator.py +154 -0
- package/hestia_earth/validation/validators/infrastructure.py +25 -0
- package/hestia_earth/validation/validators/input.py +268 -0
- package/hestia_earth/validation/validators/management.py +131 -0
- package/hestia_earth/validation/validators/measurement.py +368 -0
- package/hestia_earth/validation/validators/organisation.py +43 -0
- package/hestia_earth/validation/validators/practice.py +590 -0
- package/hestia_earth/validation/validators/product.py +263 -0
- package/hestia_earth/validation/validators/property.py +266 -0
- package/hestia_earth/validation/validators/shared.py +940 -0
- package/hestia_earth/validation/validators/site.py +312 -0
- package/hestia_earth/validation/validators/source.py +20 -0
- package/hestia_earth/validation/validators/transformation.py +250 -0
- package/hestia_earth/validation/version.py +1 -0
- package/layer/build.sh +34 -0
- package/layer/deploy.sh +18 -0
- package/package.json +59 -0
- package/release.sh +11 -0
- package/requirements-ci.txt +6 -0
- package/requirements-test.txt +4 -0
- package/requirements.txt +2 -0
- package/run-docker-test.sh +7 -0
- package/run-docker.sh +9 -0
- package/run.py +99 -0
- package/scripts/build_docs.py +283 -0
- package/scripts/build_validation_list.py +160 -0
- package/scripts/guide-create-branch.sh +15 -0
- package/scripts/update-package-version.js +28 -0
- package/search-results.json +384 -0
- package/setup.cfg +2 -0
- package/setup.py +35 -0
- package/src/index.ts +1 -0
- package/src/validations.ts +22 -0
- package/src/version.ts +1 -0
- package/tests/Dockerfile +13 -0
- package/tests/__init__.py +3 -0
- package/tests/fixtures/aggregated/cycle/inputs-impactAssessment/invalid-no-impactAssessment.json +64 -0
- package/tests/fixtures/aggregated/cycle/inputs-impactAssessment/invalid-world.json +69 -0
- package/tests/fixtures/aggregated/cycle/inputs-impactAssessment/valid.json +69 -0
- package/tests/fixtures/animal/duplicated-input-cycle/invalid.json +98 -0
- package/tests/fixtures/animal/duplicated-input-cycle/valid.json +91 -0
- package/tests/fixtures/animal/pregnancyRateTotal/invalid.json +49 -0
- package/tests/fixtures/animal/pregnancyRateTotal/valid.json +60 -0
- package/tests/fixtures/animal/required/invalid.json +59 -0
- package/tests/fixtures/animal/required/valid.json +72 -0
- package/tests/fixtures/completeness/all-values/warning.json +22 -0
- package/tests/fixtures/completeness/animalPopulation/invalid.json +58 -0
- package/tests/fixtures/completeness/animalPopulation/valid-animals.json +71 -0
- package/tests/fixtures/completeness/animalPopulation/valid-incomplete.json +58 -0
- package/tests/fixtures/completeness/animalPopulation/valid-no-liveAnimals.json +37 -0
- package/tests/fixtures/completeness/blank-nodes/agri-food processor-invalid.json +52 -0
- package/tests/fixtures/completeness/blank-nodes/invalid.json +124 -0
- package/tests/fixtures/completeness/blank-nodes/valid.json +128 -0
- package/tests/fixtures/completeness/cropland/site.json +16 -0
- package/tests/fixtures/completeness/cropland/valid.json +22 -0
- package/tests/fixtures/completeness/cropland/warning.json +22 -0
- package/tests/fixtures/completeness/freshForage/error-animals.json +63 -0
- package/tests/fixtures/completeness/freshForage/error-products.json +65 -0
- package/tests/fixtures/completeness/freshForage/valid-animal-inputs.json +63 -0
- package/tests/fixtures/completeness/freshForage/valid-animals.json +63 -0
- package/tests/fixtures/completeness/freshForage/valid-not-grazing-liveAnimal.json +55 -0
- package/tests/fixtures/completeness/freshForage/valid-not-liveAnimal.json +47 -0
- package/tests/fixtures/completeness/freshForage/valid-products.json +68 -0
- package/tests/fixtures/completeness/ingredient/invalid-agri-food-processor.json +37 -0
- package/tests/fixtures/completeness/ingredient/invalid.json +49 -0
- package/tests/fixtures/completeness/ingredient/valid-agri-food-processor-complete.json +49 -0
- package/tests/fixtures/completeness/ingredient/valid-agri-food-processor-incomplete.json +37 -0
- package/tests/fixtures/completeness/ingredient/valid.json +49 -0
- package/tests/fixtures/completeness/material/error.json +49 -0
- package/tests/fixtures/completeness/material/valid-fuel-material.json +60 -0
- package/tests/fixtures/completeness/material/valid-incomplete.json +36 -0
- package/tests/fixtures/completeness/material/valid-no-fuel.json +36 -0
- package/tests/fixtures/completeness/valid.json +22 -0
- package/tests/fixtures/cycle/aboveGroundCropResidue/invalid.json +76 -0
- package/tests/fixtures/cycle/aboveGroundCropResidue/valid.json +76 -0
- package/tests/fixtures/cycle/aggregated-valid.json +102 -0
- package/tests/fixtures/cycle/coverCrop/invalid.json +64 -0
- package/tests/fixtures/cycle/coverCrop/valid-not-coverCrop.json +54 -0
- package/tests/fixtures/cycle/coverCrop/valid.json +64 -0
- package/tests/fixtures/cycle/cropResidue/complete/invalid.json +56 -0
- package/tests/fixtures/cycle/cropResidue/complete/valid.json +82 -0
- package/tests/fixtures/cycle/cropResidue/incomplete/invalid.json +42 -0
- package/tests/fixtures/cycle/cropResidue/incomplete/valid.json +56 -0
- package/tests/fixtures/cycle/dates/invalid-emissions.json +70 -0
- package/tests/fixtures/cycle/liveAnimal-animalProduct-mapping/invalid.json +63 -0
- package/tests/fixtures/cycle/liveAnimal-animalProduct-mapping/valid.json +63 -0
- package/tests/fixtures/cycle/maximumCycleDuration/invalid-dates-year-only.json +48 -0
- package/tests/fixtures/cycle/maximumCycleDuration/invalid-dates.json +48 -0
- package/tests/fixtures/cycle/maximumCycleDuration/invalid.json +48 -0
- package/tests/fixtures/cycle/maximumCycleDuration/valid-dates-year-only.json +48 -0
- package/tests/fixtures/cycle/maximumCycleDuration/valid-dates.json +48 -0
- package/tests/fixtures/cycle/maximumCycleDuration/valid.json +48 -0
- package/tests/fixtures/cycle/otherSites/cycleDuration/invalid.json +52 -0
- package/tests/fixtures/cycle/otherSites/cycleDuration/valid-no-siteDuration.json +40 -0
- package/tests/fixtures/cycle/otherSites/cycleDuration/valid.json +52 -0
- package/tests/fixtures/cycle/practices/stockingDensityPermanentPastureAverage/invalid.json +56 -0
- package/tests/fixtures/cycle/practices/stockingDensityPermanentPastureAverage/valid.json +65 -0
- package/tests/fixtures/cycle/primary-product-as-input/invalid.json +59 -0
- package/tests/fixtures/cycle/primary-product-as-input/valid.json +48 -0
- package/tests/fixtures/cycle/product-linked-ia/cycle.json +66 -0
- package/tests/fixtures/cycle/product-linked-ia/invalid-multiple.json +58 -0
- package/tests/fixtures/cycle/product-linked-ia/valid.json +57 -0
- package/tests/fixtures/cycle/products/animals/invalid.json +69 -0
- package/tests/fixtures/cycle/products/animals/valid.json +58 -0
- package/tests/fixtures/cycle/riceGrainInHuskFlooded-minimumCycleDuration/invalid.json +53 -0
- package/tests/fixtures/cycle/riceGrainInHuskFlooded-minimumCycleDuration/valid.json +53 -0
- package/tests/fixtures/cycle/siteDuration/crop/invalid.json +53 -0
- package/tests/fixtures/cycle/siteDuration/crop/valid-different-duration.json +53 -0
- package/tests/fixtures/cycle/siteDuration/crop/valid-same-duration.json +53 -0
- package/tests/fixtures/cycle/siteDuration/invalid.json +41 -0
- package/tests/fixtures/cycle/siteDuration/valid-no-siteDuration.json +40 -0
- package/tests/fixtures/cycle/siteDuration/valid-otherSites.json +48 -0
- package/tests/fixtures/cycle/siteDuration/valid.json +45 -0
- package/tests/fixtures/cycle/substrate/required/invalid.json +50 -0
- package/tests/fixtures/cycle/substrate/required/valid.json +60 -0
- package/tests/fixtures/cycle/valid.json +343 -0
- package/tests/fixtures/emission/linked-terms/inputs/invalid.json +78 -0
- package/tests/fixtures/emission/linked-terms/inputs/valid.json +106 -0
- package/tests/fixtures/emission/linked-terms/transformation/error.json +104 -0
- package/tests/fixtures/emission/linked-terms/transformation/valid.json +107 -0
- package/tests/fixtures/emission/linked-terms/transformation/warning.json +76 -0
- package/tests/fixtures/emission/methodTier-background/invalid.json +60 -0
- package/tests/fixtures/emission/methodTier-background/valid.json +60 -0
- package/tests/fixtures/emission/not-relevant/invalid.json +71 -0
- package/tests/fixtures/emission/not-relevant/valid.json +95 -0
- package/tests/fixtures/emission/not-relevant-methodTier/invalid.json +70 -0
- package/tests/fixtures/emission/not-relevant-methodTier/valid.json +95 -0
- package/tests/fixtures/impactAssessment/aggregated-valid.json +43 -0
- package/tests/fixtures/impactAssessment/cycle-contains-product/invalid.json +34 -0
- package/tests/fixtures/impactAssessment/cycle-contains-product/valid.json +34 -0
- package/tests/fixtures/impactAssessment/cycle-endDate/invalid.json +26 -0
- package/tests/fixtures/impactAssessment/cycle-endDate/valid.json +26 -0
- package/tests/fixtures/impactAssessment/valid.json +93 -0
- package/tests/fixtures/indicator/characterisedIndicator-methodModel/invalid.json +52 -0
- package/tests/fixtures/indicator/characterisedIndicator-methodModel/valid.json +52 -0
- package/tests/fixtures/indicator/ionisingCompounds/invalid.json +23 -0
- package/tests/fixtures/indicator/ionisingCompounds/valid.json +23 -0
- package/tests/fixtures/indicator/landTransformation/invalid-grouped.json +257 -0
- package/tests/fixtures/indicator/landTransformation/invalid.json +100 -0
- package/tests/fixtures/indicator/landTransformation/valid-grouped-full.json +507 -0
- package/tests/fixtures/indicator/landTransformation/valid-grouped.json +507 -0
- package/tests/fixtures/indicator/landTransformation/valid.json +100 -0
- package/tests/fixtures/infrastructure/lifespan/invalid.json +26 -0
- package/tests/fixtures/infrastructure/lifespan/valid.json +45 -0
- package/tests/fixtures/input/animalFeed-fate/invalid.json +103 -0
- package/tests/fixtures/input/animalFeed-fate/valid.json +90 -0
- package/tests/fixtures/input/country/invalid.json +64 -0
- package/tests/fixtures/input/country/valid.json +64 -0
- package/tests/fixtures/input/distribution/animalHousing.json +103 -0
- package/tests/fixtures/input/distribution/complete/invalid.json +177 -0
- package/tests/fixtures/input/distribution/complete/valid.json +163 -0
- package/tests/fixtures/input/distribution/incomplete/valid.json +139 -0
- package/tests/fixtures/input/impactAssessment/invalid.json +99 -0
- package/tests/fixtures/input/impactAssessment/valid.json +89 -0
- package/tests/fixtures/input/input-as-product/invalid.json +57 -0
- package/tests/fixtures/input/input-as-product/valid.json +59 -0
- package/tests/fixtures/input/mustIncludeId/invalid.json +13 -0
- package/tests/fixtures/input/mustIncludeId/valid-multiple-ids.json +31 -0
- package/tests/fixtures/input/mustIncludeId/valid.json +22 -0
- package/tests/fixtures/input/saplings/invalid.json +58 -0
- package/tests/fixtures/input/saplings/valid-no-saplings.json +58 -0
- package/tests/fixtures/input/saplings/valid-not-plantation.json +58 -0
- package/tests/fixtures/input/saplings/valid.json +58 -0
- package/tests/fixtures/integration/distribution/product-yield-invalid.json +54 -0
- package/tests/fixtures/management/cycle-overlap/cycles.json +39 -0
- package/tests/fixtures/management/cycle-overlap/invalid.json +26 -0
- package/tests/fixtures/management/cycle-overlap/valid.json +26 -0
- package/tests/fixtures/management/exists/invalid.json +13 -0
- package/tests/fixtures/management/exists/valid.json +25 -0
- package/tests/fixtures/management/fallow-dates/invalid.json +24 -0
- package/tests/fixtures/management/fallow-dates/valid.json +24 -0
- package/tests/fixtures/management/termType/invalid-cropland.json +35 -0
- package/tests/fixtures/management/termType/invalid-permanent-pasture.json +25 -0
- package/tests/fixtures/management/termType/valid-cropland.json +55 -0
- package/tests/fixtures/management/termType/valid-no-management.json +13 -0
- package/tests/fixtures/management/termType/valid-permanent-pasture.json +35 -0
- package/tests/fixtures/measurement/depths/invalid.json +44 -0
- package/tests/fixtures/measurement/depths/valid.json +50 -0
- package/tests/fixtures/measurement/models/valid.json +33 -0
- package/tests/fixtures/measurement/models/warning-no-value.json +30 -0
- package/tests/fixtures/measurement/models/warning.json +33 -0
- package/tests/fixtures/measurement/pond-measurements/invalid.json +11 -0
- package/tests/fixtures/measurement/pond-measurements/valid.json +23 -0
- package/tests/fixtures/measurement/required-depths/error.json +71 -0
- package/tests/fixtures/measurement/required-depths/valid.json +126 -0
- package/tests/fixtures/measurement/required-depths/warning.json +29 -0
- package/tests/fixtures/measurement/soilTexture/missing-texture-value.json +227 -0
- package/tests/fixtures/measurement/soilTexture/percent-invalid.json +110 -0
- package/tests/fixtures/measurement/soilTexture/percent-missing-value.json +43 -0
- package/tests/fixtures/measurement/soilTexture/percent-valid.json +110 -0
- package/tests/fixtures/measurement/startDate-endDate-required/invalid.json +32 -0
- package/tests/fixtures/measurement/startDate-endDate-required/valid.json +46 -0
- package/tests/fixtures/measurement/unique/invalid.json +28 -0
- package/tests/fixtures/measurement/unique/valid.json +16 -0
- package/tests/fixtures/measurement/value-length/invalid.json +46 -0
- package/tests/fixtures/measurement/value-length/valid.json +44 -0
- package/tests/fixtures/measurement/water-salinity/invalid.json +33 -0
- package/tests/fixtures/measurement/water-salinity/valid-brakish.json +40 -0
- package/tests/fixtures/measurement/water-salinity/valid.json +33 -0
- package/tests/fixtures/organisation/valid.json +26 -0
- package/tests/fixtures/practice/croppingDuration/riceGrainInHuskFlooded/invalid.json +63 -0
- package/tests/fixtures/practice/croppingDuration/riceGrainInHuskFlooded/valid.json +63 -0
- package/tests/fixtures/practice/defaultValue/invalid.json +12 -0
- package/tests/fixtures/practice/defaultValue/valid.json +15 -0
- package/tests/fixtures/practice/excretaManagement/invalid.json +50 -0
- package/tests/fixtures/practice/excretaManagement/valid.json +60 -0
- package/tests/fixtures/practice/irrigated-complete/invalid.json +47 -0
- package/tests/fixtures/practice/irrigated-complete/valid-incomplete.json +47 -0
- package/tests/fixtures/practice/irrigated-complete/valid.json +60 -0
- package/tests/fixtures/practice/landCover-products/invalid.json +58 -0
- package/tests/fixtures/practice/landCover-products/valid-coverCrop.json +69 -0
- package/tests/fixtures/practice/landCover-products/valid.json +58 -0
- package/tests/fixtures/practice/liveAnimal-system/invalid.json +58 -0
- package/tests/fixtures/practice/liveAnimal-system/valid.json +69 -0
- package/tests/fixtures/practice/longFallowDuration/invalid.json +20 -0
- package/tests/fixtures/practice/longFallowDuration/valid.json +20 -0
- package/tests/fixtures/practice/noTillage/invalid.json +23 -0
- package/tests/fixtures/practice/noTillage/valid-value-not-100.json +23 -0
- package/tests/fixtures/practice/noTillage/valid.json +21 -0
- package/tests/fixtures/practice/pastureGrass/key-termType/invalid.json +16 -0
- package/tests/fixtures/practice/pastureGrass/key-termType/valid.json +16 -0
- package/tests/fixtures/practice/pastureGrass/key-value/invalid-numbers.json +67 -0
- package/tests/fixtures/practice/pastureGrass/key-value/invalid.json +67 -0
- package/tests/fixtures/practice/pastureGrass/key-value/valid.json +67 -0
- package/tests/fixtures/practice/pastureGrass/permanent-pasture/invalid.json +37 -0
- package/tests/fixtures/practice/pastureGrass/permanent-pasture/valid.json +47 -0
- package/tests/fixtures/practice/primaryPercent/invalid.json +49 -0
- package/tests/fixtures/practice/primaryPercent/valid.json +49 -0
- package/tests/fixtures/practice/processingOperation/invalid-no-primary.json +48 -0
- package/tests/fixtures/practice/processingOperation/invalid.json +49 -0
- package/tests/fixtures/practice/processingOperation/valid-cropland.json +37 -0
- package/tests/fixtures/practice/processingOperation/valid.json +49 -0
- package/tests/fixtures/practice/productivePhasePermanentCrops/invalid.json +48 -0
- package/tests/fixtures/practice/productivePhasePermanentCrops/valid-0-value.json +58 -0
- package/tests/fixtures/practice/productivePhasePermanentCrops/valid-no-value.json +47 -0
- package/tests/fixtures/practice/productivePhasePermanentCrops/valid.json +48 -0
- package/tests/fixtures/practice/site-management/invalid.json +75 -0
- package/tests/fixtures/practice/site-management/valid.json +75 -0
- package/tests/fixtures/practice/tillage-siteType/valid.json +51 -0
- package/tests/fixtures/practice/tillage-siteType/warning.json +42 -0
- package/tests/fixtures/practice/tillage-values/invalid-fullTillage.json +61 -0
- package/tests/fixtures/practice/tillage-values/invalid-noTillage.json +61 -0
- package/tests/fixtures/practice/tillage-values/valid.json +61 -0
- package/tests/fixtures/practice/waterRegime/rice/invalid.json +59 -0
- package/tests/fixtures/practice/waterRegime/rice/valid-0-value.json +59 -0
- package/tests/fixtures/practice/waterRegime/rice/valid.json +58 -0
- package/tests/fixtures/product/economicValueShare/invalid.json +31 -0
- package/tests/fixtures/product/economicValueShare/valid.json +22 -0
- package/tests/fixtures/product/excreta/invalid.json +62 -0
- package/tests/fixtures/product/excreta/valid.json +62 -0
- package/tests/fixtures/product/excreta/warning.json +53 -0
- package/tests/fixtures/product/excreta/with-system/invalid.json +79 -0
- package/tests/fixtures/product/excreta/with-system/valid.json +88 -0
- package/tests/fixtures/product/excreta/with-system/warning.json +70 -0
- package/tests/fixtures/product/fu_ha/invalid.json +49 -0
- package/tests/fixtures/product/fu_ha/valid.json +49 -0
- package/tests/fixtures/product/primary/invalid.json +22 -0
- package/tests/fixtures/product/primary/valid.json +22 -0
- package/tests/fixtures/product/value/valid.json +26 -0
- package/tests/fixtures/product/value/value-0/error.json +40 -0
- package/tests/fixtures/product/value/value-empty/warning.json +23 -0
- package/tests/fixtures/product/yield/invalid.json +54 -0
- package/tests/fixtures/product/yield/no-value.json +75 -0
- package/tests/fixtures/product/yield/valid.json +54 -0
- package/tests/fixtures/property/default-value/valid-allowed-exception.json +61 -0
- package/tests/fixtures/property/default-value/valid.json +61 -0
- package/tests/fixtures/property/default-value/warning.json +61 -0
- package/tests/fixtures/property/termType/invalid.json +60 -0
- package/tests/fixtures/property/termType/valid.json +60 -0
- package/tests/fixtures/property/value-min-max/invalid.json +77 -0
- package/tests/fixtures/property/value-min-max/valid-skip-maximum.json +57 -0
- package/tests/fixtures/property/value-min-max/valid.json +78 -0
- package/tests/fixtures/property/valueType/invalid.json +79 -0
- package/tests/fixtures/property/valueType/valid.json +79 -0
- package/tests/fixtures/property/volatileSolidsContent/invalid.json +99 -0
- package/tests/fixtures/property/volatileSolidsContent/valid.json +99 -0
- package/tests/fixtures/shared/coordinates/invalid.json +18 -0
- package/tests/fixtures/shared/coordinates/valid.json +18 -0
- package/tests/fixtures/shared/data-duplicates/valid.json +113 -0
- package/tests/fixtures/shared/data-duplicates/warning.json +172 -0
- package/tests/fixtures/shared/duplicated-term-units/invalid-animalProduct.json +61 -0
- package/tests/fixtures/shared/duplicated-term-units/invalid-organicFertiliser.json +61 -0
- package/tests/fixtures/shared/duplicated-term-units/valid.json +49 -0
- package/tests/fixtures/shared/list-country-region/invalid.json +54 -0
- package/tests/fixtures/shared/list-country-region/valid.json +54 -0
- package/tests/fixtures/shared/list-percent-value/invalid.json +49 -0
- package/tests/fixtures/shared/list-percent-value/valid.json +52 -0
- package/tests/fixtures/shared/list-valueType/invalid.json +49 -0
- package/tests/fixtures/shared/list-valueType/valid.json +49 -0
- package/tests/fixtures/shared/list-values-sum-100/management/with-properties/valid.json +91 -0
- package/tests/fixtures/shared/list-values-sum-100/measurements/missing-soil.json +46 -0
- package/tests/fixtures/shared/list-values-sum-100/measurements/no-depth-high-value.json +63 -0
- package/tests/fixtures/shared/list-values-sum-100/measurements/no-depth-valid.json +40 -0
- package/tests/fixtures/shared/list-values-sum-100/measurements/with-depth-high-value.json +71 -0
- package/tests/fixtures/shared/list-values-sum-100/practices/total-100.json +61 -0
- package/tests/fixtures/shared/list-values-sum-100/practices/total-110.json +61 -0
- package/tests/fixtures/shared/list-values-sum-100/practices/total-90.json +61 -0
- package/tests/fixtures/shared/min-max/value-above.json +31 -0
- package/tests/fixtures/shared/min-max/value-below.json +31 -0
- package/tests/fixtures/shared/min-max/value-valid.json +45 -0
- package/tests/fixtures/shared/model/emissions/invalid.json +102 -0
- package/tests/fixtures/shared/model/emissions/valid-variable-tolerance.json +180 -0
- package/tests/fixtures/shared/model/emissions/valid.json +102 -0
- package/tests/fixtures/shared/model/impacts/invalid.json +75 -0
- package/tests/fixtures/shared/model/impacts/valid.json +75 -0
- package/tests/fixtures/shared/model/inputs/valid-no-value.json +84 -0
- package/tests/fixtures/shared/model/inputs/valid.json +87 -0
- package/tests/fixtures/shared/model/inputs/warning.json +87 -0
- package/tests/fixtures/shared/model/products/valid-no-value.json +88 -0
- package/tests/fixtures/shared/model/products/valid.json +91 -0
- package/tests/fixtures/shared/model/products/warning.json +91 -0
- package/tests/fixtures/shared/otherModel/invalid.json +69 -0
- package/tests/fixtures/shared/otherModel/valid.json +70 -0
- package/tests/fixtures/shared/properties-duplicate-values/invalid.json +61 -0
- package/tests/fixtures/shared/properties-duplicate-values/valid.json +57 -0
- package/tests/fixtures/shared/properties-same-length/invalid.json +62 -0
- package/tests/fixtures/shared/properties-same-length/valid.json +52 -0
- package/tests/fixtures/shared/unit-percent/invalid.json +34 -0
- package/tests/fixtures/shared/unit-percent/valid.json +60 -0
- package/tests/fixtures/shared/unit-percent/warning.json +52 -0
- package/tests/fixtures/site/cycles-linked-ia/invalid.json +129 -0
- package/tests/fixtures/site/cycles-linked-ia/valid.json +129 -0
- package/tests/fixtures/site/valid.json +138 -0
- package/tests/fixtures/source/valid.json +19 -0
- package/tests/fixtures/transformation/excretaManagement/invalid.json +47 -0
- package/tests/fixtures/transformation/excretaManagement/valid.json +59 -0
- package/tests/fixtures/transformation/inputs-products/invalid.json +43 -0
- package/tests/fixtures/transformation/inputs-products/valid.json +43 -0
- package/tests/fixtures/transformation/linked-emission/invalid.json +101 -0
- package/tests/fixtures/transformation/linked-emission/valid.json +107 -0
- package/tests/fixtures/transformation/previousTransformationId/invalid-no-previous.json +127 -0
- package/tests/fixtures/transformation/previousTransformationId/invalid-previous-input.json +100 -0
- package/tests/fixtures/transformation/previousTransformationId/invalid-product-input.json +106 -0
- package/tests/fixtures/transformation/previousTransformationId/invalid-wrong-order.json +136 -0
- package/tests/fixtures/transformation/previousTransformationId/valid.json +171 -0
- package/tests/integration/__init__.py +0 -0
- package/tests/integration/test_product.py +17 -0
- package/tests/test_gee.py +10 -0
- package/tests/test_utils.py +36 -0
- package/tests/test_validation.py +11 -0
- package/tests/utils.py +28 -0
- package/tests/validators/__init__.py +0 -0
- package/tests/validators/test_aggregated_cycle.py +44 -0
- package/tests/validators/test_aggregated_shared.py +63 -0
- package/tests/validators/test_animal.py +72 -0
- package/tests/validators/test_completeness.py +337 -0
- package/tests/validators/test_cycle.py +600 -0
- package/tests/validators/test_emission.py +170 -0
- package/tests/validators/test_impact_assessment.py +80 -0
- package/tests/validators/test_indicator.py +120 -0
- package/tests/validators/test_infrastructure.py +26 -0
- package/tests/validators/test_input.py +434 -0
- package/tests/validators/test_management.py +177 -0
- package/tests/validators/test_measurement.py +317 -0
- package/tests/validators/test_organisation.py +32 -0
- package/tests/validators/test_practice.py +490 -0
- package/tests/validators/test_product.py +291 -0
- package/tests/validators/test_property.py +143 -0
- package/tests/validators/test_shared.py +1139 -0
- package/tests/validators/test_site.py +151 -0
- package/tests/validators/test_source.py +15 -0
- package/tests/validators/test_transformation.py +151 -0
- package/tests/validators/test_validators.py +74 -0
- package/tsconfig.dist.json +9 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,1123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Cycle validation
|
|
3
|
+
|
|
4
|
+
Here is the list of validations running on a [Cycle](/schema/Cycle).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from hestia_earth.schema import (
|
|
9
|
+
NodeType,
|
|
10
|
+
SiteSiteType,
|
|
11
|
+
TermTermType,
|
|
12
|
+
CycleFunctionalUnit,
|
|
13
|
+
CycleStartDateDefinition,
|
|
14
|
+
)
|
|
15
|
+
from hestia_earth.utils.tools import flatten, list_sum, non_empty_list, safe_parse_float
|
|
16
|
+
from hestia_earth.utils.date import diff_in_days, is_in_days
|
|
17
|
+
from hestia_earth.utils.model import (
|
|
18
|
+
find_term_match,
|
|
19
|
+
filter_list_term_type,
|
|
20
|
+
find_primary_product,
|
|
21
|
+
)
|
|
22
|
+
from hestia_earth.utils.lookup import get_table_value, download_lookup
|
|
23
|
+
|
|
24
|
+
from hestia_earth.validation.utils import (
|
|
25
|
+
_filter_list_errors,
|
|
26
|
+
find_linked_node,
|
|
27
|
+
_value_average,
|
|
28
|
+
list_sum_terms,
|
|
29
|
+
is_same_product,
|
|
30
|
+
is_permanent_crop,
|
|
31
|
+
get_lookup_value,
|
|
32
|
+
)
|
|
33
|
+
from hestia_earth.validation.terms import TERMS_QUERY, get_terms
|
|
34
|
+
from hestia_earth.validation.models import run_models
|
|
35
|
+
from .shared import (
|
|
36
|
+
validate_dates,
|
|
37
|
+
validate_dates_format,
|
|
38
|
+
validate_list_dates,
|
|
39
|
+
validate_list_dates_after,
|
|
40
|
+
validate_date_lt_today,
|
|
41
|
+
validate_list_min_below_max,
|
|
42
|
+
validate_list_min_max_lookup,
|
|
43
|
+
validate_list_term_percent,
|
|
44
|
+
validate_linked_source_privacy,
|
|
45
|
+
validate_list_dates_length,
|
|
46
|
+
validate_list_date_lt_today,
|
|
47
|
+
validate_list_model,
|
|
48
|
+
validate_list_model_config,
|
|
49
|
+
validate_list_dates_format,
|
|
50
|
+
validate_list_duplicate_values,
|
|
51
|
+
validate_private_has_source,
|
|
52
|
+
validate_list_value_between_min_max,
|
|
53
|
+
validate_duplicated_term_units,
|
|
54
|
+
validate_list_sum_100_percent,
|
|
55
|
+
validate_list_percent_requires_value,
|
|
56
|
+
validate_list_valueType,
|
|
57
|
+
validate_other_model,
|
|
58
|
+
validate_nested_existing_node,
|
|
59
|
+
validate_list_country_region,
|
|
60
|
+
)
|
|
61
|
+
from .aggregated_cycle import validate_linked_impactAssessment
|
|
62
|
+
from .aggregated_shared import validate_id
|
|
63
|
+
from .animal import (
|
|
64
|
+
validate_has_animals,
|
|
65
|
+
validate_duplicated_feed_inputs,
|
|
66
|
+
validate_has_pregnancyRateTotal,
|
|
67
|
+
)
|
|
68
|
+
from .emission import (
|
|
69
|
+
validate_linked_terms,
|
|
70
|
+
validate_method_not_relevant,
|
|
71
|
+
validate_methodTier_not_relevant,
|
|
72
|
+
validate_methodTier_background,
|
|
73
|
+
)
|
|
74
|
+
from .input import (
|
|
75
|
+
validate_must_include_id,
|
|
76
|
+
validate_input_country,
|
|
77
|
+
validate_related_impacts,
|
|
78
|
+
validate_input_distribution_value,
|
|
79
|
+
validate_animalFeed_requires_isAnimalFeed,
|
|
80
|
+
validate_saplings,
|
|
81
|
+
validate_input_is_product,
|
|
82
|
+
)
|
|
83
|
+
from .practice import (
|
|
84
|
+
validate_longFallowDuration,
|
|
85
|
+
validate_excretaManagement,
|
|
86
|
+
validate_no_tillage,
|
|
87
|
+
validate_tillage_site_type,
|
|
88
|
+
validate_liveAnimal_system,
|
|
89
|
+
validate_pastureGrass_key_termType,
|
|
90
|
+
validate_has_pastureGrass,
|
|
91
|
+
validate_pastureGrass_key_value,
|
|
92
|
+
validate_defaultValue,
|
|
93
|
+
validate_tillage_values,
|
|
94
|
+
validate_waterRegime_rice_products,
|
|
95
|
+
validate_croppingDuration_riceGrainInHuskFlooded,
|
|
96
|
+
validate_permanent_crop_productive_phase,
|
|
97
|
+
validate_primaryPercent,
|
|
98
|
+
validate_processing_operation,
|
|
99
|
+
validate_landCover_match_products,
|
|
100
|
+
validate_practices_management,
|
|
101
|
+
validate_irrigated_complete_has_inputs,
|
|
102
|
+
)
|
|
103
|
+
from .product import (
|
|
104
|
+
validate_economicValueShare,
|
|
105
|
+
validate_value_empty,
|
|
106
|
+
validate_value_0,
|
|
107
|
+
validate_primary as validate_product_primary,
|
|
108
|
+
validate_product_ha_functional_unit_ha,
|
|
109
|
+
validate_product_yield,
|
|
110
|
+
validate_excreta_product,
|
|
111
|
+
)
|
|
112
|
+
from .completeness import validate_completeness, validate_completeness_blank_nodes
|
|
113
|
+
from .transformation import (
|
|
114
|
+
validate_previous_transformation,
|
|
115
|
+
validate_transformation_excretaManagement,
|
|
116
|
+
validate_linked_emission,
|
|
117
|
+
)
|
|
118
|
+
from .property import (
|
|
119
|
+
validate_all as validate_properties,
|
|
120
|
+
validate_volatileSolidsContent,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
_VALIDATE_LINKED_IA = os.getenv("VALIDATE_LINKED_IA", "true") == "true"
|
|
125
|
+
_VALIDATE_COMPLETENESS_AREAS = (
|
|
126
|
+
os.getenv("VALIDATE_COMPLETENESS_AREAS", "true") == "true"
|
|
127
|
+
)
|
|
128
|
+
_RUN_CYCLE_MODELS = os.getenv("VALIDATE_RUN_CYCLE_MODELS", "false") == "true"
|
|
129
|
+
|
|
130
|
+
SITE_TYPES_CROP_RESIDUE = [
|
|
131
|
+
SiteSiteType.CROPLAND.value,
|
|
132
|
+
SiteSiteType.GLASS_OR_HIGH_ACCESSIBLE_COVER.value,
|
|
133
|
+
]
|
|
134
|
+
SITE_TYPES_NOT_1_HA = [
|
|
135
|
+
SiteSiteType.AGRI_FOOD_PROCESSOR.value,
|
|
136
|
+
SiteSiteType.FOOD_RETAILER.value,
|
|
137
|
+
]
|
|
138
|
+
PRODUCTS_MODEL_CONFIG = {
|
|
139
|
+
"aboveGroundCropResidueTotal": {
|
|
140
|
+
"level": "warning",
|
|
141
|
+
"model": "ipcc2006",
|
|
142
|
+
"delta": 0.5,
|
|
143
|
+
"resetDataCompleteness": True,
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
INPUTS_MODEL_CONFIG = {
|
|
147
|
+
"saplingsDepreciatedAmountPerCycle": {
|
|
148
|
+
"level": "warning",
|
|
149
|
+
"model": "pooreNemecek2018",
|
|
150
|
+
"delta": 0.25,
|
|
151
|
+
"resetDataCompleteness": True,
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
# list of models to run before validating the cycle
|
|
155
|
+
CYCLE_MODELS_PRE_RUN = [
|
|
156
|
+
{
|
|
157
|
+
"key": "animals",
|
|
158
|
+
"model": "cycle",
|
|
159
|
+
"value": "animal.input.properties",
|
|
160
|
+
"runStrategy": "always",
|
|
161
|
+
"mergeStrategy": "list",
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"key": "inputs",
|
|
165
|
+
"model": "cycle",
|
|
166
|
+
"value": "input.properties",
|
|
167
|
+
"runStrategy": "always",
|
|
168
|
+
"mergeStrategy": "list",
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
"key": "products",
|
|
172
|
+
"model": "cycle",
|
|
173
|
+
"value": "product.properties",
|
|
174
|
+
"runStrategy": "always",
|
|
175
|
+
"mergeStrategy": "list",
|
|
176
|
+
},
|
|
177
|
+
]
|
|
178
|
+
DUPLICATED_TERM_UNITS_TERM_TYPES = [
|
|
179
|
+
TermTermType.ANIMALPRODUCT,
|
|
180
|
+
TermTermType.ORGANICFERTILISER,
|
|
181
|
+
]
|
|
182
|
+
# sum of all values with same termTpe must be exactly 100
|
|
183
|
+
PRACTICE_SUM_100_TERM_TYPES = [TermTermType.TILLAGE, TermTermType.WATERREGIME]
|
|
184
|
+
# sum of all values with same termTpe must be maximum 100
|
|
185
|
+
PRACTICE_SUM_100_MAX_TERM_TYPES = [
|
|
186
|
+
TermTermType.CROPRESIDUEMANAGEMENT,
|
|
187
|
+
TermTermType.LANDCOVER,
|
|
188
|
+
]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def validate_functionalUnit_not_1_ha(cycle: dict, site: dict, other_sites: list = []):
|
|
192
|
+
"""
|
|
193
|
+
Validate `functionalUnit`
|
|
194
|
+
|
|
195
|
+
This validation prevents using `1 ha` as a `functionalUnit` when the `siteType` is:
|
|
196
|
+
- `agri-food processor`;
|
|
197
|
+
- `retailer`.
|
|
198
|
+
"""
|
|
199
|
+
all_sites = non_empty_list([site] + (other_sites or []))
|
|
200
|
+
site_types = non_empty_list([s.get("siteType") for s in all_sites])
|
|
201
|
+
value = cycle.get("functionalUnit")
|
|
202
|
+
forbidden = CycleFunctionalUnit._1_HA.value
|
|
203
|
+
invalid_site_type = next(
|
|
204
|
+
(site_type for site_type in site_types if site_type in SITE_TYPES_NOT_1_HA),
|
|
205
|
+
None,
|
|
206
|
+
)
|
|
207
|
+
return (
|
|
208
|
+
value != forbidden
|
|
209
|
+
or not invalid_site_type
|
|
210
|
+
or {
|
|
211
|
+
"level": "error",
|
|
212
|
+
"dataPath": ".functionalUnit",
|
|
213
|
+
"message": "must not be equal to 1 ha",
|
|
214
|
+
"params": {"siteType": invalid_site_type},
|
|
215
|
+
}
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def validate_cycle_dates(cycle: dict):
|
|
220
|
+
"""
|
|
221
|
+
Validate Cycle Dates
|
|
222
|
+
|
|
223
|
+
This validation ensures two things:
|
|
224
|
+
1. The `endDate` must be later than `startDate`;
|
|
225
|
+
2. The date format of `endDate` must be the same as `startDate`.
|
|
226
|
+
"""
|
|
227
|
+
return _filter_list_errors(
|
|
228
|
+
[
|
|
229
|
+
validate_dates(cycle)
|
|
230
|
+
or {
|
|
231
|
+
"level": "error",
|
|
232
|
+
"dataPath": ".endDate",
|
|
233
|
+
"message": "must be greater than startDate",
|
|
234
|
+
},
|
|
235
|
+
validate_dates_format(cycle)
|
|
236
|
+
or {
|
|
237
|
+
"level": "error",
|
|
238
|
+
"dataPath": ".startDate",
|
|
239
|
+
"message": "must be in the same format as endDate",
|
|
240
|
+
},
|
|
241
|
+
]
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _should_validate_cycleDuration(cycle: dict):
|
|
246
|
+
return (
|
|
247
|
+
"cycleDuration" in cycle
|
|
248
|
+
and is_in_days(cycle.get("startDate"))
|
|
249
|
+
and is_in_days(cycle.get("endDate"))
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def validate_cycleDuration(cycle: dict):
|
|
254
|
+
"""
|
|
255
|
+
Validate `cycleDuration`
|
|
256
|
+
|
|
257
|
+
When the `startDate` and `endDate` of the Cycle are provided, the `cycleDuration` must be equal to the difference
|
|
258
|
+
in days.
|
|
259
|
+
"""
|
|
260
|
+
duration = diff_in_days(cycle.get("startDate"), cycle.get("endDate"))
|
|
261
|
+
return duration == round(cycle.get("cycleDuration"), 1) or {
|
|
262
|
+
"level": "error",
|
|
263
|
+
"dataPath": ".cycleDuration",
|
|
264
|
+
"message": "must equal to endDate - startDate in days",
|
|
265
|
+
"params": {"expected": duration},
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def validate_sum_aboveGroundCropResidue(products: list):
|
|
270
|
+
"""
|
|
271
|
+
Validate total of above ground crop residue
|
|
272
|
+
|
|
273
|
+
If the Cycle contains `aboveGroundCropResidueTotal` and any of:
|
|
274
|
+
- `aboveGroundCropResidueBurnt`
|
|
275
|
+
- `aboveGroundCropResidueIncorporated`
|
|
276
|
+
- `aboveGroundCropResidueLeftOnField`
|
|
277
|
+
- `aboveGroundCropResidueRemoved`
|
|
278
|
+
|
|
279
|
+
Then the total must be euqal to the sun of the other terms.
|
|
280
|
+
"""
|
|
281
|
+
prefix = "aboveGroundCropResidue"
|
|
282
|
+
total_residue_index = next(
|
|
283
|
+
(
|
|
284
|
+
n
|
|
285
|
+
for n in range(len(products))
|
|
286
|
+
if "Total" in products[n].get("term", {}).get("@id")
|
|
287
|
+
and products[n].get("term", {}).get("@id").startswith(prefix)
|
|
288
|
+
),
|
|
289
|
+
None,
|
|
290
|
+
)
|
|
291
|
+
total_residue = (
|
|
292
|
+
None
|
|
293
|
+
if total_residue_index is None
|
|
294
|
+
else _value_average(products[total_residue_index])
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
other_residues = list(
|
|
298
|
+
filter(
|
|
299
|
+
lambda n: n.get("term").get("@id").startswith(prefix)
|
|
300
|
+
and "Total" not in n.get("term").get("@id"),
|
|
301
|
+
products,
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
other_residues_ids = list(map(lambda n: n.get("term").get("@id"), other_residues))
|
|
305
|
+
other_sum = sum([_value_average(node) for node in other_residues])
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
total_residue_index is None
|
|
309
|
+
or len(other_residues) == 0
|
|
310
|
+
or (total_residue * 1.01) >= other_sum
|
|
311
|
+
or {
|
|
312
|
+
"level": "error",
|
|
313
|
+
"dataPath": f".products[{total_residue_index}].value",
|
|
314
|
+
"message": "must be more than or equal to other crop residues",
|
|
315
|
+
"params": {"expected": other_residues_ids},
|
|
316
|
+
}
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _crop_residue_fate(cycle: dict):
|
|
321
|
+
practices = filter_list_term_type(
|
|
322
|
+
cycle.get("practices", []), TermTermType.CROPRESIDUEMANAGEMENT
|
|
323
|
+
)
|
|
324
|
+
products = filter_list_term_type(
|
|
325
|
+
cycle.get("products", []), TermTermType.CROPRESIDUE
|
|
326
|
+
)
|
|
327
|
+
terms = get_terms(TERMS_QUERY.CROP_RESIDUE)
|
|
328
|
+
above_terms = list(filter(lambda term: term.startswith("above"), terms))
|
|
329
|
+
sum_above_ground = list_sum_terms(products, above_terms)
|
|
330
|
+
below_terms = list(filter(lambda term: term.startswith("below"), terms))
|
|
331
|
+
sum_below_ground = list_sum_terms(products, below_terms)
|
|
332
|
+
return (practices, sum_above_ground, sum_below_ground)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def validate_crop_residue_complete(cycle: dict, site: dict):
|
|
336
|
+
"""
|
|
337
|
+
Validate crop residue when it is marked as complete
|
|
338
|
+
|
|
339
|
+
When `cropResidue` is marked as complete, this validation will make sure that these terms are specified:
|
|
340
|
+
- at least one `cropResidueManagement` practice;
|
|
341
|
+
- the sum of "above ground crop residue" products is > 0;
|
|
342
|
+
- and the "below ground crop residue" is specified.
|
|
343
|
+
|
|
344
|
+
If any of these conditions fail, then the `cropResidue` should not be set as complete.
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
def validate():
|
|
348
|
+
practices, sum_above_ground, sum_below_ground = _crop_residue_fate(cycle)
|
|
349
|
+
return all(
|
|
350
|
+
[len(practices) > 0, sum_above_ground, sum_below_ground is not None]
|
|
351
|
+
) or {
|
|
352
|
+
"level": "error",
|
|
353
|
+
"dataPath": "",
|
|
354
|
+
"message": "must specify the fate of cropResidue",
|
|
355
|
+
"params": {"siteType": SITE_TYPES_CROP_RESIDUE},
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
data_complete = cycle.get("completeness", {}).get(
|
|
359
|
+
TermTermType.CROPRESIDUE.value, False
|
|
360
|
+
)
|
|
361
|
+
site_type = site.get("siteType")
|
|
362
|
+
return not data_complete or site_type not in SITE_TYPES_CROP_RESIDUE or validate()
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def validate_crop_residue_incomplete(cycle: dict, site: dict):
|
|
366
|
+
"""
|
|
367
|
+
Validate crop residue when it is marked as incomplete
|
|
368
|
+
|
|
369
|
+
When `cropResidue` is marked as complete, this validation will check if these terms are specified:
|
|
370
|
+
- at least one `cropResidueManagement` practice;
|
|
371
|
+
- the sum of "above ground crop residue" products is > 0;
|
|
372
|
+
- and the "below ground crop residue" is specified.
|
|
373
|
+
|
|
374
|
+
If any of these conditions apply, then the `cropResidue` should be set as complete, and a warning will be given.
|
|
375
|
+
"""
|
|
376
|
+
|
|
377
|
+
def validate():
|
|
378
|
+
practices, sum_above_ground, sum_below_ground = _crop_residue_fate(cycle)
|
|
379
|
+
return any(
|
|
380
|
+
[len(practices) > 0, sum_above_ground, sum_below_ground is not None]
|
|
381
|
+
) or {
|
|
382
|
+
"level": "warning",
|
|
383
|
+
"dataPath": "",
|
|
384
|
+
"message": "should specify the fate of cropResidue",
|
|
385
|
+
"params": {"siteType": SITE_TYPES_CROP_RESIDUE},
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
data_complete = cycle.get("completeness", {}).get(
|
|
389
|
+
TermTermType.CROPRESIDUE.value, False
|
|
390
|
+
)
|
|
391
|
+
site_type = site.get("siteType")
|
|
392
|
+
return data_complete or site_type not in SITE_TYPES_CROP_RESIDUE or validate()
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def validate_crop_siteDuration(cycle: dict):
|
|
396
|
+
"""
|
|
397
|
+
Validate `siteDuration` for `crop` only
|
|
398
|
+
|
|
399
|
+
This validations ensures that for crop production cycles, if the user sets `siteDuration`,
|
|
400
|
+
it is only equal to `cycleDuration` when `startDateDefinition = harvest of previous crop`.
|
|
401
|
+
"""
|
|
402
|
+
is_crop = (find_primary_product(cycle) or {}).get("term", {}).get(
|
|
403
|
+
"termType"
|
|
404
|
+
) == TermTermType.CROP.value
|
|
405
|
+
cycleDuration = cycle.get("cycleDuration")
|
|
406
|
+
siteDuration = cycle.get("siteDuration")
|
|
407
|
+
startDateDefinition = cycle.get("startDateDefinition")
|
|
408
|
+
harvest_previous_crop = CycleStartDateDefinition.HARVEST_OF_PREVIOUS_CROP.value
|
|
409
|
+
is_harvest_previous_crop = startDateDefinition == harvest_previous_crop
|
|
410
|
+
permanent_crop = is_permanent_crop(cycle)
|
|
411
|
+
|
|
412
|
+
return (
|
|
413
|
+
any(
|
|
414
|
+
[
|
|
415
|
+
not is_crop,
|
|
416
|
+
permanent_crop,
|
|
417
|
+
is_harvest_previous_crop,
|
|
418
|
+
cycleDuration is None,
|
|
419
|
+
siteDuration is None,
|
|
420
|
+
]
|
|
421
|
+
)
|
|
422
|
+
or cycleDuration != siteDuration
|
|
423
|
+
or {
|
|
424
|
+
"level": "error",
|
|
425
|
+
"dataPath": ".siteDuration",
|
|
426
|
+
"message": "should not be equal to cycleDuration for crop",
|
|
427
|
+
"params": {
|
|
428
|
+
"current": startDateDefinition,
|
|
429
|
+
"expected": harvest_previous_crop,
|
|
430
|
+
},
|
|
431
|
+
}
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def validate_siteDuration(cycle: dict):
|
|
436
|
+
"""
|
|
437
|
+
Validate `siteDuration` with `otherSites`
|
|
438
|
+
|
|
439
|
+
Run multiple validation when `otherSites` is specified:
|
|
440
|
+
- If there is a single site, then `siteDuration` == `cycleDuration`.
|
|
441
|
+
- If there is more than one site, then `siteDuration` != `cycleDuration`.
|
|
442
|
+
- If there is more than one site, then `sum(siteDuration, otherSitesDuration)` == `cycleDuration`.
|
|
443
|
+
- There must be as many `otherSites` as `otherSitesDuration`.
|
|
444
|
+
"""
|
|
445
|
+
cycleDuration = cycle.get("cycleDuration")
|
|
446
|
+
siteDuration = cycle.get("siteDuration")
|
|
447
|
+
has_multiple_sites = len(cycle.get("otherSites", [])) > 0
|
|
448
|
+
return (
|
|
449
|
+
cycleDuration is None
|
|
450
|
+
or siteDuration is None
|
|
451
|
+
or has_multiple_sites
|
|
452
|
+
or siteDuration <= cycleDuration
|
|
453
|
+
or {
|
|
454
|
+
"level": "error",
|
|
455
|
+
"dataPath": ".siteDuration",
|
|
456
|
+
"message": "must be less than or equal to cycleDuration",
|
|
457
|
+
}
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def validate_durations(cycle: dict):
|
|
462
|
+
"""
|
|
463
|
+
Validate `otherSitesDuration` and `otherSitesArea`
|
|
464
|
+
|
|
465
|
+
This validation will encourage the user to add the following fields, for a `relative` `functionalUnit` Cycle:
|
|
466
|
+
- `siteDuration`
|
|
467
|
+
- `siteArea`
|
|
468
|
+
- `otherSitesDuration` (when `otherSites` is set)
|
|
469
|
+
- `otherSitesArea` (when `otherSites` is set)
|
|
470
|
+
"""
|
|
471
|
+
is_relative = cycle.get("functionalUnit") == CycleFunctionalUnit.RELATIVE.value
|
|
472
|
+
siteDuration = cycle.get("siteDuration")
|
|
473
|
+
siteArea = cycle.get("siteArea")
|
|
474
|
+
|
|
475
|
+
otherSites = cycle.get("otherSites", [])
|
|
476
|
+
otherSitesDuration = cycle.get("otherSitesDuration", [])
|
|
477
|
+
otherSitesArea = cycle.get("otherSitesArea", [])
|
|
478
|
+
|
|
479
|
+
missing_fields = (
|
|
480
|
+
[]
|
|
481
|
+
if not is_relative
|
|
482
|
+
else (
|
|
483
|
+
[
|
|
484
|
+
"siteDuration" if siteDuration is None else None,
|
|
485
|
+
"siteArea" if siteArea is None else None,
|
|
486
|
+
]
|
|
487
|
+
+ non_empty_list(
|
|
488
|
+
[
|
|
489
|
+
(
|
|
490
|
+
"otherSitesDuration"
|
|
491
|
+
if any(
|
|
492
|
+
[
|
|
493
|
+
len(otherSitesDuration) <= index
|
|
494
|
+
or otherSitesDuration[index] is None
|
|
495
|
+
for index, value in enumerate(otherSites)
|
|
496
|
+
]
|
|
497
|
+
)
|
|
498
|
+
else None
|
|
499
|
+
),
|
|
500
|
+
(
|
|
501
|
+
"otherSitesArea"
|
|
502
|
+
if any(
|
|
503
|
+
[
|
|
504
|
+
len(otherSitesArea) <= index
|
|
505
|
+
or otherSitesArea[index] is None
|
|
506
|
+
for index, value in enumerate(otherSites)
|
|
507
|
+
]
|
|
508
|
+
)
|
|
509
|
+
else None
|
|
510
|
+
),
|
|
511
|
+
]
|
|
512
|
+
if len(otherSites) > 0
|
|
513
|
+
else []
|
|
514
|
+
)
|
|
515
|
+
)
|
|
516
|
+
)
|
|
517
|
+
return not missing_fields or {
|
|
518
|
+
"level": "warning",
|
|
519
|
+
"dataPath": "",
|
|
520
|
+
"message": "should add the fields for a relative cycle",
|
|
521
|
+
"params": {"expected": missing_fields},
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def _product_cover_crop(product: dict):
|
|
526
|
+
is_cover_crop = get_lookup_value(product.get("term", {}), "possibleCoverCrop")
|
|
527
|
+
return not (not is_cover_crop) # convert numpy boolean to boolean
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def validate_possibleCoverCrop(cycle: dict):
|
|
531
|
+
"""
|
|
532
|
+
Validate using crop as a cover crop
|
|
533
|
+
|
|
534
|
+
This validation prevents using a `crop` Product, set as a "cover crop" (Practice), that can not be a crover crop.
|
|
535
|
+
To fix this error, the Product or the Practice needs to be adjusted.
|
|
536
|
+
This uses the lookup `possibleCoverCrop` on the Product to check if the Product can be a cover crop.
|
|
537
|
+
"""
|
|
538
|
+
cover_crop = find_term_match(cycle.get("practices", []), "coverCrop", None)
|
|
539
|
+
cover_crop_value = cover_crop.get("value", []) if cover_crop else None
|
|
540
|
+
has_cover_crop = cover_crop_value is not None and (
|
|
541
|
+
len(cover_crop_value) == 0
|
|
542
|
+
or (cover_crop_value[0] != 0 and str(cover_crop_value[0]).lower() != "false")
|
|
543
|
+
)
|
|
544
|
+
invalid_product = next(
|
|
545
|
+
(p for p in cycle.get("products", []) if not _product_cover_crop(p)), None
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
return (
|
|
549
|
+
not has_cover_crop
|
|
550
|
+
or invalid_product is None
|
|
551
|
+
or {
|
|
552
|
+
"level": "error",
|
|
553
|
+
"dataPath": "",
|
|
554
|
+
"message": "cover crop cycle contains non cover crop product",
|
|
555
|
+
}
|
|
556
|
+
)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def validate_set_treatment(cycle: dict, source: dict):
|
|
560
|
+
"""
|
|
561
|
+
Validate using `treatment` with `experimentDesign`
|
|
562
|
+
|
|
563
|
+
When `experimentDesign` is used, this will encourage the user to set `treatment` as well.
|
|
564
|
+
"""
|
|
565
|
+
key = "treatment"
|
|
566
|
+
has_experimentDesign = "experimentDesign" in source
|
|
567
|
+
has_treatment = key in cycle
|
|
568
|
+
return (
|
|
569
|
+
not has_experimentDesign
|
|
570
|
+
or has_treatment
|
|
571
|
+
or {
|
|
572
|
+
"level": "warning",
|
|
573
|
+
"dataPath": f".{key}",
|
|
574
|
+
"message": "should specify a treatment when experimentDesign is specified",
|
|
575
|
+
}
|
|
576
|
+
)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def validate_products_animals(cycle: dict):
|
|
580
|
+
"""
|
|
581
|
+
Validate using both `liveAnimal` and `animalProduct` products
|
|
582
|
+
|
|
583
|
+
This validation will show a warning when both `liveAnimal` and `animalProduct` are added to the Cycle.
|
|
584
|
+
"""
|
|
585
|
+
products = cycle.get("products", [])
|
|
586
|
+
has_liveAnimal = len(filter_list_term_type(products, TermTermType.LIVEANIMAL)) > 0
|
|
587
|
+
has_animalProduct = (
|
|
588
|
+
len(filter_list_term_type(products, TermTermType.ANIMALPRODUCT)) > 0
|
|
589
|
+
)
|
|
590
|
+
return not all([has_liveAnimal, has_animalProduct]) or {
|
|
591
|
+
"level": "warning",
|
|
592
|
+
"dataPath": ".products",
|
|
593
|
+
"message": "should not specify both liveAnimal and animalProduct",
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
def validate_stocking_density(cycle: dict, site: dict, list_key: str = "practices"):
|
|
598
|
+
"""
|
|
599
|
+
Validate `Stocking density`
|
|
600
|
+
|
|
601
|
+
Incite users to add the practice "Stocking density" when:
|
|
602
|
+
- the `functionalUnit` is `relative`;
|
|
603
|
+
- the Cycle occurs on `permanent pasture`;
|
|
604
|
+
- the Cycle contains a list of `animals` as `liveAnimal` or `animalProduct`.
|
|
605
|
+
"""
|
|
606
|
+
term_id = "stockingDensityPermanentPastureAverage"
|
|
607
|
+
is_relative = cycle.get("functionalUnit") == CycleFunctionalUnit.RELATIVE.value
|
|
608
|
+
is_permanent_pasture = site.get("siteType") == SiteSiteType.PERMANENT_PASTURE.value
|
|
609
|
+
products = cycle.get("products", [])
|
|
610
|
+
has_animals = any(
|
|
611
|
+
[
|
|
612
|
+
len(
|
|
613
|
+
filter_list_term_type(
|
|
614
|
+
products, [TermTermType.LIVEANIMAL, TermTermType.ANIMALPRODUCT]
|
|
615
|
+
)
|
|
616
|
+
)
|
|
617
|
+
> 0,
|
|
618
|
+
len(cycle.get("animals", [])) > 0,
|
|
619
|
+
]
|
|
620
|
+
)
|
|
621
|
+
has_practice = find_term_match(cycle.get(list_key, []), term_id, None) is not None
|
|
622
|
+
return (
|
|
623
|
+
not all([is_relative, is_permanent_pasture, has_animals])
|
|
624
|
+
or has_practice
|
|
625
|
+
or {
|
|
626
|
+
"level": "warning",
|
|
627
|
+
"dataPath": f".{list_key}",
|
|
628
|
+
"message": "should add the term stockingDensityPermanentPastureAverage",
|
|
629
|
+
"params": {"expected": term_id},
|
|
630
|
+
}
|
|
631
|
+
)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
def _filter_same_cycle(cycle: dict):
|
|
635
|
+
def filter(impact_assessment: dict):
|
|
636
|
+
ia_cycle = impact_assessment.get("cycle", {})
|
|
637
|
+
return any(
|
|
638
|
+
[
|
|
639
|
+
ia_cycle.get("id") and ia_cycle.get("id") == cycle.get("id"),
|
|
640
|
+
ia_cycle.get("@id") and ia_cycle.get("@id") == cycle.get("@id"),
|
|
641
|
+
]
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
return filter
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def _should_have_linked_impact_assessment(product: dict):
|
|
648
|
+
term = product.get("term", {})
|
|
649
|
+
should_generate_ia = get_lookup_value(term, "generateImpactAssessment")
|
|
650
|
+
return all(
|
|
651
|
+
[
|
|
652
|
+
list_sum(product.get("value", [])) > 0,
|
|
653
|
+
product.get("economicValueShare", 1) != 0,
|
|
654
|
+
product.get("price", 1) != 0,
|
|
655
|
+
product.get("revenue", 1) != 0,
|
|
656
|
+
str(should_generate_ia).lower() != "false",
|
|
657
|
+
]
|
|
658
|
+
)
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def validate_linked_impact_assessment(cycle: dict, node_map: dict = {}):
|
|
662
|
+
"""
|
|
663
|
+
Validate that every product that should have an ImpactAssessment, actually has one.
|
|
664
|
+
|
|
665
|
+
In some cases, the same product could be added multiple times with different unique properties, in which case
|
|
666
|
+
an ImpactAssessment should exist for each.
|
|
667
|
+
|
|
668
|
+
Note: on HESTIA, Impact Assessments are automatically generated during upload.
|
|
669
|
+
"""
|
|
670
|
+
uploaded_impact_assessments = node_map.get(
|
|
671
|
+
NodeType.IMPACTASSESSMENT.value, {}
|
|
672
|
+
).values()
|
|
673
|
+
related_impact_assessments = list(
|
|
674
|
+
filter(_filter_same_cycle(cycle), uploaded_impact_assessments)
|
|
675
|
+
)
|
|
676
|
+
|
|
677
|
+
def validate(values: tuple):
|
|
678
|
+
index, product = values
|
|
679
|
+
same_products = [
|
|
680
|
+
v
|
|
681
|
+
for v in related_impact_assessments
|
|
682
|
+
if is_same_product(product, v.get("product", {}))
|
|
683
|
+
]
|
|
684
|
+
return len(same_products) == 1 or (
|
|
685
|
+
{
|
|
686
|
+
"level": "error",
|
|
687
|
+
"dataPath": f".products[{index}].term",
|
|
688
|
+
"message": "multiple ImpactAssessment are associated with this Product",
|
|
689
|
+
"params": {
|
|
690
|
+
"product": product.get("term", {}),
|
|
691
|
+
"node": {"type": "Cycle", "id": cycle.get("id", cycle.get("@id"))},
|
|
692
|
+
},
|
|
693
|
+
}
|
|
694
|
+
if len(same_products) > 1
|
|
695
|
+
else {
|
|
696
|
+
"level": "error",
|
|
697
|
+
"dataPath": f".products[{index}].term",
|
|
698
|
+
"message": "no ImpactAssessment are associated with this Product",
|
|
699
|
+
"params": {
|
|
700
|
+
"product": product.get("term", {}),
|
|
701
|
+
"node": {"type": "Cycle", "id": cycle.get("id", cycle.get("@id"))},
|
|
702
|
+
},
|
|
703
|
+
}
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
products = enumerate(cycle.get("products", []))
|
|
707
|
+
products = [
|
|
708
|
+
(index, product)
|
|
709
|
+
for index, product in products
|
|
710
|
+
if _should_have_linked_impact_assessment(product)
|
|
711
|
+
]
|
|
712
|
+
return _filter_list_errors(flatten(map(validate, products)))
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
def _allowed_animal_ids(term: dict):
|
|
716
|
+
value = get_lookup_value(term, "allowedAnimalProductTermIds")
|
|
717
|
+
return (value or "").split(";")
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def validate_animal_product_mapping(cycle: dict):
|
|
721
|
+
"""
|
|
722
|
+
Validate mapping between the `liveAnimal` in the `Animal` blank nodes, and the Cycle `Product` as `animalProduct`.
|
|
723
|
+
|
|
724
|
+
This validation makes sure that the `liveAnimal` added as `Animal` have a corresponding `animalProduct` in
|
|
725
|
+
the Cycle `products`.
|
|
726
|
+
"""
|
|
727
|
+
live_animals = filter_list_term_type(
|
|
728
|
+
cycle.get("animals", []), TermTermType.LIVEANIMAL
|
|
729
|
+
)
|
|
730
|
+
allowed_term_ids = sorted(
|
|
731
|
+
list(
|
|
732
|
+
set(
|
|
733
|
+
non_empty_list(
|
|
734
|
+
flatten(
|
|
735
|
+
[_allowed_animal_ids(v.get("term", {})) for v in live_animals]
|
|
736
|
+
)
|
|
737
|
+
)
|
|
738
|
+
)
|
|
739
|
+
)
|
|
740
|
+
)
|
|
741
|
+
|
|
742
|
+
def validate(values: tuple):
|
|
743
|
+
index, product = values
|
|
744
|
+
term = product.get("term", {})
|
|
745
|
+
term_id = term.get("@id")
|
|
746
|
+
is_animal_product = term.get("termType") == TermTermType.ANIMALPRODUCT.value
|
|
747
|
+
return (
|
|
748
|
+
not is_animal_product
|
|
749
|
+
or term_id in allowed_term_ids
|
|
750
|
+
or {
|
|
751
|
+
"level": "error",
|
|
752
|
+
"dataPath": f".products[{index}].term",
|
|
753
|
+
"message": "is not an allowed animalProduct",
|
|
754
|
+
"params": {"expected": allowed_term_ids},
|
|
755
|
+
}
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
products = enumerate(cycle.get("products", []))
|
|
759
|
+
return (
|
|
760
|
+
_filter_list_errors(flatten(map(validate, products)))
|
|
761
|
+
if allowed_term_ids
|
|
762
|
+
else True
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
def _is_substrate_practice(practice: dict):
|
|
767
|
+
term_id = practice.get("term", {}).get("@id")
|
|
768
|
+
return "substrate" in term_id.lower()
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
def validate_requires_substrate(cycle: dict, site: dict):
|
|
772
|
+
"""
|
|
773
|
+
Validate substrate inputs
|
|
774
|
+
|
|
775
|
+
This validation ensures that the `substrate` Inputs are added to the Cycle
|
|
776
|
+
when the `siteType` = `glass or high accessible cover`, and a "substrate" practice has been set.
|
|
777
|
+
"""
|
|
778
|
+
site_type = site.get("siteType")
|
|
779
|
+
substrate_practice = next(
|
|
780
|
+
(p for p in cycle.get("practices", []) if _is_substrate_practice(p)), None
|
|
781
|
+
)
|
|
782
|
+
return not site_type == SiteSiteType.GLASS_OR_HIGH_ACCESSIBLE_COVER.value or (
|
|
783
|
+
not substrate_practice
|
|
784
|
+
or len(filter_list_term_type(cycle.get("inputs", []), TermTermType.SUBSTRATE))
|
|
785
|
+
> 0
|
|
786
|
+
or {
|
|
787
|
+
"level": "error",
|
|
788
|
+
"dataPath": ".inputs",
|
|
789
|
+
"message": "must add substrate inputs",
|
|
790
|
+
"params": {"term": substrate_practice.get("term")},
|
|
791
|
+
}
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
def validate_maximum_cycleDuration(cycle: dict):
|
|
796
|
+
"""
|
|
797
|
+
Validate the cycleDuration
|
|
798
|
+
|
|
799
|
+
This validation verifies the `cycleDuration` provided in the Cycle is not greater than our `maximumCycleDuration`
|
|
800
|
+
lookup.
|
|
801
|
+
"""
|
|
802
|
+
cycleDuration = cycle.get("cycleDuration")
|
|
803
|
+
startDate = cycle.get("startDate")
|
|
804
|
+
endDate = cycle.get("endDate")
|
|
805
|
+
use_dates = all([not cycleDuration, endDate, startDate])
|
|
806
|
+
duration = diff_in_days(startDate, endDate) if use_dates else cycleDuration
|
|
807
|
+
|
|
808
|
+
product = (find_primary_product(cycle) or {}).get("term", {})
|
|
809
|
+
is_crop = product.get("termType") == TermTermType.CROP.value
|
|
810
|
+
max_cycleDuration = get_lookup_value(product, "maximumCycleDuration")
|
|
811
|
+
return (
|
|
812
|
+
not duration
|
|
813
|
+
or not is_crop
|
|
814
|
+
or not max_cycleDuration
|
|
815
|
+
or duration <= int(max_cycleDuration) * 1.05
|
|
816
|
+
or {
|
|
817
|
+
"level": "error",
|
|
818
|
+
"dataPath": ".startDate" if use_dates else ".cycleDuration",
|
|
819
|
+
"message": "must be below maximum cycleDuration",
|
|
820
|
+
"params": {
|
|
821
|
+
"comparison": "<=",
|
|
822
|
+
"limit": int(max_cycleDuration),
|
|
823
|
+
"exclusive": False,
|
|
824
|
+
"current": duration,
|
|
825
|
+
},
|
|
826
|
+
}
|
|
827
|
+
)
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
def validate_riceGrainInHuskFlooded_minimum_cycleDuration(cycle: dict, site: dict):
|
|
831
|
+
"""
|
|
832
|
+
Validate "Rice, grain (in husk), flooded" has a plausible `cycleDuration`
|
|
833
|
+
|
|
834
|
+
Using the lookup `Rice_croppingDuration_days_min`, this validation will make sure that the `cycleDuration` specified
|
|
835
|
+
in the Cycle is not below this minimum duration.
|
|
836
|
+
|
|
837
|
+
Note: the minimum duration depends on the `country` specified on the Site.
|
|
838
|
+
"""
|
|
839
|
+
cycleDuration = cycle.get("cycleDuration")
|
|
840
|
+
country_id = site.get("country", {}).get("@id")
|
|
841
|
+
product = find_primary_product(cycle) or {}
|
|
842
|
+
product_term_id = product.get("term", {}).get("@id")
|
|
843
|
+
check_value = all(
|
|
844
|
+
[
|
|
845
|
+
site.get("siteType") == SiteSiteType.CROPLAND.value,
|
|
846
|
+
product_term_id == "riceGrainInHuskFlooded",
|
|
847
|
+
]
|
|
848
|
+
)
|
|
849
|
+
min_cycleDuration = (
|
|
850
|
+
safe_parse_float(
|
|
851
|
+
get_table_value(
|
|
852
|
+
download_lookup("region-ch4ef-IPCC2019.csv") if product else None,
|
|
853
|
+
"term.id",
|
|
854
|
+
country_id,
|
|
855
|
+
"Rice_croppingDuration_days_min",
|
|
856
|
+
),
|
|
857
|
+
default=0,
|
|
858
|
+
)
|
|
859
|
+
if check_value
|
|
860
|
+
else 0
|
|
861
|
+
)
|
|
862
|
+
return (
|
|
863
|
+
min_cycleDuration == 0
|
|
864
|
+
or not cycleDuration
|
|
865
|
+
or cycleDuration >= int(min_cycleDuration)
|
|
866
|
+
or {
|
|
867
|
+
"level": "warning",
|
|
868
|
+
"dataPath": ".cycleDuration",
|
|
869
|
+
"message": "should be more than the cropping duration",
|
|
870
|
+
"params": {"expected": int(min_cycleDuration), "current": cycleDuration},
|
|
871
|
+
}
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
def validate_cycle(cycle: dict, node_map: dict = {}):
|
|
876
|
+
site = find_linked_node(node_map, cycle.get("site", {}))
|
|
877
|
+
source = find_linked_node(node_map, cycle.get("defaultSource", {}))
|
|
878
|
+
other_sites = non_empty_list(
|
|
879
|
+
[find_linked_node(node_map, s) for s in cycle.get("otherSites", [])]
|
|
880
|
+
)
|
|
881
|
+
is_aggregated = cycle.get("aggregated", False)
|
|
882
|
+
cycle = run_models(cycle, CYCLE_MODELS_PRE_RUN) if _RUN_CYCLE_MODELS else cycle
|
|
883
|
+
return flatten(
|
|
884
|
+
[
|
|
885
|
+
validate_cycle_dates(cycle),
|
|
886
|
+
validate_date_lt_today(cycle, "startDate"),
|
|
887
|
+
validate_date_lt_today(cycle, "endDate"),
|
|
888
|
+
validate_linked_source_privacy(cycle, "defaultSource", node_map),
|
|
889
|
+
validate_private_has_source(cycle, "defaultSource"),
|
|
890
|
+
(
|
|
891
|
+
validate_cycleDuration(cycle)
|
|
892
|
+
if _should_validate_cycleDuration(cycle)
|
|
893
|
+
else True
|
|
894
|
+
),
|
|
895
|
+
validate_completeness(cycle, site, other_sites),
|
|
896
|
+
is_aggregated
|
|
897
|
+
or not _VALIDATE_COMPLETENESS_AREAS
|
|
898
|
+
or validate_completeness_blank_nodes(cycle, site),
|
|
899
|
+
validate_crop_siteDuration(cycle),
|
|
900
|
+
validate_siteDuration(cycle),
|
|
901
|
+
validate_durations(cycle),
|
|
902
|
+
validate_possibleCoverCrop(cycle),
|
|
903
|
+
validate_products_animals(cycle),
|
|
904
|
+
validate_set_treatment(cycle, source) if source else True,
|
|
905
|
+
(
|
|
906
|
+
validate_functionalUnit_not_1_ha(cycle, site, other_sites)
|
|
907
|
+
if site
|
|
908
|
+
else True
|
|
909
|
+
),
|
|
910
|
+
validate_stocking_density(cycle, site) if site else True,
|
|
911
|
+
validate_animalFeed_requires_isAnimalFeed(cycle, site) if site else True,
|
|
912
|
+
validate_requires_substrate(cycle, site) if site else True,
|
|
913
|
+
(
|
|
914
|
+
validate_riceGrainInHuskFlooded_minimum_cycleDuration(cycle, site)
|
|
915
|
+
if site
|
|
916
|
+
else True
|
|
917
|
+
),
|
|
918
|
+
validate_animal_product_mapping(cycle),
|
|
919
|
+
validate_duplicated_feed_inputs(cycle),
|
|
920
|
+
is_aggregated or validate_maximum_cycleDuration(cycle),
|
|
921
|
+
validate_nested_existing_node(cycle, "site"),
|
|
922
|
+
validate_nested_existing_node(cycle, "otherSites"),
|
|
923
|
+
not is_aggregated or validate_linked_impactAssessment(cycle, "inputs"),
|
|
924
|
+
not is_aggregated or validate_id(cycle),
|
|
925
|
+
]
|
|
926
|
+
) + flatten(
|
|
927
|
+
(
|
|
928
|
+
[
|
|
929
|
+
is_aggregated or validate_list_model(cycle, "emissions"),
|
|
930
|
+
validate_list_dates(cycle, "emissions"),
|
|
931
|
+
validate_list_dates_after(
|
|
932
|
+
cycle, "startDate", "emissions", ["startDate", "endDate"]
|
|
933
|
+
),
|
|
934
|
+
validate_list_dates_format(cycle, "emissions"),
|
|
935
|
+
validate_list_min_below_max(cycle, "emissions"),
|
|
936
|
+
validate_list_value_between_min_max(cycle, "emissions"),
|
|
937
|
+
validate_list_term_percent(cycle, "emissions"),
|
|
938
|
+
validate_list_dates_length(cycle, "emissions"),
|
|
939
|
+
validate_list_date_lt_today(
|
|
940
|
+
cycle, "emissions", ["startDate", "endDate"]
|
|
941
|
+
),
|
|
942
|
+
validate_properties(cycle, "emissions"),
|
|
943
|
+
validate_linked_terms(cycle, "emissions", "inputs", "inputs", True),
|
|
944
|
+
validate_linked_terms(
|
|
945
|
+
cycle, "emissions", "transformation", "transformations", True
|
|
946
|
+
),
|
|
947
|
+
validate_method_not_relevant(cycle, "emissions"),
|
|
948
|
+
validate_methodTier_not_relevant(cycle, "emissions"),
|
|
949
|
+
validate_methodTier_background(cycle, "emissions"),
|
|
950
|
+
validate_other_model(cycle, "emissions"),
|
|
951
|
+
]
|
|
952
|
+
if len(cycle.get("emissions", [])) > 0
|
|
953
|
+
else []
|
|
954
|
+
)
|
|
955
|
+
+ (
|
|
956
|
+
[
|
|
957
|
+
validate_list_country_region(cycle, "inputs"),
|
|
958
|
+
validate_list_dates(cycle, "inputs"),
|
|
959
|
+
validate_list_dates_after(
|
|
960
|
+
cycle, "startDate", "inputs", ["startDate", "endDate", "dates"]
|
|
961
|
+
),
|
|
962
|
+
validate_list_dates_format(cycle, "inputs"),
|
|
963
|
+
validate_list_dates_length(cycle, "inputs"),
|
|
964
|
+
validate_list_date_lt_today(cycle, "inputs", ["startDate", "endDate"]),
|
|
965
|
+
validate_list_min_below_max(cycle, "inputs"),
|
|
966
|
+
validate_list_value_between_min_max(cycle, "inputs"),
|
|
967
|
+
validate_list_min_max_lookup(cycle, "inputs", "value"),
|
|
968
|
+
validate_list_min_max_lookup(cycle, "inputs", "min"),
|
|
969
|
+
validate_list_min_max_lookup(cycle, "inputs", "max"),
|
|
970
|
+
validate_list_term_percent(cycle, "inputs"),
|
|
971
|
+
validate_list_sum_100_percent(cycle, "inputs"),
|
|
972
|
+
validate_properties(cycle, "inputs"),
|
|
973
|
+
validate_volatileSolidsContent(cycle, "inputs"),
|
|
974
|
+
validate_must_include_id(cycle["inputs"]),
|
|
975
|
+
validate_input_country(cycle, "inputs"),
|
|
976
|
+
validate_related_impacts(cycle, "inputs", node_map),
|
|
977
|
+
(
|
|
978
|
+
validate_input_distribution_value(cycle, site, "inputs")
|
|
979
|
+
if site
|
|
980
|
+
else True
|
|
981
|
+
),
|
|
982
|
+
validate_list_model_config(cycle, "inputs", INPUTS_MODEL_CONFIG),
|
|
983
|
+
validate_duplicated_term_units(
|
|
984
|
+
cycle, "inputs", DUPLICATED_TERM_UNITS_TERM_TYPES
|
|
985
|
+
),
|
|
986
|
+
validate_saplings(cycle, "inputs"),
|
|
987
|
+
validate_input_is_product(cycle, "inputs"),
|
|
988
|
+
]
|
|
989
|
+
if len(cycle.get("inputs", [])) > 0
|
|
990
|
+
else []
|
|
991
|
+
)
|
|
992
|
+
+ (
|
|
993
|
+
[
|
|
994
|
+
# skip validation for aggregated Cycle as we only auto-generate IAs
|
|
995
|
+
is_aggregated
|
|
996
|
+
or not _VALIDATE_LINKED_IA
|
|
997
|
+
or validate_linked_impact_assessment(cycle, node_map),
|
|
998
|
+
validate_list_dates(cycle, "products"),
|
|
999
|
+
validate_list_dates_after(
|
|
1000
|
+
cycle, "startDate", "products", ["startDate", "endDate", "dates"]
|
|
1001
|
+
),
|
|
1002
|
+
validate_list_dates_format(cycle, "products"),
|
|
1003
|
+
validate_list_dates_length(cycle, "products"),
|
|
1004
|
+
validate_list_date_lt_today(
|
|
1005
|
+
cycle, "products", ["startDate", "endDate"]
|
|
1006
|
+
),
|
|
1007
|
+
validate_list_min_below_max(cycle, "products"),
|
|
1008
|
+
validate_list_value_between_min_max(cycle, "products"),
|
|
1009
|
+
validate_list_term_percent(cycle, "products"),
|
|
1010
|
+
validate_list_sum_100_percent(cycle, "products"),
|
|
1011
|
+
validate_properties(cycle, "products"),
|
|
1012
|
+
validate_economicValueShare(cycle.get("products")),
|
|
1013
|
+
validate_sum_aboveGroundCropResidue(cycle.get("products")),
|
|
1014
|
+
validate_value_empty(cycle.get("products")),
|
|
1015
|
+
validate_value_0(cycle.get("products")),
|
|
1016
|
+
validate_product_primary(cycle.get("products")),
|
|
1017
|
+
validate_volatileSolidsContent(cycle, "products"),
|
|
1018
|
+
validate_volatileSolidsContent(cycle, "products"),
|
|
1019
|
+
validate_crop_residue_complete(cycle, site) if site else True,
|
|
1020
|
+
validate_crop_residue_incomplete(cycle, site) if site else True,
|
|
1021
|
+
validate_list_model_config(cycle, "products", PRODUCTS_MODEL_CONFIG),
|
|
1022
|
+
validate_excreta_product(cycle, "products"),
|
|
1023
|
+
validate_product_ha_functional_unit_ha(cycle, "products"),
|
|
1024
|
+
validate_product_yield(cycle, site, "products") if site else True,
|
|
1025
|
+
validate_has_animals(cycle),
|
|
1026
|
+
validate_duplicated_term_units(
|
|
1027
|
+
cycle, "products", DUPLICATED_TERM_UNITS_TERM_TYPES
|
|
1028
|
+
),
|
|
1029
|
+
]
|
|
1030
|
+
if len(cycle.get("products", [])) > 0
|
|
1031
|
+
else []
|
|
1032
|
+
)
|
|
1033
|
+
+ (
|
|
1034
|
+
[
|
|
1035
|
+
validate_list_dates(cycle, "practices"),
|
|
1036
|
+
validate_list_dates_after(
|
|
1037
|
+
cycle, "startDate", "practices", ["startDate", "endDate", "dates"]
|
|
1038
|
+
),
|
|
1039
|
+
validate_list_dates_format(cycle, "practices"),
|
|
1040
|
+
validate_list_date_lt_today(
|
|
1041
|
+
cycle, "practices", ["startDate", "endDate"]
|
|
1042
|
+
),
|
|
1043
|
+
validate_list_min_below_max(cycle, "practices"),
|
|
1044
|
+
validate_list_value_between_min_max(cycle, "practices"),
|
|
1045
|
+
validate_list_term_percent(cycle, "practices"),
|
|
1046
|
+
validate_list_sum_100_percent(cycle, "practices"),
|
|
1047
|
+
validate_list_percent_requires_value(
|
|
1048
|
+
cycle,
|
|
1049
|
+
"practices",
|
|
1050
|
+
PRACTICE_SUM_100_TERM_TYPES + PRACTICE_SUM_100_MAX_TERM_TYPES,
|
|
1051
|
+
),
|
|
1052
|
+
validate_list_valueType(cycle, "practices"),
|
|
1053
|
+
validate_properties(cycle, "practices"),
|
|
1054
|
+
validate_defaultValue(cycle, "practices"),
|
|
1055
|
+
validate_longFallowDuration(cycle.get("practices", [])),
|
|
1056
|
+
validate_volatileSolidsContent(cycle, "practices"),
|
|
1057
|
+
validate_list_duplicate_values(
|
|
1058
|
+
cycle,
|
|
1059
|
+
"practices",
|
|
1060
|
+
"term.termType",
|
|
1061
|
+
TermTermType.EXCRETAMANAGEMENT.value,
|
|
1062
|
+
),
|
|
1063
|
+
validate_excretaManagement(cycle, cycle.get("practices", [])),
|
|
1064
|
+
validate_no_tillage(cycle.get("practices", [])),
|
|
1065
|
+
validate_tillage_values(cycle.get("practices", [])),
|
|
1066
|
+
(
|
|
1067
|
+
validate_tillage_site_type(cycle.get("practices", []), site)
|
|
1068
|
+
if site
|
|
1069
|
+
else True
|
|
1070
|
+
),
|
|
1071
|
+
validate_liveAnimal_system(cycle),
|
|
1072
|
+
validate_pastureGrass_key_termType(cycle, "practices"),
|
|
1073
|
+
validate_pastureGrass_key_value(cycle, "practices"),
|
|
1074
|
+
validate_has_pastureGrass(cycle, site, "practices") if site else True,
|
|
1075
|
+
validate_waterRegime_rice_products(cycle),
|
|
1076
|
+
validate_croppingDuration_riceGrainInHuskFlooded(cycle),
|
|
1077
|
+
validate_permanent_crop_productive_phase(cycle, "practices"),
|
|
1078
|
+
validate_primaryPercent(cycle, site, "practices") if site else True,
|
|
1079
|
+
(
|
|
1080
|
+
validate_processing_operation(cycle, site, "practices")
|
|
1081
|
+
if site
|
|
1082
|
+
else True
|
|
1083
|
+
),
|
|
1084
|
+
(
|
|
1085
|
+
validate_landCover_match_products(cycle, site, "practices")
|
|
1086
|
+
if site
|
|
1087
|
+
else True
|
|
1088
|
+
),
|
|
1089
|
+
(
|
|
1090
|
+
validate_practices_management(cycle, site, "practices")
|
|
1091
|
+
if site
|
|
1092
|
+
else True
|
|
1093
|
+
),
|
|
1094
|
+
validate_irrigated_complete_has_inputs(cycle),
|
|
1095
|
+
]
|
|
1096
|
+
)
|
|
1097
|
+
+ (
|
|
1098
|
+
[
|
|
1099
|
+
validate_volatileSolidsContent(cycle, "animals"),
|
|
1100
|
+
validate_properties(cycle, "animals"),
|
|
1101
|
+
validate_has_pregnancyRateTotal(cycle),
|
|
1102
|
+
]
|
|
1103
|
+
if len(cycle.get("animals", [])) > 0
|
|
1104
|
+
else []
|
|
1105
|
+
)
|
|
1106
|
+
+ (
|
|
1107
|
+
[
|
|
1108
|
+
validate_list_dates(cycle, "transformations"),
|
|
1109
|
+
validate_list_dates_after(
|
|
1110
|
+
cycle, "startDate", "transformations", ["startDate", "endDate"]
|
|
1111
|
+
),
|
|
1112
|
+
validate_list_dates_format(cycle, "transformations"),
|
|
1113
|
+
validate_list_date_lt_today(
|
|
1114
|
+
cycle, "transformations", ["startDate", "endDate"]
|
|
1115
|
+
),
|
|
1116
|
+
validate_previous_transformation(cycle, "transformations"),
|
|
1117
|
+
validate_transformation_excretaManagement(cycle, "transformations"),
|
|
1118
|
+
validate_linked_emission(cycle, "transformations"),
|
|
1119
|
+
]
|
|
1120
|
+
if len(cycle.get("transformations", [])) > 0
|
|
1121
|
+
else []
|
|
1122
|
+
)
|
|
1123
|
+
)
|