hestia-earth-utils 0.16.13__tar.gz → 0.16.15__tar.gz

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.
Files changed (59) hide show
  1. {hestia_earth_utils-0.16.13/hestia_earth_utils.egg-info → hestia_earth_utils-0.16.15}/PKG-INFO +1 -1
  2. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/blank_node.py +34 -16
  3. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/cycle.py +24 -5
  4. hestia_earth_utils-0.16.15/hestia_earth/utils/pivot/_shared.py +110 -0
  5. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/pivot/pivot_csv.py +3 -22
  6. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/pivot/pivot_json.py +55 -21
  7. hestia_earth_utils-0.16.15/hestia_earth/utils/version.py +1 -0
  8. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15/hestia_earth_utils.egg-info}/PKG-INFO +1 -1
  9. hestia_earth_utils-0.16.15/tests/test_calculation_status.py +40 -0
  10. hestia_earth_utils-0.16.15/tests/test_cycle.py +33 -0
  11. hestia_earth_utils-0.16.13/hestia_earth/utils/pivot/_shared.py +0 -55
  12. hestia_earth_utils-0.16.13/hestia_earth/utils/version.py +0 -1
  13. hestia_earth_utils-0.16.13/tests/test_calculation_status.py +0 -40
  14. hestia_earth_utils-0.16.13/tests/test_cycle.py +0 -18
  15. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/MANIFEST.in +0 -0
  16. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/README.md +0 -0
  17. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/bin/hestia-format-upload +0 -0
  18. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/bin/hestia-pivot-csv +0 -0
  19. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/__init__.py +0 -0
  20. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/api.py +0 -0
  21. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/calculation_status.py +0 -0
  22. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/date.py +0 -0
  23. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/descriptive_stats.py +0 -0
  24. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/emission.py +0 -0
  25. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/lookup.py +0 -0
  26. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/lookup_utils.py +0 -0
  27. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/model.py +0 -0
  28. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/pipeline.py +0 -0
  29. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/pivot/__init__.py +0 -0
  30. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/request.py +0 -0
  31. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/stats.py +0 -0
  32. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/storage/__init__.py +0 -0
  33. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/storage/_azure_client.py +0 -0
  34. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/storage/_local_client.py +0 -0
  35. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/storage/_s3_client.py +0 -0
  36. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/storage/_sns_client.py +0 -0
  37. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/table.py +0 -0
  38. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/term.py +0 -0
  39. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth/utils/tools.py +0 -0
  40. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth_utils.egg-info/SOURCES.txt +0 -0
  41. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth_utils.egg-info/dependency_links.txt +0 -0
  42. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth_utils.egg-info/requires.txt +0 -0
  43. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/hestia_earth_utils.egg-info/top_level.txt +0 -0
  44. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/setup.cfg +0 -0
  45. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/setup.py +0 -0
  46. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/tests/test_api.py +0 -0
  47. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/tests/test_blank_node.py +0 -0
  48. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/tests/test_date.py +0 -0
  49. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/tests/test_descriptive_stats.py +0 -0
  50. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/tests/test_emission.py +0 -0
  51. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/tests/test_lookup.py +0 -0
  52. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/tests/test_lookup_utils.py +0 -0
  53. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/tests/test_model.py +0 -0
  54. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/tests/test_pipeline.py +0 -0
  55. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/tests/test_request.py +0 -0
  56. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/tests/test_stats.py +0 -0
  57. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/tests/test_table.py +0 -0
  58. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/tests/test_term.py +0 -0
  59. {hestia_earth_utils-0.16.13 → hestia_earth_utils-0.16.15}/tests/test_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hestia_earth_utils
3
- Version: 0.16.13
3
+ Version: 0.16.15
4
4
  Summary: HESTIA's utils library
5
5
  Home-page: https://gitlab.com/hestia-earth/hestia-utils
6
6
  Author: HESTIA Team
