hestia-earth-models 0.59.4__py3-none-any.whl → 0.59.6__py3-none-any.whl

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.

Potentially problematic release.


This version of hestia-earth-models might be problematic. Click here for more details.

Files changed (55) hide show
  1. hestia_earth/models/cycle/animal/milkYield.py +86 -0
  2. hestia_earth/models/cycle/endDate.py +50 -0
  3. hestia_earth/models/cycle/inorganicFertiliser.py +3 -2
  4. hestia_earth/models/cycle/liveAnimal.py +3 -0
  5. hestia_earth/models/cycle/milkYield.py +8 -3
  6. hestia_earth/models/cycle/pre_checks/__init__.py +1 -2
  7. hestia_earth/models/cycle/startDate.py +42 -0
  8. hestia_earth/models/cycle/utils.py +1 -1
  9. hestia_earth/models/faostat2018/liveweightPerHead.py +77 -41
  10. hestia_earth/models/faostat2018/product/price.py +30 -55
  11. hestia_earth/models/faostat2018/utils.py +10 -2
  12. hestia_earth/models/geospatialDatabase/potentialEvapotranspirationLongTermAnnualMean.py +2 -2
  13. hestia_earth/models/geospatialDatabase/potentialEvapotranspirationMonthly.py +9 -8
  14. hestia_earth/models/geospatialDatabase/precipitationMonthly.py +10 -8
  15. hestia_earth/models/geospatialDatabase/temperatureAnnual.py +2 -5
  16. hestia_earth/models/geospatialDatabase/temperatureLongTermAnnualMean.py +2 -3
  17. hestia_earth/models/geospatialDatabase/temperatureMonthly.py +8 -8
  18. hestia_earth/models/geospatialDatabase/utils.py +6 -1
  19. hestia_earth/models/haversineFormula/transport/distance.py +6 -3
  20. hestia_earth/models/ipcc2006/n2OToAirInorganicFertiliserIndirect.py +1 -1
  21. hestia_earth/models/ipcc2019/organicCarbonPerHa.py +89 -114
  22. hestia_earth/models/ipcc2019/pastureGrass.py +2 -1
  23. hestia_earth/models/linkedImpactAssessment/__init__.py +78 -43
  24. hestia_earth/models/mocking/search-results.json +244 -271
  25. hestia_earth/models/schmidt2007/h2SToAirWasteTreatment.py +58 -0
  26. hestia_earth/models/schmidt2007/n2OToAirWasteTreatmentDirect.py +58 -0
  27. hestia_earth/models/schmidt2007/nh3ToAirWasteTreatment.py +58 -0
  28. hestia_earth/models/site/management.py +107 -12
  29. hestia_earth/models/site/soilMeasurement.py +9 -9
  30. hestia_earth/models/utils/__init__.py +4 -1
  31. hestia_earth/models/utils/animalProduct.py +6 -4
  32. hestia_earth/models/utils/blank_node.py +6 -5
  33. hestia_earth/models/utils/product.py +9 -1
  34. hestia_earth/models/utils/term.py +0 -23
  35. hestia_earth/models/version.py +1 -1
  36. {hestia_earth_models-0.59.4.dist-info → hestia_earth_models-0.59.6.dist-info}/METADATA +1 -1
  37. {hestia_earth_models-0.59.4.dist-info → hestia_earth_models-0.59.6.dist-info}/RECORD +53 -43
  38. tests/models/cycle/animal/test_milkYield.py +43 -0
  39. tests/models/cycle/test_endDate.py +24 -0
  40. tests/models/cycle/test_startDate.py +22 -0
  41. tests/models/faostat2018/product/test_price.py +25 -45
  42. tests/models/faostat2018/test_liveweightPerHead.py +106 -42
  43. tests/models/ipcc2019/test_organicCarbonPerHa.py +12 -18
  44. tests/models/schmidt2007/test_h2SToAirWasteTreatment.py +45 -0
  45. tests/models/schmidt2007/test_n2OToAirWasteTreatmentDirect.py +45 -0
  46. tests/models/schmidt2007/test_nh3ToAirWasteTreatment.py +45 -0
  47. tests/models/site/test_management.py +24 -3
  48. tests/models/site/test_soilMeasurement.py +40 -21
  49. tests/models/utils/test_blank_node.py +71 -3
  50. tests/models/utils/test_term.py +1 -8
  51. hestia_earth/models/cycle/pre_checks/startDate.py +0 -52
  52. tests/models/cycle/pre_checks/test_startDate.py +0 -44
  53. {hestia_earth_models-0.59.4.dist-info → hestia_earth_models-0.59.6.dist-info}/LICENSE +0 -0
  54. {hestia_earth_models-0.59.4.dist-info → hestia_earth_models-0.59.6.dist-info}/WHEEL +0 -0
  55. {hestia_earth_models-0.59.4.dist-info → hestia_earth_models-0.59.6.dist-info}/top_level.txt +0 -0
@@ -10,7 +10,7 @@ from hestia_earth.models.utils import first_day_of_month, last_day_of_month
10
10
  from hestia_earth.models.utils.measurement import _new_measurement
