hestia-earth-models 0.73.6__py3-none-any.whl → 0.73.8__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 (64) hide show
  1. hestia_earth/models/config/Cycle.json +116 -26
  2. hestia_earth/models/config/ImpactAssessment.json +239 -199
  3. hestia_earth/models/dammgen2009/noxToAirExcreta.py +11 -9
  4. hestia_earth/models/ecoalimV9/cycle.py +29 -39
  5. hestia_earth/models/ecoalimV9/impact_assessment.py +38 -40
  6. hestia_earth/models/ecoalimV9/utils.py +82 -16
  7. hestia_earth/models/ecoinventV3/__init__.py +3 -3
  8. hestia_earth/models/emepEea2019/n2OToAirFuelCombustionDirect.py +2 -2
  9. hestia_earth/models/hestia/default_emissions.py +2 -6
  10. hestia_earth/models/hestia/default_resourceUse.py +2 -5
  11. hestia_earth/models/hestia/landCover.py +3 -3
  12. hestia_earth/models/hestia/pastureSystem.py +1 -1
  13. hestia_earth/models/hestia/seed_emissions.py +7 -3
  14. hestia_earth/models/impact_assessment/emissions.py +3 -5
  15. hestia_earth/models/ipcc2019/biocharOrganicCarbonPerHa.py +9 -3
  16. hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +1 -5
  17. hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +1 -5
  18. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +1 -33
  19. hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +1 -5
  20. hestia_earth/models/ipcc2019/n2OToAirAquacultureSystemsIndirect.py +44 -0
  21. hestia_earth/models/ipcc2019/n2OToAirCropResidueBurningIndirect.py +43 -0
  22. hestia_earth/models/ipcc2019/n2OToAirCropResidueDecompositionIndirect.py +13 -70
  23. hestia_earth/models/ipcc2019/n2OToAirExcretaIndirect.py +13 -70
  24. hestia_earth/models/ipcc2019/n2OToAirFuelCombustionIndirect.py +43 -0
  25. hestia_earth/models/ipcc2019/n2OToAirInorganicFertiliserIndirect.py +13 -70
  26. hestia_earth/models/ipcc2019/n2OToAirNaturalVegetationBurningIndirect.py +43 -0
  27. hestia_earth/models/ipcc2019/n2OToAirOrganicFertiliserIndirect.py +13 -70
  28. hestia_earth/models/ipcc2019/n2OToAirOrganicSoilBurningIndirect.py +43 -0
  29. hestia_earth/models/ipcc2019/n2OToAirOrganicSoilCultivationIndirect.py +43 -0
  30. hestia_earth/models/ipcc2019/n2OToAir_indirect_emissions_utils.py +112 -0
  31. hestia_earth/models/ipcc2019/utils.py +0 -25
  32. hestia_earth/models/jarvisAndPain1994/n2ToAirExcreta.py +11 -9
  33. hestia_earth/models/linkedImpactAssessment/emissions.py +24 -15
  34. hestia_earth/models/linkedImpactAssessment/utils.py +5 -1
  35. hestia_earth/models/mocking/search-results.json +1284 -1284
  36. hestia_earth/models/utils/background_emissions.py +17 -10
  37. hestia_earth/models/utils/emission.py +18 -8
  38. hestia_earth/models/utils/impact_assessment.py +3 -3
  39. hestia_earth/models/utils/indicator.py +8 -1
  40. hestia_earth/models/utils/lookup.py +38 -21
  41. hestia_earth/models/utils/productivity.py +1 -1
  42. hestia_earth/models/version.py +1 -1
  43. hestia_earth/orchestrator/strategies/merge/merge_list.py +41 -54
  44. {hestia_earth_models-0.73.6.dist-info → hestia_earth_models-0.73.8.dist-info}/METADATA +3 -3
  45. {hestia_earth_models-0.73.6.dist-info → hestia_earth_models-0.73.8.dist-info}/RECORD +64 -49
  46. tests/models/dammgen2009/test_noxToAirExcreta.py +2 -2
  47. tests/models/ecoalimV9/test_cycle.py +1 -1
  48. tests/models/ecoalimV9/test_impact_assessment.py +1 -1
  49. tests/models/ecoalimV9/test_utils.py +13 -0
  50. tests/models/ipcc2019/test_biocharOrganicCarbonPerHa.py +2 -1
  51. tests/models/ipcc2019/test_n2OToAirAquacultureSystemsIndirect.py +45 -0
  52. tests/models/ipcc2019/test_n2OToAirCropResidueBurningIndirect.py +45 -0
  53. tests/models/ipcc2019/test_n2OToAirCropResidueDecompositionIndirect.py +6 -32
  54. tests/models/ipcc2019/test_n2OToAirExcretaIndirect.py +6 -32
  55. tests/models/ipcc2019/test_n2OToAirFuelCombustionIndirect.py +45 -0
  56. tests/models/ipcc2019/test_n2OToAirInorganicFertiliserIndirect.py +6 -32
  57. tests/models/ipcc2019/test_n2OToAirNaturalVegetationBurningIndirect.py +45 -0
  58. tests/models/ipcc2019/test_n2OToAirOrganicFertiliserIndirect.py +6 -32
  59. tests/models/ipcc2019/test_n2OToAirOrganicSoilBurningIndirect.py +45 -0
  60. tests/models/ipcc2019/test_n2OToAirOrganicSoilCultivationIndirect.py +45 -0
  61. tests/models/ipcc2019/test_n2OToAir_indirect_emissions_utils.py +19 -0
  62. {hestia_earth_models-0.73.6.dist-info → hestia_earth_models-0.73.8.dist-info}/LICENSE +0 -0
  63. {hestia_earth_models-0.73.6.dist-info → hestia_earth_models-0.73.8.dist-info}/WHEEL +0 -0
  64. {hestia_earth_models-0.73.6.dist-info → hestia_earth_models-0.73.8.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,9 @@
1
1
  from hestia_earth.schema import TermTermType
2
2
  from hestia_earth.utils.model import find_term_match, filter_list_term_type
3
- from hestia_earth.utils.tools import flatten
3
+ from hestia_earth.utils.tools import flatten, non_empty_list
4
4
  from hestia_earth.utils.emission import cycle_emissions_in_system_boundary
5
5
 
6
- from hestia_earth.models.log import logShouldRun
6
+ from hestia_earth.models.log import logShouldRun, debugValues
7
7
  from . import is_from_model
8
8
  from .term import get_lookup_value
9
9
 
@@ -58,23 +58,30 @@ def no_gap_filled_background_emissions(
58
58
  return check_input
59
59
 
60
60
 
61
- def all_background_emission_term_ids(cycle: dict):
62
- term_ids = cycle_emissions_in_system_boundary(cycle)
61
+ def all_background_emission_term_ids(node: dict, termType: TermTermType):
62
+ term_ids = cycle_emissions_in_system_boundary(node, termType=termType)
63
63
  return list(set([
64
- get_lookup_value({'termType': TermTermType.EMISSION.value, '@id': term_id}, 'inputProductionGroupId')
64
+ get_lookup_value({'termType': termType.value, '@id': term_id}, 'inputProductionGroupId')
65
65
  for term_id in term_ids
66
66
  ]))
67
67
 
68
68
 
69
- def log_missing_emissions(cycle: dict, **log_args):
70
- all_emission_term_ids = all_background_emission_term_ids(cycle)
69
+ def log_missing_emissions(node: dict, termType: TermTermType = TermTermType.EMISSION, **log_args):
70
+ all_emission_term_ids = all_background_emission_term_ids(node, termType)
71
71
 
72
72
  def log_input(input_term_id: str, included_emission_term_ids: list, **extra_log_args):
73
- missing_emission_term_ids = [
73
+ missing_emission_term_ids = non_empty_list([
74
74
  term_id for term_id in all_emission_term_ids if term_id not in included_emission_term_ids
75
- ]
75
+ ])
76
76
  for emission_id in missing_emission_term_ids:
77
- logShouldRun(cycle, term=input_term_id, should_run=False, emission_id=emission_id,
77
+ # debug value on the emission itself so it appears for the input
78
+ debugValues(node, term=emission_id,
79
+ value=None,
80
+ coefficient=None,
81
+ input=input_term_id,
82
+ **log_args,
83
+ **extra_log_args)
84
+ logShouldRun(node, term=input_term_id, should_run=False, emission_id=emission_id,
78
85
  **log_args,
79
86
  **extra_log_args)
80
87
  return log_input
@@ -2,7 +2,7 @@ from collections.abc import Iterable
2
2
  from typing import Optional, Union
3
3
  from hestia_earth.schema import EmissionMethodTier, SchemaType, TermTermType
4
4
  from hestia_earth.utils.model import linked_node
5
-
5
+ from hestia_earth.utils.emission import cycle_emissions_in_system_boundary, emissions_in_system_boundary
6
6
 
7
7
  from . import flatten_args
8
8
  from .term import download_term
@@ -13,20 +13,22 @@ from .constant import Units, get_atomic_conversion
13
13
  EMISSION_METHOD_TIERS = [e.value for e in EmissionMethodTier]
14
14
 
15
15
 
16
- def _new_emission(term, model=None):
16
+ def _new_emission(term, model=None, country_id: str = None, key_id: str = None):
17
17
  node = {'@type': SchemaType.EMISSION.value}
18
18
  node['term'] = linked_node(term if isinstance(term, dict) else download_term(term, TermTermType.EMISSION))
19
+ if country_id:
20
+ node['country'] = linked_node(download_term(country_id, TermTermType.REGION))
21
+ if key_id:
22
+ node['key'] = linked_node(download_term(key_id))
19
23
  return include_methodModel(node, model)
20
24
 
21
25
 
22
- def get_nh3_no3_nox_to_n(cycle: dict, nh3_term_id: str, no3_term_id: str, nox_term_id: str, allow_none: bool = False):
23
- default_value = 0 if allow_none else None
24
-
25
- nh3 = find_terms_value(cycle.get('emissions', []), nh3_term_id, default=default_value)
26
+ def get_nh3_no3_nox_to_n(cycle: dict, nh3_term_id: str = None, no3_term_id: str = None, nox_term_id: str = None):
27
+ nh3 = find_terms_value(cycle.get('emissions', []), nh3_term_id, default=None)
26
28
  nh3 = None if nh3 is None else nh3 / get_atomic_conversion(Units.KG_NH3, Units.TO_N)
27
- no3 = find_terms_value(cycle.get('emissions', []), no3_term_id, default=default_value)
29
+ no3 = find_terms_value(cycle.get('emissions', []), no3_term_id, default=None)
28
30
  no3 = None if no3 is None else no3 / get_atomic_conversion(Units.KG_NO3, Units.TO_N)
29
- nox = find_terms_value(cycle.get('emissions', []), nox_term_id, default=default_value)
31
+ nox = find_terms_value(cycle.get('emissions', []), nox_term_id, default=None)
30
32
  nox = None if nox is None else nox / get_atomic_conversion(Units.KG_NOX, Units.TO_N)
31
33
 
32
34
  return (nh3, no3, nox)
@@ -100,3 +102,11 @@ def to_emission_method_tier(method: Union[EmissionMethodTier, str]) -> Optional[
100
102
  def filter_emission_inputs(emission: dict, term_type: TermTermType):
101
103
  inputs = emission.get('inputs', [])
102
104
  return [i for i in inputs if i.get('termType') == term_type.value]
105
+
106
+
107
+ def background_emissions_in_system_boundary(node: dict, term_type: TermTermType = TermTermType.EMISSION):
108
+ term_ids = (
109
+ cycle_emissions_in_system_boundary(node, term_type) if term_type == TermTermType.EMISSION else
110
+ emissions_in_system_boundary(term_type)
111
+ )
112
+ return [id for id in term_ids if 'InputsProduction' in id]
@@ -4,7 +4,7 @@ from hestia_earth.utils.model import filter_list_term_type
4
4
  from hestia_earth.utils.tools import list_sum, safe_parse_date
5
5
 
6
6
  from hestia_earth.models.log import debugValues
7
- from .lookup import all_factor_value, _region_factor_value, _aware_factor_value, fallback_country
7
+ from .lookup import all_factor_value, region_factor_value, aware_factor_value, fallback_country
8
8
  from .product import find_by_product
9
9
  from .site import region_level_1_id
10
10
 
@@ -190,7 +190,7 @@ def impact_country_value(
190
190
  blank_nodes=blank_nodes,
191
191
  grouped_key=group_key,
192
192
  default_no_values=default_no_values,
193
- factor_value_func=_region_factor_value
193
+ factor_value_func=region_factor_value
194
194
  )
195
195
 
196
196
 
@@ -231,7 +231,7 @@ def impact_aware_value(model: str, term_id: str, impact: dict, lookup: str, grou
231
231
  blank_nodes=blank_nodes,
232
232
  grouped_key=group_key,
233
233
  default_no_values=None,
234
- factor_value_func=_aware_factor_value
234
+ factor_value_func=aware_factor_value
235
235
  )
236
236
 
237
237
 
@@ -5,7 +5,10 @@ from .method import include_methodModel
5
5
  from .term import download_term
6
6
 
7
7
 
8
- def _new_indicator(term, model=None, land_cover_id: str = None, previous_land_cover_id: str = None):
8
+ def _new_indicator(
9
+ term: dict, model=None,
10
+ land_cover_id: str = None, previous_land_cover_id: str = None, country_id: str = None, key_id: str = None
11
+ ):
9
12
  node = {'@type': SchemaType.INDICATOR.value}
10
13
  node['term'] = linked_node(term if isinstance(term, dict) else download_term(
11
14
  term, TermTermType.CHARACTERISEDINDICATOR)
@@ -14,4 +17,8 @@ def _new_indicator(term, model=None, land_cover_id: str = None, previous_land_co
14
17
  node['landCover'] = linked_node(download_term(land_cover_id, TermTermType.LANDCOVER))
15
18
  if previous_land_cover_id:
16
19
  node['previousLandCover'] = linked_node(download_term(previous_land_cover_id, TermTermType.LANDCOVER))
20
+ if country_id:
21
+ node['country'] = linked_node(download_term(country_id, TermTermType.REGION))
22
+ if key_id:
23
+ node['key'] = linked_node(download_term(key_id))
17
24
  return include_methodModel(node, model)
@@ -1,4 +1,4 @@
1
- import functools
1
+ from functools import lru_cache
2
2
  from typing import Optional, List
3
3
  from hestia_earth.utils.lookup import (
4
4
  download_lookup,
@@ -19,16 +19,20 @@ def _node_value(node):
19
19
 
20
20
 
21
21
  def _factor_value(model: str, term_id: str, lookup_name: str, lookup_col: str, grouped_key: Optional[str] = None):
22
- def get_value(data: dict):
23
- node_term_id = data.get('term', {}).get('@id')
24
- grouped_data_key = grouped_key or data.get('methodModel', {}).get('@id')
25
- value = _node_value(data)
22
+ @lru_cache()
23
+ def get_coefficient(node_term_id: str, grouped_data_key: str):
26
24
  coefficient = get_region_lookup_value(lookup_name, node_term_id, lookup_col, model=model, term=term_id)
27
25
  # value is either a number or matching between a model and a value (restrict value to specific model only)
28
- coefficient = safe_parse_float(
26
+ return safe_parse_float(
29
27
  extract_grouped_data(coefficient, grouped_data_key),
30
28
  default=None
31
29
  ) if ':' in str(coefficient) else safe_parse_float(coefficient, default=None)
30
+
31
+ def get_value(data: dict):
32
+ node_term_id = data.get('term', {}).get('@id')
33
+ grouped_data_key = grouped_key or data.get('methodModel', {}).get('@id')
34
+ value = _node_value(data)
35
+ coefficient = get_coefficient(node_term_id, grouped_data_key)
32
36
  if value is not None and coefficient is not None:
33
37
  if model:
34
38
  debugValues(data, model=model, term=term_id,
@@ -40,7 +44,15 @@ def _factor_value(model: str, term_id: str, lookup_name: str, lookup_col: str, g
40
44
  return get_value
41
45
 
42
46
 
43
- def _region_factor_value(model: str, term_id: str, lookup_name: str, lookup_term_id: str, group_key: str = None):
47
+ def region_factor_value(model: str, term_id: str, lookup_name: str, lookup_term_id: str, group_key: str = None):
48
+ @lru_cache()
49
+ def get_coefficient(node_term_id: str, region_term_id: str):
50
+ coefficient = get_region_lookup_value(lookup_name, region_term_id, node_term_id, model=model, term=term_id)
51
+ return safe_parse_float(
52
+ extract_grouped_data(coefficient, group_key) if group_key else coefficient,
53
+ default=None
54
+ )
55
+
44
56
  def get_value(data: dict):
45
57
  node_term_id = data.get('term', {}).get('@id')
46
58
  value = _node_value(data)
@@ -48,11 +60,7 @@ def _region_factor_value(model: str, term_id: str, lookup_name: str, lookup_term
48
60
  region_term_id = (
49
61
  (data.get('region') or data.get('country') or {'@id': lookup_term_id}).get('@id')
50
62
  ) if lookup_term_id.startswith('GADM-') else lookup_term_id
51
- coefficient = get_region_lookup_value(lookup_name, region_term_id, node_term_id, model=model, term=term_id)
52
- coefficient = safe_parse_float(
53
- extract_grouped_data(coefficient, group_key) if group_key else coefficient,
54
- default=None
55
- )
63
+ coefficient = get_coefficient(node_term_id, region_term_id)
56
64
  if value is not None and coefficient is not None:
57
65
  debugValues(data, model=model, term=term_id,
58
66
  node=node_term_id,
@@ -62,20 +70,24 @@ def _region_factor_value(model: str, term_id: str, lookup_name: str, lookup_term
62
70
  return get_value
63
71
 
64
72
 
65
- def _aware_factor_value(model: str, term_id: str, lookup_name: str, aware_id: str, group_key: str = None):
73
+ def aware_factor_value(model: str, term_id: str, lookup_name: str, aware_id: str, group_key: str = None):
66
74
  lookup = download_lookup(lookup_name, False) # avoid saving in memory as there could be many different files used
67
75
  lookup_col = column_name('awareWaterBasinId')
68
76
 
77
+ @lru_cache()
78
+ def get_coefficient(node_term_id: str):
79
+ coefficient = _get_single_table_value(lookup, lookup_col, int(aware_id), column_name(node_term_id))
80
+ return safe_parse_float(
81
+ extract_grouped_data(coefficient, group_key),
82
+ default=None
83
+ ) if group_key else coefficient
84
+
69
85
  def get_value(data: dict):
70
86
  node_term_id = data.get('term', {}).get('@id')
71
87
  value = _node_value(data)
72
88
 
73
89
  try:
74
- coefficient = _get_single_table_value(lookup, lookup_col, int(aware_id), column_name(node_term_id))
75
- coefficient = safe_parse_float(
76
- extract_grouped_data(coefficient, group_key),
77
- default=None
78
- ) if group_key else coefficient
90
+ coefficient = get_coefficient(node_term_id)
79
91
  if value is not None and coefficient is not None:
80
92
  debugValues(data, model=model, term=term_id,
81
93
  node=node_term_id,
@@ -159,13 +171,18 @@ def fallback_country(country_id: str, lookups: List[str]) -> str:
159
171
  return country_id if country_id and is_in_lookup(country_id) else fallback_id if is_in_lookup(fallback_id) else None
160
172
 
161
173
 
162
- @functools.cache
163
- def get_region_lookup_value(lookup_name: str, term_id: str, column: str, **log_args):
174
+ def get_region_lookup(lookup_name: str, term_id: str):
164
175
  # for performance, try to load the region specific lookup if exists
165
- lookup = (
176
+ return (
166
177
  download_lookup(lookup_name.replace('region-', f"{term_id}-"))
167
178
  if lookup_name and lookup_name.startswith('region-') else None
168
179
  ) or download_lookup(lookup_name)
180
+
181
+
182
+ @lru_cache()
183
+ def get_region_lookup_value(lookup_name: str, term_id: str, column: str, **log_args):
184
+ # for performance, try to load the region specific lookup if exists
185
+ lookup = get_region_lookup(lookup_name, term_id)
169
186
  value = get_table_value(lookup, 'termid', term_id, column_name(column))
170
187
  debugMissingLookup(lookup_name, 'termid', term_id, column, value, **log_args)
171
188
  return value
@@ -17,5 +17,5 @@ PRODUCTIVITY_KEY = {
17
17
 
18
18
 
19
19
  def get_productivity(country: dict, default: PRODUCTIVITY = PRODUCTIVITY.HIGH):
20
- hdi = safe_parse_float(get_lookup_value(country, 'hdi'), default=None)
20
+ hdi = safe_parse_float(get_lookup_value(country, 'HDI'), default=None)
21
21
  return next((key for key in PRODUCTIVITY_KEY if hdi and PRODUCTIVITY_KEY[key](hdi)), default)
@@ -1 +1 @@
1
- VERSION = '0.73.6'
1
+ VERSION = '0.73.8'
@@ -1,7 +1,7 @@
1
1
  import pydash
2
2
  from datetime import datetime
3
3
  from hestia_earth.schema import UNIQUENESS_FIELDS
4
- from hestia_earth.utils.tools import safe_parse_date
4
+ from hestia_earth.utils.tools import safe_parse_date, flatten
5
5
 
6
6
  from hestia_earth.orchestrator.utils import _non_empty_list, update_node_version
7
7
  from .merge_node import merge as merge_node
@@ -27,39 +27,6 @@ def _has_property(value: dict, key: str):
27
27
  def _values_have_property(values: list, key: str): return any([_has_property(v, key) for v in values])
28
28
 
29
29
 
30
- def _match_list_el(source: list, dest: list, key: str):
31
- src_value = sorted(_non_empty_list([pydash.objects.get(x, key) for x in source]))
32
- dest_value = sorted(_non_empty_list([pydash.objects.get(x, key) for x in dest]))
33
- return src_value == dest_value
34
-
35
-
36
- def _get_value(data: dict, key: str, merge_args: dict = {}):
37
- value = pydash.objects.get(data, key)
38
- date = safe_parse_date(value) if key in ['startDate', 'endDate'] else None
39
- return datetime.strftime(date, merge_args.get('matchDatesFormat', '%Y-%m-%d')) if date else value
40
-
41
-
42
- def _match_el(source: dict, dest: dict, keys: list, merge_args: dict = {}):
43
- def match(key: str):
44
- keys = key.split('.')
45
- src_value = _get_value(source, key, merge_args)
46
- dest_value = _get_value(dest, key, merge_args)
47
- is_list = len(keys) >= 2 and (
48
- isinstance(pydash.objects.get(source, keys[0]), list) or
49
- isinstance(pydash.objects.get(dest, keys[0]), list)
50
- )
51
- return _match_list_el(
52
- pydash.objects.get(source, keys[0], []),
53
- pydash.objects.get(dest, keys[0], []),
54
- '.'.join(keys[1:])
55
- ) if is_list else src_value == dest_value
56
-
57
- source_properties = [p for p in keys if _has_property(source, p)]
58
- dest_properties = [p for p in keys if _has_property(dest, p)]
59
-
60
- return all(map(match, source_properties)) if source_properties == dest_properties else False
61
-
62
-
63
30
  def _handle_local_property(values: list, properties: list, local_id: str):
64
31
  # Handle "impactAssessment.@id" if present in the data
65
32
  existing_id = local_id.replace('.id', '.@id')
@@ -76,38 +43,58 @@ def _handle_local_property(values: list, properties: list, local_id: str):
76
43
  return properties
77
44
 
78
45
 
79
- def _find_match_el_index(values: list, el: dict, same_methodModel: bool, model: dict, node_type: str, merge_args: dict):
80
- """
81
- Find an element in the values that match the new element, based on the unique properties.
82
- To find a matching element:
46
+ def _get_value(data: dict, key: str, merge_args: dict = {}):
47
+ value = pydash.objects.get(data, key)
48
+ date = safe_parse_date(value) if key in ['startDate', 'endDate'] else None
49
+ return datetime.strftime(date, merge_args.get('matchDatesFormat', '%Y-%m-%d')) if date else value
50
+
51
+
52
+ def _value_index_key(value: dict, properties: list, merge_args: dict = {}):
53
+ def property_value(key: str):
54
+ keys = key.split('.')
55
+ prop_value = _get_value(value, key, merge_args)
56
+ is_list = len(keys) >= 2 and isinstance(pydash.objects.get(value, keys[0]), list)
57
+ return sorted(_non_empty_list([
58
+ pydash.objects.get(x, '.'.join(keys[1:]))
59
+ for x in pydash.objects.get(value, keys[0], [])
60
+ ])) if is_list else prop_value
61
+
62
+ source_properties = [p for p in properties if _has_property(value, p)]
63
+ return '-'.join(map(str, flatten(map(property_value, source_properties))))
64
+
65
+
66
+ def _build_matching_properties(values: list, model: dict = {}, merge_args: dict = {}, node_type: str = ''):
67
+ # only merge node if it has the same `methodModel`
68
+ same_methodModel = merge_args.get('sameMethodModel', False)
83
69
 
84
- 1. Update list of properties to handle `methodModel.@id` and `impactAssessment.@id`
85
- 2. Filter values that have the same unique properties as el
86
- 3. Make sure all shared unique properties are identical
87
- """
88
70
  properties = _matching_properties(model, node_type)
89
71
  properties = list(set(properties + [_METHOD_MODEL_KEY])) if same_methodModel else [
90
72
  p for p in properties if p != _METHOD_MODEL_KEY
91
73
  ]
92
- properties = _handle_local_property(values, properties, 'impactAssessment.id')
74
+ return _handle_local_property(values, properties, 'impactAssessment.id')
93
75
 
94
- return next(
95
- (i for i in range(len(values)) if _match_el(values[i], el, properties, merge_args)),
96
- None
97
- ) if properties else None
98
76
 
77
+ def merge(source: list, new_values: list, version: str, model: dict = {}, merge_args: dict = {}, node_type: str = ''):
78
+ source = [] if source is None else source
99
79
 
100
- def merge(source: list, merge_with: list, version: str, model: dict = {}, merge_args: dict = {}, node_type: str = ''):
101
- source = source if source is not None else []
102
-
103
- # only merge node if it has the same `methodModel`
104
- same_methodModel = merge_args.get('sameMethodModel', False)
105
80
  # only merge if the
106
81
  skip_same_term = merge_args.get('skipSameTerm', False)
107
82
 
108
- for el in _non_empty_list(merge_with):
109
- source_index = _find_match_el_index(source, el, same_methodModel, model, node_type, merge_args)
83
+ # build list of properties used to do the matching
84
+ properties = _build_matching_properties(source, model, merge_args, node_type)
85
+
86
+ source_index_keys = {
87
+ _value_index_key(value, properties, merge_args): index
88
+ for index, value in enumerate(source)
89
+ } if properties else None
90
+
91
+ for el in _non_empty_list(new_values):
92
+ new_value_index_key = _value_index_key(el, properties, merge_args)
93
+ source_index = source_index_keys.get(new_value_index_key) if source_index_keys else None
110
94
  if source_index is None:
95
+ # add to index keys for next elements
96
+ if source_index_keys:
97
+ source_index_keys[new_value_index_key] = len(source)
111
98
  source.append(update_node_version(version, el))
112
99
  elif not skip_same_term:
113
100
  source[source_index] = merge_node(source[source_index], el, version, model, merge_args)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: hestia-earth-models
3
- Version: 0.73.6
3
+ Version: 0.73.8
4
4
  Summary: HESTIA's set of modules for filling gaps in the activity data using external datasets (e.g. populating soil properties with a geospatial dataset using provided coordinates) and internal lookups (e.g. populating machinery use from fuel use). Includes rules for when gaps should be filled versus not (e.g. never gap fill yield, gap fill crop residue if yield provided etc.).
5
5
  Home-page: https://gitlab.com/hestia-earth/hestia-engine-models
6
6
  Author: HESTIA Team
@@ -11,8 +11,8 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
11
11
  Classifier: Programming Language :: Python :: 3.6
12
12
  Description-Content-Type: text/markdown
13
13
  License-File: LICENSE
14
- Requires-Dist: hestia-earth-schema==33.*
15
- Requires-Dist: hestia-earth-utils>=0.14.9
14
+ Requires-Dist: hestia-earth-schema<34.0.0,>=33.4.0
15
+ Requires-Dist: hestia-earth-utils>=0.15.1
16
16
  Requires-Dist: python-dateutil>=2.8.1
17
17
  Requires-Dist: CurrencyConverter==0.16.8
18
18
  Requires-Dist: haversine>=2.7.0