From fefbc3d7b27ef93d259b4c9137dcf76841ceb26f Mon Sep 17 00:00:00 2001 From: Jan Hoheisel Date: Tue, 23 Dec 2025 22:00:29 +0100 Subject: [PATCH] refactoring: type hints --- elterndienstplaner.py | 76 +++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 36 deletions(-) diff --git a/elterndienstplaner.py b/elterndienstplaner.py index f57d7a6..d65c47a 100755 --- a/elterndienstplaner.py +++ b/elterndienstplaner.py @@ -9,34 +9,35 @@ Datum: Dezember 2025 import sys import csv import pulp -from datetime import datetime, timedelta +from datetime import datetime, timedelta, date from collections import defaultdict +from typing import Dict, List, Tuple, DefaultDict, Optional 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 __init__(self, kuerzel: str, name: str, personen_anzahl: int = 1) -> None: + self.kuerzel: str = kuerzel + self.name: str = name + self.personen_anzahl: int = personen_anzahl - def __str__(self): + def __str__(self) -> str: return f"{self.kuerzel} ({self.name}): {self.personen_anzahl} Person(en)" - def __repr__(self): + def __repr__(self) -> str: return f"Dienst('{self.kuerzel}', '{self.name}', {self.personen_anzahl})" - def braucht_mehrere_personen(self): + def braucht_mehrere_personen(self) -> bool: """Gibt True zurück, wenn mehr als eine Person benötigt wird""" return self.personen_anzahl > 1 class Elterndienstplaner: - def __init__(self): + def __init__(self) -> None: # Dienste als Liste definieren - self.dienste = [ + self.dienste: List[Dienst] = [ Dienst('F', 'Frühstücksdienst', 1), Dienst('P', 'Putznotdienst', 1), Dienst('E', 'Essensausgabenotdienst', 1), @@ -45,36 +46,37 @@ class Elterndienstplaner: ] # Datenstrukturen - self.tage = [] - self.eltern = [] - 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}} - self.alle_zeitraeume = {} # {eltern: [(beginn, ende, faktor), ...]} - ALLE Zeiträume - self.vorherige_dienste = defaultdict(lambda: defaultdict(int)) # {eltern: {dienst: anzahl}} - self.historische_dienste = [] # [(datum, eltern, dienst), ...] - Alle historischen Dienste mit Datum + self.tage: List[date] = [] + self.eltern: List[str] = [] + self.benoetigte_dienste: Dict[date, List[Dienst]] = {} + self.verfügbarkeit: Dict[Tuple[str, date], bool] = {} + self.präferenzen: Dict[Tuple[str, date, Dienst], int] = {} + self.dienstfaktoren: Dict[str, Dict[date, float]] = {} + self.alle_zeitraeume: Dict[str, List[Tuple[date, date, float]]] = {} + self.vorherige_dienste: DefaultDict[str, DefaultDict[Dienst, int]] = \ + defaultdict(lambda: defaultdict(int)) + self.historische_dienste: List[Tuple[date, str, Dienst]] = [] - def get_dienst(self, kuerzel): + def get_dienst(self, kuerzel: str) -> Optional[Dienst]: """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): + def add_dienst(self, kuerzel: str, name: str, personen_anzahl: int = 1) -> Dienst: """Fügt einen neuen Dienst hinzu""" dienst = Dienst(kuerzel, name, personen_anzahl) self.dienste.append(dienst) return dienst - def print_dienste_info(self): + def print_dienste_info(self) -> None: """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: str) -> None: """Lädt die eingabe.csv mit Terminen und Präferenzen""" print(f"Lade Eingabedaten aus {datei}...") @@ -130,7 +132,7 @@ class Elterndienstplaner: self.tage.sort() print(f"Zeitraum: {self.tage[0]} bis {self.tage[-1]} ({len(self.tage)} Tage)") - def _parse_präferenzen(self, eltern, datum, präf_str): + def _parse_präferenzen(self, eltern: str, datum: date, präf_str: str) -> None: """Parst Präferenzstring wie 'F+P-E+' """ i = 0 while i < len(präf_str): @@ -150,7 +152,7 @@ class Elterndienstplaner: else: i += 1 - def lade_eltern_csv(self, datei): + def lade_eltern_csv(self, datei: str) -> None: """Lädt die eltern.csv mit Dienstfaktoren""" print(f"Lade Elterndaten aus {datei}...") @@ -195,7 +197,7 @@ class Elterndienstplaner: print(f"Dienstfaktoren geladen für {len(self.dienstfaktoren)} Eltern") print(f"Zeiträume gespeichert für globale Fairness-Berechnung") - def lade_vorherige_ausgaben_csv(self, datei): + def lade_vorherige_ausgaben_csv(self, datei: str) -> None: """Lädt vorherige-ausgaben.csv für Fairness-Constraints""" print(f"Lade vorherige Ausgaben aus {datei}...") @@ -240,7 +242,7 @@ class Elterndienstplaner: print(f"Vorherige Dienste geladen: {dict(self.vorherige_dienste)}") print(f"Historische Dienste mit Datum: {len(self.historische_dienste)} Einträge") - def berechne_dienstfaktor_an_datum(self, eltern, datum): + 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: return 0 @@ -250,11 +252,12 @@ class Elterndienstplaner: return faktor return 0 - def berechne_faire_zielverteilung(self): + def berechne_faire_zielverteilung(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""" - ziel_dienste = defaultdict(lambda: defaultdict(float)) # {eltern: {dienst: anzahl}} + ziel_dienste: DefaultDict[str, DefaultDict[Dienst, float]] = \ + defaultdict(lambda: defaultdict(float)) print("\nBerechne faire Zielverteilung basierend auf historischen Daten...") @@ -353,7 +356,7 @@ class Elterndienstplaner: return ziel_dienste - def erstelle_optimierungsmodell(self): + def erstelle_optimierungsmodell(self) -> Tuple[pulp.LpProblem, Dict[Tuple[str, date, Dienst], pulp.LpVariable]]: """Erstellt das PuLP Optimierungsmodell""" print("Erstelle Optimierungsmodell...") @@ -368,7 +371,7 @@ class Elterndienstplaner: prob = pulp.LpProblem("Elterndienstplaner", pulp.LpMinimize) # Entscheidungsvariablen: x[eltern, tag, dienst] ∈ {0,1} - x = {} + x: Dict[Tuple[str, date, Dienst], pulp.LpVariable] = {} for eltern in self.eltern: for tag in self.tage: for dienst in self.dienste: @@ -547,7 +550,8 @@ class Elterndienstplaner: print(f"Modell erstellt mit {len(x)} Variablen und {len(prob.constraints)} Constraints") return prob, x - def löse_optimierung(self, prob, x): + def löse_optimierung(self, prob: pulp.LpProblem, + x: Dict[Tuple[str, date, Dienst], pulp.LpVariable]) -> Optional[Dict[date, Dict[Dienst, List[str]]]]: """Löst das Optimierungsproblem""" print("Löse Optimierungsproblem...") @@ -574,7 +578,7 @@ class Elterndienstplaner: return None # Lösung extrahieren - lösung = {} + lösung: Dict[date, Dict[Dienst, List[str]]] = {} for (eltern, tag, dienst), var in x.items(): if var.varValue and var.varValue > 0.5: # Binary variable ist 1 if tag not in lösung: @@ -585,7 +589,7 @@ class Elterndienstplaner: return lösung - def schreibe_ausgabe_csv(self, datei, lösung): + def schreibe_ausgabe_csv(self, datei: str, lösung: Dict[date, Dict[Dienst, List[str]]]) -> None: """Schreibt die Lösung in die ausgabe.csv""" print(f"Schreibe Ergebnisse nach {datei}...") @@ -613,7 +617,7 @@ class Elterndienstplaner: print("Ausgabe erfolgreich geschrieben!") - def drucke_statistiken(self, lösung): + def drucke_statistiken(self, lösung: Dict[date, Dict[Dienst, List[str]]]) -> None: """Druckt Statistiken zur Lösung""" print("\n" + "="*50) print("STATISTIKEN") @@ -641,7 +645,7 @@ class Elterndienstplaner: print(f" {eltern:15} {faktor_summe:.1f}") -def main(): +def main() -> None: if len(sys.argv) < 4: print("Usage: ./elterndienstplaner.py []") sys.exit(1)