"""Automatisch rooster: stelt een conflictvrij voorstel voor (greedy). Houdt rekening met voorkeuren: een vak liever in de ochtend of middag, bepaalde weekdagen vrijhouden, en een maximum aantal blokken per dag per groep. """ 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 def _is_ochtend(slot): return slot.start_tijd.hour < 12 @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) vrije_dagen = {int(d) for d in (request.data.get("vrije_dagen") or [])} mpd = request.data.get("max_per_dag") try: max_per_dag = int(mpd) if mpd not in (None, "", 0, "0") else None except (TypeError, ValueError): max_per_dag = None 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, vrije dagen uitgesloten. sloten = [ s for s in Tijdslot.objects.filter(actief=True) .filter(Q(groep=groep) | Q(groep__isnull=True)) .order_by("dag", "volgorde", "start_tijd") if s.dag not in vrije_dagen ] if not sloten: return Response({"voorstel": [], "niet_geplaatst": [], "detail": "Geen bruikbare tijdsloten (let op vrije dagen)."}) alle_slot_dag = {t.id: t.dag for t in Tijdslot.objects.all()} # Bezetting bijhouden (eventueel inclusief bestaande blokken). groep_bezet = set() beg_bezet = {} lok_bezet = {} per_dag = {} # weekdag -> aantal blokken voor deze groep 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) wd = alle_slot_dag.get(b.tijdslot_id) if wd is not None: per_dag[wd] = per_dag.get(wd, 0) + 1 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 dagdeel = (w.get("dagdeel") or "").strip() if not act_id or aantal <= 0: continue beg = int(beg) if beg else None lok = int(lok) if lok else None # Voorkeur dagdeel: zet de voorkeurssloten vooraan (zachte voorkeur). if dagdeel == "ochtend": kandidaten = [s for s in sloten if _is_ochtend(s)] + [s for s in sloten if not _is_ochtend(s)] elif dagdeel == "middag": kandidaten = [s for s in sloten if not _is_ochtend(s)] + [s for s in sloten if _is_ochtend(s)] else: kandidaten = sloten geplaatst = 0 vak_dagen = set() for pas in (1, 2): # pas 1 = spreiden (max 1 per dag), pas 2 = opvullen for s in kandidaten: if geplaatst >= aantal: break if s.id in groep_bezet: continue if pas == 1 and s.dag in vak_dagen: continue if max_per_dag is not None and per_dag.get(s.dag, 0) >= max_per_dag: 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"], "dagdeel": "ochtend" if _is_ochtend(s) else "middag", "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) per_dag[s.dag] = per_dag.get(s.dag, 0) + 1 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})