Compare commits

..

No commits in common. "f7b1267c989661ec797b527a0d7a1cca6f5d223e" and "08e5cf11bddbe14ec94cff3ffe10cd6ef145deda" have entirely different histories.

3 changed files with 35 additions and 51 deletions

View File

@ -118,30 +118,28 @@ class ElterndienstAusgabe:
positive_praef_tage = {tag for tag, präf in praeferenzen_dienst.items() if präf == 1} positive_praef_tage = {tag for tag, präf in praeferenzen_dienst.items() if präf == 1}
if positive_praef_tage: # Es gibt positive Präferenzen if positive_praef_tage: # Es gibt positive Präferenzen
# Prüfe ob ALLE zugeteilten Dienste an nicht-präferierten Tagen sind
for tag in zugeteilte_tage: for tag in zugeteilte_tage:
if tag not in positive_praef_tage: if tag not in positive_praef_tage:
# Dienst wurde an nicht-präferiertem Tag zugeteilt
verletzungen[eltern][dienst]['positiv_nicht_erfuellt'] += 1 verletzungen[eltern][dienst]['positiv_nicht_erfuellt'] += 1
# Tabelle ausgeben (verbesserte Spaltenformatierung) # Tabelle ausgeben
col_width = 14 # Breite pro Dienst-Spalte (sichtbar) print(f"\n{'Eltern':<20} ", end='')
name_col = 20
# Header
print(f"\n{'Eltern':<{name_col}}", end='')
for dienst in self.daten.dienste: for dienst in self.daten.dienste:
print(f"{dienst.kuerzel:^{col_width}}", end='') print(f"{dienst.kuerzel:>12}", end='')
print() print()
print(f"{'':{name_col}}", end='') print(f"{'':20} ", end='')
for _ in self.daten.dienste: for dienst in self.daten.dienste:
print(f"{'neg, pos':^{col_width}}", end='') print(f"{'neg, pos':>12}", end='')
print() print()
print("-" * (name_col + col_width * len(self.daten.dienste))) print("-" * (20 + 12 * len(self.daten.dienste)))
gesamt_negativ = defaultdict(int) gesamt_negativ = defaultdict(int)
gesamt_positiv = defaultdict(int) gesamt_positiv = defaultdict(int)
for eltern in sorted(self.daten.eltern): for eltern in sorted(self.daten.eltern):
print(f"{eltern:<{name_col}}", end='') print(f"{eltern:<20} ", end='')
for dienst in self.daten.dienste: for dienst in self.daten.dienste:
neg = verletzungen[eltern][dienst]['negativ'] neg = verletzungen[eltern][dienst]['negativ']
pos = verletzungen[eltern][dienst]['positiv_nicht_erfuellt'] pos = verletzungen[eltern][dienst]['positiv_nicht_erfuellt']
@ -149,28 +147,22 @@ class ElterndienstAusgabe:
gesamt_negativ[dienst] += neg gesamt_negativ[dienst] += neg
gesamt_positiv[dienst] += pos gesamt_positiv[dienst] += pos
# Inhalt vor Padding erstellen # Farbcodierung
cell = f"{neg:>3}, {pos:>3}"
cell_padded = cell.center(col_width)
# Farbcodierung (erst nach Padding anwenden)
farbe = "" farbe = ""
reset = "" reset = ""
if neg > 0 or pos > 0: if neg > 0 or pos > 0:
farbe = "\033[91m" if neg > 0 else "\033[93m" # Rot für negativ, Gelb für positiv farbe = "\033[91m" if neg > 0 else "\033[93m" # Rot für negativ, Gelb für positiv
reset = "\033[0m" reset = "\033[0m"
print(f"{farbe}{cell_padded}{reset}", end='') print(f"{farbe}{neg:>3}, {pos:>3}{reset:>6}", end='')
print() print()
# Summenzeile # Summenzeile
print("-" * (name_col + col_width * len(self.daten.dienste))) print("-" * (20 + 12 * len(self.daten.dienste)))
print(f"{'SUMME':<{name_col}}", end='') print(f"{'SUMME':<20} ", end='')
for dienst in self.daten.dienste: for dienst in self.daten.dienste:
neg = gesamt_negativ[dienst] neg = gesamt_negativ[dienst]
pos = gesamt_positiv[dienst] pos = gesamt_positiv[dienst]
cell = f"{neg:>3}, {pos:>3}"
cell_padded = cell.center(col_width)
farbe = "" farbe = ""
reset = "" reset = ""
@ -178,7 +170,7 @@ class ElterndienstAusgabe:
farbe = "\033[91m" if neg > 0 else "\033[93m" farbe = "\033[91m" if neg > 0 else "\033[93m"
reset = "\033[0m" reset = "\033[0m"
print(f"{farbe}{cell_padded}{reset}", end='') print(f"{farbe}{neg:>3}, {pos:>3}{reset:>6}", end='')
print() print()
print("\nLegende:") print("\nLegende:")