11
11
  from hestia_earth.models.utils.source import get_source
12
12
  from hestia_earth.models.utils.site import related_years
13
- from .utils import KELVIN_0, download, has_geospatial_data, should_download
13
+ from .utils import download, has_geospatial_data, should_download
14
14
  from . import MODEL
15
15
 
16
16
  REQUIREMENTS = {
@@ -48,9 +48,6 @@ def _measurement(site: dict, value: list, dates: list):
48
48
  return measurement | get_source(site, BIBLIO_TITLE)
49
49
 
50
50
 
51
- def _to_celcius(kelvin_value: int): return kelvin_value - KELVIN_0 if kelvin_value else None
52
-
53
-
54
51
  def _download(site: dict, start_date: str, end_date: str):
55
52
  return download(
56
53
  TERM_ID,
@@ -63,6 +60,12 @@ def _download(site: dict, start_date: str, end_date: str):
63
60
  )
64
61
 
65
62
 
63
+ def _value_at(site: dict, start_date: str, end_date: str):
64
+ factor = 0.1
65
+ value = _download(site, start_date, end_date)
66
+ return (value * factor, start_date[0:7]) if value is not None else None
67
+
68
+
66
69
  def _run(site: dict, years: list):
67
70
  # fetch from first year to last
68
71
  years = range(years[0], years[-1] + 1) if len(years) > 1 else years
@@ -73,10 +76,8 @@ def _run(site: dict, years: list):
73
76
  for month in range(1, 13)
74
77
  ] for year in years
75
78
  ])
76
- values = non_empty_list([
77
- (_to_celcius(_download(site, start_date, end_date)), start_date[0:7]) for start_date, end_date in dates
78
- ])
79
- return _measurement(site, [v for v, d in values], [d for v, d in values])
79
+ values = non_empty_list([_value_at(site, start_date, end_date) for start_date, end_date in dates])
80
+ return [_measurement(site, [v for v, d in values], [d for v, d in values])] if values else []
80
81
 
81
82
 
82
83
  def run(site: dict):
@@ -10,7 +10,7 @@ from hestia_earth.models.utils import first_day_of_month, last_day_of_month
10
10
  from hestia_earth.models.utils.measurement import _new_measurement
11
11
  from hestia_earth.models.utils.source import get_source
12
12
  from hestia_earth.models.utils.site import related_years
13
- from .utils import KELVIN_0, download, has_geospatial_data, should_download
13
+ from .utils import download, has_geospatial_data, should_download
14
14
  from . import MODEL
15
15
 
16
16
  REQUIREMENTS = {
@@ -48,9 +48,6 @@ def _measurement(site: dict, value: list, dates: list):
48
48
  return measurement | get_source(site, BIBLIO_TITLE)
49
49
 
50
50
 
51
- def _to_celcius(kelvin_value: int): return kelvin_value - KELVIN_0 if kelvin_value else None
52
-
53
-
54
51
  def _download(site: dict, start_date: str, end_date: str):
55
52
  return download(
56
53
  TERM_ID,
@@ -63,6 +60,13 @@ def _download(site: dict, start_date: str, end_date: str):
63
60
  )
64
61
 
65
62
 
63
+ def _value_at(site: dict, start_date: str, end_date: str):
64
+ # collection is in meters, convert to millimeters
65
+ factor = 1000
66
+ value = _download(site, start_date, end_date)
67
+ return (value * factor, start_date[0:7]) if value is not None else None
68
+
69
+
66
70
  def _run(site: dict, years: list):
67
71
  # fetch from first year to last
68
72
  years = range(years[0], years[-1] + 1) if len(years) > 1 else years
@@ -73,10 +77,8 @@ def _run(site: dict, years: list):
73
77
  for month in range(1, 13)
74
78
  ] for year in years
75
79
  ])
76
- values = non_empty_list([
77
- (_to_celcius(_download(site, start_date, end_date)), start_date[0:7]) for start_date, end_date in dates
78
- ])
79
- return _measurement(site, [v for v, d in values], [d for v, d in values])
80
+ values = non_empty_list([_value_at(site, start_date, end_date) for start_date, end_date in dates])
81
+ return [_measurement(site, [v for v, d in values], [d for v, d in values])] if values else []
80
82
 
81
83
 
82
84
  def run(site: dict):
@@ -9,7 +9,7 @@ from hestia_earth.models.log import logRequirements, logShouldRun
9
9
  from hestia_earth.models.utils.measurement import _new_measurement
10
10
  from hestia_earth.models.utils.source import get_source
11
11
  from hestia_earth.models.utils.site import related_years
12
- from .utils import KELVIN_0, download, has_geospatial_data, should_download
12
+ from .utils import to_celcius, download, has_geospatial_data, should_download
13
13
  from . import MODEL
14
14
 
