hestia-earth-models 0.70.0__py3-none-any.whl → 0.70.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.
Files changed (192) hide show
  1. hestia_earth/models/aware/scarcityWeightedWaterUse.py +8 -16
  2. hestia_earth/models/cml2001Baseline/resourceUseMineralsAndMetalsDuringCycle.py +2 -1
  3. hestia_earth/models/config/Cycle.json +98 -50
  4. hestia_earth/models/config/ImpactAssessment.json +12 -4
  5. hestia_earth/models/config/Site.json +40 -21
  6. hestia_earth/models/cycle/transformation.py +1 -1
  7. hestia_earth/models/cycle/utils.py +0 -6
  8. hestia_earth/models/data/ecoinventV3/__init__.py +15 -13
  9. hestia_earth/models/ecoalimV9/__init__.py +13 -0
  10. hestia_earth/models/ecoalimV9/cycle.py +128 -0
  11. hestia_earth/models/ecoalimV9/impact_assessment.py +125 -0
  12. hestia_earth/models/ecoalimV9/utils.py +31 -0
  13. hestia_earth/models/ecoinventV3/__init__.py +6 -14
  14. hestia_earth/models/ecoinventV3/utils.py +1 -29
  15. hestia_earth/models/ecoinventV3AndEmberClimate/__init__.py +8 -2
  16. hestia_earth/models/emissionNotRelevant/__init__.py +33 -8
  17. hestia_earth/models/{cycle → hestia}/aboveGroundCropResidue.py +4 -3
  18. hestia_earth/models/{cycle → hestia}/aboveGroundCropResidueTotal.py +1 -1
  19. hestia_earth/models/{site → hestia}/brackishWater.py +1 -1
  20. hestia_earth/models/{site → hestia}/cationExchangeCapacityPerKgSoil.py +1 -1
  21. hestia_earth/models/{cycle → hestia}/coldCarcassWeightPerHead.py +1 -1
  22. hestia_earth/models/{cycle → hestia}/coldDressedCarcassWeightPerHead.py +1 -1
  23. hestia_earth/models/{cycle → hestia}/concentrateFeed.py +1 -1
  24. hestia_earth/models/{cycle → hestia}/cropResidueManagement.py +1 -1
  25. hestia_earth/models/{cycle → hestia}/croppingIntensity.py +1 -1
  26. hestia_earth/models/{cycle → hestia}/energyContentLowerHeatingValue.py +1 -1
  27. hestia_earth/models/{cycle → hestia}/excretaKgMass.py +8 -3
  28. hestia_earth/models/{cycle → hestia}/excretaKgN.py +1 -1
  29. hestia_earth/models/{cycle → hestia}/excretaKgVs.py +1 -1
  30. hestia_earth/models/{cycle → hestia}/feedConversionRatio/__init__.py +1 -1
  31. hestia_earth/models/{site → hestia}/flowingWater.py +1 -1
  32. hestia_earth/models/{site → hestia}/freshWater.py +1 -1
  33. hestia_earth/models/{cycle → hestia}/inorganicFertiliser.py +1 -1
  34. hestia_earth/models/{cycle → hestia}/irrigatedTypeUnspecified.py +14 -19
  35. hestia_earth/models/hestia/landCover.py +30 -22
  36. hestia_earth/models/{cycle → hestia}/liveAnimal.py +1 -1
  37. hestia_earth/models/{cycle → hestia}/longFallowRatio.py +1 -1
  38. hestia_earth/models/{site → hestia}/management.py +4 -6
  39. hestia_earth/models/{cycle → hestia}/materialAndSubstrate.py +1 -1
  40. hestia_earth/models/{cycle → hestia}/milkYield.py +1 -1
  41. hestia_earth/models/{site → hestia}/netPrimaryProduction.py +1 -1
  42. hestia_earth/models/{site → hestia}/organicCarbonPerHa.py +1 -1
  43. hestia_earth/models/hestia/pToSurfaceWaterAquacultureSystems.py +148 -0
  44. hestia_earth/models/{cycle → hestia}/pastureGrass.py +1 -1
  45. hestia_earth/models/{cycle → hestia}/pastureSystem.py +1 -1
  46. hestia_earth/models/{site → hestia}/potentialEvapotranspirationAnnual.py +3 -3
  47. hestia_earth/models/{site → hestia}/potentialEvapotranspirationMonthly.py +3 -3
  48. hestia_earth/models/{site → hestia}/precipitationAnnual.py +3 -3
  49. hestia_earth/models/{site → hestia}/precipitationMonthly.py +3 -3
  50. hestia_earth/models/{site → hestia}/rainfallAnnual.py +3 -3
  51. hestia_earth/models/{site → hestia}/rainfallMonthly.py +3 -3
  52. hestia_earth/models/{cycle → hestia}/readyToCookWeightPerHead.py +1 -1
  53. hestia_earth/models/{cycle → hestia}/residueBurnt.py +1 -1
  54. hestia_earth/models/{cycle → hestia}/residueIncorporated.py +1 -1
  55. hestia_earth/models/{cycle → hestia}/residueLeftOnField.py +1 -1
  56. hestia_earth/models/hestia/residueRemoved.py +65 -13
  57. hestia_earth/models/{site → hestia}/salineWater.py +1 -1
  58. hestia_earth/models/{site → hestia}/soilMeasurement.py +1 -1
  59. hestia_earth/models/{cycle → hestia}/stockingDensityAnimalHousingAverage.py +1 -1
  60. hestia_earth/models/{site → hestia}/temperatureAnnual.py +3 -3
  61. hestia_earth/models/{site → hestia}/temperatureMonthly.py +3 -3
  62. hestia_earth/models/{site → hestia}/totalNitrogenPerKgSoil.py +1 -1
  63. hestia_earth/models/{cycle → hestia}/unknownPreSeasonWaterRegime.py +1 -1
  64. hestia_earth/models/hestia/utils.py +93 -0
  65. hestia_earth/models/{site → hestia}/waterDepth.py +1 -1
  66. hestia_earth/models/hestia/waterSalinity.py +78 -0
  67. hestia_earth/models/ipcc2019/aboveGroundBiomass.py +1 -1
  68. hestia_earth/models/ipcc2019/belowGroundBiomass.py +1 -1
  69. hestia_earth/models/ipcc2019/biomass_utils.py +2 -4
  70. hestia_earth/models/ipcc2019/ch4ToAirFloodedRice.py +166 -79
  71. hestia_earth/models/ipcc2019/ch4ToAirOrganicSoilCultivation.py +270 -0
  72. hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +0 -3
  73. hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +0 -3
  74. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +88 -63
  75. hestia_earth/models/ipcc2019/co2ToAirLimeHydrolysis.py +7 -5
  76. hestia_earth/models/ipcc2019/co2ToAirOrganicSoilCultivation.py +215 -0
  77. hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +0 -3
  78. hestia_earth/models/ipcc2019/co2ToAirUreaHydrolysis.py +16 -9
  79. hestia_earth/models/ipcc2019/n2OToAirOrganicSoilCultivationDirect.py +161 -0
  80. hestia_earth/models/ipcc2019/nonCo2EmissionsToAirNaturalVegetationBurning.py +35 -47
  81. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1.py +86 -1
  82. hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2.py +127 -1
  83. hestia_earth/models/ipcc2019/organicCarbonPerHa_utils.py +7 -5
  84. hestia_earth/models/ipcc2019/organicSoilCultivation_utils.py +159 -0
  85. hestia_earth/models/mocking/search-results.json +1113 -1113
  86. hestia_earth/models/pooreNemecek2018/utils.py +8 -2
  87. hestia_earth/models/schmidt2007/ch4ToAirWasteTreatment.py +1 -4
  88. hestia_earth/models/schmidt2007/h2SToAirWasteTreatment.py +1 -4
  89. hestia_earth/models/schmidt2007/n2OToAirWasteTreatmentDirect.py +1 -4
  90. hestia_earth/models/schmidt2007/nh3ToAirWasteTreatment.py +1 -4
  91. hestia_earth/models/site/grouped_measurement.py +132 -0
  92. hestia_earth/models/utils/__init__.py +4 -3
  93. hestia_earth/models/utils/background_emissions.py +52 -0
  94. hestia_earth/models/utils/blank_node.py +47 -14
  95. hestia_earth/models/utils/constant.py +26 -20
  96. hestia_earth/models/utils/impact_assessment.py +26 -17
  97. hestia_earth/models/utils/lookup.py +48 -39
  98. hestia_earth/models/utils/measurement.py +3 -3
  99. hestia_earth/models/utils/product.py +39 -1
  100. hestia_earth/models/utils/property.py +14 -6
  101. hestia_earth/models/version.py +1 -1
  102. {hestia_earth_models-0.70.0.dist-info → hestia_earth_models-0.70.2.dist-info}/METADATA +2 -2
  103. {hestia_earth_models-0.70.0.dist-info → hestia_earth_models-0.70.2.dist-info}/RECORD +187 -171
  104. tests/models/aware/test_scarcityWeightedWaterUse.py +1 -12
  105. tests/models/ecoalimV9/__init__.py +0 -0
  106. tests/models/ecoalimV9/test_cycle.py +21 -0
  107. tests/models/ecoalimV9/test_impact_assessment.py +24 -0
  108. tests/models/environmentalFootprintV3_1/test_scarcityWeightedWaterUse.py +4 -2
  109. tests/models/{cycle → hestia}/test_aboveGroundCropResidue.py +1 -1
  110. tests/models/{cycle → hestia}/test_aboveGroundCropResidueTotal.py +1 -1
  111. tests/models/{site → hestia}/test_brackishWater.py +1 -1
  112. tests/models/{site → hestia}/test_cationExchangeCapacityPerKgSoil.py +1 -1
  113. tests/models/{cycle → hestia}/test_coldCarcassWeightPerHead.py +1 -1
  114. tests/models/{cycle → hestia}/test_coldDressedCarcassWeightPerHead.py +1 -1
  115. tests/models/{cycle → hestia}/test_concentrateFeed.py +1 -1
  116. tests/models/{cycle → hestia}/test_cropResidueManagement.py +1 -1
  117. tests/models/{cycle → hestia}/test_croppingIntensity.py +1 -1
  118. tests/models/{cycle → hestia}/test_energyContentLowerHeatingValue.py +5 -3
  119. tests/models/{cycle → hestia}/test_excretaKgMass.py +1 -1
  120. tests/models/{cycle → hestia}/test_excretaKgN.py +1 -1
  121. tests/models/{cycle → hestia}/test_excretaKgVs.py +1 -1
  122. tests/models/{cycle → hestia}/test_feedConversionRatio.py +3 -4
  123. tests/models/{site → hestia}/test_flowingWater.py +1 -1
  124. tests/models/{site → hestia}/test_freshWater.py +1 -1
  125. tests/models/{cycle → hestia}/test_inorganicFertiliser.py +1 -1
  126. tests/models/{cycle → hestia}/test_irrigatedTypeUnspecified.py +2 -5
  127. tests/models/hestia/test_landCover.py +4 -34
  128. tests/models/{cycle → hestia}/test_liveAnimal.py +1 -1
  129. tests/models/{cycle → hestia}/test_longFallowRatio.py +1 -1
  130. tests/models/{site → hestia}/test_management.py +1 -1
  131. tests/models/{cycle → hestia}/test_materialsAndSubstrate.py +1 -1
  132. tests/models/{cycle → hestia}/test_milkYield.py +1 -1
  133. tests/models/{site → hestia}/test_netPrimaryProduction.py +1 -1
  134. tests/models/{site → hestia}/test_organicCarbonPerHa.py +1 -1
  135. tests/models/{site → hestia}/test_organicCarbonPerKgSoil.py +1 -1
  136. tests/models/{site → hestia}/test_organicCarbonPerM3Soil.py +1 -1
  137. tests/models/{site → hestia}/test_organicMatterPerKgSoil.py +1 -1
  138. tests/models/{site → hestia}/test_organicMatterPerM3Soil.py +1 -1
  139. tests/models/hestia/test_pToSurfaceWaterAquacultureSystems.py +56 -0
  140. tests/models/{cycle → hestia}/test_pastureGrass.py +1 -1
  141. tests/models/{cycle → hestia}/test_pastureSystem.py +1 -1
  142. tests/models/{site → hestia}/test_potentialEvapotranspirationAnnual.py +1 -1
  143. tests/models/{site → hestia}/test_potentialEvapotranspirationMonthly.py +1 -1
  144. tests/models/{site → hestia}/test_precipitationAnnual.py +1 -1
  145. tests/models/{site → hestia}/test_precipitationMonthly.py +1 -1
  146. tests/models/{site → hestia}/test_rainfallAnnual.py +1 -1
  147. tests/models/{site → hestia}/test_rainfallMonthly.py +1 -1
  148. tests/models/{cycle → hestia}/test_readyToCookWeightPerHead.py +1 -1
  149. tests/models/{cycle → hestia}/test_residueBurnt.py +1 -1
  150. tests/models/{cycle → hestia}/test_residueIncorporated.py +1 -1
  151. tests/models/{cycle → hestia}/test_residueLeftOnField.py +1 -1
  152. tests/models/hestia/test_residueRemoved.py +15 -3
  153. tests/models/{site → hestia}/test_salineWater.py +1 -1
  154. tests/models/{site → hestia}/test_soilMeasurement.py +13 -21
  155. tests/models/{cycle → hestia}/test_stockingDensityAnimalHousingAverage.py +1 -1
  156. tests/models/{site → hestia}/test_temperatureAnnual.py +1 -1
  157. tests/models/{site → hestia}/test_temperatureMonthly.py +1 -1
  158. tests/models/{site → hestia}/test_totalNitrogenPerKgSoil.py +1 -1
  159. tests/models/{cycle → hestia}/test_unknownPreSeasonWaterRegime.py +1 -1
  160. tests/models/{site → hestia}/test_waterDepth.py +1 -1
  161. tests/models/hestia/test_waterSalinity.py +26 -0
  162. tests/models/ipcc2019/test_ch4ToAirEntericFermentation.py +2 -5
  163. tests/models/ipcc2019/test_ch4ToAirFloodedRice.py +10 -42
  164. tests/models/ipcc2019/test_ch4ToAirOrganicSoilCultivation.py +61 -0
  165. tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +11 -9
  166. tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +10 -8
  167. tests/models/ipcc2019/test_co2ToAirLimeHydrolysis.py +1 -1
  168. tests/models/ipcc2019/test_co2ToAirOrganicSoilCultivation.py +62 -0
  169. tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +11 -8
  170. tests/models/ipcc2019/test_n2OToAirOrganicSoilCultivationDirect.py +61 -0
  171. tests/models/ipcc2019/test_nonCo2EmissionsToAirNaturalVegetationBurning.py +3 -2
  172. tests/models/site/test_grouped_measurement.py +20 -0
  173. tests/models/test_ecoinventV3AndEmberClimate.py +2 -2
  174. tests/models/test_emissionNotRelevant.py +0 -8
  175. tests/models/utils/test_measurement.py +1 -1
  176. hestia_earth/models/cycle/residueRemoved.py +0 -54
  177. hestia_earth/models/hestia/nh3ToSurfaceWaterAquacultureSystems.py +0 -64
  178. hestia_earth/models/site/utils.py +0 -93
  179. tests/models/cycle/test_residueRemoved.py +0 -37
  180. tests/models/hestia/test_nh3ToSurfaceWaterAquacultureSystems.py +0 -51
  181. /hestia_earth/models/{cycle → hestia}/feedConversionRatio/feedConversionRatioCarbon.py +0 -0
  182. /hestia_earth/models/{cycle → hestia}/feedConversionRatio/feedConversionRatioDryMatter.py +0 -0
  183. /hestia_earth/models/{cycle → hestia}/feedConversionRatio/feedConversionRatioEnergy.py +0 -0
  184. /hestia_earth/models/{cycle → hestia}/feedConversionRatio/feedConversionRatioFedWeight.py +0 -0
  185. /hestia_earth/models/{cycle → hestia}/feedConversionRatio/feedConversionRatioNitrogen.py +0 -0
  186. /hestia_earth/models/{site → hestia}/organicCarbonPerKgSoil.py +0 -0
  187. /hestia_earth/models/{site → hestia}/organicCarbonPerM3Soil.py +0 -0
  188. /hestia_earth/models/{site → hestia}/organicMatterPerKgSoil.py +0 -0
  189. /hestia_earth/models/{site → hestia}/organicMatterPerM3Soil.py +0 -0
  190. {hestia_earth_models-0.70.0.dist-info → hestia_earth_models-0.70.2.dist-info}/LICENSE +0 -0
  191. {hestia_earth_models-0.70.0.dist-info → hestia_earth_models-0.70.2.dist-info}/WHEEL +0 -0
  192. {hestia_earth_models-0.70.0.dist-info → hestia_earth_models-0.70.2.dist-info}/top_level.txt +0 -0
