shrecc 0.0.4__tar.gz → 0.0.5.dev2__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.
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/LICENSE +1 -1
- {shrecc-0.0.4/shrecc.egg-info → shrecc-0.0.5.dev2}/PKG-INFO +1 -1
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/shrecc/__init__.py +1 -1
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/shrecc/database.py +37 -6
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/shrecc/download.py +2 -0
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/shrecc/treatment.py +1 -1
- {shrecc-0.0.4 → shrecc-0.0.5.dev2/shrecc.egg-info}/PKG-INFO +1 -1
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/tests/test_database.py +74 -10
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/MANIFEST.in +0 -0
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/README.md +0 -0
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/pyproject.toml +0 -0
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/setup.cfg +0 -0
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/shrecc/data/el_map_all_norm.csv +0 -0
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/shrecc/data/generation_units_by_country.csv +0 -0
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/shrecc/data/techs_agg.json +0 -0
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/shrecc.egg-info/SOURCES.txt +0 -0
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/shrecc.egg-info/dependency_links.txt +0 -0
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/shrecc.egg-info/requires.txt +0 -0
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/shrecc.egg-info/top_level.txt +0 -0
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/tests/test_download.py +0 -0
- {shrecc-0.0.4 → shrecc-0.0.5.dev2}/tests/test_treatment.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright © 2025 Luxembourg Institute of Science and Technology
|
|
3
|
+
Copyright © 2024,2025,2026 Luxembourg Institute of Science and Technology
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shrecc
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5.dev2
|
|
4
4
|
Summary: SHRECC: Smooth Hourly Resolution Electricity Consumption Calculation
|
|
5
5
|
Author-email: Sabina Bednářová <sabina.bednarova@list.lu>
|
|
6
6
|
Maintainer-email: Sabina Bednářová <sabina.bednarova@list.lu>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
# Copyright © 2024 Luxembourg Institute of Science and Technology
|
|
1
|
+
# Copyright © 2024,2025,2026 Luxembourg Institute of Science and Technology
|
|
2
2
|
# Licensed under the MIT License (see LICENSE file for details).
|
|
3
3
|
# Authors: [Sabina Bednářová, Thomas Gibon]
|
|
4
4
|
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
from importlib.resources import files
|
|
7
|
+
from packaging.version import parse as vparse
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
import re
|
|
9
10
|
|
|
@@ -14,10 +15,12 @@ from bw2data.query import Filter, Query
|
|
|
14
15
|
|
|
15
16
|
from shrecc.treatment import load_from_pickle, save_to_pickle
|
|
16
17
|
|
|
18
|
+
UNUSED_SOURCE = "Import balance (physical)"
|
|
19
|
+
|
|
17
20
|
|
|
18
21
|
def filt_cutoff(
|
|
19
22
|
countries,
|
|
20
|
-
times=
|
|
23
|
+
times=[],
|
|
21
24
|
general_range=0,
|
|
22
25
|
refined_range=0,
|
|
23
26
|
freq=0,
|
|
@@ -56,8 +59,9 @@ def filt_cutoff(
|
|
|
56
59
|
|
|
57
60
|
if general_range:
|
|
58
61
|
year = datetime.strptime(general_range[0], "%Y-%m-%d %H:%M:%S").year
|
|
59
|
-
elif times:
|
|
60
|
-
|
|
62
|
+
elif len(times):
|
|
63
|
+
times = pd.to_datetime(times)
|
|
64
|
+
year = times[0].year
|
|
61
65
|
else:
|
|
62
66
|
raise ValueError("Either `times` or `general_range` must be provided")
|
|
63
67
|
|
|
@@ -67,7 +71,9 @@ def filt_cutoff(
|
|
|
67
71
|
dataframe = dataframe.droplevel("source", axis=1)
|
|
68
72
|
dataframe = filter_by_countries(dataframe, countries)
|
|
69
73
|
|
|
70
|
-
if times:
|
|
74
|
+
if len(times):
|
|
75
|
+
# For backwards compatibility and making sure datetime is used in the filtering
|
|
76
|
+
times = pd.to_datetime(times)
|
|
71
77
|
dataframe = filter_by_times(dataframe, times)
|
|
72
78
|
if general_range:
|
|
73
79
|
dataframe = filter_by_range(dataframe, general_range, refined_range, freq)
|
|
@@ -142,11 +148,13 @@ def prepare_consumption_data(Z_cons):
|
|
|
142
148
|
pd.DataFrame: The prepared consumption data, with the trade data removed and indices swapped.
|
|
143
149
|
"""
|
|
144
150
|
Z_cons = Z_cons.sort_index()
|
|
151
|
+
|
|
145
152
|
if "trade" in Z_cons.index.get_level_values("source"):
|
|
146
153
|
Z_cons_to_multiply = Z_cons.drop("trade", axis=0).copy()
|
|
147
154
|
else:
|
|
148
155
|
Z_cons_to_multiply = Z_cons.copy()
|
|
149
156
|
Z_cons_to_multiply.index.names = ["source", "geography_mix"]
|
|
157
|
+
|
|
150
158
|
return Z_cons_to_multiply.swaplevel()
|
|
151
159
|
|
|
152
160
|
|
|
@@ -390,11 +398,13 @@ def map_known_inputs(eidb_name, dataframe_filt):
|
|
|
390
398
|
# Only UK seems concerned but consider using a dictionary
|
|
391
399
|
if loc == "UK":
|
|
392
400
|
loc = "GB"
|
|
401
|
+
|
|
393
402
|
# For ecoinvent > 3.10, the activity names have changed
|
|
394
403
|
# They now use a country code, instead of a full name
|
|
395
404
|
# We deal with them here:
|
|
396
405
|
def repl(match):
|
|
397
406
|
return f"from {country_to_code[match.group(1)]}"
|
|
407
|
+
|
|
398
408
|
if any(v in eidb_name for v in ("3.11", "3.12")):
|
|
399
409
|
pattern = re.compile(r"from (Germany|France)")
|
|
400
410
|
name = pattern.sub(repl, name)
|
|
@@ -497,15 +507,36 @@ def create_activity_dict(dataframe_filt, known_inputs, known_inputs_network, db_
|
|
|
497
507
|
country = col
|
|
498
508
|
name = f"Electricity mix in {country}"
|
|
499
509
|
code = f"electricity {i}"
|
|
510
|
+
bd_version = bd.__version__
|
|
511
|
+
if not isinstance(bd_version, str):
|
|
512
|
+
bd_version = ".".join(map(str, bd_version))
|
|
513
|
+
BW2 = vparse(bd_version) < vparse("4")
|
|
514
|
+
if BW2:
|
|
515
|
+
act_type = "process"
|
|
516
|
+
prod_exchange_type = "production"
|
|
517
|
+
else:
|
|
518
|
+
act_type = bd.labels.process_node_default
|
|
519
|
+
prod_exchange_type = bd.labels.production_edge_default
|
|
500
520
|
act = {
|
|
501
521
|
"name": name,
|
|
502
522
|
"unit": "kWh",
|
|
503
523
|
"code": code,
|
|
504
524
|
"location": str(country),
|
|
505
525
|
"reference product": "Electricity mix",
|
|
506
|
-
"type":
|
|
526
|
+
"type": act_type,
|
|
507
527
|
"exchanges": [],
|
|
508
528
|
}
|
|
529
|
+
# Add the production exchange
|
|
530
|
+
act["exchanges"].append(
|
|
531
|
+
{
|
|
532
|
+
"input": (db_name, code),
|
|
533
|
+
"name": name,
|
|
534
|
+
"location": str(country),
|
|
535
|
+
"unit": "kWh",
|
|
536
|
+
"amount": 1,
|
|
537
|
+
"type": prod_exchange_type,
|
|
538
|
+
}
|
|
539
|
+
)
|
|
509
540
|
for idx in dataframe_filt.index:
|
|
510
541
|
source, exch_name, prod, unit = idx
|
|
511
542
|
if float(dataframe_filt.loc[idx, col]) != 0:
|
|
@@ -75,6 +75,7 @@ def get_prod(start, end, country, cumul=False, rolling=False):
|
|
|
75
75
|
def get_trade(start, end, country):
|
|
76
76
|
"""
|
|
77
77
|
Downloads trade data from the Energy Charts API. Gets called from `get_data()`.
|
|
78
|
+
Uses the "cbpf" endpoint of the API.
|
|
78
79
|
|
|
79
80
|
Args:
|
|
80
81
|
start (int): Start of the download period (output of `year_to_unix()`) in unix seconds.
|
|
@@ -295,6 +296,7 @@ def cleaning_data(data, data_dir):
|
|
|
295
296
|
scale_dict = {"production mix": 1, "trade": 1000, "load": 1}
|
|
296
297
|
|
|
297
298
|
for country in data.keys():
|
|
299
|
+
print(f"Processing country: {country}")
|
|
298
300
|
data_clean[country.upper()] = {}
|
|
299
301
|
for k, v in data[country].items():
|
|
300
302
|
if type(v) is pd.DataFrame: # axis = 1 will soon be depreciated
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shrecc
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.5.dev2
|
|
4
4
|
Summary: SHRECC: Smooth Hourly Resolution Electricity Consumption Calculation
|
|
5
5
|
Author-email: Sabina Bednářová <sabina.bednarova@list.lu>
|
|
6
6
|
Maintainer-email: Sabina Bednářová <sabina.bednarova@list.lu>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
import pandas as pd
|
|
3
|
+
import bw2data as bd
|
|
3
4
|
import numpy as np
|
|
5
|
+
from packaging.version import parse as vparse
|
|
4
6
|
from pytest import approx
|
|
5
7
|
from unittest.mock import patch, MagicMock
|
|
6
8
|
import types
|
|
@@ -913,11 +915,55 @@ def test_filter_by_times_no_matching_times():
|
|
|
913
915
|
assert result.shape[1] == 0
|
|
914
916
|
|
|
915
917
|
|
|
918
|
+
@pytest.mark.parametrize(
|
|
919
|
+
"times_input",
|
|
920
|
+
[
|
|
921
|
+
pytest.param(
|
|
922
|
+
["2023-06-01 08:00:00", "2023-06-01 10:00:00"],
|
|
923
|
+
id="list_of_strings",
|
|
924
|
+
),
|
|
925
|
+
pytest.param(
|
|
926
|
+
pd.to_datetime(["2023-06-01 08:00:00", "2023-06-01 10:00:00"]),
|
|
927
|
+
id="datetime_index",
|
|
928
|
+
),
|
|
929
|
+
],
|
|
930
|
+
)
|
|
931
|
+
def test_filter_by_times_accepts_strings_and_datetimeindex(times_input):
|
|
932
|
+
"""filter_by_times should accept either a list of strings or a pd.DatetimeIndex
|
|
933
|
+
and produce the same filtered DataFrame, regardless of whether the columns'
|
|
934
|
+
`time` level is stored as strings or as datetime objects.
|
|
935
|
+
"""
|
|
936
|
+
time_values = pd.to_datetime(
|
|
937
|
+
[
|
|
938
|
+
"2023-06-01 08:00:00",
|
|
939
|
+
"2023-06-01 09:00:00",
|
|
940
|
+
"2023-06-01 10:00:00",
|
|
941
|
+
"2023-06-01 11:00:00",
|
|
942
|
+
]
|
|
943
|
+
)
|
|
944
|
+
countries = ["FR", "DE", "IT", "FR"]
|
|
945
|
+
columns = pd.MultiIndex.from_arrays(
|
|
946
|
+
[time_values, countries], names=["time", "country"]
|
|
947
|
+
)
|
|
948
|
+
index = pd.MultiIndex.from_tuples(
|
|
949
|
+
[("FR", "tech1", "electricity, high voltage", "kWh")],
|
|
950
|
+
names=["geography", "activityName", "prod", "unit"],
|
|
951
|
+
)
|
|
952
|
+
df = pd.DataFrame([[1, 2, 3, 4]], index=index, columns=columns)
|
|
953
|
+
|
|
954
|
+
result = filter_by_times(df, times_input)
|
|
955
|
+
|
|
956
|
+
expected_times = pd.to_datetime(["2023-06-01 08:00:00", "2023-06-01 10:00:00"])
|
|
957
|
+
assert isinstance(result, pd.DataFrame)
|
|
958
|
+
assert result.shape == (1, 2)
|
|
959
|
+
assert set(result.columns.get_level_values("time")) == set(expected_times)
|
|
960
|
+
assert list(result.values.flatten()) == [1, 3]
|
|
961
|
+
|
|
962
|
+
|
|
916
963
|
# ────────────────────────────────────────────────────────────────
|
|
917
964
|
# Tests for: filter_by_range() — requires multiple sub-tests
|
|
918
965
|
# ────────────────────────────────────────────────────────────────
|
|
919
966
|
def test_filter_by_range_basic():
|
|
920
|
-
|
|
921
967
|
time_range = pd.date_range(
|
|
922
968
|
start="2023-06-01 00:00:00", end="2023-08-31 23:00:00", freq="h"
|
|
923
969
|
)
|
|
@@ -1185,7 +1231,6 @@ def dummy_dataframe_for_filt_cutoff():
|
|
|
1185
1231
|
def test_filt_cutoff_filtering_by_times(
|
|
1186
1232
|
mock_df_tech_mapping, dummy_dataframe_for_filt_cutoff
|
|
1187
1233
|
):
|
|
1188
|
-
|
|
1189
1234
|
df = dummy_dataframe_for_filt_cutoff
|
|
1190
1235
|
mock_df_tech_mapping.return_value = df
|
|
1191
1236
|
|
|
@@ -1222,7 +1267,6 @@ def test_filt_cutoff_filtering_by_times(
|
|
|
1222
1267
|
def test_filt_cutoff_filtering_by_range(
|
|
1223
1268
|
mock_df_tech_mapping, dummy_dataframe_for_filt_cutoff
|
|
1224
1269
|
):
|
|
1225
|
-
|
|
1226
1270
|
df = dummy_dataframe_for_filt_cutoff
|
|
1227
1271
|
mock_df_tech_mapping.return_value = df
|
|
1228
1272
|
|
|
@@ -1338,7 +1382,6 @@ def test_setup_database_handles_multiple_calls(monkeypatch):
|
|
|
1338
1382
|
# Tests for: tech_mapping() — requires multiple sub-tests
|
|
1339
1383
|
# ──────────────────────────────────────────────────────────
|
|
1340
1384
|
def test_get_network_activities_basic():
|
|
1341
|
-
|
|
1342
1385
|
eidb_name = "ecoinvent-3.8-cutoff"
|
|
1343
1386
|
result = get_network_activities(eidb_name)
|
|
1344
1387
|
assert isinstance(result, list)
|
|
@@ -1377,7 +1420,6 @@ def test_get_network_activities_basic():
|
|
|
1377
1420
|
|
|
1378
1421
|
|
|
1379
1422
|
def test_get_network_activities_311():
|
|
1380
|
-
|
|
1381
1423
|
eidb_name = "ecoinvent-3.11-cutoff"
|
|
1382
1424
|
result = get_network_activities(eidb_name)
|
|
1383
1425
|
assert isinstance(result, list)
|
|
@@ -1388,7 +1430,6 @@ def test_get_network_activities_311():
|
|
|
1388
1430
|
|
|
1389
1431
|
|
|
1390
1432
|
def test_get_network_activities_other_versions():
|
|
1391
|
-
|
|
1392
1433
|
# Should not match "3.11" if not present
|
|
1393
1434
|
for eidb_name in ["ecoinvent 3.7", "ei3.8.1", "eco3.9", ""]:
|
|
1394
1435
|
result = get_network_activities(eidb_name)
|
|
@@ -1671,9 +1712,19 @@ def test_create_activity_dict_basic(
|
|
|
1671
1712
|
(db_name, "electricity 1"),
|
|
1672
1713
|
}
|
|
1673
1714
|
# Check structure of activity dict
|
|
1715
|
+
bd_version = bd.__version__
|
|
1716
|
+
if not isinstance(bd_version, str):
|
|
1717
|
+
bd_version = ".".join(map(str, bd_version))
|
|
1718
|
+
BW2 = vparse(bd_version) < vparse("4")
|
|
1719
|
+
if BW2:
|
|
1720
|
+
act_type = "process"
|
|
1721
|
+
prod_exchange_type = "production"
|
|
1722
|
+
else:
|
|
1723
|
+
act_type = bd.labels.process_node_default
|
|
1724
|
+
prod_exchange_type = bd.labels.production_edge_default
|
|
1674
1725
|
for act in activities.values():
|
|
1675
1726
|
assert act["unit"] == "kWh"
|
|
1676
|
-
assert act["type"] ==
|
|
1727
|
+
assert act["type"] == act_type
|
|
1677
1728
|
assert isinstance(act["exchanges"], list)
|
|
1678
1729
|
# Should have at least one exchange (the technology)
|
|
1679
1730
|
assert any(e["type"] == "technosphere" for e in act["exchanges"])
|
|
@@ -1686,7 +1737,9 @@ def test_create_activity_dict_basic(
|
|
|
1686
1737
|
# Check that the correct technology exchange is present for each activity
|
|
1687
1738
|
# act0 is for ("2023-06-01 08:00:00", "FR")
|
|
1688
1739
|
act0 = activities[(db_name, "electricity 0")]
|
|
1689
|
-
inputs0 = [
|
|
1740
|
+
inputs0 = [
|
|
1741
|
+
e["input"] for e in act0["exchanges"] if e["type"] != prod_exchange_type
|
|
1742
|
+
]
|
|
1690
1743
|
assert set(inputs0) == {
|
|
1691
1744
|
"fr_hydro_ei",
|
|
1692
1745
|
"net_trans_subsea",
|
|
@@ -1703,7 +1756,7 @@ def test_create_activity_dict_basic(
|
|
|
1703
1756
|
assert tech_ex1[0]["amount"] == 5.5 # specific network
|
|
1704
1757
|
# act1 is for ("2023-06-01 09:00:00", "DE")
|
|
1705
1758
|
act1 = activities[(db_name, "electricity 1")]
|
|
1706
|
-
inputs1 = [e["input"] for e in act1["exchanges"]]
|
|
1759
|
+
inputs1 = [e["input"] for e in act1["exchanges"] if e["type"] != prod_exchange_type]
|
|
1707
1760
|
assert set(inputs1) == {"de_wind_ei", "net_dist_lv", "net_trans_mv"}
|
|
1708
1761
|
assert "net_trans_subsea" not in inputs1
|
|
1709
1762
|
assert "net_trans_ch" not in inputs1
|
|
@@ -1716,6 +1769,14 @@ def test_create_activity_dict_basic(
|
|
|
1716
1769
|
def test_create_activity_dict_no_network_exchanges(
|
|
1717
1770
|
mock_get_network_activities, sample_dataframe_filt, known_inputs_fixture
|
|
1718
1771
|
):
|
|
1772
|
+
bd_version = bd.__version__
|
|
1773
|
+
if not isinstance(bd_version, str):
|
|
1774
|
+
bd_version = ".".join(map(str, bd_version))
|
|
1775
|
+
BW2 = vparse(bd_version) < vparse("4")
|
|
1776
|
+
if BW2:
|
|
1777
|
+
prod_exchange_type = "production"
|
|
1778
|
+
else:
|
|
1779
|
+
prod_exchange_type = bd.labels.production_edge_default
|
|
1719
1780
|
# No known_inputs_network, so only technology exchanges
|
|
1720
1781
|
mock_get_network_activities.return_value = []
|
|
1721
1782
|
db_name = "test_db"
|
|
@@ -1727,8 +1788,11 @@ def test_create_activity_dict_no_network_exchanges(
|
|
|
1727
1788
|
)
|
|
1728
1789
|
for act in activities.values():
|
|
1729
1790
|
# Only technology exchanges should be present
|
|
1791
|
+
technospere_exchanges = [
|
|
1792
|
+
e for e in act["exchanges"] if e["type"] != prod_exchange_type
|
|
1793
|
+
]
|
|
1730
1794
|
assert all(
|
|
1731
|
-
e["input"] in known_inputs_fixture.values() for e in
|
|
1795
|
+
e["input"] in known_inputs_fixture.values() for e in technospere_exchanges
|
|
1732
1796
|
)
|
|
1733
1797
|
|
|
1734
1798
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|