15
15
  REQUIREMENTS = {
@@ -49,9 +49,6 @@ def _measurement(site: dict, value: float, year: int):
49
49
  return measurement | get_source(site, BIBLIO_TITLE)
50
50
 
51
51
 
52
- def _to_celcius(kelvin_value: int): return kelvin_value - KELVIN_0 if kelvin_value else None
53
-
54
-
55
52
  def _download(site: dict, year: int):
56
53
  return download(
57
54
  TERM_ID,
@@ -64,7 +61,7 @@ def _download(site: dict, year: int):
64
61
 
65
62
 
66
63
  def _run(site: dict, year: int):
67
- value = _to_celcius(_download(site, year))
64
+ value = to_celcius(_download(site, year))
68
65
  return _measurement(site, value, year) if value else None
69
66
 
70
67
 
@@ -3,9 +3,8 @@ from hestia_earth.schema import MeasurementMethodClassification
3
3
  from hestia_earth.models.log import logRequirements, logShouldRun
4
4
  from hestia_earth.models.utils.measurement import _new_measurement
5
5
  from hestia_earth.models.utils.source import get_source
6
- from .utils import download, has_geospatial_data, should_download
6
+ from .utils import to_celcius, download, has_geospatial_data, should_download
7
7
  from . import MODEL
8
- from .temperatureAnnual import _to_celcius
9
8
 
10
9
  REQUIREMENTS = {
11
10
  "Site": {
@@ -50,7 +49,7 @@ def _measurement(site: dict, value: float):
50
49
 
51
50
 
52
51
  def _run(site: dict):
53
- value = _to_celcius(download(TERM_ID, site, EE_PARAMS))
52
+ value = to_celcius(download(TERM_ID, site, EE_PARAMS))
54
53
  return [_measurement(site, value)] if value is not None else []
55
54
 
56
55
 
@@ -10,7 +10,7 @@ from hestia_earth.models.utils import first_day_of_month, last_day_of_month
10
10
  from hestia_earth.models.utils.measurement import _new_measurement
11
11
  from hestia_earth.models.utils.source import get_source
12
12
  from hestia_earth.models.utils.site import related_years
13
- from .utils import KELVIN_0, download, has_geospatial_data, should_download
13
+ from .utils import to_celcius, download, has_geospatial_data, should_download
14
14
  from . import MODEL
15
15
 
16
16
  REQUIREMENTS = {
@@ -48,9 +48,6 @@ def _measurement(site: dict, value: list, dates: list):
48
48
  return measurement | get_source(site, BIBLIO_TITLE)
49
49
 
50
50
 
51
- def _to_celcius(kelvin_value: int): return kelvin_value - KELVIN_0 if kelvin_value else None
52
-
53
-
54
51
  def _download(site: dict, start_date: str, end_date: str):
55
52
  return download(
56
53
  TERM_ID,
@@ -63,6 +60,11 @@ def _download(site: dict, start_date: str, end_date: str):
63
60
  )
64
61
 
65
62
 
63
+ def _value_at(site: dict, start_date: str, end_date: str):
64
+ value = _download(site, start_date, end_date)
65
+ return (to_celcius(value), start_date[0:7]) if value is not None else None
66
+
67
+
66
68
  def _run(site: dict, years: list):
67
69
  # fetch from first year to last
68
70
  years = range(years[0], years[-1] + 1) if len(years) > 1 else years
@@ -73,10 +75,8 @@ def _run(site: dict, years: list):
73
75
  for month in range(1, 13)
74
76
  ] for year in years
75
77
  ])
76
- values = non_empty_list([
77
- (_to_celcius(_download(site, start_date, end_date)), start_date[0:7]) for start_date, end_date in dates
78
- ])
79
- return _measurement(site, [v for v, d in values], [d for v, d in values])
78
+ values = non_empty_list([_value_at(site, start_date, end_date) for start_date, end_date in dates])
79
+ return [_measurement(site, [v for v, d in values], [d for v, d in values])] if values else []
80
80
 
81
81
 
82
82
  def run(site: dict):
@@ -23,6 +23,9 @@ GEOPANDAS_COLLECTION_NAME = {
23
23
  KELVIN_0 = 273.15
24
24
 
25
25
 
26
+ def to_celcius(kelvin_value: int): return kelvin_value - KELVIN_0 if kelvin_value else None
27
+
28
+
26
29
  def use_geopandas(): return os.getenv('HEE_USE_GEOPANDAS', 'false') == 'true'
27
30
 
28
31
 
@@ -139,7 +142,9 @@ def _get_cached_data(term: str, site: dict, data: dict):
139
142
  data.get('end_date')
140
143
  ]))
141
144
  # data can be grouped by year when required
142
- value = cache.get(cache_sub_key) if cache_sub_key and cache is not None else cache
145
+ value = cache.get(cache_sub_key) if (
146
+ isinstance(cache, dict) and cache_sub_key in cache and cache is not None
147
+ ) else cache
143
148
  if value is not None:
144
149
  debugValues(site, model=MODEL, term=term, value_from_cache=value)
145
150
  return value
@@ -27,8 +27,11 @@ REQUIREMENTS = {
27
27
  }
28
28
  }
