hestia-earth-models 0.61.2__py3-none-any.whl → 0.61.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 (53) hide show
  1. hestia_earth/models/cycle/startDateDefinition.py +0 -1
  2. hestia_earth/models/ecoinventV3/__init__.py +6 -2
  3. hestia_earth/models/ecoinventV3AndEmberClimate/__init__.py +125 -0
  4. hestia_earth/models/ecoinventV3AndEmberClimate/utils.py +125 -0
  5. hestia_earth/models/emepEea2019/nh3ToAirInorganicFertiliser.py +10 -4
  6. hestia_earth/models/ipcc2006/n2OToAirInorganicFertiliserDirect.py +16 -8
  7. hestia_earth/models/ipcc2006/n2OToAirInorganicFertiliserIndirect.py +16 -8
  8. hestia_earth/models/ipcc2006/n2OToAirOrganicFertiliserDirect.py +15 -7
  9. hestia_earth/models/ipcc2006/n2OToAirOrganicFertiliserIndirect.py +15 -7
  10. hestia_earth/models/ipcc2019/animal/pastureGrass.py +1 -1
  11. hestia_earth/models/ipcc2019/animal/weightAtMaturity.py +104 -0
  12. hestia_earth/models/ipcc2019/ch4ToAirFloodedRice.py +15 -2
  13. hestia_earth/models/ipcc2019/n2OToAirInorganicFertiliserDirect.py +16 -8
  14. hestia_earth/models/ipcc2019/n2OToAirOrganicFertiliserDirect.py +16 -9
  15. hestia_earth/models/ipcc2019/nh3ToAirInorganicFertiliser.py +6 -0
  16. hestia_earth/models/ipcc2019/nh3ToAirOrganicFertiliser.py +9 -3
  17. hestia_earth/models/ipcc2019/no3ToGroundwaterInorganicFertiliser.py +16 -8
  18. hestia_earth/models/ipcc2019/no3ToGroundwaterOrganicFertiliser.py +6 -0
  19. hestia_earth/models/ipcc2019/noxToAirInorganicFertiliser.py +6 -0
  20. hestia_earth/models/ipcc2019/noxToAirOrganicFertiliser.py +9 -3
  21. hestia_earth/models/mocking/search-results.json +165 -114
  22. hestia_earth/models/pooreNemecek2018/no3ToGroundwaterInorganicFertiliser.py +16 -8
  23. hestia_earth/models/pooreNemecek2018/no3ToGroundwaterOrganicFertiliser.py +16 -8
  24. hestia_earth/models/schererPfister2015/pToDrainageWaterSoilFlux.py +13 -1
  25. hestia_earth/models/schererPfister2015/pToSurfaceWaterSoilFlux.py +13 -1
  26. hestia_earth/models/schererPfister2015/utils.py +3 -3
  27. hestia_earth/models/site/management.py +13 -6
  28. hestia_earth/models/site/organicCarbonPerHa.py +6 -4
  29. hestia_earth/models/stehfestBouwman2006/n2OToAirInorganicFertiliserDirect.py +16 -8
  30. hestia_earth/models/stehfestBouwman2006/n2OToAirOrganicFertiliserDirect.py +16 -8
  31. hestia_earth/models/stehfestBouwman2006/noxToAirInorganicFertiliser.py +16 -8
  32. hestia_earth/models/stehfestBouwman2006/noxToAirOrganicFertiliser.py +16 -8
  33. hestia_earth/models/stehfestBouwman2006GisImplementation/noxToAirInorganicFertiliser.py +16 -8
  34. hestia_earth/models/stehfestBouwman2006GisImplementation/noxToAirOrganicFertiliser.py +16 -8
  35. hestia_earth/models/utils/blank_node.py +29 -0
  36. hestia_earth/models/utils/cycle.py +6 -4
  37. hestia_earth/models/utils/fertiliser.py +14 -0
  38. hestia_earth/models/utils/inorganicFertiliser.py +7 -0
  39. hestia_earth/models/utils/organicFertiliser.py +9 -0
  40. hestia_earth/models/utils/pesticideAI.py +6 -17
  41. hestia_earth/models/utils/term.py +22 -0
  42. hestia_earth/models/version.py +1 -1
  43. hestia_earth/models/webbEtAl2012AndSintermannEtAl2012/nh3ToAirOrganicFertiliser.py +17 -9
  44. {hestia_earth_models-0.61.2.dist-info → hestia_earth_models-0.61.3.dist-info}/METADATA +2 -2
  45. {hestia_earth_models-0.61.2.dist-info → hestia_earth_models-0.61.3.dist-info}/RECORD +53 -46
  46. tests/models/ipcc2019/animal/test_weightAtMaturity.py +20 -0
  47. tests/models/ipcc2019/test_ch4ToAirEntericFermentation.py +0 -5
  48. tests/models/site/test_management.py +8 -4
  49. tests/models/test_ecoinventV3.py +1 -1
  50. tests/models/test_ecoinventV3AndEmberClimate.py +93 -0
  51. {hestia_earth_models-0.61.2.dist-info → hestia_earth_models-0.61.3.dist-info}/LICENSE +0 -0
  52. {hestia_earth_models-0.61.2.dist-info → hestia_earth_models-0.61.3.dist-info}/WHEEL +0 -0
  53. {hestia_earth_models-0.61.2.dist-info → hestia_earth_models-0.61.3.dist-info}/top_level.txt +0 -0
