hestia-earth-models 0.64.6__py3-none-any.whl → 0.64.8__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 (37) hide show
  1. hestia_earth/models/cycle/animal/milkYield.py +10 -22
  2. hestia_earth/models/cycle/unknownPreSeasonWaterRegime.py +0 -1
  3. hestia_earth/models/environmentalFootprintV3/soilQualityIndexLandOccupation.py +25 -24
  4. hestia_earth/models/environmentalFootprintV3/soilQualityIndexLandTransformation.py +182 -0
  5. hestia_earth/models/environmentalFootprintV3/soilQualityIndexTotalLandUseEffects.py +66 -0
  6. hestia_earth/models/environmentalFootprintV3/utils.py +1 -1
  7. hestia_earth/models/hyde32/utils.py +4 -0
  8. hestia_earth/models/ipcc2019/animal/pastureGrass.py +3 -1
  9. hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChangeLandUseChange.py +191 -0
  10. hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChangeLandUseChange.py +204 -0
  11. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +255 -35
  12. hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChangeManagementChange.py +63 -149
  13. hestia_earth/models/ipcc2019/pastureGrass.py +3 -1
  14. hestia_earth/models/mocking/search-results.json +337 -319
  15. hestia_earth/models/pooreNemecek2018/landOccupationDuringCycle.py +12 -8
  16. hestia_earth/models/site/management.py +3 -5
  17. hestia_earth/models/transformation/input/excreta.py +9 -13
  18. hestia_earth/models/utils/input.py +5 -2
  19. hestia_earth/models/utils/site.py +4 -2
  20. hestia_earth/models/version.py +1 -1
  21. {hestia_earth_models-0.64.6.dist-info → hestia_earth_models-0.64.8.dist-info}/METADATA +2 -2
  22. {hestia_earth_models-0.64.6.dist-info → hestia_earth_models-0.64.8.dist-info}/RECORD +37 -29
  23. tests/models/cycle/animal/test_milkYield.py +1 -14
  24. tests/models/environmentalFootprintV3/test_freshwaterEcotoxicityPotentialCtue.py +4 -2
  25. tests/models/environmentalFootprintV3/test_soilQualityIndexLandOccupation.py +16 -24
  26. tests/models/environmentalFootprintV3/test_soilQualityIndexLandTransformation.py +113 -0
  27. tests/models/environmentalFootprintV3/test_soilQualityIndexTotalLandUseEffects.py +50 -0
  28. tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChangeLandUseChange.py +83 -0
  29. tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChangeLandUseChange.py +83 -0
  30. tests/models/ipcc2019/test_co2ToAirCarbonStockChange_utils.py +6 -6
  31. tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChangeManagementChange.py +5 -4
  32. tests/models/pooreNemecek2018/test_landOccupationDuringCycle.py +4 -1
  33. tests/models/site/test_management.py +4 -1
  34. tests/models/utils/test_input.py +65 -1
  35. {hestia_earth_models-0.64.6.dist-info → hestia_earth_models-0.64.8.dist-info}/LICENSE +0 -0
  36. {hestia_earth_models-0.64.6.dist-info → hestia_earth_models-0.64.8.dist-info}/WHEEL +0 -0
  37. {hestia_earth_models-0.64.6.dist-info → hestia_earth_models-0.64.8.dist-info}/top_level.txt +0 -0
@@ -1,18 +1,16 @@
1
1
  """
2
2
  Milk Yield
3
3
 
4
- This model gap-fills the practice "Milk yield per animal X (raw/FPCM)" (e.g. `Milk yield per cow (raw)`) when:
5
- - the practice is added to the Cycle itself, and we can match it with the `term` of an Animal blank node;
6
- - the Animal blank node Term has a lookup value of `kgDayMilkForFeedingOffspring`.
4
+ This model gap-fills the practice "Milk yield per animal X (raw/FPCM)" (e.g. `Milk yield per cow (raw)`) when
5
+ the practice is added to the Cycle itself, and we can match it with the `term` of an Animal blank node.
7
6
  """
8
7
  from hestia_earth.schema import TermTermType