@@ -319,7 +319,13 @@ def _add_carbon_stock_change_emissions(
319
319
  value = emission_1.value + emission_2.value
320
320
  start_date = min(emission_1.start_date, emission_2.start_date)
321
321
  end_date = max(emission_1.end_date, emission_2.end_date)
322
- method = min_emission_method_tier(emission_1.method, emission_2.method)
322
+
323
+ methods = [
324
+ method for emission in (emission_1, emission_2)
325
+ if isinstance((method := emission.method), EmissionMethodTier)
326
+ ]
327
+
328
+ method = min_emission_method_tier(*methods) if methods else None
323
329
 
324
330
  return CarbonStockChangeEmission(value, start_date, end_date, method)
325
331
 
@@ -442,7 +448,6 @@ def create_should_run_function(
442
448
 
443
449
  inventory, inventory_logs = (
444
450
  compile_inventory_func(
445
- cycle_id,
446
451
  cycles,
447
452
  carbon_stock_measurements,
448
453
  land_cover_nodes
@@ -464,7 +469,8 @@ def create_should_run_function(
464
469
  logs = should_compile_logs | inventory_logs | {
465
470
  "seed": seed,
466
471
  "has_valid_inventory": has_valid_inventory,
467
- "has_consecutive_years": has_consecutive_years
472
+ "has_consecutive_years": has_consecutive_years,
473
+ "has_stock_measurements": bool(carbon_stock_measurements)
468
474
  }
469
475
 
470
476
  return should_run_, kwargs, logs
@@ -554,7 +560,6 @@ def _create_compile_inventory_function(
554
560
  The `compile_inventory` function.
555
561
  """
556
562
  def compile_inventory(
557
- cycle_id: str,
558
563
  cycles: list[dict],
559
564
  carbon_stock_measurements: list[dict],
560
565
  land_cover_nodes: list[dict]
@@ -796,26 +801,8 @@ def _preprocess_carbon_stocks(
796
801
  list[CarbonStock]
797
802
  A list of carbon stocks sorted by date.
798
803
  """
799
- sorted_measurements = sorted(
800
- flatten([split_node_by_dates(m) for m in carbon_stock_measurements]),
801
- key=lambda node: _gapfill_datestr(node["dates"][0], DatestrGapfillMode.END)
802
- )
803
-
804
- values = flatten(node["value"] for node in sorted_measurements)
805
-
806
- sds = flatten(
807
- node.get("sd", []) or [_calc_nominal_sd(v, _NOMINAL_ERROR) for v in node["value"]]
808
- for node in sorted_measurements
809
- )
810
-
811
- dates = flatten(
812
- [_gapfill_datestr(datestr, DatestrGapfillMode.END) for datestr in node["dates"]]
813
- for node in sorted_measurements
814
- )
815
-
816
- methods = flatten(
817
- [MeasurementMethodClassification(node.get("methodClassification")) for _ in node["value"]]
818
- for node in sorted_measurements
804
+ dates, values, sds, methods = _extract_node_data(
805
+ flatten([split_node_by_dates(m) for m in carbon_stock_measurements])
819
806
  )
820
807
 
821
808
  correlation_matrix = compute_time_series_correlation_matrix(
@@ -842,6 +829,35 @@ def _preprocess_carbon_stocks(
842
829
  ]
843
830
 
844
831
 
832
+ def _extract_node_data(nodes: list[dict]) -> list[dict]:
833
+
834
+ def group_node(result, node) -> dict[str, dict]:
835
+ date = _gapfill_datestr(node["dates"][0], DatestrGapfillMode.END)
836
+ result[date] = result.get(date, []) + [node]
837
+ return result
838
+
839
+ grouped_nodes = reduce(group_node, nodes, dict())
840
+
841
+ def get_values(date):
842
+ return flatten(node.get("value", []) for node in grouped_nodes[date])
843
+
844
+ def get_sds(date):
845
+ return flatten(
846
+ node.get("sd", []) or [_calc_nominal_sd(v, _NOMINAL_ERROR) for v in node.get("value", [])]
847
+ for node in grouped_nodes[date]
848
+ )
849
+
850
+ def get_methods(date):
851
+ return flatten(node.get("methodClassification", []) for node in grouped_nodes[date])
852
+
853
+ dates = sorted(grouped_nodes.keys())
854
+ values = [mean(get_values(date)) for date in dates]
855
+ sds = [mean(get_sds(date)) for date in dates]
856
+ methods = [min_measurement_method_classification(get_methods(date)) for date in dates]
857
+
858
+ return dates, values, sds, methods
859
+
860
+
845
861
  def _calc_nominal_sd(value: float, error: float) -> float:
846
862
  """
847
863
  Calculate a nominal SD for a carbon stock measurement. Can be used to gap fill SD when information not present in
@@ -1166,20 +1182,19 @@ def _squash_inventory(
1166
1182
  return _InventoryKey.CO2_EMISSION in carbon_stock_inventory.get(method, {}).get(year, {}).keys()
1167
1183
 
1168
1184
  def squash(result: dict, year: int) -> dict:
1169
- update_dict = next(
1170
- (
1171
- {
1172
- year: {
1173
- **_get_land_use_change_data(year, land_use_inventory),
1174
- **reduce(merge, [
1175
- carbon_stock_inventory.get(method, {}).get(year, {}),
1176
- cycle_inventory.get(year, {})
1177
- ], dict())
1178
- }
1179
- } for method in measurement_method_ranking if should_run_group(method, year)
1180
- ),
1181
- {}
1185
+ method = next(
1186
+ (method for method in measurement_method_ranking if should_run_group(method, year)),
1187
+ None
1182
1188
  )
1189
+ update_dict = {
1190
+ year: {
1191
+ **_get_land_use_change_data(year, land_use_inventory),
1192
+ **reduce(merge, [
1193
+ carbon_stock_inventory.get(method, {}).get(year, {}),
1194
+ cycle_inventory.get(year, {})
1195
+ ], dict())
1196
+ }
1197
+ }
1183
1198
  return result | update_dict
1184
1199
 
1185
1200
  return reduce(squash, inventory_years, dict())
@@ -1415,36 +1430,31 @@ def create_run_function(
1415
1430
  Assign emissions to either the land use or management change term ids and sum together.
1416
1431
  """
1417
1432
  data = inventory[year]
1433
+
1418
1434
  years_since_luc_event = data[_InventoryKey.YEARS_SINCE_LUC_EVENT]
1419
1435
  years_since_inventory_start = data[_InventoryKey.YEARS_SINCE_INVENTORY_START]
1436
+ share_of_emission = data[_InventoryKey.SHARE_OF_EMISSION][cycle_id]
1420
1437
 
1438
+ co2_emission = data.get(_InventoryKey.CO2_EMISSION)
1439
+
1440
+ has_co2_emission = bool(co2_emission)
1421
1441
  is_luc_emission = bool(years_since_luc_event) and years_since_luc_event <= _TRANSITION_PERIOD_YEARS
1422
1442
  is_data_complete = bool(years_since_inventory_start) and years_since_inventory_start >= _TRANSITION_PERIOD_YEARS
1423
1443
 
1424
- if is_luc_emission:
1425
- # If LUC emission allocate emissions to land use change AND add corresponding zero emission to management
1426
- emission_term_id = land_use_change_emission_term_id
1427
- zero_emission_term_id = management_change_emission_term_id
1428
- elif is_data_complete:
1429
- # If management emission && data complete allocate emissions to management change AND add corresponding
1430
- # zero emission to management
1431
- emission_term_id = management_change_emission_term_id
1432
- zero_emission_term_id = land_use_change_emission_term_id
1433
- else:
1434
- # If management emission, but not data complete allocate emissions to management change only
1435
- emission_term_id = management_change_emission_term_id
1436
- zero_emission_term_id = None
1444
+ emission_term_id = (
1445
+ land_use_change_emission_term_id if is_luc_emission else management_change_emission_term_id
1446
+ ) if has_co2_emission else None
1437
1447
 
1438
- rescaled_emission = _rescale_carbon_stock_change_emission(
1439
- data[_InventoryKey.CO2_EMISSION], data[_InventoryKey.SHARE_OF_EMISSION][cycle_id]
1448
+ zero_emission_term_id = (
1449
+ management_change_emission_term_id if is_luc_emission else
1450
+ (land_use_change_emission_term_id if is_data_complete else None)
1440
1451
  )
1441
1452
 
1442
- zero_emission = CarbonStockChangeEmission(
1443
- value=array(0),
1444
- start_date=rescaled_emission.start_date,
1445
- end_date=rescaled_emission.end_date,
1446
- method=rescaled_emission.method
1447
- ) if zero_emission_term_id else None
1453
+ rescaled_emission = _rescale_carbon_stock_change_emission(
1454
+ co2_emission, share_of_emission
1455
+ ) if emission_term_id else None
1456
+
1457
+ zero_emission = get_zero_emission(year) if zero_emission_term_id else None
1448
1458
 
1449
1459
  previous_emission = result.get(emission_term_id)
1450
1460
  previous_zero_emission = result.get(zero_emission_term_id)
@@ -1454,7 +1464,7 @@ def create_run_function(
1454
1464
  _add_carbon_stock_change_emissions(previous_emission, rescaled_emission) if previous_emission
1455
1465
  else rescaled_emission
1456
1466
  )
1457
- }
1467
+ } if emission_term_id else {}
1458
1468
 
1459
1469
  zero_emission_dict = {
1460
1470
  zero_emission_term_id: (
@@ -1497,7 +1507,7 @@ def create_run_function(
1497
1507
  return [
1498
1508
  new_emission_func(
1499
1509
  term_id=emission_term_id,
1500
- method_tier=total_emission.method,
1510
+ method_tier=_get_emission_method(total_emission),
1501
1511
  start_date=_get_emission_start_date(total_emission, cycle_start_date),
1502
1512
  end_date=_get_emission_end_date(total_emission, cycle_end_date),
1503
1513
  **calc_descriptive_stats(
@@ -1506,18 +1516,33 @@ def create_run_function(
1506
1516
  decimals=6
1507
1517
  )
1508
1518
  ) for emission_term_id, total_emission in assigned_emissions.items()
1519
+ if isinstance(total_emission, CarbonStockChangeEmission)
1509
1520
  ]
1510
1521
 
1511
1522
  return run
1512
1523
 
1513
1524
 
1525
+ def get_zero_emission(year):
1526
+ return CarbonStockChangeEmission(
1527
+ value=array(0),
1528
+ start_date=_gapfill_datestr(year),
1529
+ end_date=_gapfill_datestr(year, DatestrGapfillMode.END),
1530
+ method=None
1531
+ )
1532
+
1533
+
1534
+ def _get_emission_method(emission: CarbonStockChangeEmission):
1535
+ method = emission.method
1536
+ return method if isinstance(method, EmissionMethodTier) else EmissionMethodTier.TIER_1
1537
+
1538
+
1514
1539
  def _get_emission_start_date(emission: CarbonStockChangeEmission, cycle_start_date: str) -> str:
1515
1540
  cycle_datetime = safe_parse_date(_gapfill_datestr(cycle_start_date))
1516
1541
  emission_datetime = safe_parse_date(emission.start_date)
1517
1542
 
1518
1543
  should_run = (
1519
1544
  cycle_datetime and emission_datetime
1520
- and cycle_datetime <= emission_datetime # If the cycle starts before the emission, add a `startDate`
1545
+ and cycle_datetime < emission_datetime # If the cycle starts before the emission, add a `startDate`
1521
1546
  )
1522
1547
 
1523
1548
  return (
@@ -1527,12 +1552,12 @@ def _get_emission_start_date(emission: CarbonStockChangeEmission, cycle_start_da
1527
1552
 
1528
1553
 
1529
1554
  def _get_emission_end_date(emission: CarbonStockChangeEmission, cycle_end_date: str) -> str:
1530
- cycle_datetime = safe_parse_date(_gapfill_datestr(cycle_end_date))
1555
+ cycle_datetime = safe_parse_date(_gapfill_datestr(cycle_end_date, DatestrGapfillMode.END))
1531
1556
  emission_datetime = safe_parse_date(emission.end_date)
1532
1557
 
1533
1558
  should_run = (
1534
1559
  cycle_datetime and emission_datetime
1535
- and cycle_datetime >= emission_datetime # If the cycle ends after the emission, add an `endDate`
1560
+ and cycle_datetime > emission_datetime # If the cycle ends after the emission, add an `endDate`
1536
1561
  )
1537
1562
 
1538
1563
  return (
@@ -1,5 +1,5 @@
1
1
  from hestia_earth.schema import EmissionMethodTier, TermTermType
2
- from hestia_earth.utils.tools import list_sum
2
+ from hestia_earth.utils.tools import list_sum, non_empty_list
3
3
  from hestia_earth.utils.model import filter_list_term_type
4
4
 
5
5
  from hestia_earth.models.log import logRequirements, logShouldRun
@@ -16,7 +16,7 @@ REQUIREMENTS = {
16
16
  "@type": "Input",
17
17
  "value": "",
18
18
  "term.termType": "soilAmendment",
19
- "term.units": ["kg CaCO3", "kg MgCO3"]
19
+ "term.units": ["kg CaCO3", "kg CaMg(CO3)2"]
20
20
  }]
21
21
  }
22
22
  }
@@ -38,8 +38,10 @@ def _emission(value: float):
38
38
 
39
39
 
40
40
  def _get_lime_values(cycle: dict, inputs: list):
41
- # TODO: use lookup table
42
- values = [convert_to_unit(i, Units.KG_CO2) for i in inputs if len(i.get('value', [])) > 0]
41
+ values = non_empty_list([
42
+ convert_to_unit(i, Units.KG_CO2)
43
+ for i in inputs if len(i.get('value', [])) > 0
44
+ ])
43
45
  return [0] if (
44
46
  len(values) == 0 and _is_term_type_complete(cycle, TermTermType.SOILAMENDMENT)
45
47
  ) else values
@@ -56,7 +58,7 @@ def _run(CaCO3_values: list, MgCO3_values: list):
56
58
  def _should_run(cycle: dict):
57
59
  inputs = filter_list_term_type(cycle.get('inputs', []), TermTermType.SOILAMENDMENT)
58
60
  CaCO3_values = _get_lime_values(cycle, _filter_list_term_unit(inputs, Units.KG_CACO3))
59
- MgCO3_values = _get_lime_values(cycle, _filter_list_term_unit(inputs, Units.KG_MGCO3))
61
+ MgCO3_values = _get_lime_values(cycle, _filter_list_term_unit(inputs, Units.KG_CAMGCO32))
60
62
 
61
63
  logRequirements(cycle, model=MODEL, term=TERM_ID,
62
64
  CaCO3_values=len(CaCO3_values),
@@ -0,0 +1,215 @@
1
+ import numpy as np
2
+ import numpy.typing as npt
3
+ from typing import Callable, Union
4
+
5
+ from hestia_earth.schema import EmissionMethodTier, EmissionStatsDefinition
6
+
7
+ from hestia_earth.models.log import logRequirements, logShouldRun
8
+ from hestia_earth.models.utils.array_builders import gen_seed, repeat_single, truncated_normal_1d
9
+ from hestia_earth.models.utils.cycle import land_occupation_per_ha
10
+ from hestia_earth.models.utils.descriptive_stats import calc_descriptive_stats
11
+ from hestia_earth.models.utils.ecoClimateZone import EcoClimateZone, get_eco_climate_zone_value
12
+ from hestia_earth.models.utils.emission import _new_emission
13
+ from hestia_earth.models.utils.measurement import most_relevant_measurement_value
14
+ from hestia_earth.models.utils.site import valid_site_type
15
+
16
+ from .organicSoilCultivation_utils import (
17
+ assign_organic_soil_category, calc_emission, get_emission_factor, OrganicSoilCategory, valid_eco_climate_zone
18
+ )
19
+ from . import MODEL
20
+
21
+ REQUIREMENTS = {
22
+ "Cycle": {
23
+ "or": [
24
+ {
25
+ "cycleDuration": "",
26
+ "practices": [{"@type": "Practice", "value": "", "term.@id": "longFallowRatio"}]
27
+ },
28
+ {
29
+ "@doc": "for plantations, additional properties are required",
30
+ "practices": [
31
+ {"@type": "Practice", "value": "", "term.@id": "nurseryDensity"},
32
+ {"@type": "Practice", "value": "", "term.@id": "nurseryDuration"},
33
+ {"@type": "Practice", "value": "", "term.@id": "plantationProductiveLifespan"},
34
+ {"@type": "Practice", "value": "", "term.@id": "plantationDensity"},
35
+ {"@type": "Practice", "value": "", "term.@id": "plantationLifespan"},
36
+ {"@type": "Practice", "value": "", "term.@id": "rotationDuration"}
37
+ ]
38
+ }
39
+ ],
40
+ "site": {
41
+ "@type": "Site",
42
+ "measurements": [
43
+ {"@type": "Measurement", "value": "", "term.@id": "histosol"},
44
+ {"@type": "Measurement", "value": "", "term.@id": "ecoClimateZone"}
45
+ ]
46
+ },
47
+ "optional": {
48
+ "cycleDuration": ""
49
+ }
50
+ }
51
+ }
52
+ LOOKUPS = {
53
+ "crop": [
54
+ "isPlantation",
55
+ "IPCC_2013_ORGANIC_SOIL_CULTIVATION_CATEGORY"
56
+ ],
57
+ "forage": [
58
+ "isPlantation",
59
+ "IPCC_2013_ORGANIC_SOIL_CULTIVATION_CATEGORY"
60
+ ],
61
+ "ecoClimateZone": [
62
+ "IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_ANNUAL_CROPS",
63
+ "IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_PERENNIAL_CROPS",
64
+ "IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_ACACIA",
65
+ "IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_OIL_PALM",
66
+ "IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_SAGO_PALM",
67
+ "IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_PADDY_RICE_CULTIVATION",
68
+ "IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_GRASSLAND",
69
+ "IPCC_2013_ORGANIC_SOILS_TONNES_CO2-C_HECTARE_OTHER"
70
+ ]
71
+ }
72
+ RETURNS = {
73
+ "Emission": [{
74
+ "value": "",
75
+ "sd": "",
76
+ "min": "",
77
+ "max": "",
78
+ "observations": "",
79
+ "statsDefinition": "simulated",
80
+ "methodTier": "tier 1"
81
+ }]
82
+ }
83
+ TERM_ID = 'co2ToAirOrganicSoilCultivation'
84
+ TIER = EmissionMethodTier.TIER_1.value
85
+
86
+ _STATS_DEFINITION = EmissionStatsDefinition.SIMULATED.value
87
+ _ITERATIONS = 100000
88
+
89
+
90
+ def _emission(descriptive_stats: dict):
91
+ emission = _new_emission(TERM_ID, MODEL) | descriptive_stats
92
+ emission['methodTier'] = TIER
93
+ return emission
94
+
95
+
96
+ def sample_emission_factor(
97
+ eco_climate_zone: EcoClimateZone,
98
+ organic_soil_category: OrganicSoilCategory,
99
+ seed: Union[int, np.random.Generator, None] = None,
100
+ ) -> npt.NDArray:
101
+ factor_data = get_emission_factor(TERM_ID, eco_climate_zone, organic_soil_category)
102
+ sample_func = _get_sample_func(factor_data)
103
+ return sample_func(iterations=_ITERATIONS, seed=seed, **factor_data)
104
+
105
+
106
+ def _sample_truncated_normal(
107
+ *, iterations: int, value: float, sd: float, seed: Union[int, np.random.Generator, None] = None, **_
108
+ ) -> npt.NDArray:
109
+ """
110
+ Randomly sample a model parameter with a truncated normal distribution. Emission factors annot be below 0, so
111
+ truncated normal sampling used.
112
+ """
113
+ return truncated_normal_1d(shape=(1, iterations), mu=value, sigma=sd, low=0, high=np.inf, seed=seed)
114
+
115
+
116
+ def _sample_constant(*, value: float, **_) -> npt.NDArray:
117
+ """Sample a constant model parameter."""
118
+ return repeat_single(shape=(1, 1), value=value)
119
+
120
+
121
+ _KWARGS_TO_SAMPLE_FUNC = {
122
+ ("value", "sd"): _sample_truncated_normal,
123
+ ("value",): _sample_constant
124
+ }
125
+ """
126
+ Mapping from available distribution data to sample function.
127
+ """
128
+
129
+
130
+ def _get_sample_func(kwargs: dict) -> Callable:
131
+ """
132
+ Select the correct sample function for a parameter based on the distribution data available. All possible
133
+ parameters for the model should have, at a minimum, a `value`, meaning that no default function needs to be
134
+ specified.
135
+
136
+ This function has been extracted into it's own method to allow for mocking of sample function.
137
+
138
+ Keyword Args
139
+ ------------
140
+ value : float
141
+ The distribution mean.
142
+ sd : float
143
+ The standard deviation of the distribution.
144
+ se : float
145
+ The standard error of the distribution.
146
+ n : float
147
+ Sample size.
148
+
149
+ Returns
150
+ -------
151
+ Callable
152
+ The sample function for the distribution.
153
+ """
154
+ return next(
155
+ sample_func for required_kwargs, sample_func in _KWARGS_TO_SAMPLE_FUNC.items()
156
+ if all(kwarg in kwargs.keys() for kwarg in required_kwargs)
157
+ )
158
+
159
+
160
+ def _should_run(cycle: dict):
161
+ end_date = cycle.get('endDate')
162
+ site = cycle.get('site', {})
163
+ measurements = site.get('measurements', [])
164
+
165
+ seed = gen_seed(cycle, MODEL, TERM_ID)
166
+ rng = np.random.default_rng(seed)
167
+
168
+ def _get_measurement_content(term_id: str):
169
+ return most_relevant_measurement_value(measurements, term_id, end_date)
170
+
171
+ histosol = _get_measurement_content('histosol')
172
+ eco_climate_zone = get_eco_climate_zone_value(cycle, as_enum=True)
173
+ organic_soil_category = assign_organic_soil_category(cycle, log_id=TERM_ID)
174
+
175
+ emission_factor = (
176
+ sample_emission_factor(eco_climate_zone, organic_soil_category, seed=rng) if eco_climate_zone
177
+ else None
178
+ )
179
+ land_occupation = land_occupation_per_ha(MODEL, TERM_ID, cycle)
180
+
181
+ logRequirements(
182
+ cycle, model=MODEL, term=TERM_ID,
183
+ eco_climate_zone=eco_climate_zone,
184
+ organic_soil_category=organic_soil_category,
185
+ emission_factor=f"{np.mean(emission_factor):.3f}",
186
+ land_occupation=land_occupation,
187
+ histosol=histosol
188
+ )
189
+
190
+ should_run = all([
191
+ valid_site_type(site),
192
+ valid_eco_climate_zone(eco_climate_zone),
193
+ all(
194
+ var is not None for var in [
195
+ emission_factor,
196
+ land_occupation,
197
+ histosol
198
+ ]
199
+ )
200
+ ])
201
+
202
+ logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
203
+
204
+ return should_run, emission_factor, histosol, land_occupation
205
+
206
+
207
+ def _run(emission_factor: npt.NDArray, histosol: float, land_occupation: float):
208
+ result = calc_emission(TERM_ID, emission_factor, histosol, land_occupation)
209
+ descriptive_stats = calc_descriptive_stats(result, _STATS_DEFINITION)
210
+ return [_emission(descriptive_stats)]
211
+
212
+
213
+ def run(cycle: dict):
214
+ should_run, emission_factor, histosol, land_occupation = _should_run(cycle)
215
+ return _run(emission_factor, histosol, land_occupation) if should_run else []
@@ -199,13 +199,11 @@ def _should_compile_inventory_func(
199
199
  ) for cycle in cycles
200
200
  )
201
201
 
202
- has_stock_measurements = len(carbon_stock_measurements) > 0
203
202
  has_cycles = len(cycles) > 0
204
203
  has_functional_unit_1_ha = all(cycle.get('functionalUnit') == CycleFunctionalUnit._1_HA.value for cycle in cycles)
205
204
 
206
205
  should_run = all([
207
206
  has_soil,
208
- has_stock_measurements,
209
207
  has_cycles,
210
208
  has_functional_unit_1_ha
211
209
  ])
@@ -214,7 +212,6 @@ def _should_compile_inventory_func(
214
212
  "site_type": site_type,
215
213
  "has_soil": has_soil,
216
214
  "carbon_stock_term": _CARBON_STOCK_TERM_ID,
217
- "has_stock_measurements": has_stock_measurements,
218
215
  "has_cycles": has_cycles,
219
216
  "has_functional_unit_1_ha": has_functional_unit_1_ha,
220
217
  }
@@ -1,5 +1,5 @@
1
1
  from hestia_earth.schema import EmissionMethodTier
2
- from hestia_earth.utils.tools import list_sum, safe_parse_float
2
+ from hestia_earth.utils.tools import list_sum, safe_parse_float, non_empty_list
3
3
  from hestia_earth.utils.model import find_term_match
4
4
 
5
5
  from hestia_earth.models.log import debugValues, logRequirements, logShouldRun
@@ -68,14 +68,21 @@ def _should_run(cycle: dict):
68
68
  uan_share = get_country_breakdown(MODEL, TERM_ID, country_id, LOOKUPS['inorganicFertiliser'][1])
69
69
  urea_unspecified_as_n = list_sum(find_term_match(inputs, UNSPECIFIED_TERM_ID).get('value', []))
70
70
 
71
- urea_values = [{'id': id, 'values': _get_urea_values(cycle, inputs, id)} for id in term_ids] + ([
72
- {'id': 'ureaKgN', 'values': [urea_unspecified_as_n * urea_share]},
73
- {'id': 'ureaAmmoniumNitrateKgN', 'values': [urea_unspecified_as_n * uan_share]}
74
- ] if all([
75
- urea_share,
76
- uan_share,
77
- urea_unspecified_as_n > 0
78
- ]) else [])
71
+ urea_values = [
72
+ {
73
+ 'id': id,
74
+ 'values': _get_urea_values(cycle, inputs, id)
75
+ } for id in term_ids
76
+ ] + non_empty_list([
77
+ {
78
+ 'id': 'ureaKgN',
79
+ 'values': [urea_unspecified_as_n * urea_share]
80
+ } if urea_share is not None else None,
81
+ {
82
+ 'id': 'ureaAmmoniumNitrateKgN',
83
+ 'values': [urea_unspecified_as_n * uan_share]
84
+ } if urea_share is not None else None
85
+ ] if urea_unspecified_as_n > 0 else [])
79
86
  has_urea_value = any([len(data.get('values')) > 0 for data in urea_values])
80
87
 
81
88
  logRequirements(cycle, model=MODEL, term=TERM_ID,