hestia-earth-models 0.73.1__py3-none-any.whl → 0.73.3__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.
Files changed (49) hide show
  1. hestia_earth/models/akagiEtAl2011/utils.py +3 -1
  2. hestia_earth/models/config/Cycle.json +35 -37
  3. hestia_earth/models/config/Site.json +26 -24
  4. hestia_earth/models/environmentalFootprintV3_1/soilQualityIndexLandOccupation.py +3 -2
  5. hestia_earth/models/environmentalFootprintV3_1/soilQualityIndexLandTransformation.py +1 -1
  6. hestia_earth/models/frischknechtEtAl2000/ionisingRadiationKbqU235Eq.py +10 -6
  7. hestia_earth/models/geospatialDatabase/utils.py +20 -6
  8. hestia_earth/models/hestia/aboveGroundCropResidue.py +6 -5
  9. hestia_earth/models/hestia/cropResidueManagement.py +3 -2
  10. hestia_earth/models/hestia/default_emissions.py +1 -0
  11. hestia_earth/models/hestia/default_resourceUse.py +3 -2
  12. hestia_earth/models/hestia/excretaKgMass.py +1 -1
  13. hestia_earth/models/hestia/excretaKgN.py +1 -1
  14. hestia_earth/models/hestia/excretaKgVs.py +1 -1
  15. hestia_earth/models/hestia/landCover.py +1 -1
  16. hestia_earth/models/hestia/waterSalinity.py +13 -6
  17. hestia_earth/models/ipcc2019/aboveGroundBiomass.py +2 -4
  18. hestia_earth/models/ipcc2019/belowGroundBiomass.py +2 -4
  19. hestia_earth/models/ipcc2019/biomass_utils.py +1 -1
  20. hestia_earth/models/ipcc2019/ch4ToAirOrganicSoilCultivation.py +3 -4
  21. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +2 -4
  22. hestia_earth/models/ipcc2019/co2ToAirOrganicSoilCultivation.py +2 -3
  23. hestia_earth/models/ipcc2019/n2OToAirCropResidueBurningDirect.py +8 -7
  24. hestia_earth/models/ipcc2019/nonCo2EmissionsToAirNaturalVegetationBurning.py +2 -3
  25. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1.py +2 -3
  26. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2.py +3 -4
  27. hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +2 -3
  28. hestia_earth/models/mocking/search-results.json +1568 -1568
  29. hestia_earth/models/schmidt2007/utils.py +2 -2
  30. hestia_earth/models/utils/__init__.py +1 -1
  31. hestia_earth/models/utils/cycle.py +2 -2
  32. hestia_earth/models/utils/impact_assessment.py +14 -14
  33. hestia_earth/models/utils/lookup.py +30 -10
  34. hestia_earth/models/version.py +1 -1
  35. {hestia_earth_models-0.73.1.dist-info → hestia_earth_models-0.73.3.dist-info}/METADATA +3 -2
  36. {hestia_earth_models-0.73.1.dist-info → hestia_earth_models-0.73.3.dist-info}/RECORD +44 -49
  37. tests/models/environmentalFootprintV3_1/test_soilQualityIndexLandOccupation.py +3 -3
  38. tests/models/frischknechtEtAl2000/test_ionisingRadiationKbqU235Eq.py +85 -31
  39. tests/models/geospatialDatabase/test_utils.py +12 -1
  40. tests/models/ipcc2019/test_organicCarbonPerHa_tier_2.py +1 -1
  41. tests/models/utils/test_array_builders.py +1 -1
  42. hestia_earth/models/utils/array_builders.py +0 -590
  43. hestia_earth/models/utils/descriptive_stats.py +0 -49
  44. hestia_earth/models/utils/stats.py +0 -429
  45. tests/models/utils/test_descriptive_stats.py +0 -50
  46. tests/models/utils/test_stats.py +0 -186
  47. {hestia_earth_models-0.73.1.dist-info → hestia_earth_models-0.73.3.dist-info}/LICENSE +0 -0
  48. {hestia_earth_models-0.73.1.dist-info → hestia_earth_models-0.73.3.dist-info}/WHEEL +0 -0
  49. {hestia_earth_models-0.73.1.dist-info → hestia_earth_models-0.73.3.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,7 @@
1
1
  from hestia_earth.schema import EmissionMethodTier, EmissionStatsDefinition, TermTermType
2
2
 
3
3
  from hestia_earth.models.log import logRequirements, logShouldRun
4
+ from hestia_earth.models.utils import multiply_values
4
5
  from hestia_earth.models.utils.emission import _new_emission
5
6
  from hestia_earth.models.utils.term import get_lookup_value
6
7
  from hestia_earth.models.utils.cropResidue import get_crop_residue_burnt_value
@@ -25,7 +26,8 @@ def _run(term_id: str, product_value: list):
25
26
  term = {'termType': TermTermType.EMISSION.value, '@id': term_id}
26
27
  factor = get_lookup_value(term, LOOKUP_NAME)
27
28
  factor_sd = get_lookup_value(term, LOOKUP_NAME + '-sd')
28
- return [_emission(term_id, value * factor, value * factor_sd)]
29
+ emission_value = multiply_values([value, factor])
30
+ return [] if emission_value is None else [_emission(term_id, emission_value, multiply_values([value, factor_sd]))]
29
31
 
30
32
 
31
33
  def _should_run(term_id: str, cycle: dict):
@@ -27,17 +27,9 @@
27
27
  "stage": 1
28
28
  },