9
8
  from hestia_earth.utils.model import filter_list_term_type
10
- from hestia_earth.utils.tools import non_empty_list, safe_parse_float
9
+ from hestia_earth.utils.tools import non_empty_list
11
10
 
12
11
  from hestia_earth.models.log import logShouldRun, logRequirements, log_blank_nodes_id
13
12
  from hestia_earth.models.utils.blank_node import merge_blank_nodes
14
13
  from hestia_earth.models.utils.term import get_lookup_value
15
- from hestia_earth.models.utils.practice import _new_practice
16
14
  from .. import MODEL
17
15
 
18
16
  REQUIREMENTS = {
@@ -21,12 +19,10 @@ REQUIREMENTS = {
21
19
  "@type": "Animal",
22
20
  "term.termType": "liveAnimal"
23
21
  }],
24
- "optional": {
25
- "practices": [{
26
- "@type": "Practice",
27
- "term.termType": "animalManagement"
28
- }]
29
- }
22
+ "practices": [{
23
+ "@type": "Practice",
24
+ "term.termType": "animalManagement"
25
+ }]
30
26
  }
31
27
  }
32
28
  RETURNS = {
@@ -38,27 +34,19 @@ RETURNS = {
38
34
  }]
39
35
  }
40
36
  LOOKUPS = {
41
- "liveAnimal": ["milkYieldPracticeTermIds", "kgDayMilkForFeedingOffspring"]
37
+ "liveAnimal": "milkYieldPracticeTermIds"
42
38
  }
43
39
 
44
40
  MODEL_KEY = 'milkYield'
45
41
 
46
42
 
47
- def _default_practice(animal: dict, practice_term_id: str):
48
- term = animal.get('term', {})
49
- value = get_lookup_value(term, LOOKUPS['liveAnimal'][1], model=MODEL, model_key=MODEL_KEY)
50
- return (_new_practice(practice_term_id) | {'value': [safe_parse_float(value)]}) if value else None
51
-
52
-
53
43
  def _run(cycle: dict, animal: dict):
54
44
  term = animal.get('term', {})
55
45
  term_id = term.get('@id')
56
- value = get_lookup_value(term, LOOKUPS['liveAnimal'][0], model=MODEL, model_key=MODEL_KEY)
46
+ value = get_lookup_value(term, LOOKUPS['liveAnimal'], model=MODEL, model_key=MODEL_KEY)
57
47
  practice_ids = non_empty_list((value or '').split(';'))
58
48
  practices = non_empty_list(
59
- [p for p in cycle.get('practices', []) if p.get('term', {}).get('@id') in practice_ids] or (
60
- [_default_practice(animal, practice_ids[0])] if practice_ids else []
61
- )
49
+ [p for p in cycle.get('practices', []) if p.get('term', {}).get('@id') in practice_ids]
62
50
  )
63
51
 
64
52
  logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
@@ -36,7 +36,6 @@ def _practice():
36
36
  def _should_run(cycle: dict):
37
37
  practices = cycle.get('practices', [])
38
38
  flooded_terms = get_flooded_pre_season_terms()
39
- print(flooded_terms)
40
39
  existing_practice = next((p for p in practices if p.get('term', {}).get('@id') in flooded_terms), None)
41
40
 
