refactoring: lokale fairness

This commit is contained in:
Jan Hoheisel 2025-12-23 22:14:17 +01:00
parent fefbc3d7b2
commit 2cb98d1476

View File

@ -252,7 +252,7 @@ class Elterndienstplaner:
return faktor
return 0
def berechne_faire_zielverteilung(self) -> DefaultDict[str, DefaultDict[Dienst, float]]:
def berechne_faire_zielverteilung_global(self) -> DefaultDict[str, DefaultDict[Dienst, float]]:
"""Berechnet die faire Zielanzahl von Diensten pro Eltern-Dienst-Kombination
basierend auf tatsächlich geleisteten historischen Diensten und deren fairer Umverteilung"""
@ -356,6 +356,55 @@ class Elterndienstplaner:
return ziel_dienste
def berechne_faire_zielverteilung_lokal(self) -> DefaultDict[str, DefaultDict[Dienst, float]]:
"""Berechnet die lokale faire Zielanzahl von Diensten pro Eltern-Dienst-Kombination
basierend auf Dienstfaktoren und benötigten Diensten im aktuellen Planungsmonat"""
ziel_dienste_lokal: DefaultDict[str, DefaultDict[Dienst, float]] = \
defaultdict(lambda: defaultdict(float))
print("\nBerechne lokale faire Zielverteilung für aktuellen Monat...")
# Gesamtdienstfaktor für aktuellen Monat berechnen
gesamt_dienstfaktor_monat = sum(
sum(self.dienstfaktoren.get(e, {}).get(tag, 0) for tag in self.tage)
for e in self.eltern
)
if gesamt_dienstfaktor_monat == 0:
print(" WARNUNG: Gesamtdienstfaktor ist 0, keine lokale Zielverteilung möglich")
return ziel_dienste_lokal
# Für jeden Dienst die lokale faire Verteilung berechnen
for dienst in self.dienste:
# Anzahl benötigter Dienste im aktuellen Monat
benoetigte_dienste_monat = sum(
1 for tag in self.tage
if dienst in self.benoetigte_dienste.get(tag, [])
)
# Multipliziere mit Anzahl benötigter Personen pro Dienst
benoetigte_dienste_monat *= dienst.personen_anzahl
if benoetigte_dienste_monat > 0:
print(f" {dienst.kuerzel}: {benoetigte_dienste_monat} Dienste benötigt")
for eltern in self.eltern:
# Dienstfaktor für diesen Elternteil im aktuellen Monat
monatlicher_dienstfaktor = sum(
self.dienstfaktoren.get(eltern, {}).get(tag, 0) for tag in self.tage
)
if monatlicher_dienstfaktor > 0:
anteil = monatlicher_dienstfaktor / gesamt_dienstfaktor_monat
faire_zuteilung = anteil * benoetigte_dienste_monat
ziel_dienste_lokal[eltern][dienst] = faire_zuteilung
if faire_zuteilung > 0.1: # Debug nur für relevante Werte
print(f" {eltern}: Faktor={monatlicher_dienstfaktor:.1f} "
f"-> {faire_zuteilung:.2f} Dienste")
return ziel_dienste_lokal
def erstelle_optimierungsmodell(self) -> Tuple[pulp.LpProblem, Dict[Tuple[str, date, Dienst], pulp.LpVariable]]:
"""Erstellt das PuLP Optimierungsmodell"""
print("Erstelle Optimierungsmodell...")
@ -442,8 +491,9 @@ class Elterndienstplaner:
# FAIRNESS-CONSTRAINTS UND ZIELFUNKTION
objective_terms = []
# Berechne faire Zielverteilung
ziel_dienste = self.berechne_faire_zielverteilung()
# Berechne faire Zielverteilungen (global und lokal)
ziel_dienste_global = self.berechne_faire_zielverteilung_global()
ziel_dienste_lokal = self.berechne_faire_zielverteilung_lokal()
# Hilfsvariablen für Fairness-Abweichungen
fairness_abweichung_lokal = {} # F2
@ -459,56 +509,35 @@ class Elterndienstplaner:
# F1: Globale Fairness & F2: Lokale Fairness
for eltern in self.eltern:
for dienst in self.dienste:
# Dienstfaktor für aktuellen Monat
monatlicher_dienstfaktor = sum(
self.dienstfaktoren.get(eltern, {}).get(tag, 0) for tag in self.tage
# Tatsächliche Dienste im aktuellen Monat
tatsaechliche_dienste_monat = pulp.lpSum(
x[eltern, tag, dienst]
for tag in self.tage
if (eltern, tag, dienst) in x
)
if monatlicher_dienstfaktor > 0:
# Tatsächliche Dienste im aktuellen Monat
tatsaechliche_dienste_monat = pulp.lpSum(
x[eltern, tag, dienst]
for tag in self.tage
if (eltern, tag, dienst) in x
)
# F2: Lokale Fairness - nur aktueller Monat
ziel_lokal = ziel_dienste_lokal[eltern][dienst]
if ziel_lokal > 0:
# Lokale Fairness-Constraints
prob += (tatsaechliche_dienste_monat - ziel_lokal <=
fairness_abweichung_lokal[eltern, dienst])
prob += (ziel_lokal - tatsaechliche_dienste_monat <=
fairness_abweichung_lokal[eltern, dienst])
# F2: Lokale Fairness - nur aktueller Monat
benoetigte_dienste_monat = sum(
1 for tag in self.tage
if dienst in self.benoetigte_dienste.get(tag, [])
)
# Multipliziere mit Anzahl benötigter Personen pro Dienst
benoetigte_dienste_monat *= dienst.personen_anzahl
# F1: Globale Fairness - basierend auf berechneter Zielverteilung
ziel_global = ziel_dienste_global[eltern][dienst]
vorherige_dienste = self.vorherige_dienste[eltern][dienst]
gesamt_dienstfaktor_monat = sum(
sum(self.dienstfaktoren.get(e, {}).get(tag, 0) for tag in self.tage)
for e in self.eltern
)
if ziel_global > 0:
# Tatsächliche Dienste global (Vergangenheit + geplant)
total_dienste_inkl_vergangenheit = tatsaechliche_dienste_monat + vorherige_dienste
if gesamt_dienstfaktor_monat > 0 and benoetigte_dienste_monat > 0:
erwartete_dienste_lokal = (
monatlicher_dienstfaktor / gesamt_dienstfaktor_monat
) * benoetigte_dienste_monat
# F2: Lokale Fairness-Constraints
prob += (tatsaechliche_dienste_monat - erwartete_dienste_lokal <=
fairness_abweichung_lokal[eltern, dienst])
prob += (erwartete_dienste_lokal - tatsaechliche_dienste_monat <=
fairness_abweichung_lokal[eltern, dienst])
# F1: Globale Fairness - basierend auf berechneter Zielverteilung
ziel_gesamt = ziel_dienste[eltern][dienst]
vorherige_dienste = self.vorherige_dienste[eltern][dienst]
if ziel_gesamt > 0:
# Tatsächliche Dienste global (Vergangenheit + geplant)
total_dienste_inkl_vergangenheit = tatsaechliche_dienste_monat + vorherige_dienste
# F1: Globale Fairness-Constraints
prob += (total_dienste_inkl_vergangenheit - ziel_gesamt <=
fairness_abweichung_global[eltern, dienst])
prob += (ziel_gesamt - total_dienste_inkl_vergangenheit <=
fairness_abweichung_global[eltern, dienst])
# Globale Fairness-Constraints
prob += (total_dienste_inkl_vergangenheit - ziel_global <=
fairness_abweichung_global[eltern, dienst])
prob += (ziel_global - total_dienste_inkl_vergangenheit <=
fairness_abweichung_global[eltern, dienst])
# Gewichtung: Jahresanfang F1 stärker, Jahresende F2 stärker
# Annahme: September = Jahresanfang, Juli = Jahresende