Skip to content

Commit

Permalink
Merge pull request #16 from InspectorIncognito/feat/EDD-2961-alert-ch…
Browse files Browse the repository at this point in the history
…anges

Feat/edd 2961 alert changes
  • Loading branch information
Mrtn-fa authored Aug 22, 2024
2 parents 0d0c8c7 + ef6fa9e commit 22e6e92
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 17 deletions.
15 changes: 14 additions & 1 deletion backend/rest_api/factories.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import datetime
import uuid

import factory
from rest_api.models import Speed, Shape, Segment, HistoricSpeed, Stop
from rest_api.models import Speed, Shape, Segment, HistoricSpeed, Stop, Alert
from random import randint
import numpy as np
from django.utils import timezone
Expand All @@ -17,6 +18,8 @@ class Meta:
model = Segment

shape = factory.SubFactory(ShapeFactory)
segment_id = uuid.uuid4()
sequence = 0
geometry = [[0.0, 0.0], [0.0, 0.1], [1.0, 1.0]]


Expand All @@ -27,12 +30,15 @@ class Meta:
segment = factory.SubFactory(SegmentFactory)
latitude = factory.Faker('latitude')
longitude = factory.Faker('longitude')
stop_id = 'STOP_ID'


class SpeedFactory(factory.django.DjangoModelFactory):
class Meta:
model = Speed

segment = factory.SubFactory(SegmentFactory)
speed = 16.0
day_type = "L"


Expand All @@ -41,6 +47,13 @@ class Meta:
model = HistoricSpeed


class AlertFactory(factory.django.DjangoModelFactory):
class Meta:
model = Alert
segment = factory.SubFactory(SegmentFactory)
detected_speed = factory.SubFactory(SpeedFactory)