29
29
  {
30
- "key": "practices",
31
- "model": "hestia",
32
- "value": "croppingIntensity",
33
- "runStrategy": "add_blank_node_if_missing",
34
- "mergeStrategy": "list",
35
- "stage": 1
36
- },
37
- {
38
- "key": "practices",
39
- "model": "geospatialDatabase",
40
- "value": "croppingIntensity",
30
+ "key": "inputs",
31
+ "model": "faostat2018",
32
+ "value": "seed",
41
33
  "runStrategy": "add_blank_node_if_missing",
42
34
  "mergeStrategy": "list",
43
35
  "stage": 1
@@ -100,6 +92,22 @@
100
92
  "stage": 1
101
93
  }
102
94
  ],
95
+ {
96
+ "key": "practices",
97
+ "model": "hestia",
98
+ "value": "croppingIntensity",
99
+ "runStrategy": "add_blank_node_if_missing",
100
+ "mergeStrategy": "list",
101
+ "stage": 1
102
+ },
103
+ {
104
+ "key": "practices",
105
+ "model": "geospatialDatabase",
106
+ "value": "croppingIntensity",
107
+ "runStrategy": "add_blank_node_if_missing",
108
+ "mergeStrategy": "list",
109
+ "stage": 1
110
+ },
103
111
  [
104
112
  {
105
113
  "key": "practices",
@@ -321,14 +329,6 @@
321
329
  "stage": 1
322
330
  },
323
331
  [
324
- {
325
- "key": "practices",
326
- "model": "hestia",
327
- "value": "longFallowRatio",
328
- "runStrategy": "add_blank_node_if_missing",
329
- "mergeStrategy": "list",
330
- "stage": 1
331
- },
332
332
  {
333
333
  "key": "practices",
334
334
  "model": "hestia",
@@ -467,24 +467,22 @@
467
467
  "stage": 1
468
468
  }
469
469
  ],
470
- [
471
- {
472
- "key": "inputs",
473
- "model": "faostat2018",
474
- "value": "seed",
475
- "runStrategy": "add_blank_node_if_missing",
476
- "mergeStrategy": "list",
477
- "stage": 1
478
- },
479
- {
480
- "key": "inputs",
481
- "model": "pooreNemecek2018",
482
- "value": "saplingsDepreciatedAmountPerCycle",
483
- "runStrategy": "add_blank_node_if_missing",
484
- "mergeStrategy": "list",
485
- "stage": 1
486
- }
487
- ],
470
+ {
471
+ "key": "practices",
472
+ "model": "hestia",
473
+ "value": "longFallowRatio",
474
+ "runStrategy": "add_blank_node_if_missing",
475
+ "mergeStrategy": "list",
476
+ "stage": 1
477
+ },
478
+ {
479
+ "key": "inputs",
480
+ "model": "pooreNemecek2018",
481
+ "value": "saplingsDepreciatedAmountPerCycle",
482
+ "runStrategy": "add_blank_node_if_missing",
483
+ "mergeStrategy": "list",
484
+ "stage": 1
485
+ },
488
486
  {
489
487
  "key": "completeness",
490
488
  "model": "cycle",
@@ -65,30 +65,6 @@
65
65
  "mergeStrategy": "list",
66
66
  "stage": 1
67
67
  },
68
- {
69
- "key": "measurements",
70
- "model": "hestia",
71
- "value": "brackishWater",
72
- "runStrategy": "add_blank_node_if_missing",
73
- "mergeStrategy": "list",
74
- "stage": 1
75
- },
76
- {
77
- "key": "measurements",
78
- "model": "hestia",
79
- "value": "freshWater",
80
- "runStrategy": "add_blank_node_if_missing",
81
- "mergeStrategy": "list",
82
- "stage": 1
83
- },
84
- {
85
- "key": "measurements",
86
- "model": "hestia",
87
- "value": "salineWater",
88
- "runStrategy": "add_blank_node_if_missing",
89
- "mergeStrategy": "list",
90
- "stage": 1
91
- },
92
68
  {
93
69
  "key": "measurements",
94
70
  "model": "hestia",
@@ -470,6 +446,32 @@
470
446
  "stage": 2
471
447
  }
472
448
  ],
