refactoring: dienste in klasse
This commit is contained in:
parent
f0282a9aa9
commit
bdc3205dab
@ -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:
|
if i + 1 < len(präf_str):
|
||||||
dienst = präf_str[i]
|
dienst = self.get_dienst(präf_str[i])
|
||||||
if i + 1 < len(präf_str):
|
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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user