hestia-earth-models 0.59.0__py3-none-any.whl → 0.59.2__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 (35) hide show
  1. hestia_earth/models/cycle/irrigatedTypeUnspecified.py +6 -7
  2. hestia_earth/models/impact_assessment/irrigated.py +4 -1
  3. hestia_earth/models/ipcc2006/n2OToAirCropResidueDecompositionIndirect.py +3 -3
  4. hestia_earth/models/ipcc2006/n2OToAirExcretaIndirect.py +3 -3
  5. hestia_earth/models/ipcc2006/n2OToAirInorganicFertiliserIndirect.py +5 -4
  6. hestia_earth/models/ipcc2006/n2OToAirOrganicFertiliserIndirect.py +5 -4
  7. hestia_earth/models/ipcc2006/utils.py +0 -16
  8. hestia_earth/models/ipcc2019/n2OToAirCropResidueDecompositionDirect.py +8 -7
  9. hestia_earth/models/ipcc2019/n2OToAirCropResidueDecompositionIndirect.py +100 -0
  10. hestia_earth/models/ipcc2019/n2OToAirExcretaIndirect.py +100 -0
  11. hestia_earth/models/ipcc2019/n2OToAirInorganicFertiliserIndirect.py +54 -61
  12. hestia_earth/models/ipcc2019/n2OToAirOrganicFertiliserIndirect.py +58 -66
  13. hestia_earth/models/ipcc2019/nh3ToAirOrganicFertiliser.py +104 -0
  14. hestia_earth/models/ipcc2019/noxToAirOrganicFertiliser.py +104 -0
  15. hestia_earth/models/ipcc2019/organicCarbonPerHa.py +60 -22
  16. hestia_earth/models/ipcc2019/utils.py +28 -16
  17. hestia_earth/models/mocking/search-results.json +35 -39
  18. hestia_earth/models/site/soilMeasurement.py +197 -0
  19. hestia_earth/models/utils/cycle.py +7 -6
  20. hestia_earth/models/utils/emission.py +15 -0
  21. hestia_earth/models/utils/site.py +1 -1
  22. hestia_earth/models/version.py +1 -1
  23. {hestia_earth_models-0.59.0.dist-info → hestia_earth_models-0.59.2.dist-info}/METADATA +1 -1
  24. {hestia_earth_models-0.59.0.dist-info → hestia_earth_models-0.59.2.dist-info}/RECORD +35 -25
  25. tests/models/ipcc2019/test_n2OToAirCropResidueDecompositionIndirect.py +71 -0
  26. tests/models/ipcc2019/test_n2OToAirExcretaIndirect.py +71 -0
  27. tests/models/ipcc2019/test_n2OToAirInorganicFertiliserIndirect.py +36 -13
  28. tests/models/ipcc2019/test_n2OToAirOrganicFertiliserIndirect.py +36 -13
  29. tests/models/ipcc2019/test_nh3ToAirOrganicFertiliser.py +35 -0
  30. tests/models/ipcc2019/test_noxToAirOrganicFertiliser.py +35 -0
  31. tests/models/ipcc2019/test_organicCarbonPerHa.py +45 -4
  32. tests/models/site/test_soilMeasurement.py +159 -0
  33. {hestia_earth_models-0.59.0.dist-info → hestia_earth_models-0.59.2.dist-info}/LICENSE +0 -0
  34. {hestia_earth_models-0.59.0.dist-info → hestia_earth_models-0.59.2.dist-info}/WHEEL +0 -0
  35. {hestia_earth_models-0.59.0.dist-info → hestia_earth_models-0.59.2.dist-info}/top_level.txt +0 -0
@@ -1,108 +1,100 @@
1
- from hestia_earth.schema import EmissionMethodTier, EmissionStatsDefinition, TermTermType
1
+ from hestia_earth.schema import EmissionMethodTier, EmissionStatsDefinition
2
2
 
3
- from hestia_earth.models.log import debugValues, logRequirements, logShouldRun
3
+ from hestia_earth.models.log import debugValues, logRequirements, logShouldRun, log_as_table
4
4
  from hestia_earth.models.utils.constant import Units, get_atomic_conversion
5
5
  from hestia_earth.models.utils.completeness import _is_term_type_complete
6
- from hestia_earth.models.utils.cycle import get_organic_fertiliser_N_total, get_ecoClimateZone
7
- from hestia_earth.models.utils.emission import _new_emission
8
- from .utils import get_nh3_no3_nox_to_n, COEFF_NO3_N2O, COEFF_N_NH3NOX_organic_animal, get_FracNH3NOx_N2O, \
9
- get_FracLEACH_H
6
+ from hestia_earth.models.utils.emission import _new_emission, get_nh3_no3_nox_to_n
7
+ from hestia_earth.models.utils.cycle import get_ecoClimateZone
8
+ from .utils import ecoClimate_factors, EF4_FACTORS, EF5_FACTORS
10
9
  from . import MODEL
