hestia-earth-models 0.65.3__py3-none-any.whl → 0.65.5__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 (59) hide show
  1. hestia_earth/models/agribalyse2016/fuelElectricity.py +41 -35
  2. hestia_earth/models/aware/scarcityWeightedWaterUse.py +1 -1
  3. hestia_earth/models/cache_sites.py +2 -0
  4. hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsLandOccupation.py +1 -1
  5. hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsLandTransformation.py +1 -1
  6. hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsTotalLandUseEffects.py +1 -1
  7. hestia_earth/models/cycle/completeness/__init__.py +1 -1
  8. hestia_earth/models/cycle/completeness/electricityFuel.py +4 -2
  9. hestia_earth/models/emepEea2019/nh3ToAirInorganicFertiliser.py +1 -1
  10. hestia_earth/models/geospatialDatabase/precipitationAnnual.py +2 -2
  11. hestia_earth/models/geospatialDatabase/precipitationLongTermAnnualMean.py +2 -2
  12. hestia_earth/models/geospatialDatabase/precipitationMonthly.py +2 -2
  13. hestia_earth/models/geospatialDatabase/temperatureAnnual.py +2 -2
  14. hestia_earth/models/geospatialDatabase/temperatureLongTermAnnualMean.py +2 -2
  15. hestia_earth/models/geospatialDatabase/temperatureMonthly.py +2 -2
  16. hestia_earth/models/hestia/landCover.py +31 -46
  17. hestia_earth/models/hestia/landTransformation100YearAverageDuringCycle.py +49 -0
  18. hestia_earth/models/hestia/landTransformation20YearAverageDuringCycle.py +49 -0
  19. hestia_earth/models/hestia/resourceUse_utils.py +200 -0
  20. hestia_earth/models/hestia/seed_emissions.py +37 -28
  21. hestia_earth/models/hestia/utils.py +48 -0
  22. hestia_earth/models/impact_assessment/emissions.py +20 -5
  23. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +66 -28
  24. hestia_earth/models/ipcc2019/croppingDuration.py +5 -0
  25. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +26 -142
  26. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +3 -3
  27. hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +8 -5
  28. hestia_earth/models/linkedImpactAssessment/freshwaterWithdrawalsInputsProduction.py +2 -1
  29. hestia_earth/models/linkedImpactAssessment/landOccupationInputsProduction.py +1 -0
  30. hestia_earth/models/linkedImpactAssessment/landTransformation100YearAverageInputsProduction.py +1 -0
  31. hestia_earth/models/linkedImpactAssessment/landTransformation20YearAverageInputsProduction.py +1 -0
  32. hestia_earth/models/linkedImpactAssessment/utils.py +25 -20
  33. hestia_earth/models/mocking/search-results.json +670 -654
  34. hestia_earth/models/pooreNemecek2018/freshwaterWithdrawalsDuringCycle.py +4 -1
  35. hestia_earth/models/schererPfister2015/nErosionSoilFlux.py +23 -14
  36. hestia_earth/models/schererPfister2015/pErosionSoilFlux.py +23 -15
  37. hestia_earth/models/schererPfister2015/utils.py +3 -5
  38. hestia_earth/models/site/management.py +2 -2
  39. hestia_earth/models/utils/__init__.py +9 -0
  40. hestia_earth/models/utils/blank_node.py +28 -0
  41. hestia_earth/models/utils/ecoClimateZone.py +1 -4
  42. hestia_earth/models/utils/fuel.py +4 -1
  43. hestia_earth/models/utils/impact_assessment.py +7 -5
  44. hestia_earth/models/utils/lookup.py +8 -2
  45. hestia_earth/models/utils/pesticideAI.py +1 -0
  46. hestia_earth/models/version.py +1 -1
  47. {hestia_earth_models-0.65.3.dist-info → hestia_earth_models-0.65.5.dist-info}/METADATA +2 -2
  48. {hestia_earth_models-0.65.3.dist-info → hestia_earth_models-0.65.5.dist-info}/RECORD +59 -53
  49. tests/models/hestia/test_landTransformation100YearAverageDuringCycle.py +30 -0
  50. tests/models/hestia/test_landTransformation20YearAverageDuringCycle.py +31 -0
  51. tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +3 -1
  52. tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +3 -1
  53. tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +3 -1
  54. tests/models/ipcc2019/test_organicCarbonPerHa.py +3 -2
  55. tests/models/ipcc2019/test_organicCarbonPerHa_tier_1_utils.py +15 -11
  56. tests/models/utils/test_blank_node.py +22 -7
  57. {hestia_earth_models-0.65.3.dist-info → hestia_earth_models-0.65.5.dist-info}/LICENSE +0 -0
  58. {hestia_earth_models-0.65.3.dist-info → hestia_earth_models-0.65.5.dist-info}/WHEEL +0 -0
  59. {hestia_earth_models-0.65.3.dist-info → hestia_earth_models-0.65.5.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,14 @@
1
1
  """
2
2
  Fuel and Electricity
3
3
 
4
- This model calculates fuel and electricity data from the number of hours each machine is operated for using.
4
+ This model calculates fuel and electricity data from the number of hours each machine is used for.
5
5
  """
6
- from functools import reduce
7
6
  from hestia_earth.schema import TermTermType
8
7
  from hestia_earth.utils.model import filter_list_term_type
9
8
  from hestia_earth.utils.tools import flatten, list_sum, non_empty_list
10
9
 
11
10
  from hestia_earth.models.log import debugValues, logRequirements, logShouldRun, log_as_table
11
+ from hestia_earth.models.utils import group_by
12
12
  from hestia_earth.models.utils.term import get_lookup_value
13
13
  from hestia_earth.models.utils.input import _new_input
14
14
  from . import MODEL
@@ -68,57 +68,63 @@ def _run_operation(cycle: dict):
68
68
  return exec
69
69
 
70
70
 
71
- def _group_operations(operations: list):
72
- def grouper(group: dict, operation: dict):
73
- input_term_id = operation.get('input').get('id')
74
- group[input_term_id] = group.get(input_term_id, [])
75
- group[input_term_id].append(operation)
76
- return group
71
+ def _operation_data(practice: dict):
72
+ term = practice.get('term', {})
73
+ values = practice.get('value', [])
74
+ value = list_sum(values) if all([not isinstance(v, str) for v in values]) else None # str allowed for Practice
77
75
 
78
- return reduce(grouper, operations, {})
76
+ coeffs = get_lookup_value(term, LOOKUPS['operation'], model=MODEL, model_key=MODEL_KEY)
77
+ values = non_empty_list(coeffs.split(';')) if coeffs else []
78
+ inputs = [{'id': c.split(':')[0], 'value': float(c.split(':')[1])} for c in values]
79
79
 
80
+ return [{
81
+ 'term': term,
82
+ 'value': value,
83
+ 'input': input,
84
+ 'dates': ';'.join(practice.get('dates', []))
85
+ } for input in inputs]
80
86
 
81
- def _should_run_operation(cycle: dict):
82
- def exec(practice: dict):
83
- term = practice.get('term', {})
84
- term_id = term.get('@id')
85
- values = practice.get('value', [])
86
- value = list_sum(values) if all([not isinstance(v, str) for v in values]) else 0 # str allowed for Practice
87
- has_value = value > 0
88
87
 
89
- coeffs = get_lookup_value(term, LOOKUPS['operation'], model=MODEL, model_key=MODEL_KEY)
90
- values = non_empty_list(coeffs.split(';')) if coeffs else []
91
- inputs = [{'id': c.split(':')[0], 'value': float(c.split(':')[1])} for c in values]
92
- has_lookup_value = len(inputs) > 0
88
+ def _should_run(cycle: dict):
89
+ is_incomplete = not cycle.get('completeness', {}).get('electricityFuel', False)
90
+ operations = filter_list_term_type(cycle.get('practices', []), TermTermType.OPERATION)
93
91
 
94
- logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
95
- has_value=has_value,
96
- has_fuelUse_lookup_value=has_lookup_value)
92
+ operations = flatten(map(_operation_data, operations))
93
+ term_ids = list(set(non_empty_list(map(lambda v: v.get('term', {}).get('@id'), operations))))
97
94
 
