refactoring: type hints

This commit is contained in:
Jan Hoheisel 2025-12-23 22:00:29 +01:00
parent 2d3f49539c
commit 03d1c362f1

View File

@ -9,34 +9,35 @@ Datum: Dezember 2025
import sys import sys
import csv import csv
import pulp import pulp
from datetime import datetime, timedelta from datetime import datetime, timedelta, date
from collections import defaultdict from collections import defaultdict
from typing import Dict, List, Tuple, DefaultDict, Optional
import calendar import calendar
class Dienst: class Dienst:
"""Repräsentiert einen Diensttyp mit allen seinen Eigenschaften""" """Repräsentiert einen Diensttyp mit allen seinen Eigenschaften"""
def __init__(self, kuerzel, name, personen_anzahl=1): def __init__(self, kuerzel: str, name: str, personen_anzahl: int = 1) -> None:
self.kuerzel = kuerzel self.kuerzel: str = kuerzel
self.name = name self.name: str = name
self.personen_anzahl = personen_anzahl self.personen_anzahl: int = personen_anzahl
def __str__(self): def __str__(self) -> str:
return f"{self.kuerzel} ({self.name}): {self.personen_anzahl} Person(en)" 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})" 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""" """Gibt True zurück, wenn mehr als eine Person benötigt wird"""
return self.personen_anzahl > 1 return self.personen_anzahl > 1
class Elterndienstplaner: class Elterndienstplaner:
def __init__(self): def __init__(self) -> None:
# Dienste als Liste definieren # Dienste als Liste definieren
self.dienste = [ self.dienste: List[Dienst] = [
Dienst('F', 'Frühstücksdienst', 1), Dienst('F', 'Frühstücksdienst', 1),
Dienst('P', 'Putznotdienst', 1), Dienst('P', 'Putznotdienst', 1),
Dienst('E', 'Essensausgabenotdienst', 1), Dienst('E', 'Essensausgabenotdienst', 1),
@ -45,36 +46,37 @@ class Elterndienstplaner:
] ]
# Datenstrukturen # Datenstrukturen
self.tage = [] self.tage: List[date] = []
self.eltern = [] self.eltern: List[str] = []
self.benoetigte_dienste = {} # {datum: [dienst_objekte]} self.benoetigte_dienste: Dict[date, List[Dienst]] = {}
self.verfügbarkeit = {} # {(eltern, datum): bool} self.verfügbarkeit: Dict[Tuple[str, date], bool] = {}
self.präferenzen = {} # {(eltern, datum, dienst): 1 (bevorzugt) oder -1 (abgelehnt)} self.präferenzen: Dict[Tuple[str, date, Dienst], int] = {}
self.dienstfaktoren = {} # {eltern: {datum: faktor}} self.dienstfaktoren: Dict[str, Dict[date, float]] = {}
self.alle_zeitraeume = {} # {eltern: [(beginn, ende, faktor), ...]} - ALLE Zeiträume self.alle_zeitraeume: Dict[str, List[Tuple[date, date, float]]] = {}
self.vorherige_dienste = defaultdict(lambda: defaultdict(int)) # {eltern: {dienst: anzahl}} self.vorherige_dienste: DefaultDict[str, DefaultDict[Dienst, int]] = \
self.historische_dienste = [] # [(datum, eltern, dienst), ...] - Alle historischen Dienste mit Datum 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""" """Gibt das Dienst-Objekt für ein Kürzel zurück"""
for dienst in self.dienste: for dienst in self.dienste:
if dienst.kuerzel == kuerzel: if dienst.kuerzel == kuerzel:
return dienst return dienst
return None 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""" """Fügt einen neuen Dienst hinzu"""
dienst = Dienst(kuerzel, name, personen_anzahl) dienst = Dienst(kuerzel, name, personen_anzahl)
self.dienste.append(dienst) self.dienste.append(dienst)
return dienst return dienst
def print_dienste_info(self): def print_dienste_info(self) -> None:
"""Druckt Informationen über alle konfigurierten Dienste""" """Druckt Informationen über alle konfigurierten Dienste"""
print("Konfigurierte Dienste:") print("Konfigurierte Dienste:")
for dienst in self.dienste: for dienst in self.dienste:
print(f" {dienst}") 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""" """Lädt die eingabe.csv mit Terminen und Präferenzen"""
print(f"Lade Eingabedaten aus {datei}...") print(f"Lade Eingabedaten aus {datei}...")
@ -130,7 +132,7 @@ class Elterndienstplaner:
self.tage.sort() self.tage.sort()
print(f"Zeitraum: {self.tage[0]} bis {self.tage[-1]} ({len(self.tage)} Tage)") 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+' """ """Parst Präferenzstring wie 'F+P-E+' """
i = 0 i = 0
while i < len(präf_str): while i < len(präf_str):
@ -150,7 +152,7 @@ class Elterndienstplaner:
else: else:
i += 1 i += 1
def lade_eltern_csv(self, datei): def lade_eltern_csv(self, datei: str) -> None:
"""Lädt die eltern.csv mit Dienstfaktoren""" """Lädt die eltern.csv mit Dienstfaktoren"""
print(f"Lade Elterndaten aus {datei}...") print(f"Lade Elterndaten aus {datei}...")
@ -195,7 +197,7 @@ class Elterndienstplaner:
print(f"Dienstfaktoren geladen für {len(self.dienstfaktoren)} Eltern") print(f"Dienstfaktoren geladen für {len(self.dienstfaktoren)} Eltern")
print(f"Zeiträume gespeichert für globale Fairness-Berechnung") 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""" """Lädt vorherige-ausgaben.csv für Fairness-Constraints"""
print(f"Lade vorherige Ausgaben aus {datei}...") print(f"Lade vorherige Ausgaben aus {datei}...")
@ -240,7 +242,7 @@ class Elterndienstplaner:
print(f"Vorherige Dienste geladen: {dict(self.vorherige_dienste)}") print(f"Vorherige Dienste geladen: {dict(self.vorherige_dienste)}")
print(f"Historische Dienste mit Datum: {len(self.historische_dienste)} Einträge") 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""" """Berechnet den Dienstfaktor eines Elternteils an einem bestimmten Datum"""
if eltern not in self.alle_zeitraeume: if eltern not in self.alle_zeitraeume:
return 0 return 0
@ -250,11 +252,12 @@ class Elterndienstplaner:
return faktor return faktor
return 0 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 """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"""
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...") print("\nBerechne faire Zielverteilung basierend auf historischen Daten...")
@ -353,7 +356,7 @@ class Elterndienstplaner:
return ziel_dienste 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""" """Erstellt das PuLP Optimierungsmodell"""
print("Erstelle Optimierungsmodell...") print("Erstelle Optimierungsmodell...")
@ -368,7 +371,7 @@ class Elterndienstplaner:
prob = pulp.LpProblem("Elterndienstplaner", pulp.LpMinimize) prob = pulp.LpProblem("Elterndienstplaner", pulp.LpMinimize)
# Entscheidungsvariablen: x[eltern, tag, dienst] ∈ {0,1} # Entscheidungsvariablen: x[eltern, tag, dienst] ∈ {0,1}
x = {} x: Dict[Tuple[str, date, Dienst], pulp.LpVariable] = {}
for eltern in self.eltern: for eltern in self.eltern:
for tag in self.tage: for tag in self.tage:
for dienst in self.dienste: for dienst in self.dienste:
@ -547,7 +550,8 @@ class Elterndienstplaner:
print(f"Modell erstellt mit {len(x)} Variablen und {len(prob.constraints)} Constraints") print(f"Modell erstellt mit {len(x)} Variablen und {len(prob.constraints)} Constraints")
return prob, x 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""" """Löst das Optimierungsproblem"""
print("Löse Optimierungsproblem...") print("Löse Optimierungsproblem...")
@ -574,7 +578,7 @@ class Elterndienstplaner:
return None return None
# Lösung extrahieren # Lösung extrahieren
lösung = {} lösung: Dict[date, Dict[Dienst, List[str]]] = {}
for (eltern, tag, dienst), var in x.items(): for (eltern, tag, dienst), var in x.items():
if var.varValue and var.varValue > 0.5: # Binary variable ist 1 if var.varValue and var.varValue > 0.5: # Binary variable ist 1
if tag not in lösung: if tag not in lösung:
@ -585,7 +589,7 @@ class Elterndienstplaner:
return lösung 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""" """Schreibt die Lösung in die ausgabe.csv"""
print(f"Schreibe Ergebnisse nach {datei}...") print(f"Schreibe Ergebnisse nach {datei}...")
@ -613,7 +617,7 @@ class Elterndienstplaner:
print("Ausgabe erfolgreich geschrieben!") 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""" """Druckt Statistiken zur Lösung"""
print("\n" + "="*50) print("\n" + "="*50)
print("STATISTIKEN") print("STATISTIKEN")
@ -641,7 +645,7 @@ class Elterndienstplaner:
print(f" {eltern:15} {faktor_summe:.1f}") print(f" {eltern:15} {faktor_summe:.1f}")
def main(): def main() -> None:
if len(sys.argv) < 4: if len(sys.argv) < 4:
print("Usage: ./elterndienstplaner.py <eingabe.csv> <eltern.csv> <ausgabe.csv> [<vorherige-ausgaben.csv>]") print("Usage: ./elterndienstplaner.py <eingabe.csv> <eltern.csv> <ausgabe.csv> [<vorherige-ausgaben.csv>]")
sys.exit(1) sys.exit(1)