Commit ea618684 authored by PabloLeCrapaud's avatar PabloLeCrapaud
Browse files

Merge branch 'dev' into le_poids_gourmand

parents b5de90a2 28c1d41c
......@@ -51,7 +51,7 @@ class TTConstraint(models.Model):
department = models.ForeignKey('base.Department', null=True, on_delete=models.CASCADE)
train_progs = models.ManyToManyField('base.TrainingProgramme',
blank=True)
weeks = models.ManyToManyField('base.Week')
weeks = models.ManyToManyField('base.Week', blank=True)
weight = models.PositiveSmallIntegerField(
validators=[MinValueValidator(1), MaxValueValidator(max_weight)],
null=True, default=None, blank=True)
......
......@@ -97,7 +97,8 @@ class MinGroupsHalfDays(TTConstraint):
class MinNonPreferedTrainProgsSlot(TTConstraint):
"""
Minimize the use of unprefered Slots for groups
Minimize the use of unprefered Slots for groups.
If weight is None, make impossible the use of forbidden slots.
"""
def enrich_model(self, ttmodel, week, ponderation=None):
if ponderation is None:
......@@ -130,6 +131,23 @@ class MinNonPreferedTrainProgsSlot(TTConstraint):
train_prog][sl]
cost *= day_time_ponderation
ttmodel.add_to_group_cost(g, cost, week=week)
if self.weight is None:
for course_type in ttmodel.wdb.course_types:
for sl in ttmodel.wdb.availability_slots:
if ttmodel.avail_course[(course_type, train_prog)][sl] == 0:
ttmodel.add_constraint(
ttmodel.sum(ttmodel.TT[(sl2, c)]
for g in basic_groups
for c in ttmodel.wdb.courses_for_basic_group[g]
for sl2 in slots_filter(ttmodel.wdb.compatible_slots[c],
simultaneous_to=sl)),
'==',
0,
Constraint(constraint_type=ConstraintType.BOUND_HOURS_PER_DAY,
train_progs=train_prog,
slots=sl)
)
def one_line_description(self):
text = "Respecte les préférences"
......
......@@ -45,7 +45,8 @@ from base.timing import Time
from people.models import Tutor
from TTapp.models import MinNonPreferedTutorsSlot, Stabilize, MinNonPreferedTrainProgsSlot
from TTapp.models import MinNonPreferedTutorsSlot, Stabilize, MinNonPreferedTrainProgsSlot, \
MinimizeBusyDays, MinGroupsHalfDays, RespectBoundPerDay, ConsiderDepencies
from TTapp.slots import slots_filter, days_filter
......@@ -270,7 +271,7 @@ class TTModel(object):
forced_IBD[(i, d)] = 0
if d.day in self.wdb.physical_presence_days_for_tutor[i][d.week]:
forced_IBD[(i, d)] = 1
if self.department.mode.cosmo:
if self.department.mode.cosmo == 1:
if self.wdb.sched_courses.filter(day=d.day, course__week=d.week,
course__suspens=False,
course__tutor=i).exists():
......@@ -520,21 +521,25 @@ class TTModel(object):
self.add_constraint(self.sum([self.TT[(sl, c)] for sl in self.wdb.compatible_slots[c]]), '==', 1,
CourseConstraint(c))
# # Training half day TODO: Delete usage because redundant with NoCourseOnDay ?
# for training_half_day in self.wdb.training_half_days:
# training_slots = slots_filter(self.wdb.courses_slots, week_day=training_half_day.day, week=training_half_day.week)
# if training_half_day.apm is not None:
# training_slots = slots_filter(training_slots, apm=training_half_day.apm)
# training_progs = self.train_prog
# if training_half_day.train_prog is not None:
# training_progs = [training_half_day.train_prog]
# # , "no_course_on_%s_%s_%g" % (training_half_day.day, training_half_day.apm, self.constraint_nb)
# self.add_constraint(self.sum(self.TT[(sl, c)] for sl in training_slots
# for c in self.wdb.compatible_courses[sl]
# & set(self.wdb.courses.filter(module__train_prog__in=training_progs))),
# '==', 0,
# Constraint(constraint_type=ConstraintType.PAS_DE_COURS_DE_DEMI_JOURNEE,
# days=training_half_day.day, apm=training_half_day.apm))
# Check if RespectBound constraint is in database, and add it if not
R, created = RespectBoundPerDay.objects.get_or_create(department=self.department)
if created:
R.save()
# Check if MinimizeBusyDays constraint is in database, and add it if not
M, created = MinimizeBusyDays.objects.get_or_create(department=self.department)
if created:
M.save()
# Check if MinGroupsHalfDays constraint is in database, and add it if not
M, created = MinGroupsHalfDays.objects.get_or_create(department=self.department)
if created:
M.save()
# Check if ConsiderDependencies constraint is in database, and add it if not
C, created = ConsiderDepencies.objects.get_or_create(department=self.department)
if created:
M.save()
def add_instructors_constraints(self):
print("adding instructors constraints")
......@@ -736,7 +741,7 @@ class TTModel(object):
self.wdb.other_departments_courses_for_tutor[i]
if c.week == week)
week_holidays = [d.day for d in days_filter(self.wdb.holidays, week=week)]
if not self.department.mode.cosmo and week_holidays:
if self.department.mode.cosmo != 1 and week_holidays:
week_tutor_availabilities = set(
a for a in self.wdb.availabilities[i][week]
if a.day not in week_holidays)
......@@ -744,7 +749,7 @@ class TTModel(object):
week_tutor_availabilities = self.wdb.availabilities[i][week]
if not week_tutor_availabilities:
self.add_warning(i, "no availability information given week %g" % week)
self.add_warning(i, "no availability information given week %s" % week)
for availability_slot in week_availability_slots:
unp_slot_cost[i][availability_slot] = 0
avail_at_school_instr[i][availability_slot] = 1
......@@ -763,7 +768,7 @@ class TTModel(object):
# avail_instr[i][availability_slot] = 1
elif avail_time < total_teaching_duration:
self.add_warning(i, "%g available hours < %g courses hours including other deps week %g" % (
self.add_warning(i, "%g available hours < %g courses hours including other deps week %s" % (
avail_time / 60, total_teaching_duration / 60, week))
# We used to forget tutor availabilities in this case...
# for availability_slot in week_availability_slots:
......@@ -993,7 +998,8 @@ class TTModel(object):
# because it contains rooms/instructors availability modification...
self.add_other_departments_constraints()
self.add_rooms_constraints()
if not self.department.mode.cosmo:
self.add_rooms_constraints()
self.add_instructors_constraints()
......@@ -1017,6 +1023,11 @@ class TTModel(object):
work_copy=target_work_copy) \
.delete()
if self.department.mode.cosmo == 2:
corresponding_group = {}
for i in self.wdb.instructors:
corresponding_group[i] = self.wdb.groups.get(name=i.username)
for c in self.wdb.courses:
for sl in self.wdb.compatible_slots[c]:
for i in self.wdb.possible_tutors[c]:
......@@ -1036,12 +1047,14 @@ class TTModel(object):
start_time=sl.start_time,
day=sl.day.day,
work_copy=target_work_copy)
for rg in self.wdb.course_rg_compat[c]:
if self.get_var_value(self.TTrooms[(sl, c, rg)]) == 1:
cp.room = rg
break
if not self.department.mode.cosmo:
for rg in self.wdb.course_rg_compat[c]:
if self.get_var_value(self.TTrooms[(sl, c, rg)]) == 1:
cp.room = rg
break
cp.save()
if self.department.mode.cosmo == 2:
c.groups.add(corresponding_group[i])
for fc in self.wdb.fixed_courses:
cp = ScheduledCourse(course=fc.course,
......
......@@ -129,6 +129,7 @@ class ConstraintType(Enum):
LUNCH_BREAK = "Lunch Breaks"
BREAK_AROUND_COURSE = "Break around course"
NO_COURSE_ON_DAY = "No Course on day"
TRAIN_PROG_FORBIDDEN_SLOT = "TRAIN_PROG_FORBIDDEN_SLOT"
# From minhalfdays
MIN_HALF_DAYS_LIMIT = "Min Half Days limit"
......
......@@ -10,7 +10,10 @@ def from_week_year_to_weeks(apps, schema_editor):
Week = apps.get_model('base', "Week")
for ttc_c in all_ttconstraint_classes:
class_name = ttc_c._meta.object_name
cl = apps.get_model('TTapp', class_name)
try:
cl = apps.get_model('TTapp', class_name)
except:
continue
all_o = cl.objects.exclude(Q(week=None)|Q(year=None))
for o in all_o:
w = Week.objects.get(nb=o.week, year=o.year)
......
# Generated by Django 3.1.7 on 2021-07-13 14:39
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0081_auto_20210710_2236'),
('TTapp', '0047_auto_20210510_1941'),
]
operations = [
migrations.AlterField(
model_name='avoidbothtimes',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='boundphysicalpresencehalfdays',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='breakaroundcoursetype',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='considerdepencies',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='curfew',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='customconstraint',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='groupslunchbreak',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='limitedroomchoices',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='limitedstarttimechoices',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='limitgroupsphysicalpresence',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='limitgroupstimeperperiod',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='limitmodulestimeperperiod',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='limittutorstimeperperiod',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='lowerboundbusydays',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='mingroupshalfdays',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='minimizebusydays',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='minmoduleshalfdays',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='minnonpreferedtrainprogsslot',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='minnonpreferedtutorsslot',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='mintutorshalfdays',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='nogroupcourseonday',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='notutorcourseonday',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='novisio',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='respectboundperday',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='simultaneouscourses',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='stabilizationthroughweeks',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='stabilize',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='tutorslunchbreak',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
migrations.AlterField(
model_name='visioonly',
name='weeks',
field=models.ManyToManyField(blank=True, to='base.Week'),
),
]
......@@ -31,16 +31,6 @@ Dans la rubrique `TTapp` vos trouverez les contraintes prêtes à être en base.
![TTConstraints](static/TTapp/TTConstraints.png) En voici une description des plus fréquemment utilisées,
avec quelques caractéristiques propres.
- Groups lunch breaks: force une pause d'une durée définie entre telle heure et telle heure pour les groupes choisis
- Limit groups time per periods : limite le nombre d'heures (d'un type donné) par jour/demie-journée pour
un ou plusieurs groupes.
- Limit modules time per periods : idem pour une ou plusieurs matières (éventuellement par promo)
- Limit tutors time per periods : idem pour un enseignant
- Limited room choices : limite les salles possibles (pour un enseignant, un module, ...)
- Limited start time choices: limite les heures de début possible
- Min tutors half days : minimise les demie-journées de présence/cours pour les profs choisis
- Simultaneous courses : impose qu'un ensemble de cours donnés soient simultanés
:bulb: le paramètre weight définit s'il s'agit d'**une contrainte (si weight est laissé vide)** qui sera toujours respectée
ou d'une préférence (avec un degré d'importance de 1 à 8) que le solveur essaiera d'appliquer.
......@@ -53,15 +43,40 @@ pour une promotion particulière ou pour toutes (laisser vide), etc...
:bulb: Attention, les contraintes ne sont valables que pour le département via lequel elles sont saisies!
Certaines sont enregistrées automatiquement à la première génération puis appliquées par défaut (il est possible
### Contraintes de base
Ces contraintes sont enregistrées automatiquement à la première génération puis appliquées par défaut (il est possible
néanmoins de les désactiver):
- Min non prefered train progs slots : minimise les créneaux non préférés pour les groupes
- Min non prefered tutors slots : minimise les créneaux non préférés pour les profs
- Minimize busy days: minimise les jours de présence des profs (en respectant leur journée idéale)
- Respecter les limites horaires: respecte la limite maximale définie par les profs
- Min groups half days: tente de libérer une/plusieurs demie-journées aux étudiant⋅e⋅s
- ConsiderDependencies: prend en compte les dépendance entre cours (Dependecy) enregistrées en base
### Autres contraintes génériques
- Groups lunch breaks: force une pause d'une durée définie entre telle heure et telle heure pour les groupes choisis
- Tutors lunch breaks: idem, pour les enseignant⋅e⋅s / salarié⋅e⋅s
- Limit groups time per periods : limite le nombre d'heures (d'un type donné) par jour/demie-journée pour
un ou plusieurs groupes.
- Limit modules time per periods : idem pour une ou plusieurs matières (éventuellement par promo)
- Limit tutors time per periods : idem, pour les enseignant⋅e⋅s / salarié⋅e⋅s
- Limited room choices : limite les salles possibles (pour un enseignant, un module, ...)
- Limited start time choices: limite les heures de début possible
- Min tutors half days : minimise les demie-journées de présence/cours pour les profs choisis
- Simultaneous courses : impose qu'un ensemble de cours donnés soient simultanés
- No group course on day: permet d'interdire tout cours sur une journée/demie-journée
- No tutor course on day: idem, pour les enseignant⋅e⋅s / salarié⋅e⋅s
### Contraintes spécifiques au mode Visio
- Bound Physical presence half days: met des bornes (min/max) sur le nombre de demies-journées de présene sur site pour les groupes d'étudiant⋅e⋅s
- Curfew : définit une heure à partir de laquelle les cours en présentiel sont proscrits
- Limit groups physical presence: permet de donner un ratio détudiant⋅e⋅s pouvant être simultanément sur site
- No Visio: force les cours concernés à être en présentiel
- Visio Only: force les cours concernés à être en visio
### Spoil
### Spoiler alert
Vous ne trouverez probablement pas toutes les contraintes qui vous intéresseront dans cette liste.
De nombreuses autres contraintes sont sur le point d'être ajoutées (on y travaille!). Si vous voulez nous y aider,
ou simplement nous proposer une nouvelle contrainte qui vous serez utile, [contactez-nous!](mailto:contact@flopedt.org)
......
......@@ -106,7 +106,7 @@ class WeeksDatabase(object):
database_holidays = Holiday.objects.filter(week__in=self.weeks)
holidays = set(d for d in days if database_holidays.filter(day=d.day, week=d.week).exists())
if not self.department.mode.cosmo:
if self.department.mode.cosmo != 1:
for hd in holidays:
days.remove(hd)
......@@ -135,6 +135,7 @@ class WeeksDatabase(object):
courses_slots = set()
filtered_cstc = CourseStartTimeConstraint.objects.filter(Q(course_type__in=self.course_types)
| Q(course_type=None))
# Courses slots
for cc in filtered_cstc:
start_times = cc.allowed_start_times
if self.slots_step is None:
......@@ -149,17 +150,23 @@ class WeeksDatabase(object):
for slot in courses_slots:
self.possible_apms.add(slot.apm)
# We build availability slots considering the possible Intervals from a possible start time to another
# and adding the possible end times. It is a partition, and we may use the Partition class to do it.
dayly_availability_slots= set()
for cst in filtered_cstc:
dayly_availability_slots |= set(cst.allowed_start_times)
if cst.course_type is not None:
dayly_availability_slots |= set(st + cst.course_type.duration for st in cst.allowed_start_times)
dayly_availability_slots.add(tgs.day_finish_time)
dayly_availability_slots = list(dayly_availability_slots)
dayly_availability_slots.sort()
start_times = dayly_availability_slots[:-1]
end_times = dayly_availability_slots[1:]
for i in range(len(end_times)):
if tgs.lunch_break_start_time < end_times[i] <= tgs.lunch_break_finish_time:
end_times[i] = tgs.lunch_break_start_time
if tgs.lunch_break_start_time < tgs.lunch_break_finish_time and \
tgs.lunch_break_start_time in start_times and \
tgs.lunch_break_finish_time in end_times:
start_times.remove(tgs.lunch_break_start_time)
end_times.remove(tgs.lunch_break_finish_time)
availability_slots = {Slot(day=day,
start_time=start_times[i],
......@@ -308,8 +315,8 @@ class WeeksDatabase(object):
for c in self.courses:
sc = self.sched_courses.get(course=c)
if not c.suspens:
slots = {slot for slot in slots_filter(self.courses_slots, week=sc.course.week,
start_time=sc.start_time, course_type=sc.course.type)
slots = {slot for slot in slots_filter(self.courses_slots, week=c.week,
start_time=sc.start_time, course_type=c.type)
if slot.day.day == sc.day}
if len(slots) == 1:
sl = slots.pop()
......@@ -318,10 +325,8 @@ class WeeksDatabase(object):
compatible_courses[sl].add(c)
compatible_slots[c] = {sl}
else:
slots = set([slot for slot in slots_filter(self.courses_slots, week=sc.course.week,
course_type=sc.course.type)
if 9 * 60 <= slot.start_time <= 18 * 60
and slot.day.day not in [Day.SUNDAY, Day.SATURDAY]])
slots = set([slot for slot in slots_filter(self.courses_slots, week=c.week,
course_type=c.type)])
compatible_slots[c] = slots
for sl in slots:
compatible_courses[sl].add(c)
......
......@@ -132,7 +132,7 @@ class CourseCosmo_SC_Serializer(serializers.Serializer):
week = serializers.SerializerMethodField()
year = serializers.SerializerMethodField()
groups = Group_SC_Serializer(many=True)
module = ModuleCosmo_SC_Serializer()
module = Module_SC_Serializer()
def get_week(self, obj):
if(obj.week is not None):
......
......@@ -194,7 +194,7 @@ class WeekInfoViewSet(viewsets.ViewSet):
permission_classes = [IsAdminOrReadOnly]
def list(self, request, format=None):
week = int(request.query_params.get('week'))
week_nb = int(request.query_params.get('week'))
year = int(request.query_params.get('year'))
try:
......@@ -206,14 +206,14 @@ class WeekInfoViewSet(viewsets.ViewSet):
version = 0
for dept in bm.Department.objects.all():
version += queries.get_edt_version(dept, week, year, create=True)
version += queries.get_edt_version(dept, week_nb, year, create=True)
proposed_pref, required_pref = \
pref_requirements(department, request.user, year, week) if request.user.is_authenticated \
pref_requirements(department, request.user, year, week_nb) if request.user.is_authenticated \
else (-1, -1)
try:
regen = str(bm.Regen.objects.get(department=department, week__nb=week, week__year=year))
regen = str(bm.Regen.objects.get(department=department, week__nb=week_nb, week__year=year))
except bm.Regen.DoesNotExist:
regen = 'I'
......
......@@ -35,14 +35,12 @@ import django.contrib.auth as auth
from django.utils.translation import gettext_lazy as _
from people.models import Tutor, User
from base.models import Day, Room, Module, Course, Group, \
UserPreference, Time, ScheduledCourse, EdtVersion, CourseModification, \
TrainingProgramme, \
Regen, Holiday, TrainingHalfDay, \
from base.models import Room, Module, Course, Group, \
UserPreference, ScheduledCourse, EdtVersion, CourseModification, \
TrainingProgramme, Regen, Holiday, TrainingHalfDay, \
CoursePreference, Dependency, Department, CourseType, \
ScheduledCourseAdditional, CourseAdditional, EnrichedLink
from base.models import RoomPreference, RoomSort, RoomType, EnrichedLink, GroupPreferredLinks
ScheduledCourseAdditional, CourseAdditional, RoomPreference, RoomSort, RoomType, EnrichedLink, GroupPreferredLinks, \
Mode
from displayweb.models import ModuleDisplay
from displayweb.models import TutorDisplay
from import_export import resources, fields
......@@ -682,9 +680,6 @@ class EnrichedLinkAdmin(MyModelAdmin):
ordering = ('description',)
class GroupPreferredLinksAdmin(MyModelAdmin):
pass
# </editor-fold desc="ADMIN_MENU">
......@@ -706,4 +701,5 @@ admin.site.register(ScheduledCourse, CoursPlaceAdmin)
admin.site.register(UserPreference, DispoAdmin)
admin.site.register(Regen, RegenAdmin)
admin.site.register(EnrichedLink, EnrichedLinkAdmin)
admin.site.register(GroupPreferredLinks, GroupPreferredLinksAdmin)
admin.site.register(GroupPreferredLinks, DepartmentModelAdmin)
admin.site.register(Mode, DepartmentModelAdmin)
# Generated by Django 3.1.7 on 2021-07-09 10:03
import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('base', '0077_auto_20210511_1707'),
]
operations = [
migrations.RenameField(
model_name='mode',
old_name='cosmo',
new_name='ex_cosmo',
),
migrations.AddField(
model_name='mode',
name='new_cosmo',
field=models.PositiveSmallIntegerField(default=0, validators=[django.core.validators.MaxValueValidator(2)]),
),
migrations.AlterField(
model_name='mode',
name='visio',
field=models.BooleanField(default=False),
),
]
# Generated by Django 3.1.7 on 2021-07-09 10:04
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('base', '0078_auto_20210709_1003'),
]