brightpath 0.0.1__tar.gz → 0.0.4__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.
- brightpath-0.0.4/MANIFEST.in +5 -0
- {brightpath-0.0.1/brightpath.egg-info → brightpath-0.0.4}/PKG-INFO +24 -2
- {brightpath-0.0.1 → brightpath-0.0.4}/README.md +11 -1
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/__init__.py +3 -3
- brightpath-0.0.4/brightpath/bwconverter.py +429 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/data/export/simapro-biosphere.json +1 -1
- brightpath-0.0.4/brightpath/data/export/simapro_to_brightway_biosphere_ei310.yaml +31 -0
- brightpath-0.0.4/brightpath/simaproconverter.py +670 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/utils.py +476 -519
- {brightpath-0.0.1 → brightpath-0.0.4/brightpath.egg-info}/PKG-INFO +24 -2
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath.egg-info/SOURCES.txt +6 -1
- brightpath-0.0.4/brightpath.egg-info/requires.txt +17 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/pyproject.toml +33 -1
- {brightpath-0.0.1 → brightpath-0.0.4}/requirements.txt +1 -0
- brightpath-0.0.4/tests/test_bwconverter.py +210 -0
- brightpath-0.0.4/tests/test_regressions.py +116 -0
- brightpath-0.0.4/tests/test_simaproconverter.py +460 -0
- brightpath-0.0.4/tests/test_utils.py +311 -0
- brightpath-0.0.1/MANIFEST.in +0 -1
- brightpath-0.0.1/brightpath/bwconverter.py +0 -616
- brightpath-0.0.1/brightpath/simaproconverter.py +0 -463
- brightpath-0.0.1/brightpath.egg-info/requires.txt +0 -3
- {brightpath-0.0.1 → brightpath-0.0.4}/LICENSE +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/data/export/correspondence_biosphere_flows.yaml +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/data/export/ecoinvent_to_uvek_mapping.csv +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/data/export/ei_transport.csv +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/data/export/flows_biosphere_39.csv +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/data/export/simapro-technosphere-3.5.csv +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/data/export/simapro_blacklist.yaml +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/data/export/simapro_ei_exceptions.yaml +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/data/export/simapro_fields.yaml +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/data/export/simapro_headers.yaml +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/data/export/simapro_subcompartments.yaml +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/data/export/simapro_units.yaml +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/data/export/uvek_blacklist.yaml +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/data/export/uvek_conversion_factors.yaml +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath/data/export/waste_exchange_names.yaml +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath.egg-info/dependency_links.txt +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/brightpath.egg-info/top_level.txt +0 -0
- {brightpath-0.0.1 → brightpath-0.0.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: brightpath
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: Convert Brightway2 LCI data to Simapro and back.
|
|
5
5
|
Author-email: Romain Sacchi <romain.sacchi@psi.ch>
|
|
6
6
|
License: BSD 3-Clause License
|
|
@@ -53,6 +53,18 @@ License-File: LICENSE
|
|
|
53
53
|
Requires-Dist: bw2io
|
|
54
54
|
Requires-Dist: prettytable>=3
|
|
55
55
|
Requires-Dist: PyYAML>=6
|
|
56
|
+
Requires-Dist: XlsxWriter>=3
|
|
57
|
+
Provides-Extra: test
|
|
58
|
+
Requires-Dist: pytest; extra == "test"
|
|
59
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
60
|
+
Provides-Extra: dev
|
|
61
|
+
Requires-Dist: bandit; extra == "dev"
|
|
62
|
+
Requires-Dist: black; extra == "dev"
|
|
63
|
+
Requires-Dist: build; extra == "dev"
|
|
64
|
+
Requires-Dist: isort; extra == "dev"
|
|
65
|
+
Requires-Dist: pytest; extra == "dev"
|
|
66
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
67
|
+
Requires-Dist: ruff; extra == "dev"
|
|
56
68
|
Dynamic: license-file
|
|
57
69
|
|
|
58
70
|
# BrightPath
|
|
@@ -125,6 +137,17 @@ converter.convert_to_brightway()
|
|
|
125
137
|
# converter.i.write_database()
|
|
126
138
|
```
|
|
127
139
|
|
|
140
|
+
You can also write a Brightway Excel workbook directly, without first creating
|
|
141
|
+
a Brightway database:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
path = converter.convert_to_brightway(
|
|
145
|
+
format="excel",
|
|
146
|
+
filename="lci-my-simapro-import.xlsx",
|
|
147
|
+
)
|
|
148
|
+
print(path)
|
|
149
|
+
```
|
|
150
|
+
|
|
128
151
|
## Development
|
|
129
152
|
|
|
130
153
|
* Source code is formatted with standard Python tools and tested with
|
|
@@ -137,4 +160,3 @@ converter.convert_to_brightway()
|
|
|
137
160
|
|
|
138
161
|
BrightPath is distributed under the
|
|
139
162
|
[BSD-3-Clause license](LICENSE).
|
|
140
|
-
|
|
@@ -68,6 +68,17 @@ converter.convert_to_brightway()
|
|
|
68
68
|
# converter.i.write_database()
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
+
You can also write a Brightway Excel workbook directly, without first creating
|
|
72
|
+
a Brightway database:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
path = converter.convert_to_brightway(
|
|
76
|
+
format="excel",
|
|
77
|
+
filename="lci-my-simapro-import.xlsx",
|
|
78
|
+
)
|
|
79
|
+
print(path)
|
|
80
|
+
```
|
|
81
|
+
|
|
71
82
|
## Development
|
|
72
83
|
|
|
73
84
|
* Source code is formatted with standard Python tools and tested with
|
|
@@ -80,4 +91,3 @@ converter.convert_to_brightway()
|
|
|
80
91
|
|
|
81
92
|
BrightPath is distributed under the
|
|
82
93
|
[BSD-3-Clause license](LICENSE).
|
|
83
|
-
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
__all__ = ("BrightwayConverter", "SimaproConverter", "DATA_DIR")
|
|
2
|
-
__version__ = (0, 0,
|
|
2
|
+
__version__ = (0, 0, 4)
|
|
3
3
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
|
|
6
6
|
DATA_DIR = Path(__file__).resolve().parent / "data"
|
|
7
7
|
|
|
8
|
-
from .bwconverter import BrightwayConverter
|
|
9
|
-
from .simaproconverter import SimaproConverter
|
|
8
|
+
from .bwconverter import BrightwayConverter # noqa: E402
|
|
9
|
+
from .simaproconverter import SimaproConverter # noqa: E402
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the class BrightwayConverter, which is used to convert
|
|
3
|
+
Brightway2 inventories to Simapro CSV files.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import csv
|
|
7
|
+
import datetime
|
|
8
|
+
import logging
|
|
9
|
+
from copy import deepcopy
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .utils import (
|
|
13
|
+
add_distri_transport,
|
|
14
|
+
check_exchanges_for_conversion,
|
|
15
|
+
collect_unused_exchanges,
|
|
16
|
+
convert_sd_to_sd2,
|
|
17
|
+
escape_spreadsheet_formula,
|
|
18
|
+
find_production_exchange,
|
|
19
|
+
flag_exchanges,
|
|
20
|
+
format_exchange_name,
|
|
21
|
+
get_biosphere_exchanges,
|
|
22
|
+
get_simapro_biosphere,
|
|
23
|
+
get_simapro_ecoinvent_blacklist,
|
|
24
|
+
get_simapro_fields_list,
|
|
25
|
+
get_simapro_headers,
|
|
26
|
+
get_simapro_subcompartments,
|
|
27
|
+
get_simapro_technosphere,
|
|
28
|
+
get_simapro_uncertainty_type,
|
|
29
|
+
get_simapro_units,
|
|
30
|
+
get_subcategory,
|
|
31
|
+
get_technosphere_exchanges,
|
|
32
|
+
import_bw_inventories,
|
|
33
|
+
is_a_waste_treatment,
|
|
34
|
+
is_activity_waste_treatment,
|
|
35
|
+
is_blacklisted,
|
|
36
|
+
load_inventory_metadata,
|
|
37
|
+
round_floats_in_string,
|
|
38
|
+
validate_brightway_inventory,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
logger = logging.getLogger(__name__)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BrightwayConverter:
|
|
45
|
+
"""
|
|
46
|
+
Convert Brightway2 inventories to Simapro CSV files.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
filepath: str = None,
|
|
52
|
+
data: list = None,
|
|
53
|
+
metadata: str = None,
|
|
54
|
+
ecoinvent_version: str = "3.9",
|
|
55
|
+
export_dir: str = None,
|
|
56
|
+
):
|
|
57
|
+
"""
|
|
58
|
+
:param filepath: path to the BW inventory spreadsheet file
|
|
59
|
+
"""
|
|
60
|
+
if filepath and data is not None:
|
|
61
|
+
raise ValueError("Provide either `filepath` or `data`, not both.")
|
|
62
|
+
if not filepath and data is None:
|
|
63
|
+
raise ValueError("Provide either `filepath` or `data`.")
|
|
64
|
+
|
|
65
|
+
self.filepath = filepath
|
|
66
|
+
self.inventories = import_bw_inventories(filepath) if self.filepath else deepcopy(data)
|
|
67
|
+
validate_brightway_inventory(self.inventories)
|
|
68
|
+
self.simapro_blacklist = get_simapro_ecoinvent_blacklist()
|
|
69
|
+
self.simapro_fields = get_simapro_fields_list()
|
|
70
|
+
self.simapro_units = get_simapro_units()
|
|
71
|
+
self.simapro_headers = get_simapro_headers()
|
|
72
|
+
self.simapro_technosphere = get_simapro_technosphere()
|
|
73
|
+
self.simapro_biosphere = get_simapro_biosphere()
|
|
74
|
+
self.simapro_subcompartment = get_simapro_subcompartments()
|
|
75
|
+
self.ei_version = ecoinvent_version
|
|
76
|
+
self.metadata = {}
|
|
77
|
+
self.unused_exchanges = []
|
|
78
|
+
|
|
79
|
+
if metadata:
|
|
80
|
+
self.metadata = load_inventory_metadata(metadata)
|
|
81
|
+
|
|
82
|
+
# export directory is the current working
|
|
83
|
+
# directory unless specified otherwise
|
|
84
|
+
self.export_dir = Path(export_dir) if export_dir else Path.cwd()
|
|
85
|
+
|
|
86
|
+
def format_inventories_for_simapro(self, database: str):
|
|
87
|
+
"""
|
|
88
|
+
Format inventories to Simapro format.
|
|
89
|
+
:param database: name of the database to link to.
|
|
90
|
+
:return: list
|
|
91
|
+
"""
|
|
92
|
+
rows = self._header_rows()
|
|
93
|
+
inventories = [self._prepare_activity(activity, database) for activity in self.inventories]
|
|
94
|
+
|
|
95
|
+
for activity in inventories:
|
|
96
|
+
rows.extend(self._format_activity_rows(activity, database))
|
|
97
|
+
|
|
98
|
+
rows.extend(self._metadata_rows())
|
|
99
|
+
self.unused_exchanges = collect_unused_exchanges(inventories)
|
|
100
|
+
if self.unused_exchanges:
|
|
101
|
+
logger.warning("%s exchanges were not converted.", len(self.unused_exchanges))
|
|
102
|
+
else:
|
|
103
|
+
logger.info("All exchanges have been converted.")
|
|
104
|
+
|
|
105
|
+
return rows
|
|
106
|
+
|
|
107
|
+
def _header_rows(self):
|
|
108
|
+
rows = [
|
|
109
|
+
(
|
|
110
|
+
[item.replace("today_date", datetime.datetime.today().strftime("%d.%m.%Y"))]
|
|
111
|
+
if item.startswith("{Date")
|
|
112
|
+
else [item]
|
|
113
|
+
)
|
|
114
|
+
for item in self.simapro_headers
|
|
115
|
+
]
|
|
116
|
+
rows.append([])
|
|
117
|
+
return rows
|
|
118
|
+
|
|
119
|
+
def _metadata_rows(self):
|
|
120
|
+
rows = []
|
|
121
|
+
for field in (
|
|
122
|
+
"System description",
|
|
123
|
+
"Literature reference",
|
|
124
|
+
):
|
|
125
|
+
if field.lower() in self.metadata:
|
|
126
|
+
rows.extend([[field], []])
|
|
127
|
+
for key, val in self.metadata[field.lower()].items():
|
|
128
|
+
rows.extend(
|
|
129
|
+
[
|
|
130
|
+
[key],
|
|
131
|
+
[val],
|
|
132
|
+
[],
|
|
133
|
+
]
|
|
134
|
+
)
|
|
135
|
+
rows.append([])
|
|
136
|
+
rows.extend([["End"], []])
|
|
137
|
+
return rows
|
|
138
|
+
|
|
139
|
+
def _prepare_activity(self, activity: dict, database: str):
|
|
140
|
+
activity = deepcopy(activity)
|
|
141
|
+
if database == "uvek":
|
|
142
|
+
activity = add_distri_transport(activity)
|
|
143
|
+
return flag_exchanges(activity)
|
|
144
|
+
|
|
145
|
+
def _format_activity_rows(self, activity: dict, database: str):
|
|
146
|
+
rows = []
|
|
147
|
+
dataset_name = ""
|
|
148
|
+
is_a_waste_treatment_activity = is_activity_waste_treatment(activity, database)
|
|
149
|
+
|
|
150
|
+
for field in self.simapro_fields:
|
|
151
|
+
if is_a_waste_treatment_activity is True and field == "Products":
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
if is_a_waste_treatment_activity is False and field == "Waste treatment":
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
rows.append([field])
|
|
158
|
+
if field in ["Process", "End"]:
|
|
159
|
+
rows.append([])
|
|
160
|
+
continue
|
|
161
|
+
|
|
162
|
+
if field == "Process name":
|
|
163
|
+
dataset_name = format_exchange_name(
|
|
164
|
+
activity["name"], activity["reference product"], activity["location"], activity["unit"], database
|
|
165
|
+
)
|
|
166
|
+
rows.extend([[dataset_name], []])
|
|
167
|
+
|
|
168
|
+
if field == "Type":
|
|
169
|
+
rows.extend([["Unit process"], []])
|
|
170
|
+
|
|
171
|
+
if field == "Comment":
|
|
172
|
+
rows.extend([[self._comment_text(activity)], []])
|
|
173
|
+
|
|
174
|
+
if field == "Category type":
|
|
175
|
+
prod_exchange = find_production_exchange(activity)
|
|
176
|
+
rows.extend([[prod_exchange["simapro category"].split("/")[0]], []])
|
|
177
|
+
|
|
178
|
+
if field == "Geography":
|
|
179
|
+
rows.extend([[activity["location"]], []])
|
|
180
|
+
|
|
181
|
+
if field == "Date":
|
|
182
|
+
rows.extend([[f"{datetime.datetime.today():%d.%m.%Y}"], []])
|
|
183
|
+
|
|
184
|
+
if field in (
|
|
185
|
+
"Time period",
|
|
186
|
+
"Record",
|
|
187
|
+
"Generator",
|
|
188
|
+
"Cut off rules",
|
|
189
|
+
"Capital goods",
|
|
190
|
+
"Technology",
|
|
191
|
+
"Representativeness",
|
|
192
|
+
"Boundary with nature",
|
|
193
|
+
"Infrastructure",
|
|
194
|
+
"External documents",
|
|
195
|
+
"System description",
|
|
196
|
+
"Allocation rules",
|
|
197
|
+
"Literature references",
|
|
198
|
+
"Collection method",
|
|
199
|
+
"Data treatment",
|
|
200
|
+
"Verification",
|
|
201
|
+
):
|
|
202
|
+
rows.extend(self._activity_metadata_field_rows(field, activity))
|
|
203
|
+
|
|
204
|
+
if field in (
|
|
205
|
+
"Final waste flows",
|
|
206
|
+
"Non material emission",
|
|
207
|
+
"Social issues",
|
|
208
|
+
"Economic issues",
|
|
209
|
+
):
|
|
210
|
+
rows.append([])
|
|
211
|
+
|
|
212
|
+
if field in ("Waste treatment", "Products"):
|
|
213
|
+
rows.extend(self._product_or_waste_rows(field, dataset_name, activity))
|
|
214
|
+
|
|
215
|
+
if field in ["Materials/fuels", "Electricity/heat"]:
|
|
216
|
+
rows.extend(
|
|
217
|
+
self._technosphere_rows(
|
|
218
|
+
field,
|
|
219
|
+
activity,
|
|
220
|
+
database,
|
|
221
|
+
is_a_waste_treatment_activity,
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
if field == "Resources":
|
|
226
|
+
rows.extend(self._biosphere_rows(activity, database, "natural resource"))
|
|
227
|
+
|
|
228
|
+
if field.startswith("Emissions to"):
|
|
229
|
+
rows.extend(self._biosphere_rows(activity, database, field.split(" ")[-1].lower()))
|
|
230
|
+
|
|
231
|
+
if field == "Waste to treatment":
|
|
232
|
+
rows.extend(self._waste_to_treatment_rows(activity, database))
|
|
233
|
+
|
|
234
|
+
rows.append([])
|
|
235
|
+
return rows
|
|
236
|
+
|
|
237
|
+
def _comment_text(self, activity: dict) -> str:
|
|
238
|
+
string = ""
|
|
239
|
+
if activity.get("comment"):
|
|
240
|
+
string = f"{round_floats_in_string(activity['comment'])} "
|
|
241
|
+
|
|
242
|
+
if activity.get("source"):
|
|
243
|
+
string += f"Source: {activity['source']} "
|
|
244
|
+
|
|
245
|
+
return string.replace("\n", " ")
|
|
246
|
+
|
|
247
|
+
def _activity_metadata_field_rows(self, field: str, activity: dict):
|
|
248
|
+
if field.lower() in activity:
|
|
249
|
+
return [[activity[field.lower()]], []]
|
|
250
|
+
|
|
251
|
+
if field == "Infrastructure":
|
|
252
|
+
return [["No"], []]
|
|
253
|
+
if field == "System description" and "system description" in self.metadata:
|
|
254
|
+
return [[self.metadata["system description"]["name"]], []]
|
|
255
|
+
if field == "Literature references" and "literature reference" in self.metadata:
|
|
256
|
+
return [[self.metadata["literature reference"]["name"]], []]
|
|
257
|
+
return [["Unspecified"], []]
|
|
258
|
+
|
|
259
|
+
def _product_or_waste_rows(self, field: str, dataset_name: str, activity: dict):
|
|
260
|
+
prod_exchange = find_production_exchange(activity)
|
|
261
|
+
category = get_subcategory(prod_exchange["simapro category"])
|
|
262
|
+
amount = prod_exchange["amount"]
|
|
263
|
+
if field == "Waste treatment" and amount < 0:
|
|
264
|
+
amount = abs(amount)
|
|
265
|
+
|
|
266
|
+
prod_exchange["used"] = True
|
|
267
|
+
return [
|
|
268
|
+
[
|
|
269
|
+
dataset_name,
|
|
270
|
+
self.simapro_units[prod_exchange["unit"]],
|
|
271
|
+
"{:.3E}".format(amount),
|
|
272
|
+
"100",
|
|
273
|
+
"not defined",
|
|
274
|
+
category,
|
|
275
|
+
"not defined",
|
|
276
|
+
],
|
|
277
|
+
[],
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
def _technosphere_rows(self, field: str, activity: dict, database: str, is_waste_treatment_activity: bool):
|
|
281
|
+
rows = []
|
|
282
|
+
energy_units = {"megajoule", "kilowatt hour"}
|
|
283
|
+
want_energy = field == "Electricity/heat"
|
|
284
|
+
|
|
285
|
+
for source_exc in get_technosphere_exchanges(activity):
|
|
286
|
+
is_energy = source_exc["unit"] in energy_units
|
|
287
|
+
if is_energy != want_energy:
|
|
288
|
+
continue
|
|
289
|
+
if is_blacklisted(source_exc, database):
|
|
290
|
+
continue
|
|
291
|
+
if is_a_waste_treatment(source_exc["name"], database=database):
|
|
292
|
+
continue
|
|
293
|
+
|
|
294
|
+
exc = check_exchanges_for_conversion([source_exc], database)[0]
|
|
295
|
+
if is_waste_treatment_activity is True and exc["amount"] < 0:
|
|
296
|
+
exc["amount"] = abs(exc["amount"])
|
|
297
|
+
|
|
298
|
+
rows.append(self._technosphere_exchange_row(exc, database))
|
|
299
|
+
source_exc["used"] = True
|
|
300
|
+
|
|
301
|
+
rows.append([])
|
|
302
|
+
return rows
|
|
303
|
+
|
|
304
|
+
def _waste_to_treatment_rows(self, activity: dict, database: str):
|
|
305
|
+
rows = []
|
|
306
|
+
for source_exc in get_technosphere_exchanges(activity):
|
|
307
|
+
if is_blacklisted(source_exc, database):
|
|
308
|
+
continue
|
|
309
|
+
if not is_a_waste_treatment(source_exc["name"], database=database):
|
|
310
|
+
continue
|
|
311
|
+
|
|
312
|
+
exc = check_exchanges_for_conversion([source_exc], database)[0]
|
|
313
|
+
if exc["amount"] < 0:
|
|
314
|
+
exc["amount"] = abs(exc["amount"])
|
|
315
|
+
|
|
316
|
+
rows.append(self._technosphere_exchange_row(exc, database))
|
|
317
|
+
source_exc["used"] = True
|
|
318
|
+
|
|
319
|
+
rows.append([])
|
|
320
|
+
return rows
|
|
321
|
+
|
|
322
|
+
def _technosphere_exchange_row(self, exc: dict, database: str):
|
|
323
|
+
exchange_name = format_exchange_name(
|
|
324
|
+
exc["name"], exc["reference product"], exc.get("location", "GLO"), exc["unit"], database
|
|
325
|
+
)
|
|
326
|
+
u_type = get_simapro_uncertainty_type(exc.get("uncertainty type"))
|
|
327
|
+
return [
|
|
328
|
+
f"{exchange_name}",
|
|
329
|
+
self.simapro_units[exc["unit"]],
|
|
330
|
+
"{:.3E}".format(exc["amount"]),
|
|
331
|
+
u_type,
|
|
332
|
+
"{:.3E}".format(convert_sd_to_sd2(exc.get("scale", 1), u_type)),
|
|
333
|
+
"{:.3E}".format(exc.get("min", 0)),
|
|
334
|
+
"{:.3E}".format(exc.get("max", 0)),
|
|
335
|
+
exc.get("comment"),
|
|
336
|
+
]
|
|
337
|
+
|
|
338
|
+
def _biosphere_rows(self, activity: dict, database: str, category: str):
|
|
339
|
+
rows = []
|
|
340
|
+
for source_exc in get_biosphere_exchanges(activity, category):
|
|
341
|
+
if is_blacklisted(source_exc, database):
|
|
342
|
+
continue
|
|
343
|
+
|
|
344
|
+
exc = deepcopy(source_exc)
|
|
345
|
+
if category != "natural resource" and exc["name"].lower() == "water":
|
|
346
|
+
exc["unit"] = "kilogram"
|
|
347
|
+
exc["amount"] *= 1000
|
|
348
|
+
|
|
349
|
+
rows.append(self._biosphere_exchange_row(exc))
|
|
350
|
+
source_exc["used"] = True
|
|
351
|
+
|
|
352
|
+
rows.append([])
|
|
353
|
+
return rows
|
|
354
|
+
|
|
355
|
+
def _biosphere_exchange_row(self, exc: dict):
|
|
356
|
+
u_type = get_simapro_uncertainty_type(exc.get("uncertainty type"))
|
|
357
|
+
return [
|
|
358
|
+
f"{self.simapro_biosphere.get(exc['name'], exc['name'])}",
|
|
359
|
+
self._subcompartment(exc),
|
|
360
|
+
self.simapro_units[exc["unit"]],
|
|
361
|
+
"{:.3E}".format(exc["amount"]),
|
|
362
|
+
u_type,
|
|
363
|
+
"{:.3E}".format(convert_sd_to_sd2(exc.get("scale", 1), u_type)),
|
|
364
|
+
"{:.3E}".format(exc.get("min", 0)),
|
|
365
|
+
"{:.3E}".format(exc.get("max", 0)),
|
|
366
|
+
exc.get("comment"),
|
|
367
|
+
]
|
|
368
|
+
|
|
369
|
+
def _subcompartment(self, exc: dict) -> str:
|
|
370
|
+
if len(exc["categories"]) <= 1:
|
|
371
|
+
return ""
|
|
372
|
+
|
|
373
|
+
subcategory = exc["categories"][1]
|
|
374
|
+
if subcategory not in self.simapro_subcompartment:
|
|
375
|
+
raise ValueError(
|
|
376
|
+
f"No SimaPro subcompartment mapping for {subcategory!r} " f"on biosphere exchange {exc.get('name')!r}."
|
|
377
|
+
)
|
|
378
|
+
return self.simapro_subcompartment[subcategory]
|
|
379
|
+
|
|
380
|
+
def convert_to_simapro(self, database: str = "ecoinvent", format: str = "csv", filename: str = None) -> [str, list]:
|
|
381
|
+
"""
|
|
382
|
+
Convert the inventories to Simapro CSV files.
|
|
383
|
+
:param database: Name of the database to link to. Default is `ecoinvent`, but can be `uvek`.
|
|
384
|
+
"""
|
|
385
|
+
|
|
386
|
+
if database not in ("ecoinvent", "uvek"):
|
|
387
|
+
raise ValueError("Database must be either `ecoinvent` or `uvek`")
|
|
388
|
+
|
|
389
|
+
if format not in ("csv", "data"):
|
|
390
|
+
raise ValueError("Format must be either `csv` or `data`.")
|
|
391
|
+
|
|
392
|
+
data = self.format_inventories_for_simapro(database)
|
|
393
|
+
|
|
394
|
+
if format == "data":
|
|
395
|
+
return data
|
|
396
|
+
|
|
397
|
+
filepath = self._export_filepath(database, filename)
|
|
398
|
+
filepath.parent.mkdir(parents=True, exist_ok=True)
|
|
399
|
+
|
|
400
|
+
with open(filepath, "w", newline="", encoding="utf-8") as csvFile:
|
|
401
|
+
writer = csv.writer(csvFile, delimiter=";")
|
|
402
|
+
for row in data:
|
|
403
|
+
writer.writerow([escape_spreadsheet_formula(value) for value in row])
|
|
404
|
+
|
|
405
|
+
return f"Inventories export to: {filepath}"
|
|
406
|
+
|
|
407
|
+
def _export_filepath(self, database: str, filename: str = None) -> Path:
|
|
408
|
+
if filename:
|
|
409
|
+
filepath = Path(filename)
|
|
410
|
+
if filepath.suffix == "":
|
|
411
|
+
filepath = filepath.with_suffix(".csv")
|
|
412
|
+
if not filepath.is_absolute():
|
|
413
|
+
filepath = self.export_dir / filepath
|
|
414
|
+
else:
|
|
415
|
+
stamp = datetime.datetime.today().strftime("%Y-%m-%d_%H%M%S")
|
|
416
|
+
filepath = self.export_dir / f"simapro_{database}_{stamp}.csv"
|
|
417
|
+
|
|
418
|
+
if not filepath.exists():
|
|
419
|
+
return filepath
|
|
420
|
+
|
|
421
|
+
stem = filepath.stem
|
|
422
|
+
suffix = filepath.suffix
|
|
423
|
+
parent = filepath.parent
|
|
424
|
+
counter = 1
|
|
425
|
+
while True:
|
|
426
|
+
candidate = parent / f"{stem}_{counter}{suffix}"
|
|
427
|
+
if not candidate.exists():
|
|
428
|
+
return candidate
|
|
429
|
+
counter += 1
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Version-specific SimaPro flow names that differ from ecoinvent 3.10 Brightway
|
|
2
|
+
# biosphere names. Rule-based normalizers handle oxidation-state parentheses,
|
|
3
|
+
# water unit/location suffixes, and ", ion" suffixes before these entries are
|
|
4
|
+
# used as a final explicit name mapping.
|
|
5
|
+
---
|
|
6
|
+
global:
|
|
7
|
+
AOX, Adsorbable Organic Halogen: AOX, Adsorbable Organic Halides
|
|
8
|
+
Benzene, hexachloro-: Hexachlorobenzene
|
|
9
|
+
BOD5 (Biological Oxygen Demand): BOD5, Biological Oxygen Demand
|
|
10
|
+
Carbon monoxide biogenic: Carbon monoxide, non-fossil
|
|
11
|
+
COD (Chemical Oxygen Demand): COD, Chemical Oxygen Demand
|
|
12
|
+
Nitrogen, atmospheric: Nitrogen
|
|
13
|
+
Transformation, to rivers, artificial: Transformation, to river, artificial
|
|
14
|
+
Xylene: Xylenes, unspecified
|
|
15
|
+
|
|
16
|
+
natural resource:
|
|
17
|
+
Argon-40/kg: Argon
|
|
18
|
+
|
|
19
|
+
air:
|
|
20
|
+
Chromium, ion: Chromium VI
|
|
21
|
+
Chromium ion: Chromium VI
|
|
22
|
+
|
|
23
|
+
soil:
|
|
24
|
+
Chromium, ion: Chromium VI
|
|
25
|
+
Chromium ion: Chromium VI
|
|
26
|
+
|
|
27
|
+
water:
|
|
28
|
+
Ammonium, ion: Ammonium
|
|
29
|
+
Ammonium ion: Ammonium
|
|
30
|
+
Chromium, ion: Chromium III
|
|
31
|
+
Chromium ion: Chromium III
|