449
+ [
450
+ {
451
+ "key": "measurements",
452
+ "model": "hestia",
453
+ "value": "brackishWater",
454
+ "runStrategy": "add_blank_node_if_missing",
455
+ "mergeStrategy": "list",
456
+ "stage": 1
457
+ },
458
+ {
459
+ "key": "measurements",
460
+ "model": "hestia",
461
+ "value": "freshWater",
462
+ "runStrategy": "add_blank_node_if_missing",
463
+ "mergeStrategy": "list",
464
+ "stage": 1
465
+ },
466
+ {
467
+ "key": "measurements",
468
+ "model": "hestia",
469
+ "value": "salineWater",
470
+ "runStrategy": "add_blank_node_if_missing",
471
+ "mergeStrategy": "list",
472
+ "stage": 1
473
+ }
474
+ ],
473
475
  [
474
476
  {
475
477
  "key": "defaultMethodClassification",
@@ -16,7 +16,7 @@ REQUIREMENTS = {
16
16
  "optional": {"country": {"@type": "Term", "termType": "region"}},
17
17
  "emissionsResourceUse": [{
18
18
  "@type": "Indicator",
19
- "value": ">0",
19
+ "value": ">=0",
20
20
  "term.@id": ["landOccupationInputsProduction", "landOccupationDuringCycle"],
21
21
  "term.units": "m2*year",
22
22
  "landCover": {"@type": "Term", "term.termType": "landCover"}
@@ -60,12 +60,13 @@ def _should_run(impact_assessment: dict) -> Tuple[bool, list]:
60
60
  ]
61
61
 
62
62
  found_land_occupation_indicators = [{
63
+ 'indicator-id': indicator.get('term', {}).get('@id', ''),
63
64
  'area-by-year': _node_value(indicator),
64
65
  'area-unit': indicator.get('term', {}).get("units"),
65
66
  'land-cover-id': indicator.get('landCover', {}).get("@id"),
66
67
  'country-id': get_country_id(impact_assessment, blank_node=indicator),
67
68
  'area-by-year-is-valid': _node_value(indicator) is not None and _node_value(
68
- indicator) > 0,
69
+ indicator) >= 0,
69
70
  'area-unit-is-valid': indicator.get('term', {}).get("units") == "m2*year",
70
71
  'used-country': fallback_country(get_country_id(impact_assessment, blank_node=indicator), [LOOKUP]),
71
72
  'pef-grouping': get_pef_grouping(indicator.get('landCover', {}).get("@id"))
@@ -92,10 +92,10 @@ def _should_run(impact_assessment: dict) -> Tuple[bool, list]:
92
92
 
93
93
  found_transformations = [
94
94
  {
95
+ 'indicator-id': indicator.get('term', {}).get('@id', ''),
95
96
  'value': _node_value(indicator),
96
97
  'land-cover-id-from': indicator.get('previousLandCover', {}).get("@id"),
97
98
  'land-cover-id-to': indicator.get('landCover', {}).get("@id"),
98
- 'indicator-id': indicator.get('term', {}).get('@id', ''),
99
99
  'good-land-cover-term': all([
100
100
  bool(indicator.get('landCover')),
101
101
  bool(indicator.get('previousLandCover'))
@@ -1,9 +1,11 @@
1
+ from functools import reduce
1
2
  from hestia_earth.schema import TermTermType
2
3
  from hestia_earth.utils.lookup import get_table_value, download_lookup, column_name
3
4
  from hestia_earth.utils.model import filter_list_term_type
4
- from hestia_earth.utils.tools import flatten
5
+ from hestia_earth.utils.tools import flatten, list_sum
5
6
 
6
7
  from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
8
+ from hestia_earth.models.utils.blank_node import group_by_keys
7
9
  from hestia_earth.models.utils.indicator import _new_indicator
8
10
  from hestia_earth.models.utils.lookup import _node_value
9
11
  from . import MODEL
@@ -47,22 +49,24 @@ def _valid_emission(emission: dict) -> bool:
47
49
  return len(emission.get('inputs', [])) == 1 and isinstance(_node_value(emission), (int, float))
48
50
 
49
51
 
50
- def _indicator(value: float, input: dict):
52
+ def _indicator(value: float, input: dict) -> dict:
51
53
  indicator = _new_indicator(TERM_ID, MODEL)
52
54
  indicator['value'] = value
53
55
  indicator['inputs'] = [input]
54
56
  return indicator
55
57
 
56
58
 
57
- def _run(emissions: list):
59
+ def _run(emissions: list) -> list[dict]:
58
60
  indicators = [
59
- _indicator(value=emission['value'] * emission['coefficient'], input=emission['input'])
60
- for emission in emissions
61
+ _indicator(value=list_sum([emission['value'] * emission['coefficient'] for emission in emission_group]),
62
+ input=emission_group[0]['input'])
63
+ for emission_group in reduce(group_by_keys(['input']), emissions, {}).values()
61
64
  ]
65
+
62
66
  return indicators
63
67
 
64
68
 
65
- def _should_run(impact_assessment: dict):
69
+ def _should_run(impact_assessment: dict) -> tuple[bool, list]:
66
70
  emissions = [
67
71
  emission
68
72
  for emission in filter_list_term_type(impact_assessment.get('emissionsResourceUse', []), TermTermType.EMISSION)
@@ -1,4 +1,7 @@
1
1
  import os
2
+ import json
3
+ from area import area
4
+ from functools import reduce, lru_cache
2
5
  from hestia_earth.schema import TermTermType
3
6
  from hestia_earth.utils.tools import non_empty_list
4
7
 
@@ -8,6 +11,7 @@ from hestia_earth.models.utils.term import download_term
8
11
  from . import MODEL
9
12
 
10
13
  MAX_AREA_SIZE = int(os.getenv('MAX_AREA_SIZE', '5000'))
14
+ _ENABLE_CACHE_BOUNDARY_AREA = os.getenv('CACHE_BOUNDARY_AREA', 'false') == 'true'
11
15
  CACHE_VALUE = MODEL
12
16
  CACHE_AREA_SIZE = 'areaSize'
13
17
  GEOPANDAS_COLLECTION_NAME = {
@@ -83,18 +87,28 @@ def geospatial_data(site: dict, only_coordinates=False):
83
87
  })
84
88
 
85
89
 
86
- def _get_boundary_area_size(boundary: dict):
87
- try:
88
- from hestia_earth.earth_engine.boundary import get_size_km2
89
- except ImportError:
90
- raise ImportError("Run `pip install hestia_earth.earth_engine` to use this functionality")
90
+ def _geojson_area_size(boundary: dict):
91
+ return (
92
+ _geojson_area_size(boundary.get('geometry')) if 'geometry' in boundary else
93
+ reduce(lambda p, c: p + _geojson_area_size(c), boundary.get('features'), 0) if 'features' in boundary
94
+ else area(boundary) / 1_000_000
95
+ )
96
+
91
97
 
98
+ @lru_cache()
99
+ def _cached_boundary_area_size(boundary: str):
92
100
  try:
93
- return get_size_km2(boundary)
101
+ return _geojson_area_size(json.loads(boundary))
94
102
  except Exception:
95
103
  return None
96
104
 
97
105
 
106
+ def _get_boundary_area_size(boundary: dict):
107
+ return _cached_boundary_area_size(
108
+ boundary=json.dumps(boundary)
109
+ ) if _ENABLE_CACHE_BOUNDARY_AREA else _geojson_area_size(boundary=boundary)
110
+
111
+
98
112
  def _get_region_area_size(site: dict):
99
113
  term = site.get('region', site.get('country'))
100
114
  return term.get('area', (download_term(term.get('@id'), TermTermType.REGION) or {}).get('area')) if term else None
@@ -28,6 +28,7 @@ RETURNS = {
28
28
  "value": ""
29
29
  }]
30
30
  }
31
+ MODEL_KEY = 'aboveGroundCropResidue'
31
32
  TERM_ID = 'aboveGroundCropResidueLeftOnField,aboveGroundCropResidueBurnt,aboveGroundCropResidueIncorporated,aboveGroundCropResidueRemoved' # noqa: E501
32
33
  TOTAL_TERM_ID = 'aboveGroundCropResidueTotal'
33
34
  REMAINING_MODEL = PRODUCT_ID_TO_PRACTICES_ID[-1]['product']
@@ -59,7 +60,7 @@ def _should_run_model(model, cycle: dict, total_value: float):
59
60
  practice_value = _get_practice_value(model.get('practices'), cycle)
60
61
  has_product = find_term_match(cycle.get('products', []), term_id, None) is not None
61
62
 
62
- logRequirements(cycle, model=MODEL, term=term_id,
63
+ logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
63
64
  practice_value=practice_value,
64
65
  has_product=has_product)
65
66
 
@@ -70,7 +71,7 @@ def _should_run_model(model, cycle: dict, total_value: float):
70
71
  ]),
71
72
  not has_product
72
73
  ])
73
- logShouldRun(cycle, MODEL, term_id, should_run)
74
+ logShouldRun(cycle, MODEL, term_id, should_run, model_key=MODEL_KEY)
74
75
  return should_run, practice_value
75
76
 
76
77
 
@@ -104,7 +105,7 @@ def _run(cycle: dict, total_values: list):
104
105
  for model in models:
105
106
  term_id = model.get('product')
106
107
  value = _run_model(model, cycle, total_value)
107
- debugValues(cycle, model=MODEL, term=term_id,
108
+ debugValues(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
108
109
  total_above_ground_crop_residue=total_value,
109
110
  remaining_crop_residue_value=remaining_value,
110
111
  allocated_value=value)
@@ -132,13 +133,13 @@ def _should_run_product(cycle: dict, total_values: list, term_id: str):
132
133
  find_term_match(cycle.get('practices', []), term_id).get('value', []) == [0] for term_id in practice_term_ids
133
134
  ])
134
135
 
135
- logRequirements(cycle, model=MODEL, term=term_id,
136
+ logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
136
137
  term_type_cropResidue_incomplete=term_type_incomplete,
137
138
  has_aboveGroundCropResidueTotal=has_aboveGroundCropResidueTotal,
138
139
  practice_term_ids=';'.join(practice_term_ids),
139
140
  practice_value_is_0=is_value_0)
140
141
  should_run = all([has_aboveGroundCropResidueTotal or is_value_0, term_type_incomplete])
141
- logShouldRun(cycle, MODEL, term_id, should_run)
142
+ logShouldRun(cycle, MODEL, term_id, should_run, model_key=MODEL_KEY)
142
143
  return should_run
143
144
 
144
145
 
@@ -20,6 +20,7 @@ RETURNS = {
20
20
  "term.termType": "cropResidueManagement"
21
21
  }]
22
22
  }