98
- should_run = all([has_value, has_lookup_value])
99
- logShouldRun(cycle, MODEL, term_id, should_run, model_key=MODEL_KEY)
100
- return [{'term': term, 'value': value, 'input': input} for input in inputs] if should_run else []
101
- return exec
95
+ valid_operations = [v for v in operations if (v.get('value') or 0) > 0]
96
+ has_operations = len(valid_operations) > 0
102
97
 
98
+ # group operations by term to show in logs
99
+ grouped_operations = group_by(operations, ['term.@id'])
103
100
 
104
- def _should_run(cycle: dict):
105
- is_incomplete = not cycle.get('completeness', {}).get('electricityFuel', False)
106
- operations = filter_list_term_type(cycle.get('practices', []), TermTermType.OPERATION)
107
- operations = flatten(map(_should_run_operation(cycle), operations))
108
- has_operations = len(operations) > 0
101
+ for term_id, operations in grouped_operations.items():
102
+ logs = [
103
+ {
104
+ 'value': operation.get('value'),
105
+ 'dates': operation.get('dates'),
106
+ 'input-id': operation.get('input', {}).get('@id'),
107
+ }
108
+ for operation in operations
109
+ ]
110
+ logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
111
+ details=log_as_table(logs))
112
+
113
+ should_run = any([(v.get('value') or 0) > 0 for v in operations])
114
+ logShouldRun(cycle, MODEL, term_id, should_run, model_key=MODEL_KEY)
109
115
 
