hestia-earth-models 0.64.1__py3-none-any.whl → 0.64.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.

Potentially problematic release.


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

Files changed (47) hide show
  1. hestia_earth/models/agribalyse2016/machineryInfrastructureDepreciatedAmountPerCycle.py +2 -2
  2. hestia_earth/models/cycle/animal/input/hestiaAggregatedData.py +5 -2
  3. hestia_earth/models/cycle/animal/input/properties.py +2 -1
  4. hestia_earth/models/cycle/animal/milkYield.py +2 -1
  5. hestia_earth/models/cycle/concentrateFeed.py +19 -10
  6. hestia_earth/models/cycle/cycleDuration.py +4 -5
  7. hestia_earth/models/cycle/siteDuration.py +15 -5
  8. hestia_earth/models/cycle/startDateDefinition.py +3 -4
  9. hestia_earth/models/cycle/stockingDensityAnimalHousingAverage.py +52 -0
  10. hestia_earth/models/ipcc2019/aboveGroundBiomass.py +762 -0
  11. hestia_earth/models/ipcc2019/aboveGroundBiomass_utils.py +180 -0
  12. hestia_earth/models/ipcc2019/animal/liveweightGain.py +88 -0
  13. hestia_earth/models/ipcc2019/animal/liveweightPerHead.py +88 -0
  14. hestia_earth/models/ipcc2019/animal/pastureGrass.py +51 -42
  15. hestia_earth/models/ipcc2019/animal/utils.py +20 -0
  16. hestia_earth/models/ipcc2019/animal/weightAtMaturity.py +10 -15
  17. hestia_earth/models/ipcc2019/ch4ToAirAquacultureSystems.py +96 -0
  18. hestia_earth/models/ipcc2019/ch4ToAirExcreta.py +2 -2
  19. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +37 -50
  20. hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +0 -19
  21. hestia_earth/models/ipcc2019/pastureGrass.py +44 -31
  22. hestia_earth/models/ipcc2019/pastureGrass_utils.py +38 -22
  23. hestia_earth/models/mocking/search-results.json +489 -249
  24. hestia_earth/models/poschEtAl2008/terrestrialEutrophicationPotentialAccumulatedExceedance.py +40 -0
  25. hestia_earth/models/utils/blank_node.py +20 -1
  26. hestia_earth/models/utils/crop.py +4 -0
  27. hestia_earth/models/utils/ecoClimateZone.py +99 -0
  28. hestia_earth/models/utils/productivity.py +1 -1
  29. hestia_earth/models/utils/property.py +2 -2
  30. hestia_earth/models/version.py +1 -1
  31. {hestia_earth_models-0.64.1.dist-info → hestia_earth_models-0.64.3.dist-info}/METADATA +1 -1
  32. {hestia_earth_models-0.64.1.dist-info → hestia_earth_models-0.64.3.dist-info}/RECORD +47 -31
  33. tests/models/cycle/test_siteDuration.py +22 -0
  34. tests/models/cycle/test_stockingDensityAnimalHousingAverage.py +42 -0
  35. tests/models/ipcc2019/animal/test_liveweightGain.py +20 -0
  36. tests/models/ipcc2019/animal/test_liveweightPerHead.py +20 -0
  37. tests/models/ipcc2019/animal/test_pastureGrass.py +1 -1
  38. tests/models/ipcc2019/test_aboveGroundBiomass.py +182 -0
  39. tests/models/ipcc2019/test_aboveGroundBiomass_utils.py +92 -0
  40. tests/models/ipcc2019/test_ch4ToAirAquacultureSystems.py +60 -0
  41. tests/models/ipcc2019/test_organicCarbonPerHa_tier_1_utils.py +3 -2
  42. tests/models/ipcc2019/test_pastureGrass.py +2 -2
  43. tests/models/poschEtAl2008/test_terrestrialEutrophicationPotentialAccumulatedExceedance.py +44 -0
  44. tests/models/utils/test_ecoClimateZone.py +152 -0
  45. {hestia_earth_models-0.64.1.dist-info → hestia_earth_models-0.64.3.dist-info}/LICENSE +0 -0
  46. {hestia_earth_models-0.64.1.dist-info → hestia_earth_models-0.64.3.dist-info}/WHEEL +0 -0
  47. {hestia_earth_models-0.64.1.dist-info → hestia_earth_models-0.64.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,180 @@
1
+ from enum import Enum
2
+ from numpy import random
3
+ from numpy.typing import NDArray
4
+ from typing import Callable, Optional, Union
5
+
6
+ from hestia_earth.models.utils.array_builders import repeat_single, truncated_normal_1d
7
+ from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_ecoClimateZone_lookup_grouped_value
8
+
9
+
10
+ class BiomassCategory(Enum):
11
+ """
12
+ Enum representing biomass categories, sourced from IPCC (2006), IPCC (2019) and European Commission (2010).
13
+
14
+ Enum values formatted for logging as table.
15
+ """
16
+ ANNUAL_CROPS = "annual-crops"
17
+ COCONUT = "coconut" # European Commission (2010)
18
+ FOREST = "forest" # IPCC (2019) recalculated per eco-climate zone
19
+ GRASSLAND = "grassland"
20
+ JATROPHA = "jatropha" # European Commission (2010)
21
+ JOJOBA = "jojoba" # European Commission (2010)
22
+ NATURAL_FOREST = "natural-forest" # IPCC (2019) recalculated per eco-climate zone
23
+ OIL_PALM = "oil palm" # IPCC (2019)
24
+ OLIVE = "olive" # IPCC (2019)
25
+ ORCHARD = "orchard" # IPCC (2019)
26
+ OTHER = "other"
27
+ PLANTATION_FOREST = "plantation-forest" # IPCC (2019) recalculated per eco-climate zone
28
+ RUBBER = "rubber" # IPCC (2019)
29
+ SHORT_ROTATION_COPPICE = "short-rotation-coppice" # IPCC (2019)
30
+ TEA = "tea" # IPCC (2019)
31
+ VINE = "vine" # IPCC (2019)
32
+ WOODY_PERENNIAL = "woody-perennial" # IPCC (2006)
33
+
34
+
35
+ BIOMASS_CATEGORY_TO_LAND_COVER_LOOKUP_VALUE = {
36
+ BiomassCategory.ANNUAL_CROPS: "Annual crops",
37
+ BiomassCategory.COCONUT: "Coconut",
38
+ BiomassCategory.FOREST: "Forest",
39
+ BiomassCategory.GRASSLAND: "Grassland",
40
+ BiomassCategory.JATROPHA: "Jatropha",
41
+ BiomassCategory.JOJOBA: "Jojoba",
42
+ BiomassCategory.NATURAL_FOREST: "Natural forest",
43
+ BiomassCategory.OIL_PALM: "Oil palm",
44
+ BiomassCategory.OLIVE: "Olive",
45
+ BiomassCategory.ORCHARD: "Orchard",
46
+ BiomassCategory.OTHER: "Other",
47
+ BiomassCategory.PLANTATION_FOREST: "Plantation forest",
48
+ BiomassCategory.RUBBER: "Rubber",
49
+ BiomassCategory.SHORT_ROTATION_COPPICE: "Short rotation coppice",
50
+ BiomassCategory.TEA: "Tea",
51
+ BiomassCategory.VINE: "Vine",
52
+ BiomassCategory.WOODY_PERENNIAL: "Woody perennial"
53
+ }
54
+
55
+
56
+ def assign_biomass_category(lookup_value: str) -> BiomassCategory:
57
+ """
58
+ Return the `BiomassCategory` enum member associated with the input lookup value. If lookup value is missing or
59
+ doesn't map to any category, return `None`.
60
+ """
61
+ return next(
62
+ (key for key, value in BIOMASS_CATEGORY_TO_LAND_COVER_LOOKUP_VALUE.items() if value == lookup_value),
63
+ None
64
+ )
65
+
66
+
67
+ def sample_biomass_equilibrium(
68
+ iterations: int,
69
+ biomass_category: BiomassCategory,
70
+ eco_climate_zone: EcoClimateZone,
71
+ seed: Union[int, random.Generator, None] = None
72
+ ) -> dict:
73
+ """
74
+ Sample a biomass equilibrium using the function specified in `KWARGS_TO_SAMPLE_FUNC`.
75
+
76
+ Parameters
77
+ ----------
78
+ iterations : int
79
+ The number of samples to take.
80
+ biomass_category : BiomassCategory
81
+ The biomass category of the land cover.
82
+ eco_climate_zone : EcoClimateZone
83
+ The eco-climate zone of the site.
84
+ seed : int | Generator | None, optional
85
+ A seed to initialize the BitGenerator. If passed a Generator, it will be returned unaltered. If `None`, then
86
+ fresh, unpredictable entropy will be pulled from the OS.
87
+
88
+ Returns
89
+ -------
90
+ NDArray
91
+ The sampled parameter as a numpy array with shape `(1, iterations)`.
92
+ """
93
+ kwargs = _get_biomass_equilibrium(biomass_category, eco_climate_zone)
94
+ func = _get_sample_func(kwargs)
95
+ return func(iterations=iterations, seed=seed, **kwargs)
96
+
97
+
98
+ def _get_biomass_equilibrium(biomass_category: BiomassCategory, eco_climate_zone: EcoClimateZone) -> dict:
99
+ """
100
+ Retrieve the biomass equilibrium data for a specific combination of biomass category and eco-climate zone.
101
+
102
+ Parameters
103
+ ----------
104
+ biomass_category : BiomassCategory
105
+ The biomass category of the land cover.
106
+ eco_climate_zone : EcoClimateZone
107
+ The eco-climate zone of the site.
108
+
109
+ Returns
110
+ -------
111
+ dict
112
+ The biomass equilibrium data.
113
+ """
114
+ return get_ecoClimateZone_lookup_grouped_value(
115
+ eco_climate_zone.value,
116
+ _build_col_name(biomass_category),
117
+ default={"value": 0}
118
+ )
119
+
120
+
121
+ def _build_col_name(biomass_category: BiomassCategory) -> str:
122
+ """
123
+ Get the column name for the `ecoClimateZone-lookup.csv` for a specific biomass category equilibrium.
124
+ """
125
+ COL_NAME_ROOT = "AG_BIOMASS_EQUILIBRIUM_KG_C_HECTARE_"
126
+ return (
127
+ f"{COL_NAME_ROOT}{biomass_category.name}" if isinstance(biomass_category, BiomassCategory)
128
+ else f"{COL_NAME_ROOT}OTHER"
129
+ )
130
+
131
+
132
+ def _get_sample_func(kwargs: dict) -> Callable:
133
+ """
134
+ Select the correct sample function for a parameter based on the distribution data available. All possible
135
+ parameters for the model should have, at a minimum, a `value`, meaning that no default function needs to be
136
+ specified.
137
+
138
+ This function has been extracted into it's own method to allow for mocking of sample function.
139
+
140
+ Keyword Args
141
+ ------------
142
+ value : float
143
+ The distribution mean.
144
+ sd : float
145
+ The standard deviation of the distribution.
146
+ uncertainty : float
147
+ The +/- uncertainty of the 95% confidence interval expressed as a percentage of the mean.
148
+ error : float
149
+ Two standard deviations expressed as a percentage of the mean.
150
+
151
+ Returns
152
+ -------
153
+ Callable
154
+ The sample function for the distribution.
155
+ """
156
+ return next(
157
+ sample_func for required_kwargs, sample_func in _KWARGS_TO_SAMPLE_FUNC.items()
158
+ if all(kwarg in kwargs.keys() for kwarg in required_kwargs)
159
+ )
160
+
161
+
162
+ def sample_plus_minus_error(
163
+ *, iterations: int, value: float, error: float, seed: Optional[int] = None, **_
164
+ ) -> NDArray:
165
+ """Randomly sample a model parameter with a truncated normal distribution described using plus/minus error."""
166
+ sd = value * (error / 200)
167
+ low = value - (value * (error / 100))
168
+ high = value + (value * (error / 100))
169
+ return truncated_normal_1d(shape=(1, iterations), mu=value, sigma=sd, low=low, high=high, seed=seed)
170
+
171
+
172
+ def sample_constant(*, iterations: int, value: float, **_) -> NDArray:
173
+ """Sample a constant model parameter."""
174
+ return repeat_single(shape=(1, iterations), value=value)
175
+
176
+
177
+ _KWARGS_TO_SAMPLE_FUNC = {
178
+ ("value", "error"): sample_plus_minus_error,
179
+ ("value",): sample_constant
180
+ }
@@ -0,0 +1,88 @@
1
+ from hestia_earth.schema import TermTermType
2
+ from hestia_earth.utils.model import filter_list_term_type
3
+
4
+ from hestia_earth.models.log import logRequirements, logShouldRun
5
+ from hestia_earth.models.utils.blank_node import merge_blank_nodes
6
+ from hestia_earth.models.utils.property import _new_property, node_has_no_property
7
+ from .utils import productivity_lookup_value
8
+ from .. import MODEL
9
+
10
+ REQUIREMENTS = {
11
+ "Cycle": {
12
+ "site": {
13
+ "@type": "Site",
14
+ "country": {"@type": "Term", "termType": "region"}
15
+ },
16
+ "animals": [{
17
+ "@type": "Animal",
18
+ "term.termType": "liveAnimal",
19
+ "none": {
20
+ "properties": [{
21
+ "@type": "Property",
22
+ "value": "",
23
+ "term.@id": "liveweightGain"
24
+ }]
25
+ }
26
+ }]
27
+ }
28
+ }
29
+ LOOKUPS = {
30
+ "region-liveAnimal-liveweightGain": "liveweight gain"
31
+ }
32
+ RETURNS = {
33
+ "Animal": [{
34
+ "properties": [{
35
+ "@type": "Property",
36
+ "value": ""
37
+ }]
38
+ }]
39
+ }
40
+ TERM_ID = 'liveweightGain'
41
+
42
+
43
+ def _property(value: float):
44
+ prop = _new_property(TERM_ID, MODEL)
45
+ prop['value'] = value
46
+ return prop
47
+
48
+
49
+ def _run_animal(data: dict):
50
+ animal = data.get('animal')
51
+ value = data.get('value')
52
+ return animal | {
53
+ 'properties': merge_blank_nodes(animal.get('properties', []), [_property(value)])
54
+ }
55
+
56
+
57
+ def _should_run(cycle: dict):
58
+ country = cycle.get('site', {}).get('country', {})
59
+ country_id = country.get('@id')
60
+ live_animals = filter_list_term_type(cycle.get('animals', []), TermTermType.LIVEANIMAL)
61
+ live_animals = list(filter(node_has_no_property(TERM_ID), live_animals))
62
+ live_animals_with_value = [{
63
+ 'animal': animal,
64
+ 'value': productivity_lookup_value(TERM_ID, list(LOOKUPS.keys())[0], country, animal)
65
+ } for animal in live_animals]
66
+
67
+ def _should_run_animal(value: dict):
68
+ lookup_value = value.get('value')
69
+ term_id = value.get('animal').get('term').get('@id')
70
+
71
+ logRequirements(cycle, model=MODEL, term=term_id,
72
+ country_id=country_id,
73
+ liveweightGain=lookup_value)
74
+
75
+ should_run = all([
76
+ country_id,
77
+ lookup_value is not None
78
+ ])
79
+ logShouldRun(cycle, MODEL, term_id, should_run)
80
+
81
+ return should_run
82
+
83
+ return list(filter(_should_run_animal, live_animals_with_value))
84
+
85
+
86
+ def run(cycle: dict):
87
+ animals = _should_run(cycle)
88
+ return list(map(_run_animal, animals))
@@ -0,0 +1,88 @@
1
+ from hestia_earth.schema import TermTermType
2
+ from hestia_earth.utils.model import filter_list_term_type
3
+
4
+ from hestia_earth.models.log import logRequirements, logShouldRun
5
+ from hestia_earth.models.utils.blank_node import merge_blank_nodes
6
+ from hestia_earth.models.utils.property import _new_property, node_has_no_property
7
+ from .utils import productivity_lookup_value
8
+ from .. import MODEL
9
+
10
+ REQUIREMENTS = {
11
+ "Cycle": {
12
+ "site": {
13
+ "@type": "Site",
14
+ "country": {"@type": "Term", "termType": "region"}
15
+ },
16
+ "animals": [{
17
+ "@type": "Animal",
18
+ "term.termType": "liveAnimal",
19
+ "none": {
20
+ "properties": [{
21
+ "@type": "Property",
22
+ "value": "",
23
+ "term.@id": "liveweightPerHead"
24
+ }]
25
+ }
26
+ }]
27
+ }
28
+ }
29
+ LOOKUPS = {
30
+ "region-liveAnimal-liveweightPerHead": "liveweight per head"
31
+ }
32
+ RETURNS = {
33
+ "Animal": [{
34
+ "properties": [{
35
+ "@type": "Property",
36
+ "value": ""
37
+ }]
38
+ }]
39
+ }
40
+ TERM_ID = 'liveweightPerHead'
41
+
42
+
43
+ def _property(value: float):
44
+ prop = _new_property(TERM_ID, MODEL)
45
+ prop['value'] = value
46
+ return prop
47
+
48
+
49
+ def _run_animal(data: dict):
50
+ animal = data.get('animal')
51
+ value = data.get('value')
52
+ return animal | {
53
+ 'properties': merge_blank_nodes(animal.get('properties', []), [_property(value)])
54
+ }
55
+
56
+
57
+ def _should_run(cycle: dict):
58
+ country = cycle.get('site', {}).get('country', {})
59
+ country_id = country.get('@id')
60
+ live_animals = filter_list_term_type(cycle.get('animals', []), TermTermType.LIVEANIMAL)
61
+ live_animals = list(filter(node_has_no_property(TERM_ID), live_animals))
62
+ live_animals_with_value = [{
63
+ 'animal': animal,
64
+ 'value': productivity_lookup_value(TERM_ID, list(LOOKUPS.keys())[0], country, animal)
65
+ } for animal in live_animals]
66
+
67
+ def _should_run_animal(value: dict):
68
+ lookup_value = value.get('value')
69
+ term_id = value.get('animal').get('term').get('@id')
70
+
71
+ logRequirements(cycle, model=MODEL, term=term_id,
72
+ country_id=country_id,
73
+ liveweightPerHead=lookup_value)
74
+
75
+ should_run = all([
76
+ country_id,
77
+ lookup_value is not None
78
+ ])
79
+ logShouldRun(cycle, MODEL, term_id, should_run)
80
+
81
+ return should_run
82
+
83
+ return list(filter(_should_run_animal, live_animals_with_value))
84
+
85
+
86
+ def run(cycle: dict):
87
+ animals = _should_run(cycle)
88
+ return list(map(_run_animal, animals))
@@ -10,10 +10,10 @@ This version of the model will run at the Animal Blank Node level, if none of th
10
10
  """
11
11
  from hestia_earth.schema import TermTermType
12
12
  from hestia_earth.utils.model import filter_list_term_type
13
- from hestia_earth.utils.tools import list_sum
13
+ from hestia_earth.utils.tools import list_sum, non_empty_list
14
14
 
15
15
  from hestia_earth.models.log import logRequirements, logShouldRun, debugValues, log_as_table
16
- from hestia_earth.models.utils.blank_node import lookups_logs, properties_logs
16
+ from hestia_earth.models.utils.blank_node import lookups_logs, properties_logs, merge_blank_nodes
17
17
  from hestia_earth.models.utils.input import _new_input
18
18
  from hestia_earth.models.utils.term import get_wool_terms, get_lookup_value
19
19
  from hestia_earth.models.utils.completeness import _is_term_type_complete, _is_term_type_incomplete
@@ -42,15 +42,22 @@ REQUIREMENTS = {
42
42
  "@type": "Site",
43
43
  "siteType": "permanent pasture"
44
44
  },
45
- "practices": [{
46
- "@type": "Practice",
47
- "value": "",
48
- "term.@id": "pastureGrass",
49
- "key": {
50
- "@type": "Term",
51
- "term.termType": "landCover"
45
+ "practices": [
46
+ {
47
+ "@type": "Practice",
48
+ "value": "",
49
+ "term.termType": "system"
50
+ },
51
+ {
52
+ "@type": "Practice",
53
+ "value": "",
54
+ "term.@id": "pastureGrass",
55
+ "key": {
56
+ "@type": "Term",
57
+ "term.termType": "landCover"
58
+ }
52
59
  }
53
- }],
60
+ ],
54
61
  "animals": [{
55
62
  "@type": "Animal",
56
63
  "value": "> 0",
@@ -140,7 +147,8 @@ RETURNS = {
140
147
  "inputs": [{
141
148
  "@type": "Input",
142
149
  "term.termType": ["crop", "forage"],
143
- "value": ""
150
+ "value": "",
151
+ "isAnimalFeed": "True"
144
152
  }]
145
153
  }]
146
154
  }
@@ -150,6 +158,7 @@ MODEL_KEY = 'pastureGrass'
150
158
  def _input(term_id: str, value: float):
151
159
  node = _new_input(term_id, MODEL)
152
160
  node['value'] = [value]
161
+ node['isAnimalFeed'] = True
153
162
  return node
154
163
 
155
164
 
@@ -187,10 +196,9 @@ def calculate_NEwool(cycle: dict, animal: dict, products: list, total_weight: fl
187
196
  return total_energy * animal_weight/total_weight
188
197
 
189
198
 
190
- def _run_practice(
191
- animal: dict, values: dict, meanDE: float, meanECHHV: float, REM: float, REG: float,
192
- NEwool: float, NEm_feed: float, NEg_feed: float
193
- ):
199
+ def _run_practice(animal: dict, values: dict, meanDE: float, meanECHHV: float, REM: float, REG: float, NEwool: float):
200
+ NEm_feed, NEg_feed, log_feed = calculate_NEfeed(animal)
201
+
194
202
  def run(practice: dict):
195
203
  key = practice.get('key', {})
196
204
  key_id = key.get('@id')
@@ -202,18 +210,15 @@ def _run_practice(
202
210
 
203
211
  value = (GE / meanECHHV) * (list_sum(practice.get('value', [0])) / 100)
204
212
 
205
- logs = log_as_table(values | {
206
- 'animalId': animal.get('term', {}).get('@id'),
207
- 'practiceKeyId': key_id,
208
- 'GE': GE,
209
- 'NEmFeed': NEm_feed,
210
- 'NEgFeed': NEg_feed,
211
- 'REM': REM,
212
- 'REG': REG,
213
+ logs = log_as_table([{
214
+ 'id': animal.get('term', {}).get('@id')
215
+ } | values | {
213
216
  'NEwool': NEwool,
214
- 'meanECHHV': meanECHHV,
215
- 'meanDE': meanDE
216
- })
217
+ 'total-feed-NEm': NEm_feed,
218
+ 'total-feed-NEg': NEg_feed,
219
+ 'practiceKeyId': key_id,
220
+ 'GE': GE
221
+ }])
217
222
  animal_lookups = lookups_logs(MODEL, [animal], LOOKUPS, model_key=MODEL_KEY, term=input_term_id)
218
223
  animal_properties = properties_logs([animal], properties=[
219
224
  'liveweightPerHead',
@@ -226,23 +231,24 @@ def _run_practice(
226
231
  'weightAtOneYear',
227
232
  'weightAtSlaughter'
228
233
  ])
234
+ has_positive_feed_values = all([NEm_feed > 0, NEg_feed > 0])
229
235
 
230
236
  logRequirements(animal, model=MODEL, term=input_term_id, model_key=MODEL_KEY,
237
+ feed_logs=log_as_table(log_feed),
238
+ has_positive_feed_values=has_positive_feed_values,
231
239
  animal_logs=logs,
232
240
  animal_lookups=animal_lookups,
233
241
  animal_properties=animal_properties)
234
242
 
235
- logShouldRun(animal, MODEL, input_term_id, True, model_key=MODEL_KEY)
243
+ should_run = all([has_positive_feed_values])
244
+ logShouldRun(animal, MODEL, input_term_id, should_run, model_key=MODEL_KEY)
236
245
 
237
- return _input(input_term_id, value)
246
+ return _input(input_term_id, value) if should_run else None
238
247
 
239
248
  return run
240
249
 
241
250
 
242
- def _run_animal(cycle: dict, meanDE: float, meanECHHV: float, system: dict, practices: list):
243
- REM = calculate_REM(meanDE)
244
- REG = calculate_REG(meanDE)
245
-
251
+ def _run_animal(cycle: dict, meanDE: float, meanECHHV: float, REM: float, REG: float, systems: list, practices: list):
246
252
  wool_term_ids = get_wool_terms()
247
253
  # list of animal product
248
254
  wool_products = [p for p in cycle.get('products', []) if p.get('term', {}).get('@id') in wool_term_ids]
@@ -253,15 +259,14 @@ def _run_animal(cycle: dict, meanDE: float, meanECHHV: float, system: dict, prac
253
259
  NEwool = calculate_NEwool(cycle, animal, wool_products, total_liveWeightPerHead) if (
254
260
  total_liveWeightPerHead > 0
255
261
  ) else 0
256
- NEm_feed, NEg_feed = calculate_NEfeed(animal)
257
- animal_values = get_animal_values(cycle, animal, system)
262
+ animal_values = get_animal_values(cycle, animal, systems)
258
263
 
259
- inputs = list(map(
260
- _run_practice(animal, animal_values, meanDE, meanECHHV, REM, REG, NEwool, NEm_feed, NEg_feed),
264
+ inputs = non_empty_list(map(
265
+ _run_practice(animal, animal_values, meanDE, meanECHHV, REM, REG, NEwool),
261
266
  practices
262
267
  ))
263
268
  return animal | {
264
- 'inputs': animal.get('inputs', []) + inputs
269
+ 'inputs': merge_blank_nodes(animal.get('inputs', []), inputs)
265
270
  }
266
271
 
267
272
  return run
@@ -278,6 +283,8 @@ def _should_run(cycle: dict, animals: list, practices: dict):
278
283
 
279
284
  meanDE = calculate_meanDE(practices)
280
285
  meanECHHV = calculate_meanECHHV(practices)
286
+ REM = calculate_REM(meanDE)
287
+ REG = calculate_REG(meanDE)
281
288
 
282
289
  should_run = all([
283
290
  animalFeed_complete,
@@ -299,16 +306,18 @@ def _should_run(cycle: dict, animals: list, practices: dict):
299
306
  term_type_freshForage_incomplete=freshForage_incomplete,
300
307
  no_cycle_inputs_feed=no_cycle_inputs_feed,
301
308
  all_animals_have_value=all_animals_have_value,
302
- meanDE=calculate_meanDE(practices, term=term_id),
303
- meanECHHV=calculate_meanECHHV(practices, term=term_id))
309
+ grass_MeanDE=calculate_meanDE(practices, term=term_id),
310
+ grass_MeanECHHV=calculate_meanECHHV(practices, term=term_id),
311
+ grass_REM=REM,
312
+ grass_REG=REG)
304
313
 
305
314
  logShouldRun(animal, MODEL, term_id, should_run, model_key=MODEL_KEY)
306
315
 
307
- return should_run, meanDE, meanECHHV, systems[0] if systems else None
316
+ return should_run, meanDE, meanECHHV, REM, REG, systems
308
317
 
309
318
 
310
319
  def run(cycle: dict):
311
320
  animals = get_animals_by_period(cycle)
312
321
  practices = list(filter(should_run_practice(cycle), cycle.get('practices', [])))
313
- should_run, meanDE, meanECHHV, system = _should_run(cycle, animals, practices)
314
- return list(map(_run_animal(cycle, meanDE, meanECHHV, system, practices), animals)) if should_run else []
322
+ should_run, meanDE, meanECHHV, REM, REG, systems = _should_run(cycle, animals, practices)
323
+ return list(map(_run_animal(cycle, meanDE, meanECHHV, REM, REG, systems, practices), animals)) if should_run else []
@@ -0,0 +1,20 @@
1
+ from hestia_earth.utils.lookup import download_lookup, get_table_value, column_name, extract_grouped_data
2
+ from hestia_earth.utils.tools import safe_parse_float
3
+
4
+ from hestia_earth.models.log import debugMissingLookup
5
+ from hestia_earth.models.utils.productivity import PRODUCTIVITY, get_productivity
6
+ from .. import MODEL
7
+
8
+
9
+ def productivity_lookup_value(term_id: str, lookup: str, country: dict, animal: dict):
10
+ country_id = country.get('@id')
11
+ productivity_key = get_productivity(country)
12
+ lookup_name = f"{lookup}.csv"
13
+ lookup = download_lookup(lookup_name)
14
+ column = column_name(animal.get('term').get('@id'))
15
+ value = get_table_value(lookup, 'termid', country_id, column)
16
+ debugMissingLookup(lookup_name, 'termid', country_id, column, value, model=MODEL, term=term_id)
17
+ return safe_parse_float(
18
+ extract_grouped_data(value, productivity_key.value) or
19
+ extract_grouped_data(value, PRODUCTIVITY.HIGH.value) # defaults to high if low is not found
20
+ )
@@ -4,11 +4,11 @@ greater than or equal to `liveweightPerHead` value.
4
4
  """
5
5
  from hestia_earth.schema import TermTermType
6
6
  from hestia_earth.utils.model import filter_list_term_type, find_term_match
7
- from hestia_earth.utils.lookup import download_lookup, get_table_value, column_name
8
- from hestia_earth.utils.tools import safe_parse_float
9
7
 
10
- from hestia_earth.models.log import logRequirements, logShouldRun, debugMissingLookup
8
+ from hestia_earth.models.log import logRequirements, logShouldRun
9
+ from hestia_earth.models.utils.blank_node import merge_blank_nodes
11
10
  from hestia_earth.models.utils.property import _new_property, node_has_no_property
11
+ from .utils import productivity_lookup_value
12
12
  from .. import MODEL
13
13
 
14
14
  REQUIREMENTS = {
@@ -61,24 +61,19 @@ def _run_animal(data: dict):
61
61
  animal = data.get('animal')
62
62
  value = data.get('value')
63
63
  return animal | {
64
- 'properties': animal.get('properties', []) + [_property(value)]
64
+ 'properties': merge_blank_nodes(animal.get('properties', []), [_property(value)])
65
65
  }
66
66
 
67
67
 
68
- def _lookup_value(country_id: str, animal: dict):
69
- lookup_name = f"{list(LOOKUPS.keys())[0]}.csv"
70
- lookup = download_lookup(lookup_name)
71
- column = column_name(animal.get('term').get('@id'))
72
- value = get_table_value(lookup, 'termid', country_id, column)
73
- debugMissingLookup(lookup_name, 'termid', country_id, column, value, model=MODEL, term=TERM_ID)
74
- return safe_parse_float(value)
75
-
76
-
77
68
  def _should_run(cycle: dict):
78
- country_id = cycle.get('site', {}).get('country', {}).get('@id')
69
+ country = cycle.get('site', {}).get('country', {})
70
+ country_id = country.get('@id')
79
71
  live_animals = filter_list_term_type(cycle.get('animals', []), TermTermType.LIVEANIMAL)
80
72
  live_animals = list(filter(node_has_no_property(TERM_ID), live_animals))
81
- live_animals_with_value = [{'animal': a, 'value': _lookup_value(country_id, a)} for a in live_animals]
73
+ live_animals_with_value = [{
74
+ 'animal': animal,
75
+ 'value': productivity_lookup_value(TERM_ID, list(LOOKUPS.keys())[0], country, animal)
76
+ } for animal in live_animals]
82
77
 
83
78
  def _should_run_animal(value: dict):
84
79
  lookup_value = value.get('value')