23
+ MODEL_KEY = 'cropResidueManagement'
23
24
  PRACTICE_IDS = [
24
25
  residueBurnt.TERM_ID,
25
26
  residueIncorporated.TERM_ID,
@@ -50,10 +51,10 @@ def _should_run(cycle: dict):
50
51
  should_run = all([99.5 <= sum_practices <= 100.5])
51
52
 
52
53
  for term_id in missing_practices:
53
- logRequirements(cycle, model=MODEL, term=term_id,
54
+ logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
54
55
  sum_crop_residue_management=sum_practices)
55
56
 
56
- logShouldRun(cycle, MODEL, term_id, should_run)
57
+ logShouldRun(cycle, MODEL, term_id, should_run, model_key=MODEL_KEY)
57
58
 
58
59
  return should_run, missing_practices
59
60
 
@@ -31,6 +31,7 @@ RETURNS = {
31
31
  }]
32
32
  }
33
33
  LOOKUPS = {
34
+ "emission": "inHestiaDefaultSystemBoundary",
34
35
  "organicFertiliser": "backgroundEmissionsResourceUseDefaultValue"
35
36
  }
36
37
  MODEL_KEY = 'default_emissions'
@@ -1,6 +1,6 @@
1
1
  from hestia_earth.schema import IndicatorMethodTier, TermTermType