11
10
 
12
11
  REQUIREMENTS = {
13
12
  "Cycle": {
14
13
  "completeness.fertiliser": "True",
15
- "completeness.water": "True",
16
- "inputs": [
17
- {
18
- "@type": "Input",
19
- "value": "",
20
- "term.termType": "organicFertiliser",
21
- "term.units": "kg N"
22
- }
23
- ],
24
14
  "emissions": [
25
- {"@type": "Emission", "value": "", "term.@id": "no3ToGroundwaterInorganicFertiliser"},
26
- {"@type": "Emission", "value": "", "term.@id": "nh3ToAirInorganicFertiliser"},
27
- {"@type": "Emission", "value": "", "term.@id": "noxToAirInorganicFertiliser"}
15
+ {"@type": "Emission", "value": "", "term.@id": "no3ToGroundwaterOrganicFertiliser"},
16
+ {"@type": "Emission", "value": "", "term.@id": "nh3ToAirOrganicFertiliser"},
17
+ {"@type": "Emission", "value": "", "term.@id": "noxToAirOrganicFertiliser"}
28
18
  ],
29
- "site": {
30
- "@type": "Site",
31
- "measurements": [{"@type": "Measurement", "value": "", "term.@id": "ecoClimateZone"}]
32
- },
33
19
  "optional": {
34
- "practices": [{"@type": "Practice", "value": "", "term.termType": "waterRegime"}]
20
+ "site": {
21
+ "@type": "Site",
22
+ "measurements": [
23
+ {"@type": "Measurement", "value": "", "term.@id": "ecoClimateZone"}
24
+ ]
25
+ }
35
26
  }
36
27
  }
37
28
  }
38
29
  RETURNS = {
39
30
  "Emission": [{
40
31
  "value": "",
41
- "sd": "",
42
- "min": "",
43
- "max": "",
44
- "methodTier": "tier 1",
45
- "statsDefinition": "modelled"
32
+ "methodTier": "tier 1"
46
33
  }]
47
34
  }
48
-
49
35
  TERM_ID = 'n2OToAirOrganicFertiliserIndirect'
50
- NO3_TERM_ID = 'no3ToGroundwaterInorganicFertiliser'
51
- NH3_TERM_ID = 'nh3ToAirInorganicFertiliser'
52
- NOX_TERM_ID = 'noxToAirInorganicFertiliser'
53
36
  TIER = EmissionMethodTier.TIER_1.value
37
+ NO3_TERM_ID = 'no3ToGroundwaterOrganicFertiliser'
38
+ NH3_TERM_ID = 'nh3ToAirOrganicFertiliser'
39
+ NOX_TERM_ID = 'noxToAirOrganicFertiliser'
54
40
 
55
41
 
56
- def _emission(value: float, sd: float, min: float, max: float):
42
+ def _emission(value: float, min: float, max: float, aggregated: bool = False):
57
43
  emission = _new_emission(TERM_ID, MODEL)
58
44
  emission['value'] = [value]
59
- emission['sd'] = [sd]
60
45
  emission['min'] = [min]
61
46
  emission['max'] = [max]
62
47
  emission['methodTier'] = TIER
63
48
  emission['statsDefinition'] = EmissionStatsDefinition.MODELLED.value
49
+ emission['methodModelDescription'] = 'Aggregated version' if aggregated else 'Disaggregated version'
64
50
  return emission
65
51
 
66
52
 
67
- def _run(cycle: dict):
68
- N_total = get_organic_fertiliser_N_total(cycle)
69
- nh3_n, no3_n, nox_n = get_nh3_no3_nox_to_n(cycle, NH3_TERM_ID, NO3_TERM_ID, NOX_TERM_ID)
53
+ def _run(cycle: dict, no3: float, nh3: float, nox: float):
54
+ ecoClimateZone = get_ecoClimateZone(cycle)
55
+
56
+ ef4, aggregated = ecoClimate_factors(EF4_FACTORS, ecoClimateZone=ecoClimateZone)
57
+ ef5 = EF5_FACTORS.get('default')
58
+
70
59
  debugValues(cycle, model=MODEL, term=TERM_ID,
71
- no3_n=no3_n,
72
- nh3_n=nh3_n,
73
- nox_n=nox_n)
60
+ ecoClimateZone=ecoClimateZone,
61
+ ef4_factors_used=log_as_table(ef4),
62
+ ef5_factors_used=log_as_table(ef5),
63
+ aggregated=aggregated)
74
64
 
