Skip to content

Commit

Permalink
DBC22-1374 DBC22-1375 DBC22-1377
Browse files Browse the repository at this point in the history
  • Loading branch information
fatbird authored and ray-oxd committed Jan 2, 2024
1 parent 1ddd800 commit 2a7e25f
Show file tree
Hide file tree
Showing 12 changed files with 111 additions and 45 deletions.
2 changes: 2 additions & 0 deletions src/backend/apps/event/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class EVENT_DIRECTION:
'event_sub_type',
'status',
'severity',
'closed',
'direction',
'last_updated',
'location',
Expand All @@ -116,6 +117,7 @@ class EVENT_DIRECTION:


class EVENT_DISPLAY_CATEGORY:
CLOSURE = 'closures'
MAJOR_DELAYS = 'majorEvents'
MINOR_DELAYS = 'minorEvents'
FUTURE_DELAYS = 'futureEvents'
Expand Down
18 changes: 18 additions & 0 deletions src/backend/apps/event/migrations/0011_event_closed.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 4.2.3 on 2023-12-28 19:04

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('event', '0010_event_end_event_schedule_event_start'),
]

operations = [
migrations.AddField(
model_name='event',
name='closed',
field=models.BooleanField(default=False),
),
]
3 changes: 2 additions & 1 deletion src/backend/apps/event/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class Event(BaseModel):
# General status
status = models.CharField(max_length=32)
severity = models.CharField(max_length=32)
closed = models.BooleanField(default=False)

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

# Schedule
schedule = models.JSONField(default={})
schedule = models.JSONField(default={})

# Scheduled start and end
start = models.DateTimeField(null=True)
Expand Down
10 changes: 10 additions & 0 deletions src/backend/apps/event/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class EventSerializer(serializers.ModelSerializer):
direction_display = serializers.SerializerMethodField()
route_display = serializers.SerializerMethodField()
schedule = ScheduleSerializer()
severity = serializers.SerializerMethodField()

class Meta:
model = Event
Expand Down Expand Up @@ -52,6 +53,9 @@ def get_route_display(self, obj):
return res

def get_display_category(self, obj):
if obj.closed:
return EVENT_DISPLAY_CATEGORY.CLOSURE

if obj.event_sub_type in EVENT_DISPLAY_CATEGORY_MAP:
return EVENT_DISPLAY_CATEGORY_MAP[obj.event_sub_type]

Expand All @@ -61,3 +65,9 @@ def get_display_category(self, obj):
return EVENT_DISPLAY_CATEGORY.MAJOR_DELAYS \
if obj.severity == EVENT_SEVERITY.MAJOR \
else EVENT_DISPLAY_CATEGORY.MINOR_DELAYS

def get_severity(self, obj):
if obj.closed:
return 'CLOSURE'

return obj.severity
7 changes: 6 additions & 1 deletion src/backend/apps/event/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,15 @@ def populate_event_from_data(new_event_data):


def populate_all_event_data():
feed_data = FeedClient().get_event_list()['events']
client = FeedClient()
closures = client.get_closures_dict()
feed_data = client.get_event_list()['events']

active_event_ids = []
for event_data in feed_data:
id = event_data.get("id", "").split("/")[-1]
event_data["closed"] = closures.get(id, False)

populate_event_from_data(event_data)

# Event is active
Expand Down
18 changes: 16 additions & 2 deletions src/backend/apps/feed/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
from urllib.parse import urljoin