View File

@ -15,17 +15,16 @@ from csv_io import EingabeParser
class Dienst: class Dienst:
"""Repräsentiert einen Diensttyp mit allen seinen Eigenschaften""" """Repräsentiert einen Diensttyp mit allen seinen Eigenschaften"""
def __init__(self, kuerzel: str, name: str, personen_anzahl: int = 1, aufwand: int = 1) -> None: def __init__(self, kuerzel: str, name: str, personen_anzahl: int = 1) -> None:
self.kuerzel: str = kuerzel self.kuerzel: str = kuerzel
self.name: str = name self.name: str = name
self.personen_anzahl: int = personen_anzahl self.personen_anzahl: int = personen_anzahl
self.aufwand: int = aufwand
def __str__(self) -> str: def __str__(self) -> str:
return f"{self.kuerzel} ({self.name}): {self.personen_anzahl} Person(en), Aufwand={self.aufwand}" return f"{self.kuerzel} ({self.name}): {self.personen_anzahl} Person(en)"
def __repr__(self) -> str: def __repr__(self) -> str:
return f"Dienst('{self.kuerzel}', '{self.name}', {self.personen_anzahl}, {self.aufwand})" return f"Dienst('{self.kuerzel}', '{self.name}', {self.personen_anzahl})"
def braucht_mehrere_personen(self) -> bool: def braucht_mehrere_personen(self) -> bool:
"""Gibt True zurück, wenn mehr als eine Person benötigt wird""" """Gibt True zurück, wenn mehr als eine Person benötigt wird"""
@ -50,11 +49,11 @@ class ElterndienstplanerDaten:
def __init__(self) -> None: def __init__(self) -> None:
# Dienste als Liste definieren # Dienste als Liste definieren
self.dienste: List[Dienst] = [ self.dienste: List[Dienst] = [
Dienst('F', 'Frühstücksdienst', 1, aufwand=3), Dienst('F', 'Frühstücksdienst', 1),
Dienst('P', 'Putznotdienst', 1, aufwand=1), Dienst('P', 'Putznotdienst', 1),
Dienst('E', 'Essensausgabenotdienst', 1, aufwand=1), Dienst('E', 'Essensausgabenotdienst', 1),
Dienst('K', 'Kochen', 1, aufwand=3), Dienst('K', 'Kochen', 1),
Dienst('A', 'Elternabend', 2, aufwand=2) Dienst('A', 'Elternabend', 2)
] ]
# Datenstrukturen # Datenstrukturen

View File