2
2
  from hestia_earth.utils.tools import flatten, safe_parse_float
3
- from hestia_earth.utils.lookup import download_lookup, lookup_term_ids
3
+ from hestia_earth.utils.emission import emissions_in_system_boundary
4
4
 
5
5
  from hestia_earth.models.log import logRequirements, logShouldRun
6
6
  from hestia_earth.models.utils import _omit
@@ -34,6 +34,7 @@ RETURNS = {
34
34
  }]
35
35
  }
36
36
  LOOKUPS = {
37
+ "resourceUse": "inHestiaDefaultSystemBoundary",
37
38
  "organicFertiliser": "backgroundEmissionsResourceUseDefaultValue"
38
39
  }
39
40
  MODEL_KEY = 'default_resourceUse'
@@ -54,7 +55,7 @@ def _default_value(input: dict):
54
55
 
55
56
  def _run_input(impact: dict):
56
57
  required_resourceUse_term_ids = [
57
- id for id in lookup_term_ids(download_lookup(f"{TermTermType.RESOURCEUSE.value}.csv"))
58
+ id for id in emissions_in_system_boundary(TermTermType.RESOURCEUSE)
58
59
  if id.endswith('InputsProduction')
59
60
  ]
60
61
 
@@ -52,7 +52,7 @@ def _convert_by_product(cycle: dict, product: dict, term_id: str):
52
52
  conversion_to_kg_ratio
