From 2d3f49539cc4b80471ee4ef0c04b0b7c2c9973a7 Mon Sep 17 00:00:00 2001 From: Jan Hoheisel Date: Tue, 23 Dec 2025 21:44:05 +0100 Subject: [PATCH] refactoring: dienste in klasse --- elterndienstplaner.py | 114 +++++++++++++++++++++++++++++------------- 1 file changed, 79 insertions(+), 35 deletions(-) diff --git a/elterndienstplaner.py b/elterndienstplaner.py index 0e1860b..f57d7a6 100755 --- a/elterndienstplaner.py +++ b/elterndienstplaner.py @@ -13,21 +13,41 @@ from datetime import datetime, timedelta from collections import defaultdict 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: def __init__(self): - self.dienste = ['F', 'P', 'E', 'K', 'A'] # Frühstück, Putz, Essen, Kochen, Elternabend - self.dienst_namen = { - 'F': 'Frühstücksdienst', - 'P': 'Putznotdienst', - 'E': 'Essensausgabenotdienst', - 'K': 'Kochen', - 'A': 'Elternabend' - } + # Dienste als Liste definieren + self.dienste = [ + Dienst('F', 'Frühstücksdienst', 1), + Dienst('P', 'Putznotdienst', 1), + Dienst('E', 'Essensausgabenotdienst', 1), + Dienst('K', 'Kochen', 1), + Dienst('A', 'Elternabend', 2) + ] # Datenstrukturen self.tage = [] self.eltern = [] - self.benoetigte_dienste = {} # {datum: [dienste]} + self.benoetigte_dienste = {} # {datum: [dienst_objekte]} self.verfügbarkeit = {} # {(eltern, datum): bool} self.präferenzen = {} # {(eltern, datum, dienst): 1 (bevorzugt) oder -1 (abgelehnt)} self.dienstfaktoren = {} # {eltern: {datum: faktor}} @@ -35,6 +55,25 @@ class Elterndienstplaner: self.vorherige_dienste = defaultdict(lambda: defaultdict(int)) # {eltern: {dienst: anzahl}} 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): """Lädt die eingabe.csv mit Terminen und Präferenzen""" print(f"Lade Eingabedaten aus {datei}...") @@ -64,7 +103,12 @@ class Elterndienstplaner: continue # 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 for i, eltern_name in enumerate(self.eltern): @@ -90,9 +134,9 @@ class Elterndienstplaner: """Parst Präferenzstring wie 'F+P-E+' """ i = 0 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] == '+': self.präferenzen[(eltern, datum, dienst)] = 1 i += 2 @@ -163,9 +207,9 @@ class Elterndienstplaner: # Dienst-Spalten finden dienst_spalten = {} 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(): - if dienst_name.lower() in col_name.lower() or dienst_kürzel == col_name: - dienst_spalten[dienst_kürzel] = i + for dienst in self.dienste: + if dienst.name.lower() in col_name.lower() or dienst.kuerzel == col_name: + dienst_spalten[dienst] = i break for row in reader: @@ -219,7 +263,7 @@ class Elterndienstplaner: print(f" Analysiere {len(historische_tage)} historische Tage mit {len(self.historische_dienste)} Diensten") 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 historische_dienste_dieses_typs = [ @@ -227,7 +271,7 @@ class Elterndienstplaner: 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 dienste_pro_tag = defaultdict(list) @@ -265,8 +309,8 @@ class Elterndienstplaner: if dienst in self.benoetigte_dienste.get(tag, []) ) - if dienst == 'A': - benoetigte_dienste_monat *= 2 + # Multipliziere mit Anzahl benötigter Personen pro Dienst + benoetigte_dienste_monat *= dienst.personen_anzahl if benoetigte_dienste_monat > 0: # Gesamtdienstfaktor für aktuellen Monat @@ -295,7 +339,7 @@ class Elterndienstplaner: ) for e in self.eltern ) 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}") # Debug-Output: Detaillierte Zielverteilung @@ -305,7 +349,7 @@ class Elterndienstplaner: if ziel_dienste[eltern][dienst] > 0.1: # Nur relevante Werte ist = self.vorherige_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 @@ -330,7 +374,7 @@ class Elterndienstplaner: for dienst in self.dienste: if dienst in self.benoetigte_dienste.get(tag, []): x[eltern, tag, dienst] = pulp.LpVariable( - f"x_{eltern.replace(' ', '_')}_{tag}_{dienst}", + f"x_{eltern.replace(' ', '_')}_{tag}_{dienst.kuerzel}", cat='Binary' ) @@ -351,7 +395,7 @@ class Elterndienstplaner: woche_vars.append(x[eltern, tag, dienst]) 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_nr += 1 @@ -373,7 +417,7 @@ class Elterndienstplaner: if not self.verfügbarkeit.get((eltern, tag), True): for dienst in self.dienste: 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) for tag in self.tage: @@ -388,9 +432,9 @@ class Elterndienstplaner: verfuegbare_eltern += 1 if dienst_vars: - # Genau 1 Person pro Dienst (außer Elternabend: genau 2) - benoetigte_personen = 2 if dienst == 'A' else 1 - prob += pulp.lpSum(dienst_vars) == benoetigte_personen, f"Bedarf_{tag}_{dienst}" + # Anzahl benötigter Personen pro Dienst (aus Dienst-Objekt) + benoetigte_personen = dienst.personen_anzahl + prob += pulp.lpSum(dienst_vars) == benoetigte_personen, f"Bedarf_{tag}_{dienst.kuerzel}" # FAIRNESS-CONSTRAINTS UND ZIELFUNKTION objective_terms = [] @@ -405,9 +449,9 @@ class Elterndienstplaner: for eltern in self.eltern: for dienst in self.dienste: 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( - f"fair_global_{eltern.replace(' ', '_')}_{dienst}", lowBound=0) + f"fair_global_{eltern.replace(' ', '_')}_{dienst.kuerzel}", lowBound=0) # F1: Globale Fairness & F2: Lokale Fairness for eltern in self.eltern: @@ -430,8 +474,8 @@ class Elterndienstplaner: 1 for tag in self.tage if dienst in self.benoetigte_dienste.get(tag, []) ) - if dienst == 'A': - benoetigte_dienste_monat *= 2 + # Multipliziere mit Anzahl benötigter Personen pro Dienst + benoetigte_dienste_monat *= dienst.personen_anzahl gesamt_dienstfaktor_monat = sum( sum(self.dienstfaktoren.get(e, {}).get(tag, 0) for tag in self.tage) @@ -549,7 +593,7 @@ class Elterndienstplaner: writer = csv.writer(f) # 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) # Daten schreiben @@ -586,8 +630,8 @@ class Elterndienstplaner: print("\nDienste pro Eltern:") for eltern in sorted(self.eltern): gesamt = sum(dienste_pro_eltern[eltern].values()) - dienste_detail = ', '.join(f"{d}:{dienste_pro_eltern[eltern][d]}" - for d in self.dienste if dienste_pro_eltern[eltern][d] > 0) + dienste_detail = ', '.join(f"{dienst.kuerzel}:{dienste_pro_eltern[eltern][dienst]}" + for dienst in self.dienste if dienste_pro_eltern[eltern][dienst] > 0) print(f" {eltern:15} {gesamt:3d} ({dienste_detail})") # Dienstfaktor-Analyse