refactoring: dienste in klasse

This commit is contained in:
Jan Hoheisel 2025-12-23 21:44:05 +01:00
parent 182b7d1aff
commit 2d3f49539c

View File

@ -13,21 +13,41 @@ from datetime import datetime, timedelta
from collections import defaultdict from collections import defaultdict
import calendar import calendar
class Dienst:
"""Repräsentiert einen Diensttyp mit allen seinen Eigenschaften"""
def __init__(self, kuerzel, name, personen_anzahl=1):
self.kuerzel = kuerzel
self.name = name
self.personen_anzahl = personen_anzahl
def __str__(self):
return f"{self.kuerzel} ({self.name}): {self.personen_anzahl} Person(en)"
def __repr__(self):
return f"Dienst('{self.kuerzel}', '{self.name}', {self.personen_anzahl})"
def braucht_mehrere_personen(self):
"""Gibt True zurück, wenn mehr als eine Person benötigt wird"""
return self.personen_anzahl > 1
class Elterndienstplaner: class Elterndienstplaner:
def __init__(self): def __init__(self):
self.dienste = ['F', 'P', 'E', 'K', 'A'] # Frühstück, Putz, Essen, Kochen, Elternabend # Dienste als Liste definieren
self.dienst_namen = { self.dienste = [
'F': 'Frühstücksdienst', Dienst('F', 'Frühstücksdienst', 1),
'P': 'Putznotdienst', Dienst('P', 'Putznotdienst', 1),
'E': 'Essensausgabenotdienst', Dienst('E', 'Essensausgabenotdienst', 1),
'K': 'Kochen', Dienst('K', 'Kochen', 1),
'A': 'Elternabend' Dienst('A', 'Elternabend', 2)
} ]
# Datenstrukturen # Datenstrukturen
self.tage = [] self.tage = []
self.eltern = [] self.eltern = []
self.benoetigte_dienste = {} # {datum: [dienste]} self.benoetigte_dienste = {} # {datum: [dienst_objekte]}
self.verfügbarkeit = {} # {(eltern, datum): bool} self.verfügbarkeit = {} # {(eltern, datum): bool}
self.präferenzen = {} # {(eltern, datum, dienst): 1 (bevorzugt) oder -1 (abgelehnt)} self.präferenzen = {} # {(eltern, datum, dienst): 1 (bevorzugt) oder -1 (abgelehnt)}
self.dienstfaktoren = {} # {eltern: {datum: faktor}} self.dienstfaktoren = {} # {eltern: {datum: faktor}}
@ -35,6 +55,25 @@ class Elterndienstplaner:
self.vorherige_dienste = defaultdict(lambda: defaultdict(int)) # {eltern: {dienst: anzahl}} self.vorherige_dienste = defaultdict(lambda: defaultdict(int)) # {eltern: {dienst: anzahl}}
self.historische_dienste = [] # [(datum, eltern, dienst), ...] - Alle historischen Dienste mit Datum self.historische_dienste = [] # [(datum, eltern, dienst), ...] - Alle historischen Dienste mit Datum
def get_dienst(self, kuerzel):
"""Gibt das Dienst-Objekt für ein Kürzel zurück"""
for dienst in self.dienste:
if dienst.kuerzel == kuerzel:
return dienst
return None
def add_dienst(self, kuerzel, name, personen_anzahl=1):
"""Fügt einen neuen Dienst hinzu"""
dienst = Dienst(kuerzel, name, personen_anzahl)
self.dienste.append(dienst)
return dienst
def print_dienste_info(self):
"""Druckt Informationen über alle konfigurierten Dienste"""
print("Konfigurierte Dienste:")
for dienst in self.dienste:
print(f" {dienst}")
def lade_eingabe_csv(self, datei): def lade_eingabe_csv(self, datei):
"""Lädt die eingabe.csv mit Terminen und Präferenzen""" """Lädt die eingabe.csv mit Terminen und Präferenzen"""
print(f"Lade Eingabedaten aus {datei}...") print(f"Lade Eingabedaten aus {datei}...")
@ -64,7 +103,12 @@ class Elterndienstplaner:
continue continue
# Benötigte Dienste # Benötigte Dienste
self.benoetigte_dienste[datum_obj] = list(dienste_str) dienst_objekte = []
for kuerzel in dienste_str:
dienst = self.get_dienst(kuerzel)
if dienst:
dienst_objekte.append(dienst)
self.benoetigte_dienste[datum_obj] = dienst_objekte
# Verfügbarkeit und Präferenzen der Eltern # Verfügbarkeit und Präferenzen der Eltern
for i, eltern_name in enumerate(self.eltern): for i, eltern_name in enumerate(self.eltern):
@ -90,9 +134,9 @@ class Elterndienstplaner:
"""Parst Präferenzstring wie 'F+P-E+' """ """Parst Präferenzstring wie 'F+P-E+' """
i = 0 i = 0
while i < len(präf_str): while i < len(präf_str):
if i + 1 < len(präf_str) and präf_str[i] in self.dienste:
dienst = präf_str[i]
if i + 1 < len(präf_str): if i + 1 < len(präf_str):
dienst = self.get_dienst(präf_str[i])
if dienst and i + 1 < len(präf_str):
if präf_str[i + 1] == '+': if präf_str[i + 1] == '+':
self.präferenzen[(eltern, datum, dienst)] = 1 self.präferenzen[(eltern, datum, dienst)] = 1
i += 2 i += 2
@ -163,9 +207,9 @@ class Elterndienstplaner:
# Dienst-Spalten finden # Dienst-Spalten finden
dienst_spalten = {} dienst_spalten = {}
for i, col_name in enumerate(header[2:], 2): # Ab Spalte 2 (nach Datum, Wochentag) for i, col_name in enumerate(header[2:], 2): # Ab Spalte 2 (nach Datum, Wochentag)
for dienst_kürzel, dienst_name in self.dienst_namen.items(): for dienst in self.dienste:
if dienst_name.lower() in col_name.lower() or dienst_kürzel == col_name: if dienst.name.lower() in col_name.lower() or dienst.kuerzel == col_name:
dienst_spalten[dienst_kürzel] = i dienst_spalten[dienst] = i
break break
for row in reader: for row in reader:
@ -219,7 +263,7 @@ class Elterndienstplaner:
print(f" Analysiere {len(historische_tage)} historische Tage mit {len(self.historische_dienste)} Diensten") print(f" Analysiere {len(historische_tage)} historische Tage mit {len(self.historische_dienste)} Diensten")
for dienst in self.dienste: for dienst in self.dienste:
print(f" Verarbeite Dienst {dienst}...") print(f" Verarbeite Dienst {dienst.kuerzel}...")
# 1. HISTORISCHE PERIODE: Faire Umverteilung der tatsächlich geleisteten Dienste # 1. HISTORISCHE PERIODE: Faire Umverteilung der tatsächlich geleisteten Dienste
historische_dienste_dieses_typs = [ historische_dienste_dieses_typs = [
@ -227,7 +271,7 @@ class Elterndienstplaner:
if d == dienst if d == dienst
] ]
print(f" Gefundene historische {dienst}-Dienste: {len(historische_dienste_dieses_typs)}") print(f" Gefundene historische {dienst.kuerzel}-Dienste: {len(historische_dienste_dieses_typs)}")
# Gruppiere nach Datum # Gruppiere nach Datum
dienste_pro_tag = defaultdict(list) dienste_pro_tag = defaultdict(list)
@ -265,8 +309,8 @@ class Elterndienstplaner:
if dienst in self.benoetigte_dienste.get(tag, []) if dienst in self.benoetigte_dienste.get(tag, [])
) )
if dienst == 'A': # Multipliziere mit Anzahl benötigter Personen pro Dienst
benoetigte_dienste_monat *= 2 benoetigte_dienste_monat *= dienst.personen_anzahl
if benoetigte_dienste_monat > 0: if benoetigte_dienste_monat > 0:
# Gesamtdienstfaktor für aktuellen Monat # Gesamtdienstfaktor für aktuellen Monat
@ -295,7 +339,7 @@ class Elterndienstplaner:
) for e in self.eltern ) for e in self.eltern
) if len(historische_dienste_dieses_typs) > 0 else 0 ) if len(historische_dienste_dieses_typs) > 0 else 0
print(f" {dienst}: Historisch faire Summe={total_historisch:.1f}, " print(f" {dienst.kuerzel}: Historisch faire Summe={total_historisch:.1f}, "
f"Aktuell benötigt={benoetigte_dienste_monat}") f"Aktuell benötigt={benoetigte_dienste_monat}")
# Debug-Output: Detaillierte Zielverteilung # Debug-Output: Detaillierte Zielverteilung
@ -305,7 +349,7 @@ class Elterndienstplaner:
if ziel_dienste[eltern][dienst] > 0.1: # Nur relevante Werte if ziel_dienste[eltern][dienst] > 0.1: # Nur relevante Werte
ist = self.vorherige_dienste[eltern][dienst] ist = self.vorherige_dienste[eltern][dienst]
ziel = ziel_dienste[eltern][dienst] ziel = ziel_dienste[eltern][dienst]
print(f" {eltern} {dienst}: IST={ist}, FAIRE_ZIEL={ziel:.2f}, DIFF={ziel-ist:.2f}") print(f" {eltern} {dienst.kuerzel}: IST={ist}, FAIRE_ZIEL={ziel:.2f}, DIFF={ziel-ist:.2f}")
return ziel_dienste return ziel_dienste
@ -330,7 +374,7 @@ class Elterndienstplaner:
for dienst in self.dienste: for dienst in self.dienste:
if dienst in self.benoetigte_dienste.get(tag, []): if dienst in self.benoetigte_dienste.get(tag, []):
x[eltern, tag, dienst] = pulp.LpVariable( x[eltern, tag, dienst] = pulp.LpVariable(
f"x_{eltern.replace(' ', '_')}_{tag}_{dienst}", f"x_{eltern.replace(' ', '_')}_{tag}_{dienst.kuerzel}",
cat='Binary' cat='Binary'
) )
@ -351,7 +395,7 @@ class Elterndienstplaner:
woche_vars.append(x[eltern, tag, dienst]) woche_vars.append(x[eltern, tag, dienst])
if woche_vars: if woche_vars:
prob += pulp.lpSum(woche_vars) <= 1, f"C1_{eltern.replace(' ', '_')}_{dienst}_w{woche_nr}" prob += pulp.lpSum(woche_vars) <= 1, f"C1_{eltern.replace(' ', '_')}_{dienst.kuerzel}_w{woche_nr}"
woche_start += timedelta(days=7) woche_start += timedelta(days=7)
woche_nr += 1 woche_nr += 1
@ -373,7 +417,7 @@ class Elterndienstplaner:
if not self.verfügbarkeit.get((eltern, tag), True): if not self.verfügbarkeit.get((eltern, tag), True):
for dienst in self.dienste: for dienst in self.dienste:
if (eltern, tag, dienst) in x: if (eltern, tag, dienst) in x:
prob += x[eltern, tag, dienst] == 0, f"C3_{eltern.replace(' ', '_')}_{tag}_{dienst}" prob += x[eltern, tag, dienst] == 0, f"C3_{eltern.replace(' ', '_')}_{tag}_{dienst.kuerzel}"
# Alle benötigten Dienste müssen zugeteilt werden (flexibel) # Alle benötigten Dienste müssen zugeteilt werden (flexibel)
for tag in self.tage: for tag in self.tage:
@ -388,9 +432,9 @@ class Elterndienstplaner:
verfuegbare_eltern += 1 verfuegbare_eltern += 1
if dienst_vars: if dienst_vars:
# Genau 1 Person pro Dienst (außer Elternabend: genau 2) # Anzahl benötigter Personen pro Dienst (aus Dienst-Objekt)
benoetigte_personen = 2 if dienst == 'A' else 1 benoetigte_personen = dienst.personen_anzahl
prob += pulp.lpSum(dienst_vars) == benoetigte_personen, f"Bedarf_{tag}_{dienst}" prob += pulp.lpSum(dienst_vars) == benoetigte_personen, f"Bedarf_{tag}_{dienst.kuerzel}"
# FAIRNESS-CONSTRAINTS UND ZIELFUNKTION # FAIRNESS-CONSTRAINTS UND ZIELFUNKTION
objective_terms = [] objective_terms = []
@ -405,9 +449,9 @@ class Elterndienstplaner:
for eltern in self.eltern: for eltern in self.eltern:
for dienst in self.dienste: for dienst in self.dienste:
fairness_abweichung_lokal[eltern, dienst] = pulp.LpVariable( fairness_abweichung_lokal[eltern, dienst] = pulp.LpVariable(
f"fair_lokal_{eltern.replace(' ', '_')}_{dienst}", lowBound=0) f"fair_lokal_{eltern.replace(' ', '_')}_{dienst.kuerzel}", lowBound=0)
fairness_abweichung_global[eltern, dienst] = pulp.LpVariable( fairness_abweichung_global[eltern, dienst] = pulp.LpVariable(
f"fair_global_{eltern.replace(' ', '_')}_{dienst}", lowBound=0) f"fair_global_{eltern.replace(' ', '_')}_{dienst.kuerzel}", lowBound=0)
# F1: Globale Fairness & F2: Lokale Fairness # F1: Globale Fairness & F2: Lokale Fairness
for eltern in self.eltern: for eltern in self.eltern:
@ -430,8 +474,8 @@ class Elterndienstplaner:
1 for tag in self.tage 1 for tag in self.tage
if dienst in self.benoetigte_dienste.get(tag, []) if dienst in self.benoetigte_dienste.get(tag, [])
) )
if dienst == 'A': # Multipliziere mit Anzahl benötigter Personen pro Dienst
benoetigte_dienste_monat *= 2 benoetigte_dienste_monat *= dienst.personen_anzahl
gesamt_dienstfaktor_monat = sum( gesamt_dienstfaktor_monat = sum(
sum(self.dienstfaktoren.get(e, {}).get(tag, 0) for tag in self.tage) sum(self.dienstfaktoren.get(e, {}).get(tag, 0) for tag in self.tage)
@ -549,7 +593,7 @@ class Elterndienstplaner:
writer = csv.writer(f) writer = csv.writer(f)
# Header schreiben # Header schreiben
header = ['Datum', 'Wochentag'] + [self.dienst_namen[d] for d in self.dienste] header = ['Datum', 'Wochentag'] + [dienst.name for dienst in self.dienste]
writer.writerow(header) writer.writerow(header)
# Daten schreiben # Daten schreiben
@ -586,8 +630,8 @@ class Elterndienstplaner:
print("\nDienste pro Eltern:") print("\nDienste pro Eltern:")
for eltern in sorted(self.eltern): for eltern in sorted(self.eltern):
gesamt = sum(dienste_pro_eltern[eltern].values()) gesamt = sum(dienste_pro_eltern[eltern].values())
dienste_detail = ', '.join(f"{d}:{dienste_pro_eltern[eltern][d]}" dienste_detail = ', '.join(f"{dienst.kuerzel}:{dienste_pro_eltern[eltern][dienst]}"
for d in self.dienste if dienste_pro_eltern[eltern][d] > 0) for dienst in self.dienste if dienste_pro_eltern[eltern][dienst] > 0)
print(f" {eltern:15} {gesamt:3d} ({dienste_detail})") print(f" {eltern:15} {gesamt:3d} ({dienste_detail})")
# Dienstfaktor-Analyse # Dienstfaktor-Analyse