hestia-earth-models 0.64.4__py3-none-any.whl → 0.64.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of hestia-earth-models might be problematic. Click here for more details.

Files changed (62) hide show
  1. hestia_earth/models/blonkConsultants2016/ch4ToAirNaturalVegetationBurning.py +5 -9
  2. hestia_earth/models/blonkConsultants2016/co2ToAirAboveGroundBiomassStockChangeLandUseChange.py +5 -9
  3. hestia_earth/models/blonkConsultants2016/n2OToAirNaturalVegetationBurningDirect.py +6 -13
  4. hestia_earth/models/cycle/animal/input/properties.py +6 -0
  5. hestia_earth/models/cycle/completeness/soilAmendment.py +3 -2
  6. hestia_earth/models/cycle/concentrateFeed.py +10 -4
  7. hestia_earth/models/cycle/input/properties.py +6 -0
  8. hestia_earth/models/cycle/liveAnimal.py +2 -2
  9. hestia_earth/models/cycle/milkYield.py +3 -3
  10. hestia_earth/models/cycle/otherSitesArea.py +59 -0
  11. hestia_earth/models/cycle/otherSitesUnusedDuration.py +9 -8
  12. hestia_earth/models/cycle/pastureSystem.py +3 -2
  13. hestia_earth/models/cycle/product/properties.py +6 -0
  14. hestia_earth/models/cycle/siteArea.py +83 -0
  15. hestia_earth/models/cycle/stockingDensityAnimalHousingAverage.py +28 -16
  16. hestia_earth/models/cycle/utils.py +1 -1
  17. hestia_earth/models/environmentalFootprintV3/soilQualityIndexLandOccupation.py +128 -0
  18. hestia_earth/models/environmentalFootprintV3/utils.py +17 -0
  19. hestia_earth/models/ipcc2006/co2ToAirOrganicSoilCultivation.py +17 -6
  20. hestia_earth/models/ipcc2006/n2OToAirOrganicSoilCultivationDirect.py +17 -6
  21. hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +904 -0
  22. hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChangeManagementChange.py +70 -618
  23. hestia_earth/models/mocking/search-results.json +395 -323
  24. hestia_earth/models/pooreNemecek2018/saplings.py +10 -7
  25. hestia_earth/models/site/management.py +18 -14
  26. hestia_earth/models/utils/__init__.py +38 -0
  27. hestia_earth/models/utils/array_builders.py +63 -52
  28. hestia_earth/models/utils/blank_node.py +137 -82
  29. hestia_earth/models/utils/descriptive_stats.py +3 -239
  30. hestia_earth/models/utils/feedipedia.py +15 -2
  31. hestia_earth/models/utils/landCover.py +9 -0
  32. hestia_earth/models/utils/lookup.py +13 -2
  33. hestia_earth/models/utils/measurement.py +3 -28
  34. hestia_earth/models/utils/stats.py +429 -0
  35. hestia_earth/models/utils/term.py +15 -3
  36. hestia_earth/models/utils/time_series.py +90 -0
  37. hestia_earth/models/version.py +1 -1
  38. {hestia_earth_models-0.64.4.dist-info → hestia_earth_models-0.64.5.dist-info}/METADATA +1 -1
  39. {hestia_earth_models-0.64.4.dist-info → hestia_earth_models-0.64.5.dist-info}/RECORD +62 -48
  40. tests/models/blonkConsultants2016/test_ch4ToAirNaturalVegetationBurning.py +2 -2
  41. tests/models/blonkConsultants2016/test_co2ToAirAboveGroundBiomassStockChangeLandUseChange.py +2 -2
  42. tests/models/blonkConsultants2016/test_n2OToAirNaturalVegetationBurningDirect.py +2 -2
  43. tests/models/cycle/completeness/test_soilAmendment.py +1 -1
  44. tests/models/cycle/test_liveAnimal.py +1 -1
  45. tests/models/cycle/test_milkYield.py +1 -1
  46. tests/models/cycle/test_otherSitesArea.py +68 -0
  47. tests/models/cycle/test_siteArea.py +51 -0
  48. tests/models/cycle/test_stockingDensityAnimalHousingAverage.py +2 -2
  49. tests/models/environmentalFootprintV3/test_soilQualityIndexLandOccupation.py +136 -0
  50. tests/models/ipcc2019/test_co2ToAirCarbonStockChange_utils.py +50 -0
  51. tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChangeManagementChange.py +1 -39
  52. tests/models/pooreNemecek2018/test_saplings.py +1 -1
  53. tests/models/site/test_management.py +3 -153
  54. tests/models/utils/test_array_builders.py +67 -6
  55. tests/models/utils/test_blank_node.py +191 -7
  56. tests/models/utils/test_descriptive_stats.py +2 -86
  57. tests/models/utils/test_measurement.py +1 -22
  58. tests/models/utils/test_stats.py +186 -0
  59. tests/models/utils/test_time_series.py +88 -0
  60. {hestia_earth_models-0.64.4.dist-info → hestia_earth_models-0.64.5.dist-info}/LICENSE +0 -0
  61. {hestia_earth_models-0.64.4.dist-info → hestia_earth_models-0.64.5.dist-info}/WHEEL +0 -0
  62. {hestia_earth_models-0.64.4.dist-info → hestia_earth_models-0.64.5.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,4 @@
1
- from hestia_earth.schema import EmissionMethodTier
1
+ from hestia_earth.schema import EmissionMethodTier, CycleFunctionalUnit
2
2
 
3
3
  from hestia_earth.models.log import logRequirements, logShouldRun
4
4
  from hestia_earth.models.utils.emission import _new_emission
@@ -8,16 +8,10 @@ from . import MODEL
8
8
 
9
9
  REQUIREMENTS = {
10
10
  "Cycle": {
11
+ "functionalUnit": "1 ha",
11
12
  "or": [
12
13
  {
13
- "@doc": "if the [cycle.functionalUnit](https://hestia.earth/schema/Cycle#functionalUnit) = 1 ha, additional properties are required", # noqa: E501
14
14
  "cycleDuration": "",
15
- "products": [{
16
- "@type": "Product",
17
- "primary": "True",
18
- "value": "> 0",
19
- "economicValueShare": "> 0"
20
- }],
21
15
  "practices": [{"@type": "Practice", "value": "", "term.@id": "longFallowRatio"}]
22
16
  },
23
17
  {
@@ -65,14 +59,16 @@ def _run(land_occupation: float, ch4_forest_biomass_burning: float):
65
59
 
66
60
 
67
61
  def _should_run(cycle: dict):
62
+ is_1_ha_functional_unit = cycle.get('functionalUnit') == CycleFunctionalUnit._1_HA.value
68
63
  land_occupation = land_occupation_per_ha(MODEL, TERM_ID, cycle)
69
64
  ch4_forest_biomass_burning = get_emission_factor(TERM_ID, cycle, 'ch4forestBiomassBurning')
70
65
 
71
66
  logRequirements(cycle, model=MODEL, term=TERM_ID,
67
+ is_1_ha_functional_unit=is_1_ha_functional_unit,
72
68
  land_occupation=land_occupation,
73
69
  ch4_forest_biomass_burning=ch4_forest_biomass_burning)
74
70
 
75
- should_run = all([land_occupation, ch4_forest_biomass_burning is not None])
71
+ should_run = all([is_1_ha_functional_unit, land_occupation, ch4_forest_biomass_burning is not None])
76
72
  logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
77
73
  return should_run, land_occupation, ch4_forest_biomass_burning
78
74
 
@@ -1,4 +1,4 @@
1
- from hestia_earth.schema import EmissionMethodTier
1
+ from hestia_earth.schema import EmissionMethodTier, CycleFunctionalUnit
2
2
 
3
3
  from hestia_earth.models.log import logRequirements, logShouldRun
4
4
  from hestia_earth.models.utils.emission import _new_emission
@@ -8,16 +8,10 @@ from . import MODEL
8
8
 
9
9
  REQUIREMENTS = {
10
10
  "Cycle": {
11
+ "functionalUnit": "1 ha",
11
12
  "or": [
12
13
  {
13
- "@doc": "if the [cycle.functionalUnit](https://hestia.earth/schema/Cycle#functionalUnit) = 1 ha, additional properties are required", # noqa: E501
14
14
  "cycleDuration": "",
15
- "products": [{
16
- "@type": "Product",
17
- "primary": "True",
18
- "value": "> 0",
19
- "economicValueShare": "> 0"
20
- }],
21
15
  "practices": [{"@type": "Practice", "value": "", "term.@id": "longFallowRatio"}]
22
16
  },
23
17
  {
@@ -65,14 +59,16 @@ def _run(land_occupation: float, co2_land_use_change: float):
65
59
 
66
60
 
67
61
  def _should_run(cycle: dict):
62
+ is_1_ha_functional_unit = cycle.get('functionalUnit') == CycleFunctionalUnit._1_HA.value
68
63
  land_occupation = land_occupation_per_ha(MODEL, TERM_ID, cycle)
69
64
  co2_land_use_change = get_emission_factor(TERM_ID, cycle, 'co2LandUseChange')
70
65
 
71
66
  logRequirements(cycle, model=MODEL, term=TERM_ID,
67
+ is_1_ha_functional_unit=is_1_ha_functional_unit,
72
68
  land_occupation=land_occupation,
73
69
  co2_land_use_change=co2_land_use_change)
74
70
 
75
- should_run = all([land_occupation, co2_land_use_change is not None])
71
+ should_run = all([is_1_ha_functional_unit, land_occupation, co2_land_use_change is not None])
76
72
  logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
77
73
  return should_run, land_occupation, co2_land_use_change
78
74
 
@@ -1,4 +1,4 @@
1
- from hestia_earth.schema import EmissionMethodTier
1
+ from hestia_earth.schema import EmissionMethodTier, CycleFunctionalUnit
2
2
 
3
3
  from hestia_earth.models.log import logRequirements, logShouldRun
4
4
  from hestia_earth.models.utils.emission import _new_emission
@@ -8,20 +8,11 @@ from . import MODEL
8
8
 
9
9
  REQUIREMENTS = {
10
10
  "Cycle": {
11
+ "functionalUnit": "1 ha",
11
12
  "or": [
12
13
  {
13
- "@doc": "if the [cycle.functionalUnit](https://hestia.earth/schema/Cycle#functionalUnit) = 1 ha, additional properties are required", # noqa: E501
14
14
  "cycleDuration": "",
15
- "products": [{
16
- "@type": "Product",
17
- "primary": "True",
18
- "value": "> 0",
19
- "economicValueShare": "> 0"
20
- }],
21
- "site": {
22
- "@type": "Site",
23
- "practices": [{"@type": "Practice", "value": "", "term.@id": "longFallowRatio"}]
24
- }
15
+ "practices": [{"@type": "Practice", "value": "", "term.@id": "longFallowRatio"}]
25
16
  },
26
17
  {
27
18
  "@doc": "for plantations, additional properties are required",
@@ -68,14 +59,16 @@ def _run(land_occupation: float, n2o_forest_biomass_burning: float):
68
59
 
69
60
 
70
61
  def _should_run(cycle: dict):
62
+ is_1_ha_functional_unit = cycle.get('functionalUnit') == CycleFunctionalUnit._1_HA.value
71
63
  land_occupation = land_occupation_per_ha(MODEL, TERM_ID, cycle)
72
64
  n2o_forest_biomass_burning = get_emission_factor(TERM_ID, cycle, 'n2oforestBiomassBurning')
73
65
 
74
66
  logRequirements(cycle, model=MODEL, term=TERM_ID,
67
+ is_1_ha_functional_unit=is_1_ha_functional_unit,
75
68
  land_occupation=land_occupation,
76
69
  n2o_forest_biomass_burning=n2o_forest_biomass_burning)
77
70
 
78
- should_run = all([land_occupation, n2o_forest_biomass_burning is not None])
71
+ should_run = all([is_1_ha_functional_unit, land_occupation, n2o_forest_biomass_burning is not None])
79
72
  logShouldRun(cycle, MODEL, TERM_ID, should_run, methodTier=TIER)
80
73
  return should_run, land_occupation, n2o_forest_biomass_burning
81
74
 
@@ -41,6 +41,12 @@ RETURNS = {
41
41
  }]
42
42
  }]
43
43
  }
44
+ LOOKUPS = {
45
+ "crop-property": "dryMatter",
46
+ "forage-property": "dryMatter",
47
+ "processedFoor-property": "dryMatter",
48
+ "property": "feedipediaConversionEnum"
49
+ }
44
50
  MODEL_KEY = 'properties'
45
51
 
46
52
 
@@ -6,7 +6,8 @@ This model checks if we have the requirements below and updates the
6
6
  """
7
7
  from hestia_earth.models.log import logRequirements
8
8
  from hestia_earth.models.utils import is_from_model
9
- from hestia_earth.models.utils.measurement import most_relevant_measurement, measurement_value
9
+ from hestia_earth.models.utils.measurement import measurement_value
10
+ from hestia_earth.models.utils.blank_node import most_relevant_blank_node_by_id
10
11
  from . import MODEL
11
12
 
12
13
  REQUIREMENTS = {
@@ -30,7 +31,7 @@ MODEL_KEY = 'soilAmendment'
30
31
  def run(cycle: dict):
31
32
  end_date = cycle.get('endDate')
32
33
  measurements = cycle.get('site', {}).get('measurements', [])
33
- soilPh_measurement = most_relevant_measurement(measurements, 'soilPh', end_date)
34
+ soilPh_measurement = most_relevant_blank_node_by_id(measurements, 'soilPh', end_date)
34
35
  soilPh = measurement_value(soilPh_measurement)
35
36
 
36
37
  soilPh_added = is_from_model(soilPh_measurement)
@@ -9,6 +9,7 @@ from hestia_earth.utils.model import find_primary_product
9
9
  from hestia_earth.utils.tools import list_sum, non_empty_list, flatten
10
10
 
11
11
  from hestia_earth.models.log import logRequirements, logShouldRun
12
+ from hestia_earth.models.utils.blank_node import merge_blank_nodes
12
13
  from hestia_earth.models.utils.property import _new_property, get_node_property_value, get_property_lookup_value
13
14
  from hestia_earth.models.utils.term import get_digestible_energy_terms, get_energy_digestibility_terms
14
15
  from . import MODEL
@@ -41,6 +42,7 @@ LOOKUPS = {
41
42
  "property": "commonToSupplementInAnimalFeed"
42
43
  }
43
44
  TERM_ID = 'concentrateFeedBlend,concentrateFeedUnspecified,feedMix'
45
+ FIRST_TERM_ID = TERM_ID.split(',')[0]
44
46
  INPUT_TERM_TYPES = [
45
47
  TermTermType.CROP.value,
46
48
  TermTermType.FORAGE.value,
@@ -88,7 +90,7 @@ def _calculate_value(cycle: dict, product: dict, inputs: list, property_id: str,
88
90
  def _calculate_default_value(cycle: dict, product: dict, inputs: list, property_id: str):
89
91
  values = [(
90
92
  i.get('term', {}).get('@id'),
91
- get_node_property_value(MODEL, i, property_id, handle_percents=False, term=TERM_ID),
93
+ get_node_property_value(MODEL, i, property_id, handle_percents=False, term=FIRST_TERM_ID),
92
94
  list_sum(i.get('value', []))
93
95
  ) for i in inputs]
94
96
  return _calculate_value(cycle, product, inputs, property_id, values)
@@ -97,8 +99,9 @@ def _calculate_default_value(cycle: dict, product: dict, inputs: list, property_
97
99
  def _calculate_N_value(cycle: dict, product: dict, inputs: list, property_id: str):
98
100
  values = [(
99
101
  i.get('term', {}).get('@id'),
100
- get_node_property_value(MODEL, i, property_id, handle_percents=False, term=TERM_ID) or
101
- get_node_property_value(MODEL, i, 'crudeProteinContent', default=0, handle_percents=False, term=TERM_ID) * 0.16,
102
+ get_node_property_value(MODEL, i, property_id, handle_percents=False, term=FIRST_TERM_ID) or
103
+ get_node_property_value(
104
+ MODEL, i, 'crudeProteinContent', default=0, handle_percents=False, term=FIRST_TERM_ID) * 0.16,
102
105
  list_sum(i.get('value', []))
103
106
  ) for i in inputs]
104
107
  return _calculate_value(cycle, product, inputs, property_id, values)
@@ -136,7 +139,10 @@ def _run_property(cycle: dict, product: dict, inputs: list):
136
139
 
137
140
  def _run(cycle: dict, product: dict, inputs: list):
138
141
  properties = non_empty_list(flatten(map(_run_property(cycle, product, inputs), PROPERTY_TO_VALUE.items())))
139
- return [{**product, 'properties': product.get('properties', []) + properties}] if len(properties) > 0 else []
142
+ return [product | {
143
+ # keep the original values, so merge orignal in new values
144
+ 'properties': merge_blank_nodes(properties, product.get('properties', []))
145
+ }] if len(properties) > 0 else []
140
146
 
141
147
 
142
148
  def _should_run(cycle: dict):
@@ -34,6 +34,12 @@ RETURNS = {
34
34
  }]
35
35
  }]
36
36
  }
37
+ LOOKUPS = {
38
+ "crop-property": "dryMatter",
39
+ "forage-property": "dryMatter",
40
+ "processedFoor-property": "dryMatter",
41
+ "property": "feedipediaConversionEnum"
42
+ }
37
43
  MODEL_KEY = 'properties'
38
44
 
39
45
 
@@ -10,7 +10,7 @@ from hestia_earth.utils.tools import list_sum, safe_parse_float
10
10
  from hestia_earth.models.log import logRequirements, logShouldRun
11
11
  from hestia_earth.models.utils.product import _new_product
12
12
  from hestia_earth.models.utils.site import valid_site_type
13
- from .utils import _get_liveAnimal_term_id
13
+ from .utils import get_liveAnimal_term_id
14
14
  from . import MODEL
15
15
 
16
16
  REQUIREMENTS = {
@@ -74,7 +74,7 @@ def _should_run(cycle: dict):
74
74
  propertyPerHead_value = safe_parse_float(propertyPerHead.get('value'), None)
75
75
 
76
76
  # make sure the `liveAnimal` Term is not already present as a Product or Input
77
- term_id = _get_liveAnimal_term_id(product, model_key=MODEL_KEY)
77
+ term_id = get_liveAnimal_term_id(product, model_key=MODEL_KEY)
78
78
  has_liveAnimal_product = find_term_match(cycle.get('products', []), term_id, None) is not None
79
79
  has_liveAnimal_input = find_term_match(cycle.get('products', []), term_id, None) is not None
80
80
 
@@ -16,7 +16,7 @@ from hestia_earth.models.log import logRequirements, logShouldRun, log_blank_nod
16
16
  from hestia_earth.models.utils.practice import _new_practice
17
17
  from hestia_earth.models.utils.site import valid_site_type
18
18
  from hestia_earth.models.utils.term import get_lookup_value
19
- from .utils import _get_liveAnimal_term_id
19
+ from .utils import get_liveAnimal_term_id
20
20
  from . import MODEL
21
21
 
22
22
  REQUIREMENTS = {
@@ -85,7 +85,7 @@ def _run(cycle: dict, product: dict):
85
85
  term = product.get('term', {})
86
86
  practice_id = _practice_id(term)
87
87
 
88
- live_animal_term_id = _get_liveAnimal_term_id(product, model_key=MODEL_KEY)
88
+ live_animal_term_id = get_liveAnimal_term_id(product, model_key=MODEL_KEY)
89
89
  live_animal_node = find_term_match(cycle.get('animals', []), live_animal_term_id)
90
90
 
91
91
  value = list_sum(product.get('value')) / cycleDuration / live_animal_node.get('value')
@@ -115,7 +115,7 @@ def _should_run_product(cycle: dict, product: dict):
115
115
 
116
116
  has_product_value = list_sum(product.get('value', [])) > 0
117
117
 
118
- live_animal_term_id = _get_liveAnimal_term_id(product, model_key=MODEL_KEY)
118
+ live_animal_term_id = get_liveAnimal_term_id(product, model_key=MODEL_KEY)
119
119
  live_animal_node = find_term_match(cycle.get('animals', []), live_animal_term_id)
120
120
  has_live_animal_node_value = live_animal_node.get('value', 0) > 0
121
121
 
@@ -0,0 +1,59 @@
1
+ """
2
+ Other Sites Area
3
+
4
+ For animal production cycles, use the `stockingDensityAnimalHousingAverage` to calculate the average area required
5
+ for all animals.
6
+ """
7
+ from hestia_earth.schema import SiteSiteType
8
+
9
+ from .siteArea import _is_site_valid, _should_run as _should_run_site, _run
10
+
11
+ REQUIREMENTS = {
12
+ "Cycle": {
13
+ "otherSites": [{
14
+ "@type": "Site",
15
+ "siteType": "animal housing"
16
+ }],
17
+ "none": {
18
+ "site": {
19
+ "@type": "Site",
20
+ "siteType": "animal housing"
21
+ }
22
+ },
23
+ "animals": [{
24
+ "@type": "Animal",
25
+ "term.termType": "liveAnimal",
26
+ "value": "> 0"
27
+ }],
28
+ "practices": [{
29
+ "@type": "Practice",
30
+ "term.@id": "stockingDensityAnimalHousingAverage"
31
+ }]
32
+ }
33
+ }
34
+ RETURNS = {
35
+ "The otherSitesArea as an array of number": ""
36
+ }
37
+ MODEL_KEY = 'otherSitesArea'
38
+
39
+
40
+ def _should_run(cycle: dict):
41
+ other_sites_valid = list(filter(
42
+ lambda site: site.get('siteType') == SiteSiteType.ANIMAL_HOUSING.value,
43
+ cycle.get('otherSites', [])
44
+ ))
45
+
46
+ should_run = all([
47
+ not _is_site_valid(cycle.get('site', {})),
48
+ len(other_sites_valid) == 1
49
+ ]) and _should_run_site(cycle, other_sites_valid[0], key=MODEL_KEY)
50
+ return should_run
51
+
52
+
53
+ def run(cycle: dict):
54
+ other_sites = cycle.get('otherSites', [])
55
+ other_sites_area = cycle.get('otherSitesArea')
56
+ return [
57
+ _run(cycle) if _is_site_valid(site) else other_sites_area[index] if other_sites_area else None
58
+ for index, site in enumerate(other_sites)
59
+ ] if _should_run(cycle) else []
@@ -51,6 +51,13 @@ def _find_site_practice(practices: list, site_id: str):
51
51
  ).get('value'), None)
52
52
 
53
53
 
54
+ def _is_valid(data: dict): return all([
55
+ data.get('valid-site'),
56
+ data.get(_PRACTICE_TERM_ID) is not None,
57
+ data.get('site-duration') is not None
58
+ ])
59
+
60
+
54
61
  def _should_run(cycle: dict):
55
62
  otherSitesDuration = cycle.get('otherSitesDuration', [])
56
63
  practices = cycle.get('practices', [])
@@ -66,13 +73,7 @@ def _should_run(cycle: dict):
66
73
  for index, site in enumerate(cycle.get('otherSites', []))
67
74
  ]
68
75
 
69
- has_valid_sites = any([
70
- all([
71
- data.get('valid-site'),
72
- data.get(_PRACTICE_TERM_ID) is not None,
73
- (data.get('site-duration') or 0) >= 0
74
- ]) for data in site_data
75
- ])
76
+ has_valid_sites = any(map(_is_valid, site_data))
76
77
 
77
78
  logRequirements(cycle, model=MODEL, key=MODEL_KEY,
78
79
  has_valid_sites=has_valid_sites,
@@ -86,6 +87,6 @@ def _should_run(cycle: dict):
86
87
  def run(cycle: dict):
87
88
  should_run, site_data = _should_run(cycle)
88
89
  return [
89
- _run(data.get('site-duration'), data.get(_PRACTICE_TERM_ID)) if data.get('valid-site') else None
90
+ _run(data.get('site-duration'), data.get(_PRACTICE_TERM_ID)) if _is_valid(data) else None
90
91
  for data in site_data
91
92
  ] if should_run else []
@@ -10,7 +10,8 @@ from hestia_earth.utils.model import find_primary_product, find_term_match
10
10
  from hestia_earth.models.log import logRequirements, logShouldRun, debugValues
11
11
  from hestia_earth.models.utils.practice import _new_practice
12
12
  from hestia_earth.models.utils.site import valid_site_type
13
- from hestia_earth.models.utils.measurement import most_relevant_measurement, measurement_value
13
+ from hestia_earth.models.utils.measurement import measurement_value
14
+ from hestia_earth.models.utils.blank_node import most_relevant_blank_node_by_id
14
15
  from hestia_earth.models.utils.term import get_pasture_system_terms
15
16
  from . import MODEL
16
17
 
@@ -46,7 +47,7 @@ def _practice(term_id: str):
46
47
  def _run(cycle: dict):
47
48
  end_date = cycle.get('endDate')
48
49
  measurements = cycle.get('site', {}).get('measurements', [])
49
- slope = measurement_value(most_relevant_measurement(measurements, 'slope', end_date))
50
+ slope = measurement_value(most_relevant_blank_node_by_id(measurements, 'slope', end_date))
50
51
  term_id = 'confinedPastureSystem' if slope <= 2.5 else 'hillyPastureSystem'
51
52
 
52
53
  debugValues(cycle, model=MODEL, term=term_id,
@@ -28,6 +28,12 @@ RETURNS = {
28
28
  }]
29
29
  }]
30
30
  }
31
+ LOOKUPS = {
32
+ "crop-property": "dryMatter",
33
+ "forage-property": "dryMatter",
34
+ "processedFoor-property": "dryMatter",
35
+ "property": "feedipediaConversionEnum"
36
+ }
31
37
  MODEL_KEY = 'properties'
32
38
  DRY_MATTER_TERM_ID = 'dryMatter'
33
39
 
@@ -0,0 +1,83 @@
1
+ """
2
+ Site Area
3
+
4
+ For animal production cycles, use the `stockingDensityAnimalHousingAverage` to calculate the average area required
5
+ for all animals.
6
+ """
7
+ from hestia_earth.schema import TermTermType, SiteSiteType
8
+ from hestia_earth.utils.model import filter_list_term_type, find_term_match
9
+ from hestia_earth.utils.tools import list_sum
10
+
11
+ from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
12
+ from . import MODEL
13
+
14
+ REQUIREMENTS = {
15
+ "Cycle": {
16
+ "site": {
17
+ "@type": "Site",
18
+ "siteType": "animal housing"
19
+ },
20
+ "none": {
21
+ "otherSites": [{
22
+ "@type": "Site",
23
+ "siteType": "animal housing"
24
+ }]
25
+ },
26
+ "animals": [{
27
+ "@type": "Animal",
28
+ "term.termType": "liveAnimal",
29
+ "value": "> 0"
30
+ }],
31
+ "practices": [{
32
+ "@type": "Practice",
33
+ "term.@id": "stockingDensityAnimalHousingAverage",
34
+ "value": "> 0"
35
+ }]
36
+ }
37
+ }
38
+ RETURNS = {
39
+ "The siteArea as a number": ""
40
+ }
41
+ MODEL_KEY = 'siteArea'
42
+
43
+
44
+ def _is_site_valid(site: dict): return site.get('siteType') == SiteSiteType.ANIMAL_HOUSING.value
45
+
46
+
47
+ def _run(cycle: dict):
48
+ animals = filter_list_term_type(cycle.get('animals', []), TermTermType.LIVEANIMAL)
49
+ stocking_density = find_term_match(
50
+ cycle.get('practices', []), 'stockingDensityAnimalHousingAverage', {}).get('value', [])
51
+ return round(list_sum([a.get('value') for a in animals]) / list_sum(stocking_density), 7)
52
+
53
+
54
+ def _should_run(cycle: dict, site: dict, key=MODEL_KEY):
55
+ site_type_valid = _is_site_valid(site)
56
+ animals = filter_list_term_type(cycle.get('animals', []), TermTermType.LIVEANIMAL)
57
+ values = [{
58
+ 'id': p.get('term', {}).get('@id'),
59
+ 'value': p.get('value')
60
+ } for p in animals]
61
+ has_animals = bool(animals)
62
+ stocking_density = list_sum(
63
+ find_term_match(cycle.get('practices', []), 'stockingDensityAnimalHousingAverage', {}).get('value', [-1])
64
+ )
65
+
66
+ logRequirements(cycle, model=MODEL, key=key,
67
+ site_type_valid=site_type_valid,
68
+ values=log_as_table(values),
69
+ stocking_density=stocking_density)
70
+
71
+ should_run = all([
72
+ site_type_valid,
73
+ has_animals,
74
+ (stocking_density or 0) > 0
75
+ ])
76
+ logShouldRun(cycle, MODEL, None, should_run, key=key)
77
+ return should_run
78
+
79
+
80
+ def run(cycle: dict):
81
+ site = cycle.get('site')
82
+ has_other_sites_valid = any(map(_is_site_valid, cycle.get('otherSites', [])))
83
+ return _run(cycle) if _should_run(cycle, site, key=MODEL_KEY) and not has_other_sites_valid else None
@@ -1,17 +1,18 @@
1
1
  from hestia_earth.schema import TermTermType
2
- from hestia_earth.utils.model import find_primary_product
2
+ from hestia_earth.utils.model import filter_list_term_type
3
3
 
4
- from hestia_earth.models.log import logRequirements, logShouldRun
4
+ from hestia_earth.models.log import logRequirements, logShouldRun, log_as_table
5
+ from hestia_earth.models.utils import weighted_average
5
6
  from hestia_earth.models.utils.blank_node import get_lookup_value
6
7
  from hestia_earth.models.utils.practice import _new_practice
7
8
  from . import MODEL
8
9
 
9
10
  REQUIREMENTS = {
10
11
  "Cycle": {
11
- "products": [{
12
- "@type": "Product",
13
- "primary": "True",
14
- "term.termType": "liveAnimal"
12
+ "animals": [{
13
+ "@type": "Animal",
14
+ "term.termType": "liveAnimal",
15
+ "value": "> 0"
15
16
  }]
16
17
  }
17
18
  }
@@ -32,21 +33,32 @@ def _practice(value: float):
32
33
  return practice
33
34
 
34
35
 
35
- def _should_run(cycle: dict):
36
- product = find_primary_product(cycle)
37
- is_live_animal_product = (product or {}).get('term', {}).get('termType') == TermTermType.LIVEANIMAL.value
36
+ def _run(values: list):
37
+ # take a weighted average
38
+ value = weighted_average([
39
+ (p.get('lookup'), p.get('value')) for p in values
40
+ ])
41
+ return [_practice(value)]
42
+
38
43
 
39
- lookup_value = get_lookup_value((product or {}).get('term', {}), LOOKUPS['liveAnimal'])
44
+ def _should_run(cycle: dict):
45
+ animals = filter_list_term_type(cycle.get('animals', []), TermTermType.LIVEANIMAL)
46
+ values = [{
47
+ 'id': p.get('term', {}).get('@id'),
48
+ 'value': p.get('value'),
49
+ 'lookup': get_lookup_value(p.get('term', {}), LOOKUPS['liveAnimal'])
50
+ } for p in animals]
51
+ has_animals = bool(animals)
52
+ has_values = all([all([p.get('value') is not None, p.get('lookup') is not None]) for p in values])
40
53
 
41
54
  logRequirements(cycle, model=MODEL, term=TERM_ID,
42
- is_live_animal_product=is_live_animal_product,
43
- lookup_value=lookup_value)
55
+ values=log_as_table(values))
44
56
 
45
- should_run = all([is_live_animal_product > 0, lookup_value is not None])
57
+ should_run = all([has_animals, has_values])
46
58
  logShouldRun(cycle, MODEL, TERM_ID, should_run)
47
- return should_run, lookup_value
59
+ return should_run, values
48
60
 
49
61
 
50
62
  def run(cycle: dict):
51
- should_run, lookup_value = _should_run(cycle)
52
- return _practice(lookup_value) if should_run else []
63
+ should_run, values = _should_run(cycle)
64
+ return _run(values) if should_run else []
@@ -5,7 +5,7 @@ from hestia_earth.models.utils.term import get_lookup_value
5
5
  from . import MODEL
6
6
 
7
7
 
8
- def _get_liveAnimal_term_id(product: dict, **log_ars):
8
+ def get_liveAnimal_term_id(product: dict, **log_ars):
9
9
  term_id = get_lookup_value(product.get('term', {}), 'liveAnimalTermId', model=MODEL, **log_ars)
10
10
  return term_id.split(';')[0] if term_id else None
11
11