@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,590 @@
1
+ from hestia_earth.schema import SiteSiteType, TermTermType
2
+ from hestia_earth.utils.model import (
3
+ filter_list_term_type,
4
+ find_term_match,
5
+ find_primary_product,
6
+ )
7
+ from hestia_earth.utils.tools import flatten, list_sum, safe_parse_float, non_empty_list
8
+ from hestia_earth.utils.lookup import download_lookup, get_table_value
9
+ from hestia_earth.utils.blank_node import get_node_value
10
+
11
+ from hestia_earth.validation.utils import (
12
+ _filter_list_errors,
13
+ get_lookup_value,
14
+ is_permanent_crop,
15
+ blank_node_properties_group,
16
+ )
17
+ from hestia_earth.validation.terms import TERMS_QUERY, get_terms
18
+ from .shared import valid_list_sum, is_value_below
19
+
20
+
21
+ def _is_irrigated(term: dict):
22
+ def fallback():
23
+ term_id = get_lookup_value(term, "correspondingWaterRegimeTermId")
24
+ return (
25
+ _is_irrigated({"@id": term_id, "termType": TermTermType.WATERREGIME.value})
26
+ if term_id
27
+ else False
28
+ )
29
+
30
+ return not not get_lookup_value(term, "irrigated") or fallback()
31
+
32
+
33
+ def validate_defaultValue(data: dict, list_key: str = "practices"):
34
+ def validate(values: tuple):
35
+ index, practice = values
36
+ term = practice.get("term", {})
37
+ has_value = len(practice.get("value", [])) > 0
38
+ is_value_required = any([term.get("units", "").startswith("%")])
39
+ default_value = get_lookup_value(term, "defaultValue")
40
+ return (
41
+ has_value
42
+ or default_value is None
43
+ or is_value_required
44
+ or {
45
+ "level": "warning",
46
+ "dataPath": f".{list_key}[{index}]",
47
+ "message": "should specify a value when HESTIA has a default one",
48
+ "params": {"term": term, "expected": default_value},
49
+ }
50
+ )
51
+
52
+ return _filter_list_errors(
53
+ flatten(map(validate, enumerate(data.get(list_key, []))))
54
+ )
55
+
56
+
57
+ def validate_longFallowDuration(practices: list):
58
+ max_nb_years = 5
59
+ longFallowDuration = find_term_match(practices, "longFallowDuration", None)
60
+ longFallowDuration_index = (
61
+ practices.index(longFallowDuration) if longFallowDuration else 0
62
+ )
63
+ value = list_sum(longFallowDuration.get("value", [0])) if longFallowDuration else 0
64
+ rotationDuration = list_sum(
65
+ find_term_match(practices, "rotationDuration").get("value", 0)
66
+ )
67
+ return (
68
+ value == 0
69
+ or ((rotationDuration - value) / value) < max_nb_years * 365
70
+ or {
71
+ "level": "error",
72
+ "dataPath": f".practices[{longFallowDuration_index}].value",
73
+ "message": "longFallowDuration must be lower than 5 years",
74
+ }
75
+ )
76
+
77
+
78
+ def validate_waterRegime_rice_products(cycle: dict, list_key: str = "practices"):
79
+ """
80
+ Validate corresponding `waterRegime` practices with Rice products
81
+
82
+ This validation ensures that the correct `waterRegime` practice can be used with the specified Rice product.
83
+ """
84
+ all_rice_product_ids = get_terms(TERMS_QUERY.RICE)
85
+ primary_product = find_primary_product(cycle) or {}
86
+ primary_product_id = primary_product.get("term", {}).get("@id")
87
+ is_rice_product = primary_product_id in all_rice_product_ids
88
+
89
+ practice_term_type = TermTermType.WATERREGIME.value
90
+
91
+ def validate(values: tuple):
92
+ index, practice = values
93
+ term = practice.get("term", {})
94
+ term_type = term.get("termType")
95
+ has_value = list_sum(practice.get("value") or [0], 0) > 0
96
+ allowed_product_ids = (
97
+ get_lookup_value(term, "allowedRiceTermIds") or ""
98
+ ).split(";")
99
+ is_allowed = primary_product_id in allowed_product_ids
100
+ return (
101
+ term_type != practice_term_type
102
+ or not has_value
103
+ or is_allowed
104
+ or {
105
+ "level": "error",
106
+ "dataPath": f".{list_key}[{index}].term",
107
+ "message": "rice products not allowed for this water regime practice",
108
+ "params": {
109
+ "term": term,
110
+ "products": [primary_product.get("term", {})],
111
+ "expected": allowed_product_ids,
112
+ },
113
+ }
114
+ )
115
+
116
+ return not is_rice_product or _filter_list_errors(
117
+ flatten(map(validate, enumerate(cycle.get(list_key, []))))
118
+ )
119
+
120
+
121
+ def validate_croppingDuration_riceGrainInHuskFlooded(
122
+ cycle: dict, list_key: str = "practices"
123
+ ):
124
+ """
125
+ Validate "Rice, grain (in husk), flooded" cropping duration
126
+
127
+ When "Rice, grain (in husk), flooded" is used as a product, this validation will check the practice
128
+ `croppingDuration`, and make sure the value is between
129
+ `Rice_croppingDuration_days_min` and `Rice_croppingDuration_days_max` lookup values.
130
+ """
131
+ has_product = find_term_match(cycle.get("products", []), "riceGrainInHuskFlooded")
132
+
133
+ practice_id = "croppingDuration"
134
+ practice_index = (
135
+ next(
136
+ (
137
+ i
138
+ for i, p in enumerate(cycle.get(list_key, []))
139
+ if p.get("term", {}).get("@id") == practice_id
140
+ ),
141
+ -1,
142
+ )
143
+ if has_product
144
+ else -1
145
+ )
146
+
147
+ lookup = download_lookup("region-ch4ef-IPCC2019.csv")
148
+ country_id = cycle.get("site", {}).get("country", {}).get("@id")
149
+ min_value = safe_parse_float(
150
+ get_table_value(
151
+ lookup, "term.id", country_id, "Rice_croppingDuration_days_min"
152
+ ),
153
+ None,
154
+ )
155
+ max_value = safe_parse_float(
156
+ get_table_value(
157
+ lookup, "term.id", country_id, "Rice_croppingDuration_days_max"
158
+ ),
159
+ None,
160
+ )
161
+
162
+ value = (
163
+ list_sum(cycle.get(list_key, [])[practice_index].get("value", []))
164
+ if practice_index >= 0
165
+ else None
166
+ )
167
+
168
+ return (
169
+ practice_index == -1
170
+ or all([is_value_below(value, max_value), is_value_below(min_value, value)])
171
+ or {
172
+ "level": "error",
173
+ "dataPath": f".{list_key}[{practice_index}].value",
174
+ "message": "croppingDuration must be between min and max",
175
+ "params": {"min": min_value, "max": max_value},
176
+ }
177
+ )
178
+
179
+
180
+ def validate_excretaManagement(node: dict, practices: list):
181
+ has_input = (
182
+ len(filter_list_term_type(node.get("inputs", []), TermTermType.EXCRETA)) > 0
183
+ )
184
+ has_practice = (
185
+ len(filter_list_term_type(practices, TermTermType.EXCRETAMANAGEMENT)) > 0
186
+ )
187
+ return (
188
+ not has_practice
189
+ or has_input
190
+ or {
191
+ "level": "error",
192
+ "dataPath": ".practices",
193
+ "message": "an excreta input is required when using an excretaManagement practice",
194
+ }
195
+ )
196
+
197
+
198
+ NO_TILLAGE_ID = "noTillage"
199
+ FULL_TILLAGE_ID = "fullTillage"
200
+ TILLAGE_DEPTH_ID = "tillageDepth"
201
+ NB_TILLAGES_ID = "numberOfTillages"
202
+
203
+
204
+ def _practice_is_tillage(practice: dict):
205
+ term = practice.get("term", {})
206
+ term_type = practice.get("term", {}).get("termType")
207
+ return (
208
+ True
209
+ if term_type == TermTermType.OPERATION.value
210
+ and get_lookup_value(term, "isTillage")
211
+ else False
212
+ )
213
+
214
+
215
+ def validate_no_tillage(practices: list):
216
+ tillage_practices = filter_list_term_type(practices, TermTermType.TILLAGE)
217
+ no_tillage = find_term_match(tillage_practices, NO_TILLAGE_ID, None)
218
+ no_value = list_sum(no_tillage.get("value", [100]), 100) if no_tillage else 0
219
+
220
+ return _filter_list_errors(
221
+ [
222
+ {
223
+ "level": "error",
224
+ "dataPath": f".practices[{index}]",
225
+ "message": "is not allowed in combination with noTillage",
226
+ }
227
+ for index, p in enumerate(practices)
228
+ if _practice_is_tillage(p)
229
+ ]
230
+ if no_value == 100
231
+ else []
232
+ )
233
+
234
+
235
+ _TILLAGE_SITE_TYPES = [SiteSiteType.CROPLAND.value]
236
+
237
+
238
+ def validate_tillage_site_type(practices: list, site: dict):
239
+ has_tillage = len(filter_list_term_type(practices, TermTermType.TILLAGE)) > 0
240
+ site_type = site.get("siteType")
241
+ return (
242
+ site_type not in _TILLAGE_SITE_TYPES
243
+ or has_tillage
244
+ or {
245
+ "level": "warning",
246
+ "dataPath": ".practices",
247
+ "message": "should contain a tillage practice",
248
+ }
249
+ )
250
+
251
+
252
+ def validate_tillage_values(practices: list):
253
+ tillage_100_index = next(
254
+ (
255
+ index
256
+ for index in range(0, len(practices))
257
+ if all(
258
+ [
259
+ practices[index].get("term", {}).get("termType")
260
+ == TermTermType.TILLAGE.value,
261
+ list_sum(practices[index].get("value", [0])) == 100,
262
+ ]
263
+ )
264
+ ),
265
+ -1,
266
+ )
267
+ tillage_100_practice = (
268
+ practices[tillage_100_index] if tillage_100_index >= 0 else None
269
+ )
270
+ tillage_100_term = (tillage_100_practice or {}).get("term", {})
271
+
272
+ tillage_depth_practice = find_term_match(practices, TILLAGE_DEPTH_ID)
273
+ nb_tillages_practice = find_term_match(practices, NB_TILLAGES_ID)
274
+ error_message = (
275
+ (
276
+ "cannot use no tillage if depth or number of tillages is not 0"
277
+ if all(
278
+ [
279
+ tillage_100_term.get("@id") == NO_TILLAGE_ID,
280
+ any(
281
+ [
282
+ tillage_depth_practice
283
+ and list_sum(tillage_depth_practice.get("value", [0])) > 0,
284
+ nb_tillages_practice
285
+ and list_sum(nb_tillages_practice.get("value", [0])) > 0,
286
+ ]
287
+ ),
288
+ ]
289
+ )
290
+ else (
291
+ "cannot use full tillage if depth or number of tillages is 0"
292
+ if all(
293
+ [
294
+ tillage_100_term.get("@id") == FULL_TILLAGE_ID,
295
+ any(
296
+ [
297
+ tillage_depth_practice
298
+ and list_sum(tillage_depth_practice.get("value", [1]))
299
+ == 0,
300
+ nb_tillages_practice
301
+ and list_sum(nb_tillages_practice.get("value", [1]))
302
+ == 0,
303
+ ]
304
+ ),
305
+ ]
306
+ )
307
+ else None
308
+ )
309
+ )
310
+ if tillage_100_practice
311
+ else None
312
+ )
313
+ return (
314
+ {
315
+ "level": "error",
316
+ "dataPath": f".practices[{tillage_100_index}]",
317
+ "message": error_message,
318
+ }
319
+ if error_message
320
+ else True
321
+ )
322
+
323
+
324
+ def validate_liveAnimal_system(data: dict):
325
+ has_animal = (
326
+ len(
327
+ filter_list_term_type(
328
+ data.get("products", []),
329
+ [TermTermType.ANIMALPRODUCT, TermTermType.LIVEANIMAL],
330
+ )
331
+ )
332
+ > 0
333
+ )
334
+ has_system = (
335
+ len(filter_list_term_type(data.get("practices", []), TermTermType.SYSTEM)) > 0
336
+ )
337
+ return (
338
+ not has_animal
339
+ or has_system
340
+ or {
341
+ "level": "warning",
342
+ "dataPath": ".practices",
343
+ "message": "should add an animal production system",
344
+ }
345
+ )
346
+
347
+
348
+ PASTURE_GRASS_TERM_ID = "pastureGrass"
349
+
350
+
351
+ def validate_pastureGrass_key_termType(data: dict, list_key: str = "practices"):
352
+ validate_key_termType = TermTermType.LANDCOVER.value
353
+
354
+ def validate(values: tuple):
355
+ index, practice = values
356
+ term_id = practice.get("term", {}).get("@id")
357
+ key_termType = practice.get("key", {}).get("termType")
358
+ return (
359
+ term_id != PASTURE_GRASS_TERM_ID
360
+ or not key_termType
361
+ or key_termType == validate_key_termType
362
+ or {
363
+ "level": "error",
364
+ "dataPath": f".{list_key}[{index}].key",
365
+ "message": "pastureGrass key termType must be landCover",
366
+ "params": {
367
+ "value": key_termType,
368
+ "expected": validate_key_termType,
369
+ "term": practice.get("key", {}),
370
+ },
371
+ }
372
+ )
373
+
374
+ return _filter_list_errors(
375
+ flatten(map(validate, enumerate(data.get(list_key, []))))
376
+ )
377
+
378
+
379
+ def validate_pastureGrass_key_value(data: dict, list_key: str = "practices"):
380
+ practices = [
381
+ p
382
+ for p in data.get(list_key, [])
383
+ if p.get("term", {}).get("@id") == PASTURE_GRASS_TERM_ID
384
+ ]
385
+ total_value, valid_sum = valid_list_sum(practices)
386
+ return (
387
+ {
388
+ "level": "error",
389
+ "dataPath": f".{list_key}",
390
+ "message": "all values must be numbers",
391
+ }
392
+ if not valid_sum
393
+ else len(practices) == 0
394
+ or total_value == 100
395
+ or {
396
+ "level": "error",
397
+ "dataPath": f".{list_key}",
398
+ "message": "the sum of all pastureGrass values must be 100",
399
+ "params": {"expected": 100, "current": total_value},
400
+ }
401
+ )
402
+
403
+
404
+ def validate_has_pastureGrass(data: dict, site: dict, list_key: str = "practices"):
405
+ site_type = site.get("siteType")
406
+ has_practice = (
407
+ find_term_match(data.get(list_key, []), PASTURE_GRASS_TERM_ID, None) is not None
408
+ )
409
+ return (
410
+ site_type not in [SiteSiteType.PERMANENT_PASTURE.value]
411
+ or has_practice
412
+ or {
413
+ "level": "warning",
414
+ "dataPath": f".{list_key}",
415
+ "message": "should add the term pastureGrass",
416
+ }
417
+ )
418
+
419
+
420
+ def validate_permanent_crop_productive_phase(cycle: dict, list_key: str = "practices"):
421
+ practice_id = "productivePhasePermanentCrops"
422
+ permanent_crop = is_permanent_crop(cycle)
423
+ primary_product = find_primary_product(cycle) or {}
424
+ product_value = list_sum(primary_product.get("value", [-1]), default=-1)
425
+ has_practice = (
426
+ find_term_match(cycle.get(list_key, []), practice_id, None) is not None
427
+ )
428
+ return (
429
+ not permanent_crop
430
+ or product_value != 0
431
+ or has_practice
432
+ or {
433
+ "level": "error",
434
+ "dataPath": f".{list_key}",
435
+ "message": "must add the term productivePhasePermanentCrops",
436
+ }
437
+ )
438
+
439
+
440
+ _PROCESSING_SITE_TYPES = [SiteSiteType.AGRI_FOOD_PROCESSOR.value]
441
+
442
+
443
+ def _is_processing_operation(practice: dict):
444
+ return not (not get_lookup_value(practice.get("term", {}), "isProcessingOperation"))
445
+
446
+
447
+ def validate_primaryPercent(cycle: dict, site: dict, list_key: str = "practices"):
448
+ site_type = site.get("siteType")
449
+
450
+ def validate(values: tuple):
451
+ index, practice = values
452
+ return "primaryPercent" not in practice or {
453
+ "level": "error",
454
+ "dataPath": f".{list_key}[{index}]",
455
+ "message": "primaryPercent not allowed on this siteType",
456
+ "params": {"current": site_type, "expected": _PROCESSING_SITE_TYPES},
457
+ }
458
+
459
+ return site_type in _PROCESSING_SITE_TYPES or (
460
+ _filter_list_errors(map(validate, enumerate(cycle.get(list_key, []))))
461
+ )
462
+
463
+
464
+ def validate_processing_operation(cycle: dict, site: dict, list_key: str = "practices"):
465
+ operations = filter_list_term_type(cycle.get(list_key, []), TermTermType.OPERATION)
466
+ primary_processing_operations = [
467
+ v
468
+ for v in operations
469
+ if all([_is_processing_operation(v), (v.get("primaryPercent") or 0) > 0])
470
+ ]
471
+ site_type = site.get("siteType")
472
+ is_valid = any(
473
+ [
474
+ site_type not in _PROCESSING_SITE_TYPES,
475
+ len(primary_processing_operations) > 0,
476
+ ]
477
+ )
478
+ return is_valid or {
479
+ "level": "error",
480
+ "dataPath": f".{list_key}" if operations else "",
481
+ "message": "must have a primary processing operation",
482
+ }
483
+
484
+
485
+ def validate_landCover_match_products(
486
+ cycle: dict, site: dict, list_key: str = "practices"
487
+ ):
488
+ # validate that at least one `landCover` practice matches an equivalent Product
489
+ landCover_practice_ids = [
490
+ p.get("term", {}).get("@id")
491
+ for p in filter_list_term_type(
492
+ cycle.get("practices", []), TermTermType.LANDCOVER
493
+ )
494
+ # ignore any practices with a `blankNodesGroup=Cover crops`
495
+ if blank_node_properties_group(p) != "Cover crops"
496
+ ]
497
+ landCover_product_ids = non_empty_list(
498
+ [
499
+ get_lookup_value(p.get("term", {}), "landCoverTermId")
500
+ for p in cycle.get("products", [])
501
+ ]
502
+ )
503
+ is_cropland = site.get("siteType") == SiteSiteType.CROPLAND.value
504
+
505
+ return (
506
+ not is_cropland
507
+ or not landCover_practice_ids
508
+ or not landCover_product_ids
509
+ or any(
510
+ [(term_id in landCover_product_ids) for term_id in landCover_practice_ids]
511
+ )
512
+ or {
513
+ "level": "error",
514
+ "dataPath": f".{list_key}",
515
+ "message": "at least one landCover practice must match an equivalent product",
516
+ "params": {
517
+ "current": landCover_practice_ids,
518
+ "expected": landCover_product_ids,
519
+ },
520
+ }
521
+ )
522
+
523
+
524
+ def validate_practices_management(cycle: dict, site: dict, list_key: str = "practices"):
525
+ # validate that practices and management nodes, with same term and dates, have the same value
526
+ management_nodes = site.get("management", [])
527
+
528
+ def validate(values: tuple):
529
+ index, practice = values
530
+ term_id = practice.get("term", {}).get("@id")
531
+ value = get_node_value(practice)
532
+ management_node = [
533
+ v
534
+ for v in management_nodes
535
+ if all(
536
+ [
537
+ v.get("term", {}).get("@id") == term_id,
538
+ v.get("startDate") == practice.get("startDate"),
539
+ v.get("endDate") == practice.get("endDate"),
540
+ ]
541
+ )
542
+ ]
543
+ return (
544
+ len(management_node) == 0
545
+ or management_node[0].get("value") == value
546
+ or {
547
+ "level": "error",
548
+ "dataPath": f".{list_key}[{index}].value",
549
+ "message": "should match the site management node value",
550
+ "params": {
551
+ "current": value,
552
+ "expected": management_node[0].get("value"),
553
+ },
554
+ }
555
+ )
556
+
557
+ return (
558
+ _filter_list_errors(flatten(map(validate, enumerate(cycle.get(list_key, [])))))
559
+ if management_nodes
560
+ else True
561
+ )
562
+
563
+
564
+ def validate_irrigated_complete_has_inputs(cycle: dict):
565
+ is_complete = cycle.get("completeness", {}).get(TermTermType.WATER.value)
566
+ has_irrigated_practice = (
567
+ any([_is_irrigated(v.get("term", {})) for v in cycle.get("practices", [])])
568
+ if is_complete
569
+ else False
570
+ )
571
+ has_water_inputs = (
572
+ list_sum(
573
+ list(
574
+ map(
575
+ get_node_value,
576
+ filter_list_term_type(cycle.get("inputs", []), TermTermType.WATER),
577
+ )
578
+ ),
579
+ default=0,
580
+ )
581
+ > 0
582
+ if is_complete
583
+ else False
584
+ )
585
+
586
+ return any([not is_complete, not has_irrigated_practice, has_water_inputs]) or {
587
+ "level": "error",
588
+ "dataPath": ".inputs",
589
+ "message": "must contain water inputs",
590
+ }