hestia-earth-models 0.64.14__py3-none-any.whl → 0.65.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 (117) hide show
  1. hestia_earth/models/agribalyse2016/fuelElectricity.py +1 -1
  2. hestia_earth/models/cache_sites.py +15 -24
  3. hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsLandTransformation.py +6 -9
  4. hestia_earth/models/cycle/input/hestiaAggregatedData.py +46 -22
  5. hestia_earth/models/cycle/pre_checks/cache_sources.py +3 -25
  6. hestia_earth/models/cycle/product/economicValueShare.py +2 -2
  7. hestia_earth/models/ecoinventV3/__init__.py +3 -2
  8. hestia_earth/models/ecoinventV3/utils.py +1 -2
  9. hestia_earth/models/ecoinventV3AndEmberClimate/__init__.py +3 -2
  10. hestia_earth/models/environmentalFootprintV3/soilQualityIndexLandTransformation.py +17 -37
  11. hestia_earth/models/faostat2018/landTransformation100YearAverageDuringCycle.py +34 -0
  12. hestia_earth/models/faostat2018/landTransformation20YearAverageDuringCycle.py +34 -0
  13. hestia_earth/models/faostat2018/utils.py +47 -3
  14. hestia_earth/models/hestia/landCover.py +5 -5
  15. hestia_earth/models/hestia/seed_emissions.py +284 -0
  16. hestia_earth/models/ipcc2019/aboveGroundBiomass.py +2 -2
  17. hestia_earth/models/ipcc2019/belowGroundBiomass.py +8 -2
  18. hestia_earth/models/ipcc2019/biomass_utils.py +11 -4
  19. hestia_earth/models/ipcc2019/ch4ToAirEntericFermentation.py +19 -10
  20. hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +2 -1
  21. hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +2 -1
  22. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +8 -7
  23. hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +2 -1
  24. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +28 -34
  25. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +8 -12
  26. hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +13 -30
  27. hestia_earth/models/linkedImpactAssessment/freshwaterWithdrawalsInputsProduction.py +4 -1
  28. hestia_earth/models/linkedImpactAssessment/landOccupationInputsProduction.py +16 -1
  29. hestia_earth/models/linkedImpactAssessment/{landTransformationFromForest100YearAverageInputsProduction.py → landTransformation100YearAverageInputsProduction.py} +8 -3
  30. hestia_earth/models/linkedImpactAssessment/{landTransformationFromCropland100YearAverageInputsProduction.py → landTransformation20YearAverageInputsProduction.py} +8 -3
  31. hestia_earth/models/linkedImpactAssessment/utils.py +80 -16
  32. hestia_earth/models/mocking/search-results.json +448 -448
  33. hestia_earth/models/pooreNemecek2018/excretaKgN.py +45 -41
  34. hestia_earth/models/pooreNemecek2018/excretaKgVs.py +89 -63
  35. hestia_earth/models/pooreNemecek2018/saplingsDepreciatedAmountPerCycle.py +8 -8
  36. hestia_earth/models/pooreNemecek2018/utils.py +60 -19
  37. hestia_earth/models/schererPfister2015/nErosionSoilFlux.py +4 -3
  38. hestia_earth/models/schererPfister2015/pErosionSoilFlux.py +4 -3
  39. hestia_earth/models/schererPfister2015/utils.py +12 -9
  40. hestia_earth/models/site/management.py +70 -55
  41. hestia_earth/models/site/pre_checks/cache_sources.py +2 -20
  42. hestia_earth/models/utils/__init__.py +12 -1
  43. hestia_earth/models/utils/aggregated.py +1 -1
  44. hestia_earth/models/utils/blank_node.py +20 -12
  45. hestia_earth/models/utils/cache_sources.py +15 -0
  46. hestia_earth/models/utils/crop.py +5 -0
  47. hestia_earth/models/utils/indicator.py +3 -1
  48. hestia_earth/models/version.py +1 -1
  49. {hestia_earth_models-0.64.14.dist-info → hestia_earth_models-0.65.1.dist-info}/LICENSE +1 -1
  50. {hestia_earth_models-0.64.14.dist-info → hestia_earth_models-0.65.1.dist-info}/METADATA +3 -3
  51. {hestia_earth_models-0.64.14.dist-info → hestia_earth_models-0.65.1.dist-info}/RECORD +80 -109
  52. tests/models/cml2001Baseline/test_abioticResourceDepletionMineralsAndMetals.py +1 -1
  53. tests/models/cycle/input/test_hestiaAggregatedData.py +5 -2
  54. tests/models/environmentalFootprintV3/test_soilQualityIndexLandTransformation.py +39 -28
  55. tests/models/{hyde32/test_landTransformationFromForest20YearAverageDuringCycle.py → faostat2018/test_landTransformation100YearAverageDuringCycle.py} +5 -5
  56. tests/models/{hyde32/test_landTransformationFromForest100YearAverageDuringCycle.py → faostat2018/test_landTransformation20YearAverageDuringCycle.py} +5 -5
  57. tests/models/faostat2018/test_utils.py +28 -0
  58. tests/models/hestia/test_landCover.py +2 -1
  59. tests/models/hestia/test_seed_emissions.py +27 -0
  60. tests/models/ipcc2019/test_aboveGroundBiomass.py +40 -4
  61. tests/models/ipcc2019/test_belowGroundBiomass.py +40 -4
  62. tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +52 -15
  63. tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +50 -14
  64. tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +53 -32
  65. tests/models/ipcc2019/test_organicCarbonPerHa.py +91 -108
  66. tests/models/ipcc2019/test_organicCarbonPerHa_tier_1_utils.py +33 -50
  67. tests/models/ipcc2019/test_organicCarbonPerHa_tier_2_utils.py +0 -52
  68. tests/models/linkedImpactAssessment/test_freshwaterWithdrawalsInputsProduction.py +6 -4
  69. tests/models/linkedImpactAssessment/test_landOccupationInputsProduction.py +6 -4
  70. tests/models/linkedImpactAssessment/{test_landTransformationFromForest100YearAverageInputsProduction.py → test_landTransformation100YearAverageInputsProduction.py} +7 -5
  71. tests/models/linkedImpactAssessment/{test_landTransformationFromForest20YearAverageInputsProduction.py → test_landTransformation20YearAverageInputsProduction.py} +7 -5
  72. tests/models/pooreNemecek2018/test_excretaKgN.py +2 -2
  73. tests/models/pooreNemecek2018/test_excretaKgVs.py +1 -1
  74. tests/models/pooreNemecek2018/test_utils.py +26 -0
  75. tests/models/site/test_management.py +10 -27
  76. tests/models/test_cache_sites.py +40 -12
  77. tests/models/utils/test_blank_node.py +0 -8
  78. tests/models/utils/test_cache_sources.py +21 -0
  79. hestia_earth/models/blonkConsultants2016/landTransformationFromForest20YearAverageDuringCycle.py +0 -90
  80. hestia_earth/models/faostat2018/landTransformationFromCropland100YearAverage.py +0 -74
  81. hestia_earth/models/faostat2018/landTransformationFromCropland20YearAverage.py +0 -74
  82. hestia_earth/models/hyde32/__init__.py +0 -13
  83. hestia_earth/models/hyde32/landTransformationFromCropland100YearAverageDuringCycle.py +0 -60
  84. hestia_earth/models/hyde32/landTransformationFromCropland20YearAverageDuringCycle.py +0 -60
  85. hestia_earth/models/hyde32/landTransformationFromForest100YearAverageDuringCycle.py +0 -60
  86. hestia_earth/models/hyde32/landTransformationFromForest20YearAverageDuringCycle.py +0 -60
  87. hestia_earth/models/hyde32/landTransformationFromOtherNaturalVegetation100YearAverageDuringCycle.py +0 -61
  88. hestia_earth/models/hyde32/landTransformationFromOtherNaturalVegetation20YearAverageDuringCycle.py +0 -61
  89. hestia_earth/models/hyde32/landTransformationFromPermanentPasture100YearAverageDuringCycle.py +0 -61
  90. hestia_earth/models/hyde32/landTransformationFromPermanentPasture20YearAverageDuringCycle.py +0 -61
  91. hestia_earth/models/hyde32/utils.py +0 -72
  92. hestia_earth/models/linkedImpactAssessment/landTransformationFromCropland20YearAverageInputsProduction.py +0 -36
  93. hestia_earth/models/linkedImpactAssessment/landTransformationFromForest20YearAverageInputsProduction.py +0 -36
  94. hestia_earth/models/linkedImpactAssessment/landTransformationFromOtherNaturalVegetation100YearAverageInputsProduction.py +0 -36
  95. hestia_earth/models/linkedImpactAssessment/landTransformationFromOtherNaturalVegetation20YearAverageInputsProduction.py +0 -36
  96. hestia_earth/models/linkedImpactAssessment/landTransformationFromPermanentPasture100YearAverageInputsProduction.py +0 -36
  97. hestia_earth/models/linkedImpactAssessment/landTransformationFromPermanentPasture20YearAverageInputsProduction.py +0 -36
  98. tests/models/blonkConsultants2016/test_landTransformationFromForest20YearAverageDuringCycle.py +0 -36
  99. tests/models/cycle/pre_checks/test_cache_sources.py +0 -25
  100. tests/models/faostat2018/test_landTransformationFromCropland100YearAverage.py +0 -40
  101. tests/models/faostat2018/test_landTransformationFromCropland20YearAverage.py +0 -40
  102. tests/models/hyde32/__init__.py +0 -0
  103. tests/models/hyde32/test_landTransformationFromCropland100YearAverageDuringCycle.py +0 -21
  104. tests/models/hyde32/test_landTransformationFromCropland20YearAverageDuringCycle.py +0 -21
  105. tests/models/hyde32/test_landTransformationFromOtherNaturalVegetation100YearAverageDuringCycle.py +0 -23
  106. tests/models/hyde32/test_landTransformationFromOtherNaturalVegetation20YearAverageDuringCycle.py +0 -21
  107. tests/models/hyde32/test_landTransformationFromPermanentPasture100YearAverageDuringCycle.py +0 -21
  108. tests/models/hyde32/test_landTransformationFromPermanentPasture20YearAverageDuringCycle.py +0 -21
  109. tests/models/linkedImpactAssessment/test_landTransformationFromCropland100YearAverageInputsProduction.py +0 -23
  110. tests/models/linkedImpactAssessment/test_landTransformationFromCropland20YearAverageInputsProduction.py +0 -23
  111. tests/models/linkedImpactAssessment/test_landTransformationFromOtherNaturalVegetation100YearAverageInputsProduction.py +0 -23
  112. tests/models/linkedImpactAssessment/test_landTransformationFromOtherNaturalVegetation20YearAverageInputsProduction.py +0 -23
  113. tests/models/linkedImpactAssessment/test_landTransformationFromPermanentPasture100YearAverageInputsProduction.py +0 -24
  114. tests/models/linkedImpactAssessment/test_landTransformationFromPermanentPasture20YearAverageInputsProduction.py +0 -24
  115. tests/models/site/pre_checks/test_cache_sources.py +0 -21
  116. {hestia_earth_models-0.64.14.dist-info → hestia_earth_models-0.65.1.dist-info}/WHEEL +0 -0
  117. {hestia_earth_models-0.64.14.dist-info → hestia_earth_models-0.65.1.dist-info}/top_level.txt +0 -0
