mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-11-04 01:12:08 +01:00 
			
		
		
		
	Merge branch 'wei' into 'beta'
[WEI] Correction de l'algorithme et tests unitaires See merge request bde/nk20!173
This commit is contained in:
		@@ -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):
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ WORDS = [
 | 
			
		||||
    'Fanfare', 'Fracassage', 'Féria', 'Hard rock', 'Hoeggarden', 'House', 'Huit-six', 'IPA', 'Inclusif', 'Inferno',
 | 
			
		||||
    'Introverti', 'Jager bomb', 'Jazz', 'Jeux d\'alcool', 'Jeux de rôles', 'Jeux vidéo', 'Jul', 'Jus de fruit',
 | 
			
		||||
    'Karaoké', 'LGBTQI+', 'Lady Gaga', 'Loup garou', 'Morning beer', 'Métal', 'Nuit blanche', 'Ovalie', 'Psychedelic',
 | 
			
		||||
    'Pétanque',  'Rave', 'Reggae', 'Rhum', 'Ricard', 'Rock', 'Rosé', 'Rétro', 'Séducteur', 'Techno', 'Thérapie taxi',
 | 
			
		||||
    'Pétanque', 'Rave', 'Reggae', 'Rhum', 'Ricard', 'Rock', 'Rosé', 'Rétro', 'Séducteur', 'Techno', 'Thérapie taxi',
 | 
			
		||||
    'Théâtre', 'Trap', 'Turn up', 'Underground', 'Volley', 'Wati B', 'Zinédine Zidane',
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@@ -45,9 +45,9 @@ class WEISurveyForm2021(forms.Form):
 | 
			
		||||
        rng = Random(information.seed)
 | 
			
		||||
 | 
			
		||||
        words = []
 | 
			
		||||
        for _ in range(information.step + 1):
 | 
			
		||||
        for _ignored in range(information.step + 1):
 | 
			
		||||
            # Generate N times words
 | 
			
		||||
            words = [rng.choice(WORDS) for _ in range(10)]
 | 
			
		||||
            words = [rng.choice(WORDS) for _ignored2 in range(10)]
 | 
			
		||||
        words = [(w, w) for w in words]
 | 
			
		||||
        if self.data:
 | 
			
		||||
            self.fields["word"].choices = [(w, w) for w in WORDS]
 | 
			
		||||
@@ -162,7 +162,7 @@ class WEISurveyAlgorithm2021(WEISurveyAlgorithm):
 | 
			
		||||
        while free_surveys:  # Some students are not affected
 | 
			
		||||
            survey = free_surveys[0]
 | 
			
		||||
            buses = survey.ordered_buses()  # Preferences of the student
 | 
			
		||||
            for bus, _ in buses:
 | 
			
		||||
            for bus, _ignored in buses:
 | 
			
		||||
                if self.get_bus_information(bus).has_free_seats(surveys):
 | 
			
		||||
                    # Selected bus has free places. Put student in the bus
 | 
			
		||||
                    survey.select_bus(bus)
 | 
			
		||||
@@ -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")
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ from argparse import ArgumentParser, FileType
 | 
			
		||||
from django.core.management import BaseCommand
 | 
			
		||||
from django.db import transaction
 | 
			
		||||
 | 
			
		||||
from wei.forms import CurrentSurvey
 | 
			
		||||
from ...forms import CurrentSurvey
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Command(BaseCommand):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										109
									
								
								apps/wei/tests/test_wei_algorithm_2021.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								apps/wei/tests/test_wei_algorithm_2021.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,109 @@
 | 
			
		||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
 | 
			
		||||
# SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
 | 
			
		||||
import random
 | 
			
		||||
 | 
			
		||||
from django.contrib.auth.models import User
 | 
			
		||||
from django.test import TestCase
 | 
			
		||||
 | 
			
		||||
from ..forms.surveys.wei2021 import WEIBusInformation2021, WEISurvey2021, WORDS, WEISurveyInformation2021
 | 
			
		||||
from ..models import Bus, WEIClub, WEIRegistration
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestWEIAlgorithm(TestCase):
 | 
			
		||||
    """
 | 
			
		||||
    Run some tests to ensure that the WEI algorithm is working well.
 | 
			
		||||
    """
 | 
			
		||||
    fixtures = ('initial',)
 | 
			
		||||
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        """
 | 
			
		||||
        Create some test data, with one WEI and 10 buses with random score attributions.
 | 
			
		||||
        """
 | 
			
		||||
        self.wei = WEIClub.objects.create(
 | 
			
		||||
            name="WEI 2021",
 | 
			
		||||
            email="wei2021@example.com",
 | 
			
		||||
            date_start='2021-09-17',
 | 
			
		||||
            date_end='2021-09-19',
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.buses = []
 | 
			
		||||
        for i in range(10):
 | 
			
		||||
            bus = Bus.objects.create(wei=self.wei, name=f"Bus {i}", size=10)
 | 
			
		||||
            self.buses.append(bus)
 | 
			
		||||
            information = WEIBusInformation2021(bus)
 | 
			
		||||
            for word in WORDS:
 | 
			
		||||
                information.scores[word] = random.randint(0, 101)
 | 
			
		||||
            information.save()
 | 
			
		||||
            bus.save()
 | 
			
		||||
 | 
			
		||||
    def test_survey_algorithm_small(self):
 | 
			
		||||
        """
 | 
			
		||||
        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(10):
 | 
			
		||||
            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()
 | 
			
		||||
 | 
			
		||||
        # Ensure that everyone has its first choice
 | 
			
		||||
        for r in WEIRegistration.objects.filter(wei=self.wei).all():
 | 
			
		||||
            survey = WEISurvey2021(r)
 | 
			
		||||
            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, 25)  # Always less than 25 % of tolerance
 | 
			
		||||
 | 
			
		||||
        self.assertLessEqual(penalty / 100, 25)  # Tolerance of 5 %
 | 
			
		||||
@@ -3,7 +3,6 @@
 | 
			
		||||
 | 
			
		||||
import subprocess
 | 
			
		||||
from datetime import timedelta, date
 | 
			
		||||
from unittest import skip
 | 
			
		||||
 | 
			
		||||
from api.tests import TestAPI
 | 
			
		||||
from django.conf import settings
 | 
			
		||||
@@ -813,10 +812,6 @@ class TestWEISurveyAlgorithm(TestCase):
 | 
			
		||||
        )
 | 
			
		||||
        CurrentSurvey(self.registration).save()
 | 
			
		||||
 | 
			
		||||
    @skip  # FIXME Write good unit tests
 | 
			
		||||
    def test_survey_algorithm(self):
 | 
			
		||||
        CurrentSurvey.get_algorithm_class()().run_algorithm()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestWeiAPI(TestAPI):
 | 
			
		||||
    def setUp(self) -> None:
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user