75
- value, min_val, max_val, std = [
76
- get_FracNH3NOx_N2O(cycle, TERM_ID)[x] * (
77
- N_total * COEFF_N_NH3NOX_organic_animal[x] if nox_n == 0 or nh3_n == 0 else nh3_n + nox_n
78
- ) +
79
- COEFF_NO3_N2O[x] * (
80
- N_total * get_FracLEACH_H(cycle, TERM_ID)[x] if no3_n == 0 else no3_n
81
- ) for x in range(4)
82
- ]
83
- return [_emission(
84
- value * get_atomic_conversion(Units.KG_N2O, Units.TO_N),
85
- std * get_atomic_conversion(Units.KG_N2O, Units.TO_N),
86
- min_val * get_atomic_conversion(Units.KG_N2O, Units.TO_N),
87
- max_val * get_atomic_conversion(Units.KG_N2O, Units.TO_N)
88
- )]
65
+ value = sum([
66
+ no3 * ef5['value'],
67
+ nh3 * ef4['value'],
68
+ nox * ef4['value']
69
+ ]) * get_atomic_conversion(Units.KG_N2O, Units.TO_N)
70
+ min = sum([
71
+ no3 * ef5['min'],
72
+ nh3 * ef4['min'],
73
+ nox * ef4['min']
74
+ ]) * get_atomic_conversion(Units.KG_N2O, Units.TO_N)
75
+ max = sum([
76
+ no3 * ef5['max'],
77
+ nh3 * ef4['max'],
78
+ nox * ef4['max']
79
+ ]) * get_atomic_conversion(Units.KG_N2O, Units.TO_N)
80
+ return [_emission(value, min, max, aggregated=aggregated)]
89
81
 
90
82
 
91
83
  def _should_run(cycle: dict):
92
- N_organic_fertiliser = get_organic_fertiliser_N_total(cycle)
93
- ecoClimateZone = get_ecoClimateZone(cycle)
94
- fertiliser_complete = _is_term_type_complete(cycle, 'fertiliser')
95
- water_complete = _is_term_type_complete(cycle, TermTermType.WATER)
84
+ nh3_n, no3_n, nox_n = get_nh3_no3_nox_to_n(cycle, NH3_TERM_ID, NO3_TERM_ID, NOX_TERM_ID)
85
+ term_type_complete = _is_term_type_complete(cycle, 'fertiliser')
96
86
 
97
87
  logRequirements(cycle, model=MODEL, term=TERM_ID,
98
- N_organic_fertiliser=N_organic_fertiliser,
99
- ecoClimateZone=ecoClimateZone,
100
- term_type_fertiliser_complete=fertiliser_complete,
101
- term_type_water_complete=water_complete)
88
+ no3_n=no3_n,
89
+ nh3_n=nh3_n,
90
+ nox_n=nox_n,
91
+ term_type_cropResidue_complete=term_type_complete)
102
92
 
103
- should_run = all([N_organic_fertiliser is not None, ecoClimateZone, fertiliser_complete, water_complete])
104
- logShouldRun(cycle, MODEL, TERM_ID, should_run)
105
- return should_run
93
+ should_run = all([no3_n is not None, nh3_n is not None, nox_n is not None, term_type_complete])
94
+ logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
95
+ return should_run, no3_n, nh3_n, nox_n
106
96
 
107
97
 
