cetk 0.0.4.dev0__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.
Files changed (60) hide show
  1. cetk/__init__.py +87 -0
  2. cetk/db.py +41 -0
  3. cetk/edb/__init__.py +0 -0
  4. cetk/edb/apps.py +9 -0
  5. cetk/edb/cache.py +249 -0
  6. cetk/edb/const.py +26 -0
  7. cetk/edb/copy.py +18 -0
  8. cetk/edb/exporters.py +551 -0
  9. cetk/edb/importers/__init__.py +36 -0
  10. cetk/edb/importers/activity_import.py +279 -0
  11. cetk/edb/importers/codeset_import.py +124 -0
  12. cetk/edb/importers/gridsource_import.py +256 -0
  13. cetk/edb/importers/roadsource_import.py +1602 -0
  14. cetk/edb/importers/source_import.py +958 -0
  15. cetk/edb/importers/timevar_import.py +115 -0
  16. cetk/edb/importers/utils.py +92 -0
  17. cetk/edb/importers/validation.py +207 -0
  18. cetk/edb/ltreefield.py +76 -0
  19. cetk/edb/migrations/0001_initial.py +1731 -0
  20. cetk/edb/migrations/0002_data.py +156 -0
  21. cetk/edb/migrations/0003_create_gpkg_tables.py +14 -0
  22. cetk/edb/migrations/__init__.py +0 -0
  23. cetk/edb/models/__init__.py +49 -0
  24. cetk/edb/models/base.py +52 -0
  25. cetk/edb/models/common_models.py +144 -0
  26. cetk/edb/models/fleets.py +160 -0
  27. cetk/edb/models/gridsource_models.py +163 -0
  28. cetk/edb/models/road_classes.py +197 -0
  29. cetk/edb/models/road_models.py +413 -0
  30. cetk/edb/models/source_models.py +399 -0
  31. cetk/edb/models/timevar_models.py +111 -0
  32. cetk/edb/rasterize/__init__.py +1 -0
  33. cetk/edb/rasterize/rasterizer.py +1544 -0
  34. cetk/edb/signals.py +43 -0
  35. cetk/edb/traffic.py +75 -0
  36. cetk/edb/units.py +160 -0
  37. cetk/emissions/__init__.py +0 -0
  38. cetk/emissions/aggregate_emissions.sql +303 -0
  39. cetk/emissions/areasource_emissions.sql +53 -0
  40. cetk/emissions/calc.py +148 -0
  41. cetk/emissions/filters.py +179 -0
  42. cetk/emissions/gridsource_emissions.sql +52 -0
  43. cetk/emissions/pointsource_emissions.sql +53 -0
  44. cetk/emissions/queries.py +221 -0
  45. cetk/emissions/roadsource_emissions.sql +211 -0
  46. cetk/emissions/used_substances.sql +56 -0
  47. cetk/emissions/views.py +89 -0
  48. cetk/logging.py +68 -0
  49. cetk/settings.py +85 -0
  50. cetk/tools/__init__.py +1 -0
  51. cetk/tools/cetk_command.py +599 -0
  52. cetk/tools/manage.py +24 -0
  53. cetk/tools/utils.py +334 -0
  54. cetk/utils.py +62 -0
  55. cetk-0.0.4.dev0.dist-info/LICENSE +201 -0
  56. cetk-0.0.4.dev0.dist-info/METADATA +297 -0
  57. cetk-0.0.4.dev0.dist-info/RECORD +60 -0
  58. cetk-0.0.4.dev0.dist-info/WHEEL +5 -0
  59. cetk-0.0.4.dev0.dist-info/entry_points.txt +3 -0
  60. cetk-0.0.4.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,279 @@
