From 74ab4df9fe66d3720cbdaf5101dd7fd0206ec62d Mon Sep 17 00:00:00 2001
From: Yohann D'ANELLO <ynerant@crans.org>
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 <ynerant@crans.org>
---
 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 %
-- 
GitLab