eflips-eval 1.2.0__tar.gz → 1.2.2__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: eflips-eval
3
- Version: 1.2.0
3
+ Version: 1.2.2
4
4
  Summary:
5
5
  Author: Ludger Heide
6
6
  Author-email: ludger.heide@lhtechnologies.de
@@ -1,26 +1,10 @@
1
+ from collections import Counter
1
2
  from datetime import datetime
2
3
  from typing import Dict, List
3
4
 
4
5
  import pandas as pd
5
6
  import sqlalchemy
6
- from eflips.model import Rotation, Trip
7
-
8
-
9
- def rotation_name_for_sorting(rotation_name: str) -> str:
10
- """
11
- Takes a rotation name, which in the BVG is a string of two numbers separated by a '/' character, and returns a string
12
- that can be used for sorting. The first part of the string is returned for BVG rotations.
13
-
14
- Other rotation names are not supported and will return the rotation name itself.
15
-
16
- :param rotation_name: The rotation name
17
- :return: a sortable string
18
- """
19
-
20
- if rotation_name is not None and "/" in rotation_name:
21
- return rotation_name.split("/")[0]
22
- else:
23
- return rotation_name
7
+ from eflips.model import Rotation, Trip, Route
24
8
 
25
9
 
26
10
  def rotation_info(
@@ -40,6 +24,9 @@ def rotation_info(
40
24
  - time_start: the departure of the first trip
41
25
  - time_end: the arrival of the last trip
42
26
  - line_name: the name of the line, which is the first part of the rotation name. Used for sorting
27
+ - line_is_unified: True if the rotation only contains one line
28
+ - start_station: the name of the departure station
29
+ - end_station: the name of the arrival station
43
30
 
44
31
  :param scenario_id: The scenario id for which to create the dataframe
45
32
  :param session: An sqlalchemy session to an eflips-model database
@@ -49,7 +36,22 @@ def rotation_info(
49
36
 
50
37
  result: List[Dict[str, int | float | str | datetime]] = []
51
38
 
52
- rotations = session.query(Rotation).filter(Rotation.scenario_id == scenario_id)
39
+ rotations = (
40
+ session.query(Rotation)
41
+ .filter(Rotation.scenario_id == scenario_id)
42
+ .options(
43
+ sqlalchemy.orm.joinedload(Rotation.trips)
44
+ .joinedload(Trip.route)
45
+ .joinedload(Route.line),
46
+ sqlalchemy.orm.joinedload(Rotation.vehicle_type),
47
+ sqlalchemy.orm.joinedload(Rotation.trips)
48
+ .joinedload(Trip.route)
49
+ .joinedload(Route.departure_station),
50
+ sqlalchemy.orm.joinedload(Rotation.trips)
51
+ .joinedload(Trip.route)
52
+ .joinedload(Route.arrival_station),
53
+ )
54
+ )
53
55
 
54
56
  if rotation_ids is not None:
55
57
  if isinstance(rotation_ids, int):
@@ -62,6 +64,16 @@ def rotation_info(
62
64
  for trip in rotation.trips:
63
65
  distance += trip.route.distance / 1000
64
66
 
67
+ # We want to be able to sort and/or group by line. Therefore we need to identify the predominant line name for each
68
+ # rotation.
69
+ line_names: Dict[str, int] = {}
70
+ for trip in rotation.trips:
71
+ line_name = trip.route.line.name
72
+ if line_name not in line_names:
73
+ line_names[line_name] = 0
74
+ line_names[line_name] += 1
75
+ line_name = Counter(line_names).most_common(1)[0][0]
76
+
65
77
  result.append(
66
78
  {
67
79
  "rotation_id": rotation.id,
@@ -69,16 +81,17 @@ def rotation_info(
69
81
  "vehicle_type_id": rotation.vehicle_type_id,
70
82
  "vehicle_type_name": rotation.vehicle_type.name,
71
83
  "total_distance": distance,
84
+ "line_name": line_name,
85
+ "line_is_unified": len(line_names)
86
+ == 1, # True if there is only one line in the rotation
72
87
  "time_start": rotation.trips[0].departure_time,
73
88
  "time_end": rotation.trips[-1].arrival_time,
89
+ "start_station": rotation.trips[0].route.departure_station.name,
90
+ "end_station": rotation.trips[-1].route.arrival_station.name,
74
91
  }
75
92
  )
76
93
 
77
- # We want to properly sort by rotation name, which is a bit intricate, as it's a string of two numbers divided by a
78
- # '/' character. We can't just sort by the string, as "10/11" would come after "10/1". We need to split the string
79
- # into its components and sort by them.
80
94
  df = pd.DataFrame(result)
81
- df["line_name"] = df["rotation_name"].apply(rotation_name_for_sorting)
82
95
 
83
96
  df.sort_values(by=["line_name", "time_start"], inplace=True)
84
97
 
@@ -19,6 +19,9 @@ def rotation_info(prepared_data: pd.DataFrame) -> go.Figure:
19
19
  - time_start: the departure of the first trip
20
20
  - time_end: the arrival of the last trip
21
21
  - line_name: the name of the line, which is the first part of the rotation name. Used for sorting
22
+ - line_is_unified: True if the rotation only contains one line
23
+ - start_station: the name of the departure station
24
+ - end_station: the name of the arrival station
22
25
 
23
26
  :return: A plotly figure object
24
27
  """
@@ -30,11 +33,22 @@ def rotation_info(prepared_data: pd.DataFrame) -> go.Figure:
30
33
  y="rotation_name",
31
34
  color="total_distance",
32
35
  labels={
36
+ "time_start": "Departure Time",
37
+ "time_end": "Arrival Time",
33
38
  "rotation_name": "Rotation Name",
34
39
  "total_distance": "Total Distance (km)",
35
40
  "line_name": "Line Name",
41
+ "vehicle_type_name": "Vehicle Type Name",
42
+ "start_station": "Start Station",
43
+ "end_station": "End Station",
36
44
  },
37
- hover_data=["vehicle_type_name"],
45
+ hover_name="rotation_name",
46
+ hover_data=[
47
+ "vehicle_type_name",
48
+ "total_distance",
49
+ "start_station",
50
+ "end_station",
51
+ ],
38
52
  pattern_shape="line_name",
39
53
  )
40
54
  fig.update_layout(legend_orientation="h")
@@ -1,6 +1,6 @@
1
1
  import zoneinfo
2
2
  from datetime import datetime, timedelta
3
- from typing import Dict, List, Iterable, Tuple
3
+ from typing import Dict, List, Iterable, Tuple, Optional
4
4
 
5
5
  import numpy as np
6
6
  import numpy.typing as npt
@@ -247,8 +247,8 @@ def power_and_occupancy(
247
247
  )
248
248
  energy += this_event_energy
249
249
 
250
- # For occupancy, we we create an entry at the beginning and end of the event, then resample to 1-second intervals
251
- # with left=0 and right=0
250
+ # For occupancy, we create an entry at the beginning and end of the event, then resample to 1-second
251
+ # intervals with left=0 and right=0
252
252
  this_event_occupancy = np.interp(
253
253
  time_as_unix,
254
254
  [event.time_start.timestamp(), event.time_end.timestamp() - 1],
@@ -262,11 +262,16 @@ def power_and_occupancy(
262
262
  power = (np.diff(energy) / np.diff(time_as_unix).astype(float)) * 3600 # kW
263
263
 
264
264
  # Create the dataframe
265
+ # First, change the time to the local timezone
266
+
267
+ tz = datetime.now().astimezone().tzinfo
268
+ time_as_local_tz = pd.to_datetime(time).tz_localize("UTC").tz_convert(tz)
269
+
265
270
  result = pd.DataFrame(
266
271
  {
267
- "time": time[1:],
272
+ "time": time_as_local_tz[:-1],
268
273
  "power": power,
269
- "occupancy": occupancy[1:],
274
+ "occupancy": occupancy[:-1],
270
275
  }
271
276
  )
272
277
  return result
@@ -316,7 +321,7 @@ def specific_energy_consumption(scenario_id: int, session: Session) -> pd.DataFr
316
321
  def vehicle_soc(
317
322
  vehicle_id: int,
318
323
  session: Session,
319
- timezone: zoneinfo.ZoneInfo = zoneinfo.ZoneInfo("UTC"),
324
+ timezone: Optional[zoneinfo.ZoneInfo] = zoneinfo.ZoneInfo("Europe/Berlin"),
320
325
  ) -> Tuple[pd.DataFrame, Dict[str, List[Tuple[str, datetime, datetime]]]]:
321
326
  """
322
327
  This function takes in a vehicle id and returns a description what happened to the vehicle over time.
@@ -331,7 +336,7 @@ def vehicle_soc(
331
336
  - "rotation": A list of rotation names and the time the rotation started and ended
332
337
  - "charging": A list of the location of the charging and the time the charging started and ended
333
338
 
334
- :param timezone: Explicit timezone information to use for the visualization. Default is UTC
339
+ :param timezone: Explicit timezone information to use for the visualization. Default is Europe/Berlin
335
340
  :param vehicle_id: the unique identifier of the vehicle
336
341
  :param session: A :class:`sqlalchemy.orm.session.Session` object to an eflips-model database
337
342
  :return: A pandas DataFrame
@@ -395,4 +400,4 @@ def vehicle_soc(
395
400
  )
396
401
  )
397
402
 
398
- return (pd.DataFrame({"time": all_times, "soc": all_soc}), descriptions)
403
+ return pd.DataFrame({"time": all_times, "soc": all_soc}), descriptions
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "eflips-eval"
3
- version = "1.2.0"
3
+ version = "1.2.2"
4
4
  description = ""
5
5
  authors = [
6
6
  "Ludger Heide <ludger.heide@lhtechnologies.de>",
File without changes
File without changes