roosterwijs/backend/modules/autorooster/views.py
2026-06-17 14:48:04 +02:00

102 lines
4.3 KiB
Python

"""Automatisch rooster: stelt een conflictvrij voorstel voor (greedy)."""
from django.db.models import Q
from rest_framework.decorators import api_view
from rest_framework.response import Response
from core.models import Activiteit, Groep, Lokaal, Persoon, Roosterblok, Schooljaar, Tijdslot
@api_view(["POST"])
def voorstel(request):
sjid = request.data.get("schooljaar")
groep_id = request.data.get("groep")
wensen = request.data.get("wensen") or []
respecteer = request.data.get("respecteer_bestaand", True)
if not sjid or not groep_id:
return Response({"detail": "Geef een schooljaar en een groep op."}, status=400)
try:
sj = Schooljaar.objects.get(pk=sjid)
groep = Groep.objects.get(pk=groep_id)
except (Schooljaar.DoesNotExist, Groep.DoesNotExist):
return Response({"detail": "Schooljaar of groep niet gevonden."}, status=404)
# Kandidaat-tijdsloten: groep-specifiek of algemeen (geen groep).
sloten = list(
Tijdslot.objects.filter(actief=True)
.filter(Q(groep=groep) | Q(groep__isnull=True))
.order_by("dag", "volgorde", "start_tijd")
)
if not sloten:
return Response({"voorstel": [], "niet_geplaatst": [], "detail": "Geen tijdsloten gevonden voor deze groep."})
# Bezetting bijhouden (eventueel inclusief bestaande blokken).
groep_bezet = set()
beg_bezet = {}
lok_bezet = {}
if respecteer:
for b in Roosterblok.objects.filter(schooljaar=sj).prefetch_related("begeleiders"):
if b.groep_id == groep.id:
groep_bezet.add(b.tijdslot_id)
for p in b.begeleiders.all():
beg_bezet.setdefault(b.tijdslot_id, set()).add(p.id)
if b.lokaal_id:
lok_bezet.setdefault(b.tijdslot_id, set()).add(b.lokaal_id)
act_naam = {a.id: a.naam for a in Activiteit.objects.all()}
pers_naam = {p.id: p.volledige_naam for p in Persoon.objects.all()}
lok_naam = {l.id: l.naam for l in Lokaal.objects.all()}
slot_info = {s.id: {"label": f"{s.get_dag_display()} {s.start_tijd:%H:%M} {s.naam}", "dag": s.dag, "dag_label": s.get_dag_display()} for s in sloten}
voorstel_lijst = []
niet = []
for w in wensen:
act_id = w.get("activiteit")
aantal = int(w.get("aantal") or 0)
beg = w.get("begeleider") or None
lok = w.get("lokaal") or None
if not act_id or aantal <= 0:
continue
beg = int(beg) if beg else None
lok = int(lok) if lok else None
geplaatst = 0
vak_dagen = set()
# Twee passes: eerst spreiden (max 1 per dag), daarna opvullen.
for pas in (1, 2):
for s in sloten:
if geplaatst >= aantal:
break
if s.id in groep_bezet:
continue
if pas == 1 and s.dag in vak_dagen:
continue
if beg and beg in beg_bezet.get(s.id, set()):
continue
if lok and lok in lok_bezet.get(s.id, set()):
continue
voorstel_lijst.append({
"tijdslot": s.id,
"tijdslot_label": slot_info[s.id]["label"],
"dag": slot_info[s.id]["dag"],
"dag_label": slot_info[s.id]["dag_label"],
"activiteit": act_id,
"activiteit_naam": act_naam.get(act_id, "?"),
"groep": groep.id,
"begeleider": beg,
"begeleider_naam": pers_naam.get(beg) if beg else None,
"lokaal": lok,
"lokaal_naam": lok_naam.get(lok) if lok else None,
})
groep_bezet.add(s.id)
vak_dagen.add(s.dag)
if beg:
beg_bezet.setdefault(s.id, set()).add(beg)
if lok:
lok_bezet.setdefault(s.id, set()).add(lok)
geplaatst += 1
if geplaatst >= aantal:
break
if geplaatst < aantal:
niet.append({"activiteit_naam": act_naam.get(act_id, "?"), "tekort": aantal - geplaatst})
voorstel_lijst.sort(key=lambda x: (x["dag"], x["tijdslot_label"]))
return Response({"voorstel": voorstel_lijst, "niet_geplaatst": niet})