110
116
  logRequirements(cycle, model=MODEL, model_key=MODEL_KEY,
111
117
  is_term_type_electricityFuel_incomplete=is_incomplete,
112
118
  has_operations=has_operations,
113
- operations=';'.join(non_empty_list(map(lambda v: v.get('term', {}).get('@id'), operations))))
119
+ operations=';'.join(term_ids))
114
120
 
115
121
  should_run = all([is_incomplete, has_operations])
116
122
  logShouldRun(cycle, MODEL, None, should_run, model_key=MODEL_KEY)
117
- return should_run, operations
123
+ return should_run, valid_operations
118
124
 
119
125
 
120
126
  def run(cycle: dict):
121
127
  should_run, operations = _should_run(cycle)
122
128
  # group operations by input to show logs as table
123
- grouped_operations = _group_operations(operations)
129
+ grouped_operations = group_by(operations, ['input.id'])
124
130
  return flatten(map(_run_operation(cycle), grouped_operations.values())) if should_run else []
@@ -89,7 +89,7 @@ def _run(impact_assessment: dict):
89
89
  _get_factor_from_basinId(site, aware_id) if aware_id else None
90
90
  ) or _get_factor_from_region(impact_assessment, site)
91
91
  inputs_value = convert_value_from_cycle(
92
- product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
92
+ impact_assessment, product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
93
93
  )
94
94
 
95
95
  logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
@@ -4,6 +4,7 @@ from pydash.objects import merge
4
4
  from hestia_earth.utils.api import download_hestia
5
5
  from hestia_earth.utils.tools import flatten
6
6
 
7
+ from .log import logger
7
8
  from .utils import CACHE_KEY, cached_value
8
9
  from .utils.site import CACHE_YEARS_KEY
