hestia-earth-models 0.50.0__py3-none-any.whl → 0.51.1__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 (58) hide show
  1. hestia_earth/models/agribalyse2016/fuelElectricity.py +7 -4
  2. hestia_earth/models/agribalyse2016/machineryInfrastructureDepreciatedAmountPerCycle.py +14 -5
  3. hestia_earth/models/cycle/input/ecoinventV3.py +10 -2
  4. hestia_earth/models/cycle/input/hestiaAggregatedData.py +5 -2
  5. hestia_earth/models/emepEea2019/co2ToAirFuelCombustion.py +5 -2
  6. hestia_earth/models/emepEea2019/n2OToAirFuelCombustionDirect.py +5 -2
  7. hestia_earth/models/emepEea2019/nh3ToAirExcreta.py +10 -4
  8. hestia_earth/models/emepEea2019/noxToAirFuelCombustion.py +5 -2
  9. hestia_earth/models/emepEea2019/so2ToAirFuelCombustion.py +5 -2
  10. hestia_earth/models/emepEea2019/utils.py +22 -3
  11. hestia_earth/models/environmentalFootprintV3/{freshwaterEcotoxicityPotentialPaf.py → freshwaterEcotoxicityPotentialCtue.py} +2 -2
  12. hestia_earth/models/geospatialDatabase/aware.py +5 -4
  13. hestia_earth/models/geospatialDatabase/ecoregion.py +5 -4
  14. hestia_earth/models/geospatialDatabase/region.py +7 -11
  15. hestia_earth/models/geospatialDatabase/utils.py +39 -25
  16. hestia_earth/models/geospatialDatabase/waterDepth.py +5 -4
  17. hestia_earth/models/impact_assessment/__init__.py +3 -3
  18. hestia_earth/models/ipcc2019/ch4ToAirExcreta.py +4 -2
  19. hestia_earth/models/ipcc2019/croppingDuration.py +1 -1
  20. hestia_earth/models/ipcc2019/n2OToAirInorganicFertiliserIndirect.py +106 -0
  21. hestia_earth/models/ipcc2019/n2OToAirOrganicFertiliserIndirect.py +108 -0
  22. hestia_earth/models/ipcc2019/utils.py +37 -0
  23. hestia_earth/models/jarvisAndPain1994/__init__.py +13 -0
  24. hestia_earth/models/jarvisAndPain1994/n2ToAirExcreta.py +53 -0
  25. hestia_earth/models/koble2014/aboveGroundCropResidue.py +44 -21
  26. hestia_earth/models/koble2014/utils.py +5 -1
  27. hestia_earth/models/log.py +19 -0
  28. hestia_earth/models/mocking/search-results.json +301 -252
  29. hestia_earth/models/pooreNemecek2018/aboveGroundCropResidueTotal.py +15 -8
  30. hestia_earth/models/pooreNemecek2018/plantationProductiveLifespan.py +18 -6
  31. hestia_earth/models/pooreNemecek2018/rotationDuration.py +15 -5
  32. hestia_earth/models/pooreNemecek2018/utils.py +4 -2
  33. hestia_earth/models/schmidt2007/__init__.py +13 -0
  34. hestia_earth/models/schmidt2007/ch4ToAirWasteTreatment.py +60 -0
  35. hestia_earth/models/schmidt2007/utils.py +16 -0
  36. hestia_earth/models/transformation/input/excreta.py +4 -2
  37. hestia_earth/models/transformation/product/excreta.py +2 -2
  38. hestia_earth/models/usetoxV2/{freshwaterEcotoxicityPotentialPaf.py → freshwaterEcotoxicityPotentialCtue.py} +2 -2
  39. hestia_earth/models/utils/term.py +6 -0
  40. hestia_earth/models/version.py +1 -1
  41. {hestia_earth_models-0.50.0.dist-info → hestia_earth_models-0.51.1.dist-info}/METADATA +2 -2
  42. {hestia_earth_models-0.50.0.dist-info → hestia_earth_models-0.51.1.dist-info}/RECORD +58 -44
  43. tests/models/emepEea2019/test_utils.py +17 -3
  44. tests/models/environmentalFootprintV3/{test_freshwaterEcotoxicityPotentialPaf.py → test_freshwaterEcotoxicityPotentialCtue.py} +1 -1
  45. tests/models/geospatialDatabase/test_region.py +4 -5
  46. tests/models/geospatialDatabase/test_utils.py +10 -1
  47. tests/models/ipcc2019/test_n2OToAirInorganicFertiliserIndirect.py +48 -0
  48. tests/models/ipcc2019/test_n2OToAirOrganicFertiliserIndirect.py +48 -0
  49. tests/models/jarvisAndPain1994/__init__.py +0 -0
  50. tests/models/jarvisAndPain1994/test_n2ToAirExcreta.py +37 -0
  51. tests/models/koble2014/test_aboveGroundCropResidue.py +13 -0
  52. tests/models/schmidt2007/__init__.py +0 -0
  53. tests/models/schmidt2007/test_ch4ToAirWasteTreatment.py +45 -0
  54. tests/models/schmidt2007/test_utils.py +39 -0
  55. tests/models/usetoxV2/{test_freshwaterEcotoxicityPotentialPaf.py → test_freshwaterEcotoxicityPotentialCtue.py} +1 -1
  56. {hestia_earth_models-0.50.0.dist-info → hestia_earth_models-0.51.1.dist-info}/LICENSE +0 -0
  57. {hestia_earth_models-0.50.0.dist-info → hestia_earth_models-0.51.1.dist-info}/WHEEL +0 -0
  58. {hestia_earth_models-0.50.0.dist-info → hestia_earth_models-0.51.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,106 @@
1
+ from hestia_earth.schema import EmissionMethodTier, EmissionStatsDefinition, TermTermType
2
+
3
+ from hestia_earth.models.log import debugValues, logRequirements, logShouldRun
4
+ from hestia_earth.models.utils.constant import Units, get_atomic_conversion
5
+ from hestia_earth.models.utils.completeness import _is_term_type_complete
6
+ from hestia_earth.models.utils.cycle import get_inorganic_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_inorganic, get_FracNH3NOx_N2O, get_FracLEACH_H
9
+ from . import MODEL
10
+
11
+ REQUIREMENTS = {
12
+ "Cycle": {
13
+ "completeness.fertiliser": "True",
14
+ "completeness.water": "True",
15
+ "inputs": [
16
+ {
17
+ "@type": "Input",
18
+ "value": "",
19
+ "term.termType": "inorganicFertiliser",
20
+ "term.units": "kg N"
21
+ }
22
+ ],
23
+ "emissions": [
24
+ {"@type": "Emission", "value": "", "term.@id": "no3ToGroundwaterInorganicFertiliser"},
25
+ {"@type": "Emission", "value": "", "term.@id": "nh3ToAirInorganicFertiliser"},
26
+ {"@type": "Emission", "value": "", "term.@id": "noxToAirInorganicFertiliser"}
27
+ ],
28
+ "site": {
29
+ "@type": "Site",
30
+ "measurements": [{"@type": "Measurement", "value": "", "term.@id": "ecoClimateZone"}]
31
+ },
32
+ "optional": {
33
+ "practices": [{"@type": "Practice", "value": "", "term.termType": "waterRegime"}]
34
+ }
35
+ }
36
+ }
37
+ RETURNS = {
38
+ "Emission": [{
39
+ "value": "",
40
+ "sd": "",
41
+ "min": "",
42
+ "max": "",
43
+ "methodTier": "tier 1",
44
+ "statsDefinition": "modelled"
45
+ }]
46
+ }
47
+
48
+ TERM_ID = 'n2OToAirInorganicFertiliserIndirect'
49
+ NO3_TERM_ID = 'no3ToGroundwaterInorganicFertiliser'
50
+ NH3_TERM_ID = 'nh3ToAirInorganicFertiliser'
51
+ NOX_TERM_ID = 'noxToAirInorganicFertiliser'
52
+ TIER = EmissionMethodTier.TIER_1.value
53
+
54
+
55
+ def _emission(value: float, sd: float, min: float, max: float):
56
+ emission = _new_emission(TERM_ID, MODEL)
57
+ emission['value'] = [value]
58
+ emission['sd'] = [sd]
59
+ emission['min'] = [min]
60
+ emission['max'] = [max]
61
+ emission['methodTier'] = TIER
62
+ emission['statsDefinition'] = EmissionStatsDefinition.MODELLED.value
63
+ return emission
64
+
65
+
66
+ def _run(cycle: dict):
67
+ N_total = get_inorganic_fertiliser_N_total(cycle)
68
+ nh3_n, no3_n, nox_n = get_nh3_no3_nox_to_n(cycle, NH3_TERM_ID, NO3_TERM_ID, NOX_TERM_ID)
69
+ debugValues(cycle, model=MODEL, term=TERM_ID,
70
+ no3_n=no3_n,
71
+ nh3_n=nh3_n,
72
+ nox_n=nox_n)
73
+ value, min_val, max_val, std = [
74
+ get_FracNH3NOx_N2O(cycle, TERM_ID)[x] * (
75
+ N_total * COEFF_N_NH3NOX_inorganic[x] if nox_n == 0 or nh3_n == 0 else nh3_n + nox_n
76
+ ) +
77
+ COEFF_NO3_N2O[x] * (
78
+ N_total * get_FracLEACH_H(cycle, TERM_ID)[x] if no3_n == 0 else no3_n
79
+ ) for x in range(4)
80
+ ]
81
+ return [_emission(
82
+ value * get_atomic_conversion(Units.KG_N2O, Units.TO_N),
83
+ std * get_atomic_conversion(Units.KG_N2O, Units.TO_N),
84
+ min_val * get_atomic_conversion(Units.KG_N2O, Units.TO_N),
85
+ max_val * get_atomic_conversion(Units.KG_N2O, Units.TO_N)
86
+ )]
87
+
88
+
89
+ def _should_run(cycle: dict):
90
+ N_inorganic_fertiliser = get_inorganic_fertiliser_N_total(cycle)
91
+ ecoClimateZone = get_ecoClimateZone(cycle)
92
+ fertiliser_complete = _is_term_type_complete(cycle, {'termType': 'fertiliser'})
93
+ water_complete = _is_term_type_complete(cycle, {'termType': TermTermType.WATER.value})
94
+
95
+ logRequirements(cycle, model=MODEL, term=TERM_ID,
96
+ N_inorganic_fertiliser=N_inorganic_fertiliser,
97
+ ecoClimateZone=ecoClimateZone,
98
+ fertiliser_complete=fertiliser_complete,
99
+ water_complete=water_complete)
100
+
101
+ should_run = all([N_inorganic_fertiliser >= 0, ecoClimateZone, fertiliser_complete, water_complete])
102
+ logShouldRun(cycle, MODEL, TERM_ID, should_run)
103
+ return should_run
104
+
105
+
106
+ def run(cycle: dict): return _run(cycle) if _should_run(cycle) else []
@@ -0,0 +1,108 @@
1
+ from hestia_earth.schema import EmissionMethodTier, EmissionStatsDefinition, TermTermType
2
+
3
+ from hestia_earth.models.log import debugValues, logRequirements, logShouldRun
4
+ from hestia_earth.models.utils.constant import Units, get_atomic_conversion
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
10
+ from . import MODEL
11
+
12
+ REQUIREMENTS = {
13
+ "Cycle": {
14
+ "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
+ "emissions": [
25
+ {"@type": "Emission", "value": "", "term.@id": "no3ToGroundwaterInorganicFertiliser"},
26
+ {"@type": "Emission", "value": "", "term.@id": "nh3ToAirInorganicFertiliser"},
27
+ {"@type": "Emission", "value": "", "term.@id": "noxToAirInorganicFertiliser"}
28
+ ],
29
+ "site": {
30
+ "@type": "Site",
31
+ "measurements": [{"@type": "Measurement", "value": "", "term.@id": "ecoClimateZone"}]
32
+ },
33
+ "optional": {
34
+ "practices": [{"@type": "Practice", "value": "", "term.termType": "waterRegime"}]
35
+ }
36
+ }
37
+ }
38
+ RETURNS = {
39
+ "Emission": [{
40
+ "value": "",
41
+ "sd": "",
42
+ "min": "",
43
+ "max": "",
44
+ "methodTier": "tier 1",
45
+ "statsDefinition": "modelled"
46
+ }]
47
+ }
48
+
49
+ TERM_ID = 'n2OToAirOrganicFertiliserIndirect'
50
+ NO3_TERM_ID = 'no3ToGroundwaterInorganicFertiliser'
51
+ NH3_TERM_ID = 'nh3ToAirInorganicFertiliser'
52
+ NOX_TERM_ID = 'noxToAirInorganicFertiliser'
53
+ TIER = EmissionMethodTier.TIER_1.value
54
+
55
+
56
+ def _emission(value: float, sd: float, min: float, max: float):
57
+ emission = _new_emission(TERM_ID, MODEL)
58
+ emission['value'] = [value]
59
+ emission['sd'] = [sd]
60
+ emission['min'] = [min]
61
+ emission['max'] = [max]
62
+ emission['methodTier'] = TIER
63
+ emission['statsDefinition'] = EmissionStatsDefinition.MODELLED.value
64
+ return emission
65
+
66
+
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)
70
+ debugValues(cycle, model=MODEL, term=TERM_ID,
71
+ no3_n=no3_n,
72
+ nh3_n=nh3_n,
73
+ nox_n=nox_n)
74
+
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
+ )]
89
+
90
+
91
+ 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, {'termType': 'fertiliser'})
95
+ water_complete = _is_term_type_complete(cycle, {'termType': TermTermType.WATER.value})
96
+
97
+ logRequirements(cycle, model=MODEL, term=TERM_ID,
98
+ N_organic_fertiliser=N_organic_fertiliser,
99
+ ecoClimateZone=ecoClimateZone,
100
+ fertiliser_complete=fertiliser_complete,
101
+ water_complete=water_complete)
102
+
103
+ should_run = all([N_organic_fertiliser >= 0, ecoClimateZone, fertiliser_complete, water_complete])
104
+ logShouldRun(cycle, MODEL, TERM_ID, should_run)
105
+ return should_run
106
+
107
+
108
+ def run(cycle: dict): return _run(cycle) if _should_run(cycle) else []
@@ -4,6 +4,25 @@ from hestia_earth.models.log import debugValues
4
4
  from hestia_earth.models.utils.input import get_total_irrigation_m3
