hestia-earth-models 0.59.5__py3-none-any.whl → 0.59.6__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 (37) hide show
  1. hestia_earth/models/cycle/animal/milkYield.py +86 -0
  2. hestia_earth/models/cycle/endDate.py +50 -0
  3. hestia_earth/models/cycle/inorganicFertiliser.py +3 -2
  4. hestia_earth/models/cycle/milkYield.py +8 -3
  5. hestia_earth/models/cycle/pre_checks/__init__.py +1 -2
  6. hestia_earth/models/cycle/startDate.py +42 -0
  7. hestia_earth/models/faostat2018/liveweightPerHead.py +77 -41
  8. hestia_earth/models/faostat2018/product/price.py +30 -55
  9. hestia_earth/models/faostat2018/utils.py +10 -2
  10. hestia_earth/models/haversineFormula/transport/distance.py +6 -3
  11. hestia_earth/models/ipcc2006/n2OToAirInorganicFertiliserIndirect.py +1 -1
  12. hestia_earth/models/ipcc2019/organicCarbonPerHa.py +19 -4
  13. hestia_earth/models/ipcc2019/pastureGrass.py +2 -1
  14. hestia_earth/models/linkedImpactAssessment/__init__.py +3 -3
  15. hestia_earth/models/mocking/search-results.json +244 -232
  16. hestia_earth/models/schmidt2007/h2SToAirWasteTreatment.py +58 -0
  17. hestia_earth/models/site/management.py +3 -1
  18. hestia_earth/models/utils/__init__.py +4 -1
  19. hestia_earth/models/utils/animalProduct.py +6 -4
  20. hestia_earth/models/utils/blank_node.py +3 -2
  21. hestia_earth/models/utils/product.py +9 -1
  22. hestia_earth/models/version.py +1 -1
  23. {hestia_earth_models-0.59.5.dist-info → hestia_earth_models-0.59.6.dist-info}/METADATA +1 -1
  24. {hestia_earth_models-0.59.5.dist-info → hestia_earth_models-0.59.6.dist-info}/RECORD +35 -29
  25. tests/models/cycle/animal/test_milkYield.py +43 -0
  26. tests/models/cycle/test_endDate.py +24 -0
  27. tests/models/cycle/test_startDate.py +22 -0
  28. tests/models/faostat2018/product/test_price.py +25 -45
  29. tests/models/faostat2018/test_liveweightPerHead.py +106 -42
  30. tests/models/ipcc2019/test_organicCarbonPerHa.py +8 -0
  31. tests/models/schmidt2007/test_h2SToAirWasteTreatment.py +45 -0
  32. tests/models/utils/test_blank_node.py +71 -3
  33. hestia_earth/models/cycle/pre_checks/startDate.py +0 -52
  34. tests/models/cycle/pre_checks/test_startDate.py +0 -44
  35. {hestia_earth_models-0.59.5.dist-info → hestia_earth_models-0.59.6.dist-info}/LICENSE +0 -0
  36. {hestia_earth_models-0.59.5.dist-info → hestia_earth_models-0.59.6.dist-info}/WHEEL +0 -0
  37. {hestia_earth_models-0.59.5.dist-info → hestia_earth_models-0.59.6.dist-info}/top_level.txt +0 -0
@@ -1,61 +1,125 @@
1
1
  from unittest.mock import patch
2
2
  import json
3
3
 
4
- from hestia_earth.schema import TermTermType
5
4
  from tests.utils import fixtures_path, fake_new_property
6
5
 
7
- from hestia_earth.models.faostat2018.liveweightPerHead import MODEL, TERM_ID, _should_run, run
6
+ from hestia_earth.models.faostat2018.liveweightPerHead import MODEL, TERM_ID, run
8
7
 
9
8
  class_path = f"hestia_earth.models.{MODEL}.{TERM_ID}"
10
9
  fixtures_folder = f"{fixtures_path}/{MODEL}/{TERM_ID}"