9
10
  from .site.pre_checks.cache_geospatialDatabase import (
@@ -146,6 +147,7 @@ def run(sites: list, years: list = None, include_region: bool = False):
146
147
  batches = range(0, len(unique_years), batch_size)
147
148
 
148
149
  for batch_index in batches:
150
+ logger.info(f"Processing sites in batch {batch_index + 1} of {len(batches)}...")
149
151
  sub_years = unique_years[batch_index:batch_index + batch_size]
150
152
  sites = _run(sites, sub_years, include_region, years_only=True)
151
153
 
@@ -73,7 +73,7 @@ def _run(impact_assessment: dict):
73
73
  land_occupation_m2_kg = land_occupation_per_kg(MODEL, TERM_ID, cycle, site, product)
74
74
  factor = get_region_factor(TERM_ID, impact_assessment, LOOKUP_SUFFIX, 'medium_intensity')
75
75
  inputs_value = convert_value_from_cycle(
76
- product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
76
+ impact_assessment, product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
77
77
  )
78
78
  logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
79
79
  landOccupation=land_occupation_m2_kg,
@@ -68,7 +68,7 @@ def _run(impact_assessment: dict):
68
68
  landTransformation = _value(impact_assessment, _TRANSFORMATION_TERM_ID)
69
69
  region_factor = get_region_factor(TERM_ID, impact_assessment, _LOOKUP_SUFFIX, 'medium_intensity')
70
70
  inputs_value = convert_value_from_cycle(
71
- product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
71
+ impact_assessment, product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
72
72
  )
73
73
  logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
74
74
  landTransformation=landTransformation,
@@ -57,7 +57,7 @@ def run(impact_assessment: dict):
57
57
  cycle = impact_assessment.get('cycle', {})
58
58
  product = get_product(impact_assessment)
59
59
  inputs_value = convert_value_from_cycle(
60
- product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
60
+ impact_assessment, product, sum_input_impacts(cycle.get('inputs', []), TERM_ID), model=MODEL, term_id=TERM_ID
61
61
  )
62
62
  value = sum_values([landUseEffects, inputs_value])
63
63
  logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
@@ -31,7 +31,7 @@ def _run_model(model, cycle: dict):
31
31
 
32
32
  def _run(cycle: dict):
33
33
  values = non_empty_list([_run_model(model, cycle) for model in MODELS])
34
- value = reduce(lambda prev, curr: {**prev, **curr}, values, cycle.get('completeness', {}))
34
+ value = reduce(lambda prev, curr: prev | curr, values, cycle.get('completeness', {}))
35
35
  keys = ','.join([next(iter(val)) for val in values])
36
36
  debugValues(cycle, model=MODEL, keys=keys)
37
37
  return value if len(values) > 0 else None
@@ -40,7 +40,7 @@ def _lookup_value(practice: dict, lookup_name: str):
40
40
  def _practice_value(practice: dict):
41
41
  term = practice.get('term', {})
42
42
  fuel_use = _lookup_value(practice, LOOKUPS['operation'][0])
43
- return {'id': term.get('@id'), 'fuel_use': fuel_use}
43
+ return {'id': term.get('@id'), 'value': practice.get('value'), 'fuel_use': fuel_use}
44
44
 
45
45
 
46
46
  def run(cycle: dict):
@@ -55,6 +55,8 @@ def run(cycle: dict):
55
55
  term_type_operation_complete=operation_complete,
56
56
  values=log_as_table(practices_values))
57
57
 
58
- is_complete = all([operation_complete] + [p.get('fuel_use') for p in practices_values])
58
+ is_complete = all([operation_complete] + [
59
+ all([p.get('fuel_use'), p.get('value') is not None]) for p in practices_values
60
+ ])
59
61
 
60
62
  return is_complete
@@ -78,7 +78,7 @@ def _get_groupings():
78
78
 
79
79
  def get_grouping(groupings: dict, term_id: str):
80
80
  grouping = get_term_lookup(term_id, 'fertGroupingNitrogen')
