hestia-earth-models 0.59.3__py3-none-any.whl → 0.59.5__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 (39) hide show
  1. hestia_earth/models/cycle/liveAnimal.py +3 -0
  2. hestia_earth/models/cycle/milkYield.py +1 -1
  3. hestia_earth/models/cycle/utils.py +1 -1
  4. hestia_earth/models/geospatialDatabase/potentialEvapotranspirationLongTermAnnualMean.py +2 -2
  5. hestia_earth/models/geospatialDatabase/potentialEvapotranspirationMonthly.py +99 -0
  6. hestia_earth/models/geospatialDatabase/precipitationMonthly.py +100 -0
  7. hestia_earth/models/geospatialDatabase/temperatureAnnual.py +2 -6
  8. hestia_earth/models/geospatialDatabase/temperatureLongTermAnnualMean.py +2 -3
  9. hestia_earth/models/geospatialDatabase/temperatureMonthly.py +98 -0
  10. hestia_earth/models/geospatialDatabase/utils.py +13 -1
  11. hestia_earth/models/ipcc2019/organicCarbonPerHa.py +72 -135
  12. hestia_earth/models/linkedImpactAssessment/__init__.py +78 -43
  13. hestia_earth/models/mocking/search-results.json +8 -47
  14. hestia_earth/models/schmidt2007/n2OToAirWasteTreatmentDirect.py +58 -0
  15. hestia_earth/models/schmidt2007/nh3ToAirWasteTreatment.py +58 -0
  16. hestia_earth/models/site/management.py +106 -13
  17. hestia_earth/models/site/pre_checks/cache_geospatialDatabase.py +27 -7
  18. hestia_earth/models/site/soilMeasurement.py +9 -9
  19. hestia_earth/models/site/utils.py +2 -6
  20. hestia_earth/models/utils/__init__.py +9 -0
  21. hestia_earth/models/utils/blank_node.py +3 -3
  22. hestia_earth/models/utils/site.py +8 -5
  23. hestia_earth/models/utils/term.py +0 -23
  24. hestia_earth/models/version.py +1 -1
  25. {hestia_earth_models-0.59.3.dist-info → hestia_earth_models-0.59.5.dist-info}/METADATA +2 -2
  26. {hestia_earth_models-0.59.3.dist-info → hestia_earth_models-0.59.5.dist-info}/RECORD +39 -29
  27. tests/models/geospatialDatabase/test_potentialEvapotranspirationMonthly.py +20 -0
  28. tests/models/geospatialDatabase/test_precipitationMonthly.py +20 -0
  29. tests/models/geospatialDatabase/test_temperatureMonthly.py +20 -0
  30. tests/models/ipcc2019/test_organicCarbonPerHa.py +8 -39
  31. tests/models/schmidt2007/test_n2OToAirWasteTreatmentDirect.py +45 -0
  32. tests/models/schmidt2007/test_nh3ToAirWasteTreatment.py +45 -0
  33. tests/models/site/test_management.py +37 -16
  34. tests/models/site/test_soilMeasurement.py +40 -21
  35. tests/models/utils/test_site.py +1 -1
  36. tests/models/utils/test_term.py +1 -8
  37. {hestia_earth_models-0.59.3.dist-info → hestia_earth_models-0.59.5.dist-info}/LICENSE +0 -0
  38. {hestia_earth_models-0.59.3.dist-info → hestia_earth_models-0.59.5.dist-info}/WHEEL +0 -0
  39. {hestia_earth_models-0.59.3.dist-info → hestia_earth_models-0.59.5.dist-info}/top_level.txt +0 -0
