@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.
Files changed (410) hide show
  1. package/.coveragerc +14 -0
  2. package/.dockerignore +19 -0
  3. package/.eslintignore +17 -0
  4. package/.eslintrc.js +11 -0
  5. package/.flake8 +5 -0
  6. package/.gitlab/issue_templates/new validation.md +82 -0
  7. package/.gitlab-ci.yml +216 -0
  8. package/.readthedocs.yml +24 -0
  9. package/CODEOWNERS +11 -0
  10. package/Dockerfile +13 -0
  11. package/LICENSE +21 -0
  12. package/MANIFEST.in +2 -0
  13. package/bin/hestia-validate-data +80 -0
  14. package/build_mocking.py +14 -0
  15. package/commitlint.config.js +1 -0
  16. package/docs/Makefile +20 -0
  17. package/docs/_static/styles.css +4 -0
  18. package/docs/_templates/custom-class-template.rst +34 -0
  19. package/docs/_templates/custom-module-template.rst +66 -0
  20. package/docs/_templates/layout.html +4 -0
  21. package/docs/conf.py +74 -0
  22. package/docs/index.rst +42 -0
  23. package/docs/make.bat +35 -0
  24. package/docs/requirements.txt +13 -0
  25. package/envs/.develop.env +1 -0
  26. package/envs/.master.env +1 -0
  27. package/guide-assets/.gitkeep +0 -0
  28. package/hestia_earth/validation/README.md +5 -0
  29. package/hestia_earth/validation/__init__.py +32 -0
  30. package/hestia_earth/validation/distribution.py +22 -0
  31. package/hestia_earth/validation/gee.py +162 -0
  32. package/hestia_earth/validation/log.py +44 -0
  33. package/hestia_earth/validation/models.py +141 -0
  34. package/hestia_earth/validation/preload_requests.py +61 -0
  35. package/hestia_earth/validation/terms.py +88 -0
  36. package/hestia_earth/validation/utils.py +444 -0
  37. package/hestia_earth/validation/validators/__init__.py +141 -0
  38. package/hestia_earth/validation/validators/aggregated_cycle.py +32 -0
  39. package/hestia_earth/validation/validators/aggregated_shared.py +37 -0
  40. package/hestia_earth/validation/validators/animal.py +88 -0
  41. package/hestia_earth/validation/validators/completeness.py +252 -0
  42. package/hestia_earth/validation/validators/cycle.py +1123 -0
  43. package/hestia_earth/validation/validators/distribution.py +86 -0
  44. package/hestia_earth/validation/validators/emission.py +109 -0
  45. package/hestia_earth/validation/validators/impact_assessment.py +138 -0
  46. package/hestia_earth/validation/validators/indicator.py +154 -0
  47. package/hestia_earth/validation/validators/infrastructure.py +25 -0
  48. package/hestia_earth/validation/validators/input.py +268 -0
  49. package/hestia_earth/validation/validators/management.py +131 -0
  50. package/hestia_earth/validation/validators/measurement.py +368 -0
  51. package/hestia_earth/validation/validators/organisation.py +43 -0
  52. package/hestia_earth/validation/validators/practice.py +590 -0
  53. package/hestia_earth/validation/validators/product.py +263 -0
  54. package/hestia_earth/validation/validators/property.py +266 -0
  55. package/hestia_earth/validation/validators/shared.py +940 -0
  56. package/hestia_earth/validation/validators/site.py +312 -0
  57. package/hestia_earth/validation/validators/source.py +20 -0
  58. package/hestia_earth/validation/validators/transformation.py +250 -0
  59. package/hestia_earth/validation/version.py +1 -0
  60. package/layer/build.sh +34 -0
  61. package/layer/deploy.sh +18 -0
  62. package/package.json +59 -0
  63. package/release.sh +11 -0
  64. package/requirements-ci.txt +6 -0
  65. package/requirements-test.txt +4 -0
  66. package/requirements.txt +2 -0
  67. package/run-docker-test.sh +7 -0
  68. package/run-docker.sh +9 -0
  69. package/run.py +99 -0
  70. package/scripts/build_docs.py +283 -0
  71. package/scripts/build_validation_list.py +160 -0
  72. package/scripts/guide-create-branch.sh +15 -0
  73. package/scripts/update-package-version.js +28 -0
  74. package/search-results.json +384 -0
  75. package/setup.cfg +2 -0
  76. package/setup.py +35 -0
  77. package/src/index.ts +1 -0
  78. package/src/validations.ts +22 -0
  79. package/src/version.ts +1 -0
  80. package/tests/Dockerfile +13 -0
  81. package/tests/__init__.py +3 -0
  82. package/tests/fixtures/aggregated/cycle/inputs-impactAssessment/invalid-no-impactAssessment.json +64 -0
  83. package/tests/fixtures/aggregated/cycle/inputs-impactAssessment/invalid-world.json +69 -0
  84. package/tests/fixtures/aggregated/cycle/inputs-impactAssessment/valid.json +69 -0
  85. package/tests/fixtures/animal/duplicated-input-cycle/invalid.json +98 -0
  86. package/tests/fixtures/animal/duplicated-input-cycle/valid.json +91 -0
  87. package/tests/fixtures/animal/pregnancyRateTotal/invalid.json +49 -0
  88. package/tests/fixtures/animal/pregnancyRateTotal/valid.json +60 -0
  89. package/tests/fixtures/animal/required/invalid.json +59 -0
  90. package/tests/fixtures/animal/required/valid.json +72 -0
  91. package/tests/fixtures/completeness/all-values/warning.json +22 -0
  92. package/tests/fixtures/completeness/animalPopulation/invalid.json +58 -0
  93. package/tests/fixtures/completeness/animalPopulation/valid-animals.json +71 -0
  94. package/tests/fixtures/completeness/animalPopulation/valid-incomplete.json +58 -0
  95. package/tests/fixtures/completeness/animalPopulation/valid-no-liveAnimals.json +37 -0
  96. package/tests/fixtures/completeness/blank-nodes/agri-food processor-invalid.json +52 -0
  97. package/tests/fixtures/completeness/blank-nodes/invalid.json +124 -0
  98. package/tests/fixtures/completeness/blank-nodes/valid.json +128 -0
  99. package/tests/fixtures/completeness/cropland/site.json +16 -0
  100. package/tests/fixtures/completeness/cropland/valid.json +22 -0
  101. package/tests/fixtures/completeness/cropland/warning.json +22 -0
  102. package/tests/fixtures/completeness/freshForage/error-animals.json +63 -0
  103. package/tests/fixtures/completeness/freshForage/error-products.json +65 -0
  104. package/tests/fixtures/completeness/freshForage/valid-animal-inputs.json +63 -0
  105. package/tests/fixtures/completeness/freshForage/valid-animals.json +63 -0
  106. package/tests/fixtures/completeness/freshForage/valid-not-grazing-liveAnimal.json +55 -0
  107. package/tests/fixtures/completeness/freshForage/valid-not-liveAnimal.json +47 -0
  108. package/tests/fixtures/completeness/freshForage/valid-products.json +68 -0
  109. package/tests/fixtures/completeness/ingredient/invalid-agri-food-processor.json +37 -0
  110. package/tests/fixtures/completeness/ingredient/invalid.json +49 -0
  111. package/tests/fixtures/completeness/ingredient/valid-agri-food-processor-complete.json +49 -0
  112. package/tests/fixtures/completeness/ingredient/valid-agri-food-processor-incomplete.json +37 -0
  113. package/tests/fixtures/completeness/ingredient/valid.json +49 -0
  114. package/tests/fixtures/completeness/material/error.json +49 -0
  115. package/tests/fixtures/completeness/material/valid-fuel-material.json +60 -0
  116. package/tests/fixtures/completeness/material/valid-incomplete.json +36 -0
  117. package/tests/fixtures/completeness/material/valid-no-fuel.json +36 -0
  118. package/tests/fixtures/completeness/valid.json +22 -0
  119. package/tests/fixtures/cycle/aboveGroundCropResidue/invalid.json +76 -0
  120. package/tests/fixtures/cycle/aboveGroundCropResidue/valid.json +76 -0
  121. package/tests/fixtures/cycle/aggregated-valid.json +102 -0
  122. package/tests/fixtures/cycle/coverCrop/invalid.json +64 -0
  123. package/tests/fixtures/cycle/coverCrop/valid-not-coverCrop.json +54 -0
  124. package/tests/fixtures/cycle/coverCrop/valid.json +64 -0
  125. package/tests/fixtures/cycle/cropResidue/complete/invalid.json +56 -0
  126. package/tests/fixtures/cycle/cropResidue/complete/valid.json +82 -0
  127. package/tests/fixtures/cycle/cropResidue/incomplete/invalid.json +42 -0
  128. package/tests/fixtures/cycle/cropResidue/incomplete/valid.json +56 -0
  129. package/tests/fixtures/cycle/dates/invalid-emissions.json +70 -0
  130. package/tests/fixtures/cycle/liveAnimal-animalProduct-mapping/invalid.json +63 -0
  131. package/tests/fixtures/cycle/liveAnimal-animalProduct-mapping/valid.json +63 -0
  132. package/tests/fixtures/cycle/maximumCycleDuration/invalid-dates-year-only.json +48 -0
  133. package/tests/fixtures/cycle/maximumCycleDuration/invalid-dates.json +48 -0
  134. package/tests/fixtures/cycle/maximumCycleDuration/invalid.json +48 -0
  135. package/tests/fixtures/cycle/maximumCycleDuration/valid-dates-year-only.json +48 -0
  136. package/tests/fixtures/cycle/maximumCycleDuration/valid-dates.json +48 -0
  137. package/tests/fixtures/cycle/maximumCycleDuration/valid.json +48 -0
  138. package/tests/fixtures/cycle/otherSites/cycleDuration/invalid.json +52 -0
  139. package/tests/fixtures/cycle/otherSites/cycleDuration/valid-no-siteDuration.json +40 -0
  140. package/tests/fixtures/cycle/otherSites/cycleDuration/valid.json +52 -0
  141. package/tests/fixtures/cycle/practices/stockingDensityPermanentPastureAverage/invalid.json +56 -0
  142. package/tests/fixtures/cycle/practices/stockingDensityPermanentPastureAverage/valid.json +65 -0
  143. package/tests/fixtures/cycle/primary-product-as-input/invalid.json +59 -0
  144. package/tests/fixtures/cycle/primary-product-as-input/valid.json +48 -0
  145. package/tests/fixtures/cycle/product-linked-ia/cycle.json +66 -0
  146. package/tests/fixtures/cycle/product-linked-ia/invalid-multiple.json +58 -0
  147. package/tests/fixtures/cycle/product-linked-ia/valid.json +57 -0
  148. package/tests/fixtures/cycle/products/animals/invalid.json +69 -0
  149. package/tests/fixtures/cycle/products/animals/valid.json +58 -0
  150. package/tests/fixtures/cycle/riceGrainInHuskFlooded-minimumCycleDuration/invalid.json +53 -0
  151. package/tests/fixtures/cycle/riceGrainInHuskFlooded-minimumCycleDuration/valid.json +53 -0
  152. package/tests/fixtures/cycle/siteDuration/crop/invalid.json +53 -0
  153. package/tests/fixtures/cycle/siteDuration/crop/valid-different-duration.json +53 -0
  154. package/tests/fixtures/cycle/siteDuration/crop/valid-same-duration.json +53 -0
  155. package/tests/fixtures/cycle/siteDuration/invalid.json +41 -0
  156. package/tests/fixtures/cycle/siteDuration/valid-no-siteDuration.json +40 -0
  157. package/tests/fixtures/cycle/siteDuration/valid-otherSites.json +48 -0
  158. package/tests/fixtures/cycle/siteDuration/valid.json +45 -0
  159. package/tests/fixtures/cycle/substrate/required/invalid.json +50 -0
  160. package/tests/fixtures/cycle/substrate/required/valid.json +60 -0
  161. package/tests/fixtures/cycle/valid.json +343 -0
  162. package/tests/fixtures/emission/linked-terms/inputs/invalid.json +78 -0
  163. package/tests/fixtures/emission/linked-terms/inputs/valid.json +106 -0
  164. package/tests/fixtures/emission/linked-terms/transformation/error.json +104 -0
  165. package/tests/fixtures/emission/linked-terms/transformation/valid.json +107 -0
  166. package/tests/fixtures/emission/linked-terms/transformation/warning.json +76 -0
  167. package/tests/fixtures/emission/methodTier-background/invalid.json +60 -0
  168. package/tests/fixtures/emission/methodTier-background/valid.json +60 -0
  169. package/tests/fixtures/emission/not-relevant/invalid.json +71 -0
  170. package/tests/fixtures/emission/not-relevant/valid.json +95 -0
  171. package/tests/fixtures/emission/not-relevant-methodTier/invalid.json +70 -0
  172. package/tests/fixtures/emission/not-relevant-methodTier/valid.json +95 -0
  173. package/tests/fixtures/impactAssessment/aggregated-valid.json +43 -0
  174. package/tests/fixtures/impactAssessment/cycle-contains-product/invalid.json +34 -0
  175. package/tests/fixtures/impactAssessment/cycle-contains-product/valid.json +34 -0
  176. package/tests/fixtures/impactAssessment/cycle-endDate/invalid.json +26 -0
  177. package/tests/fixtures/impactAssessment/cycle-endDate/valid.json +26 -0
  178. package/tests/fixtures/impactAssessment/valid.json +93 -0
  179. package/tests/fixtures/indicator/characterisedIndicator-methodModel/invalid.json +52 -0
  180. package/tests/fixtures/indicator/characterisedIndicator-methodModel/valid.json +52 -0
  181. package/tests/fixtures/indicator/ionisingCompounds/invalid.json +23 -0
  182. package/tests/fixtures/indicator/ionisingCompounds/valid.json +23 -0
  183. package/tests/fixtures/indicator/landTransformation/invalid-grouped.json +257 -0
  184. package/tests/fixtures/indicator/landTransformation/invalid.json +100 -0
  185. package/tests/fixtures/indicator/landTransformation/valid-grouped-full.json +507 -0
  186. package/tests/fixtures/indicator/landTransformation/valid-grouped.json +507 -0
  187. package/tests/fixtures/indicator/landTransformation/valid.json +100 -0
  188. package/tests/fixtures/infrastructure/lifespan/invalid.json +26 -0
  189. package/tests/fixtures/infrastructure/lifespan/valid.json +45 -0
  190. package/tests/fixtures/input/animalFeed-fate/invalid.json +103 -0
  191. package/tests/fixtures/input/animalFeed-fate/valid.json +90 -0
  192. package/tests/fixtures/input/country/invalid.json +64 -0
  193. package/tests/fixtures/input/country/valid.json +64 -0
  194. package/tests/fixtures/input/distribution/animalHousing.json +103 -0
  195. package/tests/fixtures/input/distribution/complete/invalid.json +177 -0
  196. package/tests/fixtures/input/distribution/complete/valid.json +163 -0
  197. package/tests/fixtures/input/distribution/incomplete/valid.json +139 -0
  198. package/tests/fixtures/input/impactAssessment/invalid.json +99 -0
  199. package/tests/fixtures/input/impactAssessment/valid.json +89 -0
  200. package/tests/fixtures/input/input-as-product/invalid.json +57 -0
  201. package/tests/fixtures/input/input-as-product/valid.json +59 -0
  202. package/tests/fixtures/input/mustIncludeId/invalid.json +13 -0
  203. package/tests/fixtures/input/mustIncludeId/valid-multiple-ids.json +31 -0
  204. package/tests/fixtures/input/mustIncludeId/valid.json +22 -0
  205. package/tests/fixtures/input/saplings/invalid.json +58 -0
  206. package/tests/fixtures/input/saplings/valid-no-saplings.json +58 -0
  207. package/tests/fixtures/input/saplings/valid-not-plantation.json +58 -0
  208. package/tests/fixtures/input/saplings/valid.json +58 -0
  209. package/tests/fixtures/integration/distribution/product-yield-invalid.json +54 -0
  210. package/tests/fixtures/management/cycle-overlap/cycles.json +39 -0
  211. package/tests/fixtures/management/cycle-overlap/invalid.json +26 -0
  212. package/tests/fixtures/management/cycle-overlap/valid.json +26 -0
  213. package/tests/fixtures/management/exists/invalid.json +13 -0
  214. package/tests/fixtures/management/exists/valid.json +25 -0
  215. package/tests/fixtures/management/fallow-dates/invalid.json +24 -0
  216. package/tests/fixtures/management/fallow-dates/valid.json +24 -0
  217. package/tests/fixtures/management/termType/invalid-cropland.json +35 -0
  218. package/tests/fixtures/management/termType/invalid-permanent-pasture.json +25 -0
  219. package/tests/fixtures/management/termType/valid-cropland.json +55 -0
  220. package/tests/fixtures/management/termType/valid-no-management.json +13 -0
  221. package/tests/fixtures/management/termType/valid-permanent-pasture.json +35 -0
  222. package/tests/fixtures/measurement/depths/invalid.json +44 -0
  223. package/tests/fixtures/measurement/depths/valid.json +50 -0
  224. package/tests/fixtures/measurement/models/valid.json +33 -0
  225. package/tests/fixtures/measurement/models/warning-no-value.json +30 -0
  226. package/tests/fixtures/measurement/models/warning.json +33 -0
  227. package/tests/fixtures/measurement/pond-measurements/invalid.json +11 -0
  228. package/tests/fixtures/measurement/pond-measurements/valid.json +23 -0
  229. package/tests/fixtures/measurement/required-depths/error.json +71 -0
  230. package/tests/fixtures/measurement/required-depths/valid.json +126 -0
  231. package/tests/fixtures/measurement/required-depths/warning.json +29 -0
  232. package/tests/fixtures/measurement/soilTexture/missing-texture-value.json +227 -0
  233. package/tests/fixtures/measurement/soilTexture/percent-invalid.json +110 -0
  234. package/tests/fixtures/measurement/soilTexture/percent-missing-value.json +43 -0
  235. package/tests/fixtures/measurement/soilTexture/percent-valid.json +110 -0
  236. package/tests/fixtures/measurement/startDate-endDate-required/invalid.json +32 -0
  237. package/tests/fixtures/measurement/startDate-endDate-required/valid.json +46 -0
  238. package/tests/fixtures/measurement/unique/invalid.json +28 -0
  239. package/tests/fixtures/measurement/unique/valid.json +16 -0
  240. package/tests/fixtures/measurement/value-length/invalid.json +46 -0
  241. package/tests/fixtures/measurement/value-length/valid.json +44 -0
  242. package/tests/fixtures/measurement/water-salinity/invalid.json +33 -0
  243. package/tests/fixtures/measurement/water-salinity/valid-brakish.json +40 -0
  244. package/tests/fixtures/measurement/water-salinity/valid.json +33 -0
  245. package/tests/fixtures/organisation/valid.json +26 -0
  246. package/tests/fixtures/practice/croppingDuration/riceGrainInHuskFlooded/invalid.json +63 -0
  247. package/tests/fixtures/practice/croppingDuration/riceGrainInHuskFlooded/valid.json +63 -0
  248. package/tests/fixtures/practice/defaultValue/invalid.json +12 -0
  249. package/tests/fixtures/practice/defaultValue/valid.json +15 -0
  250. package/tests/fixtures/practice/excretaManagement/invalid.json +50 -0
  251. package/tests/fixtures/practice/excretaManagement/valid.json +60 -0
  252. package/tests/fixtures/practice/irrigated-complete/invalid.json +47 -0
  253. package/tests/fixtures/practice/irrigated-complete/valid-incomplete.json +47 -0
  254. package/tests/fixtures/practice/irrigated-complete/valid.json +60 -0
  255. package/tests/fixtures/practice/landCover-products/invalid.json +58 -0
  256. package/tests/fixtures/practice/landCover-products/valid-coverCrop.json +69 -0
  257. package/tests/fixtures/practice/landCover-products/valid.json +58 -0
  258. package/tests/fixtures/practice/liveAnimal-system/invalid.json +58 -0
  259. package/tests/fixtures/practice/liveAnimal-system/valid.json +69 -0
  260. package/tests/fixtures/practice/longFallowDuration/invalid.json +20 -0
  261. package/tests/fixtures/practice/longFallowDuration/valid.json +20 -0
  262. package/tests/fixtures/practice/noTillage/invalid.json +23 -0
  263. package/tests/fixtures/practice/noTillage/valid-value-not-100.json +23 -0
  264. package/tests/fixtures/practice/noTillage/valid.json +21 -0
  265. package/tests/fixtures/practice/pastureGrass/key-termType/invalid.json +16 -0
  266. package/tests/fixtures/practice/pastureGrass/key-termType/valid.json +16 -0
  267. package/tests/fixtures/practice/pastureGrass/key-value/invalid-numbers.json +67 -0
  268. package/tests/fixtures/practice/pastureGrass/key-value/invalid.json +67 -0
  269. package/tests/fixtures/practice/pastureGrass/key-value/valid.json +67 -0
  270. package/tests/fixtures/practice/pastureGrass/permanent-pasture/invalid.json +37 -0
  271. package/tests/fixtures/practice/pastureGrass/permanent-pasture/valid.json +47 -0
  272. package/tests/fixtures/practice/primaryPercent/invalid.json +49 -0
  273. package/tests/fixtures/practice/primaryPercent/valid.json +49 -0
  274. package/tests/fixtures/practice/processingOperation/invalid-no-primary.json +48 -0
  275. package/tests/fixtures/practice/processingOperation/invalid.json +49 -0
  276. package/tests/fixtures/practice/processingOperation/valid-cropland.json +37 -0
  277. package/tests/fixtures/practice/processingOperation/valid.json +49 -0
  278. package/tests/fixtures/practice/productivePhasePermanentCrops/invalid.json +48 -0
  279. package/tests/fixtures/practice/productivePhasePermanentCrops/valid-0-value.json +58 -0
  280. package/tests/fixtures/practice/productivePhasePermanentCrops/valid-no-value.json +47 -0
  281. package/tests/fixtures/practice/productivePhasePermanentCrops/valid.json +48 -0
  282. package/tests/fixtures/practice/site-management/invalid.json +75 -0
  283. package/tests/fixtures/practice/site-management/valid.json +75 -0
  284. package/tests/fixtures/practice/tillage-siteType/valid.json +51 -0
  285. package/tests/fixtures/practice/tillage-siteType/warning.json +42 -0
  286. package/tests/fixtures/practice/tillage-values/invalid-fullTillage.json +61 -0
  287. package/tests/fixtures/practice/tillage-values/invalid-noTillage.json +61 -0
  288. package/tests/fixtures/practice/tillage-values/valid.json +61 -0
  289. package/tests/fixtures/practice/waterRegime/rice/invalid.json +59 -0
  290. package/tests/fixtures/practice/waterRegime/rice/valid-0-value.json +59 -0
  291. package/tests/fixtures/practice/waterRegime/rice/valid.json +58 -0
  292. package/tests/fixtures/product/economicValueShare/invalid.json +31 -0
  293. package/tests/fixtures/product/economicValueShare/valid.json +22 -0
  294. package/tests/fixtures/product/excreta/invalid.json +62 -0
  295. package/tests/fixtures/product/excreta/valid.json +62 -0
  296. package/tests/fixtures/product/excreta/warning.json +53 -0
  297. package/tests/fixtures/product/excreta/with-system/invalid.json +79 -0
  298. package/tests/fixtures/product/excreta/with-system/valid.json +88 -0
  299. package/tests/fixtures/product/excreta/with-system/warning.json +70 -0
  300. package/tests/fixtures/product/fu_ha/invalid.json +49 -0
  301. package/tests/fixtures/product/fu_ha/valid.json +49 -0
  302. package/tests/fixtures/product/primary/invalid.json +22 -0
  303. package/tests/fixtures/product/primary/valid.json +22 -0
  304. package/tests/fixtures/product/value/valid.json +26 -0
  305. package/tests/fixtures/product/value/value-0/error.json +40 -0
  306. package/tests/fixtures/product/value/value-empty/warning.json +23 -0
  307. package/tests/fixtures/product/yield/invalid.json +54 -0
  308. package/tests/fixtures/product/yield/no-value.json +75 -0
  309. package/tests/fixtures/product/yield/valid.json +54 -0
  310. package/tests/fixtures/property/default-value/valid-allowed-exception.json +61 -0
  311. package/tests/fixtures/property/default-value/valid.json +61 -0
  312. package/tests/fixtures/property/default-value/warning.json +61 -0
  313. package/tests/fixtures/property/termType/invalid.json +60 -0
  314. package/tests/fixtures/property/termType/valid.json +60 -0
  315. package/tests/fixtures/property/value-min-max/invalid.json +77 -0
  316. package/tests/fixtures/property/value-min-max/valid-skip-maximum.json +57 -0
  317. package/tests/fixtures/property/value-min-max/valid.json +78 -0
  318. package/tests/fixtures/property/valueType/invalid.json +79 -0
  319. package/tests/fixtures/property/valueType/valid.json +79 -0
  320. package/tests/fixtures/property/volatileSolidsContent/invalid.json +99 -0
  321. package/tests/fixtures/property/volatileSolidsContent/valid.json +99 -0
  322. package/tests/fixtures/shared/coordinates/invalid.json +18 -0
  323. package/tests/fixtures/shared/coordinates/valid.json +18 -0
  324. package/tests/fixtures/shared/data-duplicates/valid.json +113 -0
  325. package/tests/fixtures/shared/data-duplicates/warning.json +172 -0
  326. package/tests/fixtures/shared/duplicated-term-units/invalid-animalProduct.json +61 -0
  327. package/tests/fixtures/shared/duplicated-term-units/invalid-organicFertiliser.json +61 -0
  328. package/tests/fixtures/shared/duplicated-term-units/valid.json +49 -0
  329. package/tests/fixtures/shared/list-country-region/invalid.json +54 -0
  330. package/tests/fixtures/shared/list-country-region/valid.json +54 -0
  331. package/tests/fixtures/shared/list-percent-value/invalid.json +49 -0
  332. package/tests/fixtures/shared/list-percent-value/valid.json +52 -0
  333. package/tests/fixtures/shared/list-valueType/invalid.json +49 -0
  334. package/tests/fixtures/shared/list-valueType/valid.json +49 -0
  335. package/tests/fixtures/shared/list-values-sum-100/management/with-properties/valid.json +91 -0
  336. package/tests/fixtures/shared/list-values-sum-100/measurements/missing-soil.json +46 -0
  337. package/tests/fixtures/shared/list-values-sum-100/measurements/no-depth-high-value.json +63 -0
  338. package/tests/fixtures/shared/list-values-sum-100/measurements/no-depth-valid.json +40 -0
  339. package/tests/fixtures/shared/list-values-sum-100/measurements/with-depth-high-value.json +71 -0
  340. package/tests/fixtures/shared/list-values-sum-100/practices/total-100.json +61 -0
  341. package/tests/fixtures/shared/list-values-sum-100/practices/total-110.json +61 -0
  342. package/tests/fixtures/shared/list-values-sum-100/practices/total-90.json +61 -0
  343. package/tests/fixtures/shared/min-max/value-above.json +31 -0
  344. package/tests/fixtures/shared/min-max/value-below.json +31 -0
  345. package/tests/fixtures/shared/min-max/value-valid.json +45 -0
  346. package/tests/fixtures/shared/model/emissions/invalid.json +102 -0
  347. package/tests/fixtures/shared/model/emissions/valid-variable-tolerance.json +180 -0
  348. package/tests/fixtures/shared/model/emissions/valid.json +102 -0
  349. package/tests/fixtures/shared/model/impacts/invalid.json +75 -0
  350. package/tests/fixtures/shared/model/impacts/valid.json +75 -0
  351. package/tests/fixtures/shared/model/inputs/valid-no-value.json +84 -0
  352. package/tests/fixtures/shared/model/inputs/valid.json +87 -0
  353. package/tests/fixtures/shared/model/inputs/warning.json +87 -0
  354. package/tests/fixtures/shared/model/products/valid-no-value.json +88 -0
  355. package/tests/fixtures/shared/model/products/valid.json +91 -0
  356. package/tests/fixtures/shared/model/products/warning.json +91 -0
  357. package/tests/fixtures/shared/otherModel/invalid.json +69 -0
  358. package/tests/fixtures/shared/otherModel/valid.json +70 -0
  359. package/tests/fixtures/shared/properties-duplicate-values/invalid.json +61 -0
  360. package/tests/fixtures/shared/properties-duplicate-values/valid.json +57 -0
  361. package/tests/fixtures/shared/properties-same-length/invalid.json +62 -0
  362. package/tests/fixtures/shared/properties-same-length/valid.json +52 -0
  363. package/tests/fixtures/shared/unit-percent/invalid.json +34 -0
  364. package/tests/fixtures/shared/unit-percent/valid.json +60 -0
  365. package/tests/fixtures/shared/unit-percent/warning.json +52 -0
  366. package/tests/fixtures/site/cycles-linked-ia/invalid.json +129 -0
  367. package/tests/fixtures/site/cycles-linked-ia/valid.json +129 -0
  368. package/tests/fixtures/site/valid.json +138 -0
  369. package/tests/fixtures/source/valid.json +19 -0
  370. package/tests/fixtures/transformation/excretaManagement/invalid.json +47 -0
  371. package/tests/fixtures/transformation/excretaManagement/valid.json +59 -0
  372. package/tests/fixtures/transformation/inputs-products/invalid.json +43 -0
  373. package/tests/fixtures/transformation/inputs-products/valid.json +43 -0
  374. package/tests/fixtures/transformation/linked-emission/invalid.json +101 -0
  375. package/tests/fixtures/transformation/linked-emission/valid.json +107 -0
  376. package/tests/fixtures/transformation/previousTransformationId/invalid-no-previous.json +127 -0
  377. package/tests/fixtures/transformation/previousTransformationId/invalid-previous-input.json +100 -0
  378. package/tests/fixtures/transformation/previousTransformationId/invalid-product-input.json +106 -0
  379. package/tests/fixtures/transformation/previousTransformationId/invalid-wrong-order.json +136 -0
  380. package/tests/fixtures/transformation/previousTransformationId/valid.json +171 -0
  381. package/tests/integration/__init__.py +0 -0
  382. package/tests/integration/test_product.py +17 -0
  383. package/tests/test_gee.py +10 -0
  384. package/tests/test_utils.py +36 -0
  385. package/tests/test_validation.py +11 -0
  386. package/tests/utils.py +28 -0
  387. package/tests/validators/__init__.py +0 -0
  388. package/tests/validators/test_aggregated_cycle.py +44 -0
  389. package/tests/validators/test_aggregated_shared.py +63 -0
  390. package/tests/validators/test_animal.py +72 -0
  391. package/tests/validators/test_completeness.py +337 -0
  392. package/tests/validators/test_cycle.py +600 -0
  393. package/tests/validators/test_emission.py +170 -0
  394. package/tests/validators/test_impact_assessment.py +80 -0
  395. package/tests/validators/test_indicator.py +120 -0
  396. package/tests/validators/test_infrastructure.py +26 -0
  397. package/tests/validators/test_input.py +434 -0
  398. package/tests/validators/test_management.py +177 -0
  399. package/tests/validators/test_measurement.py +317 -0
  400. package/tests/validators/test_organisation.py +32 -0
  401. package/tests/validators/test_practice.py +490 -0
  402. package/tests/validators/test_product.py +291 -0
  403. package/tests/validators/test_property.py +143 -0
  404. package/tests/validators/test_shared.py +1139 -0
  405. package/tests/validators/test_site.py +151 -0
  406. package/tests/validators/test_source.py +15 -0
  407. package/tests/validators/test_transformation.py +151 -0
  408. package/tests/validators/test_validators.py +74 -0
  409. package/tsconfig.dist.json +9 -0
  410. 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
+ )