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.
- cetk/__init__.py +87 -0
- cetk/db.py +41 -0
- cetk/edb/__init__.py +0 -0
- cetk/edb/apps.py +9 -0
- cetk/edb/cache.py +249 -0
- cetk/edb/const.py +26 -0
- cetk/edb/copy.py +18 -0
- cetk/edb/exporters.py +551 -0
- cetk/edb/importers/__init__.py +36 -0
- cetk/edb/importers/activity_import.py +279 -0
- cetk/edb/importers/codeset_import.py +124 -0
- cetk/edb/importers/gridsource_import.py +256 -0
- cetk/edb/importers/roadsource_import.py +1602 -0
- cetk/edb/importers/source_import.py +958 -0
- cetk/edb/importers/timevar_import.py +115 -0
- cetk/edb/importers/utils.py +92 -0
- cetk/edb/importers/validation.py +207 -0
- cetk/edb/ltreefield.py +76 -0
- cetk/edb/migrations/0001_initial.py +1731 -0
- cetk/edb/migrations/0002_data.py +156 -0
- cetk/edb/migrations/0003_create_gpkg_tables.py +14 -0
- cetk/edb/migrations/__init__.py +0 -0
- cetk/edb/models/__init__.py +49 -0
- cetk/edb/models/base.py +52 -0
- cetk/edb/models/common_models.py +144 -0
- cetk/edb/models/fleets.py +160 -0
- cetk/edb/models/gridsource_models.py +163 -0
- cetk/edb/models/road_classes.py +197 -0
- cetk/edb/models/road_models.py +413 -0
- cetk/edb/models/source_models.py +399 -0
- cetk/edb/models/timevar_models.py +111 -0
- cetk/edb/rasterize/__init__.py +1 -0
- cetk/edb/rasterize/rasterizer.py +1544 -0
- cetk/edb/signals.py +43 -0
- cetk/edb/traffic.py +75 -0
- cetk/edb/units.py +160 -0
- cetk/emissions/__init__.py +0 -0
- cetk/emissions/aggregate_emissions.sql +303 -0
- cetk/emissions/areasource_emissions.sql +53 -0
- cetk/emissions/calc.py +148 -0
- cetk/emissions/filters.py +179 -0
- cetk/emissions/gridsource_emissions.sql +52 -0
- cetk/emissions/pointsource_emissions.sql +53 -0
- cetk/emissions/queries.py +221 -0
- cetk/emissions/roadsource_emissions.sql +211 -0
- cetk/emissions/used_substances.sql +56 -0
- cetk/emissions/views.py +89 -0
- cetk/logging.py +68 -0
- cetk/settings.py +85 -0
- cetk/tools/__init__.py +1 -0
- cetk/tools/cetk_command.py +599 -0
- cetk/tools/manage.py +24 -0
- cetk/tools/utils.py +334 -0
- cetk/utils.py +62 -0
- cetk-0.0.4.dev0.dist-info/LICENSE +201 -0
- cetk-0.0.4.dev0.dist-info/METADATA +297 -0
- cetk-0.0.4.dev0.dist-info/RECORD +60 -0
- cetk-0.0.4.dev0.dist-info/WHEEL +5 -0
- cetk-0.0.4.dev0.dist-info/entry_points.txt +3 -0
- cetk-0.0.4.dev0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Generated by Django 1.11.11 on 2018-03-28 08:36
|
|
2
|
+
from __future__ import unicode_literals
|
|
3
|
+
|
|
4
|
+
from django.db import migrations
|
|
5
|
+
from django.utils.text import slugify
|
|
6
|
+
|
|
7
|
+
# slug, name and long_name of all substances
|
|
8
|
+
# these are also used to create corresponding emission and concentration params
|
|
9
|
+
SUBSTANCES = (
|
|
10
|
+
("activity", "activity", "Activity"),
|
|
11
|
+
("As", "As", "Arsenic"),
|
|
12
|
+
("BC", "BC", "Black Carbon"),
|
|
13
|
+
("BaP", "BaP", "Benzo[a]pyrene"),
|
|
14
|
+
("Cd", "Cd", "Cadmium"),
|
|
15
|
+
("Cr", "Cr", "Chromium"),
|
|
16
|
+
("Cu", "Cu", "Copper"),
|
|
17
|
+
("C6H6", "Benzene", "Benzene"),
|
|
18
|
+
("CH4", "Methane", "Methane"),
|
|
19
|
+
("CO", "CO", "Carbon monoxide"),
|
|
20
|
+
("CO2", "CO2", "Carbon dioxide"),
|
|
21
|
+
(
|
|
22
|
+
"Dioxin",
|
|
23
|
+
"Dioxin",
|
|
24
|
+
"Dioxin",
|
|
25
|
+
),
|
|
26
|
+
("HC", "Hydrocarbons", "Hydrocarbons"),
|
|
27
|
+
("HCB", "HCB", "Hexachlorobenzene"),
|
|
28
|
+
("HFC", "HFC", "Hydrofluorocarbons"),
|
|
29
|
+
("Hg", "Hg", "Mercury"),
|
|
30
|
+
("N2O", "N2O", "Nitrous oxide"),
|
|
31
|
+
("NH3", "NH3", "Ammonia"),
|
|
32
|
+
("Ni", "Ni", "Nickel"),
|
|
33
|
+
("NMHC", "NMHC", "Non-methane hydrocarbons"),
|
|
34
|
+
("NMVOC", "NMVOC", "Non-methane volatile organic compound"),
|
|
35
|
+
("NOx", "NOx", "Nitrogen oxides (as NO2)"),
|
|
36
|
+
("NO2", "NO2", "Nitrogen dioxide"),
|
|
37
|
+
("NO", "NO", "Nitrogen monooxide"),
|
|
38
|
+
("O3", "Ozone", "Ozone"),
|
|
39
|
+
("PAH4", "PAH4", "Sum of 4 polycyclic aromatic hydrocarbons"),
|
|
40
|
+
("Pb", "Pb", "Lead"),
|
|
41
|
+
("PFC", "PFC", "Perfluorocarbons"),
|
|
42
|
+
("PM10", "PM10", "Particulate matter < 10 micrometers in diameter"),
|
|
43
|
+
("PM25", "PM2.5", "Particulate matter < 2.5 micrometers in diameter"),
|
|
44
|
+
("PM10resusp", "PM10resusp", "Resuspended particles < 10 micrometers in diameter"),
|
|
45
|
+
(
|
|
46
|
+
"PM10wear",
|
|
47
|
+
"PM10wear",
|
|
48
|
+
"Particles < 10 micrometers in diameter from road, tyre and break wear",
|
|
49
|
+
),
|
|
50
|
+
(
|
|
51
|
+
"PM25resusp",
|
|
52
|
+
"PM2.5resusp",
|
|
53
|
+
"Resuspended particles < 2.5 micrometers in diameter",
|
|
54
|
+
),
|
|
55
|
+
(
|
|
56
|
+
"PM25wear",
|
|
57
|
+
"PM2.5wear",
|
|
58
|
+
"Particles < 2.5 micrometers in diameter from road, tyre and break wear",
|
|
59
|
+
),
|
|
60
|
+
("PN", "PN", "Particle Number"),
|
|
61
|
+
("Se", "Se", "Selenium"),
|
|
62
|
+
("PCB", "PCB", "Polychlorinated biphenyls"),
|
|
63
|
+
("SF6", "SF6", "Sulfur Hexafluoride"),
|
|
64
|
+
("SO2", "SO2", "Sulphur dioxide"),
|
|
65
|
+
("SOx", "SOx", "Sulphur oxides (as SO2)"),
|
|
66
|
+
("traffic_work", "traffic work", "Traffic work"),
|
|
67
|
+
("TSP", "TSP", "Total Suspended Particles"),
|
|
68
|
+
("Zn", "Zn", "Zinc"),
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# quantity, slug, name
|
|
72
|
+
PARAMETERS = (
|
|
73
|
+
("temperature", "dtmp", "Air temperature difference"),
|
|
74
|
+
("temperature", "temp", "Air temperature"),
|
|
75
|
+
("wind speed", "wspd", "Wind speed"),
|
|
76
|
+
("wind direction", "wdir", "Wind direction"),
|
|
77
|
+
("radiation", "netr", "Net radiation"),
|
|
78
|
+
("radiation", "glob", "Global radiation"),
|
|
79
|
+
("cloud cover", "tcc", "Total Cloud Cover"),
|
|
80
|
+
("pressure", "pres", "Pressure"),
|
|
81
|
+
("pressure", "psea", "Pressure at sea level"),
|
|
82
|
+
("relative humidity", "rhum", "Relative Humidity"),
|
|
83
|
+
("1 hour precipitation", "1h_prec", "1 hour Precipitation"),
|
|
84
|
+
("1 hour snow", "1h_snow", "1 hour Snow"),
|
|
85
|
+
("mixing height", "zi", "Mixing height"),
|
|
86
|
+
("albedo", "albedo", "Albedo"),
|
|
87
|
+
("roughness length", "z0", "Roughness length"),
|
|
88
|
+
("traffic condition", "traffic_condition", "Traffic condition"),
|
|
89
|
+
("landuse class", "landuse_class", "Landuse class"),
|
|
90
|
+
("length", "lmo", "Monin Obukhov Length"),
|
|
91
|
+
("height", "height", "height"),
|
|
92
|
+
("height", "z_sl", "surface layer height"),
|
|
93
|
+
("heat flux", "h_sens", "Sensible heat flux"),
|
|
94
|
+
("scale", "theta_star", "Potential temperature scale"),
|
|
95
|
+
("scale", "u_star", "Friction velocity"),
|
|
96
|
+
("scale", "h_star", "heat flux scale"),
|
|
97
|
+
("scale", "w_star", "convective velocity scale"),
|
|
98
|
+
("dummy", "dummy", "dummy"),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def auto_name_parameter(instance):
|
|
103
|
+
quantity = instance.quantity.capitalize()
|
|
104
|
+
if instance.substance is not None:
|
|
105
|
+
instance.name = f"{quantity} {instance.substance.name}"
|
|
106
|
+
else:
|
|
107
|
+
instance.name = quantity
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def auto_slug_parameter(instance):
|
|
111
|
+
if instance.substance is not None:
|
|
112
|
+
quantity = slugify(instance.quantity)
|
|
113
|
+
instance.slug = f"{quantity}_{instance.substance.slug}"
|
|
114
|
+
else:
|
|
115
|
+
instance.slug = slugify(instance.name)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def create_parameters(apps, schema_editor):
|
|
119
|
+
Substance = apps.get_model("edb", "Substance")
|
|
120
|
+
Parameter = apps.get_model("edb", "Parameter")
|
|
121
|
+
|
|
122
|
+
# create substances and related parameters
|
|
123
|
+
for slug, name, long_name in SUBSTANCES:
|
|
124
|
+
substance = Substance.objects.create(slug=slug, name=name, long_name=long_name)
|
|
125
|
+
|
|
126
|
+
# traffic work is created as 'traffic' parameter instead of 'emission'
|
|
127
|
+
if slug == "traffic_work":
|
|
128
|
+
Parameter.objects.create(
|
|
129
|
+
quantity="traffic",
|
|
130
|
+
slug="traffic_work",
|
|
131
|
+
name="Traffic work",
|
|
132
|
+
substance=substance,
|
|
133
|
+
)
|
|
134
|
+
continue
|
|
135
|
+
|
|
136
|
+
# save is not called in migrations
|
|
137
|
+
# and model methods are not available
|
|
138
|
+
emis = Parameter(quantity="emission", substance=substance)
|
|
139
|
+
auto_name_parameter(emis)
|
|
140
|
+
auto_slug_parameter(emis)
|
|
141
|
+
emis.save()
|
|
142
|
+
|
|
143
|
+
conc = Parameter(quantity="concentration", substance=substance)
|
|
144
|
+
auto_name_parameter(conc)
|
|
145
|
+
auto_slug_parameter(conc)
|
|
146
|
+
conc.save()
|
|
147
|
+
|
|
148
|
+
# create generic parameters
|
|
149
|
+
for quantity, slug, name in PARAMETERS:
|
|
150
|
+
Parameter.objects.create(quantity=quantity, name=name, slug=slug)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class Migration(migrations.Migration):
|
|
154
|
+
dependencies = [("edb", "0001_initial")]
|
|
155
|
+
|
|
156
|
+
operations = [migrations.RunPython(create_parameters)]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Generated by Django 4.2.4 on 2023-11-02 10:19
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("edb", "0002_data"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.RunSQL("select gpkgCreateBaseTables()"),
|
|
13
|
+
migrations.RunSQL("select gpkgInsertEpsgSRID(4979)"),
|
|
14
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from .common_models import Parameter, Settings, Substance # noqa
|
|
2
|
+
from .fleets import Fleet, FleetMember, FleetMemberFuel # noqa: F401
|
|
3
|
+
from .gridsource_models import ( # noqa
|
|
4
|
+
GridSource,
|
|
5
|
+
GridSourceActivity,
|
|
6
|
+
GridSourceSubstance,
|
|
7
|
+
drop_gridsource_raster,
|
|
8
|
+
get_gridsource_raster,
|
|
9
|
+
list_gridsource_rasters,
|
|
10
|
+
write_gridsource_raster,
|
|
11
|
+
)
|
|
12
|
+
from .road_classes import ( # noqa: F401
|
|
13
|
+
PrefetchRoadClassAttributes,
|
|
14
|
+
RoadAttribute,
|
|
15
|
+
RoadAttributeValue,
|
|
16
|
+
RoadClass,
|
|
17
|
+
get_valid_road_attribute_values,
|
|
18
|
+
)
|
|
19
|
+
from .road_models import ( # noqa: F401
|
|
20
|
+
VELOCITY_CHOICES,
|
|
21
|
+
CongestionProfile,
|
|
22
|
+
RoadSource,
|
|
23
|
+
TrafficSituation,
|
|
24
|
+
Vehicle,
|
|
25
|
+
VehicleEF,
|
|
26
|
+
VehicleFuel,
|
|
27
|
+
VehicleFuelComb,
|
|
28
|
+
default_congestion_profile_data,
|
|
29
|
+
)
|
|
30
|
+
from .source_models import ( # noqa
|
|
31
|
+
Activity,
|
|
32
|
+
ActivityCode,
|
|
33
|
+
AreaSource,
|
|
34
|
+
AreaSourceActivity,
|
|
35
|
+
AreaSourceSubstance,
|
|
36
|
+
CodeSet,
|
|
37
|
+
EmissionFactor,
|
|
38
|
+
Facility,
|
|
39
|
+
PointSource,
|
|
40
|
+
PointSourceActivity,
|
|
41
|
+
PointSourceSubstance,
|
|
42
|
+
VerticalDist,
|
|
43
|
+
)
|
|
44
|
+
from .timevar_models import ( # noqa
|
|
45
|
+
ColdstartTimevar,
|
|
46
|
+
FlowTimevar,
|
|
47
|
+
Timevar,
|
|
48
|
+
timevar_to_series,
|
|
49
|
+
)
|
cetk/edb/models/base.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Abstract base models for cetk."""
|
|
2
|
+
|
|
3
|
+
from django.contrib.gis.db import models
|
|
4
|
+
from django.utils.text import slugify
|
|
5
|
+
from django.utils.translation import gettext_lazy as _
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseNamedModel(models.Model):
|
|
9
|
+
"""Base class for models with a name and a slug field."""
|
|
10
|
+
|
|
11
|
+
class Meta:
|
|
12
|
+
abstract = True
|
|
13
|
+
|
|
14
|
+
def clean(self):
|
|
15
|
+
if not self.slug and self.name:
|
|
16
|
+
self.slug = slugify(self.name)
|
|
17
|
+
|
|
18
|
+
def __str__(self):
|
|
19
|
+
return self.name
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class NamedModelManager(models.Manager):
|
|
23
|
+
"""Database manager for named models."""
|
|
24
|
+
|
|
25
|
+
def get_by_natural_key(self, slug):
|
|
26
|
+
"""Return a model instance given its slug."""
|
|
27
|
+
return self.get(slug=slug)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class NaturalKeyManager(models.Manager):
|
|
31
|
+
|
|
32
|
+
"""Database manager for models with natural key."""
|
|
33
|
+
|
|
34
|
+
def get_by_natural_key(self, *key):
|
|
35
|
+
"""Return a model instance given its natural key."""
|
|
36
|
+
return self.get(**dict(zip(self.model.natural_key_fields, key)))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class NamedModel(BaseNamedModel):
|
|
40
|
+
"""A model with a unique name and slug."""
|
|
41
|
+
|
|
42
|
+
name = models.CharField(_("name"), max_length=64, unique=True)
|
|
43
|
+
slug = models.SlugField(_("slug"), max_length=64, unique=True)
|
|
44
|
+
|
|
45
|
+
objects = NamedModelManager()
|
|
46
|
+
|
|
47
|
+
class Meta:
|
|
48
|
+
abstract = True
|
|
49
|
+
|
|
50
|
+
def natural_key(self):
|
|
51
|
+
"""Return the natural key (the slug) for this model instance."""
|
|
52
|
+
return (self.slug,)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
from django.contrib.gis.db import models
|
|
2
|
+
from django.contrib.gis.geos import GEOSGeometry
|
|
3
|
+
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
|
|
4
|
+
from django.utils.text import slugify
|
|
5
|
+
from django.utils.translation import gettext_lazy as _
|
|
6
|
+
|
|
7
|
+
from cetk.edb.const import WGS84_SRID
|
|
8
|
+
from cetk.edb.models.base import NamedModel
|
|
9
|
+
from cetk.edb.models.source_models import CodeSet
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SettingsManager(models.Manager):
|
|
13
|
+
# makes sure always just one instance of settings exists
|
|
14
|
+
def get_queryset(self):
|
|
15
|
+
return super().get_queryset()[:1]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
DEFAULT_EXTENT = "Polygon((-25.0 35.0, -25.0 70.0, 40.0 70.0, 40.0 35.0, -25.0 35.0))"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Substance(NamedModel):
|
|
22
|
+
"""A chemical substance."""
|
|
23
|
+
|
|
24
|
+
long_name = models.CharField(_("long name"), max_length=64)
|
|
25
|
+
|
|
26
|
+
class Meta:
|
|
27
|
+
db_table = "substances"
|
|
28
|
+
default_related_name = "substances"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Parameter(NamedModel):
|
|
32
|
+
"""A parameter."""
|
|
33
|
+
|
|
34
|
+
quantity = models.CharField(_("physical quantity"), max_length=30)
|
|
35
|
+
substance = models.ForeignKey(
|
|
36
|
+
Substance, on_delete=models.CASCADE, null=True, verbose_name=_("substance")
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
class Meta:
|
|
40
|
+
db_table = "parameters"
|
|
41
|
+
default_related_name = "parameters"
|
|
42
|
+
|
|
43
|
+
def validate_unique(self, *args, **kwargs):
|
|
44
|
+
"""Avoid duplicate emission or conc. parameters for a substance."""
|
|
45
|
+
super().validate_unique(*args, **kwargs)
|
|
46
|
+
if self.quantity in ("emission", "concentration"):
|
|
47
|
+
duplicates = type(self).objects.filter(
|
|
48
|
+
quantity=self.quantity, substance=self.substance
|
|
49
|
+
)
|
|
50
|
+
if duplicates.exists():
|
|
51
|
+
raise ValidationError(
|
|
52
|
+
{
|
|
53
|
+
NON_FIELD_ERRORS: [
|
|
54
|
+
f"A parameter for {self.quantity} of {self.substance} "
|
|
55
|
+
f"already exist"
|
|
56
|
+
]
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def _auto_name(self):
|
|
61
|
+
"""Auto-generate a name."""
|
|
62
|
+
quantity = self.quantity.capitalize()
|
|
63
|
+
if self.substance is not None:
|
|
64
|
+
self.name = f"{quantity} {self.substance.name}"
|
|
65
|
+
else:
|
|
66
|
+
self.name = quantity
|
|
67
|
+
|
|
68
|
+
def _auto_slug(self):
|
|
69
|
+
"""Auto-generate a slug."""
|
|
70
|
+
if self.substance is not None:
|
|
71
|
+
quantity = slugify(self.quantity)
|
|
72
|
+
self.slug = f"{quantity}_{self.substance.slug}"
|
|
73
|
+
else:
|
|
74
|
+
self.slug = slugify(self.name)
|
|
75
|
+
|
|
76
|
+
def save(self, *args, **kwargs):
|
|
77
|
+
"""Overloads save to auto-generate name and slug if missing."""
|
|
78
|
+
if self.name is None:
|
|
79
|
+
self._auto_name()
|
|
80
|
+
if self.slug is None:
|
|
81
|
+
self._auto_slug()
|
|
82
|
+
super().save(*args, **kwargs)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Settings(models.Model):
|
|
86
|
+
"""Inventory specific database settings, replaces gadgets Domain and Inventory."""
|
|
87
|
+
|
|
88
|
+
srid = models.IntegerField(
|
|
89
|
+
_("SRID"), help_text=_("Spatial reference system identifier")
|
|
90
|
+
)
|
|
91
|
+
extent = models.PolygonField(_("extent"), geography=True)
|
|
92
|
+
|
|
93
|
+
timezone = models.CharField(_("timezone"), max_length=64)
|
|
94
|
+
|
|
95
|
+
# some functionality in Gadget only works for one out of the three codesets.
|
|
96
|
+
codeset1 = models.ForeignKey(
|
|
97
|
+
CodeSet, null=True, on_delete=models.SET_NULL, related_name="+"
|
|
98
|
+
)
|
|
99
|
+
codeset2 = models.ForeignKey(
|
|
100
|
+
CodeSet, null=True, on_delete=models.SET_NULL, related_name="+"
|
|
101
|
+
)
|
|
102
|
+
codeset3 = models.ForeignKey(
|
|
103
|
+
CodeSet, null=True, on_delete=models.SET_NULL, related_name="+"
|
|
104
|
+
)
|
|
105
|
+
objects = SettingsManager()
|
|
106
|
+
|
|
107
|
+
class Meta:
|
|
108
|
+
db_table = "settings"
|
|
109
|
+
default_related_name = "settings"
|
|
110
|
+
|
|
111
|
+
def get_current():
|
|
112
|
+
# Retrieve the settings, if exist
|
|
113
|
+
return Settings.objects.get_or_create(
|
|
114
|
+
defaults={
|
|
115
|
+
"srid": 3006,
|
|
116
|
+
"timezone": "Europe/Stockholm",
|
|
117
|
+
"extent": GEOSGeometry(DEFAULT_EXTENT, WGS84_SRID),
|
|
118
|
+
"codeset1": CodeSet.objects.filter(id=1).first(),
|
|
119
|
+
"codeset2": CodeSet.objects.filter(id=2).first(),
|
|
120
|
+
"codeset3": CodeSet.objects.filter(id=3).first(),
|
|
121
|
+
}
|
|
122
|
+
)[0]
|
|
123
|
+
|
|
124
|
+
def get_codeset_index(self, codeset):
|
|
125
|
+
"""Return index of a specific codeset."""
|
|
126
|
+
if type(codeset) == str:
|
|
127
|
+
codeset_slug = codeset
|
|
128
|
+
elif type(codeset) == CodeSet:
|
|
129
|
+
codeset_slug = codeset.slug
|
|
130
|
+
else:
|
|
131
|
+
raise ValueError(f"codeset '{codeset}' is not of valid type")
|
|
132
|
+
# if self.codeset1 is not None:
|
|
133
|
+
# if codeset_slug == self.codeset1.slug:
|
|
134
|
+
# return 1
|
|
135
|
+
# if self.codeset2 is not None:
|
|
136
|
+
# if codeset_slug == self.codeset2.slug:
|
|
137
|
+
# return 2
|
|
138
|
+
# if self.codeset3 is not None:
|
|
139
|
+
# if codeset_slug == self.codeset3.slug:
|
|
140
|
+
# return 3
|
|
141
|
+
if len(CodeSet.objects.filter(slug=codeset_slug)) > 0:
|
|
142
|
+
return CodeSet.objects.filter(slug=codeset_slug).first().id
|
|
143
|
+
else:
|
|
144
|
+
raise ValueError(f"codeset '{codeset}' not found in inventory settings")
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Database models to describe road fleets."""
|
|
2
|
+
|
|
3
|
+
import itertools
|
|
4
|
+
|
|
5
|
+
from django.db import models
|
|
6
|
+
from django.db.models import Q
|
|
7
|
+
|
|
8
|
+
from cetk.edb.const import CHAR_FIELD_LENGTH
|
|
9
|
+
|
|
10
|
+
from .road_models import Vehicle, VehicleFuel, VehicleFuelComb
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FleetManager(models.Manager):
|
|
14
|
+
"""Custom database manager for the Fleet model."""
|
|
15
|
+
|
|
16
|
+
use_in_migrations = True
|
|
17
|
+
|
|
18
|
+
def bulk_create_from_dicts(self, data):
|
|
19
|
+
vehicles = {v.name: v for v in Vehicle.objects.all()}
|
|
20
|
+
fuels = {f.name: f for f in VehicleFuel.objects.all()}
|
|
21
|
+
validcombinations = {
|
|
22
|
+
(vfc.vehicle.name, vfc.fuel.name)
|
|
23
|
+
for vfc in VehicleFuelComb.objects.select_related("vehicle", "fuel")
|
|
24
|
+
}
|
|
25
|
+
for f in data:
|
|
26
|
+
for m in f["members"]:
|
|
27
|
+
for fu in m["fuels"]:
|
|
28
|
+
# breakpoint()
|
|
29
|
+
if (m["vehicle"], fu["fuel"]) not in validcombinations:
|
|
30
|
+
raise ValueError(
|
|
31
|
+
f"{m['vehicle']}/{fu['fuel']} is not a valid vehicle/fuel"
|
|
32
|
+
+ " combination"
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
fleets = [
|
|
36
|
+
self.model(
|
|
37
|
+
name=f["name"],
|
|
38
|
+
default_heavy_vehicle_share=f["default_heavy_vehicle_share"],
|
|
39
|
+
tags=f.get("tags", {}),
|
|
40
|
+
)
|
|
41
|
+
for f in data
|
|
42
|
+
]
|
|
43
|
+
self.model.objects.bulk_create(fleets)
|
|
44
|
+
fleets = self.model.objects.all()
|
|
45
|
+
|
|
46
|
+
membermodel = self.model._meta.get_field("vehicles").related_model
|
|
47
|
+
members = [
|
|
48
|
+
membermodel(
|
|
49
|
+
fraction=m["fraction"],
|
|
50
|
+
coldstart_fraction=m.get("coldstart_fraction", 0),
|
|
51
|
+
vehicle=vehicles[m["vehicle"]],
|
|
52
|
+
timevar=m["timevar"],
|
|
53
|
+
coldstart_timevar=m.get("coldstart_timevar"),
|
|
54
|
+
fleet=fleet,
|
|
55
|
+
)
|
|
56
|
+
for fleet, f in zip(fleets, data)
|
|
57
|
+
for m in f["members"]
|
|
58
|
+
]
|
|
59
|
+
membermodel.objects.bulk_create(members)
|
|
60
|
+
members = membermodel.objects.all()
|
|
61
|
+
|
|
62
|
+
memberfuelmodel = membermodel._meta.get_field("fuels").related_model
|
|
63
|
+
memberfuels = [
|
|
64
|
+
memberfuelmodel(
|
|
65
|
+
fraction=fu["fraction"], fuel=fuels[fu["fuel"]], fleet_member=member
|
|
66
|
+
)
|
|
67
|
+
for member, m in zip(
|
|
68
|
+
members, itertools.chain.from_iterable(f["members"] for f in data)
|
|
69
|
+
)
|
|
70
|
+
for fu in m["fuels"]
|
|
71
|
+
]
|
|
72
|
+
memberfuelmodel.objects.bulk_create(memberfuels)
|
|
73
|
+
|
|
74
|
+
return fleets
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class Fleet(models.Model):
|
|
78
|
+
"""A composition of vehicles."""
|
|
79
|
+
|
|
80
|
+
name = models.CharField(max_length=CHAR_FIELD_LENGTH, unique=True)
|
|
81
|
+
default_heavy_vehicle_share = models.FloatField()
|
|
82
|
+
tags = models.JSONField(blank=True, default=dict)
|
|
83
|
+
|
|
84
|
+
objects = FleetManager()
|
|
85
|
+
|
|
86
|
+
class Meta:
|
|
87
|
+
default_related_name = "fleets"
|
|
88
|
+
constraints = [
|
|
89
|
+
models.CheckConstraint(
|
|
90
|
+
check=Q(default_heavy_vehicle_share__range=(0, 1)),
|
|
91
|
+
name="fleet_default_heavy_vehicle_share_between_0_and_1",
|
|
92
|
+
),
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
def __str__(self):
|
|
96
|
+
"""Unicode representation of fleet."""
|
|
97
|
+
return self.name
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def default_light_vehicle_share(self):
|
|
101
|
+
"""Default share of light vehicles in fleet."""
|
|
102
|
+
return 1 - self.default_heavy_vehicle_share
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class FleetMember(models.Model):
|
|
106
|
+
"""A member vehicle of a fleet."""
|
|
107
|
+
|
|
108
|
+
# share represented by vehicle (sums to 1.0 for the fleet)
|
|
109
|
+
fraction = models.FloatField(default=0.0)
|
|
110
|
+
coldstart_fraction = models.FloatField(default=0.0)
|
|
111
|
+
vehicle = models.ForeignKey("Vehicle", on_delete=models.PROTECT, related_name="+")
|
|
112
|
+
timevar = models.ForeignKey(
|
|
113
|
+
"FlowTimevar",
|
|
114
|
+
on_delete=models.SET_NULL,
|
|
115
|
+
related_name="+",
|
|
116
|
+
null=True,
|
|
117
|
+
blank=True,
|
|
118
|
+
)
|
|
119
|
+
coldstart_timevar = models.ForeignKey(
|
|
120
|
+
"ColdstartTimevar",
|
|
121
|
+
on_delete=models.SET_NULL,
|
|
122
|
+
related_name="+",
|
|
123
|
+
null=True,
|
|
124
|
+
blank=True,
|
|
125
|
+
)
|
|
126
|
+
fleet = models.ForeignKey(Fleet, on_delete=models.CASCADE, related_name="vehicles")
|
|
127
|
+
|
|
128
|
+
class Meta:
|
|
129
|
+
constraints = [
|
|
130
|
+
models.UniqueConstraint(
|
|
131
|
+
fields=("fleet", "vehicle"), name="fleetmember_unique_vehicle_in_fleet"
|
|
132
|
+
),
|
|
133
|
+
]
|
|
134
|
+
|
|
135
|
+
def __str__(self):
|
|
136
|
+
"""Unicode representation of fleet member vehicle."""
|
|
137
|
+
return self.vehicle.name
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class FleetMemberFuel(models.Model):
|
|
141
|
+
"""A fuel used by a fleet member."""
|
|
142
|
+
|
|
143
|
+
# share of fleet member represented by fuel (sums to 1.0 for fleet member)
|
|
144
|
+
fraction = models.FloatField(default=0.0)
|
|
145
|
+
fuel = models.ForeignKey("VehicleFuel", on_delete=models.PROTECT, related_name="+")
|
|
146
|
+
fleet_member = models.ForeignKey(
|
|
147
|
+
FleetMember, on_delete=models.CASCADE, related_name="fuels"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
class Meta:
|
|
151
|
+
constraints = [
|
|
152
|
+
models.UniqueConstraint(
|
|
153
|
+
fields=("fleet_member", "fuel"),
|
|
154
|
+
name="fleetmemberfuel_unique_fuel_in_fleetmember",
|
|
155
|
+
),
|
|
156
|
+
]
|
|
157
|
+
|
|
158
|
+
def __str__(self):
|
|
159
|
+
"""Unicode representation of fleet member fuel."""
|
|
160
|
+
return self.fuel.name
|