108
- def run(cycle: dict): return _run(cycle) if _should_run(cycle) else []
98
+ def run(cycle: dict):
99
+ should_run, no3, nh3, nox = _should_run(cycle)
100
+ return _run(cycle, no3, nh3, nox) if should_run else []
@@ -0,0 +1,104 @@
1
+ from hestia_earth.schema import EmissionMethodTier, EmissionStatsDefinition, TermTermType
2
+ from hestia_earth.utils.model import filter_list_term_type
3
+ from hestia_earth.utils.tools import list_sum
4
+
5
+ from hestia_earth.models.log import logRequirements, logShouldRun, debugValues, log_as_table
6
+ from hestia_earth.models.utils.blank_node import get_N_total
7
+ from hestia_earth.models.utils.constant import Units, get_atomic_conversion
8
+ from hestia_earth.models.utils.completeness import _is_term_type_complete
9
+ from hestia_earth.models.utils.emission import _new_emission
10
+ from hestia_earth.models.utils.cycle import get_organic_fertiliser_N_total
11
+ from hestia_earth.models.utils.term import get_lookup_value
12
+ from . import MODEL
13
+
14
+ REQUIREMENTS = {
15
+ "Cycle": {
16
+ "completeness.fertiliser": "True",
17
+ "inputs": [
18
+ {
19
+ "@type": "Input",
20
+ "value": "",
21
+ "term.termType": "organicFertiliser",
22
+ "optional": {
23
+ "properties": [{"@type": "Property", "value": "", "term.@id": "nitrogenContent"}]
24
+ }
25
+ }
26
+ ]
27
+ }
28
+ }
29
+ RETURNS = {
30
+ "Emission": [{
31
+ "value": "",
32
+ "sd": "",
33
+ "min": "",
34
+ "max": "",
35
+ "methodTier": "tier 1",
36
+ "statsDefinition": "modelled",
37
+ "methodModelDescription": "Aggregated version"
38
+ }]
39
+ }
40
+ LOOKUPS = {
41
+ "organicFertiliser": ["IPCC_2019_FRACGASM_NH3-N", "IPCC_2019_FRACGASM_NH3-N-min", "IPCC_2019_FRACGASM_NH3-N-max"]
42
+ }
43
+ TERM_ID = 'nh3ToAirOrganicFertiliser'
44
+ TIER = EmissionMethodTier.TIER_1.value
45
+
46
+
47
+ def _emission(value: float, min: float, max: float):
48
+ emission = _new_emission(TERM_ID, MODEL)
49
+ emission['value'] = [value]
50
+ emission['min'] = [min]
51
+ emission['max'] = [max]
52
+ emission['methodTier'] = TIER
53
+ emission['statsDefinition'] = EmissionStatsDefinition.MODELLED.value
54
+ emission['methodModelDescription'] = 'Aggregated version'
55
+ return emission
56
+
57
+
58
+ def _input_values(input: dict):
59
+ N_total = list_sum(get_N_total([input]))
60
+ return {
61
+ 'id': input.get('term', {}).get('@id'),
62
+ 'N': N_total,
63
+ 'value': get_lookup_value(input.get('term', {}), LOOKUPS['organicFertiliser'][0]),
64
+ 'min': get_lookup_value(input.get('term', {}), LOOKUPS['organicFertiliser'][1]),
65
+ 'max': get_lookup_value(input.get('term', {}), LOOKUPS['organicFertiliser'][2])
66
+ }
67
+
68
+
69
+ def _run(cycle: dict):
70
+ inputs = filter_list_term_type(cycle.get('inputs', []), TermTermType.ORGANICFERTILISER)
71
+ input_values = list(map(_input_values, inputs))
72
+
73
+ debugValues(cycle, model=MODEL, term=TERM_ID,
74
+ input_values=log_as_table(input_values))
75
+
76
+ value = list_sum([
77
+ v.get('N', 0) * v.get('value', 0) for v in input_values
78
+ ]) * get_atomic_conversion(Units.KG_NH3, Units.TO_N)
79
+
80
+ min = list_sum([
81
+ v.get('N', 0) * v.get('min', 0) for v in input_values
82
+ ]) * get_atomic_conversion(Units.KG_NH3, Units.TO_N)
83
+
84
+ max = list_sum([
85
+ v.get('N', 0) * v.get('max', 0) for v in input_values
86
+ ]) * get_atomic_conversion(Units.KG_NH3, Units.TO_N)
87
+
88
+ return [_emission(value, min, max)]
89
+
90
+
91
+ def _should_run(cycle: dict):
92
+ N_organic_fertiliser = get_organic_fertiliser_N_total(cycle)
93
+ fertiliser_complete = _is_term_type_complete(cycle, 'fertiliser')
94
+
95
+ logRequirements(cycle, model=MODEL, term=TERM_ID,
96
+ N_organic_fertiliser=N_organic_fertiliser,
97
+ term_type_fertiliser_complete=fertiliser_complete)
98
+
99
+ should_run = all([N_organic_fertiliser is not None, fertiliser_complete])
100
+ logShouldRun(cycle, MODEL, TERM_ID, should_run)
101
+ return should_run
102
+
103
+
104
+ def run(cycle: dict): return _run(cycle) if _should_run(cycle) else []
@@ -0,0 +1,104 @@
1
+ from hestia_earth.schema import EmissionMethodTier, EmissionStatsDefinition, TermTermType
2
+ from hestia_earth.utils.model import filter_list_term_type
3
+ from hestia_earth.utils.tools import list_sum
4
+
5
+ from hestia_earth.models.log import logRequirements, logShouldRun, debugValues, log_as_table
6
+ from hestia_earth.models.utils.blank_node import get_N_total
7
+ from hestia_earth.models.utils.constant import Units, get_atomic_conversion
8
+ from hestia_earth.models.utils.completeness import _is_term_type_complete
9
+ from hestia_earth.models.utils.emission import _new_emission
10
+ from hestia_earth.models.utils.cycle import get_organic_fertiliser_N_total
11
+ from hestia_earth.models.utils.term import get_lookup_value
12
+ from . import MODEL
13
+
14
+ REQUIREMENTS = {
15
+ "Cycle": {
16
+ "completeness.fertiliser": "True",
17
+ "inputs": [
18
+ {
19
+ "@type": "Input",
20
+ "value": "",
21
+ "term.termType": "organicFertiliser",
22
+ "optional": {
23
+ "properties": [{"@type": "Property", "value": "", "term.@id": "nitrogenContent"}]
24
+ }
25
+ }
26
+ ]
27
+ }
28
+ }
29
+ RETURNS = {
30
+ "Emission": [{
31
+ "value": "",
32
+ "sd": "",
33
+ "min": "",
34
+ "max": "",
35
+ "methodTier": "tier 1",
36
+ "statsDefinition": "modelled",
37
+ "methodModelDescription": "Aggregated version"
38
+ }]
39
+ }
40
+ LOOKUPS = {
41
+ "organicFertiliser": ["IPCC_2019_FRACGASM_NOx-N", "IPCC_2019_FRACGASM_NOx-N-min", "IPCC_2019_FRACGASM_NOx-N-max"]
42
+ }
43
+ TERM_ID = 'noxToAirOrganicFertiliser'
44
+ TIER = EmissionMethodTier.TIER_1.value
45
+
46
+
47
+ def _emission(value: float, min: float, max: float):
48
+ emission = _new_emission(TERM_ID, MODEL)
49
+ emission['value'] = [value]
50
+ emission['min'] = [min]
51
+ emission['max'] = [max]
52
+ emission['methodTier'] = TIER
53
+ emission['statsDefinition'] = EmissionStatsDefinition.MODELLED.value
54
+ emission['methodModelDescription'] = 'Aggregated version'
55
+ return emission
56
+
57
+
58
+ def _input_values(input: dict):
59
+ N_total = list_sum(get_N_total([input]))
60
+ return {
61
+ 'id': input.get('term', {}).get('@id'),
62
+ 'N': N_total,
63
+ 'value': get_lookup_value(input.get('term', {}), LOOKUPS['organicFertiliser'][0]),
64
+ 'min': get_lookup_value(input.get('term', {}), LOOKUPS['organicFertiliser'][1]),
65
+ 'max': get_lookup_value(input.get('term', {}), LOOKUPS['organicFertiliser'][2])
66
+ }
67
+
68
+
69
+ def _run(cycle: dict):
70
+ inputs = filter_list_term_type(cycle.get('inputs', []), TermTermType.ORGANICFERTILISER)
71
+ input_values = list(map(_input_values, inputs))
72
+
73
+ debugValues(cycle, model=MODEL, term=TERM_ID,
74
+ input_values=log_as_table(input_values))
75
+
76
+ value = list_sum([
77
+ v.get('N', 0) * v.get('value', 0) for v in input_values
78
+ ]) * get_atomic_conversion(Units.KG_NOX, Units.TO_N)
79
+
80
+ min = list_sum([
81
+ v.get('N', 0) * v.get('min', 0) for v in input_values
82
+ ]) * get_atomic_conversion(Units.KG_NOX, Units.TO_N)
83
+
84
+ max = list_sum([
85
+ v.get('N', 0) * v.get('max', 0) for v in input_values
86
+ ]) * get_atomic_conversion(Units.KG_NOX, Units.TO_N)
87
+
88
+ return [_emission(value, min, max)]
89
+
90
+
91
+ def _should_run(cycle: dict):
92
+ N_organic_fertiliser = get_organic_fertiliser_N_total(cycle)
93
+ fertiliser_complete = _is_term_type_complete(cycle, 'fertiliser')
94
+
95
+ logRequirements(cycle, model=MODEL, term=TERM_ID,
96
+ N_organic_fertiliser=N_organic_fertiliser,
97
+ term_type_fertiliser_complete=fertiliser_complete)
98
+
99
+ should_run = all([N_organic_fertiliser is not None, fertiliser_complete])
100
+ logShouldRun(cycle, MODEL, TERM_ID, should_run)
101
+ return should_run
102
+
103
+
104
+ def run(cycle: dict): return _run(cycle) if _should_run(cycle) else []
@@ -18,12 +18,15 @@ from typing import (
18
18
  from hestia_earth.schema import (
19
19
  CycleFunctionalUnit,
20
20
  MeasurementMethodClassification,
21
+ SchemaType,
21
22
  SiteSiteType,
22
23
  TermTermType,
23
24
  )
25
+ from hestia_earth.utils.api import find_related
24
26
  from hestia_earth.utils.model import find_term_match, filter_list_term_type
25
27
  from hestia_earth.utils.tools import flatten, list_sum, non_empty_list
26
28
 
29
+ from hestia_earth.models.utils import _load_calculated_node
27
30
  from hestia_earth.models.log import log_as_table, logRequirements, logShouldRun
28
31
  from hestia_earth.models.utils.blank_node import (
29
32
  cumulative_nodes_match,
@@ -42,7 +45,6 @@ from hestia_earth.models.utils.measurement import (
42
45
  _new_measurement,
43
46
  )
44
47
  from hestia_earth.models.utils.property import get_node_property
45
- from hestia_earth.models.utils.site import related_cycles
46
48
  from hestia_earth.models.utils.term import (
47
49
  get_cover_crop_property_terms,
48
50
  get_crop_residue_incorporated_or_left_on_field_terms,
@@ -182,7 +184,7 @@ CARBON_CONTENT_TERM_ID = "carbonContent"
182
184
  NITROGEN_CONTENT_TERM_ID = "nitrogenContent"
183
185
  LIGNIN_CONTENT_TERM_ID = "ligninContent"
184
186
 
185
- CROP_RESIDUE_PROPERTY_TERM_IDS = [
187
+ CARBON_INPUT_PROPERTY_TERM_IDS = [
186
188
  CARBON_CONTENT_TERM_ID,
187
189
  NITROGEN_CONTENT_TERM_ID,
188
190
  LIGNIN_CONTENT_TERM_ID
@@ -190,8 +192,7 @@ CROP_RESIDUE_PROPERTY_TERM_IDS = [
190
192
 
191
193
  CARBON_SOURCE_TERM_TYPES = [
192
194
  TermTermType.ORGANICFERTILISER.value,
193
- TermTermType.SOILAMENDMENT.value,
194
- TermTermType.SEED.value
195
+ TermTermType.SOILAMENDMENT.value
195
196
  ]
196
197
 
197
198
  MIN_RUN_IN_PERIOD = 5
@@ -1834,17 +1835,12 @@ def _iterate_carbon_source(node: dict) -> Union[CarbonSource, None]:
1834
1835
  CarbonSource | None
1835
1836
  A `CarbonSource` named tuple if the node is a carbon source with the required properties, else `None`.
1836
1837
  """
1837
- term = node.get("term", {})
1838
1838
  mass = list_sum(node.get("value", []))
1839
1839
  carbon_content, nitrogen_content, lignin_content = (
1840
- get_node_property(node, term_id, False).get("value", 0)/100 for term_id in CROP_RESIDUE_PROPERTY_TERM_IDS
1840
+ get_node_property(node, term_id).get("value", 0)/100 for term_id in CARBON_INPUT_PROPERTY_TERM_IDS
1841
1841
  )
1842
1842
 
1843
1843
  should_run = all([
1844
- any([
1845
- term.get("@id", None) in get_crop_residue_incorporated_or_left_on_field_terms(),
1846
- term.get("termType") in CARBON_SOURCE_TERM_TYPES
1847
- ]),
1848
1844
  mass > 0,
1849
1845
  0 < carbon_content <= 1,
1850
1846
  0 < nitrogen_content <= 1,
@@ -1878,7 +1874,13 @@ def _get_carbon_sources_from_cycles(cycles: dict) -> list[CarbonSource]:
1878
1874
  [cycle.get("inputs", []) + cycle.get("products", []) for cycle in cycles]
1879
1875
  ))
1880
1876
 
1881
- return non_empty_list([_iterate_carbon_source(node) for node in inputs_and_products])
1877
+ return non_empty_list([
1878
+ _iterate_carbon_source(node) for node in inputs_and_products
1879
+ if any([
1880
+ node.get("term", {}).get("@id") in get_crop_residue_incorporated_or_left_on_field_terms(),
1881
+ node.get("term", {}).get("termType") in CARBON_SOURCE_TERM_TYPES
1882
+ ])
1883
+ ])
1882
1884
 
1883
1885
 
1884
1886
  # --- TIER 2 SOC MODEL ---
@@ -1925,6 +1927,8 @@ def _run_tier_2(
1925
1927
  The length of the run-in period in years, must be greater than or equal to 1, default value: `5`.
1926
1928
  run_with_irrigation : bool, optional
1927
1929
  `True` if the model should run while taking into account irrigation, `False` if not.
1930
+ sand_content : float, optional
1931
+ A back-up sand content for if none are found in the inventory, decimal proportion, default value: `0.33`.
1928
1932
  params : dict | None, optional
1929
1933
  Overrides for the model parameters. If `None` only default parameters will be used.
1930
1934
 
@@ -1952,8 +1956,11 @@ def _run_tier_2(
1952
1956
  )
1953
1957
 
1954
1958
  sand_content = next(
1955
- group[_InventoryKey.SAND_CONTENT]/100 for group in valid_inventory.values()
1956
- if _InventoryKey.SAND_CONTENT in group
1959
+ (
1960
+ group[_InventoryKey.SAND_CONTENT] for group in valid_inventory.values()
1961
+ if _InventoryKey.SAND_CONTENT in group
1962
+ ),
1963
+ sand_content
1957
1964
  )
1958
1965
 
1959
1966
  # --- MERGE ANY USER-SET PARAMETERS WITH THE IPCC DEFAULTS ---
@@ -3539,7 +3546,7 @@ def _should_run(site: dict) -> tuple[bool, dict]:
3539
3546
  site_type = site.get("siteType", "")
3540
3547
  management_nodes = site.get("management", [])
3541
3548
  measurement_nodes = site.get("measurements", [])
3542
- cycles = related_cycles(site.get("@id"))
3549
+ cycles = _calculated_cycles(site.get("@id"))
3543
3550
 
3544
3551
  has_management = len(management_nodes) > 0
3545
3552
  has_measurements = len(measurement_nodes) > 0
@@ -3596,6 +3603,26 @@ def _should_run(site: dict) -> tuple[bool, dict]:
3596
3603
  return should_run_tier_1, should_run_tier_2, inventory, kwargs
3597
3604
 
3598
3605
 
3606
+ def _calculated_cycles(site_id: str):
3607
+ """
3608
+ Get the list of `Cycle`s related to the `Site`. Gets the `recalculated` data if available, else `original`.
3609
+
3610
+ Parameters
3611
+ ----------
3612
+ site_id : str
3613
+ The `@id` of the `Site`.
3614
+
3615
+ Returns
3616
+ -------
3617
+ list[dict]
3618
+ The related `Cycle`s as `dict`.
3619
+ """
3620
+ nodes = find_related(SchemaType.SITE, site_id, SchemaType.CYCLE)
3621
+ return list(
3622
+ map(lambda node: _load_calculated_node(node, SchemaType.CYCLE), nodes or [])
3623
+ )
3624
+
3625
+
3599
3626
  def _should_run_tier_1(
3600
3627
  inventory: dict,
3601
3628
  *,
@@ -3615,6 +3642,8 @@ def _should_run_tier_1(
3615
3642
 
3616
3643
  def _should_run_tier_2(
3617
3644
  inventory: dict,
3645
+ *,
3646
+ sand_content: float = None,
3618
3647
  **_
3619
3648
  ) -> bool:
3620
3649
  """
@@ -3624,7 +3653,7 @@ def _should_run_tier_2(
3624
3653
  return all([
3625
3654
  len(valid_years) >= MIN_RUN_IN_PERIOD,
3626
3655
  check_consecutive(valid_years),
3627
- any(inventory.get(year).get(_InventoryKey.SAND_CONTENT) for year in valid_years)
3656
+ any(inventory.get(year).get(_InventoryKey.SAND_CONTENT) for year in valid_years) or sand_content
3628
3657
  ])
3629
3658
 
3630
3659
 
@@ -3650,20 +3679,20 @@ def _log_inventory(inventory: dict) -> str:
3650
3679
  group.get(_InventoryKey.CI_CATEGORY).value if group.get(_InventoryKey.CI_CATEGORY) else None
3651
3680
  ),
3652
3681
  "temperature-monthly": (
3653
- " ".join(f"{val:.1f}" for val in group.get(_InventoryKey.TEMP_MONTHLY))
3682
+ " ".join(f"{val:.1f}" for val in group.get(_InventoryKey.TEMP_MONTHLY, []))
3654
3683
  if group.get(_InventoryKey.TEMP_MONTHLY) else None
3655
3684
  ),
3656
3685
  "precipitation-monthly": (
3657
- " ".join(f"{val:.1f}" for val in group.get(_InventoryKey.PRECIP_MONTHLY))
3686
+ " ".join(f"{val:.1f}" for val in group.get(_InventoryKey.PRECIP_MONTHLY, []))
3658
3687
  if group.get(_InventoryKey.PRECIP_MONTHLY) else None
3659
3688
  ),
3660
3689
  "pet-monthly": (
3661
- " ".join(f"{val:.1f}" for val in group.get(_InventoryKey.PET_MONTHLY))
3690
+ " ".join(f"{val:.1f}" for val in group.get(_InventoryKey.PET_MONTHLY, []))
3662
3691
  if group.get(_InventoryKey.PET_MONTHLY) else None
3663
3692
  ),
3664
3693
  "irrigated-monthly": (
3665
- " ".join(str(val) for val in group.get(_InventoryKey.IRRIGATED_MONTHLY))
3666
- if group.get(_InventoryKey.PET_MONTHLY) else None
3694
+ " ".join(str(val) for val in group.get(_InventoryKey.IRRIGATED_MONTHLY, []))
3695
+ if group.get(_InventoryKey.IRRIGATED_MONTHLY) else None
3667
3696
  ),
3668
3697
  "sand-content": group.get(_InventoryKey.SAND_CONTENT, None),
3669
3698
  "carbon-input": group.get(_InventoryKey.CARBON_INPUT, None),
@@ -3715,8 +3744,17 @@ def _build_inventory_tier_2(
3715
3744
  }
3716
3745
 
3717
3746
  inventory = merge(grouped_data, grouped_should_run)
3747
+
3748
+ # get a back-up value for sand content if no dated ones are available
3749
+ sand_content = get_node_value(find_term_match(
3750
+ [m for m in measurement_nodes if m.get("depthUpper") == DEPTH_UPPER and m.get("depthLower") == DEPTH_LOWER],
3751
+ SAND_CONTENT_TERM_ID,
3752
+ {}
3753
+ )) / 100
3754
+
3718
3755
  kwargs = {
3719
- "run_with_irrigation": True
3756
+ "run_with_irrigation": True,
3757
+ "sand_content": sand_content
3720
3758
  }
3721
3759
 
3722
3760
  return inventory, kwargs
@@ -3811,7 +3849,7 @@ def _get_grouped_sand_content_measurements(grouped_measurements: dict) -> dict:
3811
3849
  }
3812
3850
 
3813
3851
  return {
3814
- year: {_InventoryKey.SAND_CONTENT: get_node_value(measurement)}
3852
+ year: {_InventoryKey.SAND_CONTENT: get_node_value(measurement)/100}
3815
3853
  for year, measurement in grouped_sand_content_measurements.items() if measurement
3816
3854
  }
3817
3855
 
@@ -5,8 +5,6 @@ from hestia_earth.utils.tools import safe_parse_float
5
5
  from hestia_earth.models.log import debugValues
6
6
  from hestia_earth.models.utils.input import get_total_irrigation_m3
7
7
  from hestia_earth.models.utils.cycle import get_ecoClimateZone
8
- from hestia_earth.models.utils.constant import Units, get_atomic_conversion
9
- from hestia_earth.models.utils.blank_node import find_terms_value
10
8
  from hestia_earth.models.utils.term import get_lookup_value, get_milkYield_terms
11
9
  from hestia_earth.models.utils.ecoClimateZone import get_ecoClimateZone_lookup_value
12
10
  from . import MODEL
@@ -19,16 +17,6 @@ COEFF_N_NH3NOX_organic_animal = [0.21, 0.00, 0.31, 0.0775]
19
17
  COEFF_N_NH3NOX_inorganic = [0.11, 0.02, 0.33, 0.0775]
20
18
 
21
19
 
22
- def get_nh3_no3_nox_to_n(cycle: dict, nh3_term_id: str, no3_term_id: str, nox_term_id: str):
23
- nh3 = find_terms_value(cycle.get('emissions', []), nh3_term_id)
24
- nh3 = nh3 / get_atomic_conversion(Units.KG_NH3, Units.TO_N)
25
- no3 = find_terms_value(cycle.get('emissions', []), no3_term_id)
26
- no3 = no3 / get_atomic_conversion(Units.KG_NO3, Units.TO_N)
27
- nox = find_terms_value(cycle.get('emissions', []), nox_term_id)
28
- nox = nox / get_atomic_conversion(Units.KG_NOX, Units.TO_N)
29
- return nh3, no3, nox
30
-
31
-
32
20
  def get_FracLEACH_H(cycle: dict, term_id: str):
33
21
  eco_climate_zone = get_ecoClimateZone(cycle)
34
22
  is_eco_climate_zone_dry = eco_climate_zone % 2 == 0
@@ -130,6 +118,31 @@ N2O_FACTORS = {
130
118
  'max': 0.029
131
119
  }
132
120
  }
121
+ EF4_FACTORS = {
122
+ 'dry': {
123
+ 'value': 0.005,
124
+ 'min': 0,
125
+ 'max': 0.011
126
+
127
+ },
128
+ 'wet': {
129
+ 'value': 0.014,
130
+ 'min': 0.011,
131
+ 'max': 0.017
132
+ },
133
+ 'default': {
134
+ 'value': 0.01,
135
+ 'min': 0.002,
136
+ 'max': 0.018
137
+ }
138
+ }
139
+ EF5_FACTORS = {
140
+ 'default': {
141
+ 'value': 0.011,
142
+ 'min': 0.0,
143
+ 'max': 0.02
144
+ }
145
+ }
133
146
 
134
147
 
135
148
  def _get_waterRegime_lookup(model_term_id: str, practice: dict, col: str):
@@ -140,11 +153,10 @@ def _is_wet(ecoClimateZone: str = None):
140
153
  return get_ecoClimateZone_lookup_value(ecoClimateZone, 'wet') == 1 if ecoClimateZone else None
141
154
 
142
155
 
143
- def _ecoClimate_factors(input_term_type: TermTermType, ecoClimateZone: str = None):
156
+ def ecoClimate_factors(factors: dict, input_term_type: TermTermType = None, ecoClimateZone: str = None):
144
157
  is_wet = _is_wet(ecoClimateZone)
145
158
  factors_key = 'default' if is_wet is None else 'wet' if is_wet else 'dry'
146
- factors = N2O_FACTORS[factors_key]
147
- return (factors.get(input_term_type) if factors_key == 'wet' else factors, is_wet is None)
159
+ return (factors[factors_key].get(input_term_type, factors[factors_key]), ecoClimateZone is None)
148
160
 
149
161
 
150
162
  def _flooded_rice_factors(model_term_id: str, cycle: dict):
@@ -169,4 +181,4 @@ def get_N2O_factors(
169
181
  flooded_rice: bool = False
170
182
  ):
171
183
  return _flooded_rice_factors(model_term_id, cycle) if flooded_rice \
172
- else _ecoClimate_factors(input_term_type, ecoClimateZone)
184
+ else ecoClimate_factors(N2O_FACTORS, input_term_type, ecoClimateZone)