42
41
  logRequirements(cycle, model=MODEL, term=TERM_ID,
@@ -12,34 +12,34 @@ from . import MODEL
12
12
  from .utils import get_coefficient_factor
13
13
 
14
14
  REQUIREMENTS = {
15
- "Cycle": {
16
- "siteDuration": "> 0",
17
- "siteArea": "> 0",
18
- "site": {"siteType": "",
19
- "@type": "Site",
20
- "management": [{"@type": "Management", "value": "", "term.termType": "landCover"}],
21
- "country": {"@type": "Term", "termType": "region"}
22
- },
23
- "optional": {
24
- "otherSitesDuration": "> 0",
25
- "otherSitesArea": "> 0",
26
- "otherSites": [
27
- {
28
- "siteType": "",
15
+ "ImpactAssessment": {
16
+ "cycle": {
17
+ "@type": "Cycle",
18
+ "siteDuration": "> 0",
19
+ "siteArea": "> 0",
20
+ "site": {
21
+ "siteType": "",
22
+ "@type": "Site",
23
+ "management": [{"@type": "Management", "value": "", "term.termType": "landCover"}],
24
+ "country": {"@type": "Term", "termType": "region"}
25
+ },
26
+ "optional": {
27
+ "otherSitesDuration": "> 0",
28
+ "otherSitesArea": "> 0",
29
+ "otherSites": [{
29
30
  "@type": "Site",
31
+ "siteType": "",
30
32
  "management": [{"@type": "Management", "value": "", "term.termType": "landCover"}],
31
33
  "country": {"@type": "Term", "termType": "region"}
32
- }
33
- ]
34
+ }]
35
+ }
34
36
  }
35
37
  }
36
38
  }
37
39
  LOOKUPS = {
38
40
  "@doc": "Performs lookup on landCover.csv for column headers and region-pefTermGrouping-landOccupation.csv for CFs",
39
41
  "region-pefTermGrouping-landOccupation": "",
40
- "landCover": [
41
- "pefTermGrouping"
42
- ]
42
+ "landCover": "pefTermGrouping"
43
43
  }
44
44
 
45
45
  RETURNS = {
@@ -62,7 +62,8 @@ def _run(sites: list):
62
62
  return _indicator(list_sum(values)) if values else None
63
63
 
64
64
 
65
- def _should_run(cycle: dict):
65
+ def _should_run(impact_assessment: dict):
66
+ cycle = impact_assessment.get('cycle', {})
66
67
  end_date = cycle.get('endDate')
67
68
 
68
69
  has_site = bool(cycle.get('site', False))
@@ -76,8 +77,8 @@ def _should_run(cycle: dict):
76
77
  {
77
78
  'site-type': site.get('siteType'),
78
79
  'country-id': site.get('country', {}).get('@id'),
79
- 'area': site_areas[index],
80
- 'duration': site_durations[index],
80
+ 'area': site_areas[index] if len(site_areas) >= index + 1 else None,
81
+ 'duration': site_durations[index] if len(site_durations) >= index + 1 else None,
81
82
  'landCover-id': (most_relevant_blank_node_by_type(
82
83
  site.get("management", []),
83
84
  term_type=TermTermType.LANDCOVER.value,
@@ -123,6 +124,6 @@ def _should_run(cycle: dict):
123
124
  return should_run, valid_sites
124
125
 
125
126
 
126
- def run(cycle: dict):
127
- should_run, sites = _should_run(cycle)
127
+ def run(impact_assessment: dict):
128
+ should_run, sites = _should_run(impact_assessment)
128
129
  return _run(sites) if should_run else None
@@ -0,0 +1,182 @@
1
+ """
2
+ Characterises [soilQualityIndexLandTransformation](https://hestia.earth/term/soilQualityIndexLandTransformation)
3
+ based on an updated [LANCA model (De Laurentiis et al. 2019)](
4
+ http://publications.jrc.ec.europa.eu/repository/handle/JRC113865) and on the LANCA (Regionalised) Characterisation
5
+ Factors version 2.5 (Horn and Meier, 2018).
6
+ """
7
+ from typing import List
8
+
9
+ from hestia_earth.schema import TermTermType
10
+ from hestia_earth.utils.lookup import download_lookup
11
+ from hestia_earth.utils.model import filter_list_term_type
12
+ from hestia_earth.utils.tools import list_sum, safe_parse_date, non_empty_list
13
+
14
+ from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
15
+ from hestia_earth.models.utils import hectar_to_square_meter
16
+ from hestia_earth.models.utils import pairwise
17
+ from . import MODEL
18
+ from .utils import get_coefficient_factor
19
+ from ..utils.indicator import _new_indicator
20
+ from ..utils.landCover import get_pef_grouping
21
+ from ..utils.lookup import fallback_country
22
+
23
+ REQUIREMENTS = {
24
+ "ImpactAssessment": {
25
+ "cycle": {
26
+ "@type": "Cycle",
27
+ "site": {
28
+ "area": "> 0",
29
+ "@type": "Site",
30
+ "management": [{"@type": "Management", "term.termType": "landCover"}],
31
+ "optional": {"country": {"@type": "Term", "termType": "region"}}
32
+ },
33
+ "optional": {
34
+ "otherSitesArea": "> 0",
35
+ "otherSites": [{
36
+ "@type": "Site",
37
+ "management": [{"@type": "Management", "term.termType": "landCover"}],
38
+ "optional": {"country": {"@type": "Term", "termType": "region"}}
39
+ }]
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ LOOKUPS = {
46
+ "@doc": "Uses `landCover.csv` for column headers and region-pefTermGrouping-landTransformation-X.csv for to/from CFs. (CFs in `region-pefTermGrouping-landTransformation-from.csv` appear to be the opposite values as those in `region-pefTermGrouping-landTransformation-to.csv` but can be different in some cases)", # noqa: E501
47
+ "region-pefTermGrouping-landTransformation-from": "",
48
+ "region-pefTermGrouping-landTransformation-to": "",
49
+ "landCover": "pefTermGrouping"
50
+ }
51
+
52
+ from_lookup_file = f"{list(LOOKUPS.keys())[1]}.csv"
53
+ to_lookup_file = f"{list(LOOKUPS.keys())[2]}.csv"
54
+
55
+ RETURNS = {
56
+ "Indicator": {
57
+ "value": ""
58
+ }
59
+ }
60
+
61
+ TERM_ID = 'soilQualityIndexLandTransformation'
62
+
63
+
64
+ def _indicator(value: float):
65
+ indicator = _new_indicator(TERM_ID, MODEL)
66
+ indicator['value'] = value
67
+ return indicator
68
+
69
+
70
+ def _run(sites: List[dict]):
71
+ result = []
72
+ for site in sites:
73
+ values = [(transformation_from_factor + transformation_to_factor) * hectar_to_square_meter(site['area'])
74
+ for transformation_from_factor, transformation_to_factor in site['transformation_factors']]
75
+ result.append(list_sum(values))
76
+ return _indicator(list_sum(result)) if result else None
77
+
78
+
79
+ def _should_run(impact_assessment: dict):
80
+ cycle = impact_assessment.get('cycle', {})
81
+
82
+ has_site = bool(cycle.get('site', {}))
83
+ site_area = cycle.get('site', {}).get("area", False)
84
+ has_area = site_area > 0
85
+
86
+ has_other_sites = bool(cycle.get('otherSites', []))
87
+
88
+ all_sites = non_empty_list([cycle.get('site')] + cycle.get('otherSites', []))
89
+ site_areas = [cycle.get('site', {}).get('area')] + cycle.get('otherSitesArea', [])
90
+
91
+ sites = [
92
+ {
93
+ 'site_id': site.get('@id', site.get('id')),
94
+ 'transformation_pairs': list(pairwise(
95
+ sorted(filter_list_term_type(site.get("management", []), TermTermType.LANDCOVER.value),
96
+ key=lambda d: safe_parse_date(d['endDate'])))),
97
+ 'country_id_str': site.get('country', {}).get('@id', ''),
98
+ 'area': site_areas[index] if len(site_areas) >= index + 1 else None,
99
+ }
100
+ for index, site in enumerate(all_sites)
101
+ ]
102
+
103
+ sites = [
104
+ site for site in sites
105
+ if all([
106
+ (site.get('area') or 0) > 0,
107
+ site.get('transformation_pairs', [])
108
+ ])
109
+ ]
110
+
111
+ sites = [
112
+ site |
113
+ {
114
+ 'column_names': [(get_pef_grouping(from_transformation['term']['@id']),
115
+ get_pef_grouping(to_transformation['term']['@id']))
116
+ for from_transformation, to_transformation in site['transformation_pairs']],
117
+ } for site in sites
118
+ ]
119
+
120
+ has_lookup_column = lambda s: s['column_names'] and all([all(pair) for pair in s['column_names']]) # noqa: E731
121
+ valid_sites = [site for site in sites if has_lookup_column(site)]
122
+
123
+ has_valid_sites = bool(valid_sites)
124
+
125
+ valid_sites = [
126
+ site |
127
+ {
128
+ 'country_id': fallback_country(site['country_id_str'],
129
+ [download_lookup(from_lookup_file), download_lookup(to_lookup_file)]
130
+ )
131
+ } for site in valid_sites
132
+ ]
133
+
134
+ valid_sites = [
135
+ site |
136
+ {
137
+ 'transformation_factors': [(get_coefficient_factor(lookup_name=from_lookup_file,
138
+ country_id=site['country_id'], term_id=TERM_ID,
139
+ occupation_type=from_transformation_header),
140
+ get_coefficient_factor(lookup_name=to_lookup_file,
141
+ country_id=site['country_id'], term_id=TERM_ID,
142
+ occupation_type=to_transformation_header))
143
+ for from_transformation_header, to_transformation_header in site['column_names']]
144
+ } for site in valid_sites
145
+ ]
146
+
147
+ log_equivalent_eu_pef_land_use_names = [
148
+ [{
149
+ 'site-id': site['site_id'],
150
+ 'hestia-term': hestia_term,
151
+ 'corine-term': corine_term,
152
+ } for hestia_term, corine_term in site['column_names']
153
+ ] for site in valid_sites]
154
+
155
+ log_transformation_factors = [
156
+ [{
157
+ 'site-id': site['site_id'],
158
+ 'country-id-used-for-factors': site['country_id'],
159
+ 'country-id-in-input': site['country_id_str'],
160
+ 'factor-from': from_transformation_header,
161
+ 'factor-to': to_transformation_header,
162
+ } for from_transformation_header, to_transformation_header in site['transformation_factors']
163
+ ] for site in valid_sites]
164
+
165
+ logRequirements(impact_assessment, model=MODEL, term=TERM_ID,
166
+ has_site=has_site,
167
+ has_area=has_area,
168
+ has_other_sites=has_other_sites,
169
+ has_valid_sites=has_valid_sites,
170
+ equivalent_EU_PEF_landUse_names=log_as_table(log_equivalent_eu_pef_land_use_names),
171
+ transformation_factors=log_as_table(log_transformation_factors)
172
+ )
173
+ should_run = has_valid_sites
174
+
175
+ logShouldRun(impact_assessment, MODEL, TERM_ID, should_run)
176
+
177
+ return should_run, valid_sites
178
+
179
+
180
+ def run(impact_assessment: dict):
181
+ should_run, sites = _should_run(impact_assessment)
182
+ return _run(sites) if should_run else None
@@ -0,0 +1,66 @@
1
+ """
2
+ Characterises [soilQualityIndexTotalLandUseEffects](https://hestia.earth/term/soilQualityIndexTotalLandUseEffects)
3
+ based on an updated [LANCA model (De Laurentiis et al. 2019)](
4
+ http://publications.jrc.ec.europa.eu/repository/handle/JRC113865) and on the LANCA (Regionalised) Characterisation
5
+ Factors version 2.5 (Horn and Meier, 2018).
6
+ """
7
+ from hestia_earth.utils.model import find_term_match
8
+
9
+ from hestia_earth.models.log import logRequirements, logShouldRun
10
+ from . import MODEL
11
+ from ..utils.indicator import _new_indicator
12
+
13
+ REQUIREMENTS = {
14
+ "ImpactAssessment": {
15
+ "emissionsResourceUse": [
16
+ {"@type": "Indicator", "value": "", "term.@id": "soilQualityIndexLandOccupation"},
17
+ {"@type": "Indicator", "value": "", "term.@id": "soilQualityIndexLandTransformation"}
18
+ ]
19
+ }
20
+ }
21
+
22
+ RETURNS = {
23
+ "Indicator": {
24
+ "value": "",
25
+ "methodTier": "tier 1",
26
+ "statsDefinition": "modelled"
27
+ }
28
+ }
29
+ TERM_ID = 'soilQualityIndexTotalLandUseEffects'
30
+
31
+
32
+ def _indicator(value: float):
33
+ indicator = _new_indicator(TERM_ID, MODEL)
34
+ indicator['value'] = value
35
+ return indicator
36
+
37
+
38
+ def _run(land_occupation_indicator, transformation_indicator):
39
+ value = land_occupation_indicator['value'] + transformation_indicator['value']
40
+ return _indicator(value) if value else None
41
+
42
+
43
+ def _should_run(impactassessment: dict):
44
+ land_occupation_indicator = find_term_match(impactassessment.get('emissionsResourceUse', []),
45
+ "soilQualityIndexLandOccupation")
46
+ transformation_indicator = find_term_match(impactassessment.get('emissionsResourceUse', []),
47
+ "soilQualityIndexLandTransformation")
48
+
49
+ has_valid_values = all([isinstance(land_occupation_indicator.get('value', None), (int, float)),
50
+ isinstance(transformation_indicator.get('value', None), (int, float))])
51
+
52
+ logRequirements(impactassessment, model=MODEL, term=TERM_ID,
53
+ transformation_indicator=transformation_indicator,
54
+ land_occupation_indicator=land_occupation_indicator,
55
+ has_valid_values=has_valid_values
56
+ )
57
+
58
+ should_run = all([transformation_indicator, land_occupation_indicator, has_valid_values])
59
+
60
+ logShouldRun(impactassessment, MODEL, TERM_ID, should_run)
61
+ return should_run, land_occupation_indicator, transformation_indicator
62
+
63
+
64
+ def run(impactassessment: dict):
65
+ should_run, land_occupation_indicator, transformation_indicator = _should_run(impactassessment)
66
+ return _run(land_occupation_indicator, transformation_indicator) if should_run else None
@@ -12,6 +12,6 @@ def get_coefficient_factor(lookup_name: str, country_id: str, occupation_type: O
12
12
  """
13
13
  coefficient = get_table_value(download_lookup(lookup_name), 'termid', country_id, column_name(occupation_type))
14
14
  debugMissingLookup(
15
- lookup_name, 'termid', country_id, column_name(occupation_type), coefficient, model=MODEL, term=term_id
15
+ lookup_name, 'termid', country_id, occupation_type, coefficient, model=MODEL, term=term_id
16
16
  )
17
17
  return coefficient
@@ -45,6 +45,8 @@ def _run(impact_assessment: dict, term_id: str, land_occupation_m2: float, facto
45
45
 
46
46
 
47
47
  def _should_run(impact_assessment: dict, term_id: str, from_site_type: SiteSiteType, years: int):
48
+ site = get_site(impact_assessment)
49
+ has_site = bool(site)
48
50
  cycle = impact_assessment.get('cycle', {})
49
51
  product = get_product(impact_assessment)
50
52
  site = get_site(impact_assessment)
@@ -52,10 +54,12 @@ def _should_run(impact_assessment: dict, term_id: str, from_site_type: SiteSiteT
52
54
  land_transformation_factor = _get_emission_factor(term_id, impact_assessment, years, from_site_type)
53
55
 
54
56
  logRequirements(impact_assessment, model=MODEL, term=term_id,
57
+ has_site=has_site,
55
58
  land_occupation_m2_kg=land_occupation_m2_kg,
56
59
  land_transformation_factor=land_transformation_factor)
57
60
 
58
61
  should_run = all([
62
+ has_site,
59
63
  land_occupation_m2_kg is not None,
60
64
  land_occupation_m2_kg == 0 or land_transformation_factor is not None
61
65
  ])
@@ -137,10 +137,12 @@ LOOKUPS = {
137
137
  "isWoolProducingAnimal"
138
138
  ],
139
139
  "system-liveAnimal-activityCoefficient-ipcc2019": "using animal term @id",
140
+ "landCover": "grazedPastureGrassInputId",
140
141
  "crop-property": ["energyDigestibilityRuminants", "energyContentHigherHeatingValue"],
141
142
  "crop": "grazedPastureGrassInputId",
142
143
  "forage-property": ["energyDigestibilityRuminants", "energyContentHigherHeatingValue"],
143
- "landCover": "grazedPastureGrassInputId"
144
+ "feedFoodAdditive": "hasEnergyContent",
145
+ "feedFoodAdditive-property": ["energyDigestibilityRuminants", "energyContentHigherHeatingValue"]
144
146
  }
145
147
  RETURNS = {
146
148
  "Animal": [{
@@ -0,0 +1,191 @@
1
+ from hestia_earth.schema import CycleFunctionalUnit, EmissionMethodTier, MeasurementMethodClassification, SiteSiteType
2
+
3
+ from hestia_earth.models.log import logRequirements, logShouldRun
4
+ from hestia_earth.models.utils.blank_node import cumulative_nodes_term_match
5
+ from hestia_earth.models.utils.emission import _new_emission
6
+
7
+ from .co2ToAirCarbonStockChange_utils import create_run_function, create_should_run_function
8
+ from . import MODEL
9
+
10
+ REQUIREMENTS = {
11
+ "Cycle": {
12
+ "site": {
13
+ "measurements": [
14
+ {
15
+ "@type": "Measurement",
16
+ "value": "",
17
+ "dates": "",
18
+ "depthUpper": "0",
19
+ "depthLower": "30",
20
+ "term.@id": "aboveGroundBiomass"
21
+ }
22
+ ]
23
+ },
24
+ "functionalUnit": "1 ha",
25
+ "endDate": "",
26
+ "optional": {
27
+ "startDate": ""
28
+ }
29
+ }
30
+ }
31
+ RETURNS = {
32
+ "Emission": [{
33
+ "value": "",
34
+ "sd": "",
35
+ "min": "",
36
+ "max": "",
37
+ "statsDefinition": "simulated",
38
+ "observations": "",
39
+ "methodTier": ""
40
+ }]
41
+ }
42
+ TERM_ID = 'co2ToAirAboveGroundBiomassStockChangeLandUseChange'
43
+
44
+ _CARBON_STOCK_TERM_ID = 'aboveGroundBiomass'
45
+
46
+ _MEASUREMENT_METHOD_RANKING = [
47
+ MeasurementMethodClassification.ON_SITE_PHYSICAL_MEASUREMENT,
48
+ MeasurementMethodClassification.MODELLED_USING_OTHER_MEASUREMENTS,
49
+ MeasurementMethodClassification.TIER_3_MODEL,
50
+ MeasurementMethodClassification.TIER_2_MODEL,
51
+ MeasurementMethodClassification.TIER_1_MODEL,
52
+ MeasurementMethodClassification.GEOSPATIAL_DATASET
53
+ ]
54
+ """
55
+ The list of `MeasurementMethodClassification`s that can be used to calculate SOC stock change emissions, ranked in
56
+ order from strongest to weakest.
57
+ """
58
+
59
+ _SITE_TYPE_SYSTEMS_MAPPING = {
60
+ SiteSiteType.GLASS_OR_HIGH_ACCESSIBLE_COVER.value: [
61
+ "protectedCroppingSystemSoilBased",
62
+ "protectedCroppingSystemSoilAndSubstrateBased"
63
+ ]
64
+ }
65
+
66
+
67
+ def _emission(
68
+ *,
69
+ value: list[float],
70
+ method_tier: EmissionMethodTier,
71
+ sd: list[float] = None,
72
+ min: list[float] = None,
73
+ max: list[float] = None,
74
+ statsDefinition: str = None,
75
+ observations: list[int] = None
76
+ ) -> dict:
77
+ """
78
+ Create an emission node based on the provided value and method tier.
79
+
80
+ See [Emission schema](https://www.hestia.earth/schema/Emission) for more information.
81
+
82
+ Parameters
83
+ ----------
84
+ value : float
85
+ The emission value (kg CO2 ha-1).
86
+ sd : float
87
+ The standard deviation (kg CO2 ha-1).
88
+ method_tier : EmissionMethodTier
89
+ The emission method tier.
90
+
91
+ Returns
92
+ -------
93
+ dict
94
+ The emission dictionary with keys 'depth', 'value', and 'methodTier'.
95
+ """
96
+ update_dict = {
97
+ "value": value,
98
+ "sd": sd,
99
+ "min": min,
100
+ "max": max,
101
+ "statsDefinition": statsDefinition,
102
+ "observations": observations,
103
+ "methodTier": method_tier.value
104
+ }
105
+ emission = _new_emission(TERM_ID, MODEL) | {
106
+ key: value for key, value in update_dict.items() if value
107
+ }
108
+ return emission
109
+
110
+
111
+ def run(cycle: dict) -> list[dict]:
112
+ """
113
+ Run the `ipcc2019.co2ToAirAboveGroundBiomassStockChangeManagementChange`.
114
+
115
+ Parameters
116
+ ----------
117
+ cycle : dict
118
+ A HESTIA (Cycle node)[https://www.hestia.earth/schema/Cycle].
119
+
120
+ Returns
121
+ -------
122
+ list[dict]
123
+ A list of [Emission nodes](https://www.hestia.earth/schema/Emission) containing model results.
124
+ """
125
+ should_run_exec = create_should_run_function(
126
+ _CARBON_STOCK_TERM_ID,
127
+ _should_compile_inventory_func,
128
+ measurement_method_ranking=_MEASUREMENT_METHOD_RANKING
129
+ )
130
+
131
+ run_exec = create_run_function(_emission)
132
+
133
+ should_run, cycle_id, inventory, logs = should_run_exec(cycle)
134
+
135
+ logRequirements(cycle, model=MODEL, term=TERM_ID, **logs)
136
+ logShouldRun(cycle, MODEL, TERM_ID, should_run)
137
+
138
+ return run_exec(cycle_id, inventory) if should_run else []
139
+
140
+
141
+ def _should_compile_inventory_func(
142
+ site: dict, cycles: list[dict], carbon_stock_measurements: list[dict]
143
+ ) -> tuple[bool, dict]:
144
+ """
145
+ Determine whether a site is suitable and has enough data to compile a carbon stock inventory.
146
+
147
+ Parameters
148
+ ----------
149
+ site : dict
150
+ A HESTIA (Site node)[https://www.hestia.earth/schema/Site]
151
+ cycles : list[dict]
152
+ A list of HESTIA (Cycle nodes)[https://www.hestia.earth/schema/Cycle] that are related to the site.
153
+ carbon_stock_measurements : list[dict]
154
+ A list of HESTIA carbon stock (Measurement nodes)[https://www.hestia.earth/schema/Measurement] that are related
155
+ to the site.
156
+
157
+ Returns
158
+ -------
159
+ tuple[bool, dict]
160
+ `(should_run, logs)`.
161
+ """
162
+ site_type = site.get("siteType")
163
+ has_soil = site_type not in _SITE_TYPE_SYSTEMS_MAPPING or all(
164
+ cumulative_nodes_term_match(
165
+ cycle.get("practices", []),
166
+ target_term_ids=_SITE_TYPE_SYSTEMS_MAPPING[site_type],
167
+ cumulative_threshold=0
168
+ ) for cycle in cycles
169
+ )
170
+
171
+ has_stock_measurements = len(carbon_stock_measurements) > 0
172
+ has_cycles = len(cycles) > 0
173
+ has_functional_unit_1_ha = all(cycle.get('functionalUnit') == CycleFunctionalUnit._1_HA.value for cycle in cycles)
174
+
175
+ should_run = all([
176
+ has_soil,
177
+ has_stock_measurements,
178
+ has_cycles,
179
+ has_functional_unit_1_ha
180
+ ])
181
+
182
+ logs = {
183
+ "site_type": site_type,
184
+ "has_soil": has_soil,
185
+ "carbon_stock_term": _CARBON_STOCK_TERM_ID,
186
+ "has_stock_measurements": has_stock_measurements,
187
+ "has_cycles": has_cycles,
188
+ "has_functional_unit_1_ha": has_functional_unit_1_ha,
189
+ }
190
+
191
+ return should_run, logs