11
- FAO_TERM = {
12
- '@id': 'meatChickenReadyToCookWeight',
13
- '@type': 'Term',
14
- 'units': 'kg ready-to-cook weight',
15
- 'defaultProperties': [{
16
- 'term': {'@id': 'processingConversionLiveweightToColdCarcassWeight', 'units': '%'},
17
- 'value': 72.30401869158878
18
- }, {
19
- 'term': {'@id': 'processingConversionColdCarcassWeightToReadyToCookWeight', 'units': '%'},
20
- 'value': 72.45065789473684
21
- }]
22
- }
23
-
24
-
25
- def test_should_run():
26
- cycle = {}
27
-
28
- # no animal products => no run
29
- should_run, *args = _should_run(cycle)
30
- assert not should_run
31
-
32
- # with animal products => no run
33
- cycle['products'] = [
34
- {
35
- 'term': {
36
- 'termType': TermTermType.ANIMALPRODUCT.value,
37
- 'units': 'kg liveweight'
10
+
11
+
12
+ @patch(f"{class_path}._new_property", side_effect=fake_new_property)
13
+ def test_run_animalProduct(*args):
14
+ with open(f"{fixtures_folder}/animalProduct/cycle.jsonld", encoding='utf-8') as f:
15
+ cycle = json.load(f)
16
+
17
+ with open(f"{fixtures_folder}/animalProduct/result.jsonld", encoding='utf-8') as f:
18
+ expected = json.load(f)
19
+
20
+ value = run(cycle)
21
+ assert value == expected
22
+
23
+
24
+ @patch(f"{class_path}._new_property", side_effect=fake_new_property)
25
+ @patch(f"{class_path}.download_hestia")
26
+ def test_run_liveAnimal_chicken(mock_download_hestia, *args):
27
+ with open(f"{fixtures_folder}/liveAnimal/chicken/cycle.jsonld", encoding='utf-8') as f:
28
+ cycle = json.load(f)
29
+
30
+ with open(f"{fixtures_folder}/liveAnimal/chicken/result.jsonld", encoding='utf-8') as f:
31
+ expected = json.load(f)
32
+
33
+ mock_download_hestia.return_value = {
34
+ "@id": "meatChickenReadyToCookWeight",
35
+ "@type": "Term",
36
+ "units": "kg ready-to-cook weight",
37
+ "defaultProperties": [
38
+ {
39
+ "term": {
40
+ "@type": "Term",
41
+ "name": "Processing conversion, liveweight to cold carcass weight",
42
+ "termType": "property",
43
+ "@id": "processingConversionLiveweightToColdCarcassWeight",
44
+ "units": "%"
45
+ },
46
+ "value": 72.30401869158878,
47
+ "@type": "Property"
48
+ },
49
+ {
50
+ "term": {
51
+ "@type": "Term",
52
+ "name": "Processing conversion, cold carcass weight to ready-to-cook weight",
53
+ "termType": "property",
54
+ "@id": "processingConversionColdCarcassWeightToReadyToCookWeight",
55
+ "units": "%"
56
+ },
57
+ "value": 72.45065789473684,
58
+ "@type": "Property"
38
59
  }
39
- }
40
- ]
41
- should_run, *args = _should_run(cycle)
42
- assert not should_run
60
+ ]
61
+ }
62
+ value = run(cycle)
63
+ assert value == expected
64
+
65
+
66
+ @patch(f"{class_path}._new_property", side_effect=fake_new_property)
67
+ @patch(f"{class_path}.download_hestia")
68
+ def test_run_liveAnimal_pig(mock_download_hestia, *args):
69
+ with open(f"{fixtures_folder}/liveAnimal/pig/cycle.jsonld", encoding='utf-8') as f:
70
+ cycle = json.load(f)
43
71
 
44
- # with an endDate and country => run
45
- cycle['endDate'] = '2020'
46
- cycle['site'] = {'country': {'@id': 'GADM-GBR'}}
47
- should_run, *args = _should_run(cycle)
48
- assert should_run is True
72
+ with open(f"{fixtures_folder}/liveAnimal/pig/result.jsonld", encoding='utf-8') as f:
73
+ expected = json.load(f)
74
+
75
+ mock_download_hestia.return_value = {
76
+ "@id": "meatPigColdDressedCarcassWeight",
77
+ "@type": "Term",
78
+ "units": "kg cold dressed carcass weight",
79
+ "defaultProperties": [
80
+ {
81
+ "term": {
82
+ "@type": "Term",
83
+ "name": "Processing conversion, liveweight to cold carcass weight",
84
+ "termType": "property",
85
+ "@id": "processingConversionLiveweightToColdCarcassWeight",
86
+ "units": "%"
87
+ },
88
+ "value": 75.22666597366735,
89
+ "@type": "Property"
90
+ }
91
+ ]
92
+ }
93
+ value = run(cycle)
94
+ assert value == expected
49
95
 
50
96
 
51
- @patch(f"hestia_earth.models.{MODEL}.utils.download_hestia", return_value=FAO_TERM)
52
97
  @patch(f"{class_path}._new_property", side_effect=fake_new_property)
53
- def test_run(*args):
54
- with open(f"{fixtures_folder}/cycle.jsonld", encoding='utf-8') as f:
98
+ @patch(f"{class_path}.download_hestia")
99
+ def test_run_liveAnimal_sheepLamb(mock_download_hestia, *args):
100
+ with open(f"{fixtures_folder}/liveAnimal/sheepLamb/cycle.jsonld", encoding='utf-8') as f:
55
101
  cycle = json.load(f)
56
102
 
57
- with open(f"{fixtures_folder}/result.jsonld", encoding='utf-8') as f:
103
+ with open(f"{fixtures_folder}/liveAnimal/sheepLamb/result.jsonld", encoding='utf-8') as f:
58
104
  expected = json.load(f)
59
105
 
106
+ mock_download_hestia.return_value = {
107
+ "@id": "meatSheepColdDressedCarcassWeight",
108
+ "@type": "Term",
109
+ "units": "kg cold dressed carcass weight",
110
+ "defaultProperties": [
111
+ {
112
+ "term": {
113
+ "@type": "Term",
114
+ "name": "Processing conversion, liveweight to cold carcass weight",
115
+ "termType": "property",
116
+ "@id": "processingConversionLiveweightToColdCarcassWeight",
117
+ "units": "%"
118
+ },
119
+ "value": 47.301833667687326,
120
+ "@type": "Property"
121
+ }
122
+ ]
123
+ }
60
124
  value = run(cycle)
61
125
  assert value == expected
@@ -106,6 +106,7 @@ def find_term_property_side_effect(term: dict, property: str, *_):
106
106
  # subfolder, load_cycles
107
107
  RUN_SUBFOLDERS = [
108
108
  ("tier-1-and-2/cropland", True),
109
+ ("tier-1-and-2/with-zero-carbon-input", True), # Closes issue 777
109
110
  ("tier-2/with-generalised-monthly-measurements", True), # Closes issue 600
110
111
  ("tier-2/with-incomplete-climate-data", True), # Closes issue 599
111
112
  ("tier-2/with-initial-soc", True),
@@ -227,6 +228,13 @@ def test_calc_water_factor():
227
228
  assert _calc_water_factor(1, 1) == _calc_water_factor(1000, 1000)
228
229
 
229
230
 
231
+ def test_calc_water_factor_zero():
232
+ """
233
+ Closes issue 771. Function should not raise 0 error and should return the maximum water factor.
234
+ """
235
+ assert _calc_water_factor(0, 0) == 1.49961875
236
+
237
+
230
238
  # --- IPCC SOIL CATEGORY TESTS ---
231
239
 
232
240
 
@@ -0,0 +1,45 @@
1
+ from unittest.mock import patch
2
+ import json
3
+ from tests.utils import fixtures_path, fake_new_emission
4
+
5
+ from hestia_earth.models.schmidt2007.h2SToAirWasteTreatment import MODEL, TERM_ID, run, _should_run
6
+
7
+ class_path = f"hestia_earth.models.{MODEL}.{TERM_ID}"
8
+ fixtures_folder = f"{fixtures_path}/{MODEL}/{TERM_ID}"
9
+
10
+
11
+ @patch(f"{class_path}.get_waste_values")
12
+ def test_should_run(mock_get_waste_values):
13
+ # no fuel values => no run
14
+ mock_get_waste_values.return_value = []
15
+ should_run, *args = _should_run({})
16
+ assert not should_run
17
+
18
+ # with fuel values => run
19
+ mock_get_waste_values.return_value = [0]
20
+ should_run, *args = _should_run({})
21
+ assert should_run is True
22
+
23
+
24
+ @patch(f"{class_path}._new_emission", side_effect=fake_new_emission)
25
+ def test_run(*args):
26
+ with open(f"{fixtures_folder}/cycle.jsonld", encoding='utf-8') as f:
27
+ cycle = json.load(f)
28
+
29
+ with open(f"{fixtures_folder}/result.jsonld", encoding='utf-8') as f:
30
+ expected = json.load(f)
31
+
32
+ value = run(cycle)
33
+ assert value == expected
34
+
35
+
36
+ @patch(f"{class_path}._new_emission", side_effect=fake_new_emission)
37
+ def test_run_data_complete(*args):
38
+ with open(f"{fixtures_folder}/no-input-data-complete/cycle.jsonld", encoding='utf-8') as f:
39
+ cycle = json.load(f)
40
+
41
+ with open(f"{fixtures_folder}/no-input-data-complete/result.jsonld", encoding='utf-8') as f:
42
+ expected = json.load(f)
43
+
44
+ value = run(cycle)
45
+ assert value == expected
@@ -1,8 +1,10 @@
1
1
  from datetime import datetime
2
2
  import pytest
3
+ from pytest import mark
3
4
  from unittest.mock import patch
4
5
 
5
6
  from hestia_earth.schema import SiteSiteType
7
+ from hestia_earth.utils.tools import parse
6
8
 
7
9
  from hestia_earth.models.utils.blank_node import (
8
10
  _calc_datetime_range_intersection_duration,
@@ -552,13 +554,31 @@ GROUP_NODES_BY_YEAR_SCENARIO_A_CYCLES = [
552
554
  ]
553
555
 
554
556
 
555
- def test_group_nodes_by_year_scenario_a():
557
+ @mark.parametrize(
558
+ "system_datetime",
559
+ [
560
+ datetime(2024, 5, 1), # Closes issue 772
561
+ datetime(2024, 1, 1), # Jan 1st
562
+ datetime(2024, 2, 29), # Leap year
563
+ datetime(2024, 12, 31), # Dec 31st
564
+ # 3 random dates...
565
+ datetime(2030, 5, 4),
566
+ datetime(2035, 7, 22),
567
+ datetime(2047, 8, 14)
568
+ ],
569
+ ids=lambda system_datetime: f"system_datetime = {datetime.strftime(system_datetime, r'%Y-%m-%d')}"
570
+ )
571
+ @patch("hestia_earth.utils.tools.parse")
572
+ def test_group_nodes_by_year_scenario_a(mock_parse, system_datetime):
556
573
  """
557
574
  Datestr in format `YYYY`. Some nodes missing `startDate` field. One multi-year cycle.
558
575
 
559
576
  Incomplete datestrs are gapfilled by default; with `endDate` datestrs snapping to the end
560
577
  of the calendar year, and `startDate` datestrs snapping to the beginning of the calendar
561
578
  year.
579
+
580
+ A bug used to occur in this function when it was run on the first day of a calendar month (e.g., 2024-04-01). To
581
+ verify that the bug is fixed we have simulated running the function on a variety of different dates.
562
582
  """
563
583
 
564
584
  EXPECTED = {
@@ -597,6 +617,9 @@ def test_group_nodes_by_year_scenario_a():
597
617
  }]
598
618
  }
599
619
 
620
+ # As we can't mock builtin method datetime.now(), we have to pass our test system_datetime into the parse function.
621
+ mock_parse.side_effect = lambda *args, **kwargs: parse(*args, default=system_datetime, **kwargs)
622
+
600
623
  result = group_nodes_by_year(GROUP_NODES_BY_YEAR_SCENARIO_A_CYCLES)
601
624
  assert result == EXPECTED
602
625
 
@@ -965,7 +988,26 @@ def test_group_nodes_by_year_missing_dates():
965
988
  assert result == EXPECTED
966
989
 
967
990
 
968
- def test_group_nodes_by_year_multiple_values_and_dates():
991
+ @mark.parametrize(
992
+ "system_datetime",
993
+ [
994
+ datetime(2024, 5, 1), # Closes issue 772
995
+ datetime(2024, 1, 1), # Jan 1st
996
+ datetime(2024, 2, 29), # Leap year
997
+ datetime(2024, 12, 31), # Dec 31st
998
+ # 3 random dates...
999
+ datetime(2026, 5, 14),
1000
+ datetime(2032, 10, 10),
1001
+ datetime(2043, 8, 22)
1002
+ ],
1003
+ ids=lambda system_datetime: f"system_datetime = {datetime.strftime(system_datetime, r'%Y-%m-%d')}"
1004
+ )
1005
+ @patch("hestia_earth.utils.tools.parse")
1006
+ def test_group_nodes_by_year_multiple_values_and_dates(mock_parse, system_datetime):
1007
+ """
1008
+ A bug used to occur in this function when it was run on the first day of a calendar month (e.g., 2024-04-01). To
1009
+ verify that the bug is fixed we have simulated running the function on a variety of different dates.
1010
+ """
969
1011
 
970
1012
  NODES = [{
971
1013
  "value": [1, 2, 3, 4, 5],
@@ -1005,11 +1047,34 @@ def test_group_nodes_by_year_multiple_values_and_dates():
1005
1047
  }]
1006
1048
  }
