Skip to content

Commit 2a7e25f

Browse files
fatbirdray-oxd
authored andcommitted
DBC22-1374 DBC22-1375 DBC22-1377
1 parent 1ddd800 commit 2a7e25f

File tree

12 files changed

+111
-45
lines changed

12 files changed

+111
-45
lines changed

src/backend/apps/event/enums.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ class EVENT_DIRECTION:
103103
'event_sub_type',
104104
'status',
105105
'severity',
106+
'closed',
106107
'direction',
107108
'last_updated',
108109
'location',
@@ -116,6 +117,7 @@ class EVENT_DIRECTION:
116117

117118

118119
class EVENT_DISPLAY_CATEGORY:
120+
CLOSURE = 'closures'
119121
MAJOR_DELAYS = 'majorEvents'
120122
MINOR_DELAYS = 'minorEvents'
121123
FUTURE_DELAYS = 'futureEvents'
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 4.2.3 on 2023-12-28 19:04
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('event', '0010_event_end_event_schedule_event_start'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='event',
15+
name='closed',
16+
field=models.BooleanField(default=False),
17+
),
18+
]

src/backend/apps/event/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class Event(BaseModel):
1313
# General status
1414
status = models.CharField(max_length=32)
1515
severity = models.CharField(max_length=32)
16+
closed = models.BooleanField(default=False)
1617

1718
# Location
1819
direction = models.CharField(max_length=32)
@@ -26,7 +27,7 @@ class Event(BaseModel):
2627
last_updated = models.DateTimeField()
2728

2829
# Schedule
29-
schedule = models.JSONField(default={})
30+
schedule = models.JSONField(default={})
3031

3132
# Scheduled start and end
3233
start = models.DateTimeField(null=True)

src/backend/apps/event/serializers.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class EventSerializer(serializers.ModelSerializer):
2323
direction_display = serializers.SerializerMethodField()
2424
route_display = serializers.SerializerMethodField()
2525
schedule = ScheduleSerializer()
26+
severity = serializers.SerializerMethodField()
2627

2728
class Meta:
2829
model = Event
@@ -52,6 +53,9 @@ def get_route_display(self, obj):
5253
return res
5354

5455
def get_display_category(self, obj):
56+
if obj.closed:
57+
return EVENT_DISPLAY_CATEGORY.CLOSURE
58+
5559
if obj.event_sub_type in EVENT_DISPLAY_CATEGORY_MAP:
5660
return EVENT_DISPLAY_CATEGORY_MAP[obj.event_sub_type]
5761

@@ -61,3 +65,9 @@ def get_display_category(self, obj):
6165
return EVENT_DISPLAY_CATEGORY.MAJOR_DELAYS \
6266
if obj.severity == EVENT_SEVERITY.MAJOR \
6367
else EVENT_DISPLAY_CATEGORY.MINOR_DELAYS
68+
69+
def get_severity(self, obj):
70+
if obj.closed:
71+
return 'CLOSURE'
72+
73+
return obj.severity

src/backend/apps/event/tasks.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,15 @@ def populate_event_from_data(new_event_data):
5858

5959

6060
def populate_all_event_data():
61-
feed_data = FeedClient().get_event_list()['events']
61+
client = FeedClient()
62+
closures = client.get_closures_dict()
63+
feed_data = client.get_event_list()['events']
6264

6365
active_event_ids = []
6466
for event_data in feed_data:
67+
id = event_data.get("id", "").split("/")[-1]
68+
event_data["closed"] = closures.get(id, False)
69+
6570
populate_event_from_data(event_data)
6671

6772
# Event is active

src/backend/apps/feed/client.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
from urllib.parse import urljoin
44

