From 909e8ff9a0fb51455c98a0e290172a6a6683dbf6 Mon Sep 17 00:00:00 2001 From: Jan Hoheisel Date: Thu, 25 Dec 2025 19:45:36 +0100 Subject: [PATCH] redundante daten der dienstfaktoren entfernt --- csv_io.py | 37 ++++++++--------------- elterndienstplaner.py | 69 +++++++++++++++++++------------------------ 2 files changed, 42 insertions(+), 64 deletions(-) diff --git a/csv_io.py b/csv_io.py index 7fbb338..4b2ed6c 100644 --- a/csv_io.py +++ b/csv_io.py @@ -5,7 +5,7 @@ Trennt CSV-Parsing und -Schreiben von der Business-Logik """ import csv -from datetime import datetime, date +from datetime import datetime, date, timedelta from typing import Dict, List, Tuple, DefaultDict from collections import defaultdict @@ -97,24 +97,20 @@ class EingabeParser: return eltern, tage, benoetigte_dienste, verfügbarkeit, präferenzen @staticmethod - def parse_eltern_csv(datei: str, tage: List[date]) -> Tuple[ - Dict[str, Dict[date, float]], # dienstfaktoren - Dict[str, List[Tuple[date, date, float]]] # alle_zeitraeume - ]: + def parse_eltern_csv(datei: str) -> Dict[str, DefaultDict[date, float]]: """ Lädt die eltern.csv mit Dienstfaktoren Args: datei: Pfad zur eltern.csv - tage: Liste der Planungstage Returns: - Tuple mit (dienstfaktoren, alle_zeitraeume) + Dictionary mit Dienstfaktoren für alle Tage in den definierten Zeiträumen + (DefaultDict gibt 0 für Tage außerhalb der Zeiträume zurück) """ print(f"Lade Elterndaten aus {datei}...") dienstfaktoren = {} - alle_zeitraeume = {} with open(datei, 'r', encoding='utf-8') as f: reader = csv.reader(f) @@ -126,9 +122,8 @@ class EingabeParser: eltern_name = row[0].strip() - # Initialisiere Datenstrukturen - dienstfaktoren[eltern_name] = {} - alle_zeitraeume[eltern_name] = [] + # Initialisiere mit DefaultDict (Standard: 0) + dienstfaktoren[eltern_name] = defaultdict(float) # Alle Zeiträume einlesen (jeweils 3 Spalten: Beginn, Ende, Faktor) for i in range(1, len(row), 3): @@ -138,26 +133,18 @@ class EingabeParser: ende = datetime.strptime(row[i + 1].strip(), '%Y-%m-%d').date() faktor = float(row[i + 2].strip()) if row[i + 2].strip() else 0 - # Zeitraum speichern - alle_zeitraeume[eltern_name].append((beginn, ende, faktor)) - - # Faktor für Tage im aktuellen Planungsmonat setzen - for tag in tage: - if beginn <= tag <= ende: - dienstfaktoren[eltern_name][tag] = faktor + # Faktor für alle Tage im Zeitraum setzen/überschreiben + aktueller_tag = beginn + while aktueller_tag <= ende: + dienstfaktoren[eltern_name][aktueller_tag] = faktor + aktueller_tag += timedelta(days=1) except (ValueError, IndexError): continue - # Tage ohne expliziten Faktor auf 0 setzen - for tag in tage: - if tag not in dienstfaktoren[eltern_name]: - dienstfaktoren[eltern_name][tag] = 0 - print(f"Dienstfaktoren geladen für {len(dienstfaktoren)} Eltern") - print(f"Zeiträume gespeichert für globale Fairness-Berechnung") - return dienstfaktoren, alle_zeitraeume + return dienstfaktoren @staticmethod def parse_vorherige_ausgaben_csv( diff --git a/elterndienstplaner.py b/elterndienstplaner.py index 2ca2bfa..eabbc96 100755 --- a/elterndienstplaner.py +++ b/elterndienstplaner.py @@ -46,7 +46,7 @@ class Elterndienstplaner: ] # Datenstrukturen - self.tage: List[date] = [] + self.planungszeitraum: List[date] = [] self.eltern: List[str] = [] self.benoetigte_dienste: Dict[date, List[Dienst]] = {} self.verfügbarkeit: Dict[Tuple[str, date], bool] = {} @@ -56,7 +56,6 @@ class Elterndienstplaner: # Wenn es eltern nicht gibt -> keyerror # Wenn es tag nicht gibt -> default 0.0 self.dienstfaktoren: Dict[str, DefaultDict[date, float]] = {} - self.alle_zeitraeume: Dict[str, List[Tuple[date, date, float]]] = {} self.historische_dienste: List[Tuple[date, str, Dienst]] = [] def get_dienst(self, kuerzel: str) -> Optional[Dienst]: @@ -80,13 +79,12 @@ class Elterndienstplaner: def lade_eingabe_csv(self, datei: str) -> None: """Lädt die eingabe.csv mit Terminen und Präferenzen""" - self.eltern, self.tage, self.benoetigte_dienste, self.verfügbarkeit, self.präferenzen = \ + self.eltern, self.planungszeitraum, self.benoetigte_dienste, self.verfügbarkeit, self.präferenzen = \ EingabeParser.parse_eingabe_csv(datei, self.get_dienst) def lade_eltern_csv(self, datei: str) -> None: """Lädt die eltern.csv mit Dienstfaktoren""" - self.dienstfaktoren, self.alle_zeitraeume = \ - EingabeParser.parse_eltern_csv(datei, self.tage) + self.dienstfaktoren = EingabeParser.parse_eltern_csv(datei) def lade_vorherige_ausgaben_csv(self, datei: str) -> None: """Lädt vorherige-ausgaben.csv für Fairness-Constraints""" @@ -95,13 +93,9 @@ class Elterndienstplaner: def berechne_dienstfaktor_an_datum(self, eltern: str, datum: date) -> float: """Berechnet den Dienstfaktor eines Elternteils an einem bestimmten Datum""" - if eltern not in self.alle_zeitraeume: + if eltern not in self.dienstfaktoren: return 0 - - for beginn, ende, faktor in self.alle_zeitraeume[eltern]: - if beginn <= datum <= ende: - return faktor - return 0 + return self.dienstfaktoren[eltern][datum] # DefaultDict gibt 0 zurück für unbekannte Tage def berechne_faire_zielverteilung_global(self) -> DefaultDict[str, DefaultDict[Dienst, float]]: """Berechnet die faire Zielanzahl von Diensten für den Planungszeitraum @@ -137,35 +131,32 @@ class Elterndienstplaner: dienste_pro_tag[datum].append(eltern) # Für jeden historischen Tag faire Umverteilung berechnen - for hist_datum, geleistete_eltern in dienste_pro_tag.items(): + for tag, geleistete_eltern in dienste_pro_tag.items(): anzahl_dienste = len(geleistete_eltern) # Anzahl Dienste an diesem Tag # Dienstfaktoren aller Eltern für diesen historischen Tag berechnen - dienstfaktoren_tag = {} gesamt_dienstfaktor_tag = 0 for eltern in self.eltern: - faktor = self.berechne_dienstfaktor_an_datum(eltern, hist_datum) - dienstfaktoren_tag[eltern] = faktor - gesamt_dienstfaktor_tag += faktor + gesamt_dienstfaktor_tag += self.dienstfaktoren[eltern][tag] # Faire Umverteilung der an diesem Tag geleisteten Dienste if gesamt_dienstfaktor_tag > 0: for eltern in self.eltern: - if dienstfaktoren_tag[eltern] > 0: - anteil = dienstfaktoren_tag[eltern] / gesamt_dienstfaktor_tag + if self.dienstfaktoren[eltern][tag] > 0: + anteil = self.dienstfaktoren[eltern][tag] / gesamt_dienstfaktor_tag faire_zuteilung = anteil * anzahl_dienste ziel_dienste[eltern][dienst] += faire_zuteilung if faire_zuteilung > 0.01: # Debug nur für relevante Werte - print(f" {hist_datum}: {eltern} Faktor={dienstfaktoren_tag[eltern]} " + print(f" {tag}: {eltern} Faktor={self.dienstfaktoren[eltern][tag]} " f"-> {faire_zuteilung:.2f} von {anzahl_dienste} Diensten") # 2. AKTUELLER MONAT: Faire Verteilung der benötigten Dienste (tageweise wie bei historischen Diensten) benoetigte_dienste_monat = 0 # Für jeden Tag im aktuellen Monat faire Umverteilung berechnen - for tag in self.tage: + for tag in self.planungszeitraum: # Prüfe ob an diesem Tag der Dienst benötigt wird if dienst not in self.benoetigte_dienste.get(tag, []): continue @@ -173,18 +164,18 @@ class Elterndienstplaner: benoetigte_dienste_monat += dienst.personen_anzahl # Dienstfaktoren aller Eltern für diesen Tag berechnen - dienstfaktoren_tag = {} + dienstfaktoren = {} gesamt_dienstfaktor_tag = 0 for eltern in self.eltern: faktor = self.dienstfaktoren[eltern][tag] - dienstfaktoren_tag[eltern] = faktor + dienstfaktoren[eltern] = faktor gesamt_dienstfaktor_tag += faktor # Faire Umverteilung der an diesem Tag benötigten Dienste if gesamt_dienstfaktor_tag > 0: for eltern in self.eltern: - anteil = dienstfaktoren_tag[eltern] / gesamt_dienstfaktor_tag + anteil = dienstfaktoren[eltern] / gesamt_dienstfaktor_tag faire_zuteilung = anteil * dienst.personen_anzahl ziel_dienste[eltern][dienst] += faire_zuteilung @@ -211,7 +202,7 @@ class Elterndienstplaner: # Gesamtdienstfaktor für aktuellen Monat berechnen gesamt_dienstfaktor_monat = sum( - sum(self.dienstfaktoren[e][tag] for tag in self.tage) + sum(self.dienstfaktoren[e][tag] for tag in self.planungszeitraum) for e in self.eltern ) @@ -223,7 +214,7 @@ class Elterndienstplaner: for dienst in self.dienste: # Anzahl benötigter Dienste im aktuellen Monat benoetigte_dienste_monat = sum( - 1 for tag in self.tage + 1 for tag in self.planungszeitraum if dienst in self.benoetigte_dienste.get(tag, []) ) # Multipliziere mit Anzahl benötigter Personen pro Dienst @@ -235,7 +226,7 @@ class Elterndienstplaner: for eltern in self.eltern: # Dienstfaktor für diesen Elternteil im aktuellen Monat monatlicher_dienstfaktor = sum( - self.dienstfaktoren[eltern][tag] for tag in self.tage + self.dienstfaktoren[eltern][tag] for tag in self.planungszeitraum ) if monatlicher_dienstfaktor > 0: @@ -249,7 +240,7 @@ class Elterndienstplaner: """Erstellt die binären Entscheidungsvariablen x[eltern, tag, dienst]""" x: Dict[Tuple[str, date, Dienst], pulp.LpVariable] = {} for eltern in self.eltern: - for tag in self.tage: + for tag in self.planungszeitraum: for dienst in self.dienste: if dienst in self.benoetigte_dienste.get(tag, []): x[eltern, tag, dienst] = pulp.LpVariable( @@ -264,13 +255,13 @@ class Elterndienstplaner: x: Dict[Tuple[str, date, Dienst], pulp.LpVariable] ) -> None: """C1: Je Eltern und Dienst nur einmal die Woche (Woche = Montag bis Sonntag)""" - erster_tag = self.tage[0] + erster_tag = self.planungszeitraum[0] # weekday(): 0=Montag, 6=Sonntag # Finde Montag am oder vor dem ersten Planungstag (für historische Dienste) woche_start = erster_tag - timedelta(days=erster_tag.weekday()) woche_nr = 0 - letzter_tag = self.tage[-1] + letzter_tag = self.planungszeitraum[-1] while woche_start <= letzter_tag: woche_ende = woche_start + timedelta(days=6) # Sonntag @@ -289,7 +280,7 @@ class Elterndienstplaner: historische_dienste_in_woche += 1 # Sammle Variablen für Planungszeitraum in dieser Woche - for tag in self.tage: + for tag in self.planungszeitraum: if woche_start <= tag <= woche_ende: if (eltern, tag, dienst) in x: woche_vars.append(x[eltern, tag, dienst]) @@ -309,7 +300,7 @@ class Elterndienstplaner: ) -> None: """C2: Je Eltern nur einen Dienst am Tag""" for eltern in self.eltern: - for tag in self.tage: + for tag in self.planungszeitraum: tag_vars = [] for dienst in self.dienste: if (eltern, tag, dienst) in x: @@ -325,7 +316,7 @@ class Elterndienstplaner: ) -> None: """C3: Dienste nur verfügbaren Eltern zuteilen""" for eltern in self.eltern: - for tag in self.tage: + for tag in self.planungszeitraum: if not self.verfügbarkeit.get((eltern, tag), True): for dienst in self.dienste: if (eltern, tag, dienst) in x: @@ -338,7 +329,7 @@ class Elterndienstplaner: x: Dict[Tuple[str, date, Dienst], pulp.LpVariable] ) -> None: """C4: Alle benötigten Dienste müssen zugeteilt werden""" - for tag in self.tage: + for tag in self.planungszeitraum: for dienst in self.benoetigte_dienste.get(tag, []): dienst_vars = [] for eltern in self.eltern: @@ -386,7 +377,7 @@ class Elterndienstplaner: # Tatsächliche Dienste im aktuellen Monat tatsaechliche_dienste_monat = pulp.lpSum( x[eltern, tag, dienst] - for tag in self.tage + for tag in self.planungszeitraum if (eltern, tag, dienst) in x ) @@ -431,7 +422,7 @@ class Elterndienstplaner: # Tatsächliche Gesamtdienste für diesen Elternteil tatsaechliche_dienste_gesamt = pulp.lpSum( x[eltern, tag, dienst] - for tag in self.tage + for tag in self.planungszeitraum for dienst in self.dienste if (eltern, tag, dienst) in x ) @@ -449,7 +440,7 @@ class Elterndienstplaner: def _berechne_fairness_gewichte(self) -> Tuple[int, int, int, int]: """Berechnet Gewichtung basierend auf Jahreszeit (Sep-Jul Schuljahr)""" - aktueller_monat = self.tage[0].month if self.tage else 1 + aktueller_monat = self.planungszeitraum[0].month if self.planungszeitraum else 1 if 9 <= aktueller_monat <= 12: # Sep-Dez: Jahresanfang gewicht_f1 = 100 # Global wichtiger @@ -534,7 +525,7 @@ class Elterndienstplaner: # Debugging: Verfügbarkeit prüfen print("\nDebug: Verfügbarkeit analysieren...") - for tag in self.tage[:5]: # Erste 5 Tage + for tag in self.planungszeitraum[:5]: # Erste 5 Tage verfügbare = [e for e in self.eltern if self.verfügbarkeit.get((e, tag), True)] benötigte = self.benoetigte_dienste.get(tag, []) print(f" {tag}: Benötigt {len(benötigte)} Dienste {benötigte}, verfügbar: {verfügbare}") @@ -623,7 +614,7 @@ class Elterndienstplaner: def schreibe_ausgabe_csv(self, datei: str, lösung: Dict[date, Dict[Dienst, List[str]]]) -> None: """Schreibt die Lösung in die ausgabe.csv""" - AusgabeWriter.schreibe_ausgabe_csv(datei, lösung, self.tage, self.dienste) + AusgabeWriter.schreibe_ausgabe_csv(datei, lösung, self.planungszeitraum, self.dienste) def drucke_statistiken(self, lösung: Dict[date, Dict[Dienst, List[str]]]) -> None: """Druckt Statistiken zur Lösung""" @@ -649,7 +640,7 @@ class Elterndienstplaner: # Dienstfaktor-Analyse print(f"\nDienstfaktoren im Planungszeitraum:") for eltern in sorted(self.eltern): - faktor_summe = sum(self.dienstfaktoren[eltern][tag] for tag in self.tage) + faktor_summe = sum(self.dienstfaktoren[eltern][tag] for tag in self.planungszeitraum) print(f" {eltern:15} {faktor_summe:.1f}") def visualisiere_praeferenz_verletzungen(