@@ -42,7 +42,6 @@ def _run(cycle: dict):
42
42
  product = find_primary_product(cycle)
43
43
  grouping = get_crop_grouping_fao(MODEL, MODEL_KEY, product.get('term', {}))
44
44
  is_permanent_crop = grouping == 'Permanent crops'
45
- print(is_permanent_crop)
46
45
  return (
47
46
  CycleStartDateDefinition.START_OF_YEAR.value if _is_last_day_of_month(cycle.get('endDate'))
48
47
  else CycleStartDateDefinition.ONE_YEAR_PRIOR.value
@@ -23,7 +23,8 @@ from hestia_earth.models.data.ecoinventV3 import ecoinventV3_emissions
23
23
  from hestia_earth.models.utils.term import get_lookup_value
24
24
  from hestia_earth.models.utils.emission import _new_emission
25
25
  from hestia_earth.models.utils.blank_node import group_by_keys
26
- from hestia_earth.models.utils.pesticideAI import get_from_inputs as get_pesticides
26
+ from hestia_earth.models.utils.pesticideAI import get_pesticides_from_inputs
27
+ from hestia_earth.models.utils.fertiliser import get_fertilisers_from_inputs
27
28
 
28
29
  REQUIREMENTS = {
29
30
  "Cycle": {
@@ -152,7 +153,10 @@ def _animal_inputs(animal: dict):
152
153
  def run(_, cycle: dict):
153
154
  # add all the properties of some Term that inlcude others with the mapping
154
155
  inputs = flatten(
155
- cycle.get('inputs', []) + list(map(_animal_inputs, cycle.get('animals', []))) + get_pesticides(cycle)
156
+ cycle.get('inputs', []) +
157
+ list(map(_animal_inputs, cycle.get('animals', []))) +
158
+ get_pesticides_from_inputs(cycle) +
159
+ get_fertilisers_from_inputs(cycle)
156
160
  )
157
161
  inputs = list(filter(_should_run_input(cycle.get('products', [])), inputs))
158
162
  # group inputs with same id/operation to avoid adding emissions twice
@@ -0,0 +1,125 @@
1
+ """
2
+ ecoinvent v3 and Ember Climate
3
+
4
+ All emissions to air for the cycle.
5
+ """
6
+ from functools import reduce
7
+ from typing import Tuple
8
+
9
+ from hestia_earth.utils.tools import list_sum, flatten
10
+ from hestia_earth.schema import EmissionMethodTier
11
+
12
+ from hestia_earth.models.log import logShouldRun, logRequirements, log_blank_nodes_id
13
+ from hestia_earth.models.utils.emission import _new_emission
14
+ from hestia_earth.models.utils.blank_node import group_by_keys
15
+ from hestia_earth.models.utils.completeness import _is_term_type_complete
16
+ from hestia_earth.models.utils.term import get_electricity_grid_mix_terms
17
+ from .utils import get_emission, get_all_emission_terms
18
+
19
+ REQUIREMENTS = {
20
+ "Cycle": {
21
+ "site": {
22
+ "@type": "Site",
23
+ "country": {"@type": "Term", "termType": "region"}
24
+ },
25
+ "inputs": [{
26
+ "@type": "Input",
27
+ "term.@id": ["electricityGridMarketMix", "electricityGridRenewableMix"],
28
+ "value": ""
29
+ }],
30
+ "completeness.electricityFuel": "True"
31
+ }
32
+ }
33
+ RETURNS = {
34
+ "Emission": [{
35
+ "value": "",
36
+ "methodTier": "background",
37
+ "@type": "Emission",
38
+ "inputs": ""
39
+ }]
40
+ }
41
+ LOOKUPS = {
42
+ "region-ember-energySources": "using `country`",
43
+ "ember-ecoinvent-mapping": ["ember", "ecoinventId", "ecoinventName"]
44
+ }
45
+
46
+ MODEL = 'ecoinventV3AndEmberClimate'
47
+ MODEL_KEY = 'impactAssessment' # keep to generate entry in "model-links.json"
48
+ TIER = EmissionMethodTier.BACKGROUND.value
49
+
50
+
51
+ def _emission(value: float, term_id: str, inputs: list, operation: dict) -> dict:
52
+ emission = _new_emission(term_id, MODEL)
53
+ emission['value'] = [value]
54
+ emission['methodTier'] = TIER
55
+ emission["inputs"] = list(inputs)
56
+ if operation:
57
+ emission["operation"] = operation
58
+ return emission
59
+
60
+
61
+ def _grid_inputs(inputs: list, electricity_grid_terms: list):
62
+ electricity_grid_term_ids = [v.get('@id') for v in electricity_grid_terms]
63
+ return [
64
+ i for i in inputs if i.get("term", {}).get("@id") in electricity_grid_term_ids
65
+ ]
66
+
67
+
68
+ def _run_input(cycle: dict, inputs: list, emission_term_id: str, input_term: dict):
69
+ inputs = _grid_inputs(inputs, [input_term])
70
+ return [
71
+ _emission(
72
+ value=get_emission(
73
+ term_id=emission_term_id,
74
+ country=cycle.get("site", {}).get("country", {}).get("@id", ""),
75
+ energy=list_sum(flatten([i.get("value", []) for i in op_inputs])),
76
+ year=cycle.get("endDate", ""),
77
+ model=MODEL
78
+ ),
79
+ term_id=emission_term_id,
80
+ inputs=[input_term],
81
+ operation=op_inputs[0].get("operation")
82
+ )
83
+ for op_inputs in _group_by_operation(inputs).values()
84
+ ] if inputs else []
85
+
86
+
87
+ def _group_by_operation(inputs: list) -> dict:
88
+ return reduce(group_by_keys(['operation']), inputs, {})
89
+
90
+
91
+ def _run_emission(cycle: dict, electricity_grid_terms: list, inputs: list, emission_term_id: str) -> list:
92
+ return flatten([
93
+ _run_input(
94
+ cycle=cycle,
95
+ inputs=inputs,
96
+ emission_term_id=emission_term_id,
97
+ input_term=input_term
98
+ ) for input_term in electricity_grid_terms
99
+ ])
100
+
101
+
102
+ def _should_run_emission(cycle: dict, electricity_grid_terms: list, term_id: str) -> Tuple[bool, list]:
103
+ term_type_complete = _is_term_type_complete(cycle, 'electricityFuel')
104
+ inputs = _grid_inputs(cycle.get('inputs', []), electricity_grid_terms)
105
+ has_relevant_inputs = bool(inputs)
106
+ has_country = bool(cycle.get("site", {}).get("country", {}))
107
+
108
+ logRequirements(cycle, model=MODEL, term=term_id,
109
+ input_ids=log_blank_nodes_id(inputs))
110
+
111
+ should_run = all([term_type_complete, has_relevant_inputs, has_country])
112
+ logShouldRun(cycle, MODEL, term_id, should_run, methodTier=TIER)
113
+ return should_run, inputs
114
+
115
+
116
+ def _run_emissions(cycle: dict, electricity_grid_terms: list):
117
+ def run_emissions_for_term(term_id: str) -> list:
118
+ should_run, inputs = _should_run_emission(cycle, electricity_grid_terms, term_id)
119
+ return _run_emission(cycle, electricity_grid_terms, inputs, term_id) if should_run else []
120
+ return run_emissions_for_term
121
+
122
+
123
+ def run(cycle: dict):
124
+ electricity_grid_terms = get_electricity_grid_mix_terms()
125
+ return flatten(list(map(_run_emissions(cycle, electricity_grid_terms), get_all_emission_terms())))
@@ -0,0 +1,125 @@
1
+ from typing import Dict, Any, Union, List
2
+
3
+ from hestia_earth.utils.lookup import download_lookup, get_table_value, column_name, extract_grouped_data
4
+ from hestia_earth.utils.tools import safe_parse_float, non_empty_list
5
+
6
+ from hestia_earth.models.log import debugMissingLookup
7
+ from hestia_earth.models.data.ecoinventV3 import ecoinventV3_emissions
8
+
9
+
10
+ EMBER_ECOINVENT_LOOKUP_NAME = "ember-ecoinvent-mapping.csv"
11
+
12
+
13
+ def _lookup_data(term_id: str, grouping: str, country_id: str, year: int, lookup_name: str, **log_args):
14
+ lookup = download_lookup(lookup_name)
15
+ data = get_table_value(lookup, 'termid', country_id, column_name(grouping))
16
+ debugMissingLookup(lookup_name, 'termid', country_id, grouping, data, term=term_id, **log_args)
17
+ percentage = extract_grouped_data(data, str(year))
18
+ return safe_parse_float(percentage, None)
19
+
20
+
21
+ def _convert_name(name: str) -> str: return name.replace(";", ",")
22
+
23
+
24
+ def _safe_parse_int(value: str, default=0):
25
+ """
26
+ Parse a string into an int.
27
+ """
28
+ try:
29
+ return int(value)
30
+ except ValueError:
31
+ return default
32
+
33
+
34
+ def _zero_from_non_numeric(value: Any) -> float:
35
+ try:
36
+ return float(value)
37
+ except (ValueError, TypeError):
38
+ return 0.0
39
+
40
+
41
+ def _extract_emission_value(value_iter: Any) -> Union[float, None]:
42
+ value_list = list(value_iter)
43
+ try:
44
+ if len(list(value_list)) > 0 and len(list(value_list)[0]) > 1:
45
+ return safe_parse_float(list(value_list)[0][1])
46
+ except ValueError:
47
+ return None
48
+
49
+
50
+ def _get_emission_rate_per_source(ember_ecoinvent_mapping: Dict, emission_term_id: str) -> Dict:
51
+ """
52
+ Returns the emissions rate in kg/kWh as a mapping indexed by ember energy source name.
53
+ eg: {"bioenergy": 0.8947372128046288, "coal": 0.000124581084822, ... }
54
+ """
55
+
56
+ return {
57
+ ember_source: _extract_emission_value(filter(
58
+ lambda x: x[0] == emission_term_id,
59
+ ecoinventV3_emissions(ecoinvent_lookup["ecoinventname"])
60
+ ))
61
+ for ember_source, ecoinvent_lookup in ember_ecoinvent_mapping.items()
62
+ }
63
+
64
+
65
+ def get_emission(term_id: str, country: str, year: str, energy: float, model: str) -> float:
66
+ """
67
+ Get the <term_id> emissions in kg for the energy consumed.
68
+ a: ecoInventId of each source - from "ember-ecoinvent-mapping.csv"
69
+ b: Ember sources list - from b
70
+ c: Percentages per source - from region-ember-energySources.csv (country, ember-source, year:value)
71
+ d: Energy per source (kWh) - from energy * each of c
72
+ e: Emissions/kWh per source - from ecoinvent (using id from b)
73
+ f: Emissions per source (kg) - from e * d for each source
74
+ g: Total emissions - from sum(f)
75
+ """
76
+ # a: ecoInventId of each source
77
+ ember_ecoinvent_lookup = download_lookup(EMBER_ECOINVENT_LOOKUP_NAME)
78
+ ember_ecoinvent_mapping = {
79
+ row["ember"].lower(): {"ecoinventid": row["ecoinventid"], "ecoinventname": _convert_name(row["ecoinventname"])}
80
+ for row in ember_ecoinvent_lookup
81
+ }
82
+
83
+ # b: Ember sources
84
+ ember_sources = list(ember_ecoinvent_mapping.keys())
85
+
86
+ # c: Percentages per source
87
+ percentages_per_source = {
88
+ source: _lookup_data(
89
+ term_id=source,
90
+ grouping=source,
91
+ country_id=country,
92
+ year=_safe_parse_int(year),
93
+ lookup_name="region-ember-energySources.csv",
94
+ model=model
95
+ )
96
+ for source in ember_sources
97
+ }
98
+
99
+ # d: Energy per source (kWh)
100
+ energy_per_source = {
101
+ source: energy * _zero_from_non_numeric(percentage) / 100
102
+ for source, percentage in percentages_per_source.items()
103
+ }
104
+
105
+ # e: Emissions/kWh per source
106
+ emission_rate_per_source = _get_emission_rate_per_source(
107
+ ember_ecoinvent_mapping=ember_ecoinvent_mapping,
108
+ emission_term_id=term_id
109
+ )
110
+
111
+ # f: Emissions per source (kg)
112
+ emissions_per_source = {
113
+ source: energy_per_source[source] * emission_rate_per_source[source]
114
+ for source in ember_sources
115
+ if energy_per_source[source] is not None and emission_rate_per_source[source] is not None
116
+ }
117
+
118
+ # g: Total emissions (kg of <term_id>)
119
+ return sum(emissions_per_source.values())
120
+
121
+
122
+ def get_all_emission_terms() -> List[str]:
123
+ ember_ecoinvent_lookup = download_lookup(EMBER_ECOINVENT_LOOKUP_NAME)
124
+ eco_invent_name = _convert_name(ember_ecoinvent_lookup[0]["ecoinventname"])
125
+ return [e[0] for e in non_empty_list(ecoinventV3_emissions(eco_invent_name))]
@@ -1,14 +1,14 @@
1
1
  from functools import reduce
2
- from hestia_earth.schema import EmissionMethodTier, TermTermType
2
+ from hestia_earth.schema import EmissionMethodTier
3
3
  from hestia_earth.utils.lookup import download_lookup
4
- from hestia_earth.utils.model import find_term_match, filter_list_term_type
4
+ from hestia_earth.utils.model import find_term_match
5
5
  from hestia_earth.utils.tools import list_sum
6
6
 
7
7
  from hestia_earth.models.log import debugValues, logRequirements, logShouldRun
8
8
  from hestia_earth.models.utils import _filter_list_term_unit
9
9
  from hestia_earth.models.utils.completeness import _is_term_type_complete
10
10
  from hestia_earth.models.utils.inorganicFertiliser import (
11
- get_NH3_emission_factor, get_terms, get_term_lookup, BREAKDOWN_LOOKUP, get_country_breakdown
11
+ get_NH3_emission_factor, get_terms, get_term_lookup, BREAKDOWN_LOOKUP, get_country_breakdown, get_cycle_inputs
12
12
  )
13
13
  from hestia_earth.models.utils.constant import Units
14
14
  from hestia_earth.models.utils.emission import _new_emission
@@ -26,6 +26,12 @@ REQUIREMENTS = {
26
26
  "optional": {
27
27
  "properties": [{"@type": "Property", "value": "", "term.@id": "nitrogenContent"}]
28
28
  }
29
+ },
30
+ {
31
+ "@type": "Input",
32
+ "value": "",
33
+ "term.termType": "fertiliserBrandName",
34
+ "properties": [{"@type": "Property", "value": "", "key.termType": "inorganicFertiliser"}]
29
35
  }
30
36
  ],
31
37
  "site": {
@@ -115,7 +121,7 @@ def _should_run(cycle: dict):
115
121
  measurements, 'temperatureAnnual', end_date) or most_relevant_measurement_value(
116
122
  measurements, 'temperatureLongTermAnnualMean', end_date)
117
123
 
118
- inputs = filter_list_term_type(cycle.get('inputs', []), TermTermType.INORGANICFERTILISER)
124
+ inputs = get_cycle_inputs(cycle)
119
125
  N_inputs = _filter_list_term_unit(inputs, Units.KG_N)
120
126
  has_N_inputs = len(N_inputs) > 0
121
127
 
@@ -11,15 +11,23 @@ from . import MODEL
11
11
  REQUIREMENTS = {
12
12
  "Cycle": {
13
13
  "completeness.fertiliser": "True",
14
- "inputs": [{
15
- "@type": "Input",
16
- "value": "",
17
- "term.units": ["kg", "kg N"],
18
- "term.termType": "inorganicFertiliser",
19
- "optional": {
20
- "properties": [{"@type": "Property", "value": "", "term.@id": "nitrogenContent"}]
14
+ "inputs": [
15
+ {
16
+ "@type": "Input",
17
+ "value": "",
18
+ "term.units": ["kg", "kg N"],
19
+ "term.termType": "inorganicFertiliser",
20
+ "optional": {
21
+ "properties": [{"@type": "Property", "value": "", "term.@id": "nitrogenContent"}]
22
+ }
23
+ },
24
+ {
25
+ "@type": "Input",
26
+ "value": "",
27
+ "term.termType": "fertiliserBrandName",
28
+ "properties": [{"@type": "Property", "value": "", "key.termType": "inorganicFertiliser"}]
21
29
  }
22
- }]
30
+ ]
23
31
  }
24
32
  }
25
33
  RETURNS = {
@@ -12,15 +12,23 @@ from . import MODEL
12
12
  REQUIREMENTS = {
13
13
  "Cycle": {
14
14
  "completeness.fertiliser": "True",
15
- "inputs": [{
16
- "@type": "Input",
17
- "value": "",
18
- "term.units": ["kg", "kg N"],
19
- "term.termType": "inorganicFertiliser",
20
- "optional": {
21
- "properties": [{"@type": "Property", "value": "", "term.@id": "nitrogenContent"}]
15
+ "inputs": [
16
+ {
17
+ "@type": "Input",
18
+ "value": "",
19
+ "term.units": ["kg", "kg N"],
20
+ "term.termType": "inorganicFertiliser",
21
+ "optional": {
22
+ "properties": [{"@type": "Property", "value": "", "term.@id": "nitrogenContent"}]
23
+ }
24
+ },
25
+ {
26
+ "@type": "Input",
27
+ "value": "",
28
+ "term.termType": "fertiliserBrandName",
29
+ "properties": [{"@type": "Property", "value": "", "key.termType": "inorganicFertiliser"}]
22
30
  }
23
- }],
31
+ ],
24
32
  "emissions": [
25
33
  {"@type": "Emission", "value": "", "term.@id": "no3ToGroundwaterInorganicFertiliser"},
26
34
  {"@type": "Emission", "value": "", "term.@id": "nh3ToAirInorganicFertiliser"},
@@ -11,14 +11,22 @@ from . import MODEL
11
11
  REQUIREMENTS = {
12
12
  "Cycle": {
13
13
  "completeness.fertiliser": "True",
14
- "inputs": [{
15
- "@type": "Input",
16
- "value": "",
17
- "term.termType": "organicFertiliser",
18
- "optional": {
19
- "properties": [{"@type": "Property", "value": "", "term.@id": "nitrogenContent"}]
14
+ "inputs": [
15
+ {
16
+ "@type": "Input",
17
+ "value": "",
18
+ "term.termType": "organicFertiliser",
19
+ "optional": {
20
+ "properties": [{"@type": "Property", "value": "", "term.@id": "nitrogenContent"}]
21
+ }
22
+ },
23
+ {
24
+ "@type": "Input",
25
+ "value": "",
26
+ "term.termType": "fertiliserBrandName",
27
+ "properties": [{"@type": "Property", "value": "", "key.termType": "organicFertiliser"}]
20
28
  }
21
- }]
29
+ ]
22
30
  }
23
31
  }
24
32
  RETURNS = {
@@ -12,14 +12,22 @@ from . import MODEL
12
12
  REQUIREMENTS = {
13
13
  "Cycle": {
14
14
  "completeness.fertiliser": "True",
15
- "inputs": [{
16
- "@type": "Input",
17
- "value": "",
18
- "term.termType": "organicFertiliser",
19
- "optional": {
20
- "properties": [{"@type": "Property", "value": "", "term.@id": "nitrogenContent"}]
15
+ "inputs": [
16
+ {
17
+ "@type": "Input",
18
+ "value": "",
19
+ "term.termType": "organicFertiliser",
20
+ "optional": {
21
+ "properties": [{"@type": "Property", "value": "", "term.@id": "nitrogenContent"}]
22
+ }
23
+ },
24
+ {
25
+ "@type": "Input",
26
+ "value": "",
27
+ "term.termType": "fertiliserBrandName",
28
+ "properties": [{"@type": "Property", "value": "", "key.termType": "organicFertiliser"}]
21
29
  }
22
- }],
30
+ ],
23
31
  "emissions": [
24
32
  {"@type": "Emission", "value": "", "term.@id": "no3ToGroundwaterOrganicFertiliser"},
25
33
  {"@type": "Emission", "value": "", "term.@id": "nh3ToAirOrganicFertiliser"},
@@ -131,7 +131,7 @@ LOOKUPS = {
131
131
  }
132
132
  RETURNS = {
133
133
  "Animal": [{
134
- "Input": [{
134
+ "inputs": [{
135
135
  "@type": "Input",
136
136
  "term.termType": ["crop", "forage"],
137
137
  "value": ""
@@ -0,0 +1,104 @@
1
+ """
2
+ Note: when the `liveweightPerHead` property is provided, this model will only work if the returned value is
3
+ greater than or equal to `liveweightPerHead` value.
4
+ """
5
+ from hestia_earth.schema import TermTermType
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
+
10
+ from hestia_earth.models.log import logRequirements, logShouldRun, debugMissingLookup
11
+ from hestia_earth.models.utils.property import _new_property, node_has_no_property
12
+ from .. import MODEL
13
+
14
+ REQUIREMENTS = {
15
+ "Cycle": {
16
+ "site": {
17
+ "@type": "Site",
18
+ "country": {"@type": "Term", "termType": "region"}
19
+ },
20
+ "animals": [{
21
+ "@type": "Animal",
22
+ "term.termType": "liveAnimal",
23
+ "none": {
24
+ "properties": [{
25
+ "@type": "Property",
26
+ "value": "",
27
+ "term.@id": "weightAtMaturity"
28
+ }]
29
+ },
30
+ "optional": {
31
+ "properties": [{
32
+ "@type": "Property",
33
+ "value": "",
34
+ "term.@id": "liveweightPerHead"
35
+ }]
36
+ }
37
+ }]
38
+ }
39
+ }
40
+ LOOKUPS = {
41
+ "region-liveAnimal-weightAtMaturity": "weight at maturity"
42
+ }
43
+ RETURNS = {
44
+ "Animal": [{
45
+ "properties": [{
46
+ "@type": "Property",
47
+ "value": ""
48
+ }]
49
+ }]
50
+ }
51
+ TERM_ID = 'weightAtMaturity'
52
+
53
+
54
+ def _property(value: float):
55
+ prop = _new_property(TERM_ID, MODEL)
56
+ prop['value'] = value
57
+ return prop
58
+
59
+
60
+ def _run_animal(data: dict):
61
+ animal = data.get('animal')
62
+ value = data.get('value')
63
+ return animal | {
64
+ 'properties': animal.get('properties', []) + [_property(value)]
65
+ }
66
+
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
+ def _should_run(cycle: dict):
78
+ country_id = cycle.get('site', {}).get('country', {}).get('@id')
79
+ live_animals = filter_list_term_type(cycle.get('animals', []), TermTermType.LIVEANIMAL)
80
+ 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]
82
+
83
+ def _should_run_animal(value: dict):
84
+ lookup_value = value.get('value')
85
+ term_id = value.get('animal').get('term').get('@id')
86
+ liveweightPerHead = find_term_match(value.get('animal').get('properties', []), 'liveweightPerHead', {})
87
+ liveweightPerHead_value = liveweightPerHead.get('value')
88
+
89
+ logRequirements(cycle, model=MODEL, term=term_id,
90
+ country_id=country_id,
91
+ weightAtMaturity=lookup_value,
92
+ liveweightPerHead=liveweightPerHead_value)
93
+
94
+ should_run = all([country_id, value.get('value') is not None, lookup_value >= liveweightPerHead_value])
95
+ logShouldRun(cycle, MODEL, term_id, should_run)
96
+
97
+ return should_run
98
+
99
+ return list(filter(_should_run_animal, live_animals_with_value))
100
+
101
+
102
+ def run(cycle: dict):
103
+ animals = _should_run(cycle)
104
+ return list(map(_run_animal, animals))
@@ -7,6 +7,7 @@ from hestia_earth.models.log import debugValues, logRequirements, logShouldRun
7
7
  from hestia_earth.models.utils.term import get_lookup_value
8
8
  from hestia_earth.models.utils.emission import _new_emission
9
9
  from hestia_earth.models.utils.product import has_flooded_rice
10
+ from hestia_earth.models.utils.organicFertiliser import get_cycle_inputs as get_organicFertiliser_inputs
10
11
  from . import MODEL
11
12
 
12
13
  REQUIREMENTS = {
@@ -17,7 +18,19 @@ REQUIREMENTS = {
17
18
  "country": {"@type": "Term", "termType": "region"}
18
19
  },
19
20
  "optional": {
20
- "inputs": [{"@type": "Input", "value": "", "term.termType": "organicFertiliser"}],
21
+ "inputs": [
22
+ {
23
+ "@type": "Input",
24
+ "value": "",
25
+ "term.termType": "organicFertiliser"
26
+ },
27
+ {
28
+ "@type": "Input",
29
+ "value": "",
30
+ "term.termType": "fertiliserBrandName",
31
+ "properties": [{"@type": "Property", "value": "", "key.termType": "organicFertiliser"}]
32
+ }
33
+ ],
21
34
  "products": [{"@type": "Product", "value": "", "term.@id": "aboveGroundCropResidueIncorporated"}],
22
35
  "practices": [
23
36
  {"@type": "Practice", "value": "", "term.termType": "cropResidueManagement"},
@@ -99,7 +112,7 @@ def _get_fertiliser_value(input: dict, suffix: str = ''):
99
112
 
100
113
  def _calculate_SFo(cycle: dict, suffix: str = ''):
101
114
  cropResidue = _get_cropResidue_value(cycle, suffix)
102
- fertilisers = filter_list_term_type(cycle.get('inputs', []), TermTermType.ORGANICFERTILISER)
115
+ fertilisers = get_organicFertiliser_inputs(cycle)
103
116
  fert_value = list_sum([_get_fertiliser_value(i, suffix) for i in fertilisers])
104
117
  return (1 + (fert_value/1000) + (cropResidue/1000)) ** 0.59
105
118
 
@@ -12,15 +12,23 @@ from . import MODEL
12
12
  REQUIREMENTS = {
13
13
  "Cycle": {
14
14
  "completeness.fertiliser": "True",
15
- "inputs": [{
16
- "@type": "Input",
17
- "value": "",
18
- "term.units": ["kg", "kg N"],
19
- "term.termType": "inorganicFertiliser",
20
- "optional": {
21
- "properties": [{"@type": "Property", "value": "", "term.@id": "nitrogenContent"}]
15
+ "inputs": [
16
+ {
17
+ "@type": "Input",
18
+ "value": "",
19
+ "term.units": ["kg", "kg N"],
20
+ "term.termType": "inorganicFertiliser",
21
+ "optional": {
22
+ "properties": [{"@type": "Property", "value": "", "term.@id": "nitrogenContent"}]
23
+ }
24
+ },
25
+ {
26
+ "@type": "Input",
27
+ "value": "",
28
+ "term.termType": "fertiliserBrandName",
29
+ "properties": [{"@type": "Property", "value": "", "key.termType": "inorganicFertiliser"}]
22
30
  }
23
- }],
31
+ ],
24
32
  "optional": {
25
33
  "endDate": "",
26
34
  "site": {