55
import httpx
6-
from apps.feed.constants import INLAND_FERRY, OPEN511, WEBCAM
6+
from apps.feed.constants import DIT, INLAND_FERRY, OPEN511, WEBCAM
77
from apps.feed.serializers import (
8+
CarsClosureEventSerializer,
89
EventAPISerializer,
910
EventFeedSerializer,
1011
FerryAPISerializer,
@@ -28,6 +29,9 @@ def __init__(self):
2829
OPEN511: {
2930
"base_url": settings.DRIVEBC_OPEN_511_API_BASE_URL,
3031
},
32+
DIT: {
33+
"base_url": settings.DRIVEBC_DIT_API_BASE_URL,
34+
},
3135
INLAND_FERRY: {
3236
"base_url": settings.DRIVEBC_INLAND_FERRY_API_BASE_URL,
3337
},
@@ -84,7 +88,7 @@ def get_list_feed(self, resource_type, resource_name, serializer_cls, params=Non
8488
params = {}
8589
endpoint = self._get_endpoint(resource_type, resource_name)
8690
response_data = self._process_get_request(endpoint, params, resource_type)
87-
serializer = serializer_cls(data=response_data)
91+
serializer = serializer_cls(data=response_data, many=isinstance(response_data, list))
8892

8993
try:
9094
serializer.is_valid(raise_exception=True)
@@ -125,6 +129,16 @@ def get_event_list(self):
125129
{"format": "json", "limit": 500}
126130
)
127131

132+
def get_closures_dict(self):
133+
""" Return a dict of <id>:True for fast lookup of closed events by <id>. """
134+
135+
events = self.get_list_feed(
136+
DIT, 'dbcevents', CarsClosureEventSerializer,
137+
{"format": "json", "limit": 500}
138+
)
139+
140+
return {event["id"]:True for event in events if event["closed"]}
141+
128142
# Ferries
129143
def get_ferries_list(self):
130144
return self.get_list_feed(

src/backend/apps/feed/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
ROUTE_PLANNER = "route_planner"
22
WEBCAM = "webcam"
33
OPEN511 = "open511"
4+
DIT = "dit"
45
INLAND_FERRY = "inland_ferry"
56

67
DIRECTIONS = {

src/backend/apps/feed/serializers.py

Lines changed: 50 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,55 @@ class WebcamAPISerializer(serializers.Serializer):
5353

5454

5555
# Event
56+
class CarsClosureEventSerializer(serializers.Serializer):
57+
"""
58+
Serializer to take CARS API events and retrieve ID and closed flag.
59+
60+
As of January 2024, the CARS API events have the following structure
61+
that we need:
62+
63+
{
64+
'event-id': <id>,
65+
...,
66+
'details': [
67+
{
68+
'category': <category>,
69+
'code': <subcategory>,
70+
},
71+
...
72+
],
73+
...
74+
}
75+
76+
An event is consider to be marking a road closed if the category is
77+
'traffic_pattern' and the code is one that starts with 'closed'
78+
(e.g., 'closed', 'closed ahead', 'closed for repairs'). Other subcategories
79+
include the word 'closed' not at the beginning ('right lane closed') and
80+
do no indicate a complete closure.
81+
82+
TODO: Get list of closed subcategories for explicit enumeration.
83+
"""
84+
85+
id = serializers.CharField()
86+
closed = serializers.BooleanField(default=False)
87+
88+
def to_internal_value(self, data):
89+
data["id"] = data["event-id"]
90+
91+
data["closed"] = False
92+
for detail in data.get("details", []):
93+
if data["closed"]: break
94+
95+
for description in detail.get("descriptions", []):
96+
if data["closed"]: break
97+
98+
kind = description.get("kind", {})
99+
data["closed"] = (kind.get("category") == "traffic_pattern" and
100+
kind.get("code").startswith("closed"))
101+
102+
return super().to_internal_value(data)
103+
104+
56105
class EventFeedSerializer(serializers.Serializer):
57106
id = serializers.CharField(max_length=32)
58107

@@ -66,6 +115,7 @@ class EventFeedSerializer(serializers.Serializer):
66115
# General status
67116
status = serializers.CharField(max_length=32)
68117
severity = serializers.CharField(max_length=32)
118+
closed = serializers.BooleanField(default=False)
69119

70120
# Location
71121
roads = EventRoadsField(source="*")
@@ -80,23 +130,6 @@ class EventFeedSerializer(serializers.Serializer):
80130
schedule = serializers.JSONField()
81131

82132
def to_internal_value(self, data):
83-
# mapping CARS API fields to Open511 fields
84-
# data['id'] = data["event-id"]
85-
# details = data.get('open511-event-details', {})
86-
# data['event_type'] = details['event_type_description']
87-
# data['event_sub_type'] = details['event_subtype']
88-
# data['severity'] = data['representation']['priority']['name'].upper()
89-
# data['updated'] = datetime.fromtimestamp(data['update-time']['time']/1000,
90-
# pytz.timezone(data['update-time']['timeZoneId'])).isoformat()
91-
# data['created'] = data['updated'] # hack because CARS API doesn't include event creation time
92-
# data['status'] = EVENT_STATUS.ACTIVE
93-
# data['roads'] = {
94-
# 'to': details['event_road_to'],
95-
# 'from': details['event_road_from'],
96-
# 'name': 'Other roads',
97-
# 'direction': DIRECTIONS.get(details['event_road_direction'], 'NONE')
98-
# }
99-
# data['geography'] = data['geometry']
100133

101134
internal_data = super().to_internal_value(data)
102135
schedule = internal_data.get('schedule', {})
@@ -112,16 +145,6 @@ def to_internal_value(self, data):
112145

113146
return internal_data
114147

115-
def get_closed(self, obj):
116-
for detail in self.initial_data.get('details', []):
117-
for desc in detail.get('descriptions', []):
118-
kind = desc.get('kind', {})
119-
if (kind.get('category') == 'traffic_pattern' and
120-
kind.get('code') == 'closed'):
121-
return True
122-
123-
return False
124-
125148

126149
class EventAPISerializer(serializers.Serializer):
127150
events = EventFeedSerializer(many=True)

src/backend/config/settings/drivebc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@
1111
DRIVEBC_WEBCAM_API_BASE_URL = env("DRIVEBC_WEBCAM_API_BASE_URL")
1212
DRIVEBC_OPEN_511_API_BASE_URL = env("DRIVEBC_OPEN_511_API_BASE_URL")
1313
DRIVEBC_INLAND_FERRY_API_BASE_URL = env("DRIVEBC_INLAND_FERRY_API_BASE_URL")
14+
DRIVEBC_DIT_API_BASE_URL = env("DRIVEBC_DIT_API_BASE_URL")

src/frontend/src/Components/data/events.js

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,7 @@ export function getEvents(routePoints) {
44
const payload = routePoints ? { route: routePoints } : {};
55

66
return get(`${window.API_HOST}/api/events/`, payload)
7-
.then((data) => {
8-
data.forEach((datum) => {
9-
datum.roadIsClosed = !! datum.description.match(/Road closed(\.| )/);
10-
if (datum.roadIsClosed) {
11-
datum.severity = 'CLOSURE';
12-
datum.display_category = 'closures';
13-
}
14-
})
15-
return data;
16-
})
7+
.then((data) => data)
178
.catch((error) => {
189
console.log(error);
1910
});

src/frontend/src/Components/map/helper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { closureStyles, eventStyles } from '../data/featureStyleDefinitions.js';
1010

1111
// Static assets
1212
export const getEventIcon = (event, state) => {
13-
if (event.get('roadIsClosed')) {
13+
if (event.get('closed')) {
1414
return closureStyles[state];
1515
}
1616
const severity = event.get('severity').toLowerCase();

src/frontend/src/Components/map/mapPopup.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import parse from 'html-react-parser';
1010
import colocatedCamIcon from '../../images/colocated-camera.svg';
1111

1212
const displayCategoryMap = {
13-
closure: 'Closure',
13+
closures: 'Closure',
1414
majorEvents: 'Major Delay',
1515
minorEvents: 'Minor Delay',
1616
futureEvents: 'Future Delay',
@@ -112,10 +112,10 @@ export function getEventPopup(eventFeature) {
112112
<div className="delay-type">
113113
<div className="bold delay-severity">
114114
<div className="delay-icon">
115-
<EventTypeIcon displayCategory={ eventData.roadIsClosed ? 'closure' : eventData.display_category} />
115+
<EventTypeIcon displayCategory={ eventData.display_category} />
116116
</div>
117117

118-
<p className="bold">{ eventData.roadIsClosed ? 'Closure' : displayCategoryMap[eventData.display_category]}</p>
118+
<p className="bold">{ displayCategoryMap[eventData.display_category]}</p>
119119
</div>
120120

121121
<p className="bold friendly-time--mobile">

0 commit comments

Comments
 (0)