1
+ """import activities and emission-factors."""
2
+
3
+ from django.db import IntegrityError
4
+
5
+ from cetk.edb.cache import cache_queryset
6
+ from cetk.edb.models import (
7
+ Activity,
8
+ AreaSource,
9
+ AreaSourceActivity,
10
+ EmissionFactor,
11
+ Facility,
12
+ GridSource,
13
+ GridSourceActivity,
14
+ PointSource,
15
+ PointSourceActivity,
16
+ Substance,
17
+ )
18
+ from cetk.edb.units import activity_ef_unit_to_si
19
+
20
+ from .utils import import_error, worksheet_to_dataframe
21
+
22
+
23
+ def import_emissionfactorsheet(workbook, validation):
24
+ return_dict = {}
25
+ return_message = []
26
+ activities = cache_queryset(
27
+ Activity.objects.prefetch_related("emissionfactors").all(), "name"
28
+ )
29
+ data = workbook["EmissionFactor"].values
30
+ df_activity = worksheet_to_dataframe(data)
31
+
32
+ activity_names = df_activity["activity_name"]
33
+ update_activities = {}
34
+ create_activities = {}
35
+ drop_emfacs = []
36
+ for row_nr, activity_name in enumerate(activity_names):
37
+ try:
38
+ activity = activities[activity_name]
39
+ if activity_name not in update_activities.keys():
40
+ if df_activity["activity_unit"][row_nr] != activity.unit:
41
+ # check if all sources with this activity are updated.
42
+ error_message = (
43
+ "Changing activity rate unit for activity "
44
+ f"{activity_name} without updating all sources"
45
+ " in inventory with this activity. Define as new"
46
+ " activity by giving new name, or update all"
47
+ " sources with this activity simultaneously."
48
+ )
49
+ pointsource_ids = [
50
+ psa.source_id
51
+ for psa in PointSourceActivity.objects.filter(
52
+ activity_id=activity.id
53
+ )
54
+ ]
55
+ if len(pointsource_ids) > 0:
56
+ pointsources = [
57
+ PointSource.objects.get(id=i) for i in pointsource_ids
58
+ ]
59
+ facility_names = [
60
+ Facility.objects.get(id=source.facility_id).name
61
+ for source in pointsources
62
+ ]
63
+ source_names = [source.name for source in pointsources]
64
+ if "PointSource" in workbook.sheetnames:
65
+ new_pointsources = worksheet_to_dataframe(
66
+ workbook["PointSource"].values
67
+ )
68
+ for facility, source in zip(facility_names, source_names):
69
+ row_exists = (
70
+ (new_pointsources["source_name"] == source)
71
+ & (new_pointsources["facility_name"] == facility)
72
+ ).any()
73
+ if not row_exists:
74
+ return_message.append(
75
+ import_error(
76
+ error_message, validation=validation
77
+ )
78
+ )
79
+ else:
80
+ return_message.append(
81
+ import_error(error_message, validation=validation)
82
+ )
83
+ areasource_ids = [
84
+ psa.source_id
85
+ for psa in AreaSourceActivity.objects.filter(
86
+ activity_id=activity.id
87
+ )
88
+ ]
89
+ if len(areasource_ids) > 0:
90
+ areasources = [
91
+ AreaSource.objects.get(id=i) for i in areasource_ids
92
+ ]
93
+ facility_names = [
94
+ Facility.objects.get(id=source.facility_id).name
95
+ for source in areasources
96
+ ]
97
+ source_names = [source.name for source in areasources]
98
+ if "AreaSource" in workbook.sheetnames:
99
+ new_areasources = worksheet_to_dataframe(
100
+ workbook["AreaSource"].values
101
+ )
102
+ for facility, source in zip(facility_names, source_names):
103
+ row_exists = (
104
+ (new_areasources["source_name"] == source)
105
+ & (new_areasources["facility_name"] == facility)
106
+ ).any()
107
+ if not row_exists:
108
+ return_message.append(
109
+ import_error(
110
+ error_message, validation=validation
111
+ )
112
+ )
113
+ else:
114
+ return_message.append(
115
+ import_error(error_message, validation=validation)
116
+ )
117
+ gridsource_ids = [
118
+ psa.source_id
119
+ for psa in GridSourceActivity.objects.filter(
120
+ activity_id=activity.id
121
+ )
122
+ ]
123
+ if len(gridsource_ids) > 0:
124
+ gridsources = [
125
+ GridSource.objects.get(id=i) for i in gridsource_ids
126
+ ]
127
+ source_names = [source.name for source in gridsources]
128
+ if "GridSource" in workbook.sheetnames:
129
+ update_sources = [
130
+ name
131
+ in worksheet_to_dataframe(
132
+ workbook["GridSource"].values
133
+ )["name"]
134
+ for name in gridsources
135
+ ]
136
+ if not all(update_sources):
137
+ return_message.append(
138
+ import_error(
139
+ error_message,
140
+ validation=validation,
141
+ )
142
+ )
143
+ else:
144
+ return_message.append(
145
+ import_error(error_message, validation=validation)
146
+ )
147
+ setattr(activity, "unit", df_activity["activity_unit"][row_nr])
148
+ update_activities[activity_name] = activity
149
+ drop_emfacs += list(activities[activity_name].emissionfactors.all())
150
+ elif (
151
+ df_activity["activity_unit"][row_nr]
152
+ != update_activities[activity_name].unit
153
+ ):
154
+ return_message.append(
155
+ import_error(
156
+ f"conflicting units for activity '{activity_name}'",
157
+ validation=validation,
158
+ )
159
+ )
160
+ except KeyError:
161
+ if activity_name not in create_activities:
162
+ activity = Activity(
163
+ name=activity_name, unit=df_activity["activity_unit"][row_nr]
164
+ )
165
+ create_activities[activity_name] = activity
166
+ else:
167
+ if (
168
+ df_activity["activity_unit"][row_nr]
169
+ != create_activities[activity_name].unit
170
+ ):
171
+ return_message.append(
172
+ import_error(
173
+ "multiple rows for the same activity " + str(activity_name),
174
+ validation=validation,
175
+ )
176
+ )
177
+ Activity.objects.bulk_create(create_activities.values())
178
+ Activity.objects.bulk_update(
179
+ update_activities.values(),
180
+ [
181
+ "name",
182
+ "unit",
183
+ ],
184
+ )
185
+ # drop existing emfacs of activities that will be updated
186
+ EmissionFactor.objects.filter(pk__in=[inst.id for inst in drop_emfacs]).delete()
187
+ return_dict.update(
188
+ {
189
+ "activity": {
190
+ "updated": len(update_activities),
191
+ "created": len(create_activities),
192
+ }
193
+ }
194
+ )
195
+ data = workbook["EmissionFactor"].values
196
+ df_emfac = worksheet_to_dataframe(data)
197
+ substances = cache_queryset(Substance.objects.all(), "slug")
198
+ activities = cache_queryset(Activity.objects.all(), "name")
199
+ # unique together activity_name and substance
200
+ emissionfactors = cache_queryset(
201
+ EmissionFactor.objects.all(), ["activity", "substance"]
202
+ )
203
+ update_emfacs = []
204
+ create_emfacs = []
205
+ for row_nr in range(len(df_emfac)):
206
+ activity_name = df_emfac.iloc[row_nr]["activity_name"]
207
+ try:
208
+ activity = activities[activity_name]
209
+ subst = df_emfac.iloc[row_nr]["substance"]
210
+ try:
211
+ substance = substances[subst]
212
+ factor = df_emfac.iloc[row_nr]["factor"]
213
+ factor_unit = df_emfac.iloc[row_nr]["emissionfactor_unit"]
214
+ activity_quantity_unit, time_unit = activity.unit.split("/")
215
+ mass_unit, factor_quantity_unit = factor_unit.split("/")
216
+ if activity_quantity_unit != factor_quantity_unit:
217
+ # emission factor and activity need to have the same unit
218
+ # for quantity, eg GJ, m3 "pellets", number of produces bottles
219
+ return_message.append(
220
+ import_error(
221
+ "Units for emission factor and activity rate for"
222
+ f" '{activity_name}'"
223
+ " are inconsistent, convert units before importing.",
224
+ validation=validation,
225
+ )
226
+ )
227
+ else:
228
+ factor = activity_ef_unit_to_si(factor, factor_unit)
229
+ try:
230
+ emfac = emissionfactors[(activity, substance)]
231
+ setattr(emfac, "activity", activity)
232
+ setattr(emfac, "substance", substance)
233
+ setattr(emfac, "factor", factor)
234
+ update_emfacs.append(emfac)
235
+ except KeyError:
236
+ emfac = EmissionFactor(
237
+ activity=activity, substance=substance, factor=factor
238
+ )
239
+ create_emfacs.append(emfac)
240
+ except KeyError:
241
+ if subst == "PM2.5":
242
+ substance = substances["PM25"]
243
+ else:
244
+ return_message.append(
245
+ import_error(
246
+ f"unknown substance '{subst}'"
247
+ f" for emission factor on row '{row_nr}'",
248
+ validation=validation,
249
+ )
250
+ )
251
+ except KeyError:
252
+ return_message.append(
253
+ import_error(
254
+ f"unknown activity '{activity_name}'"
255
+ f" for emission factor on row '{row_nr}'",
256
+ validation=validation,
257
+ )
258
+ )
259
+ try:
260
+ EmissionFactor.objects.bulk_create(create_emfacs)
261
+ except IntegrityError:
262
+ return_message.append(
263
+ import_error(
264
+ "Two emission factors for same activity and substance are given.",
265
+ validation=validation,
266
+ )
267
+ )
268
+ EmissionFactor.objects.bulk_update(
269
+ update_emfacs, ["activity", "substance", "factor"]
270
+ )
271
+ return_dict.update(
272
+ {
273
+ "emission_factors": {
274
+ "updated": len(update_emfacs),
275
+ "created": len(create_emfacs),
276
+ }
277
+ }
278
+ )
279
+ return return_dict, return_message
@@ -0,0 +1,124 @@
1
+ from cetk.edb.models import ActivityCode, CodeSet, VerticalDist
2
+
3
+ from .utils import import_error, worksheet_to_dataframe
4
+
5
+
6
+ def import_codesetsheet(workbook, validation):
7
+ return_message = []
8
+ return_dict = {}
9
+ nr_codesets = CodeSet.objects.count()
10
+ data = workbook["CodeSet"].values
11
+ df_codeset = worksheet_to_dataframe(data)
12
+ slugs = df_codeset["slug"]
13
+ update_codesets = []
14
+ create_codesets = {}
15
+ for row_nr, slug in enumerate(slugs):
16
+ try:
17
+ codeset = CodeSet.objects.get(slug=slug)
18
+ setattr(codeset, "name", df_codeset["name"][row_nr])
19
+ setattr(codeset, "description", df_codeset["description"][row_nr])
20
+ update_codesets.append(codeset)
21
+ except CodeSet.DoesNotExist:
22
+ if nr_codesets + len(create_codesets) < 3:
23
+ codeset = CodeSet(
24
+ name=df_codeset["name"][row_nr],
25
+ slug=slug,
26
+ description=df_codeset["description"][row_nr],
27
+ )
28
+ if slug not in create_codesets:
29
+ create_codesets[slug] = codeset
30
+ else:
31
+ return_message.append(
32
+ import_error(
33
+ "Trying to import a new codeset," " but can have maximum 3.",
34
+ validation=validation,
35
+ )
36
+ )
37
+
38
+ CodeSet.objects.bulk_create(create_codesets.values())
39
+ CodeSet.objects.bulk_update(
40
+ update_codesets,
41
+ [
42
+ "name",
43
+ "description",
44
+ ],
45
+ )
46
+ return_dict = {
47
+ "codeset": {
48
+ "updated": len(update_codesets),
49
+ "created": len(create_codesets),
50
+ }
51
+ }
52
+ return return_dict, return_message
53
+
54
+
55
+ def import_activitycodesheet(workbook, validation):
56
+ return_message = []
57
+ data = workbook["ActivityCode"].values
58
+ df_activitycode = worksheet_to_dataframe(data)
59
+ update_activitycodes = []
60
+ create_activitycodes = {}
61
+ for row_key, row in df_activitycode.iterrows():
62
+ row_dict = row.to_dict()
63
+ try:
64
+ codeset = CodeSet.objects.get(slug=row_dict["codeset_slug"])
65
+ if "vertical_distribution_slug" in row_dict:
66
+ if row_dict["vertical_distribution_slug"] is not None:
67
+ try:
68
+ vdist = VerticalDist.objects.get(
69
+ slug=row_dict["vertical_distribution_slug"]
70
+ )
71
+ vdist_id = vdist.id
72
+ except VerticalDist.DoesNotExist:
73
+ return_message.append(
74
+ import_error(
75
+ f"Trying to import an activity-code "
76
+ "from row '{row_key}' but "
77
+ "Vertical Distribution "
78
+ f"'{row_dict['vertical_distribution_slug']}'"
79
+ " is not defined.",
80
+ validation=validation,
81
+ )
82
+ )
83
+ else:
84
+ vdist_id = None
85
+ else:
86
+ vdist_id = None
87
+ try:
88
+ activitycode = ActivityCode.objects.get(
89
+ code_set_id=codeset.id, code=row_dict["activitycode"]
90
+ )
91
+ setattr(activitycode, "label", row_dict["label"])
92
+ setattr(activitycode, "vertical_dist_id", vdist_id)
93
+ update_activitycodes.append(activitycode)
94
+ except ActivityCode.DoesNotExist:
95
+ activitycode = ActivityCode(
96
+ code=row_dict["activitycode"],
97
+ label=row_dict["label"],
98
+ code_set_id=codeset.id,
99
+ vertical_dist_id=vdist_id,
100
+ )
101
+ create_activitycodes[row_dict["activitycode"]] = activitycode
102
+ except CodeSet.DoesNotExist:
103
+ return_message.append(
104
+ import_error(
105
+ f"Trying to import an activity code from row '{row_key}'"
106
+ f"but CodeSet '{row_dict['codeset_slug']}' is not defined.",
107
+ validation=validation,
108
+ )
109
+ )
110
+ ActivityCode.objects.bulk_create(create_activitycodes.values())
111
+ ActivityCode.objects.bulk_update(
112
+ update_activitycodes,
113
+ [
114
+ "label",
115
+ "vertical_dist_id",
116
+ ],
117
+ )
118
+ return_dict = {
119
+ "activitycode": {
120
+ "updated": len(update_activitycodes),
121
+ "created": len(create_activitycodes),
122
+ }
123
+ }
124
+ return return_dict, return_message
@@ -0,0 +1,256 @@
1
+ import logging
2
+ from os.path import dirname
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ import rasterio as rio
7
+ from openpyxl import load_workbook
8
+
9
+ from cetk.edb.cache import cache_queryset
10
+ from cetk.edb.models import (
11
+ Activity,
12
+ GridSource,
13
+ Substance,
14
+ Timevar,
15
+ drop_gridsource_raster,
16
+ list_gridsource_rasters,
17
+ write_gridsource_raster,
18
+ )
19
+ from cetk.edb.units import activity_rate_unit_to_si, emission_unit_to_si
20
+
21
+ from .utils import (
22
+ cache_codesets,
23
+ get_activity_rate_columns,
24
+ get_substance_emission_columns,
25
+ nan2None,
26
+ worksheet_to_dataframe,
27
+ )
28
+ from .validation import (
29
+ data_to_raster,
30
+ validate_activity,
31
+ validate_activitycodes,
32
+ validate_columns,
33
+ validate_emission,
34
+ validate_raster,
35
+ validate_timevar,
36
+ validate_unit,
37
+ with_rownr_and_substance,
38
+ )
39
+
40
+ REQUIRED_COLUMNS_GRID = ["name", "rastername", "timevar", "path", "emission_unit"]
41
+
42
+
43
+ log = logging.getLogger(__name__)
44
+
45
+
46
+ def read_csv(filepath, encoding=None):
47
+ with open(filepath, encoding=encoding or "utf-8") as csvfile:
48
+ log.debug("reading point-sources from csv-file")
49
+ df = pd.read_csv(
50
+ csvfile,
51
+ sep=";",
52
+ skip_blank_lines=True,
53
+ comment="#",
54
+ dtype=np._str,
55
+ )
56
+ for col in REQUIRED_COLUMNS_GRID:
57
+ if col not in df.columns:
58
+ raise ImportError(f"Missing required column '{col}'")
59
+ return df
60
+
61
+
62
+ def read_xlsx(filepath):
63
+ try:
64
+ workbook = load_workbook(filename=filepath, data_only=True, read_only=True)
65
+ except Exception as exc:
66
+ raise ImportError(f"could not read workbook: {exc}")
67
+ if "GridSource" not in workbook:
68
+ raise ImportError("no 'GridSource' sheet found")
69
+
70
+ worksheet = workbook["GridSource"]
71
+ data = worksheet.values
72
+ df = worksheet_to_dataframe(data)
73
+ df = df.astype(dtype="string")
74
+ workbook.close()
75
+ return df
76
+
77
+
78
+ def read_import_file(filepath, encoding=None):
79
+ """read import file."""
80
+ if filepath.suffix == ".csv":
81
+ df = read_csv(filepath, encoding=encoding)
82
+ elif filepath.suffix == ".xlsx":
83
+ df = read_xlsx(filepath)
84
+ else:
85
+ raise ImportError("only xlsx and csv files are supported for import")
86
+ if "name" not in df.columns:
87
+ raise ImportError("Missing column 'name' for in GridSource sheet")
88
+ df = df.set_index("name")
89
+ return df
90
+
91
+
92
+ def validate_gridsources(df, timevars, code_sets, raster_names, datadir):
93
+ """validate grid-sources in dataframe but do not write to database.
94
+ returns (rasters, messages)
95
+ where rasters is a nested dict with all gridsource rasters
96
+ and messages is a list of all validation error messages.
97
+ """
98
+ row_nr = 2
99
+ rasters = {}
100
+ messages = []
101
+ act_cols = get_activity_rate_columns(df)
102
+ subst_cols = get_substance_emission_columns(df)
103
+ for _, row in df.iterrows():
104
+ row_dict = nan2None(row.to_dict())
105
+ messages += validate_activitycodes(row_dict, code_sets, row_nr=row_nr)
106
+ messages += validate_timevar(row_dict, timevars, row_nr=row_nr)
107
+ messages += validate_unit(row_dict, row_nr)
108
+ for col in filter(lambda x: row_dict[x] is not None, subst_cols):
109
+ subst_slug = col[6:]
110
+ messages += validate_raster(
111
+ row_dict, raster_names, datadir, rasters, row_nr, subst_slug
112
+ )
113
+ messages += validate_emission(row_dict, row_nr, subst_slug)
114
+ # if sum is specified instead of an emission total, the emission value
115
+ # is calculated as the sum of the emission raster
116
+ if row_dict[col] == "sum" and row_dict["path"] is None:
117
+ messages.append(
118
+ with_rownr_and_substance(
119
+ "raster path required for emission specified by 'sum'",
120
+ row_nr,
121
+ subst_slug,
122
+ )
123
+ )
124
+ for col in filter(lambda x: row_dict[x] is not None, act_cols):
125
+ act_name = col[4:].strip()
126
+ messages += validate_raster(
127
+ row_dict, raster_names, datadir, rasters, row_nr, subst_slug
128
+ )
129
+ messages += validate_activity(row_dict, row_nr, act_name)
130
+ # if sum is specified instead of an emission total, the emission value
131
+ # is calculated as the sum of the emission raster
132
+ if row_dict[col] == "sum" and row_dict["path"] is None:
133
+ messages.append(
134
+ with_rownr_and_substance(
135
+ "raster path required for activity rate specified by 'sum'",
136
+ row_nr,
137
+ act_name,
138
+ )
139
+ )
140
+ row_nr += 1
141
+ return rasters, messages
142
+
143
+
144
+ def create_or_update_rasters(rasters, raster_names):
145
+ """
146
+ Write all rasters with a path specified to the database.
147
+ Existing rasters will be replaced.
148
+ """
149
+ for raster_name, raster_data in rasters.items():
150
+ if raster_data["path"] is not None:
151
+ if raster_name in raster_names:
152
+ drop_gridsource_raster(raster_name)
153
+ with rio.open(raster_data["path"]) as raster:
154
+ write_gridsource_raster(raster, raster_name)
155
+
156
+
157
+ def import_gridsources(filepath, encoding=None):
158
+ """validate and/or import grid-sources from file."""
159
+
160
+ substances = cache_queryset(Substance.objects.all(), "slug")
161
+ timevars = cache_queryset(Timevar.objects.all(), "name")
162
+ code_sets = cache_codesets()
163
+ activities = cache_queryset(Activity.objects.all(), "name")
164
+ raster_names = list_gridsource_rasters()
165
+ datadir = dirname(filepath)
166
+
167
+ try:
168
+ df = read_import_file(filepath, encoding)
169
+ except ImportError as err:
170
+ return {}, [f"{err}"]
171
+
172
+ messages = []
173
+ messages += validate_columns(
174
+ df,
175
+ REQUIRED_COLUMNS_GRID,
176
+ substances=substances,
177
+ activities=activities,
178
+ code_sets=code_sets,
179
+ )
180
+ if len(messages) > 0:
181
+ messages += ["Could not validate gridsources, due to error in columns."]
182
+ return {}, messages
183
+
184
+ row_nr = 2
185
+ rasters, messages_sources = validate_gridsources(
186
+ df, timevars, code_sets, raster_names, datadir
187
+ )
188
+ messages += messages_sources
189
+ if len(messages) > 0:
190
+ return {}, messages
191
+
192
+ create_or_update_rasters(rasters, raster_names)
193
+ nr_created_sources = 0
194
+ nr_updated_sources = 0
195
+
196
+ subst_cols = get_substance_emission_columns(df)
197
+ act_cols = get_activity_rate_columns(df)
198
+ for name, row in df.iterrows():
199
+ row_dict = nan2None(row.to_dict())
200
+ src, created = GridSource.objects.get_or_create(name=name)
201
+ if created:
202
+ nr_created_sources += 1
203
+ else:
204
+ nr_updated_sources += 1
205
+ # set tags
206
+ tag_keys = [key for key in row_dict.keys() if key.startswith("tag:")]
207
+ src.tags = {
208
+ key[4:]: row_dict[key] for key in tag_keys if row_dict[key] is not None
209
+ }
210
+ validate_activitycodes(row_dict, code_sets, row_nr, src)
211
+ validate_timevar(row_dict, timevars, row_nr, src)
212
+ src.save()
213
+
214
+ # remove any existing emissions
215
+ if not created:
216
+ src.substances.all().delete()
217
+ src.activities.all().delete()
218
+
219
+ for col in filter(lambda x: row_dict[x] is not None, subst_cols):
220
+ subst = col[6:]
221
+ # if sum is specified instead of an emission total,
222
+ # the emission value is calculated as the sum of the raster
223
+ rname, rpath = data_to_raster(
224
+ row_dict["rastername"], row_dict["path"], datadir, subst
225
+ )
226
+ emis = {"substance": substances[subst], "raster": rname}
227
+ emis_value = row_dict[col]
228
+ unit = row_dict["emission_unit"]
229
+ if emis_value == "sum":
230
+ emis["value"] = emission_unit_to_si(rasters[rname]["sum"], unit)
231
+ else:
232
+ emis["value"] = emission_unit_to_si(float(emis_value), unit)
233
+ src.substances.create(**emis)
234
+
235
+ for col in filter(lambda x: row_dict[x] is not None, act_cols):
236
+ activity_name = col[4:]
237
+ rname, rpath = data_to_raster(
238
+ row_dict["rastername"], row_dict["path"], datadir, subst
239
+ )
240
+ emis = {"activity": activities[activity_name], "raster": rname}
241
+ rate = row_dict[col]
242
+ if rate == "sum":
243
+ emis["rate"] = activity_rate_unit_to_si(
244
+ rasters[rname]["sum"], emis["activity"].unit
245
+ )
246
+ else:
247
+ emis["rate"] = activity_rate_unit_to_si(
248
+ float(rate), emis["activity"].unit
249
+ )
250
+ src.activities.create(**emis)
251
+
252
+ row_nr += 1
253
+ db_updates = {
254
+ "gridsources": {"updated": nr_updated_sources, "created": nr_created_sources}
255
+ }
256
+ return db_updates, messages