diff --git a/apps/wei/forms/surveys/wei2024.py b/apps/wei/forms/surveys/wei2024.py index 7e8c576e..ad7605d0 100644 --- a/apps/wei/forms/surveys/wei2024.py +++ b/apps/wei/forms/surveys/wei2024.py @@ -11,60 +11,95 @@ from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInf from ...models import WEIMembership - - buses_descr = [ -["Magi[Kar]p","#ef5568", -"bus faible en alcool mais fort en connerie avec une partie calme pour les amateurs de sieste et de jeux de société. non discriminant il accepte tout le monde y compris le plus nulle des pokémons (magicarpe !!!!!!). Malgré les accusations mensongères, il n'y a aucun weeb dans le Magi[Kar]p"], -["Va[car]me","#fd7a28", -"descr"], -["[Kar]aïbes","#a5cfdd", -"descr"], -["[Kar]di [Bus]","#e46398", -"descr"], -["Sparta[bus] 🐺🐒🏉","#ebdac2", -"descr"], -["Zanzo[Bus]","#FFFF", -"descr"], -["Bran[Kar]","#6da1ac", -"Si vous ne connaissez pas le Bran[Kar], c’est comme une grande famille qui fait un apéro, qui se bourre un peu la gueule en discutant des heures autour d’une table remplie de bouffe et de super bons cocktails (la plupart des barmen/barwomen du bus sont les barmans de Shakens), sauf qu’on est un bus du Wei (vous comprendrez bien le nom de notre bus en voyant l’état de certain·e·s). Il nous arrive de faire quelques conneries, mais surtout de jouer au Bière-pong en musique !"], -["Techno [kar]ade","#8065a3", -"descr"], -["[Bus]ka-P","#7c4768", -"descr"] + [ + "Magi[Kar]p", "#ef5568", + """bus faible en alcool mais fort en connerie avec une partie calme pour les amateurs de sieste et de jeux de société. + Non discriminant il accepte tout le monde y compris le plus nulle des pokémons (magicarpe !!!!!!). Malgré les + accusations mensongères, il n'y a aucun weeb dans le Magi[Kar]p""", + ], + [ + "Va[car]me", "#fd7a28", + "descr", + ], + [ + "[Kar]aïbes", "#a5cfdd", + "descr", + ], + [ + "[Kar]di [Bus]", "#e46398", + "descr", + ], + [ + "Sparta[bus] 🐺🐒🏉", "#ebdac2", + "descr", + ], + [ + "Zanzo[Bus]", "#FFFF", + "descr", + ], + [ + "Bran[Kar]", "#6da1ac", + """Si vous ne connaissez pas le Bran[Kar], c’est comme une grande famille qui fait un apéro, qui se bourre un peu la + gueule en discutant des heures autour d’une table remplie de bouffe et de super bons cocktails (la plupart des + barmen/barwomen du bus sont les barmans de Shakens), sauf qu’on est un bus du Wei (vous comprendrez bien le nom de notre + bus en voyant l’état de certain·e·s). Il nous arrive de faire quelques conneries, mais surtout de jouer au Bière-pong en + musique !""", + ], + [ + "Techno [kar]ade", "#8065a3", + "descr" + ], + [ + "[Bus]ka-P", "#7c4768", + "descr", + ], ] + def get_survey_info(id): s = {"recap": { - "1": 0, - "2": 0, - "3": 0, - "4": 0, - "5": 0 + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0, }} - s_ = {f"bus{id}" : {f"{i}" : 0 for i in range(1,5+1)} for id in range(len(buses_descr))} + s_ = {f"bus{id}": {f"{i}": 0 for i in range(1, 5 + 1)} for id in range(len(buses_descr))} s.update(s_) - s.update({f"bus{id}" : {f"{i}" : i for i in range(1,5+1)}}) - return {"scores" : s} + s.update({f"bus{id}": {f"{i}": i for i in range(1, 5 + 1)}}) + return {"scores": s} def print_bus(id): return buses_descr[id][0] + "\n\n" + buses_descr[id][2] -def print_all_buses(): - l = [print_bus(id) for id in range(len(buses_descr))] - return "\n\n---------\n\n".join(l) -WORDS = {"recap": ["Cher(e) 1A, te voilà arrivé(e) devant un choix fatidique, le choix de ton bus....... \n (Musique effrayante) \n Peite blagounette évidemment, chacun des bus te permettra de passer un excellent WEI ! Mais quitte à avoir le choix, voici la liste de tous les bus ainsi qu'une description détaillée de ces derniers ! Prends ton temps, observe les bien et quand tu te sens prêt(e), appuye sur le bouton 'Noter les bus' pour continuer (pas besoin d'apprendre par coeur les bus, la descirption du bus te sera rappeler avant de le noter !) \n\n\n" + print_all_buses(), { - 1: "Noter les bus :" -}]} +def print_all_buses(): + liste = [print_bus(id) for id in range(len(buses_descr))] + return "