@@ -21,13 +21,13 @@ from hestia_earth.models.utils.property import _get_nitrogen_content
21
21
  from hestia_earth.models.utils.input import get_feed_inputs
22
22
  from hestia_earth.models.utils.product import _new_product, get_animal_produced_nitrogen
23
23
  from hestia_earth.models.utils.blank_node import convert_to_nitrogen
24
- from .utils import get_excreta_product
24
+ from .utils import get_excreta_product_with_ratio
25
25
  from . import MODEL
26
26
 
27
27
  REQUIREMENTS = {
28
28
  "Cycle": {
29
- "completeness.animalFeed": "",
30
- "completeness.product": "",
29
+ "completeness.animalFeed": "True",
30
+ "completeness.product": "True",
31
31
  "products": [{
32
32
  "@type": "Product",
33
33
  "value": "",
@@ -63,7 +63,10 @@ REQUIREMENTS = {
63
63
  ]
64
64
  }]
65
65
  }
66
- ]
66
+ ],
67
+ "optional": {
68
+ "practices": [{"@type": "Practice", "term.termType": "systemn"}]
69
+ }
67
70
  }
68
71
  }
69
72
  RETURNS = {
@@ -73,9 +76,9 @@ RETURNS = {
73
76
  }
74
77
  LOOKUPS = {
75
78
  "crop-property": ["nitrogenContent", "crudeProteinContent"],
76
- "animalProduct": ["excretaKgNTermIds", "allowedExcretaKgNTermIds"],
77
- "liveAnimal": ["excretaKgNTermIds", "allowedExcretaKgNTermIds"],
78
- "liveAquaticSpecies": ["excretaKgNTermIds", "allowedExcretaKgNTermIds"]
79
+ "animalProduct": ["excretaKgNTermIds"],
80
+ "liveAnimal": ["excretaKgNTermIds"],
81
+ "liveAquaticSpecies": ["excretaKgNTermIds"]
79
82
  }
80
83
  MODEL_KEY = 'excretaKgN'
81
84
 
@@ -85,30 +88,29 @@ def _product(excreta_product: str, value: float):
85
88
  return excreta_product | product
86
89
 
87
90
 
88
- def _run(excreta_product: dict, mass_balance_items: list, alternate_items: list):
91
+ def _run(excreta_products: list, mass_balance_items: list, alternate_items: list):
89
92
  inputs_n, products_n = mass_balance_items
90
93
  product_value, nitrogen_content = alternate_items
91
94
  value = inputs_n - products_n if all(mass_balance_items) else 3.31 * product_value * nitrogen_content / 100
92
- return [_product(excreta_product, value)] if value > 0 else []
93
-
94
-
95
- def _get_excreta_product(cycle: dict, primary_product: dict):
96
- term = primary_product.get('term', {})
97
- return get_excreta_product(cycle, term, 'excretaKgNTermIds', 'allowedExcretaKgNTermIds')
95
+ # ratio is stored in product value
96
+ return [
97
+ _product(excreta_product, round(value * excreta_product.get('value')[0] / 100, 7))
98
+ for excreta_product in excreta_products
99
+ ] if value > 0 else []
98
100
 
99
101
 
100
102
  def _should_run(cycle: dict):
101
103
  primary_prod = find_primary_product(cycle) or {}
102
- excreta_product = _get_excreta_product(cycle, primary_prod)
103
- excreta_term_id = excreta_product.get('term', {}).get('@id')
104
- should_add_product = all([not excreta_product.get('value', [])])
104
+
105
+ excreta_products = get_excreta_product_with_ratio(cycle, 'excretaKgNTermIds', model_key=MODEL_KEY)
106
+ first_term_id = excreta_products[0].get('term', {}).get('@id') if excreta_products else None
105
107
 
106
108
  dc = cycle.get('completeness', {})
107
109
  is_animalFeed_complete = dc.get('animalFeed', False)
108
110
  is_product_complete = dc.get('product', False)
109
111
 
110
112
  inputs_feed = get_feed_inputs(cycle)
111
- inputs_n = convert_to_nitrogen(cycle, MODEL, excreta_term_id, inputs_feed, model_key=MODEL_KEY)
113
+ inputs_n = convert_to_nitrogen(cycle, MODEL, inputs_feed, term=first_term_id, model_key=MODEL_KEY)
112
114
 
113
115
  products_n = get_animal_produced_nitrogen(MODEL, cycle.get('products', []))
114
116
 
@@ -117,37 +119,39 @@ def _should_run(cycle: dict):
117
119
  product_value = list_sum(primary_prod.get('value', [0]))
118
120
  nitrogen_content = _get_nitrogen_content(primary_prod)
119
121
 
120
- if is_liveAquaticSpecies:
121
- logRequirements(cycle, model=MODEL, term=excreta_term_id, model_key=MODEL_KEY,
122
- is_liveAquaticSpecies=is_liveAquaticSpecies,
123
- product_value=product_value,
124
- nitrogen_content=nitrogen_content)
125
-
126
- else:
127
- logRequirements(cycle, model=MODEL, term=excreta_term_id, model_key=MODEL_KEY,
128
- is_animalFeed_complete=is_animalFeed_complete,
129
- is_product_complete=is_product_complete,
130
- inputs_n=inputs_n,
131
- products_n=products_n)
132
-
133
122
  mass_balance_items = [inputs_n, products_n]
134
123
  alternate_items = [product_value, nitrogen_content]
135
124
 
136
125
  should_run = all([
137
- excreta_term_id,
138
- should_add_product,
126
+ is_animalFeed_complete,
127
+ is_product_complete,
139
128
  any([
140
- is_animalFeed_complete and is_product_complete and all(mass_balance_items),
129
+ all(mass_balance_items),
141
130
  is_liveAquaticSpecies and all(alternate_items)
142
131
  ])
143
132
  ])
144
- # only log if the excreta term does not exist to avoid showing failure when it already exists
145
- if should_add_product:
146
- logShouldRun(cycle, MODEL, excreta_term_id, should_run, model_key=MODEL_KEY)
147
- logShouldRun(cycle, MODEL, None, should_run)
148
- return should_run, excreta_product, mass_balance_items, alternate_items
133
+
134
+ for excreta_product in excreta_products:
135
+ term_id = excreta_product.get('term', {}).get('@id')
136
+
137
+ if is_liveAquaticSpecies:
138
+ logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
139
+ is_liveAquaticSpecies=is_liveAquaticSpecies,
140
+ product_value=product_value,
141
+ nitrogen_content=nitrogen_content)
142
+
143
+ else:
144
+ logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
145
+ is_animalFeed_complete=is_animalFeed_complete,
146
+ is_product_complete=is_product_complete,
147
+ inputs_n=inputs_n,
148
+ products_n=products_n)
149
+
150
+ logShouldRun(cycle, MODEL, term_id, should_run, model_key=MODEL_KEY)
151
+
152
+ return should_run, excreta_products, mass_balance_items, alternate_items
149
153
 