1007
1049
 
1050
+ # As we can't mock builtin method datetime.now(), we have to pass our test system_datetime into the parse function.
1051
+ mock_parse.side_effect = lambda *args, **kwargs: parse(*args, default=system_datetime, **kwargs)
1052
+
1008
1053
  result = group_nodes_by_year(NODES, mode=GroupNodesByYearMode.DATES)
1009
1054
  assert result == EXPECTED
1010
1055
 
1011
1056
 
1012
- def test_group_nodes_by_year_and_month():
1057
+ @mark.parametrize(
1058
+ "system_datetime",
1059
+ [
1060
+ datetime(2024, 5, 1), # Closes issue 772
1061
+ datetime(2024, 1, 1), # Jan 1st
1062
+ datetime(2024, 2, 29), # Leap year
1063
+ datetime(2024, 12, 31), # Dec 31st
1064
+ # 3 random dates...
1065
+ datetime(2026, 4, 6),
1066
+ datetime(2034, 7, 24),
1067
+ datetime(2049, 10, 10)
1068
+ ],
1069
+ ids=lambda system_datetime: f"system_datetime = {datetime.strftime(system_datetime, r'%Y-%m-%d')}"
1070
+ )
1071
+ @patch("hestia_earth.utils.tools.parse")
1072
+ def test_group_nodes_by_year_and_month(mock_parse, system_datetime):
1073
+ """
1074
+ A bug used to occur in this function when it was run on the first day of a calendar month (e.g., 2024-04-01). To
1075
+ verify that the bug is fixed we have simulated running the function on a variety of different dates.
1076
+ """
1077
+
1013
1078
  MANAGEMENT = [
1014
1079
  {
1015
1080
  "term.@id": "fullTillage",
@@ -1064,5 +1129,8 @@ def test_group_nodes_by_year_and_month():
1064
1129
  }
1065
1130
  }