@@ -186,6 +186,38 @@ def get_node_value(
186
186
  _BLANK_NODE_GROUPING_KEYS = {TermTermType.EMISSION: ["methodModel"]}
187
187
 
188
188
 
189
+ def _pluralize_key(key: str):
190
+ return key + ("" if key.endswith("s") else "s")
191
+
192
+
193
+ def _blank_node_ids(values: list):
194
+ return sorted(list(set(list(map(lambda v: v.get("@id"), values)))))
195
+
196
+
197
+ def _blank_node_sub_values(blank_nodes: list, key: str):
198
+ values = flatten(map(lambda v: v.get(key, []), blank_nodes))
199
+ return {_pluralize_key(key): _blank_node_ids(values)} if values else {}
200
+
201
+
202
+ def _blank_node_data(blank_nodes: list):
203
+ value = get_node_value(
204
+ {
205
+ "term": blank_nodes[0].get("term"),
206
+ "value": list(map(get_node_value, blank_nodes)),
207
+ }
208
+ )
209
+ sub_values = ["inputs", "operation", "transformation"]
210
+ has_cycle_value = any(
211
+ [
212
+ all([get_node_value(v) is not None, not v.get("transformation")])
213
+ for v in blank_nodes
214
+ ]
215
+ )
216
+ return {"value": value, "hasCycleValue": has_cycle_value} | reduce(
217
+ lambda p, c: p | _blank_node_sub_values(blank_nodes, c), sub_values, {}
218
+ )
219
+
220
+
189
221
  def get_blank_nodes_calculation_status(
190
222
  node: dict, list_key: str, termType: TermTermType
191
223
  ):
@@ -214,20 +246,6 @@ def get_blank_nodes_calculation_status(
214
246
  blank_nodes_by_term = group_by_keys(blank_nodes, ["term"])
215
247
  blank_nodes_grouping_keys = _BLANK_NODE_GROUPING_KEYS.get(termType) or []
216
248
 
217
- def blank_node_data(blank_nodes: list):
218
- value = get_node_value(
219
- {
220
- "term": blank_nodes[0].get("term"),
221
- "value": list(map(get_node_value, blank_nodes)),
222
- }
223
- )
224
- inputs = flatten(map(lambda v: v.get("inputs", []), blank_nodes))
225
- return {"value": value} | (
226
- {"inputs": sorted(list(map(lambda v: v.get("@id"), inputs)))}
227
- if inputs
228
- else {}
229
- )
230
-
231
249
  def map_blank_node(term_id: str):
232
250
  values = blank_nodes_by_term.get(term_id, [])
233
251
  grouped_blank_nodes = (
@@ -239,9 +257,9 @@ def get_blank_nodes_calculation_status(
239
257
  {}
240
258
  if not values
241
259
  else (
242
- {k: blank_node_data(v) for k, v in grouped_blank_nodes.items()}
260
+ {k: _blank_node_data(v) for k, v in grouped_blank_nodes.items()}
243
261
  if grouped_blank_nodes
244
- else blank_node_data([values[0]])
262
+ else _blank_node_data([values[0]])
245
263
  )
246
264
  )
247
265
 
@@ -4,10 +4,16 @@ from .tools import flatten
4
4
  from .blank_node import get_blank_nodes_calculation_status
5
5
 
6
6
 
7
- def _extend_missing_inputs(value: dict, input_ids: set):
8
- included_inputs = set(flatten([v.get("inputs", []) for v in value.values()]))
9
- missing_inputs = input_ids - included_inputs
10
- return {"missingInputs": sorted(list(missing_inputs))} if missing_inputs else {}
7
+ def _extend_missing_values(
8
+ value: dict, all_values: set, key: str, must_have_key: bool = False
9
+ ):
10
+ included_values = set(flatten([v.get(key, []) for v in value.values()]))
11
+ missing_values = all_values - included_values
12
+ return (
13
+ {"missing" + key.capitalize(): sorted(list(missing_values))}
14
+ if all([missing_values, not must_have_key or included_values])
15
+ else {}
16
+ )
11
17
 
12
18
 
13
19
  def get_cycle_emissions_calculation_status(cycle: dict):
@@ -30,7 +36,20 @@ def get_cycle_emissions_calculation_status(cycle: dict):
30
36
  cycle, "emissions", TermTermType.EMISSION
31
37
  )
32
38
  input_ids = set([v.get("term", {}).get("@id") for v in cycle.get("inputs", [])])
39
+ transformation_ids = set(
40
+ [v.get("term", {}).get("@id") for v in cycle.get("transformations", [])]
41
+ )
33
42
  return {
34
- k: v | (_extend_missing_inputs(v, input_ids) if "InputsProduction" in k else {})
43
+ k: v
44
+ | (
45
+ _extend_missing_values(v, input_ids, "inputs")
46
+ if "InputsProduction" in k
47
+ else {}
48
+ )
49
+ | (
50
+ _extend_missing_values(
51
+ v, transformation_ids, "transformations", must_have_key=True
52
+ )
53
+ )
35
54
  for k, v in status.items()
36
55
  }
@@ -0,0 +1,110 @@
1
+ import json
2
+ import numpy as np
3
+ import pandas as pd
4
+ from hestia_earth.schema import SCHEMA_TYPES, NODE_TYPES, EmissionMethodTier
5
+ from flatten_json import flatten as flatten_json
6
+
7
+ from ..tools import list_sum
8
+
9
+
10
+ EXCLUDE_FIELDS = ["@type", "type", "@context"]
11
+ EXCLUDE_PRIVATE_FIELDS = [
12
+ "added",
13
+ "addedVersion",
14
+ "updated",
15
+ "updatedVersion",
16
+ "aggregatedVersion",
17
+ "_cache",
18
+ ]
19
+
20
+
21
+ # assuming column labels always camelCase
22
+ def _get_node_type_label(node_type):
23
+ return node_type[0].lower() + node_type[1:]
24
+
25
+
26
+ def _get_node_type_from_label(node_type):
27
+ return node_type[0].upper() + node_type[1:]
28
+
29
+
30
+ def _is_blank_node(data: dict):
31
+ node_type = data.get("@type") or data.get("type")
32
+ return node_type in SCHEMA_TYPES and node_type not in NODE_TYPES
33
+
34
+
35
+ def _with_csv_formatting(dct):
36
+ """
37
+ Use as object_hook when parsing a JSON node: json.loads(node, object_hook=_with_csv_formatting).
38
+ Ensures parsed JSON has field values formatted according to hestia csv conventions.
39
+ """
40
+ if "boundary" in dct:
41
+ dct["boundary"] = json.dumps(dct["boundary"])
42
+ for key, value in dct.items():
43
+ if _is_scalar_list(value):
44
+ dct[key] = ";".join([str(el) for el in value])
45
+ return dct
46
+
47
+
48
+ def _is_scalar_list(value):
49
+ if not isinstance(value, list):
50
+ return False
51
+ all_scalar = True
52
+ for element in value:
53
+ if not np.isscalar(element):
54
+ all_scalar = False
55
+ break
56
+ return all_scalar
57
+
58
+
59
+ def _filter_not_relevant(blank_node: dict):
60
+ return blank_node.get("methodTier") != EmissionMethodTier.NOT_RELEVANT.value
61
+
62
+
63
+ def _filter_emissions_not_relevant(node: dict):
64
+ """
65
+ Ignore all emissions where `methodTier=not relevant` to save space.
66
+ """
67
+ return node | (
68
+ {
69
+ key: list(filter(_filter_not_relevant, node[key]))
70
+ for key in ["emissions", "emissionsResourceUse"]
71
+ if key in node
72
+ }
73
+ )
74
+
75
+
76
+ def _filter_zero_value(blank_node: dict):
77
+ value = blank_node.get("value")
78
+ value = (
79
+ list_sum(blank_node.get("value"), default=-1)
80
+ if isinstance(value, list)
81
+ else value
82
+ )
83
+ return value != 0
84
+
85
+
86
+ def _filter_zero_values(node: dict):
87
+ """
88
+ Ignore all blank nodes where `value=0` to save space.
89
+ """
90
+ return node | (
91
+ {
92
+ key: list(filter(_filter_zero_value, value))
93
+ for key, value in node.items()
94
+ if isinstance(value, list)
95
+ and isinstance(value[0], dict)
96
+ and _is_blank_node(value[0])
97
+ }
98
+ )
99
+
100
+
101
+ def nodes_to_df(nodes: list[dict]):
102
+ nodes_flattened = [
103
+ flatten_json(
104
+ dict([(_get_node_type_label(node.get("@type", node.get("type"))), node)]),
105
+ ".",
106
+ )
107
+ for node in nodes
108
+ ]
109
+
110
+ return pd.json_normalize(nodes_flattened)
@@ -5,7 +5,6 @@ import numpy as np
5
5
  import pandas as pd
6
6
  from hestia_earth.schema import UNIQUENESS_FIELDS, Term, NODE_TYPES
7
7
  from hestia_earth.schema.utils.sort import get_sort_key, SORT_CONFIG
8
- from flatten_json import flatten as flatten_json
9
8
 
10
9
  # __package__ = "hestia_earth.utils" # required to run interactively in vscode
11
10
  from ..api import find_term_ids_by_names
@@ -14,6 +13,9 @@ from ._shared import (
14
13
  EXCLUDE_PRIVATE_FIELDS,
15
14
  _with_csv_formatting,
16
15
  _filter_emissions_not_relevant,
16
+ _get_node_type_label,
17
+ _get_node_type_from_label,
18
+ nodes_to_df,
17
19
  )
18
20
 
19
21
 
@@ -36,15 +38,6 @@ def _get_blank_node_uniqueness_fields():
36
38
  BLANK_NODE_UNIQUENESS_FIELDS = _get_blank_node_uniqueness_fields()
37
39
 
38
40
 
39
- # assuming column labels always camelCase
40
- def _get_node_type_label(node_type):
41
- return node_type[0].lower() + node_type[1:]
42
-
43
-
44
- def _get_node_type_from_label(node_type):
45
- return node_type[0].upper() + node_type[1:]
46
-
47
-
48
41
  def _get_names(df):
49
42
  names = []
50
43
  for node_type, array_fields in BLANK_NODE_UNIQUENESS_FIELDS.items():
@@ -283,18 +276,6 @@ def _format_and_pivot(df_in):
283
276
  return df_out
284
277
 
285
278
 
286
- def nodes_to_df(nodes: list[dict]):
287
- nodes_flattened = [
288
- flatten_json(
289
- dict([(_get_node_type_label(node.get("@type", node.get("type"))), node)]),
290
- ".",
291
- )
292
- for node in nodes
293
- ]
294
-
295
- return pd.json_normalize(nodes_flattened)
296
-
297
-
298
279
  def pivot_nodes(nodes: list[dict]):
299
280
  """
300
281
  Pivot array of nodes in dict format (e.g under the 'nodes' key of a .hestia file)
@@ -11,6 +11,7 @@ from ._shared import (
11
11
  EXCLUDE_PRIVATE_FIELDS,
12
12
  _with_csv_formatting,
13
13
  _filter_emissions_not_relevant,
14
+ _filter_zero_values,
14
15
  )
15
16
 
16
17
  pivot_exclude_fields = Term().fields
@@ -22,6 +23,8 @@ term_exclude_fields = Term().fields
22
23
  del term_exclude_fields["name"]
23
24
  term_exclude_fields.update({k: "" for k in EXCLUDE_PRIVATE_FIELDS})
24
25
 
26
+ include_all_unique_keys = ["emissions", "emissionsResourceUse"]
27
+
25
28
  # Treat properties uniqueness fields as special case for now
26
29
  PROPERTIES_VIRTUAL_UNIQUENESS_FIELD = "propertyValues"
27
30
  ADAPTED_UNIQUENESS_FIELDS = deepcopy(UNIQUENESS_FIELDS)
@@ -90,6 +93,7 @@ def _do_pivot(node, parent_node_type=None, parent_field=None, level=0): # noqa:
90
93
 
91
94
  # print('\n', level, 'fields_to_pivot', fields_to_pivot)
92
95
  for field, uniqueness_fields in fields_to_pivot:
96
+ include_all_unique_fields = field in include_all_unique_keys
93
97
  # print('\nbefore processing node field', level, field, node[field], '\n')
94
98
  # Compress lists of 'Node' nodes to dict with single @id key.
95
99
  # The compressed field matches uniqueness fields like cycle.emissions.inputs.@id.
@@ -155,14 +159,13 @@ def _do_pivot(node, parent_node_type=None, parent_field=None, level=0): # noqa:
155
159
  del term_data["combined_fields"][id_key]
156
160
  # print('combined_fields', field, term_id, term_data['combined_fields'], '\n')
157
161
  fields_to_include = {
158
- field: any(
159
- by_idx[idx].get(field) != by_idx[indexes[0]].get(field)
160
- for idx in indexes
162
+ k: include_all_unique_fields
163
+ or any(
164
+ by_idx[idx].get(k) != by_idx[indexes[0]].get(k) for idx in indexes
161
165
  )
162
- for field in term_data["combined_fields"].keys()
163
- if field in uniqueness_fields
164
- or field != "value"
165
- and field.split(".")[-1] not in pivot_exclude_fields
166
+ for k in term_data["combined_fields"].keys()
167
+ if k in uniqueness_fields
168
+ or (k != "value" and k.split(".")[-1] not in pivot_exclude_fields)
166
169
  }
167
170
  # print('fields_to_include', level, field, term_id, fields_to_include, '\n')
168
171
  for idx in indexes:
@@ -179,19 +182,23 @@ def _do_pivot(node, parent_node_type=None, parent_field=None, level=0): # noqa:
179
182
  )
180
183
  ]
181
184
  # print('distingishing_field_fields', level, field, term_id, distingishing_field_fields, '\n')
182
- unanimous_fields = {
183
- field: term_data["combined_fields"][field]
184
- for field, not_unanimous in fields_to_include.items()
185
- if field not in distingishing_field_fields
186
- and not not_unanimous
187
- and field is not PROPERTIES_VIRTUAL_UNIQUENESS_FIELD
188
- }
189
185
  # print('unanimous_fields', level, field, term_id, unanimous_fields, '\n')
186
+ unanimous_fields = (
187
+ {}
188
+ if include_all_unique_fields
189
+ else {
190
+ field: term_data["combined_fields"][field]
191
+ for field, not_unanimous in fields_to_include.items()
192
+ if field not in distingishing_field_fields
193
+ and not not_unanimous
194
+ and field is not PROPERTIES_VIRTUAL_UNIQUENESS_FIELD
195
+ }
196
+ )
190
197
  differentiated_fields = {
191
198
  field: term[field]
192
199
  for field, not_unanimous in fields_to_include.items()
193
200
  if field not in distingishing_field_fields
194
- and not_unanimous
201
+ and (include_all_unique_fields or not_unanimous)
195
202
  and field in term
196
203
  }
197
204
  # print('differentiated_fields', level, field, term_id, differentiated_fields, '\n')
@@ -223,19 +230,35 @@ def _do_pivot(node, parent_node_type=None, parent_field=None, level=0): # noqa:
223
230
  return pivoted_node
224
231
 
225
232
 
226
- def pivot_node(node: dict):
233
+ def pivot_node(
234
+ node: dict,
235
+ include_emissions_not_relevant: bool = False,
236
+ include_zero_values: bool = True,
237
+ ):
227
238
  """
228
239
  Pivot single node in dict format parsed with object_hook=_with_csv_formatting
229
240
  """
230
- return _do_pivot(_filter_emissions_not_relevant(node))
241
+ node = (
242
+ node if include_emissions_not_relevant else _filter_emissions_not_relevant(node)
243
+ )
244
+ node = node if include_zero_values else _filter_zero_values(node)
245
+ return _do_pivot(node)
231
246
 
232
247
 
233
- def pivot_json_node(json_node: str):
248
+ def pivot_json_node(
249
+ json_node: str,
250
+ include_emissions_not_relevant: bool = False,
251
+ include_zero_values: bool = True,
252
+ ):
234
253
  """
235
254
  Pivot single schema-compliant unparsed json string node
236
255
  """
237
256
  node = json.loads(json_node, object_hook=_with_csv_formatting)
238
- return pivot_node(node)
257
+ return pivot_node(
258
+ node,
259
+ include_emissions_not_relevant=include_emissions_not_relevant,
260
+ include_zero_values=include_zero_values,
261
+ )
239
262
 
240
263
 
241
264
  def pivot_hestia_file(hestia_file: str):
@@ -246,8 +269,19 @@ def pivot_hestia_file(hestia_file: str):
246
269
  return pivot_nodes(parsed.get("nodes", []))
247
270
 
248
271
 
249
- def pivot_nodes(nodes: list[dict]):
272
+ def pivot_nodes(
273
+ nodes: list[dict],
274
+ include_emissions_not_relevant: bool = False,
275
+ include_zero_values: bool = True,
276
+ ):
250
277
  """
251
278
  Pivot multiple nodes in dict format parsed with object_hook=_with_csv_formatting
252
279
  """
253
- return [pivot_node(node) for node in nodes]
280
+ return [
281
+ pivot_node(
282
+ node,
283
+ include_emissions_not_relevant=include_emissions_not_relevant,
284
+ include_zero_values=include_zero_values,
285
+ )
286
+ for node in nodes
287
+ ]
@@ -0,0 +1 @@
1
+ VERSION = "0.16.15"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hestia_earth_utils
3
- Version: 0.16.13
3
+ Version: 0.16.15
4
4
  Summary: HESTIA's utils library
5
5
  Home-page: https://gitlab.com/hestia-earth/hestia-utils
6
6
  Author: HESTIA Team
@@ -0,0 +1,40 @@
1
+ import os
2
+ import json
3
+
4
+ from tests.utils import fixtures_path
5
+ from hestia_earth.utils.calculation_status import _emissions_with_status
6
+
7
+ fixtures_folder = os.path.join(fixtures_path, "calculation_status")
8
+
9
+
10
+ def test_emissions_with_status():
11
+ with open(os.path.join(fixtures_folder, "nodes.json")) as f:
12
+ nodes = json.load(f)
13
+
14
+ result = _emissions_with_status(nodes[0])
15
+ assert result == {
16
+ "emissions-total": 195,
17
+ "emissions-complete": 55,
18
+ "emissions-incomplete": 0,
19
+ "emissions-missing": 140,
20
+ "emissions": result["emissions"], # ignore
21
+ }
22
+
23
+ result = _emissions_with_status(nodes[1])
24
+ assert result == {
25
+ "emissions-total": 195,
26
+ "emissions-complete": 0,
27
+ "emissions-incomplete": 13,
28
+ "emissions-missing": 182,
29
+ "emissions": result["emissions"], # ignore
30
+ }
31
+
32
+
33
+ # def test_get_nodes_calculations_status_dataframe():
34
+ # with open(os.path.join(fixtures_folder, 'nodes.json')) as f:
35
+ # nodes = json.load(f)
36
+
37
+ # expected = open(os.path.join(fixtures_folder, 'result.csv'), 'r').read()
38
+
39
+ # df = get_nodes_calculations_status_dataframe(nodes, file_format='csv')
40
+ # assert df.to_csv(None, index=None) == expected
@@ -0,0 +1,33 @@
1
+ import os
2
+ import json
3
+ import pytest
4
+
5
+ from tests.utils import fixtures_path
6
+ from hestia_earth.utils.cycle import get_cycle_emissions_calculation_status
7
+
8
+ fixtures_folder = os.path.join(fixtures_path, "blank_node", "calculation_status")
9
+ _folders = [
10
+ d
11
+ for d in os.listdir(fixtures_folder)
12
+ if os.path.isdir(os.path.join(fixtures_folder, d))
13
+ ]
14
+
15
+
16
+ @pytest.mark.parametrize("folder", _folders)
17
+ def test_get_cycle_emissions_calculation_status(folder: str):
18
+ with open(
19
+ os.path.join(fixtures_folder, folder, "node.jsonld"), encoding="utf-8"
20
+ ) as f:
21
+ cycle = json.load(f)
22
+
23
+ with open(
24
+ os.path.join(
25
+ fixtures_folder, folder, "emissions-emission-with-missing-inputs.json"
26
+ ),
27
+ encoding="utf-8",
28
+ ) as f:
29
+ expected = json.load(f)
30
+
31
+ result = get_cycle_emissions_calculation_status(cycle)
32
+ print(json.dumps(result, indent=2))
33
+ assert result == expected
@@ -1,55 +0,0 @@
1
- import json
2
- import numpy as np
3
- from hestia_earth.schema import EmissionMethodTier
4
-
5
-
6
- EXCLUDE_FIELDS = ["@type", "type", "@context"]
7
- EXCLUDE_PRIVATE_FIELDS = [
8
- "added",
9
- "addedVersion",
10
- "updated",
11
- "updatedVersion",
12
- "aggregatedVersion",
13
- "_cache",
14
- ]
15
-
16
-
17
- def _with_csv_formatting(dct):
18
- """
19
- Use as object_hook when parsing a JSON node: json.loads(node, object_hook=_with_csv_formatting).
20
- Ensures parsed JSON has field values formatted according to hestia csv conventions.
21
- """
22
- if "boundary" in dct:
23
- dct["boundary"] = json.dumps(dct["boundary"])
24
- for key, value in dct.items():
25
- if _is_scalar_list(value):
26
- dct[key] = ";".join([str(el) for el in value])
27
- return dct
28
-
29
-
30
- def _is_scalar_list(value):
31
- if not isinstance(value, list):
32
- return False
33
- all_scalar = True
34
- for element in value:
35
- if not np.isscalar(element):
36
- all_scalar = False
37
- break
38
- return all_scalar
39
-
40
-
41
- def _filter_not_relevant(blank_node: dict):
42
- return blank_node.get("methodTier") != EmissionMethodTier.NOT_RELEVANT.value
43
-
44
-
45
- def _filter_emissions_not_relevant(node: dict):
46
- """
47
- Ignore all emissions where `methodTier=not relevant` so save space.
48
- """
49
- return node | (
50
- {
51
- key: list(filter(_filter_not_relevant, node[key]))
52
- for key in ["emissions", "emissionsResourceUse"]
53
- if key in node
54
- }
55
- )
@@ -1 +0,0 @@
1
- VERSION = "0.16.13"
@@ -1,40 +0,0 @@
1
- # import os
2
- # import json
3
-
4
- # from tests.utils import fixtures_path
5
- # from hestia_earth.utils.calculation_status import _emissions_with_status, get_nodes_calculations_status_dataframe
6
-
7
- # fixtures_folder = os.path.join(fixtures_path, 'calculation_status')
8
-
9
-
10
- # def test_emissions_with_status():
11
- # with open(os.path.join(fixtures_folder, 'nodes.json')) as f:
12
- # nodes = json.load(f)
13
-
14
- # result = _emissions_with_status(nodes[0])
15
- # assert result == {
16
- # 'emissions-total': 193,
17
- # 'emissions-complete': 56,
18
- # 'emissions-incomplete': 1,
19
- # 'emissions-missing': 136,
20
- # 'emissions': result['emissions'] # ignore
21
- # }
22
-
23
- # result = _emissions_with_status(nodes[1])
24
- # assert result == {
25
- # 'emissions-total': 193,
26
- # 'emissions-complete': 0,
27
- # 'emissions-incomplete': 13,
28
- # 'emissions-missing': 180,
29
- # 'emissions': result['emissions'] # ignore
30
- # }
31
-
32
-
33
- # def test_get_nodes_calculations_status_dataframe():
34
- # with open(os.path.join(fixtures_folder, 'nodes.json')) as f:
35
- # nodes = json.load(f)
36
-
37
- # expected = open(os.path.join(fixtures_folder, 'result.csv'), 'r').read()
38
-
39
- # df = get_nodes_calculations_status_dataframe(nodes, file_format='csv')
40
- # assert df.to_csv(None, index=None) == expected
@@ -1,18 +0,0 @@
1
- # import os
2
- # import json
3
-
4
- # from tests.utils import fixtures_path
5
- # from hestia_earth.utils.cycle import get_cycle_emissions_calculation_status
6
-
7
-
8
- # def test_get_cycle_emissions_calculation_status():
9
- # folder = os.path.join(fixtures_path, 'blank_node', 'calculation_status', 'cycle')
10
-
11
- # with open(f"{folder}/node.jsonld", encoding='utf-8') as f:
12
- # cycle = json.load(f)
13
-
14
- # with open(f"{folder}/emissions-emission-with-missing-inputs.json", encoding='utf-8') as f:
15
- # expected = json.load(f)
16
-
17
- # result = get_cycle_emissions_calculation_status(cycle)
18
- # assert result == expected