150
154
 
151
155
  def run(cycle: dict):
152
- should_run, excreta_product, mass_balance_items, alternate_items = _should_run(cycle)
153
- return _run(excreta_product, mass_balance_items, alternate_items) if should_run else []
156
+ should_run, excreta_products, mass_balance_items, alternate_items = _should_run(cycle)
157
+ return _run(excreta_products, mass_balance_items, alternate_items) if should_run else []
@@ -9,23 +9,26 @@ If the mass balance fails
9
9
  (i.e. [animal feed](https://hestia.earth/schema/Completeness#animalFeed) is not complete, see requirements below),
10
10
  the fomula is = total excreta as N / [Volatile solids content](https://hestia.earth/term/volatileSolidsContent).
11
11
  """
12
- from hestia_earth.schema import SiteSiteType
13
- from hestia_earth.utils.model import find_primary_product, find_term_match
12
+ from hestia_earth.schema import SiteSiteType, TermTermType
13
+ from hestia_earth.utils.model import find_primary_product, find_term_match, filter_list_term_type
14
14
  from hestia_earth.utils.tools import list_sum, safe_parse_float
15
15
 
16
16
  from hestia_earth.models.log import logRequirements, logShouldRun
17
+ from hestia_earth.models.utils import get_kg_VS_term_id, _filter_list_term_unit
18
+ from hestia_earth.models.utils.constant import Units
17
19
  from hestia_earth.models.utils.term import get_lookup_value
18
20
  from hestia_earth.models.utils.property import get_node_property
19
21
  from hestia_earth.models.utils.product import _new_product
20
22
  from hestia_earth.models.utils.input import get_feed_inputs
21
23
  from hestia_earth.models.utils.measurement import most_relevant_measurement_value
22
24
  from hestia_earth.models.utils.blank_node import convert_to_carbon
23
- from .utils import get_excreta_product
24
- from .excretaKgN import _get_excreta_product as get_excreta_n_product
25
+ from .utils import get_excreta_product_with_ratio
25
26
  from . import MODEL
26
27
 
27
28
  REQUIREMENTS = {
28
29
  "Cycle": {
30
+ "completeness.animalFeed": "True",
31
+ "completeness.product": "True",
29
32
  "products": [{
30
33
  "@type": "Product",
31
34
  "primary": "True",
@@ -34,8 +37,20 @@ REQUIREMENTS = {
34
37
  }],
35
38
  "or": [
36
39
  {
37
- "completeness.animalFeed": "",
38
- "completeness.product": "",
40
+ "products": [{
41
+ "@type": "Product",
42
+ "primary": "True",
43
+ "value": "",
44
+ "properties": [{"@type": "Property", "value": "", "term.@id": "carbonContent"}]
45
+ }],
46
+ "practices": [
47
+ {"@type": "Practice", "value": "", "term.@id": "slaughterAge"},
48
+ {"@type": "Practice", "value": "", "term.@id": "yieldOfPrimaryAquacultureProductLiveweightPerM2"}
49
+ ],
50
+ "site": {
51
+ "@type": "Site",
52
+ "measurements": [{"@type": "Measurement", "value": "", "term.@id": "netPrimaryProduction"}]
53
+ },
39
54
  "or": [
40
55
  {
41
56
  "animals": [{
@@ -64,20 +79,18 @@ REQUIREMENTS = {
64
79
  }]
65
80
  }
66
81
  ],
82
+ "optional": {
83
+ "practices": [{"@type": "Practice", "term.termType": "systemn"}]
84
+ }
85
+ },
86
+ {
67
87
  "products": [{
68
88
  "@type": "Product",
69
- "primary": "True",
89
+ "term.termType": "excreta",
90
+ "term.units": "kg N",
70
91
  "value": "",
71
- "properties": [{"@type": "Property", "value": "", "term.@id": "carbonContent"}]
72
- }],
73
- "practices": [
74
- {"@type": "Practice", "value": "", "term.@id": "slaughterAge"},
75
- {"@type": "Practice", "value": "", "term.@id": "yieldOfPrimaryAquacultureProductLiveweightPerM2"}
76
- ],
77
- "site": {
78
- "@type": "Site",
79
- "measurements": [{"@type": "Measurement", "value": "", "term.@id": "netPrimaryProduction"}]
80
- }
92
+ "properties": [{"@type": "Property", "value": "", "term.@id": "volatileSolidsContent"}]
93
+ }]
81
94
  }
82
95
  ]
83
96
  }
@@ -89,9 +102,9 @@ RETURNS = {
89
102
  }
90
103
  LOOKUPS = {
91
104
  "crop-property": ["carbonContent", "energyContentHigherHeatingValue"],
92
- "animalProduct": ["excretaKgVsTermIds", "allowedExcretaKgVsTermIds"],
93
- "liveAnimal": ["excretaKgVsTermIds", "allowedExcretaKgVsTermIds"],
94
- "liveAquaticSpecies": ["excretaKgVsTermIds", "allowedExcretaKgVsTermIds"]
105
+ "animalProduct": ["excretaKgVsTermIds"],
106
+ "liveAnimal": ["excretaKgVsTermIds"],
107
+ "liveAquaticSpecies": ["excretaKgVsTermIds"]
95
108
  }
96
109
  MODEL_KEY = 'excretaKgVs'
97
110
 
@@ -106,14 +119,20 @@ def _product(excreta_product: str, value: float):
106
119
  return excreta_product | product
107
120
 
108
121
 
109
- def _run(excreta_product: dict, mass_balance_items: list, inputs_c: float, alternate_items: list):
122
+ def _run(excreta_vs_products: list, excreta_n_products: list, mass_balance_items: list, inputs_c: float):
110
123
  carbonContent, tsy, slaughterAge, aqocsed, npp = mass_balance_items
111
- excretaKgN, vsc = alternate_items
112
124
  value = max(
113
125
  inputs_c + (npp * slaughterAge) / (tsy * 1000) - carbonContent - carbonContent * Conv_AQ_CLW_CO2CR,
114
126
  carbonContent * Conv_AQ_CLW_CExcr
115
- ) * aqocsed if all(mass_balance_items) else excretaKgN * vsc / 100
116
- return [_product(excreta_product, value)] if value > 0 else []
127
+ ) * aqocsed if all(mass_balance_items) else 0
128
+ return [
129
+ _product(excreta_product, excreta_product.get('value'))
130
+ for excreta_product in excreta_n_products
131
+ ] if excreta_n_products else [
132
+ # ratio is stored in product value
133
+ _product(excreta_product, round(value * excreta_product.get('value')[0] / 100, 7))
134
+ for excreta_product in excreta_vs_products
135
+ ] if value > 0 else []
117
136
 
118
137
 
119
138
  def _get_carbonContent(cycle: dict):
@@ -127,16 +146,9 @@ def _get_conv_aq_ocsed(siteType: str):
127
146
  return Conv_AQ_OC_OCSed_Marine if siteType == SiteSiteType.SEA_OR_OCEAN.value else Conv_AQ_OC_OCSed_Fresh
128
147
 
129
148
 
130
- def _get_excreta_product(cycle: dict, primary_product: dict):
131
- term = primary_product.get('term', {})
132
- return get_excreta_product(cycle, term, 'excretaKgVsTermIds', 'allowedExcretaKgVsTermIds')
133
-
134
-
135
149
  def _should_run(cycle: dict):
136
- primary_prod = find_primary_product(cycle) or {}
137
- excreta_product = _get_excreta_product(cycle, primary_prod)
138
- excreta_term_id = excreta_product.get('term', {}).get('@id')
139
- should_add_product = all([not excreta_product.get('value', [])])
150
+ excreta_vs_products = get_excreta_product_with_ratio(cycle, 'excretaKgVsTermIds', model_key=MODEL_KEY)
151
+ first_term_id = excreta_vs_products[0].get('term', {}).get('@id') if excreta_vs_products else None
140
152
 
141
153
  dc = cycle.get('completeness', {})
142
154
  is_animalFeed_complete = dc.get('animalFeed', False)
@@ -145,7 +157,7 @@ def _should_run(cycle: dict):
145
157
  carbonContent = _get_carbonContent(cycle)
146
158
 
147
159
  inputs_feed = get_feed_inputs(cycle)
148
- inputs_c = convert_to_carbon(cycle, MODEL, excreta_term_id, inputs_feed, model_key=MODEL_KEY)
160
+ inputs_c = convert_to_carbon(cycle, MODEL, inputs_feed, term=first_term_id, model_key=MODEL_KEY)
149
161
 
150
162
  practices = cycle.get('practices', [])
151
163
  tsy = list_sum(find_term_match(practices, 'yieldOfPrimaryAquacultureProductLiveweightPerM2').get('value', []))
@@ -157,40 +169,54 @@ def _should_run(cycle: dict):
157
169
  npp = most_relevant_measurement_value(site.get('measurements', []), 'netPrimaryProduction', end_date, 0)
158
170
 
159
171
  # we can still run the model with excreta in "kg N" units
160
- excreta_n_product = get_excreta_n_product(cycle, primary_prod)
161
- excretaKgN = list_sum(excreta_n_product.get('value', [0]))
162
- vsc = get_node_property(excreta_n_product, 'volatileSolidsContent').get('value', 0)
163
-
164
- logRequirements(cycle, model=MODEL, term=excreta_term_id, model_key=MODEL_KEY,
165
- is_animalFeed_complete=is_animalFeed_complete,
166
- is_product_complete=is_product_complete,
167
- aqocsed=aqocsed,
168
- inputs_c=inputs_c,
169
- carbonContent=carbonContent,
170
- yield_of_target_species=tsy,
171
- slaughterAge=slaughterAge,
172
- netPrimaryProduction=npp,
173
- excretaKgN=excretaKgN,
174
- volatileSolidsContent=vsc)
172
+ excreta_products = filter_list_term_type(cycle.get('products', []), TermTermType.EXCRETA)
173
+ default_using_kg_N = not any([
174
+ find_term_match(excreta_products, product.get('term', {}).get('@id'))
175
+ for product in excreta_vs_products
176
+ ])
177
+ excreta_n_products = _filter_list_term_unit(excreta_products, Units.KG_N) if default_using_kg_N else []
178
+ excreta_n_products = [
179
+ (
180
+ excreta_n_product,
181
+ get_node_property(excreta_n_product, 'volatileSolidsContent').get('value', 0)
182
+ )
183
+ for excreta_n_product in excreta_n_products
184
+ ]
185
+ excreta_n_products = [
186
+ {
187
+ 'term': {'@id': get_kg_VS_term_id(excreta_n_product.get('term', {}).get('@id'))},
188
+ 'value': list_sum(excreta_n_product.get('value', [])) * vsc / 100
189
+ }
190
+ for (excreta_n_product, vsc) in excreta_n_products
191
+ if vsc > 0
192
+ ]
175
193
 
176
194
  mass_balance_items = [carbonContent, tsy, slaughterAge, aqocsed, npp]
177
- alternate_items = [excretaKgN, vsc]
178
195
 
179
196
  should_run = all([
180
- excreta_term_id,
181
- should_add_product,
182
- any([
183
- is_animalFeed_complete and is_product_complete and all(mass_balance_items),
184
- all(alternate_items)
185
- ])
197
+ is_animalFeed_complete,
198
+ is_product_complete,
199
+ all(mass_balance_items) or excreta_n_products
186
200
  ])
187
- # only log if the excreta term does not exist to avoid showing failure when it already exists
188
- if should_add_product:
189
- logShouldRun(cycle, MODEL, excreta_term_id, should_run, model_key=MODEL_KEY)
190
- logShouldRun(cycle, MODEL, None, should_run)
191
- return should_run, excreta_product, mass_balance_items, inputs_c, alternate_items
201
+
202
+ for excreta_product in excreta_vs_products + excreta_n_products:
203
+ term_id = excreta_product.get('term', {}).get('@id')
204
+
205
+ logRequirements(cycle, model=MODEL, term=term_id, model_key=MODEL_KEY,
206
+ is_animalFeed_complete=is_animalFeed_complete,
207
+ is_product_complete=is_product_complete,
208
+ aqocsed=aqocsed,
209
+ inputs_c=inputs_c,
210
+ carbonContent=carbonContent,
211
+ yield_of_target_species=tsy,
212
+ slaughterAge=slaughterAge,
213
+ netPrimaryProduction=npp,
214
+ default_using_kg_N=default_using_kg_N)
215
+ logShouldRun(cycle, MODEL, term_id, should_run)
216
+
217
+ return should_run, excreta_vs_products, excreta_n_products, mass_balance_items, inputs_c
192
218
 
193
219
 
194
220
  def run(cycle: dict):
195
- should_run, excreta_product, mass_balance_items, inputs_c, alternate_items = _should_run(cycle)
196
- return _run(excreta_product, mass_balance_items, inputs_c, alternate_items) if should_run else []
221
+ should_run, excreta_vs_products, excreta_n_products, mass_balance_items, inputs_c = _should_run(cycle)
222
+ return _run(excreta_vs_products, excreta_n_products, mass_balance_items, inputs_c) if should_run else []
@@ -38,9 +38,9 @@ def _get_value(product: dict):
38
38
  return safe_parse_float(get_crop_lookup_value(MODEL, TERM_ID, term_id, LOOKUPS['crop']), None)
39
39
 
40
40
 
41
- def _run(product: dict, plantation_duration: float, cycleDuration: float):
41
+ def _run(product: dict, plantationLifespan: float, cycleDuration: float):
42
42
  value = _get_value(product)
43
- return [_input(value / plantation_duration * cycleDuration)]
43
+ return [_input(value / plantationLifespan * cycleDuration)]
44
44
 
45
45
 
46
46
  def _should_run_product(product: dict): return _get_value(product) is not None
@@ -50,19 +50,19 @@ def _should_run(cycle: dict):
50
50
  cycleDuration = cycle.get('cycleDuration')
51
51
  term_type_incomplete = _is_term_type_incomplete(cycle, TERM_ID)
52
52
  product = next((p for p in cycle.get('products', []) if _should_run_product(p)), None)
53
- plantation_duration = list_sum(find_term_match(cycle.get('practices', []), PRACTICE_TERM_ID).get('value'), None)
53
+ plantationLifespan = list_sum(find_term_match(cycle.get('practices', []), PRACTICE_TERM_ID).get('value'), None)
54
54
 
55
55
  logRequirements(cycle, model=MODEL, term=TERM_ID,
56
56
  term_type_seed_incomplete=term_type_incomplete,
57
57
  product_id=(product or {}).get('term', {}).get('@id'),
58
- plantation_duration=plantation_duration,
58
+ plantationLifespan=plantationLifespan,
59
59
  cycleDuration=cycleDuration)
60
60
 
61
- should_run = all([term_type_incomplete, product, plantation_duration, (cycleDuration or 0) > 0])
61
+ should_run = all([term_type_incomplete, product, plantationLifespan, (cycleDuration or 0) > 0])
62
62
  logShouldRun(cycle, MODEL, TERM_ID, should_run)
63
- return should_run, product, plantation_duration, cycleDuration
63
+ return should_run, product, plantationLifespan, cycleDuration
64
64
 
65
65
 
66
66
  def run(cycle: dict):
67
- should_run, product, plantation_duration, cycleDuration = _should_run(cycle)
68
- return _run(product, plantation_duration, cycleDuration) if should_run else []
67
+ should_run, product, plantationLifespan, cycleDuration = _should_run(cycle)
68
+ return _run(product, plantationLifespan, cycleDuration) if should_run else []
@@ -1,5 +1,7 @@
1
- from hestia_earth.utils.model import find_term_match
2
- from hestia_earth.utils.tools import non_empty_list, list_average
1
+ from functools import reduce
2
+ from hestia_earth.schema import TermTermType
3
+ from hestia_earth.utils.model import find_term_match, filter_list_term_type, find_primary_product
4
+ from hestia_earth.utils.tools import non_empty_list, list_average, list_sum
3
5
  from hestia_earth.utils.lookup import extract_grouped_data
4
6
 
5
7
  from hestia_earth.models.log import logShouldRun, logRequirements, log_as_table
@@ -29,21 +31,60 @@ def run_products_average(cycle: dict, term_id: str, get_value_func):
29
31
  return list_average(values) if should_run else None
30
32
 
31
33
 
32
- def get_excreta_product(cycle: dict, term: dict, lookup_expected_id: str, lookup_allowed_ids: str):
33
- primary_excreta_data = get_lookup_value(term, lookup_expected_id, model=MODEL) or (
34
- # TODO: remove fallback when glossary is fixed for liveAquaticSpecies
35
- get_lookup_value(term, lookup_expected_id[0:-1], model=MODEL) or ''
36
- )
37
- expected_term_id = extract_grouped_data(primary_excreta_data, 'default') if ':' in primary_excreta_data \
38
- else primary_excreta_data
39
- allowed_term_ids = (get_lookup_value(term, lookup_allowed_ids, model=MODEL) or '').split(';')
40
- products = non_empty_list([
41
- find_term_match(cycle.get('products', []), term_id) for term_id in non_empty_list(
42
- [expected_term_id] + allowed_term_ids
43
- )
44
- ])
45
- # take the first product available or create a new one with expected id
46
- return products[0] if products else {
34
+ def get_excreta_product_with_ratio(cycle: dict, lookup: str, **log_args):
35
+ product = find_primary_product(cycle) or {}
36
+
37
+ data = get_lookup_value(product.get('term'), lookup, model=MODEL, **log_args)
38
+
39
+ default_product_id = extract_grouped_data(data, 'default')
40
+
41
+ # find matching practices and assign a ratio for each
42
+ # if no matches, use default id with 100% ratio
43
+ practices = filter_list_term_type(cycle.get('practices', []), TermTermType.SYSTEM)
44
+ practices = [
45
+ {
46
+ 'id': practice.get('term', {}).get('@id'),
47
+ 'product-id': extract_grouped_data(data, practice.get('term', {}).get('@id')) or default_product_id,
48
+ 'value': list_sum(practice.get('value'))
49
+ }
50
+ for practice in practices
51
+ # only keep practices with positive value
52
+ if list_sum(practice.get('value', [-1]), 0) > 0
53
+ ]
54
+ logRequirements(cycle, model=MODEL, **log_args,
55
+ practices=log_as_table(practices),
56
+ default_product_id=default_product_id)
57
+
58
+ total_value = list_sum([p.get('value') for p in practices])
59
+
60
+ # group practices by product id
61
+ grouped_practices = reduce(lambda p, c: p | {
62
+ c['product-id']: p.get(c['product-id'], []) + [c]
63
+ }, practices, {})
64
+ # calculate ratio for each product
65
+ grouped_practices = [
66
+ values[0] | {'ratio': round(list_sum([v.get('value') for v in values]) * 100 / total_value, 2)}
67
+ for values in grouped_practices.values()
68
+ ]
69
+
70
+ practices_with_products = [
71
+ (find_term_match(cycle.get('products', []), p.get('product-id')), p)
72
+ for p in grouped_practices
73
+ ]
74
+ products = [
75
+ (
76
+ product or {
77
+ '@type': 'Product',
78
+ 'term': {'@type': 'Term', '@id': practice.get('product-id')}
79
+ }
80
+ ) | {'value': [practice.get('ratio')]}
81
+ for product, practice in practices_with_products
82
+ # ignore matching products with an existing value
83
+ if not product or not product.get('value', [])
84
+ ] if practices_with_products else None
85
+
86
+ return products or [{
47
87
  '@type': 'Product',
48
- 'term': {'@type': 'Term', '@id': expected_term_id}
49
- }
88
+ 'term': {'@type': 'Term', '@id': default_product_id},
89
+ 'value': [100]
90
+ }]
@@ -84,10 +84,10 @@ def _should_run(cycle: dict):
84
84
  precipitation = _get_measurement_content('precipitationAnnual')
85
85
  water = get_water(cycle, precipitation)
86
86
 
87
- practice_factor = get_practice_factor(site)
87
+ practice_factor = get_practice_factor(TERM_ID, site)
88
88
  pcorr = get_pcorr(slope / 100) if slope is not None else None
89
- p_ef_c1 = get_p_ef_c1(cycle)
90
- ef_p_c2 = get_ef_p_c2(cycle)
89
+ p_ef_c1 = get_p_ef_c1(TERM_ID, cycle)
90
+ ef_p_c2 = get_ef_p_c2(TERM_ID, cycle)
91
91
 
92
92
  list_of_contents_for_A = [
93
93
  practice_factor, erodibility, slope_length,
@@ -96,6 +96,7 @@ def _should_run(cycle: dict):
96
96
  list_of_contents_for_value = [nla_environment, soil_nitrogen_content]
97
97
 
98
98
  logRequirements(cycle, model=MODEL, term=TERM_ID,
99
+ country=site.get('country', {}).get('@id'),
99
100
  practice_factor=practice_factor,
100
101
  erodibility=erodibility,
101
102
  slope_length=slope_length,
@@ -85,10 +85,10 @@ def _should_run(cycle: dict):
85
85
  precipitation = _get_measurement_content('precipitationAnnual')
86
86
  water = get_water(cycle, precipitation)
87
87
 
88
- practice_factor = get_practice_factor(site)
88
+ practice_factor = get_practice_factor(TERM_ID, site)
89
89
  pcorr = get_pcorr(slope / 100) if slope is not None else None
90
- p_ef_c1 = get_p_ef_c1(cycle)
91
- ef_p_c2 = get_ef_p_c2(cycle)
90
+ p_ef_c1 = get_p_ef_c1(TERM_ID, cycle)
91
+ ef_p_c2 = get_ef_p_c2(TERM_ID, cycle)
92
92
 
93
93
  list_of_contents_for_A = [
94
94
  practice_factor, erodibility, slope_length,
@@ -97,6 +97,7 @@ def _should_run(cycle: dict):
97
97
  list_of_contents_for_value = [nla_environment, soil_phosphorus_content]
98
98
 
99
99
  logRequirements(cycle, model=MODEL, term=TERM_ID,
100
+ country=site.get('country', {}).get('@id'),
100
101
  practice_factor=practice_factor,
101
102
  erodibility=erodibility,
102
103
  slope_length=slope_length,
@@ -6,6 +6,7 @@ from hestia_earth.models.utils.blank_node import get_total_value, get_P2O5_total
6
6
  from hestia_earth.models.utils.term import get_lookup_value, get_tillage_terms
7
7
  from hestia_earth.models.utils.input import match_lookup_value
8
8
  from hestia_earth.models.utils.organicFertiliser import get_cycle_inputs as get_organicFertiliser_inputs
9
+ from . import MODEL
9
10
 
10
11
  SLOPE_RANGE = [
11
12
  [0.0, 0.03, 1.2],
@@ -41,24 +42,26 @@ def get_pcorr(slope: float):
41
42
  return next((element[2] for element in SLOPE_RANGE if slope >= element[0] and slope < element[1]), None)
42
43
 
43
44
 
44
- def _get_C2_factor(term: dict):
45
- return safe_parse_float(get_lookup_value(term, 'C2_FACTORS'), None)
45
+ def _get_C2_factor(term_id: str, term: dict):
46
+ return safe_parse_float(get_lookup_value(term, 'C2_FACTORS', model=MODEL, term=term_id), None)
46
47
 
47
48
 
48
- def get_practice_factor(site: dict):
49
- return safe_parse_float(get_lookup_value(site.get('country', {}), 'Practice_Factor'), None)
49
+ def get_practice_factor(term_id: str, site: dict):
50
+ return safe_parse_float(
51
+ get_lookup_value(site.get('country', {}), 'Practice_Factor', model=MODEL, term=term_id), None)
50
52
 
51
53
 
52
- def get_p_ef_c1(cycle: dict):
54
+ def get_p_ef_c1(term_id: str, cycle: dict):
53
55
  primary_product = find_primary_product(cycle) or {}
54
- return safe_parse_float(get_lookup_value(primary_product.get('term', {}), 'P_EF_C1'), None)
56
+ return safe_parse_float(
57
+ get_lookup_value(primary_product.get('term', {}), 'P_EF_C1', model=MODEL, term=term_id), None)
55
58
 
56
59
 
57
- def get_ef_p_c2(cycle: dict):
60
+ def get_ef_p_c2(term_id: str, cycle: dict):
58
61
  tillage = _get_tillage(cycle)
59
62
  country = cycle.get('site', {}).get('country', {})
60
- # TODO: handle pasture
61
- return safe_parse_float(get_lookup_value(country, 'EF_P_C2'), None) if tillage is None else _get_C2_factor(tillage)
63
+ return _get_C2_factor(term_id, tillage) if tillage else safe_parse_float(
64
+ get_lookup_value(country, 'EF_P_C2', model=MODEL, term=term_id), None)
62
65
 
63
66
 
64
67
  def get_water(cycle: dict, precipitation: float):