hestia-earth-models 0.73.7__py3-none-any.whl → 0.74.0__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.
- hestia_earth/models/aware/scarcityWeightedWaterUse.py +7 -6
- hestia_earth/models/aware2_0/__init__.py +14 -0
- hestia_earth/models/aware2_0/scarcityWeightedWaterUse.py +115 -0
- hestia_earth/models/config/Cycle.json +121 -29
- hestia_earth/models/config/ImpactAssessment.json +240 -200
- hestia_earth/models/config/__init__.py +26 -2
- hestia_earth/models/cycle/animal/input/hestiaAggregatedData.py +2 -2
- hestia_earth/models/cycle/animal/input/properties.py +6 -5
- hestia_earth/models/cycle/animal/milkYield.py +8 -3
- hestia_earth/models/cycle/utils.py +6 -6
- hestia_earth/models/dammgen2009/noxToAirExcreta.py +11 -9
- hestia_earth/models/data/ecoinventV3/__init__.py +8 -26
- hestia_earth/models/ecoalimV9/cycle.py +51 -45
- hestia_earth/models/ecoalimV9/impact_assessment.py +63 -45
- hestia_earth/models/ecoalimV9/utils.py +21 -15
- hestia_earth/models/ecoinventV3/__init__.py +8 -140
- hestia_earth/models/ecoinventV3/cycle.py +140 -0
- hestia_earth/models/ecoinventV3/utils.py +28 -1
- hestia_earth/models/ecoinventV3AndEmberClimate/__init__.py +8 -137
- hestia_earth/models/ecoinventV3AndEmberClimate/cycle.py +144 -0
- hestia_earth/models/emepEea2019/n2OToAirFuelCombustionDirect.py +2 -2
- hestia_earth/models/emepEea2019/utils.py +2 -3
- hestia_earth/models/environmentalFootprintV3_1/environmentalFootprintSingleOverallScore.py +5 -7
- hestia_earth/models/frischknechtEtAl2000/ionisingRadiationKbqU235Eq.py +41 -43
- hestia_earth/models/geospatialDatabase/awareWaterBasinId.py +2 -2
- hestia_earth/models/geospatialDatabase/awareWaterBasinId_v1.py +45 -0
- hestia_earth/models/hestia/default_emissions.py +7 -7
- hestia_earth/models/hestia/default_resourceUse.py +7 -6
- hestia_earth/models/hestia/landCover.py +110 -12
- hestia_earth/models/hestia/seed_emissions.py +7 -3
- hestia_earth/models/hestia/utils.py +1 -0
- hestia_earth/models/hestia/waterSalinity.py +2 -3
- hestia_earth/models/impact_assessment/emissions.py +3 -5
- hestia_earth/models/ipcc2019/biocharOrganicCarbonPerHa.py +9 -3
- hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +1 -5
- hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +1 -5
- hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +1 -33
- hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +1 -5
- hestia_earth/models/ipcc2019/n2OToAirAquacultureSystemsIndirect.py +44 -0
- hestia_earth/models/ipcc2019/n2OToAirCropResidueBurningIndirect.py +43 -0
- hestia_earth/models/ipcc2019/n2OToAirCropResidueDecompositionIndirect.py +13 -70
- hestia_earth/models/ipcc2019/n2OToAirExcretaIndirect.py +13 -70
- hestia_earth/models/ipcc2019/n2OToAirFuelCombustionIndirect.py +43 -0
- hestia_earth/models/ipcc2019/n2OToAirInorganicFertiliserIndirect.py +13 -70
- hestia_earth/models/ipcc2019/n2OToAirNaturalVegetationBurningIndirect.py +43 -0
- hestia_earth/models/ipcc2019/n2OToAirOrganicFertiliserIndirect.py +13 -70
- hestia_earth/models/ipcc2019/n2OToAirOrganicSoilBurningIndirect.py +43 -0
- hestia_earth/models/ipcc2019/n2OToAirOrganicSoilCultivationIndirect.py +43 -0
- hestia_earth/models/ipcc2019/n2OToAir_indirect_emissions_utils.py +112 -0
- hestia_earth/models/ipcc2019/utils.py +0 -25
- hestia_earth/models/jarvisAndPain1994/n2ToAirExcreta.py +11 -9
- hestia_earth/models/linkedImpactAssessment/emissions.py +25 -16
- hestia_earth/models/linkedImpactAssessment/utils.py +5 -1
- hestia_earth/models/log.py +8 -3
- hestia_earth/models/mocking/search-results.json +1670 -1666
- hestia_earth/models/utils/__init__.py +3 -0
- hestia_earth/models/utils/background_emissions.py +121 -14
- hestia_earth/models/utils/blank_node.py +1 -11
- hestia_earth/models/utils/emission.py +18 -8
- hestia_earth/models/utils/feedipedia.py +2 -2
- hestia_earth/models/utils/impact_assessment.py +4 -6
- hestia_earth/models/utils/indicator.py +8 -1
- hestia_earth/models/utils/lookup.py +30 -18
- hestia_earth/models/utils/productivity.py +1 -1
- hestia_earth/models/version.py +1 -1
- hestia_earth/orchestrator/log.py +8 -3
- hestia_earth/orchestrator/strategies/merge/merge_list.py +41 -54
- {hestia_earth_models-0.73.7.dist-info → hestia_earth_models-0.74.0.dist-info}/METADATA +3 -3
- {hestia_earth_models-0.73.7.dist-info → hestia_earth_models-0.74.0.dist-info}/RECORD +99 -75
- tests/models/aware2_0/__init__.py +0 -0
- tests/models/aware2_0/test_scarcityWeightedWaterUse.py +58 -0
- tests/models/dammgen2009/test_noxToAirExcreta.py +2 -2
- tests/models/ecoalimV9/test_cycle.py +1 -1
- tests/models/ecoalimV9/test_impact_assessment.py +1 -1
- tests/models/ecoinventV3/__init__.py +0 -0
- tests/models/{test_ecoinventV3.py → ecoinventV3/test_cycle.py} +5 -5
- tests/models/ecoinventV3AndEmberClimate/__init__.py +0 -0
- tests/models/{test_ecoinventV3AndEmberClimate.py → ecoinventV3AndEmberClimate/test_cycle.py} +6 -4
- tests/models/environmentalFootprintV3_1/test_environmentalFootprintSingleOverallScore.py +2 -2
- tests/models/frischknechtEtAl2000/test_ionisingRadiationKbqU235Eq.py +18 -27
- tests/models/hestia/test_landCover.py +16 -6
- tests/models/ipcc2019/test_biocharOrganicCarbonPerHa.py +2 -1
- tests/models/ipcc2019/test_n2OToAirAquacultureSystemsIndirect.py +45 -0
- tests/models/ipcc2019/test_n2OToAirCropResidueBurningIndirect.py +45 -0
- tests/models/ipcc2019/test_n2OToAirCropResidueDecompositionIndirect.py +6 -32
- tests/models/ipcc2019/test_n2OToAirExcretaIndirect.py +6 -32
- tests/models/ipcc2019/test_n2OToAirFuelCombustionIndirect.py +45 -0
- tests/models/ipcc2019/test_n2OToAirInorganicFertiliserIndirect.py +6 -32
- tests/models/ipcc2019/test_n2OToAirNaturalVegetationBurningIndirect.py +45 -0
- tests/models/ipcc2019/test_n2OToAirOrganicFertiliserIndirect.py +6 -32
- tests/models/ipcc2019/test_n2OToAirOrganicSoilBurningIndirect.py +45 -0
- tests/models/ipcc2019/test_n2OToAirOrganicSoilCultivationIndirect.py +45 -0
- tests/models/ipcc2019/test_n2OToAir_indirect_emissions_utils.py +19 -0
- tests/models/site/pre_checks/test_cache_geospatialDatabase.py +4 -4
- tests/models/test_config.py +53 -7
- tests/models/utils/test_background_emissions.py +13 -0
- {hestia_earth_models-0.73.7.dist-info → hestia_earth_models-0.74.0.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.73.7.dist-info → hestia_earth_models-0.74.0.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.73.7.dist-info → hestia_earth_models-0.74.0.dist-info}/top_level.txt +0 -0
|
@@ -43,6 +43,9 @@ def _omit(values: dict, keys: list) -> dict: return {k: v for k, v in values.ite
|
|
|
43
43
|
def _include(value: dict, keys: list) -> dict: return {k: v for k, v in value.items() if k in keys}
|
|
44
44
|
|
|
45
45
|
|
|
46
|
+
def unique_values(values: list, key='@id'): return list({v[key]: v for v in values}.values())
|
|
47
|
+
|
|
48
|
+
|
|
46
49
|
def _run_in_serie(data: dict, models: list): return reduce(lambda prev, model: model(prev), models, data)
|
|
47
50
|
|
|
48
51
|
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
from
|
|
1
|
+
from functools import reduce
|
|
2
|
+
from typing import Callable, Tuple
|
|
3
|
+
from hestia_earth.schema import TermTermType, EmissionMethodTier
|
|
4
|
+
from hestia_earth.utils.lookup import _is_missing_value, lookup_columns
|
|
2
5
|
from hestia_earth.utils.model import find_term_match, filter_list_term_type
|
|
3
|
-
from hestia_earth.utils.tools import flatten
|
|
6
|
+
from hestia_earth.utils.tools import flatten, non_empty_list, safe_parse_float
|
|
4
7
|
from hestia_earth.utils.emission import cycle_emissions_in_system_boundary
|
|
5
8
|
|
|
6
|
-
from hestia_earth.models.log import logShouldRun
|
|
7
|
-
from . import is_from_model
|
|
9
|
+
from hestia_earth.models.log import logShouldRun, logRequirements, debugValues
|
|
10
|
+
from . import is_from_model, _omit
|
|
8
11
|
from .term import get_lookup_value
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
def _animal_inputs(animal: dict):
|
|
12
15
|
inputs = animal.get('inputs', [])
|
|
13
|
-
return [(input | {'animal': animal.get('term', {})}) for input in inputs]
|
|
16
|
+
return [(input | {'animalId': animal['animalId'], 'animal': animal.get('term', {})}) for input in inputs]
|
|
14
17
|
|
|
15
18
|
|
|
16
19
|
def _should_run_input(products: list):
|
|
@@ -58,23 +61,127 @@ def no_gap_filled_background_emissions(
|
|
|
58
61
|
return check_input
|
|
59
62
|
|
|
60
63
|
|
|
61
|
-
def
|
|
62
|
-
term_ids = cycle_emissions_in_system_boundary(
|
|
63
|
-
|
|
64
|
-
get_lookup_value({'termType':
|
|
64
|
+
def _all_background_emission_term_ids(node: dict, termType: TermTermType):
|
|
65
|
+
term_ids = cycle_emissions_in_system_boundary(node, termType=termType)
|
|
66
|
+
background_ids = list(set([
|
|
67
|
+
get_lookup_value({'termType': termType.value, '@id': term_id}, 'inputProductionGroupId')
|
|
65
68
|
for term_id in term_ids
|
|
66
69
|
]))
|
|
70
|
+
# make sure input production emission is itself in the system boundary
|
|
71
|
+
return [term_id for term_id in background_ids if term_id in term_ids]
|
|
67
72
|
|
|
68
73
|
|
|
69
|
-
def log_missing_emissions(
|
|
70
|
-
all_emission_term_ids =
|
|
74
|
+
def log_missing_emissions(node: dict, termType: TermTermType = TermTermType.EMISSION, **log_args):
|
|
75
|
+
all_emission_term_ids = _all_background_emission_term_ids(node, termType)
|
|
71
76
|
|
|
72
77
|
def log_input(input_term_id: str, included_emission_term_ids: list, **extra_log_args):
|
|
73
|
-
missing_emission_term_ids = [
|
|
78
|
+
missing_emission_term_ids = non_empty_list([
|
|
74
79
|
term_id for term_id in all_emission_term_ids if term_id not in included_emission_term_ids
|
|
75
|
-
]
|
|
80
|
+
])
|
|
81
|
+
|
|
76
82
|
for emission_id in missing_emission_term_ids:
|
|
77
|
-
|
|
83
|
+
# debug value on the emission itself so it appears for the input
|
|
84
|
+
debugValues(node, term=emission_id,
|
|
85
|
+
value=None,
|
|
86
|
+
coefficient=None,
|
|
87
|
+
input=input_term_id,
|
|
88
|
+
**log_args,
|
|
89
|
+
**extra_log_args)
|
|
90
|
+
logRequirements(node, term=input_term_id,
|
|
91
|
+
emission_id=emission_id,
|
|
92
|
+
has_emission_factor=False,
|
|
93
|
+
**log_args,
|
|
94
|
+
**extra_log_args)
|
|
95
|
+
logShouldRun(node, term=input_term_id, should_run=False,
|
|
96
|
+
emission_id=emission_id,
|
|
78
97
|
**log_args,
|
|
79
98
|
**extra_log_args)
|
|
80
99
|
return log_input
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
_KEY_TO_FIELD = {
|
|
103
|
+
'inputs': 'key'
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _key_to_field(key: str): return _KEY_TO_FIELD.get(key) or key
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _values_from_column(index_column: str, column: str, value: str):
|
|
111
|
+
values = column.split('+')
|
|
112
|
+
term_id = values[0]
|
|
113
|
+
value = safe_parse_float(value, default=None)
|
|
114
|
+
return {
|
|
115
|
+
term_id: {
|
|
116
|
+
'value': value
|
|
117
|
+
} | {
|
|
118
|
+
_key_to_field(v.split('[')[0]): v.split('[')[1][:-1] for v in values[1:]
|
|
119
|
+
}
|
|
120
|
+
} if all([
|
|
121
|
+
column != index_column,
|
|
122
|
+
not column.startswith('ecoinvent'),
|
|
123
|
+
not column.startswith('ecoalim'),
|
|
124
|
+
not _is_missing_value(value)
|
|
125
|
+
]) else {}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def convert_background_lookup(lookup, index_column: str):
|
|
129
|
+
columns = lookup_columns(lookup)
|
|
130
|
+
return {
|
|
131
|
+
row[index_column]: reduce(
|
|
132
|
+
lambda prev, curr: prev | _values_from_column(index_column, curr, row[curr]),
|
|
133
|
+
columns,
|
|
134
|
+
{}
|
|
135
|
+
)
|
|
136
|
+
for row in lookup
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def parse_term_id(term_id: str): return term_id.split('-')[0]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def join_term_id(term_id: str, data: dict):
|
|
144
|
+
return '-'.join(non_empty_list([term_id] + list(_omit(data, ['value']).values())))
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _process_mapping(
|
|
148
|
+
node: dict,
|
|
149
|
+
input: dict,
|
|
150
|
+
term_type: TermTermType,
|
|
151
|
+
extract_mapping: Callable[[Tuple, TermTermType], Tuple[dict, float]],
|
|
152
|
+
**log_args
|
|
153
|
+
) -> dict:
|
|
154
|
+
input_term_id = input.get('term', {}).get('@id')
|
|
155
|
+
operation_term_id = input.get('operation', {}).get('@id')
|
|
156
|
+
animal_term_id = input.get('animal', {}).get('@id')
|
|
157
|
+
|
|
158
|
+
def add(prev: dict, mapping: Tuple):
|
|
159
|
+
values, coefficient = extract_mapping(mapping, term_type)
|
|
160
|
+
for term_id, data in values:
|
|
161
|
+
# log run on each node so we know it did run
|
|
162
|
+
logShouldRun(node, term=input_term_id, should_run=True,
|
|
163
|
+
methodTier=EmissionMethodTier.BACKGROUND.value,
|
|
164
|
+
emission_id=term_id,
|
|
165
|
+
**log_args)
|
|
166
|
+
debugValues(node, term=term_id,
|
|
167
|
+
value=data.get('value'),
|
|
168
|
+
coefficient=coefficient,
|
|
169
|
+
input=input_term_id,
|
|
170
|
+
operation=operation_term_id,
|
|
171
|
+
animal=animal_term_id,
|
|
172
|
+
**log_args)
|
|
173
|
+
group_id = join_term_id(term_id, data)
|
|
174
|
+
prev[group_id] = prev.get(group_id, []) + [data | {'coefficient': coefficient}]
|
|
175
|
+
return prev
|
|
176
|
+
return add
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def process_input_mappings(
|
|
180
|
+
node: dict,
|
|
181
|
+
input: dict,
|
|
182
|
+
mappings: list,
|
|
183
|
+
term_type: TermTermType,
|
|
184
|
+
extract_mapping: Callable[[tuple, TermTermType], Tuple[dict, float]],
|
|
185
|
+
**log_args
|
|
186
|
+
):
|
|
187
|
+
return reduce(_process_mapping(node, input, term_type, extract_mapping, **log_args), mappings, {})
|
|
@@ -111,17 +111,6 @@ def properties_logs(blank_nodes: list, properties: Union[dict, list]):
|
|
|
111
111
|
return log_as_table(logs)
|
|
112
112
|
|
|
113
113
|
|
|
114
|
-
def group_by_keys(group_keys: list = ['term']):
|
|
115
|
-
def run(group: dict, node: dict):
|
|
116
|
-
group_key = '-'.join(non_empty_list([
|
|
117
|
-
node.get(v, {}).get('@id') if isinstance(node.get(v), dict) else node.get(v)
|
|
118
|
-
for v in group_keys
|
|
119
|
-
]))
|
|
120
|
-
group[group_key] = group.get(group_key, []) + [node]
|
|
121
|
-
return group
|
|
122
|
-
return run
|
|
123
|
-
|
|
124
|
-
|
|
125
114
|
def _module_term_id(term_id: str, module):
|
|
126
115
|
term_id_str = term_id.split('.')[-1] if '.' in term_id else term_id
|
|
127
116
|
return getattr(module, 'TERM_ID', term_id_str).split(',')[0]
|
|
@@ -1337,6 +1326,7 @@ def get_inputs_from_properties(input: dict, term_types: Union[TermTermType, List
|
|
|
1337
1326
|
{
|
|
1338
1327
|
'term': p.get('key'),
|
|
1339
1328
|
'value': [(p.get('value') / 100) * (p.get('share', 100) / 100) * input_value],
|
|
1329
|
+
# for grouping
|
|
1340
1330
|
'parent': term
|
|
1341
1331
|
} for p in (properties or []) if all([p.get('key'), p.get('value')])
|
|
1342
1332
|
]) if input_value > 0 else []
|
|
@@ -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
|
|
23
|
-
|
|
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=
|
|
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=
|
|
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]
|
|
@@ -47,7 +47,7 @@ def _map_properties(lookup, term_id: str, column_prefix: str):
|
|
|
47
47
|
return {'value': value, 'sd': sd, 'min': min, 'max': max}
|
|
48
48
|
|
|
49
49
|
|
|
50
|
-
def rescale_properties_from_dryMatter(model: str, node: dict, blank_nodes: list):
|
|
50
|
+
def rescale_properties_from_dryMatter(model: str, node: dict, blank_nodes: list, **log_args):
|
|
51
51
|
properties = get_feedipedia_properties()
|
|
52
52
|
# download all to save time
|
|
53
53
|
term_types = [blank_node.get('term', {}).get('termType') for blank_node in blank_nodes]
|
|
@@ -78,7 +78,7 @@ def rescale_properties_from_dryMatter(model: str, node: dict, blank_nodes: list)
|
|
|
78
78
|
])
|
|
79
79
|
])
|
|
80
80
|
for prop in new_properties:
|
|
81
|
-
logShouldRun(node, model, term_id, True, property=prop.get('term', {}).get('@id'))
|
|
81
|
+
logShouldRun(node, model, term_id, True, property=prop.get('term', {}).get('@id'), **log_args)
|
|
82
82
|
return (
|
|
83
83
|
blank_node | {'properties': merge_blank_nodes(all_properties, new_properties)}
|
|
84
84
|
) if new_properties else blank_node
|
|
@@ -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,
|
|
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=
|
|
193
|
+
factor_value_func=region_factor_value
|
|
194
194
|
)
|
|
195
195
|
|
|
196
196
|
|
|
@@ -219,10 +219,8 @@ def impact_aware_value(model: str, term_id: str, impact: dict, lookup: str, grou
|
|
|
219
219
|
blank_nodes = impact.get('emissionsResourceUse', [])
|
|
220
220
|
site = get_site(impact)
|
|
221
221
|
aware_id = site.get('awareWaterBasinId')
|
|
222
|
-
if aware_id is None:
|
|
223
|
-
return None
|
|
224
222
|
|
|
225
|
-
return all_factor_value(
|
|
223
|
+
return None if aware_id is None else all_factor_value(
|
|
226
224
|
logs_model=model,
|
|
227
225
|
logs_term_id=term_id,
|
|
228
226
|
node=impact,
|
|
@@ -231,7 +229,7 @@ def impact_aware_value(model: str, term_id: str, impact: dict, lookup: str, grou
|
|
|
231
229
|
blank_nodes=blank_nodes,
|
|
232
230
|
grouped_key=group_key,
|
|
233
231
|
default_no_values=None,
|
|
234
|
-
factor_value_func=
|
|
232
|
+
factor_value_func=aware_factor_value
|
|
235
233
|
)
|
|
236
234
|
|
|
237
235
|
|
|
@@ -5,7 +5,10 @@ from .method import include_methodModel
|
|
|
5
5
|
from .term import download_term
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def _new_indicator(
|
|
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)
|
|
@@ -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
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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,26 +70,30 @@ 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
|
|
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 =
|
|
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,
|
|
82
94
|
value=value,
|
|
83
95
|
coefficient=coefficient)
|
|
84
|
-
except
|
|
96
|
+
except Exception: # factor does not exist
|
|
85
97
|
coefficient = None
|
|
86
98
|
|
|
87
99
|
return {'id': node_term_id, 'value': value, 'coefficient': coefficient}
|
|
@@ -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, '
|
|
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)
|
hestia_earth/models/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
VERSION = '0.
|
|
1
|
+
VERSION = '0.74.0'
|
hestia_earth/orchestrator/log.py
CHANGED
|
@@ -3,6 +3,8 @@ import sys
|
|
|
3
3
|
import logging
|
|
4
4
|
|
|
5
5
|
LOG_LEVEL = os.getenv('LOG_LEVEL', 'INFO')
|
|
6
|
+
_EXTENDED_LOGS = os.getenv('LOG_EXTENDED', 'true') == 'true'
|
|
7
|
+
_LOG_DATE_FORMAT = os.getenv('LOG_DATE_FORMAT', '%Y-%m-%dT%H:%M:%S%z')
|
|
6
8
|
|
|
7
9
|
# disable root logger
|
|
8
10
|
root_logger = logging.getLogger()
|
|
@@ -25,9 +27,12 @@ def log_to_file(filepath: str):
|
|
|
25
27
|
Path of the file.
|
|
26
28
|
"""
|
|
27
29
|
formatter = logging.Formatter(
|
|
28
|
-
'{"timestamp": "%(asctime)s", "level": "%(levelname)s", "logger": "%(name)s", '
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
'{"timestamp": "%(asctime)s", "level": "%(levelname)s", "logger": "%(name)s", "message": "%(message)s"}',
|
|
31
|
+
_LOG_DATE_FORMAT
|
|
32
|
+
) if _EXTENDED_LOGS else logging.Formatter(
|
|
33
|
+
'{"logger": "%(name)s", "message": "%(message)s"}',
|
|
34
|
+
_LOG_DATE_FORMAT
|
|
35
|
+
)
|
|
31
36
|
handler = logging.FileHandler(filepath, encoding='utf-8')
|
|
32
37
|
handler.setFormatter(formatter)
|
|
33
38
|
handler.setLevel(logging.getLevelName(LOG_LEVEL))
|
|
@@ -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
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
-
|
|
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.
|
|
3
|
+
Version: 0.74.0
|
|
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
|
|
15
|
-
Requires-Dist: hestia-earth-utils>=0.
|
|
14
|
+
Requires-Dist: hestia-earth-schema<34.0.0,>=33.5.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
|