...
 
Commits (15)
......@@ -25,11 +25,7 @@ __Else :__
- CMD : python manage.py loaddata dump.json
- CMD : python manage.py runserver
At this point, the server is running on localhost:8000. Try to make a request with your browser.
Then try to make a request with your front app STARTED ON PORT 3000.
You may have CORS issue.
If so (CORS issue encountered):
- Incoming (soon, as soon as you tell me you have had a CORS issue)
Then try to make a request with your front app STARTED ON PORT 3000.
## Routes
......@@ -43,4 +39,17 @@ If so (CORS issue encountered):
* /incidentreport ->POST Ajout d'une nouvelle déclaration incident (attion au JSON envoyé)
## To Test the server
- Open a shell in the django project root directory (which contains a file manage.py)
- CMD : python -W ignore manage.py test saclaze_back.tests
## Routines
__Be sure to run the following commands every day at least:__
- CMD : python manage.py startwtf (it refresh the historical average)
This diff is collapsed.
This diff is collapsed.
[
{
"model": "saclaze_back.locationcategory",
"pk": 1,
"fields": {
"name": "Restaurant Universitaire"
}
}
]
\ No newline at end of file
[
{
"model": "saclaze_back.location",
"pk": 1,
"fields": {
"category": 1,
"name": "RU - Eiffel",
"description": "RU - Eiffel",
"latitude": "48.710458",
"longitude": "2.167252"
}
},
{
"model": "saclaze_back.location",
"pk": 2,
"fields": {
"category": 1,
"name": "RU - Breguet",
"description": "RU - Breguet",
"latitude": "48.708145",
"longitude": "2.163908"
}
},
{
"model": "saclaze_back.location",
"pk": 3,
"fields": {
"category": 1,
"name": "RU - Lieu de vie",
"description": "RU - Lieu de vie",
"latitude": "48.709454",
"longitude": "2.171146"
}
}
]
\ No newline at end of file
[
{
"model": "saclaze_back.schedule",
"pk": 1,
"fields": {
"location": 1,
"day_of_the_week": 0,
"opening_time": "11:30:00",
"closing_time": "14:00:00"
}
},
{
"model": "saclaze_back.schedule",
"pk": 2,
"fields": {
"location": 1,
"day_of_the_week": 1,
"opening_time": "11:30:00",
"closing_time": "14:00:00"
}
},
{
"model": "saclaze_back.schedule",
"pk": 3,
"fields": {
"location": 1,
"day_of_the_week": 2,
"opening_time": "11:30:00",
"closing_time": "14:00:00"
}
},
{
"model": "saclaze_back.schedule",
"pk": 4,
"fields": {
"location": 1,
"day_of_the_week": 3,
"opening_time": "11:30:00",
"closing_time": "14:00:00"
}
},
{
"model": "saclaze_back.schedule",
"pk": 5,
"fields": {
"location": 1,
"day_of_the_week": 4,
"opening_time": "11:30:00",
"closing_time": "14:00:00"
}
},
{
"model": "saclaze_back.schedule",
"pk": 6,
"fields": {
"location": 2,
"day_of_the_week": 0,
"opening_time": "11:30:00",
"closing_time": "14:00:00"
}
},
{
"model": "saclaze_back.schedule",
"pk": 7,
"fields": {
"location": 2,
"day_of_the_week": 1,
"opening_time": "11:30:00",
"closing_time": "14:00:00"
}
},
{
"model": "saclaze_back.schedule",
"pk": 8,
"fields": {
"location": 2,
"day_of_the_week": 2,
"opening_time": "11:30:00",
"closing_time": "14:00:00"
}
},
{
"model": "saclaze_back.schedule",
"pk": 9,
"fields": {
"location": 2,
"day_of_the_week": 3,
"opening_time": "11:30:00",
"closing_time": "14:00:00"
}
},
{
"model": "saclaze_back.schedule",
"pk": 10,
"fields": {
"location": 2,
"day_of_the_week": 4,
"opening_time": "11:30:00",
"closing_time": "14:00:00"
}
},
{
"model": "saclaze_back.schedule",
"pk": 11,
"fields": {
"location": 3,
"day_of_the_week": 0,
"opening_time": "11:30:00",
"closing_time": "14:00:00"
}
},
{
"model": "saclaze_back.schedule",
"pk": 12,
"fields": {
"location": 3,
"day_of_the_week": 1,
"opening_time": "11:30:00",
"closing_time": "14:00:00"
}
},
{
"model": "saclaze_back.schedule",
"pk": 13,
"fields": {
"location": 3,
"day_of_the_week": 2,
"opening_time": "11:30:00",
"closing_time": "14:00:00"
}
},
{
"model": "saclaze_back.schedule",
"pk": 14,
"fields": {
"location": 3,
"day_of_the_week": 3,
"opening_time": "11:30:00",
"closing_time": "14:00:00"
}
},
{
"model": "saclaze_back.schedule",
"pk": 15,
"fields": {
"location": 3,
"day_of_the_week": 4,
"opening_time": "11:30:00",
"closing_time": "14:00:00"
}
}
]
\ No newline at end of file
"""
To use this command, run (in manage.py folder):
- python manage.py startwtf
"""
from django.core.management.base import BaseCommand
from saclaze_back.waitingtimefactory import WaitingTimeFactory
class Command(BaseCommand):
help = 'Launch the WaitingTimeFactory to refresh historical average waiting times.'
def add_arguments(self, parser):
pass
def handle(self, *args, **options):
self.stdout.write(self.style.WARNING(
'Please wait, the compute can take some time (about 5 sec).'))
wtf = WaitingTimeFactory()
wtf.estimate_all_locations()
self.stdout.write(self.style.SUCCESS(
'Successfully refreshed all historical average.'))
......@@ -10,7 +10,8 @@ from django.utils import timezone
class LocationCategory(models.Model):
"""
TODO
A LocationCategory model.
ex : Restaurant Universitaire, etc.
"""
name = models.CharField(max_length=63)
initial_waiting_time = models.IntegerField()
......@@ -21,7 +22,20 @@ class LocationCategory(models.Model):
class Location(models.Model):
"""
TODO
A complete Location Model with a name, a description, a geographical position.
Additionally a Location has:
- a Category (see above)
- a start_date/end_date: these attributes enable the creation of temporary location/event
- a last_estimation: the current waiting_time for this factory.
- a last_est_timestamp: a timestamp of the last time an estimation was made for this location.
A Location instance has 4 methods :
- get_waiting_time_now: method called to get the last estimation
(and compute a new one if necessary)
- get_wt_now_level: method which compute the waiting-time level,
called when a new last_estimation is computed.
- get_schedule: method which returns the schedule of the current day for this Location.
- get_incident: method which returns active indicent on a location (could be a manager)
"""
category = models.ForeignKey(LocationCategory, on_delete=models.SET_NULL, null=True)
name = models.CharField(max_length=63)
......@@ -33,10 +47,17 @@ class Location(models.Model):
last_estimation = models.DurationField(null=True, blank=True)
last_est_timestamp = models.DateTimeField(null=True, blank=True)
estimation_frequency = timedelta(seconds=60)
def __str__(self):
return self.name
def get_waiting_time_now(self):
"""
Compute a new estimation from the last estimation
if no estimation was made during the last minute.
Else it just return the current estimation.
"""
from saclaze_back.waitingtimenow import WaitingTimeNow
if not self.last_est_timestamp is None:
......@@ -44,7 +65,7 @@ class Location(models.Model):
else:
seconds_since_last_update = 86400
if seconds_since_last_update > 60: # 60 seconds in production
if seconds_since_last_update > self.estimation_frequency.seconds:
wtn = WaitingTimeNow()
self.last_estimation = timedelta(seconds=wtn.compute_waiting_time(self))
self.last_est_timestamp = timezone.now()
......@@ -54,13 +75,15 @@ class Location(models.Model):
def get_wt_now_level(self):
"""
Compute the wt level from the last estimated waiting time
The returned level is:
- 1 if the current estimation is lower (or equal) than the historical average
- 2 if the current estimation is lower (or equal) than twice the historical average
- 3 if the current estimation is greater than twice the historical average
"""
# self.get_waiting_time_now() # Is this necessary ?
dt = timezone.now()
rounded_minute = ((dt.minute+5)//10)*10
query_time = datetime.time(hour=dt.hour+rounded_minute//60, minute= rounded_minute%60)
qs = EstimatedWaitingTime.objects.filter(location=self, day_of_the_week=dt.weekday(), time=query_time)
query_time = datetime.time(hour=dt.hour+rounded_minute//60, minute=rounded_minute%60)
qs = self.estimated_waiting_times.filter(day_of_the_week=dt.weekday(), time=query_time)
estimated_wt = qs[0].waiting_time
......@@ -73,16 +96,18 @@ class Location(models.Model):
def get_schedule(self):
dt = timezone.now()
return Schedule.objects.filter(location=self, day_of_the_week=dt.weekday())
return self.schedules.filter(day_of_the_week=dt.weekday())
def get_incident(self):
qs = Incident.objects.filter(location=self, status=True).order_by('-id')
qs = self.incident_set.filter(status=True).order_by('-id')
return qs[0] if bool(qs) else None
class Incident(models.Model):
"""
TODO
An Incident model.
A new instance is created after several reports of an incident
(see IncidentReport (models.py) & signals.py).
"""
location = models.ForeignKey(Location, on_delete=models.CASCADE)
status = models.BooleanField(default=False)
......@@ -97,7 +122,10 @@ class Incident(models.Model):
class IncidentReport(models.Model):
"""
TODO
An IncidentReport model.
Users can POST report of an incident with their application.
Several reports of an incident on the same Location can generate an Incident
(see Incident (models.py) & signals.py).
"""
location = models.ForeignKey(Location, on_delete=models.CASCADE)
status = models.BooleanField()
......@@ -112,7 +140,11 @@ class IncidentReport(models.Model):
class ObservedWaitingTime(models.Model):
"""
TODO
An ObservedWaitingTime instance is an observation from the users.
They are used in the computation of a new current waiting time for a Location
(see WaitingTimeNow (waitingtimenow.py)).
They are also used in the computation of the new historical average waiting time
(see WaitingTimeFactory (waitingtimefactory.py))
"""
location = models.ForeignKey(Location, on_delete=models.CASCADE)
waiting_time = models.DurationField()
......@@ -127,7 +159,11 @@ class ObservedWaitingTime(models.Model):
class EstimatedWaitingTime(models.Model):
"""
TODO
An EstimatedWaitingTime instance is an historical average waiting time.
They are used in the computation of a new current waiting time for a Location
(see WaitingTimeNow (waitingtimenow.py)).
They are computed thanks to the previous average and the new observations from the users.
(see WaitingTimeFactory (waitingtimefactory.py))
"""
location = models.ForeignKey(
Location,
......
"""
Unit Test on the Location model and its methods.
"""
from datetime import timedelta
import time
from django.utils import timezone
from rest_framework.test import APITestCase
from saclaze_back.models import Location, LocationCategory
class LocationTest(APITestCase):
def setUp(self):
"""
We create a basic Database which contains :
- 1 location category
- 1 location (and the 7 * 24 * 6 EstimatedWaitingTimes associated with)
"""
LocationCategory.objects.create(
name="Restaurant Universitaire",
initial_waiting_time=20)
self.test_location = Location.objects.create(
category_id=1,
name="RU - Eiffel",
latitude="48.710458",
longitude="2.167252")
for estimation in self.test_location.estimated_waiting_times.all():
estimation.waiting_time = timedelta(minutes=20)
estimation.save()
def test_get_waiting_time_now_0(self):
"""
Ensure we compute a correct estimation for the first time
without any observation in database.
"""
self.assertIsNone(self.test_location.last_estimation)
self.test_location.get_waiting_time_now()
self.assertIsNotNone(self.test_location.last_estimation)
self.assertEqual(
self.test_location.last_estimation,
timedelta(minutes=20))
def test_get_waiting_time_now_1(self):
"""
Ensure we compute only one time per minute our estimation.
"""
self.test_location.get_waiting_time_now()
self.assertIsNotNone(self.test_location.last_estimation)
self.assertEqual(
self.test_location.last_estimation,
timedelta(minutes=20))
dt_now = timezone.now()
for i in range(5):
self.test_location.observedwaitingtime_set.create(
day_of_the_week=dt_now.weekday(),
timestamp=dt_now,
waiting_time=timedelta(minutes=10),)
self.test_location.get_waiting_time_now()
self.assertEqual(
self.test_location.last_estimation,
timedelta(minutes=20))
def test_get_waiting_time_now_2(self):
"""
Ensure we recompute our estimation every minutes.
For more effective test, we set the refreshing frequency at 10 seconds.
"""
Location.estimation_frequency = timedelta(seconds=10)
self.test_location.get_waiting_time_now()
self.assertIsNotNone(self.test_location.last_estimation)
self.assertEqual(
self.test_location.last_estimation,
timedelta(minutes=20))
dt_now = timezone.now()
for i in range(5):
self.test_location.observedwaitingtime_set.create(
day_of_the_week=dt_now.weekday(),
timestamp=dt_now,
waiting_time=timedelta(minutes=10),)
time.sleep(11)
self.test_location.get_waiting_time_now()
self.assertLess(
self.test_location.last_estimation,
timedelta(minutes=20))
self.assertLess(
self.test_location.last_estimation,
timedelta(minutes=15))
def test_get_wt_now_level_0(self):
"""
Ensure we compute a correct level
without any observation in database.
"""
self.test_location.get_waiting_time_now()
self.assertIsNotNone(self.test_location.last_estimation)
test_level = self.test_location.get_wt_now_level()
self.assertEqual(
test_level,
1)
def test_get_wt_now_level_1(self):
"""
Ensure we compute a correct level
without any observation in database.
"""
self.test_location.get_waiting_time_now()
self.assertIsNotNone(self.test_location.last_estimation)
self.test_location.last_estimation *= 2
test_level = self.test_location.get_wt_now_level()
self.assertEqual(
test_level,
2)
def test_get_wt_now_level_2(self):
"""
Ensure we compute a correct level
without any observation in database.
"""
self.test_location.get_waiting_time_now()
self.assertIsNotNone(self.test_location.last_estimation)
self.test_location.last_estimation *= 3
test_level = self.test_location.get_wt_now_level()
self.assertEqual(
test_level,
3)
"""
Unit Test on the WaitingTimeFactory class and its methods.
"""
from datetime import datetime, timedelta, time
# from django.urls import reverse
from django.utils import timezone
......@@ -210,21 +213,22 @@ class WaitingTimeFactoryTest(APITestCase):
wtf = WaitingTimeFactory()
wtf.estimate_by_location(self.test_location)
'''
for new_est in self.test_location.estimated_waiting_times.filter(
time=datetime(2018, month=1, day=1, hour=12).time()):
time=time(hour=12, minute=0)):
self.assertEqual(
new_est.waiting_time,
timedelta(minutes=15))
'''
for new_est in (self.test_location.estimated_waiting_times.filter(time=time(hour=12, minute=0)) |
self.test_location.estimated_waiting_times.filter(time=time(hour=11, minute=50))):
for new_est in (
self.test_location.estimated_waiting_times.filter(time=time(hour=12, minute=0)) |
self.test_location.estimated_waiting_times.filter(time=time(hour=11, minute=50))):
self.assertEqual(
new_est.waiting_time,
timedelta(minutes=15))
for new_est in (self.test_location.estimated_waiting_times.exclude(time=time(hour=12, minute=0)) &
self.test_location.estimated_waiting_times.exclude(time=time(hour=11, minute=50))):
for new_est in (
self.test_location.estimated_waiting_times.exclude(time=time(hour=12, minute=0)) &
self.test_location.estimated_waiting_times.exclude(time=time(hour=11, minute=50))):
self.assertEqual(
new_est.waiting_time,
timedelta(minutes=20))
"""
Unit Test on the WaitingTimeNow class and its methods.
"""
from datetime import datetime, timedelta
# from django.urls import reverse
from django.utils import timezone
from rest_framework.test import APITestCase
# from rest_framework import status
from saclaze_back.models import Location, LocationCategory
from saclaze_back.waitingtimenow import WaitingTimeNow
......@@ -94,9 +95,6 @@ class WaitingTimeNowTest(APITestCase):
self.assertLessEqual(
int(current_waiting_time),
20*60)
# J'ai 978 ici j'imagine que c'est normal mais je voulais te laisser checker ça
# Le initial waiting_time pour cette location est de 20 min (1200s).abs
# # Oui, c'est normal, je rajoute un test avec beaucoup plus de temps observés pour vérifier que ça tend vers 15 minutes
def test_compute_waiting_time_2(self):
"""
......@@ -141,13 +139,11 @@ class WaitingTimeNowTest(APITestCase):
self.assertEqual(
int(current_waiting_time),
int(60 * 20 * 2))
# Condition line 46 vérifiée (?) En fait la condition est fausse, c'était un truc ad hoc pour réparer une division par 0
# Le résultat attendu est bien 20*2 minutes puisque si sur N lieux N-1 est HS, incident_ratio vaut N
def test_compute_waiting_time_4(self):
"""
Ensure that with enough (identical) observations, computed waiting time converges to the observed value
Ensure that with enough (identical) observations,
computed waiting time converges to the observed value
"""
duration = 15
for i in range(20):
......@@ -161,4 +157,4 @@ class WaitingTimeNowTest(APITestCase):
self.assertEqual(
int(current_waiting_time),
15*60)
\ No newline at end of file
15*60)