53
53
  ]) else None
54
54
 
55
- debugValues(cycle, model=MODEL, term=term_id,
55
+ debugValues(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
56
56
  using_excreta_product=existing_product.get('term', {}).get('@id'),
57
57
  conversion_to_kg_ratio=conversion_to_kg_ratio,
58
58
  value=value)
@@ -38,7 +38,7 @@ def _run_product(cycle: dict, term_id: str):
38
38
  existing_kg_product = find_term_match(cycle.get('products', []), get_kg_term_id(term_id))
39
39
  value = convert_product_to_unit(existing_kg_product, Units.KG_N)
40
40
 
41
- debugValues(cycle, model=MODEL, term=term_id,
41
+ debugValues(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
42
42
  using_excreta_product=existing_kg_product.get('term', {}).get('@id'),
43
43
  value=value)
44
44
 
@@ -38,7 +38,7 @@ def _run_product(cycle: dict, term_id: str):
38
38
  existing_kg_product = find_term_match(cycle.get('products', []), get_kg_term_id(term_id))
39
39
  value = convert_product_to_unit(existing_kg_product, Units.KG_VS)
40
40
 
41
- debugValues(cycle, model=MODEL, term=term_id,
41
+ debugValues(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
42
42
  using_excreta_product=existing_kg_product.get('term', {}).get('@id'),
43
43
  value=value)
44
44
 
@@ -818,7 +818,7 @@ def _should_run(site: dict) -> tuple[bool, list, dict]:
818
818
 
819
819
  has_no_prior_land_cover_data = _no_prior_land_cover_data(
820
820
  nodes=management_nodes,
821
- target_node=relevant_nodes[-1:][0]
821
+ target_node=relevant_nodes[0]
822
822
  ) if relevant_nodes else None
823
823
 
824
824
  should_run_nodes, site_area = _should_run_historical_land_use_change(
@@ -1,11 +1,13 @@
1
+ from functools import reduce
1
2
  from hestia_earth.schema import MeasurementMethodClassification, TermTermType
2
3
  from hestia_earth.utils.model import filter_list_term_type
3
- from hestia_earth.utils.tools import safe_parse_float
4
+ from hestia_earth.utils.tools import safe_parse_float, list_average, non_empty_list
4
5
 
5
6
  from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
6
7
  from hestia_earth.models.utils.measurement import _new_measurement
7
8
  from hestia_earth.models.utils.site import related_cycles
8
9
  from hestia_earth.models.utils.term import get_lookup_value
10
+ from hestia_earth.models.utils.blank_node import group_by_keys
9
11
  from . import MODEL
10
12
 
11
13
  REQUIREMENTS = {
@@ -43,7 +45,7 @@ def _measurement(value: float, start_date: str = None, end_date: str = None):
43
45
  if start_date:
44
46
  data['startDate'] = start_date
45
47
  data['methodClassification'] = MeasurementMethodClassification.EXPERT_OPINION.value
46
- return data
48
+ return data if value is not None else None
47
49
 
48
50
 
49
51
  def _should_run(site: dict):
@@ -73,7 +75,12 @@ def _should_run(site: dict):
73
75
 
74
76
  def run(site: dict):
75
77
  should_run, values = _should_run(site)
76
- return [
77
- _measurement(value.get('lookup-value'), value.get('start-date'), value.get('end-date'))
78
- for value in values if value.get('lookup-value') is not None
79
- ] if should_run else []
78
+ grouped_values = reduce(group_by_keys(['start-date', 'end-date']), values, {})
79
+ return non_empty_list([
80
+ _measurement(
81
+ list_average([v.get('lookup-value') for v in value if v.get('lookup-value')], default=0),
82
+ value[0].get('start-date'),
83
+ value[0].get('end-date')
84
+ )
85
+ for value in grouped_values.values()
86
+ ]) if should_run else []
@@ -3,20 +3,18 @@ from functools import reduce
3
3
  from numpy import average, copy, random, vstack
4
4
  from numpy.typing import NDArray
5
5
  from typing import Optional, Union
6
-
7
6
  from hestia_earth.schema import (
8
7
  MeasurementMethodClassification,
9
8
  MeasurementStatsDefinition,
10
9
  SiteSiteType
11
10
  )
12
-
13
11
  from hestia_earth.utils.tools import non_empty_list
12
+ from hestia_earth.utils.stats import gen_seed
13
+ from hestia_earth.utils.descriptive_stats import calc_descriptive_stats
14
14
 
15
15
  from hestia_earth.models.log import log_as_table, logRequirements, logShouldRun
16
16
  from hestia_earth.models.utils import pairwise
17
- from hestia_earth.models.utils.array_builders import gen_seed
18
17
  from hestia_earth.models.utils.blank_node import group_nodes_by_year
19
- from hestia_earth.models.utils.descriptive_stats import calc_descriptive_stats
20
18
  from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_eco_climate_zone_value
21
19
  from hestia_earth.models.utils.source import get_source
22
20
  from hestia_earth.models.utils.measurement import _new_measurement
@@ -3,20 +3,18 @@ from functools import reduce
3
3
  from numpy import average, copy, random, vstack
4
4
  from numpy.typing import NDArray
5
5
  from typing import Optional, Union
6
-
7
6
  from hestia_earth.schema import (
8
7
  MeasurementMethodClassification,
9
8
  MeasurementStatsDefinition,
10
9
  SiteSiteType
11
10
  )
12
-
13
11
  from hestia_earth.utils.tools import non_empty_list
12
+ from hestia_earth.utils.stats import gen_seed
13
+ from hestia_earth.utils.descriptive_stats import calc_descriptive_stats
14
14
 
15
15
  from hestia_earth.models.log import log_as_table, logRequirements, logShouldRun
16
16
  from hestia_earth.models.utils import pairwise
17
- from hestia_earth.models.utils.array_builders import gen_seed
18
17
  from hestia_earth.models.utils.blank_node import group_nodes_by_year
19
- from hestia_earth.models.utils.descriptive_stats import calc_descriptive_stats
20
18
  from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_eco_climate_zone_value
21
19
  from hestia_earth.models.utils.source import get_source
22
20
  from hestia_earth.models.utils.measurement import _new_measurement
@@ -9,7 +9,7 @@ from hestia_earth.schema import TermTermType
9
9
  from hestia_earth.utils.blank_node import get_node_value
10
10
  from hestia_earth.utils.model import filter_list_term_type
11
11
 
12
- from hestia_earth.models.utils.array_builders import repeat_single, truncated_normal_1d
12
+ from hestia_earth.utils.stats import repeat_single, truncated_normal_1d
13
13
  from hestia_earth.models.utils.blank_node import node_term_match, validate_start_date_end_date
14
14
  from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_ecoClimateZone_lookup_grouped_value
15
15
  from hestia_earth.models.utils.term import get_cover_crop_property_terms, get_lookup_value
@@ -1,15 +1,14 @@
1
1
  import numpy as np
2
2
  import numpy.typing as npt
3
3
  from typing import Callable, Union
4
-
5
4
  from hestia_earth.schema import EmissionMethodTier, EmissionStatsDefinition
6
-
7
5
  from hestia_earth.models.log import logRequirements, logShouldRun
8
- from hestia_earth.models.utils.array_builders import (
6
+ from hestia_earth.utils.stats import (
9
7
  discrete_uniform_1d, gen_seed, normal_1d, repeat_single, triangular_1d
10
8
  )
9
+ from hestia_earth.utils.descriptive_stats import calc_descriptive_stats
10
+
11
11
  from hestia_earth.models.utils.cycle import land_occupation_per_ha
12
- from hestia_earth.models.utils.descriptive_stats import calc_descriptive_stats
13
12
  from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_eco_climate_zone_value
14
13
  from hestia_earth.models.utils.emission import _new_emission
15
14
  from hestia_earth.models.utils.measurement import most_relevant_measurement_value
@@ -2,7 +2,6 @@
2
2
  Utilities for calculating CO2 emissions based on changes in carbon stocks (e.g., `organicCarbonPerHa`,
3
3
  `aboveGroundBiomass` and `belowGroundBiomass`).
4
4
  """
5
-
6
5
  from datetime import datetime, timedelta
7
6
  from enum import Enum
8
7
  from functools import reduce
@@ -11,22 +10,21 @@ from numpy import array, random, mean
11
10
  from numpy.typing import NDArray
12
11
  from pydash.objects import merge
13
12
  from typing import Any, Callable, NamedTuple, Optional, Union
14
-
15
13
  from hestia_earth.schema import (
16
14
  EmissionMethodTier, EmissionStatsDefinition, MeasurementMethodClassification
17
15
  )
18
16
  from hestia_earth.utils.date import diff_in_days, YEAR
19
17
  from hestia_earth.utils.tools import flatten, non_empty_list, safe_parse_date
18
+ from hestia_earth.utils.stats import correlated_normal_2d, gen_seed
19
+ from hestia_earth.utils.descriptive_stats import calc_descriptive_stats
20
20
 
21
21
  from hestia_earth.models.log import log_as_table
22
22
  from hestia_earth.models.utils import pairwise
23
- from hestia_earth.models.utils.array_builders import correlated_normal_2d, gen_seed
24
23
  from hestia_earth.models.utils.blank_node import (
25
24
  _gapfill_datestr, _get_datestr_format, DatestrGapfillMode, DatestrFormat, group_nodes_by_year, node_term_match,
26
25
  split_node_by_dates
27
26
  )
28
27
  from hestia_earth.models.utils.constant import Units, get_atomic_conversion
29
- from hestia_earth.models.utils.descriptive_stats import calc_descriptive_stats
30
28
  from hestia_earth.models.utils.emission import min_emission_method_tier
31
29
  from hestia_earth.models.utils.measurement import (
32
30
  group_measurements_by_method_classification, min_measurement_method_classification,
@@ -1,13 +1,12 @@
1
1
  import numpy as np
2
2
  import numpy.typing as npt
3
3
  from typing import Callable, Union
4
-
5
4
  from hestia_earth.schema import EmissionMethodTier, EmissionStatsDefinition
5
+ from hestia_earth.utils.stats import gen_seed, repeat_single, truncated_normal_1d
6
+ from hestia_earth.utils.descriptive_stats import calc_descriptive_stats
6
7
 
7
8
  from hestia_earth.models.log import logRequirements, logShouldRun
8
- from hestia_earth.models.utils.array_builders import gen_seed, repeat_single, truncated_normal_1d
9
9
  from hestia_earth.models.utils.cycle import land_occupation_per_ha
10
- from hestia_earth.models.utils.descriptive_stats import calc_descriptive_stats
11
10
  from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_eco_climate_zone_value
12
11
  from hestia_earth.models.utils.emission import _new_emission
13
12
  from hestia_earth.models.utils.measurement import most_relevant_measurement_value