import httpx
from apps.feed.constants import INLAND_FERRY, OPEN511, WEBCAM
from apps.feed.constants import DIT, INLAND_FERRY, OPEN511, WEBCAM
from apps.feed.serializers import (
CarsClosureEventSerializer,
EventAPISerializer,
EventFeedSerializer,
FerryAPISerializer,
Expand All @@ -28,6 +29,9 @@ def __init__(self):
OPEN511: {
"base_url": settings.DRIVEBC_OPEN_511_API_BASE_URL,
},
DIT: {
"base_url": settings.DRIVEBC_DIT_API_BASE_URL,
},
INLAND_FERRY: {
"base_url": settings.DRIVEBC_INLAND_FERRY_API_BASE_URL,
},
Expand Down Expand Up @@ -84,7 +88,7 @@ def get_list_feed(self, resource_type, resource_name, serializer_cls, params=Non
params = {}
endpoint = self._get_endpoint(resource_type, resource_name)
response_data = self._process_get_request(endpoint, params, resource_type)
serializer = serializer_cls(data=response_data)
serializer = serializer_cls(data=response_data, many=isinstance(response_data, list))

try:
serializer.is_valid(raise_exception=True)
Expand Down Expand Up @@ -125,6 +129,16 @@ def get_event_list(self):
{"format": "json", "limit": 500}
)

def get_closures_dict(self):
""" Return a dict of <id>:True for fast lookup of closed events by <id>. """

events = self.get_list_feed(
DIT, 'dbcevents', CarsClosureEventSerializer,
{"format": "json", "limit": 500}
)

return {event["id"]:True for event in events if event["closed"]}

# Ferries
def get_ferries_list(self):
return self.get_list_feed(
Expand Down
1 change: 1 addition & 0 deletions src/backend/apps/feed/constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
ROUTE_PLANNER = "route_planner"
WEBCAM = "webcam"
OPEN511 = "open511"
DIT = "dit"
INLAND_FERRY = "inland_ferry"

DIRECTIONS = {
Expand Down
77 changes: 50 additions & 27 deletions src/backend/apps/feed/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,55 @@ class WebcamAPISerializer(serializers.Serializer):


# Event
class CarsClosureEventSerializer(serializers.Serializer):
"""
Serializer to take CARS API events and retrieve ID and closed flag.
As of January 2024, the CARS API events have the following structure
that we need:
{
'event-id': <id>,
...,
'details': [
{
'category': <category>,
'code': <subcategory>,
},
...
],
...
}
An event is consider to be marking a road closed if the category is
'traffic_pattern' and the code is one that starts with 'closed'
(e.g., 'closed', 'closed ahead', 'closed for repairs'). Other subcategories
include the word 'closed' not at the beginning ('right lane closed') and
do no indicate a complete closure.
TODO: Get list of closed subcategories for explicit enumeration.
"""

id = serializers.CharField()
closed = serializers.BooleanField(default=False)

def to_internal_value(self, data):
data["id"] = data["event-id"]

data["closed"] = False
for detail in data.get("details", []):
if data["closed"]: break

for description in detail.get("descriptions", []):
if data["closed"]: break

kind = description.get("kind", {})
data["closed"] = (kind.get("category") == "traffic_pattern" and
kind.get("code").startswith("closed"))

return super().to_internal_value(data)


class EventFeedSerializer(serializers.Serializer):
id = serializers.CharField(max_length=32)

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

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

def to_internal_value(self, data):
# mapping CARS API fields to Open511 fields
# data['id'] = data["event-id"]
# details = data.get('open511-event-details', {})
# data['event_type'] = details['event_type_description']
# data['event_sub_type'] = details['event_subtype']
# data['severity'] = data['representation']['priority']['name'].upper()
# data['updated'] = datetime.fromtimestamp(data['update-time']['time']/1000,
# pytz.timezone(data['update-time']['timeZoneId'])).isoformat()
# data['created'] = data['updated'] # hack because CARS API doesn't include event creation time
# data['status'] = EVENT_STATUS.ACTIVE
# data['roads'] = {
# 'to': details['event_road_to'],
# 'from': details['event_road_from'],
# 'name': 'Other roads',
# 'direction': DIRECTIONS.get(details['event_road_direction'], 'NONE')
# }
# data['geography'] = data['geometry']

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

return internal_data

def get_closed(self, obj):
for detail in self.initial_data.get('details', []):
for desc in detail.get('descriptions', []):
kind = desc.get('kind', {})
if (kind.get('category') == 'traffic_pattern' and
kind.get('code') == 'closed'):
return True

return False


class EventAPISerializer(serializers.Serializer):
events = EventFeedSerializer(many=True)
Expand Down
1 change: 1 addition & 0 deletions src/backend/config/settings/drivebc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
DRIVEBC_WEBCAM_API_BASE_URL = env("DRIVEBC_WEBCAM_API_BASE_URL")
DRIVEBC_OPEN_511_API_BASE_URL = env("DRIVEBC_OPEN_511_API_BASE_URL")
DRIVEBC_INLAND_FERRY_API_BASE_URL = env("DRIVEBC_INLAND_FERRY_API_BASE_URL")
DRIVEBC_DIT_API_BASE_URL = env("DRIVEBC_DIT_API_BASE_URL")
11 changes: 1 addition & 10 deletions src/frontend/src/Components/data/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,7 @@ export function getEvents(routePoints) {
const payload = routePoints ? { route: routePoints } : {};

return get(`${window.API_HOST}/api/events/`, payload)
.then((data) => {
data.forEach((datum) => {
datum.roadIsClosed = !! datum.description.match(/Road closed(\.| )/);
if (datum.roadIsClosed) {
datum.severity = 'CLOSURE';
datum.display_category = 'closures';
}
})
return data;
})
.then((data) => data)
.catch((error) => {
console.log(error);
});
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/Components/map/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { closureStyles, eventStyles } from '../data/featureStyleDefinitions.js';

// Static assets
export const getEventIcon = (event, state) => {
if (event.get('roadIsClosed')) {
if (event.get('closed')) {
return closureStyles[state];
}
const severity = event.get('severity').toLowerCase();
Expand Down
6 changes: 3 additions & 3 deletions src/frontend/src/Components/map/mapPopup.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import parse from 'html-react-parser';
import colocatedCamIcon from '../../images/colocated-camera.svg';

const displayCategoryMap = {
closure: 'Closure',
closures: 'Closure',
majorEvents: 'Major Delay',
minorEvents: 'Minor Delay',
futureEvents: 'Future Delay',
Expand Down Expand Up @@ -112,10 +112,10 @@ export function getEventPopup(eventFeature) {
<div className="delay-type">
<div className="bold delay-severity">
<div className="delay-icon">
<EventTypeIcon displayCategory={ eventData.roadIsClosed ? 'closure' : eventData.display_category} />
<EventTypeIcon displayCategory={ eventData.display_category} />
</div>

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

<p className="bold friendly-time--mobile">
Expand Down

0 comments on commit 2a7e25f

Please sign in to comment.