81
- return {**groupings, **({grouping: term_id} if len(grouping) > 0 else {})}
81
+ return groupings | ({grouping: term_id} if len(grouping) > 0 else {})
82
82
 
83
83
  return reduce(get_grouping, term_ids, {})
84
84
 
@@ -31,8 +31,8 @@ RETURNS = {
31
31
  }
32
32
  TERM_ID = 'precipitationAnnual'
33
33
  EE_PARAMS = {
34
- 'collection': 'ECMWF/ERA5/MONTHLY',
35
- 'band_name': 'total_precipitation',
34
+ 'collection': 'ECMWF/ERA5_LAND/MONTHLY_AGGR',
35
+ 'band_name': 'total_precipitation_sum',
36
36
  'ee_type': 'raster',
37
37
  'reducer': 'mean',
38
38
  'reducer_annual': 'sum'
@@ -27,8 +27,8 @@ TERM_ID = 'precipitationLongTermAnnualMean'
27
27
  START_DATE = '1979-01-01'
28
28
  END_DATE = '2020-12-31'
29
29
  EE_PARAMS = {
30
- 'collection': 'ECMWF/ERA5/MONTHLY',
31
- 'band_name': 'total_precipitation',
30
+ 'collection': 'ECMWF/ERA5_LAND/MONTHLY_AGGR',
31
+ 'band_name': 'total_precipitation_sum',
32
32
  'ee_type': 'raster',
33
33
  'reducer': 'mean',
34
34
  'reducer_annual': 'sum',
@@ -31,8 +31,8 @@ RETURNS = {
31
31
  }
32
32
  TERM_ID = 'precipitationMonthly'
33
33
  EE_PARAMS = {
34
- 'collection': 'ECMWF/ERA5/MONTHLY',
35
- 'band_name': 'total_precipitation',
34
+ 'collection': 'ECMWF/ERA5_LAND/MONTHLY_AGGR',
35
+ 'band_name': 'total_precipitation_sum',
36
36
  'ee_type': 'raster',
37
37
  'reducer': 'mean',
38
38
  'reducer_annual': 'sum'
@@ -31,8 +31,8 @@ RETURNS = {
31
31
  }
32
32
  TERM_ID = 'temperatureAnnual'
33
33
  EE_PARAMS = {
34
- 'collection': 'ECMWF/ERA5/MONTHLY',
35
- 'band_name': 'mean_2m_air_temperature',
34
+ 'collection': 'ECMWF/ERA5_LAND/MONTHLY_AGGR',
35
+ 'band_name': 'temperature_2m',
36
36
  'ee_type': 'raster',
37
37
  'reducer': 'mean',
38
38
  'reducer_annual': 'mean'
@@ -27,8 +27,8 @@ TERM_ID = 'temperatureLongTermAnnualMean'
27
27
  START_DATE = '1979-01-01'
28
28
  END_DATE = '2020-12-31'
29
29
  EE_PARAMS = {
30
- 'collection': 'ECMWF/ERA5/MONTHLY',
31
- 'band_name': 'mean_2m_air_temperature',
30
+ 'collection': 'ECMWF/ERA5_LAND/MONTHLY_AGGR',
31
+ 'band_name': 'temperature_2m',
32
32
  'ee_type': 'raster',
33
33
  'reducer': 'mean',
34
34
  'reducer_annual': 'mean',
@@ -31,8 +31,8 @@ RETURNS = {
31
31
  }
32
32
  TERM_ID = 'temperatureMonthly'
33
33
  EE_PARAMS = {
34
- 'collection': 'ECMWF/ERA5/MONTHLY',
35
- 'band_name': 'mean_2m_air_temperature',
34
+ 'collection': 'ECMWF/ERA5_LAND/MONTHLY_AGGR',
35
+ 'band_name': 'temperature_2m',
36
36
  'ee_type': 'raster',
37
37
  'reducer': 'mean',
38
38
  'reducer_annual': 'mean'
@@ -17,6 +17,20 @@ from hestia_earth.utils.tools import safe_parse_float, to_precision, non_empty_v
17
17
  from hestia_earth.models.log import logRequirements, log_as_table, logShouldRun
18
18
  from hestia_earth.models.utils.management import _new_management
19
19
  from hestia_earth.models.utils.term import get_lookup_value
20
+ from .utils import (
21
+ IPCC_LAND_USE_CATEGORY_ANNUAL,
22
+ IPCC_LAND_USE_CATEGORY_PERENNIAL,
23
+ LAND_USE_TERMS_FOR_TRANSFORMATION,
24
+ ANNUAL_CROPLAND,
25
+ PERMANENT_CROPLAND,
26
+ FOREST_LAND,
27
+ OTHER_LAND,
28
+ PERMANENT_PASTURE,
29
+ TOTAL_CROPLAND,
30
+ TOTAL_AGRICULTURAL_CHANGE,
31
+ ALL_LAND_USE_TERMS,
32
+ crop_ipcc_land_use_category,
33
+ )
20
34
  from . import MODEL
21
35
 
22
36
  REQUIREMENTS = {
@@ -69,30 +83,6 @@ LOOKUPS = {
69
83
  MODEL_KEY = 'landCover'
70
84
 
71
85
  LAND_AREA = LOOKUPS["region-faostatArea"][3]
72
- TOTAL_CROPLAND = "Cropland"
73
- ANNUAL_CROPLAND = "Arable land"
74
- FOREST_LAND = "Forest land"
75
- OTHER_LAND = "Other land"
76
- PERMANENT_CROPLAND = "Permanent crops"
77
- PERMANENT_PASTURE = "Permanent meadows and pastures"
78
- TOTAL_AGRICULTURAL_CHANGE = "Total agricultural change"
79
- ALL_LAND_USE_TERMS = [
80
- FOREST_LAND,
81
- TOTAL_CROPLAND,
82
- ANNUAL_CROPLAND,
83
- PERMANENT_CROPLAND,
84
- PERMANENT_PASTURE,
85
- OTHER_LAND
86
- ]
87
- # Mapping from Land use terms to Management node terms.
88
- # land use term: (@id, name)
89
- LAND_USE_TERMS_FOR_TRANSFORMATION = {
90
- FOREST_LAND: ("forest", "Forest"),
91
- ANNUAL_CROPLAND: ("annualCropland", "Annual cropland"),
92
- PERMANENT_CROPLAND: ("permanentCropland", "Permanent cropland"),
93
- PERMANENT_PASTURE: ("permanentPasture", "Permanent pasture"),
94
- OTHER_LAND: ("otherLand", OTHER_LAND) # Not used yet
95
- }
96
86
  SITE_TYPES = {
97
87
  SiteSiteType.CROPLAND.value,
98
88
  SiteSiteType.FOREST.value,
@@ -100,8 +90,6 @@ SITE_TYPES = {
100
90
  SiteSiteType.PERMANENT_PASTURE.value
101
91
  }
102
92
  DEFAULT_WINDOW_IN_YEARS = 20
103
- IPCC_LAND_USE_CATEGORY_ANNUAL = "Annual crops"
104
- IPCC_LAND_USE_CATEGORY_PERENNIAL = "Perennial crops"
105
93
  OUTPUT_SIGNIFICANT_DIGITS = 3
106
94
 
107
95
 
@@ -137,22 +125,6 @@ def _lookup_land_use_type(nodes: list) -> str:
137
125
  )
138
126
 
139
127
 
140
- def _crop_ipcc_land_use_category(
141
- crop_term_id: str,
142
- lookup_term_type: str = TermTermType.LANDCOVER.value
143
- ) -> str:
144
- """
145
- Looks up the crop in the lookup.
146
- Returns the IPCC_LAND_USE_CATEGORY.
147
- """
148
- return get_lookup_value(
149
- lookup_term={"@id": crop_term_id, "type": "Term", "termType": lookup_term_type},
150
- column=LOOKUPS.get("crop")[1],
151
- model=MODEL,
152
- term={"@id": crop_term_id, "type": "Term", "termType": lookup_term_type}
153
- )
154
-
155
-
156
128
  def get_changes(country_id: str, end_year: int) -> dict:
157
129
  """
158
130
  For each entry in ALL_LAND_USE_TERMS, creates a key: value in output dictionary, also TOTAL
@@ -399,7 +371,7 @@ def _get_complete_faostat_to_crop_mapping() -> dict:
399
371
  get_table_value(lookup, 'termid', crop_term_id, column_name("cropGroupingFaostatArea"))
400
372
  )
401
373
  if key:
402
- mappings[key].append(_crop_ipcc_land_use_category(crop_term_id=crop_term_id, lookup_term_type="crop"))
374
+ mappings[key].append(crop_ipcc_land_use_category(crop_term_id=crop_term_id, lookup_term_type="crop"))
403
375
  return {
404
376
  fao_name: max(set(crop_terms), key=crop_terms.count)
405
377
  for fao_name, crop_terms in mappings.items()
@@ -420,6 +392,18 @@ def _get_harvested_area(country_id: str, year: int, faostat_name: str) -> float:
420
392
  )
421
393
 
422
394
 
395
+ def _get_term_id_for_crop(nodes: set, land_type: str) -> str:
396
+ """Use the original crop term id for permanent/perennial crops and the land use id for other types."""
397
+ result = next(
398
+ (node for node in nodes if crop_ipcc_land_use_category(node[0]) == IPCC_LAND_USE_CATEGORY_PERENNIAL), None
399
+ )
400
+ return (
401
+ # Take first perennial crop - not multi-cropping
402
+ result[0] if land_type == PERMANENT_CROPLAND and result else
403
+ LAND_USE_TERMS_FOR_TRANSFORMATION[land_type][0]
404
+ )
405
+
406
+
423
407
  def _run_make_management_nodes(existing_nodes: list, percentage_transformed_from: dict, start_year: int) -> list:
424
408
  """Creates a list of new management nodes, excluding any dates matching existing ones."""
425
409
  existing_nodes_set = {
@@ -435,7 +419,8 @@ def _run_make_management_nodes(existing_nodes: list, percentage_transformed_from
435
419
  "percentage": 0 if ratio == -0.0 else to_precision(
436
420
  number=ratio * 100,
437
421
  digits=OUTPUT_SIGNIFICANT_DIGITS
438
- )
422
+ ),
423
+ "term_id": _get_term_id_for_crop(existing_nodes_set, land_type=land_type)
439
424
  }
440
425
  for land_type, ratio in percentage_transformed_from.items()
441
426
  ]
@@ -443,7 +428,7 @@ def _run_make_management_nodes(existing_nodes: list, percentage_transformed_from
443
428
 
444
429
  return [
445
430
  _management(
446
- term_id=LAND_USE_TERMS_FOR_TRANSFORMATION[value.get("land_type")][0],
431
+ term_id=value.get("term_id"),
447
432
  value=value.get("percentage"),
448
433
  start_date=value.get("land_management_key")[1],
449
434
  end_date=value.get("land_management_key")[2]
@@ -698,7 +683,7 @@ def _should_run_historical_land_use_change_single_crop(
698
683
  sum_of_site_areas_is_100
699
684
  ]
700
685
  )
701
- logShouldRun(site, MODEL, term=term.get("@id"), should_run=should_run, key=MODEL_KEY)
686
+ logShouldRun(site, MODEL, term.get("@id"), should_run, model_key=MODEL_KEY)
702
687
 
703
688
  return should_run, site_area
704
689
 
@@ -0,0 +1,49 @@
1
+ """
2
+ Creates an [emissionsResourceUse](https://hestia.earth/schema/Emission) for every landCover land transformation.
3
+ contained within the [ImpactAssesment.cycle](https://hestia.earth/schema/ImpactAssessment#cycle), averaged over the last
4
+ 100 years.
5
+
6
+ It does this by multiplying the land occupation during the cycle by the
7
+ [Site](https://www-staging.hestia.earth/schema/Site) area 100 years ago and dividing by 100.
8
+
9
+ Land transformation from [land type] 100 years =
10
+ (Land occupation, during Cycle * Site Percentage Area 100 years ago [land type] / 100) / 100
11
+ """
12
+ from .resourceUse_utils import run_resource_use
13
+
14
+ REQUIREMENTS = {
15
+ "ImpactAssessment": {
16
+ "Site": {
17
+ "management": [{"@type": "Management", "value": ">=0", "term.termType": "landCover", "endDate": ""}]
18
+ },
19
+ "emissionsResourceUse": [
20
+ {
21
+ "@type": "Indicator",
22
+ "term.@id": "landOccupationDuringCycle",
23
+ "landCover": {
24
+ "@type": "Term",
25
+ "termType": "landCover"
26
+ },
27
+ "value": ">=0"
28
+ }
29
+ ],
30
+ "endDate": ""
31
+ }
32
+ }
33
+ RETURNS = {
34
+ "Indicator": [{
35
+ "value": "",
36
+ "landCover": "",
37
+ "previousLandCover": ""
38
+ }]
39
+ }
40
+ TERM_ID = 'landTransformation100YearAverageDuringCycle'
41
+ _HISTORIC_DATE_OFFSET = 100
42
+
43
+
44
+ def run(impact_assessment: dict):
45
+ return run_resource_use(
46
+ impact_assessment=impact_assessment,
47
+ historic_date_offset=_HISTORIC_DATE_OFFSET,
48
+ term_id=TERM_ID
49
+ )
@@ -0,0 +1,49 @@
1
+ """
2
+ Creates an [emissionsResourceUse](https://hestia.earth/schema/Emission) for every landCover land transformation.
3
+ contained within the [ImpactAssesment.cycle](https://hestia.earth/schema/ImpactAssessment#cycle), averaged over the last
4
+ 20 years.
5
+
6
+ It does this by multiplying the land occupation during the cycle by the
7
+ [Site](https://www-staging.hestia.earth/schema/Site) area 20 years ago and dividing by 20.
8
+
9
+ Land transformation from [land type] 20 years =
10
+ (Land occupation, during Cycle * Site Percentage Area 20 years ago [land type] / 100) / 20
11
+ """
12
+ from .resourceUse_utils import run_resource_use
13
+
14
+ REQUIREMENTS = {
15
+ "ImpactAssessment": {
16
+ "Site": {
17
+ "management": [{"@type": "Management", "value": ">=0", "term.termType": "landCover", "endDate": ""}]
18
+ },
19
+ "emissionsResourceUse": [
20
+ {
21
+ "@type": "Indicator",
22
+ "term.@id": "landOccupationDuringCycle",
23
+ "landCover": {
24
+ "@type": "Term",
25
+ "termType": "landCover"
26
+ },
27
+ "value": ">=0"
28
+ }
29
+ ],
30
+ "endDate": ""
31
+ }
32
+ }
33
+ RETURNS = {
34
+ "Indicator": [{
35
+ "value": "",
36
+ "landCover": "",
37
+ "previousLandCover": ""
38
+ }]
39
+ }
40
+ TERM_ID = 'landTransformation20YearAverageDuringCycle'
41
+ _HISTORIC_DATE_OFFSET = 20
42
+
43
+
44
+ def run(impact_assessment: dict):
45
+ return run_resource_use(
46
+ impact_assessment=impact_assessment,
47
+ historic_date_offset=_HISTORIC_DATE_OFFSET,
48
+ term_id=TERM_ID
49
+ )