@@ -18,15 +18,12 @@ from typing import (
18
18
  from hestia_earth.schema import (
19
19
  CycleFunctionalUnit,
20
20
  MeasurementMethodClassification,
21
- SchemaType,
22
21
  SiteSiteType,
23
22
  TermTermType,
24
23
  )
25
- from hestia_earth.utils.api import find_related
26
24
  from hestia_earth.utils.model import find_term_match, filter_list_term_type
27
25
  from hestia_earth.utils.tools import flatten, list_sum, non_empty_list
28
26
 
29
- from hestia_earth.models.utils import _load_calculated_node
30
27
  from hestia_earth.models.log import log_as_table, logRequirements, logShouldRun
31
28
  from hestia_earth.models.utils.blank_node import (
32
29
  cumulative_nodes_match,
@@ -49,12 +46,11 @@ from hestia_earth.models.utils.term import (
49
46
  get_cover_crop_property_terms,
50
47
  get_crop_residue_incorporated_or_left_on_field_terms,
51
48
  get_irrigated_terms,
52
- get_long_fallow_land_cover_terms,
53
49
  get_residue_removed_or_burnt_terms,
54
50
  get_upland_rice_crop_terms,
55
51
  get_upland_rice_land_cover_terms
56
52
  )
57
-
53
+ from hestia_earth.models.utils.site import related_cycles
58
54
  from .utils import check_consecutive
59
55
  from . import MODEL
60
56
 
@@ -613,25 +609,26 @@ A dictionary mapping IPCC soil categories to corresponding soil type and USDA so
613
609
  `"IPCC_SOIL_CATEGORY"` column.
614
610
  """
615
611
 
616
- IPCC_LAND_USE_CATEGORY_TO_SITE_TYPE = {
617
- IpccLandUseCategory.GRASSLAND: SiteSiteType.PERMANENT_PASTURE.value,
618
- IpccLandUseCategory.PERENNIAL_CROPS: SiteSiteType.CROPLAND.value,
619
- IpccLandUseCategory.PADDY_RICE_CULTIVATION: SiteSiteType.CROPLAND.value,
620
- IpccLandUseCategory.ANNUAL_CROPS_WET: SiteSiteType.CROPLAND.value,
621
- IpccLandUseCategory.ANNUAL_CROPS: SiteSiteType.CROPLAND.value,
622
- IpccLandUseCategory.SET_ASIDE: SiteSiteType.CROPLAND.value,
623
- IpccLandUseCategory.FOREST: SiteSiteType.FOREST.value,
624
- IpccLandUseCategory.NATIVE: SiteSiteType.OTHER_NATURAL_VEGETATION.value
612
+ SITE_TYPE_TO_IPCC_LAND_USE_CATEGORY = {
613
+ SiteSiteType.PERMANENT_PASTURE.value: IpccLandUseCategory.GRASSLAND,
614
+ SiteSiteType.FOREST.value: IpccLandUseCategory.FOREST,
615
+ SiteSiteType.OTHER_NATURAL_VEGETATION.value: IpccLandUseCategory.NATIVE
625
616
  }
626
617
  """
627
- A dictionary mapping IPCC land use categories to corresponding site types.
618
+ A dictionary mapping site types to corresponding IPCC land use categories.
628
619
  """
629
620
 
630
621
  IPCC_LAND_USE_CATEGORY_TO_LAND_COVER_LOOKUP_VALUE = {
622
+ IpccLandUseCategory.GRASSLAND: "Grassland",
631
623
  IpccLandUseCategory.PERENNIAL_CROPS: "Perennial crops",
632
624
  IpccLandUseCategory.PADDY_RICE_CULTIVATION: "Paddy rice cultivation",
633
625
  IpccLandUseCategory.ANNUAL_CROPS_WET: "Annual crops",
634
- IpccLandUseCategory.ANNUAL_CROPS: "Annual crops"
626
+ IpccLandUseCategory.ANNUAL_CROPS: "Annual crops",
627
+ IpccLandUseCategory.SET_ASIDE: [
628
+ "Annual crops", "Paddy rice cultivation", "Perennial crops", "Set aside"
629
+ ],
630
+ IpccLandUseCategory.FOREST: "Forest",
631
+ IpccLandUseCategory.NATIVE: "Native"
635
632
  }
636
633
  """
637
634
  A dictionary mapping IPCC land use categories to corresponding land cover lookup values in the
@@ -2528,7 +2525,7 @@ def _has_irrigation(water_regime_nodes: list[dict]) -> bool:
2528
2525
 
2529
2526
  Parameters
2530
2527
  ----------
2531
- water_regime_nodes : List[dict]
2528
+ water_regime_nodes : list[dict]
2532
2529
  List of water regime nodes to be checked.
2533
2530
 
2534
2531
  Returns
@@ -2545,13 +2542,13 @@ def _has_irrigation(water_regime_nodes: list[dict]) -> bool:
2545
2542
 
2546
2543
  def _has_long_fallow(land_cover_nodes: list[dict]) -> bool:
2547
2544
  """
2548
- Check if long fallow terms is present in the land cover nodes.
2545
+ Check if long fallow terms are present in the land cover nodes.
2549
2546
 
2550
2547
  n.b., a super majority of the site area must be under long fallow for it to be classified as set aside.
2551
2548
 
2552
2549
  Parameters
2553
2550
  ----------
2554
- land_cover_nodes : List[dict]
2551
+ land_cover_nodes : list[dict]
2555
2552
  List of land cover nodes to be checked.
2556
2553
 
2557
2554
  Returns
@@ -2559,9 +2556,12 @@ def _has_long_fallow(land_cover_nodes: list[dict]) -> bool:
2559
2556
  bool
2560
2557
  `True` if long fallow is present, `False` otherwise.
2561
2558
  """
2562
- return cumulative_nodes_term_match(
2559
+ LOOKUP = LOOKUPS["landCover"][0]
2560
+ TARGET_LOOKUP_VALUE = "Set aside"
2561
+ return cumulative_nodes_lookup_match(
2563
2562
  land_cover_nodes,
2564
- target_term_ids=get_long_fallow_land_cover_terms(),
2563
+ lookup=LOOKUP,
2564
+ target_lookup_values=TARGET_LOOKUP_VALUE,
2565
2565
  cumulative_threshold=SUPER_MAJORITY_AREA_THRESHOLD
2566
2566
  ) or cumulative_nodes_match(
2567
2567
  lambda node: get_node_property(node, LONG_FALLOW_CROP_TERM_ID, False).get("value", 0),
@@ -2576,7 +2576,7 @@ def _has_upland_rice(land_cover_nodes: list[dict]) -> bool:
2576
2576
 
2577
2577
  Parameters
2578
2578
  ----------
2579
- land_cover_nodes : List[dict]
2579
+ land_cover_nodes : list[dict]
2580
2580
  List of land cover nodes to be checked.
2581
2581
 
2582
2582
  Returns
@@ -2592,63 +2592,39 @@ def _has_upland_rice(land_cover_nodes: list[dict]) -> bool:
2592
2592
 
2593
2593
 
2594
2594
  IPCC_LAND_USE_CATEGORY_TO_VALIDATION_KWARGS = {
2595
+ IpccLandUseCategory.ANNUAL_CROPS_WET: {"has_wetland_soils"},
2595
2596
  IpccLandUseCategory.SET_ASIDE: {"has_long_fallow"},
2596
- IpccLandUseCategory.ANNUAL_CROPS_WET: {"has_wetland_soils"}
2597
2597
  }
2598
2598
  """
2599
- Keyword arguments that need to be checked for specific `IpccLandUseCategory`s.
2599
+ Keyword arguments that need to be validated in addition to the `landCover` lookup match for specific
2600
+ `IpccLandUseCategory`s.
2600
2601
  """
2601
2602
 
2602
-
2603
- def _check_ipcc_land_use_category(*, key: IpccLandUseCategory, site_type: str, **kwargs) -> bool:
2604
- """
2605
- Check if the site type matches the target site type for the given key.
2606
-
2607
- Parameters
2608
- ----------
2609
- key : IpccLandUseCategory
2610
- The IPCC land use category to check.
2611
- site_type : str
2612
- The site type to check.
2613
-
2614
- Keyword Args
2615
- ------------
2616
- has_long_fallow : bool
2617
- Indicates whether long fallow is present on more than 30% of the site.
2618
- has_wetland_soils : bool
2619
- Indicates whether wetland soils are present to more than 30% of the site.
2620
-
2621
- Returns
2622
- -------
2623
- bool
2624
- `True` if the conditions match the specified land use category, `False` otherwise.
2625
-
2626
- """
2627
- target_site_type = IPCC_LAND_USE_CATEGORY_TO_SITE_TYPE.get(key, None)
2628
- validation_kwargs = IPCC_LAND_USE_CATEGORY_TO_VALIDATION_KWARGS.get(key, set())
2629
- valid_kwargs = all(v for k, v in kwargs.items() if k in validation_kwargs)
2630
- return site_type == target_site_type and valid_kwargs
2603
+ IPCC_LAND_USE_CATEGORY_TO_OVERRIDE_KWARGS = {
2604
+ IpccLandUseCategory.PADDY_RICE_CULTIVATION: {"has_irrigated_upland_rice"}
2605
+ }
2606
+ """
2607
+ Keyword arguments that can override the `landCover` lookup match for specific `IpccLandUseCategory`s.
2608
+ """
2631
2609
 
2632
2610
 
2633
- def _check_cropland_land_use_category(
2634
- *, key: IpccLandUseCategory, site_type: str, land_cover_nodes: list[dict], **kwargs
2635
- ) -> bool:
2611
+ def _check_ipcc_land_use_category(*, key: IpccLandUseCategory, land_cover_nodes: list[dict], **kwargs) -> bool:
2636
2612
  """
2637
- Check if the site type and land cover nodes match the target conditions for a cropland IpccLandUseCategory.
2638
-
2639
- This function is special case of `_check_ipcc_land_use_category`.
2613
+ Check if the land cover nodes and keyword args satisfy the requirements for the given key.
2640
2614
 
2641
2615
  Parameters
2642
2616
  ----------
2643
2617
  key : IpccLandUseCategory
2644
2618
  The IPCC land use category to check.
2645
- site_type : str
2646
- The site type to check.
2619
+ land_cover_nodes : list[dict]
2620
+ List of land cover nodes to be checked.
2647
2621
 
2648
2622
  Keyword Args
2649
2623
  ------------
2624
+ has_irrigated_upland_rice : bool
2625
+ Indicates whether irrigated upland rice is present on more than 30% of the site.
2650
2626
  has_long_fallow : bool
2651
- Indicates whether long fallow is present on more than 30% of the site.
2627
+ Indicates whether long fallow is present on more than 70% of the site.
2652
2628
  has_wetland_soils : bool
2653
2629
  Indicates whether wetland soils are present to more than 30% of the site.
2654
2630
 
@@ -2665,48 +2641,23 @@ def _check_cropland_land_use_category(
2665
2641
  target_lookup_values=target_lookup_values,
2666
2642
  cumulative_threshold=MIN_AREA_THRESHOLD
2667
2643
  )
2668
- return _check_ipcc_land_use_category(key=key, site_type=site_type, **kwargs) and valid_lookup
2669
-
2670
-
2671
- def _check_paddy_rice_cultivation_land_use_category(
2672
- *, key: IpccLandUseCategory, site_type: str, has_irrigated_upland_rice: bool, **kwargs
2673
- ) -> bool:
2674
- """
2675
- Check if the site type and land cover nodes match the target conditions for a cropland IpccLandUseCategory.
2676
2644
 
2677
- This function is special case of `_check_cropland_land_use_category`.
2645
+ validation_kwargs = IPCC_LAND_USE_CATEGORY_TO_VALIDATION_KWARGS.get(key, set())
2646
+ valid_kwargs = all(v for k, v in kwargs.items() if k in validation_kwargs)
2678
2647
 
2679
- Parameters
2680
- ----------
2681
- key : IpccLandUseCategory
2682
- The IPCC land use category to check.
2683
- site_type : str
2684
- The site type to check.
2648
+ override_kwargs = IPCC_LAND_USE_CATEGORY_TO_OVERRIDE_KWARGS.get(key, set())
2649
+ valid_override = any(v for k, v in kwargs.items() if k in override_kwargs)
2685
2650
 
2686
- Keyword Args
2687
- ------------
2688
- has_irrigated_upland_rice : bool
2689
- Indicates whether irrigated upland rice is present on more than 30% of the site.
2690
- has_long_fallow : bool
2691
- Indicates whether long fallow is present on more than 30% of the site.
2692
- has_wetland_soils : bool
2693
- Indicates whether wetland soils are present to more than 30% of the site.
2694
-
2695
- Returns
2696
- -------
2697
- bool
2698
- `True` if the conditions match the specified land use category, `False` otherwise.
2699
- """
2700
- return _check_cropland_land_use_category(key=key, site_type=site_type, **kwargs) or has_irrigated_upland_rice
2651
+ return (valid_lookup and valid_kwargs) or valid_override
2701
2652
 
2702
2653
 
2703
2654
  LAND_USE_CATEGORY_DECISION_TREE = {
2704
2655
  IpccLandUseCategory.GRASSLAND: _check_ipcc_land_use_category,
2705
2656
  IpccLandUseCategory.SET_ASIDE: _check_ipcc_land_use_category,
2706
- IpccLandUseCategory.PERENNIAL_CROPS: _check_cropland_land_use_category,
2707
- IpccLandUseCategory.PADDY_RICE_CULTIVATION: _check_paddy_rice_cultivation_land_use_category,
2708
- IpccLandUseCategory.ANNUAL_CROPS_WET: _check_cropland_land_use_category,
2709
- IpccLandUseCategory.ANNUAL_CROPS: _check_cropland_land_use_category,
2657
+ IpccLandUseCategory.PERENNIAL_CROPS: _check_ipcc_land_use_category,
2658
+ IpccLandUseCategory.PADDY_RICE_CULTIVATION: _check_ipcc_land_use_category,
2659
+ IpccLandUseCategory.ANNUAL_CROPS_WET: _check_ipcc_land_use_category,
2660
+ IpccLandUseCategory.ANNUAL_CROPS: _check_ipcc_land_use_category,
2710
2661
  IpccLandUseCategory.FOREST: _check_ipcc_land_use_category,
2711
2662
  IpccLandUseCategory.NATIVE: _check_ipcc_land_use_category,
2712
2663
  IpccLandUseCategory.OTHER: _check_ipcc_land_use_category
@@ -2715,21 +2666,19 @@ LAND_USE_CATEGORY_DECISION_TREE = {
2715
2666
  A decision tree mapping IPCC soil categories to corresponding check functions.
2716
2667
 
2717
2668
  Key: IpccLandUseCategory
2718
- Value: Corresponding function for checking the match of the given land use category based on site type
2719
- and land cover nodes.
2669
+ Value: Corresponding function for checking the match of the given land use category based on land cover nodes
2670
+ and additional kwargs.
2720
2671
  """
2721
2672
 
2722
2673
 
2723
2674
  def _assign_ipcc_land_use_category(
2724
- site_type: str, management_nodes: list[dict], ipcc_soil_category: IpccSoilCategory
2675
+ management_nodes: list[dict], ipcc_soil_category: IpccSoilCategory,
2725
2676
  ) -> IpccLandUseCategory:
2726
2677
  """
2727
- Assigns IPCC land use category based on site type, management nodes, and soil category.
2678
+ Assigns IPCC land use category based on management nodes and soil category.
2728
2679
 
2729
2680
  Parameters
2730
2681
  ----------
2731
- site_type : str
2732
- The type of the site.
2733
2682
  management_nodes : list[dict]
2734
2683
  List of management nodes.
2735
2684
  ipcc_soil_category : IpccSoilCategory
@@ -2752,14 +2701,13 @@ def _assign_ipcc_land_use_category(
2752
2701
  has_long_fallow = _has_long_fallow(land_cover_nodes)
2753
2702
  has_wetland_soils = ipcc_soil_category is IpccSoilCategory.WETLAND_SOILS
2754
2703
 
2755
- should_run = bool(site_type)
2704
+ should_run = bool(land_cover_nodes)
2756
2705
 
2757
2706
  return next(
2758
2707
  (
2759
2708
  key for key in DECISION_TREE
2760
2709
  if DECISION_TREE[key](
2761
2710
  key=key,
2762
- site_type=site_type,
2763
2711
  land_cover_nodes=land_cover_nodes,
2764
2712
  has_long_fallow=has_long_fallow,
2765
2713
  has_irrigated_upland_rice=has_irrigated_upland_rice,
@@ -2783,7 +2731,7 @@ def _check_grassland_ipcc_management_category(
2783
2731
  ----------
2784
2732
  key : IpccManagementCategory
2785
2733
  The IPCC management category to check.
2786
- land_cover_nodes : List[dict]
2734
+ land_cover_nodes : list[dict]
2787
2735
  List of land cover nodes to be checked.
2788
2736
 
2789
2737
  Returns
@@ -2809,7 +2757,7 @@ def _check_tillage_ipcc_management_category(
2809
2757
  ----------
2810
2758
  key : IpccManagementCategory
2811
2759
  The IPCC management category to check.
2812
- tillage_nodes : List[dict]
2760
+ tillage_nodes : list[dict]
2813
2761
  List of tillage nodes to be checked.
2814
2762
 
2815
2763
  Returns
@@ -3554,7 +3502,7 @@ def _should_run(site: dict) -> tuple[bool, dict]:
3554
3502
  site_type = site.get("siteType", "")
3555
3503
  management_nodes = site.get("management", [])
3556
3504
  measurement_nodes = site.get("measurements", [])
3557
- cycles = _calculated_cycles(site.get("@id"))
3505
+ cycles = related_cycles(site.get("@id"))
3558
3506
 
3559
3507
  has_management = len(management_nodes) > 0
3560
3508
  has_measurements = len(measurement_nodes) > 0
@@ -3611,26 +3559,6 @@ def _should_run(site: dict) -> tuple[bool, dict]:
3611
3559
  return should_run_tier_1, should_run_tier_2, inventory, kwargs
3612
3560
 
3613
3561
 
3614
- def _calculated_cycles(site_id: str):
3615
- """
3616
- Get the list of `Cycle`s related to the `Site`. Gets the `recalculated` data if available, else `original`.
3617
-
3618
- Parameters
3619
- ----------
3620
- site_id : str
3621
- The `@id` of the `Site`.
3622
-
3623
- Returns
3624
- -------
3625
- list[dict]
3626
- The related `Cycle`s as `dict`.
3627
- """
3628
- nodes = find_related(SchemaType.SITE, site_id, SchemaType.CYCLE)
3629
- return list(
3630
- map(lambda node: _load_calculated_node(node, SchemaType.CYCLE), nodes or [])
3631
- )
3632
-
3633
-
3634
3562
  def _should_run_tier_1(
3635
3563
  inventory: dict,
3636
3564
  *,
@@ -3806,11 +3734,15 @@ def _should_run_inventory_year_tier_2(group: dict) -> bool:
3806
3734
  def _get_grouped_climate_measurements(grouped_measurements: dict) -> dict:
3807
3735
  return {
3808
3736
  year: {
3809
- _InventoryKey.TEMP_MONTHLY: find_term_match(measurements, TEMPERATURE_MONTHLY_TERM_ID, {}).get("value", []),
3810
- _InventoryKey.PRECIP_MONTHLY: (
3737
+ _InventoryKey.TEMP_MONTHLY: non_empty_list(
3738
+ find_term_match(measurements, TEMPERATURE_MONTHLY_TERM_ID, {}).get("value", [])
3739
+ ),
3740
+ _InventoryKey.PRECIP_MONTHLY: non_empty_list(
3811
3741
  find_term_match(measurements, PRECIPITATION_MONTHLY_TERM_ID, {}).get("value", [])
3812
3742
  ),
3813
- _InventoryKey.PET_MONTHLY: find_term_match(measurements, PET_MONTHLY_TERM_ID, {}).get("value", [])
3743
+ _InventoryKey.PET_MONTHLY: non_empty_list(
3744
+ find_term_match(measurements, PET_MONTHLY_TERM_ID, {}).get("value", [])
3745
+ )
3814
3746
  } for year, measurements in grouped_measurements.items()
3815
3747
  }
3816
3748
 
@@ -3940,15 +3872,19 @@ def _build_inventory_tier_1(
3940
3872
  eco_climate_zone = _get_eco_climate_zone(measurement_nodes)
3941
3873
  ipcc_soil_category = _assign_ipcc_soil_category(measurement_nodes)
3942
3874
  soc_ref = _retrieve_soc_ref(eco_climate_zone, ipcc_soil_category)
3875
+ grouped_management = group_nodes_by_year(management_nodes)
3876
+
3877
+ # If no `landCover` nodes in `site.management` use `site.siteType` to assign static `IpccLandUseCategory`
3878
+ run_with_site_type = len(filter_list_term_type(management_nodes, [TermTermType.LANDCOVER])) == 0
3879
+ site_type_ipcc_land_use_category = SITE_TYPE_TO_IPCC_LAND_USE_CATEGORY.get(site_type, IpccLandUseCategory.OTHER)
3943
3880
 
3944
3881
  grouped_management = group_nodes_by_year(management_nodes)
3945
3882
 
3946
3883
  grouped_land_use_categories = {
3947
3884
  year: {
3948
- _InventoryKey.LU_CATEGORY: _assign_ipcc_land_use_category(
3949
- site_type,
3950
- nodes,
3951
- ipcc_soil_category
3885
+ _InventoryKey.LU_CATEGORY: (
3886
+ site_type_ipcc_land_use_category if run_with_site_type
3887
+ else _assign_ipcc_land_use_category(nodes, ipcc_soil_category)
3952
3888
  )
3953
3889
  } for year, nodes in grouped_management.items()
3954
3890
  }
@@ -3986,7 +3922,8 @@ def _build_inventory_tier_1(
3986
3922
  kwargs = {
3987
3923
  "eco_climate_zone": eco_climate_zone,
3988
3924
  "ipcc_soil_category": ipcc_soil_category,
3989
- "soc_ref": soc_ref,
3925
+ "run_with_site_type": run_with_site_type,
3926
+ "soc_ref": soc_ref
3990
3927
  }
3991
3928
 
3992
3929
  return inventory, kwargs
@@ -8,7 +8,7 @@ from hestia_earth.schema import EmissionMethodTier
8
8
  from hestia_earth.utils.lookup import download_lookup, get_table_value, column_name
9
9
  from hestia_earth.utils.tools import flatten, list_sum
10
10
 
11
- from hestia_earth.models.log import debugValues, logRequirements, logShouldRun
11
+ from hestia_earth.models.log import debugValues, logRequirements, logShouldRun, log_as_table
12
12
  from hestia_earth.models.utils.emission import _new_emission
13
13
  from hestia_earth.models.utils.input import load_impacts
14
14
  from hestia_earth.models.utils.blank_node import group_by_keys
@@ -52,71 +52,99 @@ MODEL_AGGREGATED = 'hestiaAggregatedData'
52
52
  TIER = EmissionMethodTier.BACKGROUND.value
53
53
 
54
54
 
55
- def _emission(cycle: dict, term_id: str, value: float, input: dict, model: str):
56
- # log run on each emission so we know it did run
57
- input_term_id = input.get('term', {}).get('@id')
58
- operation_term_id = input.get('operation', {}).get('@id')
59
- animal_term_id = input.get('animal', {}).get('@id')
60
-
61
- logShouldRun(cycle, model, term_id, True, methodTier=TIER,
62
- input=input_term_id,
63
- operation=operation_term_id,
64
- animal=animal_term_id)
65
-
55
+ def _emission(model: str, term_id: str, value: float, input: dict, operation={}, animal={}):
66
56
  emission = _new_emission(term_id, model)
67
57
  emission['value'] = [value]
68
58
  emission['methodTier'] = TIER
69
- emission['inputs'] = [input.get('term')]
70
- if input.get('operation'):
71
- emission['operation'] = input.get('operation')
72
- if input.get('animal'):
73
- emission['animals'] = [input.get('animal')]
59
+ emission['inputs'] = [input]
60
+ if operation:
61
+ emission['operation'] = operation
62
+ if animal:
63
+ emission['animals'] = [animal]
74
64
  return emission
75
65
 
76
66
 
67
+ def _run_emission(cycle: dict, term_id: str, data: dict):
68
+ def run_input(values: dict):
69
+ value = values.get('value', 0)
70
+ term = values.get('term', {})
71
+ operation = values.get('operation', {})
72
+ animal = values.get('animal', {})
73
+ is_aggregated = any(values.get('aggregated', []))
74
+ model = MODEL_AGGREGATED if is_aggregated else MODEL
75
+
76
+ details = values.get('details', {})
77
+ logRequirements(cycle, model=model, term=term_id,
78
+ values=log_as_table([{'impact_assessment_id': key} | value for key, value in details.items()]))
79
+
80
+ logShouldRun(cycle, model, term_id, True, methodTier=TIER,
81
+ input=term.get('@id'),
82
+ operation=operation.get('@id'),
83
+ animal=animal.get('@id'))
84
+
85
+ return _emission(model, term_id, value, input=term, operation=operation, animal=animal)
86
+
87
+ return list(map(run_input, data.values()))
88
+
89
+
77
90
  def _emission_group(term_id: str):
78
91
  lookup = download_lookup('emission.csv', True)
79
92
  return get_table_value(lookup, 'termid', term_id, column_name('inputProductionGroupId'))
80
93
 
81
94
 
82
95
  def _group_emissions(impact: dict):
83
- def _group_by(prev: dict, emission: dict):
96
+ def _group_by(group: dict, emission: dict):
84
97
  term_id = emission.get('term', {}).get('@id')
85
98
  grouping = _emission_group(term_id)
86
99
  value = emission.get('value') or 0
87
100
  if grouping:
88
- prev[grouping] = prev.get(grouping, 0) + value
89
- return prev
101
+ group[grouping] = group.get(grouping, 0) + value
102
+ return group
90
103
 
91
104
  emissions = impact.get('emissionsResourceUse', [])
92
105
  return reduce(_group_by, emissions, {})
93
106
 
94
107
 
95
- def _run_input(cycle: dict):
96
- def run(inputs: list):
97
- input = inputs[0]
98
- input_value = list_sum(flatten(input.get('value', []) for input in inputs))
99
- term_id = input.get('term', {}).get('@id')
100
- impact = input.get('impactAssessment')
101
- model = MODEL_AGGREGATED if impact.get('aggregated', False) else MODEL
102
- emissions = _group_emissions(impact)
103
-
104
- logRequirements(cycle, model=model, term=term_id,
105
- impact_assessment_id=input.get('impactAssessment', {}).get('@id'),
106
- input_value=input_value)
107
- logShouldRun(cycle, model, term_id, True, methodTier=TIER)
108
-
109
- return [
110
- _emission(cycle, id, input_value * value, input, model) for id, value in emissions.items()
111
- ]
112
- return run
113
-
114
-
115
108
  def _animal_inputs(animal: dict):
116
109
  inputs = load_impacts(animal.get('inputs', []))
117
110
  return [(input | {'animal': animal.get('term', {})}) for input in inputs]
118
111
 
119
112
 
113
+ def _group_input_emissions(input: dict):
114
+ impact = input.get('impactAssessment')
115
+ emissions = _group_emissions(impact)
116
+ return input | {'emissions': emissions}
117
+
118
+
119
+ def _group_inputs(group: dict, values: tuple):
120
+ # input_group_key = 'group-id'
121
+ # inputs = [{'term': {}, 'value':[], 'impactAssessment': {}, 'emissions': {'co2ToAirInputsProduction': 10}}]
122
+ input_group_key, inputs = values
123
+ for input in inputs:
124
+ input_value = list_sum(input.get('value'))
125
+ emissions = input.get('emissions', {})
126
+ for emission_term_id, emission_value in emissions.items():
127
+ group[emission_term_id] = group.get(emission_term_id, {})
128
+
129
+ grouped_inputs = group[emission_term_id].get(input_group_key, {
130
+ 'term': input.get('term', {}),
131
+ 'operation': input.get('operation', {}),
132
+ 'animal': input.get('animal', {}),
133
+ 'value': 0,
134
+ 'aggregated': [],
135
+ 'details': {}
136
+ })
137
+ grouped_inputs['aggregated'].append(input.get('impactAssessment', {}).get('agregated', False))
138
+ grouped_inputs['value'] = grouped_inputs['value'] + (emission_value * input_value)
139
+ # for logging
140
+ grouped_inputs['details'][input.get('impactAssessment', {}).get('@id')] = {
141
+ 'emission_value': emission_value,
142
+ 'input_value': input_value
143
+ }
144
+ group[emission_term_id][input_group_key] = grouped_inputs
145
+ return group
146
+
147
+
120
148
  def run(_, cycle: dict):
121
149
  inputs = flatten(
122
150
  load_impacts(cycle.get('inputs', [])) +
@@ -124,9 +152,16 @@ def run(_, cycle: dict):
124
152
  )
125
153
  inputs = [i for i in inputs if list_sum(i.get('value', [])) > 0]
126
154
 
155
+ # group inputs with same term/operation/animal to avoid adding emissions twice
156
+ # inputs = {'group-id': [{'term': {},'value':[10],'impactAssessment': {}}]}
157
+ inputs = reduce(group_by_keys(['term', 'operation', 'animal']), inputs, {})
158
+ inputs = {key: list(map(_group_input_emissions, value)) for key, value in inputs.items()}
159
+
127
160
  debugValues(cycle, model=MODEL,
128
161
  nb_inputs=len(inputs))
129
162
 
130
- # group inputs with same id/operation to avoid adding emissions twice
131
- inputs = reduce(group_by_keys(['term', 'operation', 'animal']), inputs, {})
132
- return flatten(map(_run_input(cycle), inputs.values()))
163
+ # finally group everything by emission so we can log inputs together
164
+ # emissions = {'co2ToAirInputsProduct': {'group-id':{'term':{},'value':10,'details':{}}}}
165
+ emissions = reduce(_group_inputs, inputs.items(), {})
166
+
167
+ return flatten([_run_emission(cycle, term_id, data) for term_id, data in emissions.items()])
@@ -918,11 +918,11 @@
918
918
  },
919
919
  {
920
920
  "@type": "Term",
921
- "@id": "residueIncorporated"
921
+ "@id": "residueIncorporatedMoreThan30DaysBeforeCultivation"
922
922
  },
923
923
  {
924
924
  "@type": "Term",
925
- "@id": "residueIncorporatedMoreThan30DaysBeforeCultivation"
925
+ "@id": "residueIncorporated"
926
926
  },
927
927
  {
928
928
  "@type": "Term",
@@ -1299,7 +1299,7 @@
1299
1299
  "@type": "Term",
1300
1300
  "name": "Generic crop, seed",
1301
1301
  "@id": "genericCropSeed",
1302
- "_score": 23.9092
1302
+ "_score": 23.910892
1303
1303
  }
1304
1304
  ]
1305
1305
  },
@@ -1405,11 +1405,11 @@
1405
1405
  "results": [
1406
1406
  {
1407
1407
  "@type": "Term",
1408
- "@id": "waterMarine"
1408
+ "@id": "waterDrainageCanal"
1409
1409
  },
1410
1410
  {
1411
1411
  "@type": "Term",
1412
- "@id": "waterDrainageCanal"
1412
+ "@id": "waterMarine"
1413
1413
  },
1414
1414
  {
1415
1415
  "@type": "Term",
@@ -1532,45 +1532,6 @@
1532
1532
  }
1533
1533
  ]
1534
1534
  },
1535
- {
1536
- "name": "get_long_fallow_land_cover_terms",
1537
- "query": {
1538
- "bool": {
1539
- "must": [
1540
- {
1541
- "match": {
1542
- "@type": "Term"
1543
- }
1544
- },
1545
- {
1546
- "match": {
1547
- "termType.keyword": "landCover"
1548
- }
1549
- },
1550
- {
1551
- "match_phrase_prefix": {
1552
- "name": "long"
1553
- }
1554
- },
1555
- {
1556
- "match": {
1557
- "name": "fallow"
1558
- }
1559
- }
1560
- ]
1561
- }
1562
- },
1563
- "results": [
1564
- {
1565
- "@type": "Term",
1566
- "@id": "longFallow"
1567
- },
1568
- {
1569
- "@type": "Term",
1570
- "@id": "longBareFallow"
1571
- }
1572
- ]
1573
- },
1574
1535
  {
1575
1536
  "name": "get_milkYield_terms",
1576
1537
  "query": {
@@ -1740,15 +1701,15 @@
1740
1701
  },
1741
1702
  {
1742
1703
  "@type": "Term",
1743
- "@id": "fullInversionTillage"
1704
+ "@id": "fullTillage"
1744
1705
  },
1745
1706
  {
1746
1707
  "@type": "Term",
1747
- "@id": "fullTillage"
1708
+ "@id": "minimumTillage"
1748
1709
  },
1749
1710
  {
1750
1711
  "@type": "Term",
1751
- "@id": "minimumTillage"
1712
+ "@id": "fullInversionTillage"
1752
1713
  },
1753
1714
  {
1754
1715
  "@type": "Term",