29
29
  RETURNS = {
30
- "Transport": [{
31
- "distance": ""
30
+ "Input": [{
31
+ "transport": [{
32
+ "@type": "Transport",
33
+ "distance": ""
34
+ }]
32
35
  }]
33
36
  }
34
37
  MODEL_KEY = 'distance'
@@ -98,7 +101,7 @@ def _should_run(cycle: dict):
98
101
  # can only run if the site country has centroid coordinates
99
102
  logRequirements(cycle, model=MODEL, term=None, key=MODEL_KEY,
100
103
  latitude=country.get('latitude'),
101
- longitude=country.get('latitude'),
104
+ longitude=country.get('longitude'),
102
105
  has_inputs_transport=len(inputs) > 0)
103
106
  should_run = all([country.get('latitude'), country.get('latitude'), len(inputs) > 0])
104
107
  logShouldRun(cycle, MODEL, None, should_run, key=MODEL_KEY)
@@ -55,7 +55,7 @@ def _run(cycle: dict, N_total: float):
55
55
  nh3_n=nh3_n,
56
56
  nox_n=nox_n)
57
57
  value = COEFF_NH3NOX_N2O * (
58
- sum_values([nh3_n, nox_n]) or N_total * 0.2
58
+ sum_values([nh3_n, nox_n]) or N_total * 0.1
59
59
  ) + COEFF_NO3_N2O * (
60
60
  no3_n or N_total * 0.3
61
61
  )
@@ -2,7 +2,13 @@
2
2
  The IPCC model for estimating soil organic carbon stock changes in the 0 - 30cm depth interval due to management
3
3
  changes. This model combines the Tier 1 & Tier 2 methodologies. It first tries to run Tier 2 (only on croplands
4
4
  remaining croplands). If Tier 2 cannot run, it will try to run Tier 1 (for croplands remaining croplands and for
5
- grasslands remaining grasslands). Source:
5
+ grasslands remaining grasslands).
6
+
7
+ More information on this model, including data requirements **and** recommendations, tier methodologies, and examples,
8
+ can be found in the
9
+ [Hestia SOC wiki](https://gitlab.com/hestia-earth/hestia-engine-models/-/wikis/Soil-organic-carbon-modelling).
10
+
11
+ Source:
6
12
  [IPCC 2019, Vol. 4, Chapter 10](https://www.ipcc-nggip.iges.or.jp/public/2019rf/pdf/4_Volume4/19R_V4_Ch05_Cropland.pdf).
7
13
  """
8
14
  from enum import Enum
@@ -46,7 +52,6 @@ from hestia_earth.models.utils.term import (
46
52
  get_cover_crop_property_terms,
47
53
  get_crop_residue_incorporated_or_left_on_field_terms,
48
54
  get_irrigated_terms,
49
- get_long_fallow_land_cover_terms,
50
55
  get_residue_removed_or_burnt_terms,
51
56
  get_upland_rice_crop_terms,
52
57
  get_upland_rice_land_cover_terms
@@ -610,25 +615,26 @@ A dictionary mapping IPCC soil categories to corresponding soil type and USDA so
610
615
  `"IPCC_SOIL_CATEGORY"` column.
611
616
  """
612
617
 
613
- IPCC_LAND_USE_CATEGORY_TO_SITE_TYPE = {
614
- IpccLandUseCategory.GRASSLAND: SiteSiteType.PERMANENT_PASTURE.value,
615
- IpccLandUseCategory.PERENNIAL_CROPS: SiteSiteType.CROPLAND.value,
616
- IpccLandUseCategory.PADDY_RICE_CULTIVATION: SiteSiteType.CROPLAND.value,
617
- IpccLandUseCategory.ANNUAL_CROPS_WET: SiteSiteType.CROPLAND.value,
618
- IpccLandUseCategory.ANNUAL_CROPS: SiteSiteType.CROPLAND.value,
619
- IpccLandUseCategory.SET_ASIDE: SiteSiteType.CROPLAND.value,
620
- IpccLandUseCategory.FOREST: SiteSiteType.FOREST.value,
621
- IpccLandUseCategory.NATIVE: SiteSiteType.OTHER_NATURAL_VEGETATION.value
618
+ SITE_TYPE_TO_IPCC_LAND_USE_CATEGORY = {
619
+ SiteSiteType.PERMANENT_PASTURE.value: IpccLandUseCategory.GRASSLAND,
620
+ SiteSiteType.FOREST.value: IpccLandUseCategory.FOREST,
621
+ SiteSiteType.OTHER_NATURAL_VEGETATION.value: IpccLandUseCategory.NATIVE
622
622
  }
623
623
  """
624
- A dictionary mapping IPCC land use categories to corresponding site types.
624
+ A dictionary mapping site types to corresponding IPCC land use categories.
625
625
  """
626
626
 
627
627
  IPCC_LAND_USE_CATEGORY_TO_LAND_COVER_LOOKUP_VALUE = {
628
+ IpccLandUseCategory.GRASSLAND: "Grassland",
628
629
  IpccLandUseCategory.PERENNIAL_CROPS: "Perennial crops",
629
630
  IpccLandUseCategory.PADDY_RICE_CULTIVATION: "Paddy rice cultivation",
630
631
  IpccLandUseCategory.ANNUAL_CROPS_WET: "Annual crops",
631
- IpccLandUseCategory.ANNUAL_CROPS: "Annual crops"
632
+ IpccLandUseCategory.ANNUAL_CROPS: "Annual crops",
633
+ IpccLandUseCategory.SET_ASIDE: [
634
+ "Annual crops", "Paddy rice cultivation", "Perennial crops", "Set aside"
635
+ ],
636
+ IpccLandUseCategory.FOREST: "Forest",
637
+ IpccLandUseCategory.NATIVE: "Native"
632
638
  }
633
639
  """
634
640
  A dictionary mapping IPCC land use categories to corresponding land cover lookup values in the
@@ -846,7 +852,7 @@ def _calc_water_factor(
846
852
  float
847
853
  The water effect on decomposition for a given month, dimensionless, between `0.2129` and `1.5`.
848
854
  """
849
- mappet = min(1.25, precipitation / pet)
855
+ mappet = min(1.25, precipitation / pet) if pet else 1.25
850
856
  return 0.775 if is_irrigated else 0.2129 + (water_factor_slope * (mappet)) - (0.2413 * pow(mappet, 2))
851
857
 
852
858
 
@@ -937,7 +943,8 @@ def _calc_average_nitrogen_content_of_organic_carbon_sources(
937
943
  weighted_values = [
938
944
  c.mass * (c.nitrogen_content if c.nitrogen_content else default_nitrogen_content) for c in carbon_sources
939
945
  ]
940
- return sum(weighted_values) / total_weight if total_weight > 0 else default_nitrogen_content
946
+ should_run = total_weight > 0
947
+ return sum(weighted_values) / total_weight if should_run else 0
941
948
 
942
949
 
943
950
  def _calc_average_lignin_content_of_organic_carbon_sources(
@@ -963,7 +970,8 @@ def _calc_average_lignin_content_of_organic_carbon_sources(
963
970
  weighted_values = [
964
971
  c.mass * (c.lignin_content if c.lignin_content else default_lignin_content) for c in carbon_sources
965
972
  ]
966
- return sum(weighted_values) / total_weight if total_weight > 0 else default_lignin_content
973
+ should_run = total_weight > 0
974
+ return sum(weighted_values) / total_weight if should_run else 0
967
975
 
968
976
 
969
977
  # --- TIER 2 FUNCTIONS: ACTIVE SUB-POOL SOC STOCK ---
@@ -2525,7 +2533,7 @@ def _has_irrigation(water_regime_nodes: list[dict]) -> bool:
2525
2533
 
2526
2534
  Parameters
2527
2535
  ----------
2528
- water_regime_nodes : List[dict]
2536
+ water_regime_nodes : list[dict]
2529
2537
  List of water regime nodes to be checked.
2530
2538
 
2531
2539
  Returns
@@ -2542,13 +2550,13 @@ def _has_irrigation(water_regime_nodes: list[dict]) -> bool:
2542
2550
 
2543
2551
  def _has_long_fallow(land_cover_nodes: list[dict]) -> bool:
2544
2552
  """
2545
- Check if long fallow terms is present in the land cover nodes.
2553
+ Check if long fallow terms are present in the land cover nodes.
2546
2554
 
2547
2555
  n.b., a super majority of the site area must be under long fallow for it to be classified as set aside.
2548
2556
 
2549
2557
  Parameters
2550
2558
  ----------
2551
- land_cover_nodes : List[dict]
2559
+ land_cover_nodes : list[dict]
2552
2560
  List of land cover nodes to be checked.
2553
2561
 
2554
2562
  Returns
@@ -2556,9 +2564,12 @@ def _has_long_fallow(land_cover_nodes: list[dict]) -> bool:
2556
2564
  bool
2557
2565
  `True` if long fallow is present, `False` otherwise.
2558
2566
  """
2559
- return cumulative_nodes_term_match(
2567
+ LOOKUP = LOOKUPS["landCover"][0]
2568
+ TARGET_LOOKUP_VALUE = "Set aside"
2569
+ return cumulative_nodes_lookup_match(
2560
2570
  land_cover_nodes,
2561
- target_term_ids=get_long_fallow_land_cover_terms(),
2571
+ lookup=LOOKUP,
2572
+ target_lookup_values=TARGET_LOOKUP_VALUE,
2562
2573
  cumulative_threshold=SUPER_MAJORITY_AREA_THRESHOLD
2563
2574
  ) or cumulative_nodes_match(
2564
2575
  lambda node: get_node_property(node, LONG_FALLOW_CROP_TERM_ID, False).get("value", 0),
@@ -2573,7 +2584,7 @@ def _has_upland_rice(land_cover_nodes: list[dict]) -> bool:
2573
2584
 
2574
2585
  Parameters
2575
2586
  ----------
2576
- land_cover_nodes : List[dict]
2587
+ land_cover_nodes : list[dict]
2577
2588
  List of land cover nodes to be checked.
2578
2589
 
2579
2590
  Returns
@@ -2589,63 +2600,39 @@ def _has_upland_rice(land_cover_nodes: list[dict]) -> bool:
2589
2600
 
2590
2601
 
2591
2602
  IPCC_LAND_USE_CATEGORY_TO_VALIDATION_KWARGS = {
2603
+ IpccLandUseCategory.ANNUAL_CROPS_WET: {"has_wetland_soils"},
2592
2604
  IpccLandUseCategory.SET_ASIDE: {"has_long_fallow"},
2593
- IpccLandUseCategory.ANNUAL_CROPS_WET: {"has_wetland_soils"}
2594
2605
  }
2595
2606
  """
2596
- Keyword arguments that need to be checked for specific `IpccLandUseCategory`s.
2607
+ Keyword arguments that need to be validated in addition to the `landCover` lookup match for specific
2608
+ `IpccLandUseCategory`s.
2597
2609
  """
2598
2610
 
2599
-
2600
- def _check_ipcc_land_use_category(*, key: IpccLandUseCategory, site_type: str, **kwargs) -> bool:
2601
- """
2602
- Check if the site type matches the target site type for the given key.
2603
-
2604
- Parameters
2605
- ----------
2606
- key : IpccLandUseCategory
2607
- The IPCC land use category to check.
2608
- site_type : str
2609
- The site type to check.
2610
-
2611
- Keyword Args
2612
- ------------
2613
- has_long_fallow : bool
2614
- Indicates whether long fallow is present on more than 30% of the site.
2615
- has_wetland_soils : bool
2616
- Indicates whether wetland soils are present to more than 30% of the site.
2617
-
2618
- Returns
2619
- -------
2620
- bool
2621
- `True` if the conditions match the specified land use category, `False` otherwise.
2622
-
2623
- """
2624
- target_site_type = IPCC_LAND_USE_CATEGORY_TO_SITE_TYPE.get(key, None)
2625
- validation_kwargs = IPCC_LAND_USE_CATEGORY_TO_VALIDATION_KWARGS.get(key, set())
2626
- valid_kwargs = all(v for k, v in kwargs.items() if k in validation_kwargs)
2627
- return site_type == target_site_type and valid_kwargs
2611
+ IPCC_LAND_USE_CATEGORY_TO_OVERRIDE_KWARGS = {
2612
+ IpccLandUseCategory.PADDY_RICE_CULTIVATION: {"has_irrigated_upland_rice"}
2613
+ }
2614
+ """
2615
+ Keyword arguments that can override the `landCover` lookup match for specific `IpccLandUseCategory`s.
2616
+ """
2628
2617
 
2629
2618
 
2630
- def _check_cropland_land_use_category(
2631
- *, key: IpccLandUseCategory, site_type: str, land_cover_nodes: list[dict], **kwargs
2632
- ) -> bool:
2619
+ def _check_ipcc_land_use_category(*, key: IpccLandUseCategory, land_cover_nodes: list[dict], **kwargs) -> bool:
2633
2620
  """
2634
- Check if the site type and land cover nodes match the target conditions for a cropland IpccLandUseCategory.
2635
-
2636
- This function is special case of `_check_ipcc_land_use_category`.
2621
+ Check if the land cover nodes and keyword args satisfy the requirements for the given key.
2637
2622
 
2638
2623
  Parameters
2639
2624
  ----------
2640
2625
  key : IpccLandUseCategory
2641
2626
  The IPCC land use category to check.
2642
- site_type : str
2643
- The site type to check.
2627
+ land_cover_nodes : list[dict]
2628
+ List of land cover nodes to be checked.
2644
2629
 
2645
2630
  Keyword Args
2646
2631
  ------------
2632
+ has_irrigated_upland_rice : bool
2633
+ Indicates whether irrigated upland rice is present on more than 30% of the site.
2647
2634
  has_long_fallow : bool
2648
- Indicates whether long fallow is present on more than 30% of the site.
2635
+ Indicates whether long fallow is present on more than 70% of the site.
2649
2636
  has_wetland_soils : bool
2650
2637
  Indicates whether wetland soils are present to more than 30% of the site.
2651
2638
 
@@ -2662,48 +2649,23 @@ def _check_cropland_land_use_category(
2662
2649
  target_lookup_values=target_lookup_values,
2663
2650
  cumulative_threshold=MIN_AREA_THRESHOLD
2664
2651
  )
2665
- return _check_ipcc_land_use_category(key=key, site_type=site_type, **kwargs) and valid_lookup
2666
2652
 
2653
+ validation_kwargs = IPCC_LAND_USE_CATEGORY_TO_VALIDATION_KWARGS.get(key, set())
2654
+ valid_kwargs = all(v for k, v in kwargs.items() if k in validation_kwargs)
2667
2655
 
2668
- def _check_paddy_rice_cultivation_land_use_category(
2669
- *, key: IpccLandUseCategory, site_type: str, has_irrigated_upland_rice: bool, **kwargs
2670
- ) -> bool:
2671
- """
2672
- Check if the site type and land cover nodes match the target conditions for a cropland IpccLandUseCategory.
2656
+ override_kwargs = IPCC_LAND_USE_CATEGORY_TO_OVERRIDE_KWARGS.get(key, set())
2657
+ valid_override = any(v for k, v in kwargs.items() if k in override_kwargs)
2673
2658
 
2674
- This function is special case of `_check_cropland_land_use_category`.
2675
-
2676
- Parameters
2677
- ----------
2678
- key : IpccLandUseCategory
2679
- The IPCC land use category to check.
2680
- site_type : str
2681
- The site type to check.
2682
-
2683
- Keyword Args
2684
- ------------
2685
- has_irrigated_upland_rice : bool
2686
- Indicates whether irrigated upland rice is present on more than 30% of the site.
2687
- has_long_fallow : bool
2688
- Indicates whether long fallow is present on more than 30% of the site.
2689
- has_wetland_soils : bool
2690
- Indicates whether wetland soils are present to more than 30% of the site.
2691
-
2692
- Returns
2693
- -------
2694
- bool
2695
- `True` if the conditions match the specified land use category, `False` otherwise.
2696
- """
2697
- return _check_cropland_land_use_category(key=key, site_type=site_type, **kwargs) or has_irrigated_upland_rice
2659
+ return (valid_lookup and valid_kwargs) or valid_override
2698
2660
 
2699
2661
 
2700
2662
  LAND_USE_CATEGORY_DECISION_TREE = {
2701
2663
  IpccLandUseCategory.GRASSLAND: _check_ipcc_land_use_category,
2702
2664
  IpccLandUseCategory.SET_ASIDE: _check_ipcc_land_use_category,
2703
- IpccLandUseCategory.PERENNIAL_CROPS: _check_cropland_land_use_category,
2704
- IpccLandUseCategory.PADDY_RICE_CULTIVATION: _check_paddy_rice_cultivation_land_use_category,
2705
- IpccLandUseCategory.ANNUAL_CROPS_WET: _check_cropland_land_use_category,
2706
- IpccLandUseCategory.ANNUAL_CROPS: _check_cropland_land_use_category,
2665
+ IpccLandUseCategory.PERENNIAL_CROPS: _check_ipcc_land_use_category,
2666
+ IpccLandUseCategory.PADDY_RICE_CULTIVATION: _check_ipcc_land_use_category,
2667
+ IpccLandUseCategory.ANNUAL_CROPS_WET: _check_ipcc_land_use_category,
2668
+ IpccLandUseCategory.ANNUAL_CROPS: _check_ipcc_land_use_category,
2707
2669
  IpccLandUseCategory.FOREST: _check_ipcc_land_use_category,
2708
2670
  IpccLandUseCategory.NATIVE: _check_ipcc_land_use_category,
2709
2671
  IpccLandUseCategory.OTHER: _check_ipcc_land_use_category
@@ -2712,21 +2674,19 @@ LAND_USE_CATEGORY_DECISION_TREE = {
2712
2674
  A decision tree mapping IPCC soil categories to corresponding check functions.
2713
2675
 
2714
2676
  Key: IpccLandUseCategory
2715
- Value: Corresponding function for checking the match of the given land use category based on site type
2716
- and land cover nodes.
2677
+ Value: Corresponding function for checking the match of the given land use category based on land cover nodes
2678
+ and additional kwargs.
2717
2679
  """
2718
2680
 
2719
2681
 
2720
2682
  def _assign_ipcc_land_use_category(
2721
- site_type: str, management_nodes: list[dict], ipcc_soil_category: IpccSoilCategory
2683
+ management_nodes: list[dict], ipcc_soil_category: IpccSoilCategory,
2722
2684
  ) -> IpccLandUseCategory:
2723
2685
  """
2724
- Assigns IPCC land use category based on site type, management nodes, and soil category.
2686
+ Assigns IPCC land use category based on management nodes and soil category.
2725
2687
 
2726
2688
  Parameters
2727
2689
  ----------
2728
- site_type : str
2729
- The type of the site.
2730
2690
  management_nodes : list[dict]
2731
2691
  List of management nodes.
2732
2692
  ipcc_soil_category : IpccSoilCategory
@@ -2749,14 +2709,13 @@ def _assign_ipcc_land_use_category(
2749
2709
  has_long_fallow = _has_long_fallow(land_cover_nodes)
2750
2710
  has_wetland_soils = ipcc_soil_category is IpccSoilCategory.WETLAND_SOILS
2751
2711
 
2752
- should_run = bool(site_type)
2712
+ should_run = bool(land_cover_nodes)
2753
2713
 
2754
2714
  return next(
2755
2715
  (
2756
2716
  key for key in DECISION_TREE
2757
2717
  if DECISION_TREE[key](
2758
2718
  key=key,
2759
- site_type=site_type,
2760
2719
  land_cover_nodes=land_cover_nodes,
2761
2720
  has_long_fallow=has_long_fallow,
2762
2721
  has_irrigated_upland_rice=has_irrigated_upland_rice,
@@ -2780,7 +2739,7 @@ def _check_grassland_ipcc_management_category(
2780
2739
  ----------
2781
2740
  key : IpccManagementCategory
2782
2741
  The IPCC management category to check.
2783
- land_cover_nodes : List[dict]
2742
+ land_cover_nodes : list[dict]
2784
2743
  List of land cover nodes to be checked.
2785
2744
 
2786
2745
  Returns
@@ -2806,7 +2765,7 @@ def _check_tillage_ipcc_management_category(
2806
2765
  ----------
2807
2766
  key : IpccManagementCategory
2808
2767
  The IPCC management category to check.
2809
- tillage_nodes : List[dict]
2768
+ tillage_nodes : list[dict]
2810
2769
  List of tillage nodes to be checked.
2811
2770
 
2812
2771
  Returns
@@ -3773,9 +3732,16 @@ def _should_run_inventory_year_tier_2(group: dict) -> bool:
3773
3732
  }
3774
3733
  )
3775
3734
 
3735
+ carbon_input_data_complete = all([
3736
+ group.get(_InventoryKey.CARBON_INPUT, 0) > 0,
3737
+ group.get(_InventoryKey.N_CONTENT, 0) > 0,
3738
+ group.get(_InventoryKey.LIGNIN_CONTENT, 0) > 0,
3739
+ ])
3740
+
3776
3741
  return all([
3777
3742
  not group.get(_InventoryKey.IS_PADDY_RICE),
3778
3743
  monthly_data_complete,
3744
+ carbon_input_data_complete,
3779
3745
  all(key in group.keys() for key in REQUIRED_KEYS_TIER_2),
3780
3746
  ])
3781
3747
 
@@ -3783,11 +3749,15 @@ def _should_run_inventory_year_tier_2(group: dict) -> bool:
3783
3749
  def _get_grouped_climate_measurements(grouped_measurements: dict) -> dict:
3784
3750
  return {
3785
3751
  year: {
3786
- _InventoryKey.TEMP_MONTHLY: find_term_match(measurements, TEMPERATURE_MONTHLY_TERM_ID, {}).get("value", []),
3787
- _InventoryKey.PRECIP_MONTHLY: (
3752
+ _InventoryKey.TEMP_MONTHLY: non_empty_list(
3753
+ find_term_match(measurements, TEMPERATURE_MONTHLY_TERM_ID, {}).get("value", [])
3754
+ ),
3755
+ _InventoryKey.PRECIP_MONTHLY: non_empty_list(
3788
3756
  find_term_match(measurements, PRECIPITATION_MONTHLY_TERM_ID, {}).get("value", [])
3789
3757
  ),
3790
- _InventoryKey.PET_MONTHLY: find_term_match(measurements, PET_MONTHLY_TERM_ID, {}).get("value", [])
3758
+ _InventoryKey.PET_MONTHLY: non_empty_list(
3759
+ find_term_match(measurements, PET_MONTHLY_TERM_ID, {}).get("value", [])
3760
+ )
3791
3761
  } for year, measurements in grouped_measurements.items()
3792
3762
  }
3793
3763
 
@@ -3917,15 +3887,19 @@ def _build_inventory_tier_1(
3917
3887
  eco_climate_zone = _get_eco_climate_zone(measurement_nodes)
3918
3888
  ipcc_soil_category = _assign_ipcc_soil_category(measurement_nodes)
3919
3889
  soc_ref = _retrieve_soc_ref(eco_climate_zone, ipcc_soil_category)
3890
+ grouped_management = group_nodes_by_year(management_nodes)
3891
+
3892
+ # If no `landCover` nodes in `site.management` use `site.siteType` to assign static `IpccLandUseCategory`
3893
+ run_with_site_type = len(filter_list_term_type(management_nodes, [TermTermType.LANDCOVER])) == 0
3894
+ site_type_ipcc_land_use_category = SITE_TYPE_TO_IPCC_LAND_USE_CATEGORY.get(site_type, IpccLandUseCategory.OTHER)
3920
3895
 
3921
3896
  grouped_management = group_nodes_by_year(management_nodes)
3922
3897
 
3923
3898
  grouped_land_use_categories = {
3924
3899
  year: {
3925
- _InventoryKey.LU_CATEGORY: _assign_ipcc_land_use_category(
3926
- site_type,
3927
- nodes,
3928
- ipcc_soil_category
3900
+ _InventoryKey.LU_CATEGORY: (
3901
+ site_type_ipcc_land_use_category if run_with_site_type
3902
+ else _assign_ipcc_land_use_category(nodes, ipcc_soil_category)
3929
3903
  )
3930
3904
  } for year, nodes in grouped_management.items()
3931
3905
  }
@@ -3963,7 +3937,8 @@ def _build_inventory_tier_1(
3963
3937
  kwargs = {
3964
3938
  "eco_climate_zone": eco_climate_zone,
3965
3939
  "ipcc_soil_category": ipcc_soil_category,
3966
- "soc_ref": soc_ref,
3940
+ "run_with_site_type": run_with_site_type,
3941
+ "soc_ref": soc_ref
3967
3942
  }
3968
3943
 
3969
3944
  return inventory, kwargs
@@ -497,7 +497,8 @@ def _sum_values(values): return sum([value for term_id, value in values])
497
497
  def _calculate_GE(cycle: dict, meanDE: float, system: dict):
498
498
  animals = [
499
499
  a for a in cycle.get('animals', []) if all([
500
- a.get('value') and a.get('referencePeriod') == AnimalReferencePeriod.AVERAGE.value
500
+ a.get('value'),
501
+ a.get('referencePeriod') == AnimalReferencePeriod.AVERAGE.value
501
502
  ])
502
503
  ]
503
504