Compare commits
No commits in common. "f7b1267c989661ec797b527a0d7a1cca6f5d223e" and "08e5cf11bddbe14ec94cff3ffe10cd6ef145deda" have entirely different histories.
f7b1267c98
...
08e5cf11bd
38
ausgabe.py
38
ausgabe.py
@ -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:")
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user