5
5
  from hestia_earth.models.utils.cycle import get_ecoClimateZone
6
6
  from . import MODEL
7
+ from hestia_earth.models.utils.constant import Units, get_atomic_conversion
8
+ from hestia_earth.models.utils.blank_node import find_terms_value
9
+
10
+ # From IPCC2019 Indirect N2O emission factor, in N [avg, min, max, std]
11
+ COEFF_NO3_N2O = [0.011, 0.00, 0.02, 0.005]
12
+ # Volatilized Nitrogen as NH3-N and NOx-N per kg N applied organic fertilisers and animal dung and urine
13
+ COEFF_N_NH3NOX_organic_animal = [0.21, 0.00, 0.31, 0.0775]
14
+ # Volatilized Nitrogen as NH3-N and NOx-N per kg N applied inorganic fertilisers
15
+ COEFF_N_NH3NOX_inorganic = [0.11, 0.02, 0.33, 0.0775]
16
+
17
+
18
+ def get_nh3_no3_nox_to_n(cycle: dict, nh3_term_id: str, no3_term_id: str, nox_term_id: str):
19
+ nh3 = find_terms_value(cycle.get('emissions', []), nh3_term_id)
20
+ nh3 = nh3 / get_atomic_conversion(Units.KG_NH3, Units.TO_N)
21
+ no3 = find_terms_value(cycle.get('emissions', []), no3_term_id)
22
+ no3 = no3 / get_atomic_conversion(Units.KG_NO3, Units.TO_N)
23
+ nox = find_terms_value(cycle.get('emissions', []), nox_term_id)
24
+ nox = nox / get_atomic_conversion(Units.KG_NOX, Units.TO_N)
25
+ return nh3, no3, nox
7
26
 