1066
1131
 
1132
+ # As we can't mock builtin method datetime.now(), we have to pass our test system date into the parse function.
1133
+ mock_parse.side_effect = lambda *args, **kwargs: parse(*args, default=system_datetime, **kwargs)
1134
+
1067
1135
  result = group_nodes_by_year_and_month(MANAGEMENT)
1068
1136
  assert result == EXPECTED
@@ -1,52 +0,0 @@
1
- """
2
- Pre Checks Start Date
3
-
4
- This model calculates the
5
- [startDate](https://hestia.earth/schema/Cycle#startDate) from the
6
- [endDate](https://hestia.earth/schema/Cycle#endDate) and
7
- [cycleDuration](https://hestia.earth/schema/Cycle#cycleDuration).
8
- """
9
- from dateutil.relativedelta import relativedelta
10
- from hestia_earth.utils.date import is_in_days
11
- from hestia_earth.utils.tools import safe_parse_date
12
-
13
- from hestia_earth.models.log import logRequirements, logShouldRun
14
- from .. import MODEL
15
-
16
- REQUIREMENTS = {
17
- "Cycle": {
18
- "endDate": "",
19
- "cycleDuration": ""
20
- }
21
- }
22
- RETURNS = {
23
- "Cycle": {
24
- "startDate": ""
25
- }
26
- }
27
- MODEL_KEY = 'startDate'
28
-
29
-
30
- def _run(cycle: dict):
31
- days = -float(cycle.get('cycleDuration'))
32
- return (safe_parse_date(cycle.get('endDate')) + relativedelta(days=days)).strftime('%Y-%m-%d')
33
-
34
-
35
- def _should_run(cycle: dict):
36
- has_full_date = is_in_days(cycle.get('endDate'))
37
- cycleDuration = cycle.get('cycleDuration')
38
-
39
- logRequirements(cycle, model=MODEL, key=MODEL_KEY,
40
- has_full_date=has_full_date,
41
- cycleDuration=cycleDuration)
42
-
43
- should_run = all([
44
- has_full_date,
45
- cycleDuration is not None,
46
- cycle.get(MODEL_KEY) is None
47
- ])
48
- logShouldRun(cycle, MODEL, None, should_run, key=MODEL_KEY)
49
- return should_run
50
-
51
-
52
- def run(cycle: dict): return {**cycle, MODEL_KEY: _run(cycle)} if _should_run(cycle) else cycle
@@ -1,44 +0,0 @@
1
- import json
2
- from tests.utils import fixtures_path
3
-
4
- from hestia_earth.models.cycle.pre_checks.startDate import run, _should_run
5
-
6
- fixtures_folder = f"{fixtures_path}/cycle/pre_checks/startDate"
7
-
8
-
9
- def test_should_run():
10
- cycle = {}
11
-
12
- # no endDate => no run
13
- cycle['endDate'] = None
14
- assert not _should_run(cycle)
15
-
16
- cycle['endDate'] = '2010'
17
- # no cycleDuration => no run
18
- cycle['cycleDuration'] = None
19
- assert not _should_run(cycle)
20
-
21
- cycle['cycleDuration'] = 120
22
- # with a startDate => no run
23
- cycle['startDate'] = '2010'
24
- assert not _should_run(cycle)
25
-
26
- cycle['startDate'] = None
27
- # endDate not precise enough => no run
28
- cycle['endDate'] = '2020-01'
29
- assert not _should_run(cycle)
30
-
31
- # endDate is precise enough => run
32
- cycle['endDate'] = '2020-01-01'
33
- assert _should_run(cycle) is True
34
-
35
-
36
- def test_run():
37
- with open(f"{fixtures_folder}/cycle.jsonld", encoding='utf-8') as f:
38
- cycle = json.load(f)
39
-
40
- with open(f"{fixtures_folder}/result.jsonld", encoding='utf-8') as f:
41
- expected = json.load(f)
42
-
43
- value = run(cycle)
44
- assert value == expected