---------

".join(liste) + + +WORDS = { + "recap": + [ + """Chèr⋅e 1A, te voilà arrivé⋅e devant un choix fatidique, le choix de ton bus.......
+ (Musique effrayante)
+ Petite blagounette évidemment, chacun des bus te permettra de passer un excellent WEI ! + Mais quitte à avoir le choix, voici la liste de tous les bus ainsi qu'une description détaillée de ces derniers ! + Prends ton temps, observe les bien et quand tu te sens prêt⋅e, appuye sur le bouton 'Noter les bus' pour continuer + (pas besoin d'apprendre par cœur les bus, la description du bus te sera rappeler avant de le noter !)


""" + print_all_buses(), + { + 1: "Noter les bus :", + } + ] +} WORDS.update({ - f"bus{id}" : [print_bus(id),{i : f"Noter {i}/5" for i in range(1,5+1)}] for id in range(len(buses_descr)) + f"bus{id}": [print_bus(id), {i: f"Noter {i}/5" for i in range(1, 5 + 1)}] for id in range(len(buses_descr)) }) - class WEISurveyForm2024(forms.Form): """ Survey form for the year 2024. @@ -300,4 +335,3 @@ class WEISurveyAlgorithm2024(WEISurveyAlgorithm): if tqdm_obj is not None: tqdm_obj.n = len(surveys) - len(free_surveys) tqdm_obj.refresh() - diff --git a/apps/wei/tests/test_wei_algorithm_2023.py b/apps/wei/tests/test_wei_algorithm_2023.py index c94cbb0c..55c4a6d7 100644 --- a/apps/wei/tests/test_wei_algorithm_2023.py +++ b/apps/wei/tests/test_wei_algorithm_2023.py @@ -6,8 +6,6 @@ from datetime import date, timedelta from django.contrib.auth.models import User from django.test import TestCase -from django.urls import reverse -from note.models import NoteUser from ..forms.surveys.wei2023 import WEIBusInformation2023, WEISurvey2023, WORDS, WEISurveyInformation2023 from ..models import Bus, WEIClub, WEIRegistration @@ -127,44 +125,3 @@ class TestWEIAlgorithm(TestCase): self.assertLessEqual(max_score - score, 25) # Always less than 25 % of tolerance self.assertLessEqual(penalty / 100, 25) # Tolerance of 5 % - - def test_register_1a(self): - """ - Test register a first year member to the WEI and complete the survey - """ - response = self.client.get(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk))) - self.assertEqual(response.status_code, 200) - - user = User.objects.create(username="toto", email="toto@example.com") - NoteUser.objects.create(user=user) - response = self.client.post(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk)), dict( - user=user.id, - soge_credit=True, - birth_date=date(2000, 1, 1), - gender='nonbinary', - clothing_cut='female', - clothing_size='XS', - health_issues='I am a bot', - emergency_contact_name='NoteKfet2020', - emergency_contact_phone='+33123456789', - )) - qs = WEIRegistration.objects.filter(user_id=user.id) - self.assertTrue(qs.exists()) - registration = qs.get() - self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, 200) - for question in WORDS: - # Fill 1A Survey, 20 pages - # be careful if questionnary form change (number of page, type of answer...) - response = self.client.post(reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), { - question: "1" - }) - registration.refresh_from_db() - survey = WEISurvey2023(registration) - self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, - 302 if survey.is_complete() else 200) - self.assertIsNotNone(getattr(survey.information, question), "Survey page " + question + " failed") - survey = WEISurvey2023(registration) - self.assertTrue(survey.is_complete()) - survey.select_bus(self.buses[0]) - survey.save() - self.assertIsNotNone(survey.information.get_selected_bus()) diff --git a/apps/wei/tests/test_wei_algorithm_2024.py b/apps/wei/tests/test_wei_algorithm_2024.py new file mode 100644 index 00000000..dddf8417 --- /dev/null +++ b/apps/wei/tests/test_wei_algorithm_2024.py @@ -0,0 +1,172 @@ +# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay +# SPDX-License-Identifier: GPL-3.0-or-later + +import random +from datetime import date, timedelta + +from django.contrib.auth.models import User +from django.test import TestCase +from django.urls import reverse +from note.models import NoteUser + +from ..forms.surveys.wei2024 import WEIBusInformation2024, WEISurvey2024, WORDS, WEISurveyInformation2024 +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.user = User.objects.create_superuser( + username="weiadmin", + password="admin", + email="admin@example.com", + ) + self.user.save() + self.client.force_login(self.user) + sess = self.client.session + sess["permission_mask"] = 42 + sess.save() + + self.wei = WEIClub.objects.create( + name="WEI 2024", + email="wei2024@example.com", + parent_club_id=2, + membership_fee_paid=12500, + membership_fee_unpaid=5500, + membership_start='2024-01-01', + membership_end='2024-12-31', + date_start=date.today() + timedelta(days=2), + date_end='2024-12-31', + year=2024, + ) + + 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 = WEIBusInformation2024(bus) + for question in WORDS: + information.scores[question] = {answer: random.randint(1, 5) for answer in WORDS[question][1]} + 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 = WEISurveyInformation2024(registration) + for question in WORDS: + options = list(WORDS[question][1].keys()) + setattr(information, question, random.choice(options)) + information.step = 20 + information.save(registration) + registration.save() + + # Run algorithm + WEISurvey2024.get_algorithm_class()().run_algorithm() + + # Ensure that everyone has its first choice + for r in WEIRegistration.objects.filter(wei=self.wei).all(): + survey = WEISurvey2024(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 = WEISurveyInformation2024(registration) + for question in WORDS: + options = list(WORDS[question][1].keys()) + setattr(information, question, random.choice(options)) + information.step = 20 + information.save(registration) + registration.save() + + # Run algorithm + WEISurvey2024.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 = WEISurvey2024(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 % + + def test_register_1a(self): + """ + Test register a first year member to the WEI and complete the survey + """ + response = self.client.get(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk))) + self.assertEqual(response.status_code, 200) + + user = User.objects.create(username="toto", email="toto@example.com") + NoteUser.objects.create(user=user) + response = self.client.post(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk)), dict( + user=user.id, + soge_credit=True, + birth_date=date(2000, 1, 1), + gender='nonbinary', + clothing_cut='female', + clothing_size='XS', + health_issues='I am a bot', + emergency_contact_name='NoteKfet2020', + emergency_contact_phone='+33123456789', + )) + qs = WEIRegistration.objects.filter(user_id=user.id) + self.assertTrue(qs.exists()) + registration = qs.get() + self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, 200) + for question in WORDS: + # Fill 1A Survey, 20 pages + # be careful if questionnary form change (number of page, type of answer...) + response = self.client.post(reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), { + question: "1" + }) + registration.refresh_from_db() + survey = WEISurvey2024(registration) + self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, + 302 if survey.is_complete() else 200) + self.assertIsNotNone(getattr(survey.information, question), "Survey page " + question + " failed") + survey = WEISurvey2024(registration) + self.assertTrue(survey.is_complete()) + survey.select_bus(self.buses[0]) + survey.save() + self.assertIsNotNone(survey.information.get_selected_bus()) diff --git a/apps/wei/tests/test_wei_registration.py b/apps/wei/tests/test_wei_registration.py index 74fdcf0b..604945a4 100644 --- a/apps/wei/tests/test_wei_registration.py +++ b/apps/wei/tests/test_wei_registration.py @@ -767,7 +767,7 @@ class TestDefaultWEISurvey(TestCase): WEISurvey.update_form(None, None) self.assertEqual(CurrentSurvey.get_algorithm_class().get_survey_class(), CurrentSurvey) - self.assertEqual(CurrentSurvey.get_year(), 2023) + self.assertEqual(CurrentSurvey.get_year(), 2024) class TestWeiAPI(TestAPI):