From 74ab4df9fe66d3720cbdaf5101dd7fd0206ec62d Mon Sep 17 00:00:00 2001 From: Yohann D'ANELLO Date: Thu, 2 Sep 2021 01:36:37 +0200 Subject: [PATCH] [WEI] Extreme test with full buses and quality constraints Signed-off-by: Yohann D'ANELLO --- apps/wei/forms/surveys/base.py | 3 +- apps/wei/forms/surveys/wei2021.py | 3 ++ apps/wei/tests/test_wei_algorithm_2021.py | 46 ++++++++++++++++++++++- 3 files changed, 49 insertions(+), 3 deletions(-) diff --git a/apps/wei/forms/surveys/base.py b/apps/wei/forms/surveys/base.py index ec0bc980..030f9078 100644 --- a/apps/wei/forms/surveys/base.py +++ b/apps/wei/forms/surveys/base.py @@ -53,7 +53,8 @@ class WEIBusInformation: def free_seats(self, surveys: List["WEISurvey"] = None): size = self.bus.size already_occupied = WEIMembership.objects.filter(bus=self.bus).count() - valid_surveys = sum(1 for survey in surveys if survey.information.valid) if surveys else 0 + valid_surveys = sum(1 for survey in surveys if survey.information.valid + and survey.information.get_selected_bus() == self.bus) if surveys else 0 return size - already_occupied - valid_surveys def has_free_seats(self, surveys=None): diff --git a/apps/wei/forms/surveys/wei2021.py b/apps/wei/forms/surveys/wei2021.py index 49c1c628..2a9d5d27 100644 --- a/apps/wei/forms/surveys/wei2021.py +++ b/apps/wei/forms/surveys/wei2021.py @@ -190,6 +190,9 @@ class WEISurveyAlgorithm2021(WEISurveyAlgorithm): # If it does not exist, choose the next bus. least_preferred_survey.free() least_preferred_survey.save() + free_surveys.append(least_preferred_survey) survey.select_bus(bus) survey.save() break + else: + raise ValueError(f"User {survey.registration.user} has no free seat") diff --git a/apps/wei/tests/test_wei_algorithm_2021.py b/apps/wei/tests/test_wei_algorithm_2021.py index c64c4c9e..ccac4c9d 100644 --- a/apps/wei/tests/test_wei_algorithm_2021.py +++ b/apps/wei/tests/test_wei_algorithm_2021.py @@ -1,3 +1,4 @@ +import math import random from django.contrib.auth.models import User @@ -26,7 +27,7 @@ class TestWEIAlgorithm(TestCase): self.buses = [] for i in range(10): - bus = Bus.objects.create(wei=self.wei, name=f"Bus {i}", size=50) + bus = Bus.objects.create(wei=self.wei, name=f"Bus {i}", size=10) self.buses.append(bus) information = WEIBusInformation2021(bus) for word in WORDS: @@ -39,7 +40,7 @@ class TestWEIAlgorithm(TestCase): There are only a few people in each bus, ensure that each person has its best bus """ # Add a few users - for i in range(50): + for i in range(10): user = User.objects.create(username=f"user{i}") registration = WEIRegistration.objects.create( user=user, @@ -63,3 +64,44 @@ class TestWEIAlgorithm(TestCase): preferred_bus = survey.ordered_buses()[0][0] chosen_bus = survey.information.get_selected_bus() self.assertEqual(preferred_bus, chosen_bus) + + def test_survey_algorithm_full(self): + """ + Buses are full of first year people, ensure that they are happy + """ + # Add a lot of users + for i in range(95): + user = User.objects.create(username=f"user{i}") + registration = WEIRegistration.objects.create( + user=user, + wei=self.wei, + first_year=True, + birth_date='2000-01-01', + ) + information = WEISurveyInformation2021(registration) + for j in range(1, 21): + setattr(information, f'word{j}', random.choice(WORDS)) + information.step = 20 + information.save(registration) + registration.save() + + # Run algorithm + WEISurvey2021.get_algorithm_class()().run_algorithm() + + penalty = 0 + # Ensure that everyone seems to be happy + # We attribute a penalty for each user that didn't have its first choice + # The penalty is the square of the distance between the score of the preferred bus + # and the score of the attributed bus + # We consider it acceptable if the mean of this distance is lower than 5 % + for r in WEIRegistration.objects.filter(wei=self.wei).all(): + survey = WEISurvey2021(r) + chosen_bus = survey.information.get_selected_bus() + buses = survey.ordered_buses() + score = min(v for bus, v in buses if bus == chosen_bus) + max_score = buses[0][1] + penalty += (max_score - score) ** 2 + + self.assertLessEqual(max_score - score, 20) # Always less than 20 % of tolerance + + self.assertLessEqual(penalty / 100, 25) # Tolerance of 5 %