@ -8,7 +8,6 @@ Datum: Dezember 2025
import sys import sys
import pulp import pulp
import multiprocessing
from datetime import timedelta, date from datetime import timedelta, date
from collections import defaultdict from collections import defaultdict
from typing import Dict, List, Tuple, DefaultDict, Optional from typing import Dict, List, Tuple, DefaultDict, Optional
@ -356,16 +355,14 @@ class Elterndienstplaner:
lowBound=0) lowBound=0)
# Zähle tatsächliche Dienste gewichtet mit dem Aufwand des Dienstes
tatsaechliche_dienste_gesamt = pulp.lpSum( tatsaechliche_dienste_gesamt = pulp.lpSum(
dienst.aufwand * x[eltern, tag, dienst] x[eltern, tag, dienst]
for tag in self.daten.planungszeitraum for tag in self.daten.planungszeitraum
for dienst in self.daten.dienste for dienst in self.daten.dienste
if (eltern, tag, dienst) in x if (eltern, tag, dienst) in x
) )
# Zielgesamt ebenfalls mit Dienst-Aufwand gewichtet ziel_gesamt = sum(ziel_dienste[eltern][dienst] for dienst in self.daten.dienste)
ziel_gesamt = sum(ziel_dienste[eltern][dienst] * dienst.aufwand for dienst in self.daten.dienste)
prob += (tatsaechliche_dienste_gesamt - ziel_gesamt <= prob += (tatsaechliche_dienste_gesamt - ziel_gesamt <=
fairness_abweichung_gesamt[eltern]) fairness_abweichung_gesamt[eltern])
@ -396,23 +393,21 @@ class Elterndienstplaner:
for eltern in self.daten.eltern: for eltern in self.daten.eltern:
for dienst in self.daten.dienste: for dienst in self.daten.dienste:
# Skaliere diensttyp-spezifische Fairness mit dem Aufwand des Dienstes objective_terms.append(gewicht_f1 * fairness_abweichung_global[eltern, dienst])
objective_terms.append(gewicht_f1 * fairness_abweichung_global[eltern, dienst] * dienst.aufwand) objective_terms.append(gewicht_f2 * fairness_abweichung_lokal[eltern, dienst])
objective_terms.append(gewicht_f2 * fairness_abweichung_lokal[eltern, dienst] * dienst.aufwand)
# Gesamt-Fairness (bereits dienstabhängig in den Constraints) — keine zusätzliche Mean-Skalierung mehr
objective_terms.append(gewicht_f3_global * fairness_abweichung_gesamt_global[eltern]) objective_terms.append(gewicht_f3_global * fairness_abweichung_gesamt_global[eltern])
objective_terms.append(gewicht_f4_lokal * fairness_abweichung_gesamt_lokal[eltern]) objective_terms.append(gewicht_f4_lokal * fairness_abweichung_gesamt_lokal[eltern])
# P1: Bevorzugte Dienste (stärker für aufwändigere Dienste) # P1: Bevorzugte Dienste
for (eltern, tag, dienst), praef in self.daten.praeferenzen.items(): for (eltern, tag, dienst), praef in self.daten.praeferenzen.items():
if (eltern, tag, dienst) in x and praef == 1: if (eltern, tag, dienst) in x and praef == 1:
objective_terms.append(-10 * dienst.aufwand * x[eltern, tag, dienst]) objective_terms.append(-5 * x[eltern, tag, dienst])
# P2: Abgelehnte Dienste (stärker für aufwändigere Dienste) # P2: Abgelehnte Dienste
for (eltern, tag, dienst), praef in self.daten.praeferenzen.items(): for (eltern, tag, dienst), praef in self.daten.praeferenzen.items():
if (eltern, tag, dienst) in x and praef == -1: if (eltern, tag, dienst) in x and praef == -1:
objective_terms.append(20 * dienst.aufwand * x[eltern, tag, dienst]) objective_terms.append(25 * x[eltern, tag, dienst])
if objective_terms: if objective_terms:
prob += pulp.lpSum(objective_terms) prob += pulp.lpSum(objective_terms)
@ -485,15 +480,13 @@ class Elterndienstplaner:
solver = None solver = None
try: try:
cpu_count = multiprocessing.cpu_count() print("Versuche CBC Solver...")
threads = max(1, cpu_count - 1) solver = pulp.PULP_CBC_CMD(msg=0, timeLimit=60)
print(f"Versuche CBC Solver mit {threads} Threads...") except:
solver = pulp.PULP_CBC_CMD(msg=0, timeLimit=20, threads=threads)
except Exception:
try: try:
print("Versuche GLPK Solver...") print("Versuche GLPK Solver...")
solver = pulp.GLPK_CMD(msg=0) solver = pulp.GLPK_CMD(msg=0)
except Exception: except:
print("Kein spezifizierter Solver verfügbar, verwende Standard.") print("Kein spezifizierter Solver verfügbar, verwende Standard.")
solver = None solver = None