8
27
 
9
28
  def get_FracLEACH_H(cycle: dict, term_id: str):
@@ -21,3 +40,21 @@ def get_FracLEACH_H(cycle: dict, term_id: str):
21
40
  is_eco_climate_zone_dry,
22
41
  any([irrigation_value_m3 <= 250, is_drip_irrigated])
23
42
  ]) else (0.24, 0.01, 0.73, 0.18) # value, min, max, sd
43
+
44
+
45
+ # Indirect N2O emissions from volatilized NH3 and NOx
46
+ def get_FracNH3NOx_N2O(cycle: dict, term_id: str):
47
+ eco_climate_zone = get_ecoClimateZone(cycle)
48
+ is_eco_climate_zone_dry = eco_climate_zone % 2 == 0
49
+ irrigation_value_m3 = get_total_irrigation_m3(cycle)
50
+ is_drip_irrigated = find_term_match(cycle.get('practices', []), 'irrigatedDripIrrigation', None) is not None
51
+
52
+ debugValues(cycle, model=MODEL, term=term_id,
53
+ is_eco_climate_zone_dry=is_eco_climate_zone_dry,
54
+ irrigation_value_m3=irrigation_value_m3,
55
+ is_drip_irrigated=is_drip_irrigated)
56
+
57
+ return (0.005, 0, 0.011, 0.00275) if all([
58
+ is_eco_climate_zone_dry,
59
+ any([irrigation_value_m3 <= 250, is_drip_irrigated])
60
+ ]) else (0.014, 0.011, 0.017, 0.0015) # value, min, max, sd
@@ -0,0 +1,13 @@
1
+ from os.path import dirname, abspath
2
+ import sys
3
+ from importlib import import_module
4
+
5
+ from hestia_earth.models.utils.blank_node import run_if_required
6
+
7
+ CURRENT_DIR = dirname(abspath(__file__)) + '/'
8
+ sys.path.append(CURRENT_DIR)
9
+ MODEL = 'jarvisAndPain1994'
10
+ PKG = '.'.join(['hestia_earth', 'models', MODEL])
11
+
12
+
13
+ def run(model: str, data): return run_if_required(MODEL, model, data, import_module(f".{model}", package=PKG))
@@ -0,0 +1,53 @@
1
+ from hestia_earth.models.utils.constant import Units, get_atomic_conversion
2
+ from hestia_earth.utils.model import find_term_match
3
+ from hestia_earth.utils.tools import list_sum
4
+ from hestia_earth.schema import EmissionMethodTier, EmissionStatsDefinition
5
+ from . import MODEL
6
+ from hestia_earth.models.log import logRequirements, logShouldRun
7
+ from hestia_earth.models.utils.emission import _new_emission
8
+
9
+ REQUIREMENTS = {
10
+ "Cycle": {
11
+ "emissions": [
12
+ {"@type": "Emission", "value": "", "term.@id": "n2OToAirExcretaDirect"}
13
+ ]
14
+ }
15
+ }
16
+ RETURNS = {
17
+ "Emission": [{
18
+ "value": "",
19
+ "methodTier": "tier 1",
20
+ "statsDefinition": "modelled"
21
+ }]
22
+ }
23
+ TERM_ID = 'n2ToAirExcreta'
24
+ TIER = EmissionMethodTier.TIER_1.value
25
+ N2O_TERM_ID = 'n2OToAirExcretaDirect'
26
+
27
+
28
+ def _emission(value: float):
29
+ emission = _new_emission(TERM_ID, MODEL)
30
+ emission['value'] = [value]
31
+ emission['methodTier'] = TIER
32
+ emission['statsDefinition'] = EmissionStatsDefinition.MODELLED.value
33
+ return emission
34
+
35
+
36
+ def _run(n2o: dict):
37
+ value = 3 * list_sum(n2o.get("value", [])) / get_atomic_conversion(Units.KG_N2O, Units.TO_N)
38
+ return [_emission(value)]
39
+
40
+
41
+ def _should_run(cycle: dict):
42
+ n2o = find_term_match(cycle.get('emissions', []), N2O_TERM_ID)
43
+
44
+ logRequirements(cycle, model=MODEL, term=TERM_ID, has_n2o=n2o is not None)
45
+
46
+ should_run = all([n2o])
47
+ logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
48
+ return should_run, n2o
49
+
50
+
51
+ def run(cycle: dict):
52
+ should_run, n2o = _should_run(cycle)
53
+ return _run(n2o) if should_run else []
@@ -9,10 +9,10 @@ This model returns the amounts and destinations of above ground crop residue, wo
9
9
  """
10
10
  from functools import reduce
11
11
  from hestia_earth.schema import ProductStatsDefinition
12
- from hestia_earth.utils.model import find_primary_product, find_term_match
12
+ from hestia_earth.utils.model import find_term_match
13
13
  from hestia_earth.utils.tools import flatten, list_sum, list_average
14
14
 
15
- from hestia_earth.models.log import logRequirements, logShouldRun, logger
15
+ from hestia_earth.models.log import logRequirements, logShouldRun, debugValues
16
16
  from hestia_earth.models.utils.product import _new_product
17
17
  from hestia_earth.models.utils.completeness import _is_term_type_incomplete
18
18
  from . import MODEL
@@ -52,6 +52,10 @@ MODELS = [
52
52
  REMAINING_MODEL = 'aboveGroundCropResidueLeftOnField'
53
53
 
54
54
 
55
+ def _get_practices(term_id: str):
56
+ return flatten([model.get('practices', []) for model in MODELS if model.get('term') == term_id])
57
+
58
+
55
59
  def _get_practice_value(term_ids: list, cycle: dict) -> float:
56
60
  # multiple practices starting with the `@id` might be present, group together
57
61
  values = flatten([
@@ -66,23 +70,28 @@ def _product(term_id: str, value: float):
66
70
  return product
67
71
 
68
72
 
69
- def _should_run_model(model, cycle: dict, primary_product: dict):
73
+ def _should_run_model(model, cycle: dict, total_value: float):
70
74
  term_id = model.get('term')
71
75
  practice_value = _get_practice_value(model.get('practices'), cycle)
72
76
  has_product = find_term_match(cycle.get('products', []), term_id, None) is not None
73
77
 
74
78
  logRequirements(cycle, model=MODEL, term=term_id,
75
79
  practice_value=practice_value,
76
- primary_product=(primary_product or {}).get('@id'),
77
80
  has_product=has_product)
78
81
 
79
- should_run = all([practice_value is not None, primary_product, not has_product])
82
+ should_run = all([
83
+ any([
84
+ practice_value == 0,
85
+ practice_value is not None and total_value > 0
86
+ ]),
87
+ not has_product
88
+ ])
80
89
  logShouldRun(cycle, MODEL, term_id, should_run)
81
90
  return should_run, practice_value
82
91
 
83
92
 
84
- def _run_model(model, cycle: dict, primary_product: dict, total_value: float):
85
- should_run, practice_value = _should_run_model(model, cycle, primary_product)
93
+ def _run_model(model, cycle: dict, total_value: float):
94
+ should_run, practice_value = _should_run_model(model, cycle, total_value)
86
95
  return total_value * practice_value if should_run else None
87
96
 
88
97
 
@@ -93,7 +102,6 @@ def _model_value(term_id: str, products: list):
93
102
 
94
103
  def _run(cycle: dict, total_values: list):
95
104
  products = cycle.get('products', [])
96
- primary_product = find_primary_product(cycle)
97
105
  total_value = list_average(total_values)
98
106
  # first, calculate the remaining value available after applying all user-uploaded data
99
107
  remaining_value = reduce(
@@ -106,14 +114,19 @@ def _run(cycle: dict, total_values: list):
106
114
  # then run every model in order up to the remaining value
107
115
  for model in MODELS:
108
116
  term_id = model.get('term')
109
- value = _run_model(model, cycle, primary_product, total_value)
110
- logger.debug('model=%s, term=%s, value=%s', MODEL, term_id, value)
111
- if remaining_value > 0 and value is not None and value >= 0:
117
+ value = _run_model(model, cycle, total_value)
118
+ debugValues(cycle, model=MODEL, term=term_id,
119
+ total_value=total_value,
120
+ remaining_value=remaining_value,
121
+ value=value)
122
+
123
+ if value == 0:
124
+ values.extend([_product(term_id, value)])
125
+ elif remaining_value > 0 and value is not None and value >= 0:
112
126
  value = value if value < remaining_value else remaining_value
113
127
  values.extend([_product(term_id, value)])
114
128
  remaining_value = remaining_value - value
115
129
  if remaining_value == 0:
116
- logger.debug('model=%s, term=%s, no more residue - stopping', MODEL, term_id)
117
130
  break
118
131
 
119
132
  return values + [
@@ -122,19 +135,29 @@ def _run(cycle: dict, total_values: list):
122
135
  ] if remaining_value > 0 else values
123
136
 
124
137
 
125
- def _should_run(cycle: dict):
138
+ def _should_run_product(cycle: dict, total_values: list, term_id: str):
126
139
  term_type_incomplete = _is_term_type_incomplete(cycle, TOTAL_TERM_ID)
127
- total_values = find_term_match(cycle.get('products', []), TOTAL_TERM_ID).get('value', [])
128
140
  has_aboveGroundCropResidueTotal = len(total_values) > 0
141
+ practice_term_ids = _get_practices(term_id)
142
+ is_value_0 = any([
143
+ find_term_match(cycle.get('practices', []), term_id).get('value', []) == [0] for term_id in practice_term_ids
144
+ ])
145
+
146
+ logRequirements(cycle, model=MODEL, term=term_id,
147
+ term_type_cropResidue_incomplete=term_type_incomplete,
148
+ has_aboveGroundCropResidueTotal=has_aboveGroundCropResidueTotal,
149
+ practice_term_ids=';'.join(practice_term_ids),
150
+ practice_value_is_0=is_value_0)
151
+ should_run = all([has_aboveGroundCropResidueTotal or is_value_0, term_type_incomplete])
152
+ logShouldRun(cycle, MODEL, term_id, should_run)
153
+ return should_run
129
154
 
130
- should_run = all([has_aboveGroundCropResidueTotal, term_type_incomplete])
131
- for term_id in TERM_ID.split(','):
132
- logRequirements(cycle, model=MODEL, term=term_id,
133
- term_type_cropResidue_incomplete=term_type_incomplete,
134
- has_aboveGroundCropResidueTotal=has_aboveGroundCropResidueTotal)
135
- logShouldRun(cycle, MODEL, term_id, should_run)
136
155
 
137
- return should_run, total_values
156
+ def _should_run(cycle: dict):
157
+ total_values = find_term_match(cycle.get('products', []), TOTAL_TERM_ID).get('value', [])
158
+ return any([
159
+ _should_run_product(cycle, total_values, term_id) for term_id in TERM_ID.split(',')
160
+ ]), total_values
138
161
 
139
162
 
140
163
  def run(cycle: dict):
@@ -3,7 +3,7 @@ from hestia_earth.schema import TermTermType, PracticeStatsDefinition
3
3
  from hestia_earth.utils.model import find_primary_product, find_term_match
4
4
  from hestia_earth.utils.tools import list_sum
5
5
 
6
- from hestia_earth.models.log import logRequirements, logShouldRun
6
+ from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
7
7
  from hestia_earth.models.utils.completeness import _is_term_type_incomplete
8
8
  from hestia_earth.models.utils.practice import _new_practice
9
9
  from hestia_earth.models.utils.term import get_crop_residue_management_terms
@@ -28,6 +28,9 @@ def _should_run(term_id: str, cycle: dict, require_country: bool = False):
28
28
  practices = cycle.get('practices', [])
29
29
  residue_terms = get_crop_residue_management_terms()
30
30
  remaining_value = reduce(lambda prev, term: prev - _model_value(term, practices), residue_terms, 100)
31
+ residue_values = log_as_table([
32
+ {'id': term_id, 'value': _model_value(term_id, practices)} for term_id in residue_terms
33
+ ])
31
34
  has_remaining_value = remaining_value > 0
32
35
 
33
36
  country_id = cycle.get('site', {}).get('country', {}).get('@id')
@@ -36,6 +39,7 @@ def _should_run(term_id: str, cycle: dict, require_country: bool = False):
36
39
  has_primary_product=has_primary_product,
37
40
  crop_residue_incomplete=crop_residue_incomplete,
38
41
  has_remaining_value=has_remaining_value,
42
+ crop_residue_values=residue_values,
39
43
  country_id=country_id)
40
44
 
41
45
  should_run = all([
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  import sys
3
3
  import logging
4
+ from typing import Union
4
5
 
5
6
  LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')
6
7
 
@@ -69,3 +70,21 @@ def debugMissingLookup(lookup_name: str, row: str, row_value: str, col: str, val
69
70
 
70
71
  def logErrorRun(model: str, term: str, error: str):
71
72
  logger.error('model=%s, term=%s, error=%s', model, term, error)
73
+
74
+
75
+ def log_as_table(values: Union[list, dict]):
76
+ """
77
+ Log a list of values to display as a table.
78
+ Can either use a single dictionary, represented using id/value pair,
79
+ or a list of dictionaries using their keys as columns.
80
+
81
+ Parameters
82
+ ----------
83
+ values : list | dict
84
+ Values to display as a table.
85
+ """
86
+ return ';'.join([
87
+ f"id:{k}_value:{v}" for k, v in values.items()
88
+ ] if isinstance(values, dict) else [
89
+ '_'.join([f"{k}:{v}" for k, v in value.items()]) for value in values
90
+ ])