hestia-earth-models 0.67.0__py3-none-any.whl → 0.68.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.
- hestia_earth/models/aware/scarcityWeightedWaterUse.py +5 -6
- hestia_earth/models/blonkConsultants2016/ch4ToAirNaturalVegetationBurning.py +1 -1
- hestia_earth/models/blonkConsultants2016/co2ToAirAboveGroundBiomassStockChangeLandUseChange.py +1 -1
- hestia_earth/models/blonkConsultants2016/n2OToAirNaturalVegetationBurningDirect.py +1 -1
- hestia_earth/models/blonkConsultants2016/utils.py +9 -9
- hestia_earth/models/cache_sites.py +26 -14
- hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsLandOccupation.py +2 -2
- hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsLandTransformation.py +2 -2
- hestia_earth/models/chaudharyBrooks2018/utils.py +13 -8
- hestia_earth/models/cml2001Baseline/abioticResourceDepletionFossilFuels.py +2 -3
- hestia_earth/models/cml2001Baseline/abioticResourceDepletionMineralsAndMetals.py +1 -1
- hestia_earth/models/cml2001Baseline/resourceUseEnergyDepletionDuringCycle.py +5 -10
- hestia_earth/models/config/Cycle.json +15 -0
- hestia_earth/models/config/ImpactAssessment.json +14 -1
- hestia_earth/models/config/Site.json +8 -0
- hestia_earth/models/cycle/completeness/freshForage.py +7 -3
- hestia_earth/models/cycle/excretaKgMass.py +2 -2
- hestia_earth/models/cycle/inorganicFertiliser.py +67 -17
- hestia_earth/models/cycle/materialAndSubstrate.py +3 -2
- hestia_earth/models/cycle/pastureGrass.py +3 -3
- hestia_earth/models/dammgen2009/noxToAirExcreta.py +1 -1
- hestia_earth/models/ecoinventV3AndEmberClimate/__init__.py +1 -1
- hestia_earth/models/ecoinventV3AndEmberClimate/utils.py +2 -6
- hestia_earth/models/emissionNotRelevant/__init__.py +4 -4
- hestia_earth/models/environmentalFootprintV3_1/environmentalFootprintSingleOverallScore.py +60 -46
- hestia_earth/models/environmentalFootprintV3_1/photochemicalOzoneCreationPotentialHumanHealthNmvocEq.py +36 -0
- hestia_earth/models/environmentalFootprintV3_1/scarcityWeightedWaterUse.py +2 -2
- hestia_earth/models/environmentalFootprintV3_1/soilQualityIndexLandOccupation.py +9 -8
- hestia_earth/models/environmentalFootprintV3_1/soilQualityIndexLandTransformation.py +45 -34
- hestia_earth/models/environmentalFootprintV3_1/soilQualityIndexTotalLandUseEffects.py +24 -21
- hestia_earth/models/faostat2018/coldCarcassWeightPerHead.py +2 -2
- hestia_earth/models/faostat2018/coldDressedCarcassWeightPerHead.py +2 -2
- hestia_earth/models/faostat2018/liveweightPerHead.py +7 -8
- hestia_earth/models/faostat2018/product/price.py +34 -28
- hestia_earth/models/faostat2018/readyToCookWeightPerHead.py +2 -2
- hestia_earth/models/faostat2018/utils.py +15 -27
- hestia_earth/models/frischknechtEtAl2000/ionisingRadiationKbqU235Eq.py +16 -9
- hestia_earth/models/geospatialDatabase/altitude.py +60 -0
- hestia_earth/models/geospatialDatabase/croppingIntensity.py +1 -1
- hestia_earth/models/geospatialDatabase/ecoClimateZone.py +2 -2
- hestia_earth/models/geospatialDatabase/longFallowRatio.py +1 -1
- hestia_earth/models/geospatialDatabase/utils.py +4 -1
- hestia_earth/models/globalCropWaterModel2008/rootingDepth.py +2 -3
- hestia_earth/models/haversineFormula/transport/distance.py +3 -3
- hestia_earth/models/hestia/landCover.py +72 -45
- hestia_earth/models/hestia/landTransformation100YearAverageDuringCycle.py +1 -1
- hestia_earth/models/hestia/landTransformation20YearAverageDuringCycle.py +1 -1
- hestia_earth/models/hestia/seed_emissions.py +11 -7
- hestia_earth/models/impact_assessment/__init__.py +3 -3
- hestia_earth/models/ipcc2019/aboveGroundBiomass.py +1 -1
- hestia_earth/models/ipcc2019/animal/fatContent.py +1 -1
- hestia_earth/models/ipcc2019/animal/hoursWorkedPerDay.py +1 -1
- hestia_earth/models/ipcc2019/animal/liveweightGain.py +1 -1
- hestia_earth/models/ipcc2019/animal/liveweightPerHead.py +1 -1
- hestia_earth/models/ipcc2019/animal/milkYieldPerAnimal.py +1 -1
- hestia_earth/models/ipcc2019/animal/pastureGrass.py +1 -1
- hestia_earth/models/ipcc2019/animal/pregnancyRateTotal.py +1 -1
- hestia_earth/models/ipcc2019/animal/trueProteinContent.py +1 -1
- hestia_earth/models/ipcc2019/animal/utils.py +5 -7
- hestia_earth/models/ipcc2019/animal/weightAtMaturity.py +1 -1
- hestia_earth/models/ipcc2019/belowGroundBiomass.py +1 -1
- hestia_earth/models/ipcc2019/ch4ToAirEntericFermentation.py +2 -2
- hestia_earth/models/ipcc2019/ch4ToAirExcreta.py +6 -7
- hestia_earth/models/ipcc2019/ch4ToAirFloodedRice.py +5 -3
- hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +1 -1
- hestia_earth/models/ipcc2019/croppingDuration.py +3 -6
- hestia_earth/models/ipcc2019/nonCo2EmissionsToAirNaturalVegetationBurning.py +947 -0
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +4 -4
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +1 -1
- hestia_earth/models/ipcc2019/pastureGrass.py +1 -1
- hestia_earth/models/koble2014/residueBurnt.py +5 -7
- hestia_earth/models/koble2014/residueRemoved.py +5 -7
- hestia_earth/models/lcImpactAllEffects100Years/damageToHumanHealthWaterStress.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToHumanHealthWaterStress.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToHumanHealthWaterStress.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToHumanHealthWaterStress.py +2 -2
- hestia_earth/models/log.py +1 -1
- hestia_earth/models/mocking/search-results.json +3477 -1045
- hestia_earth/models/site/management.py +1 -1
- hestia_earth/models/site/post_checks/__init__.py +3 -2
- hestia_earth/models/site/post_checks/country.py +9 -0
- hestia_earth/models/site/pre_checks/__init__.py +3 -2
- hestia_earth/models/site/pre_checks/country.py +9 -0
- hestia_earth/models/utils/__init__.py +1 -16
- hestia_earth/models/utils/blank_node.py +89 -36
- hestia_earth/models/utils/completeness.py +3 -2
- hestia_earth/models/utils/cycle.py +5 -4
- hestia_earth/models/utils/ecoClimateZone.py +2 -2
- hestia_earth/models/utils/emission.py +5 -5
- hestia_earth/models/utils/feedipedia.py +6 -6
- hestia_earth/models/utils/impact_assessment.py +6 -6
- hestia_earth/models/utils/indicator.py +9 -7
- hestia_earth/models/utils/inorganicFertiliser.py +4 -6
- hestia_earth/models/utils/input.py +6 -5
- hestia_earth/models/utils/lookup.py +35 -105
- hestia_earth/models/utils/management.py +4 -4
- hestia_earth/models/utils/measurement.py +6 -7
- hestia_earth/models/utils/method.py +20 -0
- hestia_earth/models/utils/practice.py +4 -5
- hestia_earth/models/utils/product.py +4 -5
- hestia_earth/models/utils/property.py +12 -22
- hestia_earth/models/utils/site.py +14 -8
- hestia_earth/models/utils/term.py +27 -1
- hestia_earth/models/version.py +1 -1
- hestia_earth/orchestrator/log.py +0 -11
- hestia_earth/orchestrator/models/__init__.py +17 -4
- hestia_earth/orchestrator/strategies/run/add_blank_node_if_missing.py +2 -20
- {hestia_earth_models-0.67.0.dist-info → hestia_earth_models-0.68.0.dist-info}/METADATA +2 -2
- {hestia_earth_models-0.67.0.dist-info → hestia_earth_models-0.68.0.dist-info}/RECORD +159 -151
- tests/models/cml2001Baseline/test_abioticResourceDepletionFossilFuels.py +3 -3
- tests/models/cml2001Baseline/test_resourceUseEnergyDepletionDuringCycle.py +68 -35
- tests/models/cycle/test_coldCarcassWeightPerHead.py +1 -1
- tests/models/cycle/test_coldDressedCarcassWeightPerHead.py +1 -1
- tests/models/cycle/test_concentrateFeed.py +1 -1
- tests/models/cycle/test_energyContentLowerHeatingValue.py +1 -1
- tests/models/cycle/test_excretaKgMass.py +1 -1
- tests/models/cycle/test_feedConversionRatio.py +3 -3
- tests/models/cycle/test_pastureGrass.py +1 -1
- tests/models/cycle/test_readyToCookWeightPerHead.py +1 -1
- tests/models/environmentalFootprintV3_1/test_environmentalFootprintSingleOverallScore.py +38 -8
- tests/models/environmentalFootprintV3_1/test_photochemicalOzoneCreationPotentialHumanHealthNmvocEq.py +30 -0
- tests/models/environmentalFootprintV3_1/test_soilQualityIndexLandTransformation.py +65 -36
- tests/models/environmentalFootprintV3_1/test_soilQualityIndexTotalLandUseEffects.py +30 -7
- tests/models/faostat2018/product/test_price.py +27 -14
- tests/models/faostat2018/test_faostat_utils.py +4 -24
- tests/models/faostat2018/test_liveweightPerHead.py +9 -9
- tests/models/globalCropWaterModel2008/test_rootingDepth.py +7 -3
- tests/models/haversineFormula/transport/test_distance.py +1 -1
- tests/models/hestia/test_landCover.py +53 -5
- tests/models/ipcc2019/animal/test_pastureGrass.py +5 -3
- tests/models/ipcc2019/test_aboveGroundCropResidueTotal.py +4 -4
- tests/models/ipcc2019/test_belowGroundCropResidue.py +4 -4
- tests/models/ipcc2019/test_ch4ToAirEntericFermentation.py +10 -10
- tests/models/ipcc2019/test_croppingDuration.py +1 -1
- tests/models/ipcc2019/test_nonCo2EmissionsToAirNaturalVegetationBurning.py +83 -0
- tests/models/ipcc2019/test_organicCarbonPerHa.py +12 -12
- tests/models/ipcc2019/test_pastureGrass.py +5 -3
- tests/models/pooreNemecek2018/test_excretaKgN.py +5 -5
- tests/models/pooreNemecek2018/test_excretaKgVs.py +2 -2
- tests/models/site/post_checks/test_country.py +6 -0
- tests/models/site/pre_checks/test_cache_geospatialDatabase.py +1 -1
- tests/models/site/pre_checks/test_country.py +12 -0
- tests/models/site/test_management.py +1 -4
- tests/models/test_ecoinventV3.py +7 -3
- tests/models/utils/test_blank_node.py +17 -177
- tests/models/utils/test_dataCompleteness.py +5 -5
- tests/models/utils/test_emission.py +2 -2
- tests/models/utils/test_indicator.py +2 -2
- tests/models/utils/test_input.py +2 -2
- tests/models/utils/test_measurement.py +2 -4
- tests/models/utils/test_practice.py +4 -2
- tests/models/utils/test_product.py +2 -2
- tests/models/utils/test_property.py +4 -2
- tests/models/utils/test_site.py +7 -0
- tests/orchestrator/models/test_transformations.py +4 -1
- tests/orchestrator/strategies/run/test_add_blank_node_if_missing.py +4 -9
- hestia_earth/models/environmentalFootprintV3_1/utils.py +0 -17
- tests/models/utils/test_lookup.py +0 -10
- {hestia_earth_models-0.67.0.dist-info → hestia_earth_models-0.68.0.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.67.0.dist-info → hestia_earth_models-0.68.0.dist-info}/WHEEL +0 -0
- {hestia_earth_models-0.67.0.dist-info → hestia_earth_models-0.68.0.dist-info}/top_level.txt +0 -0
@@ -3,7 +3,7 @@ This model calculates the scarcity weighted water use based on the geospatial AW
|
|
3
3
|
(see UNEP (2016); Boulay et al (2016); Boulay et al (2020); EC-JRC (2017)).
|
4
4
|
"""
|
5
5
|
from hestia_earth.schema import SiteSiteType
|
6
|
-
from hestia_earth.utils.lookup import download_lookup, _get_single_table_value, column_name
|
6
|
+
from hestia_earth.utils.lookup import download_lookup, _get_single_table_value, column_name
|
7
7
|
from hestia_earth.utils.tools import safe_parse_float
|
8
8
|
|
9
9
|
from hestia_earth.models.log import logRequirements, debugMissingLookup, logShouldRun
|
@@ -13,6 +13,7 @@ from hestia_earth.models.utils.impact_assessment import (
|
|
13
13
|
convert_value_from_cycle, emission_value, get_product, get_site, get_region_id
|
14
14
|
)
|
15
15
|
from hestia_earth.models.utils.input import sum_input_impacts
|
16
|
+
from hestia_earth.models.utils.lookup import get_region_lookup_value
|
16
17
|
from . import MODEL
|
17
18
|
|
18
19
|
REQUIREMENTS = {
|
@@ -40,8 +41,8 @@ RETURNS = {
|
|
40
41
|
}
|
41
42
|
LOOKUPS = {
|
42
43
|
"@doc": "Different lookup files are used depending on the situation",
|
43
|
-
"awareWaterBasinId": "
|
44
|
-
"region-aware-factors": "
|
44
|
+
"awareWaterBasinId": "",
|
45
|
+
"region-aware-factors": ""
|
45
46
|
}
|
46
47
|
TERM_ID = 'scarcityWeightedWaterUse'
|
47
48
|
AWARE_KEY = 'awareWaterBasinId'
|
@@ -71,11 +72,9 @@ def _get_factor_from_region(impact_assessment: dict, site: dict):
|
|
71
72
|
region_id = get_region_id(impact_assessment)
|
72
73
|
site_type = site.get('siteType')
|
73
74
|
lookup_name = 'region-aware-factors.csv'
|
74
|
-
lookup = download_lookup(lookup_name)
|
75
75
|
lookup_suffix = 'unspecified' if not site_type else ('irri' if site_type in IRRIGATED_SITE_TYPES else 'non_irri')
|
76
76
|
column = f"Agg_CF_{lookup_suffix}"
|
77
|
-
value =
|
78
|
-
debugMissingLookup(lookup_name, 'termid', region_id, column, value, model=MODEL, term=TERM_ID)
|
77
|
+
value = get_region_lookup_value(lookup_name, region_id, column, model=MODEL, term=TERM_ID)
|
79
78
|
return safe_parse_float(value, None)
|
80
79
|
|
81
80
|
|
@@ -34,7 +34,7 @@ REQUIREMENTS = {
|
|
34
34
|
}
|
35
35
|
LOOKUPS = {
|
36
36
|
"crop": ["isPlantation", "cropGroupingFaostatArea"],
|
37
|
-
"region-crop-cropGroupingFaostatArea-ch4forestBiomassBurning": "
|
37
|
+
"region-crop-cropGroupingFaostatArea-ch4forestBiomassBurning": ""
|
38
38
|
}
|
39
39
|
RETURNS = {
|
40
40
|
"Emission": [{
|
hestia_earth/models/blonkConsultants2016/co2ToAirAboveGroundBiomassStockChangeLandUseChange.py
CHANGED
@@ -34,7 +34,7 @@ REQUIREMENTS = {
|
|
34
34
|
}
|
35
35
|
LOOKUPS = {
|
36
36
|
"crop": ["isPlantation", "cropGroupingFaostatArea"],
|
37
|
-
"region-crop-cropGroupingFaostatArea-co2LandUseChange": "
|
37
|
+
"region-crop-cropGroupingFaostatArea-co2LandUseChange": ""
|
38
38
|
}
|
39
39
|
RETURNS = {
|
40
40
|
"Emission": [{
|
@@ -34,7 +34,7 @@ REQUIREMENTS = {
|
|
34
34
|
}
|
35
35
|
LOOKUPS = {
|
36
36
|
"crop": ["isPlantation", "cropGroupingFaostatArea"],
|
37
|
-
"region-crop-cropGroupingFaostatArea-n2oforestBiomassBurning": "
|
37
|
+
"region-crop-cropGroupingFaostatArea-n2oforestBiomassBurning": ""
|
38
38
|
}
|
39
39
|
RETURNS = {
|
40
40
|
"Emission": [{
|
@@ -1,9 +1,10 @@
|
|
1
1
|
from hestia_earth.utils.model import find_primary_product
|
2
|
-
from hestia_earth.utils.lookup import
|
2
|
+
from hestia_earth.utils.lookup import extract_grouped_data
|
3
3
|
from hestia_earth.utils.tools import safe_parse_float
|
4
4
|
|
5
|
-
from hestia_earth.models.log import
|
5
|
+
from hestia_earth.models.log import logger
|
6
6
|
from hestia_earth.models.utils.crop import FAOSTAT_AREA_LOOKUP_COLUMN, get_crop_grouping_faostat_area
|
7
|
+
from hestia_earth.models.utils.lookup import get_region_lookup_value
|
7
8
|
from . import MODEL
|
8
9
|
|
9
10
|
|
@@ -18,13 +19,12 @@ def get_emission_factor(term_id: str, cycle: dict, factor: str):
|
|
18
19
|
MODEL, country_id, product_id, f"'{crop_grouping}'")
|
19
20
|
|
20
21
|
lookup_name = f"region-crop-{FAOSTAT_AREA_LOOKUP_COLUMN}-{factor}.csv"
|
21
|
-
|
22
|
-
|
23
|
-
|
22
|
+
value = get_region_lookup_value(
|
23
|
+
lookup_name, country_id, crop_grouping, model=MODEL, term=term_id
|
24
|
+
) if crop_grouping else get_region_lookup_value(
|
25
|
+
lookup_name, country_id, 'NONE', model=MODEL, term=term_id
|
26
|
+
)
|
24
27
|
|
25
28
|
data = safe_parse_float(value, None)
|
26
29
|
# fallback to site.siteType data if possible
|
27
|
-
return data if data is not None else safe_parse_float(
|
28
|
-
extract_grouped_data(get_table_value(lookup, 'termid', country_id, column_name('NONE')), site.get('siteType')),
|
29
|
-
None
|
30
|
-
)
|
30
|
+
return data if data is not None else safe_parse_float(extract_grouped_data(value, site.get('siteType')), None)
|
@@ -2,7 +2,7 @@ from functools import reduce
|
|
2
2
|
from enum import Enum
|
3
3
|
from pydash.objects import merge
|
4
4
|
from hestia_earth.utils.api import download_hestia
|
5
|
-
from hestia_earth.utils.tools import flatten
|
5
|
+
from hestia_earth.utils.tools import flatten, non_empty_list
|
6
6
|
|
7
7
|
from .log import logger
|
8
8
|
from .utils import CACHE_KEY, cached_value
|
@@ -30,14 +30,24 @@ _VALUE_AS_PARAM = {
|
|
30
30
|
}
|
31
31
|
|
32
32
|
|
33
|
+
def _value_as_param(param_type: ParamType = None): return _VALUE_AS_PARAM.get(param_type, lambda *args: None)
|
34
|
+
|
35
|
+
|
33
36
|
def _cache_results(results: list, collections: list, index: int):
|
34
37
|
start = index * len(collections)
|
35
38
|
end = start + len(collections)
|
36
39
|
return cache_site_results(results[start:end], collections)
|
37
40
|
|
38
41
|
|
39
|
-
def _run_values(
|
40
|
-
|
42
|
+
def _run_values(
|
43
|
+
sites: list,
|
44
|
+
param_type: ParamType = None,
|
45
|
+
rasters: list = [],
|
46
|
+
vectors: list = [],
|
47
|
+
years: list = None
|
48
|
+
):
|
49
|
+
get_param_values = _value_as_param(param_type)
|
50
|
+
param_values = non_empty_list(map(get_param_values, sites))
|
41
51
|
# unique list
|
42
52
|
param_values = list(set(param_values)) if param_type == ParamType.GADM_IDS else list({
|
43
53
|
str(v): v for v in param_values
|
@@ -59,13 +69,13 @@ def _run_values(sites: list, param_type: ParamType, rasters: list = [], vectors:
|
|
59
69
|
site, area_size = site_values
|
60
70
|
|
61
71
|
# get real index in values to handle duplicates
|
62
|
-
param_value =
|
63
|
-
index = param_values.index(param_value)
|
72
|
+
param_value = get_param_values([site])
|
73
|
+
index = param_values.index(param_value) if param_value is not None else None
|
64
74
|
|
65
|
-
cached_data = {
|
75
|
+
cached_data = ({
|
66
76
|
**_cache_results(raster_results, rasters, index),
|
67
77
|
**_cache_results(vector_results, vectors, index)
|
68
|
-
} | ({CACHE_AREA_SIZE: area_size} if area_size is not None else {})
|
78
|
+
} if index is not None else {}) | ({CACHE_AREA_SIZE: area_size} if area_size is not None else {})
|
69
79
|
cached_data = merge(cached_value(site, CACHE_GEOSPATIAL_KEY, {}), cached_data)
|
70
80
|
site_cache = merge(
|
71
81
|
site.get(CACHE_KEY, {}),
|
@@ -96,33 +106,35 @@ def _group_sites(sites: dict, check_has_cache: bool = True):
|
|
96
106
|
(n, ) + (_should_run(n, area_size=get_region_area_size(n), check_has_cache=check_has_cache)) for n in sites
|
97
107
|
]
|
98
108
|
# restrict sites based on should_cache result
|
99
|
-
|
109
|
+
sites_run = [(site, area_size) for site, should_cache, area_size in sites if should_cache]
|
110
|
+
# will only cache area and years
|
111
|
+
sites_no_run = [(site, area_size) for site, should_cache, area_size in sites if not should_cache]
|
100
112
|
|
101
113
|
with_coordinates = [
|
102
|
-
(site, area_size) for site, area_size in
|
114
|
+
(site, area_size) for site, area_size in sites_run if has_coordinates(site)
|
103
115
|
]
|
104
116
|
with_boundaries = [
|
105
|
-
(site, area_size) for site, area_size in
|
117
|
+
(site, area_size) for site, area_size in sites_run if not has_coordinates(site) and has_boundary(site)
|
106
118
|
]
|
107
119
|
with_gadm_ids = [
|
108
|
-
(site, area_size) for site, area_size in
|
120
|
+
(site, area_size) for site, area_size in sites_run if not has_coordinates(site) and not has_boundary(site)
|
109
121
|
]
|
110
122
|
|
111
123
|
return {
|
112
124
|
ParamType.COORDINATES: with_coordinates,
|
113
125
|
ParamType.BOUNDARIES: with_boundaries,
|
114
126
|
ParamType.GADM_IDS: with_gadm_ids
|
115
|
-
}
|
127
|
+
}, sites_no_run
|
116
128
|
|
117
129
|
|
118
130
|
def _run(sites: list, years: list = [], years_only: bool = False):
|
119
131
|
rasters = list_rasters(years=years, years_only=years_only)
|
120
132
|
vectors = [] if years_only else list_vectors(sites)
|
121
|
-
filtered_data = _group_sites(sites, not years_only)
|
133
|
+
filtered_data, sites_no_run = _group_sites(sites, not years_only)
|
122
134
|
return flatten([
|
123
135
|
_run_values(filtered_data.get(param_type), param_type, rasters, vectors, years)
|
124
136
|
for param_type in [e for e in ParamType] if len(filtered_data.get(param_type)) > 0
|
125
|
-
])
|
137
|
+
] + _run_values(sites_no_run, years=years))
|
126
138
|
|
127
139
|
|
128
140
|
def run(sites: list, years: list = None):
|
@@ -53,8 +53,8 @@ RETURNS = {
|
|
53
53
|
}
|
54
54
|
LOOKUPS = {
|
55
55
|
"@doc": "Different lookup files are used depending on the situation",
|
56
|
-
"ecoregion-siteType-LandOccupationChaudaryBrooks2018CF": "
|
57
|
-
"region-siteType-LandOccupationChaudaryBrooks2018CF": "
|
56
|
+
"ecoregion-siteType-LandOccupationChaudaryBrooks2018CF": "",
|
57
|
+
"region-siteType-LandOccupationChaudaryBrooks2018CF": ""
|
58
58
|
}
|
59
59
|
TERM_ID = 'damageToTerrestrialEcosystemsLandOccupation'
|
60
60
|
LOOKUP_SUFFIX = 'LandOccupationChaudaryBrooks2018CF'
|
@@ -41,8 +41,8 @@ RETURNS = {
|
|
41
41
|
}
|
42
42
|
LOOKUPS = {
|
43
43
|
"@doc": "Different lookup files are used depending on the situation",
|
44
|
-
"ecoregion-siteType-LandTransformationChaudaryBrooks2018CF": "
|
45
|
-
"region-siteType-LandTransformationChaudaryBrooks2018CF": "
|
44
|
+
"ecoregion-siteType-LandTransformationChaudaryBrooks2018CF": "",
|
45
|
+
"region-siteType-LandTransformationChaudaryBrooks2018CF": ""
|
46
46
|
}
|
47
47
|
TERM_ID = 'damageToTerrestrialEcosystemsLandTransformation'
|
48
48
|
|
@@ -3,14 +3,14 @@ from hestia_earth.utils.tools import safe_parse_float
|
|
3
3
|
|
4
4
|
from hestia_earth.models.log import debugMissingLookup, logRequirements
|
5
5
|
from hestia_earth.models.utils.impact_assessment import get_site, get_country_id
|
6
|
+
from hestia_earth.models.utils.lookup import get_region_lookup_value
|
6
7
|
from . import MODEL
|
7
8
|
|
8
9
|
|
9
|
-
def _lookup_value(term_id: str, lookup_name: str, col_match: str, col_val: str, column: str
|
10
|
-
value = get_table_value(download_lookup(
|
11
|
-
|
12
|
-
|
13
|
-
return safe_parse_float(value)
|
10
|
+
def _lookup_value(term_id: str, lookup_name: str, col_match: str, col_val: str, column: str):
|
11
|
+
value = get_table_value(download_lookup(lookup_name), col_match, col_val, column_name(column))
|
12
|
+
debugMissingLookup(lookup_name, col_match, col_val, column, value, model=MODEL, term=term_id)
|
13
|
+
return value
|
14
14
|
|
15
15
|
|
16
16
|
def get_region_factor(term_id: str, impact_assessment: dict, lookup_suffix: str, group_key: str = None):
|
@@ -20,12 +20,17 @@ def get_region_factor(term_id: str, impact_assessment: dict, lookup_suffix: str,
|
|
20
20
|
site_type = site.get('siteType')
|
21
21
|
|
22
22
|
lookup_prefix = 'ecoregion' if ecoregion else 'region' if country_id else None
|
23
|
-
|
24
|
-
col_val = ecoregion or country_id
|
23
|
+
lookup_name = f"{lookup_prefix}-siteType-{lookup_suffix}.csv"
|
25
24
|
|
26
25
|
logRequirements(impact_assessment, model=MODEL, term=term_id,
|
27
26
|
site_type=site_type,
|
28
27
|
ecoregion=ecoregion,
|
29
28
|
country_id=country_id)
|
30
29
|
|
31
|
-
|
30
|
+
value = get_region_lookup_value(
|
31
|
+
lookup_name, country_id, site_type, model=MODEL, term=term_id
|
32
|
+
) if lookup_prefix == 'region' else _lookup_value(
|
33
|
+
term_id, lookup_name, 'ecoregion', ecoregion, site_type
|
34
|
+
)
|
35
|
+
value = extract_grouped_data(value, group_key) if group_key else value
|
36
|
+
return safe_parse_float(value)
|
@@ -17,7 +17,7 @@ Source: [JRC Technical reports Suggestions for updating the Product Environmenta
|
|
17
17
|
Source: [Differences between EF model versions](https://eplca.jrc.ec.europa.eu/EFVersioning.html)
|
18
18
|
""" # noqa: E501
|
19
19
|
from itertools import chain
|
20
|
-
from hestia_earth.utils.lookup import download_lookup, column_name
|
20
|
+
from hestia_earth.utils.lookup import download_lookup, column_name, find_term_ids_by
|
21
21
|
from hestia_earth.utils.tools import list_sum, flatten
|
22
22
|
|
23
23
|
from hestia_earth.models.log import logShouldRun, logRequirements, log_as_table
|
@@ -66,8 +66,7 @@ def get_all_non_renewable_terms(lookup_file_name: str, column: str) -> list:
|
|
66
66
|
returns all non renewable term ids in lookup files like `electricity.csv` or `fuel.csv`
|
67
67
|
"""
|
68
68
|
lookup = download_lookup(lookup_file_name)
|
69
|
-
|
70
|
-
return list(map(str, results))
|
69
|
+
return find_term_ids_by(lookup, column_name(column), True)
|
71
70
|
|
72
71
|
|
73
72
|
def _valid_resource_indicator(resource: dict) -> bool:
|
@@ -95,7 +95,7 @@ def _should_run(impact_assessment: dict) -> tuple[bool, list]:
|
|
95
95
|
"indicator-input-is-valid": _valid_input(input),
|
96
96
|
"value": _node_value(resource_indicator),
|
97
97
|
"coefficient": get_table_value(
|
98
|
-
|
98
|
+
lookup=download_lookup(filename=f"{input.get('termType')}.csv"),
|
99
99
|
col_match='termid',
|
100
100
|
col_match_with=input.get('@id'),
|
101
101
|
col_val=column_name(LOOKUPS.get(input.get('termType'), ''))) if input else None
|
@@ -1,6 +1,6 @@
|
|
1
1
|
"""
|
2
2
|
This model converts all "energy" terms found in a `Cycle > Inputs` to `MJ` using optional
|
3
|
-
`
|
3
|
+
`energyContentLowerHeatingValue` and `density` properties or the term's "defaultProperties",
|
4
4
|
aggregates them, and places them inside a 'resourceUseEnergyDepletionDuringCycle' indicator per aggregated input id.
|
5
5
|
"""
|
6
6
|
from collections import defaultdict
|
@@ -12,7 +12,7 @@ from hestia_earth.utils.model import filter_list_term_type
|
|
12
12
|
from hestia_earth.utils.tools import list_sum
|
13
13
|
|
14
14
|
from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
|
15
|
-
from hestia_earth.models.utils import Units
|
15
|
+
from hestia_earth.models.utils import Units, _include
|
16
16
|
from hestia_earth.models.utils.blank_node import convert_unit
|
17
17
|
from hestia_earth.models.utils.indicator import _new_indicator
|
18
18
|
from . import MODEL
|
@@ -31,7 +31,7 @@ REQUIREMENTS = {
|
|
31
31
|
{
|
32
32
|
"@type": "Property",
|
33
33
|
"value": "",
|
34
|
-
"term.@id": "
|
34
|
+
"term.@id": "energyContentLowerHeatingValue",
|
35
35
|
"term.units": "MJ / kg"
|
36
36
|
},
|
37
37
|
{
|
@@ -57,7 +57,7 @@ RETURNS = {
|
|
57
57
|
}
|
58
58
|
|
59
59
|
LOOKUPS = {
|
60
|
-
"fuel": ["
|
60
|
+
"fuel": ["energyContentLowerHeatingValue", "density"]
|
61
61
|
}
|
62
62
|
|
63
63
|
TERM_ID = 'resourceUseEnergyDepletionDuringCycle'
|
@@ -123,12 +123,7 @@ def _should_run(cycle: dict) -> Tuple[bool, dict]:
|
|
123
123
|
grouped_energy_terms[k].extend(list(v))
|
124
124
|
|
125
125
|
logs = [
|
126
|
-
{
|
127
|
-
'id': input.get('@id'),
|
128
|
-
'units': input.get('units'),
|
129
|
-
'termType': input.get('termType'),
|
130
|
-
'value': input.get('value'),
|
131
|
-
'value-in-MJ': input.get('value-in-MJ'),
|
126
|
+
_include(input, ['id', 'units', 'termType', 'value', 'value-in-MJ']) | {
|
132
127
|
'properties': " ".join([
|
133
128
|
f"{p.get('term', {}).get('@id')}= {p.get('value')} ({p.get('term', {}).get('units')})"
|
134
129
|
for p in input.get('properties', [])
|
@@ -1183,6 +1183,21 @@
|
|
1183
1183
|
},
|
1184
1184
|
"stage": 2
|
1185
1185
|
},
|
1186
|
+
{
|
1187
|
+
"key": "emissions",
|
1188
|
+
"model": "ipcc2019",
|
1189
|
+
"value": "nonCo2EmissionsToAirNaturalVegetationBurning",
|
1190
|
+
"runStrategy": "always",
|
1191
|
+
"runArgs": {
|
1192
|
+
"runNonMeasured": true,
|
1193
|
+
"runNonAddedTerm": true
|
1194
|
+
},
|
1195
|
+
"mergeStrategy": "list",
|
1196
|
+
"mergeArgs": {
|
1197
|
+
"replaceThreshold": ["value", 0.01]
|
1198
|
+
},
|
1199
|
+
"stage": 2
|
1200
|
+
},
|
1186
1201
|
{
|
1187
1202
|
"key": "emissions",
|
1188
1203
|
"model": "schmidt2007",
|
@@ -865,7 +865,20 @@
|
|
865
865
|
},
|
866
866
|
"stage": 1
|
867
867
|
},
|
868
|
-
|
868
|
+
{
|
869
|
+
"key": "impacts",
|
870
|
+
"model": "environmentalFootprintV3-1",
|
871
|
+
"value": "photochemicalOzoneCreationPotentialHumanHealthNmvocEq",
|
872
|
+
"runStrategy": "always",
|
873
|
+
"mergeStrategy": "list",
|
874
|
+
"mergeArgs": {
|
875
|
+
"replaceThreshold": [
|
876
|
+
"value",
|
877
|
+
0.01
|
878
|
+
]
|
879
|
+
},
|
880
|
+
"stage": 1
|
881
|
+
},
|
869
882
|
{
|
870
883
|
"key": "impacts",
|
871
884
|
"model": "environmentalFootprintV3-1",
|
@@ -340,6 +340,14 @@
|
|
340
340
|
"runStrategy": "add_blank_node_if_missing",
|
341
341
|
"mergeStrategy": "list",
|
342
342
|
"stage": 1
|
343
|
+
},
|
344
|
+
{
|
345
|
+
"key": "measurements",
|
346
|
+
"model": "geospatialDatabase",
|
347
|
+
"value": "altitude",
|
348
|
+
"runStrategy": "add_blank_node_if_missing",
|
349
|
+
"mergeStrategy": "list",
|
350
|
+
"stage": 1
|
343
351
|
}
|
344
352
|
],
|
345
353
|
[
|
@@ -4,7 +4,8 @@ Completeness Fresh Forage
|
|
4
4
|
This model checks if we have the requirements below and updates the
|
5
5
|
[Data Completeness](https://hestia.earth/schema/Completeness#cropResidue) value.
|
6
6
|
"""
|
7
|
-
from hestia_earth.schema import SiteSiteType
|
7
|
+
from hestia_earth.schema import SiteSiteType, TermTermType
|
8
|
+
from hestia_earth.utils.model import filter_list_term_type
|
8
9
|
from hestia_earth.utils.tools import list_sum
|
9
10
|
|
10
11
|
from hestia_earth.models.log import logRequirements
|
@@ -46,18 +47,21 @@ ALLOWED_SITE_TYPES = [
|
|
46
47
|
def _valid_input(input: dict): return is_from_model(input) and list_sum(input.get('value', [-1])) >= 0
|
47
48
|
|
48
49
|
|
50
|
+
def _inputs(node: dict): return filter_list_term_type(node.get('inputs', []), TermTermType.FORAGE)
|
51
|
+
|
52
|
+
|
49
53
|
def run(cycle: dict):
|
50
54
|
site_type = cycle.get('site', {}).get('siteType')
|
51
55
|
site_type_allowed = site_type in ALLOWED_SITE_TYPES
|
52
56
|
|
53
|
-
cycle_has_added_forage_input = any(map(_valid_input, cycle
|
57
|
+
cycle_has_added_forage_input = any(map(_valid_input, _inputs(cycle)))
|
54
58
|
|
55
59
|
animals = [
|
56
60
|
a for a in cycle.get('animals', [])
|
57
61
|
if get_lookup_value(a.get('term', {}), 'isGrazingAnimal', model=MODEL, key=MODEL_KEY)
|
58
62
|
]
|
59
63
|
all_animals_have_added_forage_input = bool(animals) and all([
|
60
|
-
any(map(_valid_input, animal
|
64
|
+
any(map(_valid_input, _inputs(animal))) for animal in animals
|
61
65
|
])
|
62
66
|
|
63
67
|
logRequirements(cycle, model=MODEL, term=None, key=MODEL_KEY,
|
@@ -4,12 +4,12 @@ Excreta (kg)
|
|
4
4
|
This model calculates the amount of excreta in `kg` based on the amount of excreta in `kg N` or `kg Vs`.
|
5
5
|
"""
|
6
6
|
from hestia_earth.schema import NodeType, TermTermType
|
7
|
-
from hestia_earth.utils.api import download_hestia
|
8
7
|
from hestia_earth.utils.model import filter_list_term_type, find_term_match
|
9
8
|
from hestia_earth.utils.tools import non_empty_list, list_sum
|
10
9
|
|
11
10
|
from hestia_earth.models.log import debugValues, logRequirements, logShouldRun
|
12
11
|
from hestia_earth.models.utils import get_kg_term_id, get_kg_N_term_id, get_kg_VS_term_id
|
12
|
+
from hestia_earth.models.utils.term import download_term
|
13
13
|
from hestia_earth.models.utils.constant import Units
|
14
14
|
from hestia_earth.models.utils.product import _new_product, convert_product_to_unit
|
15
15
|
from . import MODEL
|
@@ -68,7 +68,7 @@ def _run_product(cycle: dict, product_term_id: str):
|
|
68
68
|
]
|
69
69
|
|
70
70
|
# convert to 1kg first, then apply ratio to current value
|
71
|
-
term =
|
71
|
+
term = download_term(product_term_id, TermTermType.EXCRETA)
|
72
72
|
product = {
|
73
73
|
'term': term,
|
74
74
|
'value': [1]
|
@@ -49,16 +49,28 @@ UNITS = [
|
|
49
49
|
]
|
50
50
|
VALUE_BY_UNIT = {
|
51
51
|
Units.KG_N.value: {
|
52
|
-
Units.KG_K2O.value: lambda
|
53
|
-
|
52
|
+
Units.KG_K2O.value: lambda data: (
|
53
|
+
data.get('value') / data.get('nitrogenContent-divide')
|
54
|
+
) * data.get('potassiumContentAsK2O-multiply'),
|
55
|
+
Units.KG_P2O5.value: lambda data: (
|
56
|
+
data.get('value') / data.get('nitrogenContent-divide')
|
57
|
+
) * data.get('phosphateContentAsP2O5-multiply')
|
54
58
|
},
|
55
59
|
Units.KG_K2O.value: {
|
56
|
-
Units.KG_N.value: lambda
|
57
|
-
|
60
|
+
Units.KG_N.value: lambda data: (
|
61
|
+
data.get('value') / data.get('potassiumContentAsK2O-divide')
|
62
|
+
) * data.get('nitrogenContent-multiply'),
|
63
|
+
Units.KG_P2O5.value: lambda data: (
|
64
|
+
data.get('value') / data.get('potassiumContentAsK2O-divide')
|
65
|
+
) * data.get('phosphateContentAsP2O5-multiply')
|
58
66
|
},
|
59
67
|
Units.KG_P2O5.value: {
|
60
|
-
Units.KG_N.value: lambda
|
61
|
-
|
68
|
+
Units.KG_N.value: lambda data: (
|
69
|
+
data.get('value') / data.get('phosphateContentAsP2O5-divide')
|
70
|
+
) * data.get('nitrogenContent-multiply'),
|
71
|
+
Units.KG_K2O.value: lambda data: (
|
72
|
+
data.get('value') / data.get('phosphateContentAsP2O5-divide')
|
73
|
+
) * data.get('potassiumContentAsK2O-multiply')
|
62
74
|
}
|
63
75
|
}
|
64
76
|
|
@@ -81,6 +93,7 @@ def _include_term_ids(term_id: str):
|
|
81
93
|
|
82
94
|
def _run_input(cycle: dict, input: dict):
|
83
95
|
term_id = input.get('term', {}).get('@id')
|
96
|
+
input_term_ids = _include_term_ids(term_id)
|
84
97
|
nitrogenContent = safe_parse_float(get_term_lookup(term_id, 'nitrogenContent'), 0)
|
85
98
|
nitrogenContent_min = safe_parse_float(get_term_lookup(term_id, 'nitrogenContent-min'), None)
|
86
99
|
nitrogenContent_max = safe_parse_float(get_term_lookup(term_id, 'nitrogenContent-max'), None)
|
@@ -96,28 +109,64 @@ def _run_input(cycle: dict, input: dict):
|
|
96
109
|
min_values = non_empty_list([nitrogenContent_min, phosphateContentAsP2O5_min, potassiumContentAsK2O_min])
|
97
110
|
max_values = non_empty_list([nitrogenContent_max, phosphateContentAsP2O5_max, potassiumContentAsK2O_max])
|
98
111
|
|
99
|
-
def include_input(input_term_id):
|
112
|
+
def include_input(input_term_id: str):
|
100
113
|
to_units = Units.KG_N.value if input_term_id.endswith('KgN') else (
|
101
114
|
Units.KG_K2O.value if input_term_id.endswith('KgK2O') else Units.KG_P2O5.value
|
102
115
|
)
|
103
116
|
|
104
117
|
debugValues(cycle, model=MODEL_LOG, term=input_term_id,
|
118
|
+
from_input_id=term_id,
|
105
119
|
from_units=from_units,
|
106
120
|
to_units=to_units,
|
107
|
-
input_value=input_value
|
108
|
-
|
109
|
-
|
110
|
-
|
121
|
+
input_value=input_value,
|
122
|
+
nitrogenContent=nitrogenContent,
|
123
|
+
nitrogenContent_min=nitrogenContent_min,
|
124
|
+
nitrogenContent_max=nitrogenContent_max,
|
125
|
+
phosphateContentAsP2O5=phosphateContentAsP2O5,
|
126
|
+
phosphateContentAsP2O5_min=phosphateContentAsP2O5_min,
|
127
|
+
phosphateContentAsP2O5_max=phosphateContentAsP2O5_max,
|
128
|
+
potassiumContentAsK2O=potassiumContentAsK2O,
|
129
|
+
potassiumContentAsK2O_min=potassiumContentAsK2O_min,
|
130
|
+
potassiumContentAsK2O_max=potassiumContentAsK2O_max)
|
131
|
+
|
132
|
+
converter = VALUE_BY_UNIT.get(from_units, {}).get(to_units, lambda *args: None)
|
133
|
+
value = converter(
|
134
|
+
{
|
135
|
+
'value': input_value,
|
136
|
+
'nitrogenContent-multiply': nitrogenContent,
|
137
|
+
'nitrogenContent-divide': nitrogenContent,
|
138
|
+
'phosphateContentAsP2O5-multiply': phosphateContentAsP2O5,
|
139
|
+
'phosphateContentAsP2O5-divide': phosphateContentAsP2O5,
|
140
|
+
'potassiumContentAsK2O-multiply': potassiumContentAsK2O,
|
141
|
+
'potassiumContentAsK2O-divide': potassiumContentAsK2O,
|
142
|
+
}
|
111
143
|
)
|
112
|
-
min =
|
113
|
-
|
144
|
+
min = converter(
|
145
|
+
{
|
146
|
+
'value': input_value,
|
147
|
+
'nitrogenContent-multiply': nitrogenContent_min,
|
148
|
+
'nitrogenContent-divide': nitrogenContent_max,
|
149
|
+
'phosphateContentAsP2O5-multiply': phosphateContentAsP2O5_min,
|
150
|
+
'phosphateContentAsP2O5-divide': phosphateContentAsP2O5_max,
|
151
|
+
'potassiumContentAsK2O-multiply': potassiumContentAsK2O_min,
|
152
|
+
'potassiumContentAsK2O-divide': potassiumContentAsK2O_max,
|
153
|
+
}
|
114
154
|
) if len(min_values) >= 2 else None
|
115
|
-
max =
|
116
|
-
|
155
|
+
max = converter(
|
156
|
+
{
|
157
|
+
'value': input_value,
|
158
|
+
'nitrogenContent-multiply': nitrogenContent_max,
|
159
|
+
'nitrogenContent-divide': nitrogenContent_min,
|
160
|
+
'phosphateContentAsP2O5-multiply': phosphateContentAsP2O5_max,
|
161
|
+
'phosphateContentAsP2O5-divide': phosphateContentAsP2O5_min,
|
162
|
+
'potassiumContentAsK2O-multiply': potassiumContentAsK2O_max,
|
163
|
+
'potassiumContentAsK2O-divide': potassiumContentAsK2O_min,
|
164
|
+
}
|
117
165
|
) if len(max_values) >= 2 else None
|
166
|
+
|
118
167
|
return _input(input_term_id, value, min, max) if value else None
|
119
168
|
|
120
|
-
return list(map(include_input,
|
169
|
+
return list(map(include_input, input_term_ids))
|
121
170
|
|
122
171
|
|
123
172
|
def _should_run_input(cycle: dict, input: dict):
|
@@ -130,7 +179,8 @@ def _should_run_input(cycle: dict, input: dict):
|
|
130
179
|
# skip inputs that already have all the inlcuded term with a value
|
131
180
|
inputs = cycle.get('inputs', [])
|
132
181
|
include_term_ids = [
|
133
|
-
term_id for term_id in _include_term_ids(term_id)
|
182
|
+
term_id for term_id in _include_term_ids(term_id)
|
183
|
+
if len(find_term_match(inputs, term_id).get('value', [])) == 0
|
134
184
|
]
|
135
185
|
should_run = all([
|
136
186
|
has_value,
|
@@ -5,7 +5,7 @@ This model gap-fills depreciated amount per Cycle from Site Infrastructure node.
|
|
5
5
|
"""
|
6
6
|
from typing import Union
|
7
7
|
from hestia_earth.schema import TermTermType
|
8
|
-
from hestia_earth.utils.lookup import download_lookup
|
8
|
+
from hestia_earth.utils.lookup import download_lookup, lookup_term_ids
|
9
9
|
from hestia_earth.utils.tools import to_precision, flatten, list_sum
|
10
10
|
from hestia_earth.utils.model import filter_list_term_type
|
11
11
|
|
@@ -100,7 +100,8 @@ def _run_input(cycle: dict, input_node: dict) -> dict:
|
|
100
100
|
|
101
101
|
def _has_depreciated_term(term: dict):
|
102
102
|
lookup = download_lookup(f"{term.get('termType')}.csv")
|
103
|
-
|
103
|
+
term_ids = lookup_term_ids(lookup)
|
104
|
+
return term.get('@id') + _ID_SUFFIX in term_ids
|
104
105
|
|
105
106
|
|
106
107
|
def _should_run_input(cycle: dict, input_node: dict) -> bool:
|
@@ -1,9 +1,9 @@
|
|
1
|
-
from hestia_earth.schema import SiteSiteType
|
2
|
-
from hestia_earth.utils.api import download_hestia
|
1
|
+
from hestia_earth.schema import SiteSiteType, TermTermType
|
3
2
|
from hestia_earth.utils.model import linked_node
|
4
3
|
|
5
4
|
from hestia_earth.models.log import logRequirements, logShouldRun
|
6
5
|
from hestia_earth.models.utils.practice import _new_practice
|
6
|
+
from hestia_earth.models.utils.term import download_term
|
7
7
|
from . import MODEL
|
8
8
|
|
9
9
|
REQUIREMENTS = {
|
@@ -27,7 +27,7 @@ KEY_TERM_ID = 'genericGrassPlant'
|
|
27
27
|
def _practice():
|
28
28
|
node = _new_practice(TERM_ID)
|
29
29
|
node['value'] = [100]
|
30
|
-
node['key'] = linked_node(
|
30
|
+
node['key'] = linked_node(download_term(KEY_TERM_ID, TermTermType.LANDCOVER))
|
31
31
|
return node
|
32
32
|
|
33
33
|
|