refactoring: lokale fairness

This commit is contained in:
Jan Hoheisel 2025-12-23 22:14:17 +01:00
parent 03d1c362f1
commit 9588e75ee0

View File

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