hestia-earth-models 0.61.7__py3-none-any.whl → 0.62.0__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 (51) hide show
  1. hestia_earth/models/cycle/completeness/electricityFuel.py +60 -0
  2. hestia_earth/models/cycle/product/economicValueShare.py +47 -31
  3. hestia_earth/models/emepEea2019/nh3ToAirInorganicFertiliser.py +44 -59
  4. hestia_earth/models/geospatialDatabase/histosol.py +4 -0
  5. hestia_earth/models/ipcc2006/co2ToAirOrganicSoilCultivation.py +4 -2
  6. hestia_earth/models/ipcc2006/n2OToAirOrganicSoilCultivationDirect.py +1 -1
  7. hestia_earth/models/ipcc2019/aboveGroundCropResidueTotal.py +1 -1
  8. hestia_earth/models/ipcc2019/animal/pastureGrass.py +30 -24
  9. hestia_earth/models/ipcc2019/belowGroundCropResidue.py +1 -1
  10. hestia_earth/models/ipcc2019/ch4ToAirExcreta.py +1 -1
  11. hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChangeManagementChange.py +511 -458
  12. hestia_earth/models/ipcc2019/co2ToAirUreaHydrolysis.py +5 -1
  13. hestia_earth/models/ipcc2019/organicCarbonPerHa.py +116 -3882
  14. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +2060 -0
  15. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +1630 -0
  16. hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +324 -0
  17. hestia_earth/models/ipcc2019/pastureGrass.py +37 -19
  18. hestia_earth/models/ipcc2019/pastureGrass_utils.py +4 -21
  19. hestia_earth/models/mocking/search-results.json +293 -289
  20. hestia_earth/models/site/organicCarbonPerHa.py +58 -44
  21. hestia_earth/models/site/soilMeasurement.py +18 -13
  22. hestia_earth/models/utils/__init__.py +28 -0
  23. hestia_earth/models/utils/array_builders.py +578 -0
  24. hestia_earth/models/utils/blank_node.py +55 -39
  25. hestia_earth/models/utils/descriptive_stats.py +285 -0
  26. hestia_earth/models/utils/emission.py +73 -2
  27. hestia_earth/models/utils/inorganicFertiliser.py +2 -2
  28. hestia_earth/models/utils/measurement.py +118 -4
  29. hestia_earth/models/version.py +1 -1
  30. {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.62.0.dist-info}/METADATA +2 -2
  31. {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.62.0.dist-info}/RECORD +51 -39
  32. tests/models/cycle/completeness/test_electricityFuel.py +21 -0
  33. tests/models/cycle/product/test_economicValueShare.py +8 -0
  34. tests/models/emepEea2019/test_nh3ToAirInorganicFertiliser.py +2 -2
  35. tests/models/ipcc2019/animal/test_pastureGrass.py +2 -2
  36. tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChangeManagementChange.py +55 -165
  37. tests/models/ipcc2019/test_organicCarbonPerHa.py +219 -460
  38. tests/models/ipcc2019/test_organicCarbonPerHa_tier_1_utils.py +471 -0
  39. tests/models/ipcc2019/test_organicCarbonPerHa_tier_2_utils.py +208 -0
  40. tests/models/ipcc2019/test_organicCarbonPerHa_utils.py +75 -0
  41. tests/models/ipcc2019/test_pastureGrass.py +0 -16
  42. tests/models/site/test_organicCarbonPerHa.py +3 -12
  43. tests/models/site/test_soilMeasurement.py +3 -18
  44. tests/models/utils/test_array_builders.py +253 -0
  45. tests/models/utils/test_blank_node.py +154 -15
  46. tests/models/utils/test_descriptive_stats.py +134 -0
  47. tests/models/utils/test_emission.py +51 -1
  48. tests/models/utils/test_measurement.py +54 -2
  49. {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.62.0.dist-info}/LICENSE +0 -0
  50. {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.62.0.dist-info}/WHEEL +0 -0
  51. {hestia_earth_models-0.61.7.dist-info → hestia_earth_models-0.62.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,60 @@
1
+ """
2
+ Completeness Electricity Fuel
3
+
4
+ This model checks if we have the requirements below and updates the
5
+ [Data Completeness](https://hestia.earth/schema/Completeness#electricityFuel) value.
6
+ """
7
+ from hestia_earth.schema import TermTermType
8
+ from hestia_earth.utils.model import filter_list_term_type
9
+
10
+ from hestia_earth.models.log import logRequirements, log_as_table
11
+ from hestia_earth.models.utils.blank_node import get_lookup_value
12
+ from hestia_earth.models.utils.completeness import _is_term_type_complete
13
+ from . import MODEL
14
+
15
+ REQUIREMENTS = {
16
+ "Cycle": {
17
+ "completeness.operation": "True",
18
+ "completeness.electricityFuel": "False",
19
+ "practices": [
20
+ {"@type": "Practice", "value": "", "term.termType": "operation"}
21
+ ]
22
+ }
23
+ }
24
+ RETURNS = {
25
+ "Completeness": {
26
+ "electricityFuel": ""
27
+ }
28
+ }
29
+ LOOKUPS = {
30
+ "operation": ["fuelUse", "combustionType"]
31
+ }
32
+ MODEL_KEY = 'electricityFuel'
33
+ _VALID_COMBUSTION_TYPES = ['mobile', 'stationary']
34
+
35
+
36
+ def _lookup_value(practice: dict, lookup_name: str):
37
+ return get_lookup_value(practice.get('term', {}), lookup_name, model=MODEL, model_key=MODEL_KEY)
38
+
39
+
40
+ def _practice_value(practice: dict):
41
+ term = practice.get('term', {})
42
+ fuel_use = _lookup_value(practice, LOOKUPS['operation'][0])
43
+ return {'id': term.get('@id'), 'fuel_use': fuel_use}
44
+
45
+
46
+ def run(cycle: dict):
47
+ operation_complete = _is_term_type_complete(cycle, TermTermType.OPERATION)
48
+ practices = filter_list_term_type(cycle.get('practices', []), TermTermType.OPERATION)
49
+ combustion_practices = [
50
+ p for p in practices if _lookup_value(p, LOOKUPS['operation'][1]) in _VALID_COMBUSTION_TYPES
51
+ ]
52
+ practices_values = list(map(_practice_value, combustion_practices))
53
+
54
+ logRequirements(cycle, model=MODEL, term=None, key=MODEL_KEY,
55
+ term_type_operation_complete=operation_complete,
56
+ values=log_as_table(practices_values))
57
+
58
+ is_complete = all([operation_complete] + [p.get('fuel_use') for p in practices_values])
59
+
60
+ return is_complete
@@ -2,20 +2,21 @@
2
2
  Product Economic Value Share
3
3
 
4
4
  This model quantifies the relative economic value share of each marketable Product in a Cycle.
5
- Marketable Products are all Products in the Glossary with the exception of crop residue not sold.
5
+ Marketable Products are all Products identified with the lookup "generateImpactAssessment = TRUE".
6
6
 
7
7
  It works in the following order:
8
- 1. If revenue data are provided for all marketable products,
8
+ 1. For every product with no `value`, the `economicValueShare` is set to `0`.
9
+ 2. If revenue data are provided for all marketable products,
9
10
  the `economicValueShare` is directly calculated as the share of revenue of each Product;
10
- 2. If the primary product is a crop and it is the only crop Product,
11
- `economicValueShare` is assigned based on a lookup table containing typical global average economic value shares
12
- drawn from [Poore & Nemecek (2018)](https://science.sciencemag.org/content/360/6392/987).
11
+ 3. Based on a lookup table containing typical global average economic value shares
12
+ drawn from [Poore & Nemecek (2018)](https://science.sciencemag.org/content/360/6392/987), if all products have a
13
+ corresponding value, the `economicValueShare` will be proportionally distributed among the products.
13
14
  """
14
- from hestia_earth.schema import TermTermType
15
15
  from hestia_earth.utils.model import find_term_match
16
16
  from hestia_earth.utils.tools import list_sum
17
17
 
18
18
  from hestia_earth.models.log import logRequirements, logShouldRun
19
+ from hestia_earth.models.utils.blank_node import get_lookup_value
19
20
  from hestia_earth.models.utils.cycle import unique_currencies
20
21
  from .utils import lookup_share
21
22
  from .. import MODEL
@@ -25,9 +26,11 @@ REQUIREMENTS = {
25
26
  "products": [{
26
27
  "@type": "Product",
27
28
  "value": "",
28
- "economicValueShare": "",
29
- "revenue": "",
30
- "currency": ""
29
+ "optional": {
30
+ "economicValueShare": "",
31
+ "revenue": "",
32
+ "currency": ""
33
+ }
31
34
  }],
32
35
  "optional": {
33
36
  "completeness.product": ""
@@ -42,7 +45,8 @@ RETURNS = {
42
45
  LOOKUPS = {
43
46
  "@doc": "Depending on the primary product [termType](https://hestia.earth/schema/Product#term)",
44
47
  "crop": "global_economic_value_share",
45
- "excreta": "global_economic_value_share"
48
+ "excreta": "global_economic_value_share",
49
+ "animalProduct": "global_economic_value_share"
46
50
  }
47
51
  MODEL_KEY = 'economicValueShare'
48
52
  MAX_VALUE = 100.5
@@ -60,7 +64,7 @@ def _is_complete(cycle: dict): return cycle.get('completeness', {}).get('product
60
64
  def _no_yield(product): return list_sum(product.get('value', [-1]), -1) == 0
61
65
 
62
66
 
63
- def _total_revenue(products: list): return sum([p.get(MODEL_KEY, 0) for p in products])
67
+ def _total_evs(products: list): return sum([p.get(MODEL_KEY, 0) for p in products])
64
68
 
65
69
 
66
70
  def _product_with_value(product: dict):
@@ -72,18 +76,33 @@ def _rescale_value(products: list, total: float):
72
76
  return list(map(lambda p: {**p, MODEL_KEY: p.get(MODEL_KEY) * 100 / total}, products))
73
77
 
74
78
 
79
+ def _should_run_by_default(cycle: dict, products: list):
80
+ run_by = 'default'
81
+ # only run if all products have the lookup data or value will be incorrect
82
+ products = list(map(_product_with_value, products))
83
+ all_with_economicValueShare = all([p.get(MODEL_KEY) is not None for p in products])
84
+
85
+ should_run = all([all_with_economicValueShare])
86
+
87
+ for p in products:
88
+ term_id = p.get('term', {}).get('@id')
89
+ logRequirements(cycle, model=MODEL, term=term_id, key=MODEL_KEY, by=run_by,
90
+ all_with_economicValueShare=all_with_economicValueShare)
91
+ logShouldRun(cycle, MODEL, term_id, should_run, key=MODEL_KEY, by=run_by)
92
+
93
+ return should_run
94
+
95
+
75
96
  def _run_by_default(cycle: dict, products: list):
76
97
  run_by = 'default'
77
98
  is_complete = _is_complete(cycle)
78
99
  products = list(map(_product_with_value, products))
79
- # remove results where lookup share was not found
80
- results = list(filter(lambda p: p.get(MODEL_KEY) is not None, products))
81
100
  # only return list if the new total of evs is not above threshold
82
- total_revenue = _total_revenue(results)
83
- below_max_threshold = total_revenue <= MAX_VALUE
84
- should_rescale = is_complete and MIN_COMPLETE_VALUE <= total_revenue <= MAX_VALUE
85
- above_min_threshold = True if should_rescale else total_revenue >= MIN_VALUE if is_complete else True
86
- results = _rescale_value(results, total_revenue) if should_rescale else results
101
+ total_economicValueShare = _total_evs(products)
102
+ below_max_threshold = total_economicValueShare <= MAX_VALUE
103
+ should_rescale = is_complete and MIN_COMPLETE_VALUE <= total_economicValueShare <= MAX_VALUE
104
+ above_min_threshold = True if should_rescale else total_economicValueShare >= MIN_VALUE if is_complete else True
105
+ results = _rescale_value(products, total_economicValueShare) if should_rescale else products
87
106
 
88
107
  should_run = all([below_max_threshold, above_min_threshold])
89
108
 
@@ -93,7 +112,7 @@ def _run_by_default(cycle: dict, products: list):
93
112
  logRequirements(cycle, model=MODEL, term=term_id, key=MODEL_KEY, by=run_by,
94
113
  below_max_threshold=below_max_threshold,
95
114
  above_min_threshold=above_min_threshold,
96
- total_revenue=total_revenue)
115
+ total_economicValueShare=total_economicValueShare)
97
116
  logShouldRun(cycle, MODEL, term_id, p_should_run, key=MODEL_KEY, by=run_by)
98
117
 
99
118
  return results if should_run else []
@@ -109,8 +128,8 @@ def _run_by_revenue(products: list):
109
128
  def _should_run_by_revenue(cycle: dict, products: list):
110
129
  run_by = 'revenue'
111
130
  is_complete = _is_complete(cycle)
112
- total_value = _total_revenue(products)
113
- below_threshold = total_value < MAX_VALUE
131
+ total_economicValueShare = _total_evs(products)
132
+ below_threshold = total_economicValueShare < MAX_VALUE
114
133
  # ignore products with no yield
115
134
  products = list(filter(lambda p: not _no_yield(p), products))
116
135
  currencies = unique_currencies(cycle)
@@ -122,7 +141,7 @@ def _should_run_by_revenue(cycle: dict, products: list):
122
141
  term_id = p.get('term', {}).get('@id')
123
142
  logRequirements(cycle, model=MODEL, term=term_id, key=MODEL_KEY, by=run_by,
124
143
  is_complete=is_complete,
125
- total_value=total_value,
144
+ total_economicValueShare=total_economicValueShare,
126
145
  below_threshold=below_threshold,
127
146
  all_with_revenue=all_with_revenue,
128
147
  currencies=';'.join(currencies),
@@ -133,14 +152,14 @@ def _should_run_by_revenue(cycle: dict, products: list):
133
152
 
134
153
 
135
154
  def _run_single_missing_evs(products: list):
136
- total_value = _total_revenue(products)
155
+ total_value = _total_evs(products)
137
156
  return list(map(lambda p: _product(p, 100 - total_value) if p.get(MODEL_KEY) is None else p, products))
138
157
 
139
158
 
140
159
  def _should_run_single_missing_evs(cycle: dict, products: list):
141
160
  run_by = '1-missing-evs'
142
161
  is_complete = _is_complete(cycle)
143
- total_value = _total_revenue(products)
162
+ total_value = _total_evs(products)
144
163
  # ignore products with no yield
145
164
  products = list(filter(lambda p: not _no_yield(p), products))
146
165
  missing_values = [p for p in products if p.get(MODEL_KEY) is None]
@@ -175,20 +194,17 @@ def _should_run_no_value(cycle: dict, product: dict):
175
194
 
176
195
 
177
196
  def _should_have_evs(product: dict):
178
- term_type = product.get('term', {}).get('termType')
179
- return term_type not in [
180
- TermTermType.CROPRESIDUE.value,
181
- TermTermType.EXCRETA.value
182
- ]
197
+ should_generate_ia = get_lookup_value(product.get('term', {}), 'generateImpactAssessment')
198
+ return str(should_generate_ia).lower() != 'false'
183
199
 
184
200
 
185
201
  def run(cycle: dict):
186
202
  products = cycle.get('products', [])
187
- # skip any product that will never has value
203
+ # skip any product that is not marketable
188
204
  products = list(filter(_should_have_evs, products))
189
205
  products = list(map(lambda p: _product(p, 0) if _should_run_no_value(cycle, p) else p, products))
190
206
  return (
191
207
  _run_single_missing_evs(products) if _should_run_single_missing_evs(cycle, products) else
192
208
  _run_by_revenue(products) if _should_run_by_revenue(cycle, products) else
193
- _run_by_default(cycle, products)
209
+ _run_by_default(cycle, products) if _should_run_by_default(cycle, products) else []
194
210
  )
@@ -1,14 +1,13 @@
1
1
  from functools import reduce
2
2
  from hestia_earth.schema import EmissionMethodTier
3
- from hestia_earth.utils.lookup import download_lookup
4
3
  from hestia_earth.utils.model import find_term_match
5
- from hestia_earth.utils.tools import list_sum
4
+ from hestia_earth.utils.tools import list_sum, non_empty_list
6
5
 
7
- from hestia_earth.models.log import debugValues, logRequirements, logShouldRun
6
+ from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
8
7
  from hestia_earth.models.utils import _filter_list_term_unit
9
8
  from hestia_earth.models.utils.completeness import _is_term_type_complete
10
9
  from hestia_earth.models.utils.inorganicFertiliser import (
11
- get_NH3_emission_factor, get_terms, get_term_lookup, BREAKDOWN_LOOKUP, get_country_breakdown, get_cycle_inputs
10
+ get_NH3_emission_factor, get_terms, get_term_lookup, get_country_breakdown, get_cycle_inputs
12
11
  )
13
12
  from hestia_earth.models.utils.constant import Units
14
13
  from hestia_earth.models.utils.emission import _new_emission
@@ -66,23 +65,21 @@ def _emission(value: float):
66
65
  return emission
67
66
 
68
67
 
69
- def _get_input_value(cycle: dict, soilPh: float, temperature: float):
68
+ def _input_with_factor(soilPh: float, temperature: float):
70
69
  def get_value(input: dict):
71
70
  term_id = input.get('term', {}).get('@id')
72
- factor = get_NH3_emission_factor(term_id, soilPh, temperature)
73
- value = list_sum(input.get('value'))
74
- debugValues(cycle, model=MODEL, term=TERM_ID,
75
- product=term_id,
76
- factor=factor,
77
- value=value)
78
- return value * factor
71
+ factor = get_NH3_emission_factor(term_id, soilPh, temperature) if all([
72
+ soilPh is not None,
73
+ temperature is not None
74
+ ]) else None
75
+ value = list_sum(input.get('value'), None)
76
+ return {'id': term_id, 'value': value, 'factor': factor} if all([
77
+ value is not None,
78
+ factor is not None
79
+ ]) else None
79
80
  return get_value
80
81
 
81
82
 
82
- def _run(cycle: dict, temperature: float, soilPh: float, inputs: float):
83
- return list_sum(list(map(_get_input_value(cycle, soilPh, temperature), inputs)))
84
-
85
-
86
83
  def _get_groupings():
87
84
  term_ids = get_terms()
88
85
 
@@ -93,23 +90,22 @@ def _get_groupings():
93
90
  return reduce(get_grouping, term_ids, {})
94
91
 
95
92
 
96
- def _get_term_value(cycle: dict, soilPh: float, temperature: float, country_id: str, grouping: str, term_id: str):
97
- factor = get_NH3_emission_factor(term_id, soilPh, temperature)
98
- value = get_country_breakdown(MODEL, TERM_ID, country_id, grouping)
99
- debugValues(cycle, model=MODEL, term=TERM_ID,
100
- grouping=grouping,
101
- NH3_factor=factor,
102
- country_breakdown=value)
103
- return value * factor
104
-
105
-
106
- def _run_with_unspecified(cycle: dict, temperature: float, soilPh: float, unspecifiedKgN_value: float, country_id: str):
93
+ def _unspecified_inputs_with_factor(temperature: float, soilPh: float, unspecifiedKgN_value: float, site: dict):
94
+ country_id = site.get('country', {}).get('@id')
107
95
  # creates a dictionary grouping => term_id with only a single key per group (avoid counting twice)
108
96
  groupings = _get_groupings()
109
- return list_sum([
110
- _get_term_value(cycle, soilPh, temperature, country_id, grouping, term_id)
111
- for grouping, term_id in groupings.items()
112
- ]) * unspecifiedKgN_value
97
+ breakdown_inputs = [(
98
+ term_id, get_country_breakdown(MODEL, TERM_ID, country_id, grouping)
99
+ ) for grouping, term_id in groupings.items()] if all([country_id, unspecifiedKgN_value is not None]) else []
100
+ # create inputs from country breakdown
101
+ N_inputs = [
102
+ {
103
+ 'term': {'@id': term_id},
104
+ 'value': [value * unspecifiedKgN_value]
105
+ }
106
+ for term_id, value in breakdown_inputs if value is not None
107
+ ]
108
+ return non_empty_list(map(_input_with_factor(soilPh, temperature), N_inputs))
113
109
 
114
110
 
115
111
  def _should_run(cycle: dict):
@@ -121,49 +117,38 @@ def _should_run(cycle: dict):
121
117
  measurements, 'temperatureAnnual', end_date) or most_relevant_measurement_value(
122
118
  measurements, 'temperatureLongTermAnnualMean', end_date)
123
119
 
124
- inputs = get_cycle_inputs(cycle)
125
- N_inputs = _filter_list_term_unit(inputs, Units.KG_N)
120
+ N_inputs = _filter_list_term_unit(get_cycle_inputs(cycle), Units.KG_N)
126
121
  has_N_inputs = len(N_inputs) > 0
127
122
 
128
- unspecifiedKgN = find_term_match(cycle.get('inputs', []), UNSPECIFIED_TERM_ID).get('value', [])
129
- fertiliser_complete = _is_term_type_complete(cycle, 'fertiliser')
130
- has_unspecifiedKgN = len(unspecifiedKgN) > 0 or fertiliser_complete
123
+ N_inputs_with_factor = non_empty_list(map(_input_with_factor(soilPh, temperature), N_inputs))
124
+ has_N_inputs_with_factor = len(N_inputs_with_factor) > 0
131
125
 
132
- country_id = site.get('country', {}).get('@id')
133
- lookup = download_lookup(BREAKDOWN_LOOKUP)
134
- has_country_data = country_id in list(lookup.termid)
126
+ # fallback using country averages of fertilisers usage
127
+ unspecifiedKgN_value = list_sum(find_term_match(N_inputs, UNSPECIFIED_TERM_ID).get('value'), None)
128
+ unspecified_inputs_with_factor = _unspecified_inputs_with_factor(temperature, soilPh, unspecifiedKgN_value, site)
129
+ has_unspecified_inputs_with_factor = len(unspecified_inputs_with_factor) > 0
135
130
 
136
- run_with_unspecified = (has_country_data and has_unspecifiedKgN) or fertiliser_complete
137
- unspecifiedKgN = (
138
- 0 if len(unspecifiedKgN) == 0 and fertiliser_complete else list_sum(unspecifiedKgN, None)
139
- ) if run_with_unspecified else None
131
+ fertiliser_complete = _is_term_type_complete(cycle, 'fertiliser')
140
132
 
141
133
  logRequirements(cycle, model=MODEL, term=TERM_ID,
134
+ term_type_fertiliser_complete=fertiliser_complete,
142
135
  temperature=temperature,
143
136
  soilPh=soilPh,
144
- term_type_fertiliser_complete=fertiliser_complete,
145
- has_unspecifiedKgN=has_unspecifiedKgN,
146
- has_country_data=has_country_data,
147
- run_with_unspecified=run_with_unspecified,
148
- has_N_inputs=has_N_inputs)
137
+ has_N_inputs=has_N_inputs,
138
+ inorganic_fertiliser_inputs=log_as_table(N_inputs_with_factor),
139
+ unspecified_fertiliser_inputs=log_as_table(unspecified_inputs_with_factor))
149
140
 
150
141
  should_run = all([
142
+ fertiliser_complete,
151
143
  temperature,
152
144
  soilPh,
153
- any([
154
- run_with_unspecified,
155
- has_N_inputs,
156
- not has_N_inputs and fertiliser_complete
157
- ])
145
+ not has_N_inputs or has_N_inputs_with_factor or has_unspecified_inputs_with_factor
158
146
  ])
159
147
  logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
160
- return should_run, temperature, soilPh, N_inputs, unspecifiedKgN, country_id
148
+ return should_run, N_inputs_with_factor or unspecified_inputs_with_factor
161
149
 
162
150
 
163
151
  def run(cycle: dict):
164
- should_run, temperature, soilPh, N_inputs, unspecifiedKgN, country_id = _should_run(cycle)
165
- value = _run(cycle, temperature, soilPh, N_inputs) or (
166
- _run_with_unspecified(cycle, temperature, soilPh, unspecifiedKgN, country_id)
167
- if unspecifiedKgN is not None else None
168
- ) if should_run else None
152
+ should_run, N_inputs_with_factor = _should_run(cycle)
153
+ value = list_sum([i.get('value') * i.get('factor') for i in N_inputs_with_factor]) if should_run else None
169
154
  return [_emission(value)] if value is not None else []
@@ -21,6 +21,8 @@ REQUIREMENTS = {
21
21
  RETURNS = {
22
22
  "Measurement": [{
23
23
  "value": "",
24
+ "depthUpper": "0",
25
+ "depthLower": "30",
24
26
  "methodClassification": "geospatial dataset"
25
27
  }]
26
28
  }
@@ -36,6 +38,8 @@ BIBLIO_TITLE = 'Harmonized World Soil Database Version 1.2. Food and Agriculture
36
38
  def _measurement(site: dict, value: float):
37
39
  measurement = _new_measurement(TERM_ID)
38
40
  measurement['value'] = [round(value, 7)]
41
+ measurement['depthUpper'] = 0
42
+ measurement['depthLower'] = 30
39
43
  measurement['methodClassification'] = MeasurementMethodClassification.GEOSPATIAL_DATASET.value
40
44
  return measurement | get_source(site, BIBLIO_TITLE)
41
45
 
@@ -1,6 +1,7 @@
1
1
  from hestia_earth.schema import EmissionMethodTier
2
2
 
3
3
  from hestia_earth.models.log import logRequirements, logShouldRun
4
+ from hestia_earth.models.utils.constant import Units, get_atomic_conversion
4
5
  from hestia_earth.models.utils.emission import _new_emission
5
6
  from hestia_earth.models.utils.measurement import most_relevant_measurement_value
6
7
  from hestia_earth.models.utils.ecoClimateZone import get_ecoClimateZone_lookup_value
@@ -39,7 +40,6 @@ RETURNS = {
39
40
  }
40
41
  TERM_ID = 'co2ToAirOrganicSoilCultivation'
41
42
  TIER = EmissionMethodTier.TIER_1.value
42
- CONVERT_FACTOR = 44 / 120
43
43
 
44
44
 
45
45
  def _emission(value: float):
@@ -55,7 +55,9 @@ def _run(histosol: float, organic_soil_factor: float, land_occupation: float):
55
55
 
56
56
 
57
57
  def _get_CO2_factor(eco_climate_zone: str, site_type: str):
58
- return get_ecoClimateZone_lookup_value(eco_climate_zone, LOOKUPS['ecoClimateZone'], site_type) * CONVERT_FACTOR
58
+ return get_ecoClimateZone_lookup_value(
59
+ eco_climate_zone, LOOKUPS['ecoClimateZone'], site_type
60
+ ) * 1000 * get_atomic_conversion(Units.KG_CO2, Units.TO_C)
59
61
 
60
62
 
61
63
  def _should_run(cycle: dict):
@@ -57,7 +57,7 @@ def _run(histosol: float, organic_soil_factor: float, land_occupation: float):
57
57
  def _get_N2O_factor(eco_climate_zone: str):
58
58
  return get_ecoClimateZone_lookup_value(
59
59
  eco_climate_zone, LOOKUPS['ecoClimateZone']
60
- ) * get_atomic_conversion(Units.KG_N2O, Units.TO_N) / 10000
60
+ ) * get_atomic_conversion(Units.KG_N2O, Units.TO_N)
61
61
 
62
62
 
63
63
  def _should_run(cycle: dict):
@@ -75,7 +75,7 @@ def _should_run(cycle: dict):
75
75
  term_type_incomplete = _is_term_type_incomplete(cycle, TERM_ID)
76
76
 
77
77
  logRequirements(cycle, model=MODEL, term=TERM_ID,
78
- has_crop_forage_products=has_crop_forage_products,
78
+ has_crop_forage_products_with_dryMatter=has_crop_forage_products,
79
79
  term_type_cropResidue_incomplete=term_type_incomplete)
80
80
 
81
81
  should_run = all([term_type_incomplete, has_crop_forage_products])
@@ -1,9 +1,12 @@
1
1
  """
2
- Full Grass Consumption
2
+ Animal Pasture Grass
3
3
 
4
4
  This model estimates the energetic requirements of ruminants and can be used to estimate the amount of grass they graze.
5
5
  Source:
6
6
  [IPCC 2019, Vol.4, Chapter 10](https://www.ipcc-nggip.iges.or.jp/public/2019rf/pdf/4_Volume4/19R_V4_Ch10_Livestock.pdf).
7
+
8
+ This version of the model will run at the Animal Blank Node level, if none of the Cycle Input are given as feed
9
+ (see https://www.hestia.earth/schema/Input#isAnimalFeed).
7
10
  """
8
11
  from hestia_earth.schema import TermTermType
9
12
  from hestia_earth.utils.model import filter_list_term_type
@@ -138,7 +141,7 @@ RETURNS = {
138
141
  }]
139
142
  }]
140
143
  }
141
- MODEL_KEY = 'animal/pastureGrass'
144
+ MODEL_KEY = 'pastureGrass'
142
145
 
143
146
 
144
147
  def _input(term_id: str, value: float):
@@ -173,7 +176,7 @@ def calculate_NEwool(cycle: dict, animal: dict, products: list, total_weight: fl
173
176
  ])
174
177
  animal_weight = _sum_liveWeightPerHead([animal])
175
178
 
176
- debugValues(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
179
+ debugValues(animal, model=MODEL, term=term_id, model_key=MODEL_KEY,
177
180
  total_energy=total_energy,
178
181
  animal_liveWeightPerHead=animal_weight,
179
182
  total_liveWeightPerHead=total_weight)
@@ -189,7 +192,13 @@ def _calculate_GE(
189
192
  NEm, NEa, NEl, NEwork, NEp, NEg = get_animal_values(cycle, animal, system)
190
193
 
191
194
  NEm_feed, NEg_feed = calculate_NEfeed(animal)
192
- debugValues(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
195
+ debugValues(animal, model=MODEL, term=term_id, model_key=MODEL_KEY,
196
+ NEm=NEm,
197
+ NEa=NEa,
198
+ NEl=NEl,
199
+ NEwork=NEwork,
200
+ NEp=NEp,
201
+ NEg=NEg,
193
202
  NEm_feed=NEm_feed,
194
203
  NEg_feed=NEg_feed)
195
204
 
@@ -233,7 +242,7 @@ def _run_animal(cycle: dict, meanDE: float, meanECHHV: float, system: dict, prac
233
242
  ) else 0
234
243
  GE = (_calculate_GE(cycle, animal, REM, REG, NEwool, system) / (meanDE/100)) if all([REM, REG]) else 0
235
244
 
236
- debugValues(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
245
+ debugValues(animal, model=MODEL, term=term_id, model_key=MODEL_KEY,
237
246
  REM=REM,
238
247
  REG=REG,
239
248
  NEwool=NEwool,
@@ -248,17 +257,12 @@ def _run_animal(cycle: dict, meanDE: float, meanECHHV: float, system: dict, prac
248
257
  return run
249
258
 
250
259
 
251
- def _run(cycle: dict, meanDE: float, meanECHHV: float, system: dict, practices: list):
252
- animals = get_animals(cycle)
253
- return list(map(_run_animal(cycle, meanDE, meanECHHV, system, practices), animals))
254
-
255
-
256
- def _should_run(cycle: dict, practices: dict):
260
+ def _should_run(cycle: dict, animals: list, practices: dict):
257
261
  systems = filter_list_term_type(cycle.get('practices', []), TermTermType.SYSTEM)
258
262
  animalFeed_complete = _is_term_type_complete(cycle, 'animalFeed')
259
263
  animalPopulation_complete = _is_term_type_complete(cycle, 'animalPopulation')
260
264
  freshForage_incomplete = _is_term_type_incomplete(cycle, 'freshForage')
261
- all_animals_have_value = all([a.get('value', 0) > 0 for a in cycle.get('animals', [])])
265
+ all_animals_have_value = all([a.get('value', 0) > 0 for a in animals])
262
266
 
263
267
  no_cycle_inputs_feed = all([not input.get('isAnimalFeed', False) for input in cycle.get('inputs', [])])
264
268
 
@@ -278,21 +282,23 @@ def _should_run(cycle: dict, practices: dict):
278
282
  ])
279
283
 
280
284
  for term_id in [MODEL_KEY] + [practice_input_id(p) for p in practices]:
281
- logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
282
- term_type_animalFeed_complete=animalFeed_complete,
283
- term_type_animalPopulation_complete=animalPopulation_complete,
284
- term_type_freshForage_incomplete=freshForage_incomplete,
285
- no_cycle_inputs_feed=no_cycle_inputs_feed,
286
- all_animals_have_value=all_animals_have_value,
287
- meanDE=meanDE,
288
- meanECHHV=meanECHHV)
289
-
290
- logShouldRun(cycle, MODEL, term_id, should_run, model_key=MODEL_KEY)
285
+ for animal in animals:
286
+ logRequirements(animal, model=MODEL, term=term_id, model_key=MODEL_KEY,
287
+ term_type_animalFeed_complete=animalFeed_complete,
288
+ term_type_animalPopulation_complete=animalPopulation_complete,
289
+ term_type_freshForage_incomplete=freshForage_incomplete,
290
+ no_cycle_inputs_feed=no_cycle_inputs_feed,
291
+ all_animals_have_value=all_animals_have_value,
292
+ meanDE=meanDE,
293
+ meanECHHV=meanECHHV)
294
+
295
+ logShouldRun(animal, MODEL, term_id, should_run, model_key=MODEL_KEY)
291
296
 
292
297
  return should_run, meanDE, meanECHHV, systems[0] if systems else None
293
298
 
294
299
 
295
300
  def run(cycle: dict):
301
+ animals = get_animals(cycle)
296
302
  practices = list(filter(should_run_practice(cycle), cycle.get('practices', [])))
297
- should_run, meanDE, meanECHHV, system = _should_run(cycle, practices)
298
- return _run(cycle, meanDE, meanECHHV, system, practices) if should_run else []
303
+ should_run, meanDE, meanECHHV, system = _should_run(cycle, animals, practices)
304
+ return list(map(_run_animal(cycle, meanDE, meanECHHV, system, practices), animals)) if should_run else []
@@ -82,7 +82,7 @@ def _should_run(cycle: dict):
82
82
  term_type_incomplete = _is_term_type_incomplete(cycle, TERM_ID)
83
83
 
84
84
  logRequirements(cycle, model=MODEL, term=TERM_ID,
85
- has_crop_forage_products=has_crop_forage_products,
85
+ has_crop_forage_products_with_dryMatter=has_crop_forage_products,
86
86
  term_type_cropResidue_incomplete=term_type_incomplete)
87
87
 
88
88
  should_run = all([term_type_incomplete, has_crop_forage_products])
@@ -95,7 +95,7 @@ def _get_excreta_b0(country: dict, input: dict):
95
95
  )
96
96
 
97
97
 
98
- def _get_excretaManagement_MCF_from_lookup(term_id: str, ecoClimateZone: int, duration_key: DURATION_KEY):
98
+ def _get_excretaManagement_MCF_from_lookup(term_id: str, ecoClimateZone: int, duration_key: DURATION):
99
99
  lookup_name = 'excretaManagement-ecoClimateZone-CH4conv.csv'
100
100
  lookup = download_lookup(lookup_name)
101
101
  data_values = get_table_value(lookup, 'termid', term_id, str(ecoClimateZone))