def create_speed_dataset(segment_n: int = 5, speed_n: int = 10):
shape = ShapeFactory()
dataset = {
Expand Down
19 changes: 19 additions & 0 deletions backend/rest_api/migrations/0022_segment_segment_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 4.1.1 on 2024-08-20 00:10

from django.db import migrations, models
import uuid


class Migration(migrations.Migration):

dependencies = [
('rest_api', '0021_alertthreshold'),
]

operations = [
migrations.AddField(
model_name='segment',
name='segment_id',
field=models.UUIDField(default=uuid.uuid4),
),
]
5 changes: 3 additions & 2 deletions backend/rest_api/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import uuid

from django.contrib.postgres.fields import ArrayField
from django.db import models
from shapely.geometry import LineString as shp_LineString
Expand Down Expand Up @@ -59,6 +61,7 @@ def __str__(self):


class Segment(models.Model):
segment_id = models.UUIDField(default=uuid.uuid4)
shape = models.ForeignKey(Shape, on_delete=models.CASCADE)
sequence = models.IntegerField(blank=False, null=False)
geometry = ArrayField(ArrayField(models.FloatField()), blank=False, null=False)
Expand Down Expand Up @@ -169,8 +172,6 @@ class HistoricSpeed(models.Model):
timestamp = models.DateTimeField(default=timezone.localtime)


# TODO: Create alert

class SingletonModel(models.Model):
class Meta:
abstract = True
Expand Down
4 changes: 3 additions & 1 deletion backend/rest_api/tests/test_alert.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ def setUp(self):

@skip("Skipped")
def test_send_alert(self):
segment = SegmentFactory()
speed = SpeedFactory()
site_manager = TranSappSiteManager()
alert_data = create_alert_data(self.stop_codes)
alert_data = create_alert_data(self.stop_codes, segment, speed)
response = site_manager.create_alert(alert_data)

def test_create_alerts(self):
Expand Down
26 changes: 26 additions & 0 deletions backend/rest_api/tests/test_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from rest_api.factories import SegmentFactory, SpeedFactory, StopFactory, AlertFactory
from rest_api.tests.tests_views_base import BaseTestCase
from rest_api.util.alert import TranSappSiteManager
from unittest import skip
from rest_api.util.alert import update_alert_from_admin


class TestManager(BaseTestCase):
def setUp(self):
self.manager = TranSappSiteManager()
self.segment = SegmentFactory()

@skip('Skipping')
def test_edit(self):
segment_obj = SegmentFactory(segment_id='82342493-ae05-4ea0-910a-80459ac52447')
StopFactory(stop_id='PD335', segment=segment_obj)

speed_obj = SpeedFactory(segment=segment_obj)
alert_obj = AlertFactory(segment=segment_obj, detected_speed=speed_obj)
segment_uuid = segment_obj.segment_id

response = self.manager.alert_lookup(segment_uuid)
data = response["data"]
if len(data) > 0:
alert_data = data[0]
update_alert_from_admin(self.manager, alert_obj, alert_data)
102 changes: 90 additions & 12 deletions backend/rest_api/util/alert.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import datetime
import json

import requests
import logging
from typing import List
from decouple import config
from datetime import timedelta
from django.utils import timezone
Expand All @@ -21,10 +22,14 @@ def __init__(self):
self.server_username = config('TRANSAPP_SITE_USERNAME')
# urls
self.LOGIN_URL = '{0}/login/?next=/'.format(self.server_name)
self.CREATE_ALERT_URL = '{0}/adminapp/alert/add'.format(self.server_name)

self.ALERT_URL = '{0}/adminapp/alert'.format(self.server_name)
self.LOOKUP_URL = '{0}/adminapp/alert/data'.format(self.server_name)
self.CREATE_ALERT_URL = "{0}/add".format(self.ALERT_URL)
self.session = self.get_logged_session()

def get_update_alert_url(self, alert_id):
return "{0}/{1}".format(self.ALERT_URL, alert_id)

def get_logged_session(self):
payload = {
'username': self.server_username,
Expand Down Expand Up @@ -54,9 +59,58 @@ def create_alert(self, alert_data: dict):

return self.session.post(self.CREATE_ALERT_URL, data=payload, cookies=res.cookies)

def update_alert(self, alert_data: dict, alert_id):
payload = alert_data
update_alert_url = self.get_update_alert_url(alert_id)

res = self.session.get(update_alert_url)
csrf_token = res.cookies['csrftoken']
payload['csrfmiddlewaretoken'] = csrf_token

return self.session.post(update_alert_url, data=payload, cookies=res.cookies)

def alert_lookup(self, alert_name: str):
url = '{0}{1}{2}'.format(self.LOOKUP_URL,
'?draw=1&columns%5B0%5D%5Bdata%5D=activated&columns%5B0%5D%5Bname%5D=&columns%5B0%5D%5Bsearchable%5D=true&columns%5B0%5D%5Borderable%5D=true&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=name&columns%5B1%5D%5Bname%5D=&columns%5B1%5D%5Bsearchable%5D=true&columns%5B1%5D%5Borderable%5D=true&columns%5B1%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B1%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B2%5D%5Bdata%5D=start&columns%5B2%5D%5Bname%5D=&columns%5B2%5D%5Bsearchable%5D=true&columns%5B2%5D%5Borderable%5D=true&columns%5B2%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B2%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B3%5D%5Bdata%5D=end&columns%5B3%5D%5Bname%5D=&columns%5B3%5D%5Bsearchable%5D=true&columns%5B3%5D%5Borderable%5D=true&columns%5B3%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B3%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B4%5D%5Bdata%5D=start_time_day&columns%5B4%5D%5Bname%5D=&columns%5B4%5D%5Bsearchable%5D=true&columns%5B4%5D%5Borderable%5D=true&columns%5B4%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B4%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B5%5D%5Bdata%5D=end_time_day&columns%5B5%5D%5Bname%5D=&columns%5B5%5D%5Bsearchable%5D=true&columns%5B5%5D%5Borderable%5D=true&columns%5B5%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B5%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B6%5D%5Bdata%5D=week_days&columns%5B6%5D%5Bname%5D=&columns%5B6%5D%5Bsearchable%5D=true&columns%5B6%5D%5Borderable%5D=true&columns%5B6%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B6%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B7%5D%5Bdata%5D=stop_number&columns%5B7%5D%5Bname%5D=&columns%5B7%5D%5Bsearchable%5D=true&columns%5B7%5D%5Borderable%5D=true&columns%5B7%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B7%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B8%5D%5Bdata%5D=useful&columns%5B8%5D%5Bname%5D=&columns%5B8%5D%5Bsearchable%5D=true&columns%5B8%5D%5Borderable%5D=false&columns%5B8%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B8%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B9%5D%5Bdata%5D=useless&columns%5B9%5D%5Bname%5D=&columns%5B9%5D%5Bsearchable%5D=true&columns%5B9%5D%5Borderable%5D=false&columns%5B9%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B9%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B10%5D%5Bdata%5D=&columns%5B10%5D%5Bname%5D=&columns%5B10%5D%5Bsearchable%5D=true&columns%5B10%5D%5Borderable%5D=false&columns%5B10%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B10%5D%5Bsearch%5D%5Bregex%5D=false&order%5B0%5D%5Bcolumn%5D=0&order%5B0%5D%5Bdir%5D=asc&start=0&length=10&search%5Bvalue%5D=&search%5Bregex%5D=false&_=1564452662157',
f'search[value]={alert_name}'
)
response_raw = self.session.get(url)
response_json = json.loads(response_raw.content.decode())
return response_json


def create_alert_to_admin(site_manager: TranSappSiteManager, alert_obj: Alert):
segment = alert_obj.segment
speed = alert_obj.detected_speed
alert_data = create_alert_data(segment, speed)
site_manager.create_alert(alert_data)


def create_alert_data(stops: List[str]):
alert_data = {}
def update_alert_from_admin(site_manager: TranSappSiteManager, alert_obj: Alert, alert_data: dict):
segment = alert_obj.segment
speed = alert_obj.detected_speed
alert_public_id = alert_data['public_id']

new_end = timezone.now().strftime('%Y-%m-%d')
delta = timedelta(minutes=15)
new_end_time_day = str((datetime.datetime.strptime(alert_data['end_time_day'], '%H:%M:%S') + delta).time())[:-3]

alert_data = create_alert_data(segment, speed)
new_alert_data = dict(
name=alert_data['name'],
start=alert_data['start'],
end=new_end,
start_day_time=alert_data['start_time_day'],
end_day_time=new_end_time_day,
)
alert_data.update(new_alert_data)

site_manager.update_alert(alert_data, alert_public_id)


def create_alert_data(segment: Segment, speed: Speed):
alert_data = dict()
stops = segment.get_stops()

now = timezone.localtime()
now = now.replace(microsecond=0)
Expand All @@ -71,9 +125,26 @@ def create_alert_data(stops: List[str]):
alert_data['start_time_day'] = f"{start_time_day.hour:02}:{start_time_day.minute:02}"
alert_data['end_time_day'] = f"{end_time_day.hour:02}:{end_time_day.minute:02}"

alert_data['name'] = "Speed Anomaly Test"
alert_data['message'] = "Speed Anomaly detected"
alert_data['stops'] = [json.dumps(dict(label="Test stops", value='|'.join(stops)))]
segment_uuid = str(segment.segment_id)
segment_id = str(segment.pk)
shape_id = str(segment.shape.pk)
temporal_segment = str(speed.temporal_segment)
day_type = str(speed.day_type)
detected_speed = str(speed.speed)

alert_data['name'] = "Speed Anomaly {}".format(segment_uuid)

# prefill data = shape_id|segment_id|temporal_segment|day_type|detected_speed
prefill_data = '|'.join([shape_id, segment_id, temporal_segment, day_type, detected_speed])
alert_prefill_data = '&entry.1001130690={}'.format(prefill_data)

alert_url = 'https://docs.google.com/forms/d/e/1FAIpQLSfPOqOT45GnlP_gBol0613yl2X-ObMC6ApWfTLnSSrcpGdRKg/viewform?usp=pp_url{}'.format(
alert_prefill_data)

alert_data['message'] = """
Hemos detectado <strong>congestión</strong>, ayúdanos a resolverla respondiendo esta breve encuesta🚌<br><a target="_blank" href="{}">Presiona aquí👈</a>
""".format(alert_url)
alert_data['stops'] = [json.dumps(dict(label="Affected Stops", value='|'.join(stops)))]

alert_data['activated'] = False
alert_data['author'] = ALERT_AUTHOR
Expand Down Expand Up @@ -104,7 +175,8 @@ def create_alerts():
continue
speed_value = speed.speed
historic_speed_value = historic_speed.speed
alert_condition = alert_threshold * speed_value < historic_speed_value
alert_condition = speed_value < historic_speed_value / alert_threshold

if alert_condition:
alert_obj_data = {
"segment": segment,
Expand All @@ -124,6 +196,12 @@ def send_alerts():
alerts = Alert.objects.filter(timestamp__gte=start_time, timestamp__lte=end_time)
for alert in alerts:
segment = alert.segment
stops = segment.get_stops()
alert_data = create_alert_data(stops)
site_manager.create_alert(alert_data)
segment_uuid = segment.segment_id
alert_search_response = site_manager.alert_lookup(segment_uuid)
data = alert_search_response['data']
if len(data) > 0: # Check if the alert already exists in TranSapp's admin
alert_data = data[0]
update_alert_from_admin(site_manager, alert, alert_data)

else: # The alert doesn't exist, create it instead
create_alert_to_admin(site_manager, alert)
3 changes: 2 additions & 1 deletion backend/rest_api/util/process.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from gtfs_rt.processors.speed import calculate_speed
from rest_api.util.alert import send_alerts
from rest_api.util.alert import create_alerts, send_alerts


def calculate_speed_and_check_alerts():
calculate_speed()
create_alerts()
send_alerts()

0 comments on commit 22e6e92

Please sign in to comment.