Compare commits
2 Commits
f4791cf7bb
...
5acef1e2ae
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5acef1e2ae | ||
|
|
a25f516d4c |
223
README.md
223
README.md
@ -2,73 +2,108 @@
|
|||||||
|
|
||||||
Automatische Zuteilung von Elterndiensten im Kinderladen unter Berücksichtigung von Fairness und Präferenzen.
|
Automatische Zuteilung von Elterndiensten im Kinderladen unter Berücksichtigung von Fairness und Präferenzen.
|
||||||
|
|
||||||
## Verwendung
|
## Wie funktioniert die Dienstplanung?
|
||||||
|
|
||||||
```bash
|
Der Elterndienstplaner verteilt die anfallenden Dienstzuteilungen für einen Monat automatisch auf die Eltern. Dabei werden folgende Ziele berücksichtigt:
|
||||||
./elterndienstplaner.py <eingabe.csv> <eltern.csv> <ausgabe.csv> [<vorherige-ausgaben.csv>]
|
|
||||||
```
|
|
||||||
|
|
||||||
**Parameter:**
|
### 1. Regeln (Harte Constraints)
|
||||||
- `eingabe.csv`: Benötigte Dienste und Eltern-Präferenzen für den Planungsmonat
|
|
||||||
- `eltern.csv`: Dienstfaktoren der Eltern (Anzahl betreuter Kinder)
|
|
||||||
- `ausgabe.csv`: Hier wird die Zuteilung geschrieben
|
|
||||||
- `vorherige-ausgaben.csv` (optional): Historische Daten für Fairness über das Jahr
|
|
||||||
|
|
||||||
## Dienste
|
Diese Regeln **müssen** immer eingehalten werden:
|
||||||
|
|
||||||
|
- **C1: Maximal einmal pro Woche pro Diensttyp** - Niemand muss z.B. zweimal in einer Woche Frühstücksdienst machen
|
||||||
|
- **C2: Maximal eine Zuteilung pro Tag** - Niemand bekommt mehrere Dienstzuteilungen am selben Tag
|
||||||
|
- **C3: Nur bei Verfügbarkeit** - Zuteilungen erfolgen nur bei Verfügbarkeit (keine Abwesenheiten)
|
||||||
|
- **C4: Alle Dienste werden besetzt** - Jeder benötigte Diensttyp wird an jedem Tag besetzt
|
||||||
|
|
||||||
|
### 2. Fairness und Präferenzen (Weiche Constraints)
|
||||||
|
|
||||||
|
Diese Ziele werden optimiert, können aber nicht immer perfekt erfüllt werden:
|
||||||
|
|
||||||
|
**Fairness:**
|
||||||
|
- **F1: Faire Jahresverteilung** - Über das ganze Kitajahr hinweg werden Zuteilungen fair verteilt (pro Diensttyp)
|
||||||
|
- **F2: Faire Monatsverteilung** - Innerhalb eines Monats werden Zuteilungen fair verteilt (pro Diensttyp). **Besonderheit:** Abwesenheitstage werden aus der Dienstpflicht herausgerechnet, um eine gleichmäßigere Verteilung zu erreichen. Die "verpassten" Dienste werden über F1 im Jahresverlauf ausgeglichen.
|
||||||
|
- **F3: Ausgewogene Diensttypen (Jahr)** - Verhindert über das ganze Jahr, dass einzelne Familien über alle Diensttypen hinweg zu viele Zuteilungen bekommen
|
||||||
|
- **F4: Ausgewogene Diensttypen (Monat)** - Verhindert im aktuellen Monat, dass einzelne Familien über alle Diensttypen hinweg zu viele Zuteilungen bekommen
|
||||||
|
|
||||||
|
**Präferenzen:**
|
||||||
|
- **P1: Bevorzugte Tage für Diensttypen** - An bestimmten Tagen bevorzugte Diensttypen werden nach Möglichkeit zugeteilt
|
||||||
|
- **P2: Vermeiden an bestimmten Tagen** - An bestimmten Tagen abgelehnte Diensttypen werden nach Möglichkeit vermieden
|
||||||
|
|
||||||
|
Die Fairness bestimmt **wie viele** Zuteilungen jede Familie erhält, die Präferenzen beeinflussen **an welchen Tagen welcher Diensttyp** zugeteilt wird. Selbst bei Ablehnung kann ein Diensttyp an einem bestimmten Tag zugeteilt werden, wenn das für die faire Verteilung nötig ist.
|
||||||
|
|
||||||
|
## Diensttypen
|
||||||
|
|
||||||
- **F** - Frühstücksdienst (täglich, 1 Person)
|
- **F** - Frühstücksdienst (täglich, 1 Person)
|
||||||
- **P** - Putznotdienst (täglich, 1 Person)
|
- **P** - Putznotdienst (täglich, 1 Person)
|
||||||
- **E** - Essensausgabenotdienst (täglich, 1 Person)
|
- **E** - Essensausgabenotdienst (täglich, 1 Person)
|
||||||
- **K** - Kochen (ca. alle 2 Wochen, 1 Person)
|
- **K** - Kochen (ca. alle 2 Wochen, 1 Person)
|
||||||
- **A** - Elternabend (nach Bedarf, 2 Personen)
|
- **A** - Elternabend (ca. einmal im Monat, 2 Personen)
|
||||||
|
|
||||||
Die Planung erfolgt für einen Kalendermonat.
|
Die Planung erfolgt für einen Kalendermonat.
|
||||||
|
|
||||||
## Eingabedateien
|
## Abwesenheiten und Präferenzen angeben (eingabe.csv)
|
||||||
|
|
||||||
### eingabe.csv
|
Diese Datei enthält für jeden Tag des Planungsmonats, welche Dienste anfallen und welche Eltern verfügbar sind bzw. Präferenzen haben.
|
||||||
|
|
||||||
Informationen zu benötigten Diensten und Verfügbarkeit/Präferenzen der Eltern.
|
|
||||||
|
|
||||||
**Format:**
|
**Format:**
|
||||||
```
|
```
|
||||||
Datum,Wochentag,Dienste,Eltern1,Eltern2,...
|
Datum, Wochentag, Dienste, Sarah & Tim, Leon, Maya, ...
|
||||||
2025-01-06,Montag,FPE,F+,x,...
|
2026-01-06, Montag, FPE, F+, x, , ...
|
||||||
2025-01-07,Dienstag,FPE,P-,F+P+,...
|
2026-01-07, Dienstag, FPE, P-, F+P+, , ...
|
||||||
```
|
```
|
||||||
|
|
||||||
**Spalten:**
|
**Spalten:**
|
||||||
1. Datum (ISO-Format: YYYY-MM-DD)
|
1. **Datum** (ISO-Format: YYYY-MM-DD)
|
||||||
2. Wochentag (zur Information)
|
2. **Wochentag** (zur Information)
|
||||||
3. Benötigte Dienste (z.B. "FPE" für Frühstück, Putzen, Essen)
|
3. **Diensttypen** - Welche Diensttypen an diesem Tag benötigt werden (z.B. "FPE" für Frühstück, Putzen, Essen)
|
||||||
4-n. Für jeden Elternteil:
|
4-n. **Eine Spalte pro Familie** - Abwesenheiten und Präferenzen:
|
||||||
- `x` = nicht verfügbar
|
- `x` = nicht verfügbar (Urlaub, Krankheit, etc.)
|
||||||
- `F+` = Frühstücksdienst bevorzugt
|
- `Kürzel+` = diesen Dienst bevorzugt (z.B. `F+` für Frühstücksdienst bevorzugt)
|
||||||
- `P-` = Putznotdienst nur notfalls
|
- `Kürzel-` = diesen Dienst abgelehnt (z.B. `P-` für Putznotdienst abgelehnt)
|
||||||
- Mehrere Präferenzen kombinierbar: `F+P-E+`
|
- Mehrere kombinierbar: `F+P-E+` (Frühstück bevorzugt, Putzen abgelehnt, Essen bevorzugt)
|
||||||
- Leer = verfügbar, keine Präferenz
|
- Leer = verfügbar, keine Präferenz
|
||||||
|
|
||||||
|
**Beispiel:**
|
||||||
|
```
|
||||||
|
Datum ,Wochentag ,Dienste,Sarah & Tim,Leon ,Maya
|
||||||
|
2026-01-06 ,Montag ,FPE ,x , ,F+
|
||||||
|
2026-01-07 ,Dienstag ,FPE , ,F+P- ,
|
||||||
|
2026-01-10 ,Freitag ,FPEK ,F+K+ , ,P-
|
||||||
|
```
|
||||||
|
|
||||||
|
- Sarah & Tim sind am 6.1. nicht verfügbar
|
||||||
|
- Leon bevorzugt am 7.1. Frühstück, lehnt Putzen ab
|
||||||
|
- Maya bevorzugt am 6.1. Frühstück
|
||||||
|
- Am 10.1. bevorzugen Sarah & Tim Frühstück oder Kochen
|
||||||
|
|
||||||
|
## Weitere Eingabedateien
|
||||||
|
|
||||||
### eltern.csv
|
### eltern.csv
|
||||||
|
|
||||||
Dienstfaktoren (= Anzahl betreuter Kinder) pro Elternteil und Zeitraum.
|
Dienstfaktoren (= Anzahl betreuter Kinder) pro Elternteil und Zeitraum.
|
||||||
|
|
||||||
**Format:**
|
**Format:**
|
||||||
```
|
```
|
||||||
Eltern,Beginn,Ende,Faktor,Beginn,Ende,Faktor,...
|
Eltern, Beginn, Ende, Faktor, Beginn, Ende, Faktor, ...
|
||||||
Müller,2024-09-01,2025-07-31,2
|
Sarah & Tim, 2024-09-01, 2025-07-31, 2
|
||||||
Schmidt,2024-09-01,2024-12-31,1,2025-01-01,2025-07-31,0
|
Leon, 2024-09-01, 2024-12-31, 1, 2025-01-01, 2025-07-31, 0
|
||||||
|
Maya, 2024-09-01, 2025-07-31, 1
|
||||||
```
|
```
|
||||||
|
|
||||||
**Spalten:**
|
**Spalten:**
|
||||||
1. Elternname (Kind-Name zur Identifikation)
|
1. **Kind-Name(n)** - Bei mehreren Kindern durch & verbunden (z.B. "Sarah & Tim")
|
||||||
2-4. Zeitraum 1: Beginn, Ende, Dienstfaktor
|
2-4. **Zeitraum 1:** Beginn (Datum), Ende (Datum), Dienstfaktor
|
||||||
5-7. Zeitraum 2: Beginn, Ende, Dienstfaktor (optional)
|
5-7. **Zeitraum 2:** Beginn, Ende, Dienstfaktor (optional, für Änderungen während des Jahres)
|
||||||
...
|
...
|
||||||
|
|
||||||
**Hinweise:**
|
**Hinweise:**
|
||||||
|
- Der Dienstfaktor entspricht der Anzahl der Kinder in der Familie (z.B. 2 für "Sarah & Tim", 1 für "Leon")
|
||||||
|
- Familien mit mehreren Kindern werden als ein Eintrag mit entsprechendem Dienstfaktor geführt
|
||||||
- Bei überlappenden Zeiträumen gilt der letzte Eintrag
|
- Bei überlappenden Zeiträumen gilt der letzte Eintrag
|
||||||
- Außerhalb definierter Zeiträume: Faktor = 0 (keine Dienstpflicht)
|
- Außerhalb definierter Zeiträume: Faktor = 0 (keine Dienstpflicht)
|
||||||
- Faktor = 0 bedeutet: Befreiung (z.B. durch Vorstandsamt)
|
- Faktor = 0 bedeutet: Befreiung von Diensten (z.B. durch Vorstandsamt)
|
||||||
|
|
||||||
|
**Beispiel:** Sarah & Tim (Dienstfaktor 2), Leon (Dienstfaktor 1, aber ab Januar 2025 keine Dienstpflicht), Maya (Dienstfaktor 1).
|
||||||
|
|
||||||
### vorherige-ausgaben.csv (optional)
|
### vorherige-ausgaben.csv (optional)
|
||||||
|
|
||||||
@ -78,86 +113,114 @@ Frühere Ausgaben des Programms zur Berechnung der Jahres-Fairness.
|
|||||||
|
|
||||||
**Verwendung:**
|
**Verwendung:**
|
||||||
- Zu Beginn des Kita-Jahres (September): Keine Datei nötig
|
- Zu Beginn des Kita-Jahres (September): Keine Datei nötig
|
||||||
- Ab Oktober: Vorherige Ausgaben anhängen für kumulative Fairness
|
- Ab Oktober: Vorherige Ausgaben anhängen für kumulative Fairness über das Jahr
|
||||||
- Im Jahresverlauf sammeln sich die Ausgaben an
|
- Im Jahresverlauf sammeln sich die Ausgaben an
|
||||||
|
|
||||||
## Ausgabe
|
## Ausgabedatei
|
||||||
|
|
||||||
### ausgabe.csv
|
### ausgabe.csv
|
||||||
|
|
||||||
Zugeteilte Dienste pro Tag.
|
Dienstzuteilungen pro Tag. Diese Datei wird vom Programm erstellt.
|
||||||
|
|
||||||
**Format:**
|
**Format:**
|
||||||
```
|
```
|
||||||
Datum,Wochentag,Frühstücksdienst,Putznotdienst,Essensausgabenotdienst,Kochen,Elternabend
|
Datum, Wochentag, Frühstücksdienst, Putznotdienst, Essensausgabenotdienst, Kochen, Elternabend
|
||||||
2025-01-06,Montag,Müller,Schmidt,Weber,,
|
2026-01-06, Montag, Sarah & Tim, Leon, Maya, ,
|
||||||
2025-01-07,Dienstag,Weber,Müller,Schmidt,,
|
2026-01-07, Dienstag, Maya, Sarah & Tim, Leon, ,
|
||||||
```
|
```
|
||||||
|
|
||||||
## Constraints
|
Jede Zeile entspricht einem Tag, die Spalten enthalten die Kindernamen, denen die jeweiligen Diensttypen zugeteilt wurden.
|
||||||
|
|
||||||
### Harte Constraints (müssen erfüllt sein)
|
## Verwendung
|
||||||
|
|
||||||
- **C1**: Pro Eltern und Dienst maximal **einmal pro Woche** (Mo-So)
|
```bash
|
||||||
- **C2**: Pro Eltern maximal **ein Dienst pro Tag**
|
./elterndienstplaner.py <eingabe.csv> <eltern.csv> <ausgabe.csv> [<vorherige-ausgaben.csv>]
|
||||||
- **C3**: Nur **verfügbare** Eltern einteilen
|
```
|
||||||
- **C4**: Alle **benötigten Dienste** müssen besetzt werden
|
|
||||||
|
|
||||||
### Weiche Constraints (werden optimiert)
|
**Parameter:**
|
||||||
|
- `eingabe.csv`: Benötigte Diensttypen und Eltern-Präferenzen für den Planungsmonat
|
||||||
|
- `eltern.csv`: Dienstfaktoren der Eltern (Anzahl betreuter Kinder)
|
||||||
|
- `ausgabe.csv`: Hier werden die Zuteilungen geschrieben
|
||||||
|
- `vorherige-ausgaben.csv` (optional): Historische Zuteilungen für Fairness über das Jahr
|
||||||
|
|
||||||
**Fairness** (nach Priorität):
|
## Wie werden die Constraints umgesetzt?
|
||||||
- **F1 (Global)**: Dienste proportional zum Dienstfaktor über das **ganze Jahr**
|
|
||||||
- Berücksichtigt historische Dienste aus `vorherige-ausgaben.csv`
|
|
||||||
- Gewichtung: 40% (zu Jahresbeginn) → 60% (zu Jahresende)
|
|
||||||
|
|
||||||
- **F2 (Lokal)**: Dienste proportional zum Dienstfaktor im **aktuellen Monat**
|
Dieser Abschnitt erklärt die technische Umsetzung für technisch interessierte Eltern.
|
||||||
- Nur aktueller Planungszeitraum
|
|
||||||
- Gewichtung: 60% (zu Jahresbeginn) → 40% (zu Jahresende)
|
|
||||||
|
|
||||||
- **F3 (Dienstübergreifend)**: Gleiche Gesamtanzahl über alle Diensttypen
|
### Mathematisches Optimierungsproblem
|
||||||
- Verhindert Häufung bei einzelnen Eltern
|
|
||||||
|
|
||||||
**Präferenzen** (niedrigere Priorität):
|
Der Elterndienstplaner formuliert die Dienstverteilung als **lineares Optimierungsproblem**. Das bedeutet: Es gibt viele mögliche Dienstverteilungen, die alle die harten Constraints (C1-C4) erfüllen. Das Programm sucht diejenige, die die Fairness-Ziele am besten erfüllt und Präferenzen berücksichtigt.
|
||||||
- **P1**: Bevorzugte Dienste (`+`) werden bevorzugt zugeteilt
|
|
||||||
- **P2**: Abgelehnte Dienste (`-`) werden vermieden
|
|
||||||
|
|
||||||
### Fairness-Logik
|
**Entscheidungsvariablen:** Für jeden Tag, jeden Eintrag und jeden Diensttyp gibt es eine Variable: "Wird Sarah & Tim am 6. Januar der Frühstücksdienst zugeteilt?" (Ja/Nein)
|
||||||
|
|
||||||
**Beispiel:** Familie Müller hat 2 Kinder, Familie Schmidt 1 Kind.
|
**Constraints (Nebenbedingungen):** Diese schränken die möglichen Lösungen ein:
|
||||||
|
|
||||||
**Lokale Fairness (F2):**
|
- **C1 (Wöchentliches Limit):** Für jeden Eintrag und jeden Diensttyp: Die Summe der Zuweisungen pro Woche ≤ 1
|
||||||
- Im Januar sollen beide verfügbar sein
|
- **C2 (Tageslimit):** Für jeden Eintrag und jeden Tag: Die Summe aller Zuweisungen ≤ 1
|
||||||
- Müller sollte 2× so viele Dienste bekommen wie Schmidt
|
- **C3 (Verfügbarkeit):** Wenn im Feld "x" steht, wird die entsprechende Variable auf 0 gesetzt
|
||||||
- Verhindert: Müller bekommt alle Dienste auf einmal
|
- **C4 (Bedarfsdeckung):** Für jeden Tag und Diensttyp: Summe der Zuweisungen = benötigte Personenzahl
|
||||||
|
|
||||||
**Globale Fairness (F1):**
|
### Zielfunktion: Fairness und Präferenzen
|
||||||
- Müller war im Dezember im Urlaub → 0 Dienste
|
|
||||||
- Im Januar sollte Müller aufholen
|
|
||||||
- Über das Jahr: 2:1 Verhältnis wird ausgeglichen
|
|
||||||
|
|
||||||
**Gewichtung im Jahresverlauf:**
|
Die **Zielfunktion** bewertet, wie gut eine Dienstverteilung ist. Das Programm minimiert Abweichungen von fairer Verteilung und berücksichtigt Präferenzen.
|
||||||
- **September-November**: F2 (lokal) stärker → sanftes Einführen
|
|
||||||
- **Dezember-Mai**: Ausgewogen
|
|
||||||
- **Juni-Juli**: F1 (global) stärker → Jahresausgleich
|
|
||||||
|
|
||||||
## Ausgabe-Statistiken
|
**F1 (Globale Fairness):**
|
||||||
|
- Berechnung: Für jeden Eintrag und jeden Diensttyp wird gezählt, wie viele Zuteilungen bisher über das Jahr verteilt wurden (aus `vorherige-ausgaben.csv`)
|
||||||
|
- Ziel: Die Gesamtanzahl soll proportional zum Dienstfaktor sein
|
||||||
|
- Beispiel: Sarah & Tim (Dienstfaktor 2) hatten bisher 10 Zuteilungen, Leon (Dienstfaktor 1) hatte 8 Zuteilungen. Das ist unfair (sollte 2:1 sein, also z.B. 12:6). Im aktuellen Monat sollte Leon bevorzugt werden, um das auszugleichen.
|
||||||
|
**F2 (Lokale Fairness):**
|
||||||
|
- Berechnung: Nur für den aktuellen Planungsmonat
|
||||||
|
- Ziel: Die Anzahl der Zuteilungen im aktuellen Monat soll proportional zum Dienstfaktor sein
|
||||||
|
- **Besonderheit Abwesenheiten:** Abwesenheitstage werden aus der Dienstpflicht herausgerechnet (Dienstfaktor = 0). Das bedeutet: Bei einer 2-wöchigen Abwesenheit werden in den verbleibenden 2 Wochen keine zusätzlichen Dienste zugeteilt, um die Abwesenheit auszugleichen.
|
||||||
|
- **Warum?** Dies führt zu einer gleichmäßigeren Verteilung im aktuellen Monat und verhindert, dass Familien in den wenigen verfügbaren Tagen überproportional viele Dienste bekommen müssen.
|
||||||
|
- **Ausgleich:** Die durch Abwesenheit "verpassten" Dienste werden über F1 (globale Fairness) im Jahresverlauf ausgeglichen.
|
||||||
|
- Beispiel: Im Januar sollten Sarah & Tim ca. 2× so viele Zuteilungen erhalten wie Leon (sofern beide den ganzen Monat verfügbar sind)
|
||||||
|
|
||||||
|
**F3 (Dienstübergreifende Fairness - Global):**
|
||||||
|
- Berechnung: Gesamtanzahl aller Zuteilungen (über alle Diensttypen) pro Eintrag über das ganze Jahr
|
||||||
|
- Ziel: Verhindert, dass einzelne Familien über verschiedene Diensttypen hinweg zu viele Zuteilungen bekommen
|
||||||
|
- Beispiel: Sarah & Tim hatten bisher 10 Zuteilungen über alle Diensttypen, Leon nur 3. Das Verhältnis sollte 2:1 sein (12:6). F3 würde Leon im aktuellen Monat bevorzugen.
|
||||||
|
|
||||||
|
**F4 (Dienstübergreifende Fairness - Lokal):**
|
||||||
|
- Berechnung: Gesamtanzahl aller Zuteilungen (über alle Diensttypen) pro Eintrag im aktuellen Monat
|
||||||
|
- Ziel: Verhindert, dass einzelne Familien im aktuellen Monat über verschiedene Diensttypen hinweg zu viele Zuteilungen bekommen
|
||||||
|
- Beispiel: Im Januar werden Sarah & Tim 3× Frühstück, 2× Putzen, 2× Essen = 7 Zuteilungen zugeteilt. Leon bekommt nur 1× Frühstück, 1× Putzen = 2 Zuteilungen. F4 würde Leon weitere Zuteilungen zuweisen, um die Gesamtzahl im Monat anzugleichen.
|
||||||
|
|
||||||
|
**P1 und P2 (Präferenzen):**
|
||||||
|
- An bestimmten Tagen bevorzugte Diensttypen (`+`) bekommen einen Bonus in der Zielfunktion
|
||||||
|
- An bestimmten Tagen abgelehnte Diensttypen (`-`) bekommen eine Strafe in der Zielfunktion
|
||||||
|
- Diese Effekte sind **schwächer** als die Fairness-Terme, d.h. Fairness hat Vorrang
|
||||||
|
- **Wichtig:** Präferenzen beeinflussen nur, an welchen Tagen welcher Diensttyp zugeteilt wird, nicht die Gesamtanzahl der Zuteilungen
|
||||||
|
|
||||||
|
### Gewichtung
|
||||||
|
|
||||||
|
Die verschiedenen Fairness-Ziele werden gewichtet:
|
||||||
|
- **F1 (global): 40%** - Wichtig für Ausgleich über das Jahr (pro Diensttyp)
|
||||||
|
- **F2 (lokal): 60%** - Wichtiger für den aktuellen Monat (pro Diensttyp)
|
||||||
|
- **F3 (global): 10%** - Verhindert extreme Ungleichverteilung über Diensttypen im Jahr
|
||||||
|
- **F4 (lokal): 15%** - Verhindert extreme Ungleichverteilung über Diensttypen im Monat
|
||||||
|
- **P1/P2: niedrig** - Präferenzen werden berücksichtigt, wenn Fairness gewahrt ist
|
||||||
|
|
||||||
|
## Programmausgabe und Statistiken
|
||||||
|
|
||||||
Das Programm zeigt nach der Optimierung:
|
Das Programm zeigt nach der Optimierung:
|
||||||
|
|
||||||
1. **Dienste pro Eltern**: Übersicht der zugeteilten Dienste
|
1. **Zuteilungen pro Eintrag**: Übersicht der zugeteilten Diensttypen für jeden Eintrag
|
||||||
2. **Dienstfaktoren**: Summe im Planungszeitraum
|
2. **Dienstfaktoren**: Summe der Dienstfaktoren im Planungszeitraum
|
||||||
3. **Verteilungsvergleich**: Soll (lokal/global) vs. Ist mit Abweichungen
|
3. **Verteilungsvergleich**:
|
||||||
4. **Präferenz-Verletzungen**: Wie oft wurden Ablehnungen ignoriert
|
- Soll-Werte (lokal und global) basierend auf Fairness
|
||||||
|
- Ist-Werte (tatsächlich zugeteilte Diensttypen)
|
||||||
|
- Abweichungen zwischen Soll und Ist
|
||||||
|
4. **Präferenz-Verletzungen**: Wie oft wurden abgelehnte Diensttypen (`-`) trotzdem zugeteilt
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
**"Keine optimale Lösung gefunden":**
|
**"Keine optimale Lösung gefunden":**
|
||||||
- Zu viele Eltern nicht verfügbar
|
- Zu viele Eltern nicht verfügbar
|
||||||
- Nicht genug Eltern für alle Dienste
|
- Nicht genug Eltern für alle benötigten Diensttypen
|
||||||
- Widersprüchliche Präferenzen
|
- Widersprüchliche Präferenzen
|
||||||
|
|
||||||
**"Unfaire Verteilung":**
|
**"Unfaire Verteilung":**
|
||||||
- Prüfen Sie die Dienstfaktoren in `eltern.csv`
|
- Prüfen Sie die Dienstfaktoren in `eltern.csv`
|
||||||
- Stellen Sie sicher, dass `vorherige-ausgaben.csv` korrekt ist
|
- Stellen Sie sicher, dass `vorherige.ausgaben.csv` korrekt ist
|
||||||
- Mehr Eltern verfügbar machen
|
- Mehr Eltern verfügbar machen
|
||||||
|
|
||||||
|
|||||||
@ -84,7 +84,7 @@ class ElterndienstAusgabe:
|
|||||||
# Sammle Präferenzen strukturiert
|
# Sammle Präferenzen strukturiert
|
||||||
# praeferenzen_pro_eltern_dienst[eltern][dienst] = {datum: präf_wert}
|
# praeferenzen_pro_eltern_dienst[eltern][dienst] = {datum: präf_wert}
|
||||||
praeferenzen_pro_eltern_dienst = defaultdict(lambda: defaultdict(dict))
|
praeferenzen_pro_eltern_dienst = defaultdict(lambda: defaultdict(dict))
|
||||||
for (eltern, tag, dienst), präf in self.daten.präferenzen.items():
|
for (eltern, tag, dienst), präf in self.daten.praeferenzen.items():
|
||||||
praeferenzen_pro_eltern_dienst[eltern][dienst][tag] = präf
|
praeferenzen_pro_eltern_dienst[eltern][dienst][tag] = präf
|
||||||
|
|
||||||
# Berechne Verletzungen
|
# Berechne Verletzungen
|
||||||
|
|||||||
@ -60,8 +60,8 @@ class ElterndienstplanerDaten:
|
|||||||
self.planungszeitraum: List[date] = []
|
self.planungszeitraum: List[date] = []
|
||||||
self.eltern: List[Eltern] = []
|
self.eltern: List[Eltern] = []
|
||||||
self.benoetigte_dienste: Dict[date, List[Dienst]] = {}
|
self.benoetigte_dienste: Dict[date, List[Dienst]] = {}
|
||||||
self.verfügbarkeit: Dict[Tuple[Eltern, date], bool] = {}
|
self.verfuegbarkeit: Dict[Tuple[Eltern, date], bool] = {}
|
||||||
self.präferenzen: Dict[Tuple[Eltern, date, Dienst], int] = {}
|
self.praeferenzen: Dict[Tuple[Eltern, date, Dienst], int] = {}
|
||||||
|
|
||||||
# dienstfaktoren[eltern][tag] = faktor.
|
# dienstfaktoren[eltern][tag] = faktor.
|
||||||
# Wenn es eltern nicht gibt -> keyerror
|
# Wenn es eltern nicht gibt -> keyerror
|
||||||
@ -102,7 +102,7 @@ class ElterndienstplanerDaten:
|
|||||||
vorherige_datei: Optionaler Pfad zur vorherige-ausgaben.csv für Fairness-Constraints
|
vorherige_datei: Optionaler Pfad zur vorherige-ausgaben.csv für Fairness-Constraints
|
||||||
"""
|
"""
|
||||||
# Eingabe CSV: Termine, Präferenzen, Verfügbarkeit
|
# Eingabe CSV: Termine, Präferenzen, Verfügbarkeit
|
||||||
self.eltern, self.planungszeitraum, self.benoetigte_dienste, self.verfügbarkeit, self.präferenzen = \
|
self.eltern, self.planungszeitraum, self.benoetigte_dienste, self.verfuegbarkeit, self.praeferenzen = \
|
||||||
EingabeParser.parse_eingabe_csv(eingabe_datei, self.get_dienst)
|
EingabeParser.parse_eingabe_csv(eingabe_datei, self.get_dienst)
|
||||||
|
|
||||||
# Eltern CSV: Dienstfaktoren
|
# Eltern CSV: Dienstfaktoren
|
||||||
|
|||||||
@ -17,32 +17,31 @@ from ausgabe import ElterndienstAusgabe
|
|||||||
|
|
||||||
|
|
||||||
class Elterndienstplaner:
|
class Elterndienstplaner:
|
||||||
"""Optimierungs-Engine für Elterndienstplanung"""
|
"""Optimierungs-Engine fuer Elterndienstplanung"""
|
||||||
|
|
||||||
def __init__(self, daten: ElterndienstplanerDaten, ausgabe: ElterndienstAusgabe) -> None:
|
def __init__(self, daten: ElterndienstplanerDaten, ausgabe: ElterndienstAusgabe) -> None:
|
||||||
self.daten = daten
|
self.daten = daten
|
||||||
self.ausgabe = ausgabe
|
self.ausgabe = ausgabe
|
||||||
|
|
||||||
def berechne_faire_zielverteilung_global(self) -> Zielverteilung:
|
def berechne_faire_zielverteilung_global(self) -> Zielverteilung:
|
||||||
"""Berechnet die faire Zielanzahl von Diensten für den Planungszeitraum
|
"""Berechnet die faire Zielanzahl von Diensten fuer den Planungszeitraum
|
||||||
basierend auf globaler Fairness (Historie + aktueller Planungszeitraum).
|
basierend auf globaler Fairness (Historie + aktueller Planungszeitraum).
|
||||||
|
|
||||||
Gibt die Ziel-Dienstanzahl für den aktuellen Planungszeitraum zurück,
|
Gibt die Ziel-Dienstanzahl fuer den aktuellen Planungszeitraum zurueck,
|
||||||
korrigiert um bereits geleistete Dienste. Kann negativ sein, wenn bereits
|
korrigiert um bereits geleistete Dienste. Kann negativ sein, wenn bereits
|
||||||
mehr Dienste geleistet wurden als fair wäre."""
|
mehr Dienste geleistet wurden als fair waere."""
|
||||||
|
|
||||||
ziel_dienste: Zielverteilung = defaultdict(lambda: defaultdict(float))
|
ziel_dienste: Zielverteilung = defaultdict(lambda: defaultdict(float))
|
||||||
|
|
||||||
print("\nBerechne faire Zielverteilung basierend auf historischen Daten...")
|
print("\nBerechne faire Zielverteilung basierend auf historischen Daten...")
|
||||||
|
|
||||||
# Historische Dienste nach Datum gruppieren
|
|
||||||
historische_tage = set(datum for datum, _, _ in self.daten.historische_dienste) if self.daten.historische_dienste else set()
|
historische_tage = set(datum for datum, _, _ in self.daten.historische_dienste) if self.daten.historische_dienste else set()
|
||||||
print(f" Analysiere {len(historische_tage)} historische Tage mit {len(self.daten.historische_dienste)} Diensten")
|
print(f" Analysiere {len(historische_tage)} historische Tage mit {len(self.daten.historische_dienste)} Diensten")
|
||||||
|
|
||||||
for dienst in self.daten.dienste:
|
for dienst in self.daten.dienste:
|
||||||
print(f" Verarbeite Dienst {dienst.kuerzel}...")
|
print(f" Verarbeite Dienst {dienst.kuerzel}...")
|
||||||
|
|
||||||
# 1. HISTORISCHE PERIODE: Faire Umverteilung der tatsächlich geleisteten Dienste
|
# 1. HISTORISCHE PERIODE: Faire Umverteilung
|
||||||
historische_dienste_dieses_typs = [
|
historische_dienste_dieses_typs = [
|
||||||
(datum, eltern) for datum, eltern, d in self.daten.historische_dienste
|
(datum, eltern) for datum, eltern, d in self.daten.historische_dienste
|
||||||
if d == dienst
|
if d == dienst
|
||||||
@ -50,22 +49,18 @@ class Elterndienstplaner:
|
|||||||
|
|
||||||
print(f" Gefundene historische {dienst.kuerzel}-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)
|
dienste_pro_tag = defaultdict(list)
|
||||||
for datum, eltern in historische_dienste_dieses_typs:
|
for datum, eltern in historische_dienste_dieses_typs:
|
||||||
dienste_pro_tag[datum].append(eltern)
|
dienste_pro_tag[datum].append(eltern)
|
||||||
|
|
||||||
# Für jeden historischen Tag faire Umverteilung berechnen
|
|
||||||
for tag, 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
|
anzahl_dienste = len(geleistete_eltern)
|
||||||
|
|
||||||
# Dienstfaktoren aller Eltern für diesen historischen Tag berechnen
|
|
||||||
gesamt_dienstfaktor_tag = 0
|
gesamt_dienstfaktor_tag = 0
|
||||||
|
|
||||||
for eltern in self.daten.eltern:
|
for eltern in self.daten.eltern:
|
||||||
gesamt_dienstfaktor_tag += self.daten.dienstfaktoren[eltern][tag]
|
gesamt_dienstfaktor_tag += self.daten.dienstfaktoren[eltern][tag]
|
||||||
|
|
||||||
# Faire Umverteilung der an diesem Tag geleisteten Dienste
|
|
||||||
if gesamt_dienstfaktor_tag > 0:
|
if gesamt_dienstfaktor_tag > 0:
|
||||||
for eltern in self.daten.eltern:
|
for eltern in self.daten.eltern:
|
||||||
if self.daten.dienstfaktoren[eltern][tag] > 0:
|
if self.daten.dienstfaktoren[eltern][tag] > 0:
|
||||||
@ -73,22 +68,19 @@ class Elterndienstplaner:
|
|||||||
faire_zuteilung = anteil * anzahl_dienste
|
faire_zuteilung = anteil * anzahl_dienste
|
||||||
ziel_dienste[eltern][dienst] += faire_zuteilung
|
ziel_dienste[eltern][dienst] += faire_zuteilung
|
||||||
|
|
||||||
if faire_zuteilung > 0.01: # Debug nur für relevante Werte
|
if faire_zuteilung > 0.01:
|
||||||
print(f" {tag}: {eltern} Faktor={self.daten.dienstfaktoren[eltern][tag]} "
|
print(f" {tag}: {eltern} Faktor={self.daten.dienstfaktoren[eltern][tag]} "
|
||||||
f"-> {faire_zuteilung:.2f} von {anzahl_dienste} Diensten")
|
f"-> {faire_zuteilung:.2f} von {anzahl_dienste} Diensten")
|
||||||
|
|
||||||
# 2. AKTUELLER PLANUNGSZEITRAUM: Faire Verteilung der benötigten Dienste (tageweise wie bei historischen Diensten)
|
# 2. AKTUELLER PLANUNGSZEITRAUM: Faire Verteilung
|
||||||
benoetigte_dienste_planungszeitraum = 0
|
benoetigte_dienste_planungszeitraum = 0
|
||||||
|
|
||||||
# Für jeden Tag im aktuellen Planungszeitraum faire Umverteilung berechnen
|
|
||||||
for tag in self.daten.planungszeitraum:
|
for tag in self.daten.planungszeitraum:
|
||||||
# Prüfe ob an diesem Tag der Dienst benötigt wird
|
|
||||||
if dienst not in self.daten.benoetigte_dienste.get(tag, []):
|
if dienst not in self.daten.benoetigte_dienste.get(tag, []):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
benoetigte_dienste_planungszeitraum += dienst.personen_anzahl
|
benoetigte_dienste_planungszeitraum += dienst.personen_anzahl
|
||||||
|
|
||||||
# Dienstfaktoren aller Eltern für diesen Tag berechnen
|
|
||||||
dienstfaktoren = {}
|
dienstfaktoren = {}
|
||||||
gesamt_dienstfaktor_tag = 0
|
gesamt_dienstfaktor_tag = 0
|
||||||
|
|
||||||
@ -97,7 +89,6 @@ class Elterndienstplaner:
|
|||||||
dienstfaktoren[eltern] = faktor
|
dienstfaktoren[eltern] = faktor
|
||||||
gesamt_dienstfaktor_tag += faktor
|
gesamt_dienstfaktor_tag += faktor
|
||||||
|
|
||||||
# Faire Umverteilung der an diesem Tag benötigten Dienste
|
|
||||||
if gesamt_dienstfaktor_tag > 0:
|
if gesamt_dienstfaktor_tag > 0:
|
||||||
for eltern in self.daten.eltern:
|
for eltern in self.daten.eltern:
|
||||||
anteil = dienstfaktoren[eltern] / gesamt_dienstfaktor_tag
|
anteil = dienstfaktoren[eltern] / gesamt_dienstfaktor_tag
|
||||||
@ -105,9 +96,7 @@ class Elterndienstplaner:
|
|||||||
ziel_dienste[eltern][dienst] += faire_zuteilung
|
ziel_dienste[eltern][dienst] += faire_zuteilung
|
||||||
|
|
||||||
# 3. ABZUG DER BEREITS GELEISTETEN DIENSTE
|
# 3. ABZUG DER BEREITS GELEISTETEN DIENSTE
|
||||||
# Ziehe die tatsächlich geleisteten Dienste ab, um das Ziel für den Planungszeitraum zu erhalten
|
|
||||||
for eltern in self.daten.eltern:
|
for eltern in self.daten.eltern:
|
||||||
# Berechne vorherige Dienste on-the-fly aus historischen Diensten
|
|
||||||
vorherige_anzahl = sum(
|
vorherige_anzahl = sum(
|
||||||
1 for _, hist_eltern, hist_dienst in self.daten.historische_dienste
|
1 for _, hist_eltern, hist_dienst in self.daten.historische_dienste
|
||||||
if hist_eltern == eltern and hist_dienst == dienst
|
if hist_eltern == eltern and hist_dienst == dienst
|
||||||
@ -118,13 +107,21 @@ class Elterndienstplaner:
|
|||||||
|
|
||||||
def berechne_faire_zielverteilung_lokal(self) -> Zielverteilung:
|
def berechne_faire_zielverteilung_lokal(self) -> Zielverteilung:
|
||||||
"""Berechnet die lokale faire Zielanzahl von Diensten pro Eltern-Dienst-Kombination
|
"""Berechnet die lokale faire Zielanzahl von Diensten pro Eltern-Dienst-Kombination
|
||||||
basierend auf Dienstfaktoren und benötigten Diensten im aktuellen Planungszeitraum"""
|
basierend auf Dienstfaktoren und benoetigten Diensten im aktuellen Planungszeitraum
|
||||||
|
|
||||||
|
WICHTIG: Bei der lokalen Fairness werden Abwesenheitstage NICHT in die Dienstpflicht
|
||||||
|
eingerechnet (Dienstfaktor = 0 an Abwesenheitstagen). Das führt zu einer gleichmäßigeren
|
||||||
|
Verteilung im aktuellen Monat und verhindert, dass Familien mit längeren Abwesenheiten
|
||||||
|
in den wenigen verfügbaren Tagen überproportional viele Dienste bekommen.
|
||||||
|
|
||||||
|
Die "verpassten" Dienste werden dann über die globale Fairness (F1) im Jahresverlauf
|
||||||
|
ausgeglichen."""
|
||||||
|
|
||||||
ziel_dienste_lokal: Zielverteilung = defaultdict(lambda: defaultdict(float))
|
ziel_dienste_lokal: Zielverteilung = defaultdict(lambda: defaultdict(float))
|
||||||
|
|
||||||
print("\nBerechne lokale faire Zielverteilung für aktuellen Planungszeitraum...")
|
print("\nBerechne lokale faire Zielverteilung für aktuellen Planungszeitraum...")
|
||||||
|
print(" (Abwesenheitstage werden aus der Dienstpflicht herausgerechnet)")
|
||||||
|
|
||||||
# Gesamtdienstfaktor für aktuellen Planungszeitraum berechnen
|
|
||||||
summe_dienstfaktor_planungszeitraum_alle_eltern = sum(
|
summe_dienstfaktor_planungszeitraum_alle_eltern = sum(
|
||||||
sum(self.daten.dienstfaktoren[e][tag] for tag in self.daten.planungszeitraum)
|
sum(self.daten.dienstfaktoren[e][tag] for tag in self.daten.planungszeitraum)
|
||||||
for e in self.daten.eltern
|
for e in self.daten.eltern
|
||||||
@ -134,21 +131,17 @@ class Elterndienstplaner:
|
|||||||
print(" WARNUNG: Gesamtdienstfaktor ist 0, keine lokale Zielverteilung möglich")
|
print(" WARNUNG: Gesamtdienstfaktor ist 0, keine lokale Zielverteilung möglich")
|
||||||
return ziel_dienste_lokal
|
return ziel_dienste_lokal
|
||||||
|
|
||||||
# Für jeden Dienst die lokale faire Verteilung berechnen
|
|
||||||
for dienst in self.daten.dienste:
|
for dienst in self.daten.dienste:
|
||||||
# Anzahl benötigter Dienste im aktuellen Planungszeitraum
|
|
||||||
benoetigte_dienste_planungszeitraum = sum(
|
benoetigte_dienste_planungszeitraum = sum(
|
||||||
1 for tag in self.daten.planungszeitraum
|
1 for tag in self.daten.planungszeitraum
|
||||||
if dienst in self.daten.benoetigte_dienste.get(tag, [])
|
if dienst in self.daten.benoetigte_dienste.get(tag, [])
|
||||||
)
|
)
|
||||||
# Multipliziere mit Anzahl benötigter Personen pro Dienst
|
|
||||||
benoetigte_dienste_planungszeitraum *= dienst.personen_anzahl
|
benoetigte_dienste_planungszeitraum *= dienst.personen_anzahl
|
||||||
|
|
||||||
if benoetigte_dienste_planungszeitraum > 0:
|
if benoetigte_dienste_planungszeitraum > 0:
|
||||||
print(f" {dienst.kuerzel}: {benoetigte_dienste_planungszeitraum} Dienste benötigt")
|
print(f" {dienst.kuerzel}: {benoetigte_dienste_planungszeitraum} Dienste benötigt")
|
||||||
|
|
||||||
for eltern in self.daten.eltern:
|
for eltern in self.daten.eltern:
|
||||||
# Dienstfaktor für diesen Elternteil im aktuellen Planungszeitraum
|
|
||||||
summe_dienstfaktor_planungszeitraum = sum(
|
summe_dienstfaktor_planungszeitraum = sum(
|
||||||
self.daten.dienstfaktoren[eltern][tag] for tag in self.daten.planungszeitraum
|
self.daten.dienstfaktoren[eltern][tag] for tag in self.daten.planungszeitraum
|
||||||
)
|
)
|
||||||
@ -161,7 +154,7 @@ class Elterndienstplaner:
|
|||||||
return ziel_dienste_lokal
|
return ziel_dienste_lokal
|
||||||
|
|
||||||
def _erstelle_entscheidungsvariablen(self) -> Entscheidungsvariablen:
|
def _erstelle_entscheidungsvariablen(self) -> Entscheidungsvariablen:
|
||||||
"""Erstellt die binären Entscheidungsvariablen x[eltern, tag, dienst]"""
|
"""Erstellt die binaeren Entscheidungsvariablen x[eltern, tag, dienst]"""
|
||||||
x: Entscheidungsvariablen = {}
|
x: Entscheidungsvariablen = {}
|
||||||
for eltern in self.daten.eltern:
|
for eltern in self.daten.eltern:
|
||||||
for tag in self.daten.planungszeitraum:
|
for tag in self.daten.planungszeitraum:
|
||||||
@ -180,21 +173,20 @@ class Elterndienstplaner:
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""C1: Je Eltern und Dienst nur einmal die Woche (Woche = Montag bis Sonntag)"""
|
"""C1: Je Eltern und Dienst nur einmal die Woche (Woche = Montag bis Sonntag)"""
|
||||||
erster_tag = self.daten.planungszeitraum[0]
|
erster_tag = self.daten.planungszeitraum[0]
|
||||||
# weekday(): 0=Montag, 6=Sonntag
|
# Finde Montag am oder vor dem ersten Planungstag
|
||||||
# Finde Montag am oder vor dem ersten Planungstag (für historische Dienste)
|
|
||||||
woche_start = erster_tag - timedelta(days=erster_tag.weekday())
|
woche_start = erster_tag - timedelta(days=erster_tag.weekday())
|
||||||
|
|
||||||
woche_nr = 0
|
woche_nr = 0
|
||||||
letzter_tag = self.daten.planungszeitraum[-1]
|
letzter_tag = self.daten.planungszeitraum[-1]
|
||||||
|
|
||||||
while woche_start <= letzter_tag:
|
while woche_start <= letzter_tag:
|
||||||
woche_ende = woche_start + timedelta(days=6) # Sonntag
|
woche_ende = woche_start + timedelta(days=6)
|
||||||
|
|
||||||
for eltern in self.daten.eltern:
|
for eltern in self.daten.eltern:
|
||||||
for dienst in self.daten.dienste:
|
for dienst in self.daten.dienste:
|
||||||
woche_vars = []
|
woche_vars = []
|
||||||
|
|
||||||
# Zähle historische Dienste in dieser Woche (VOR dem Planungszeitraum)
|
# Zaehle historische Dienste in dieser Woche (VOR Planungszeitraum)
|
||||||
historische_dienste_in_woche = 0
|
historische_dienste_in_woche = 0
|
||||||
if woche_start < erster_tag:
|
if woche_start < erster_tag:
|
||||||
for hist_datum, hist_eltern, hist_dienst in self.daten.historische_dienste:
|
for hist_datum, hist_eltern, hist_dienst in self.daten.historische_dienste:
|
||||||
@ -203,13 +195,11 @@ class Elterndienstplaner:
|
|||||||
woche_start <= hist_datum < erster_tag):
|
woche_start <= hist_datum < erster_tag):
|
||||||
historische_dienste_in_woche += 1
|
historische_dienste_in_woche += 1
|
||||||
|
|
||||||
# Sammle Variablen für Planungszeitraum in dieser Woche
|
|
||||||
for tag in self.daten.planungszeitraum:
|
for tag in self.daten.planungszeitraum:
|
||||||
if woche_start <= tag <= woche_ende:
|
if woche_start <= tag <= woche_ende:
|
||||||
if (eltern, tag, dienst) in x:
|
if (eltern, tag, dienst) in x:
|
||||||
woche_vars.append(x[eltern, tag, dienst])
|
woche_vars.append(x[eltern, tag, dienst])
|
||||||
|
|
||||||
# Constraint: Historische + geplante Dienste <= 1
|
|
||||||
if woche_vars:
|
if woche_vars:
|
||||||
prob += pulp.lpSum(woche_vars) <= 1 - historische_dienste_in_woche, \
|
prob += pulp.lpSum(woche_vars) <= 1 - historische_dienste_in_woche, \
|
||||||
f"C1_{eltern.replace(' ', '_')}_{dienst.kuerzel}_w{woche_nr}"
|
f"C1_{eltern.replace(' ', '_')}_{dienst.kuerzel}_w{woche_nr}"
|
||||||
@ -238,10 +228,10 @@ class Elterndienstplaner:
|
|||||||
prob: pulp.LpProblem,
|
prob: pulp.LpProblem,
|
||||||
x: Entscheidungsvariablen
|
x: Entscheidungsvariablen
|
||||||
) -> None:
|
) -> None:
|
||||||
"""C3: Dienste nur verfügbaren Eltern zuteilen"""
|
"""C3: Dienste nur verfuegbaren Eltern zuteilen"""
|
||||||
for eltern in self.daten.eltern:
|
for eltern in self.daten.eltern:
|
||||||
for tag in self.daten.planungszeitraum:
|
for tag in self.daten.planungszeitraum:
|
||||||
if not self.daten.verfügbarkeit.get((eltern, tag), True):
|
if not self.daten.verfuegbarkeit.get((eltern, tag), True):
|
||||||
for dienst in self.daten.dienste:
|
for dienst in self.daten.dienste:
|
||||||
if (eltern, tag, dienst) in x:
|
if (eltern, tag, dienst) in x:
|
||||||
prob += x[eltern, tag, dienst] == 0, \
|
prob += x[eltern, tag, dienst] == 0, \
|
||||||
@ -252,41 +242,46 @@ class Elterndienstplaner:
|
|||||||
prob: pulp.LpProblem,
|
prob: pulp.LpProblem,
|
||||||
x: Entscheidungsvariablen
|
x: Entscheidungsvariablen
|
||||||
) -> None:
|
) -> None:
|
||||||
"""C4: Alle benötigten Dienste müssen zugeteilt werden"""
|
"""C4: Alle benoetigten Dienste muessen zugeteilt werden"""
|
||||||
for tag in self.daten.planungszeitraum:
|
for tag in self.daten.planungszeitraum:
|
||||||
for dienst in self.daten.benoetigte_dienste.get(tag, []):
|
for dienst in self.daten.benoetigte_dienste.get(tag, []):
|
||||||
dienst_vars = []
|
dienst_vars = []
|
||||||
for eltern in self.daten.eltern:
|
for eltern in self.daten.eltern:
|
||||||
if (eltern, tag, dienst) in x:
|
if (eltern, tag, dienst) in x:
|
||||||
# Prüfe ob Eltern verfügbar
|
if self.daten.verfuegbarkeit.get((eltern, tag), True):
|
||||||
if self.daten.verfügbarkeit.get((eltern, tag), True):
|
|
||||||
dienst_vars.append(x[eltern, tag, dienst])
|
dienst_vars.append(x[eltern, tag, dienst])
|
||||||
|
|
||||||
if dienst_vars:
|
if dienst_vars:
|
||||||
# Anzahl benötigter Personen pro Dienst (aus Dienst-Objekt)
|
# Anzahl benoetigter Personen pro Dienst
|
||||||
benoetigte_personen = dienst.personen_anzahl
|
benoetigte_personen = dienst.personen_anzahl
|
||||||
prob += pulp.lpSum(dienst_vars) == benoetigte_personen, \
|
prob += pulp.lpSum(dienst_vars) == benoetigte_personen, \
|
||||||
f"Bedarf_{tag}_{dienst.kuerzel}"
|
f"Bedarf_{tag}_{dienst.kuerzel}"
|
||||||
|
|
||||||
def _add_fairness_constraints(
|
def _add_constraint_fairness_diensttypspezifisch(
|
||||||
self,
|
self,
|
||||||
prob: pulp.LpProblem,
|
prob: pulp.LpProblem,
|
||||||
x: Entscheidungsvariablen,
|
x: Entscheidungsvariablen,
|
||||||
ziel_dienste: Zielverteilung,
|
ziel_dienste: Zielverteilung,
|
||||||
constraint_prefix: str
|
constraint_prefix: str
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
"""Erstellt Fairness-Variablen und fügt Fairness-Constraints hinzu
|
"""F1/F2: Fairness pro Diensttyp - gleicht Anzahl je Diensttyp aus
|
||||||
|
|
||||||
|
Berechnet die Abweichung der zugeteilten Dienste vom fairen Ziel
|
||||||
|
fuer jeden Diensttyp separat. Dies sorgt dafuer, dass z.B. Kochdienste
|
||||||
|
und Putzdienste jeweils fair verteilt werden.
|
||||||
|
|
||||||
|
F1 (global): Basierend auf historischen Daten + aktuellem Planungszeitraum
|
||||||
|
F2 (lokal): Nur fuer den aktuellen Planungszeitraum
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
prob: Das LP-Problem
|
prob: Das LP-Problem
|
||||||
x: Die Entscheidungsvariablen
|
x: Die Entscheidungsvariablen
|
||||||
ziel_dienste: Die Zielverteilung der Dienste
|
ziel_dienste: Die Zielverteilung (global oder lokal)
|
||||||
constraint_prefix: Präfix für Constraint-Namen ('lokal' oder 'global')
|
constraint_prefix: Praefix fuer Constraint-Namen ('global' fuer F1, 'lokal' fuer F2)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary mit Fairness-Abweichungsvariablen
|
Dictionary mit Fairness-Abweichungsvariablen pro Diensttyp
|
||||||
"""
|
"""
|
||||||
# Hilfsvariablen für Fairness-Abweichungen erstellen
|
|
||||||
fairness_abweichung = {}
|
fairness_abweichung = {}
|
||||||
|
|
||||||
for eltern in self.daten.eltern:
|
for eltern in self.daten.eltern:
|
||||||
@ -295,17 +290,16 @@ class Elterndienstplaner:
|
|||||||
f"fair_{constraint_prefix}_{eltern.replace(' ', '_')}_{dienst.kuerzel}",
|
f"fair_{constraint_prefix}_{eltern.replace(' ', '_')}_{dienst.kuerzel}",
|
||||||
lowBound=0)
|
lowBound=0)
|
||||||
|
|
||||||
# Fairness-Constraints hinzufügen
|
|
||||||
for eltern in self.daten.eltern:
|
for eltern in self.daten.eltern:
|
||||||
for dienst in self.daten.dienste:
|
for dienst in self.daten.dienste:
|
||||||
# Tatsächliche Dienste im aktuellen Planungszeitraum
|
|
||||||
zugeteilte_dienste_planungszeitraum = pulp.lpSum(
|
zugeteilte_dienste_planungszeitraum = pulp.lpSum(
|
||||||
x[eltern, tag, dienst]
|
x[eltern, tag, dienst]
|
||||||
for tag in self.daten.planungszeitraum
|
for tag in self.daten.planungszeitraum
|
||||||
if (eltern, tag, dienst) in x
|
if (eltern, tag, dienst) in x
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ziel für diese Fairness-Variante
|
|
||||||
ziel = ziel_dienste[eltern][dienst]
|
ziel = ziel_dienste[eltern][dienst]
|
||||||
prob += (zugeteilte_dienste_planungszeitraum - ziel <=
|
prob += (zugeteilte_dienste_planungszeitraum - ziel <=
|
||||||
fairness_abweichung[eltern, dienst])
|
fairness_abweichung[eltern, dienst])
|
||||||
@ -314,27 +308,30 @@ class Elterndienstplaner:
|
|||||||
|
|
||||||
return fairness_abweichung
|
return fairness_abweichung
|
||||||
|
|
||||||
def _add_constraint_gesamtfairness(
|
def _add_constraint_fairness_typuebergreifend(
|
||||||
self,
|
self,
|
||||||
prob: pulp.LpProblem,
|
prob: pulp.LpProblem,
|
||||||
x: Entscheidungsvariablen,
|
x: Entscheidungsvariablen,
|
||||||
ziel_dienste: Zielverteilung,
|
ziel_dienste: Zielverteilung,
|
||||||
constraint_prefix: str
|
constraint_prefix: str
|
||||||
) -> Dict:
|
) -> Dict:
|
||||||
"""F3: Dienstübergreifende Fairness - verhindert Häufung bei einzelnen Eltern
|
"""F3/F4: Diensttypuebergreifende Fairness - verhindert Haeufung bei einzelnen Familien
|
||||||
|
|
||||||
Berechnet die Abweichung der Gesamtdienstanzahl (über alle Diensttypen)
|
Berechnet die Abweichung der Gesamtdienstanzahl (ueber alle Diensttypen)
|
||||||
vom fairen Gesamtziel. Dies verhindert, dass einzelne Eltern über alle
|
vom fairen Gesamtziel. Dies verhindert, dass einzelne Familien ueber alle
|
||||||
Diensttypen hinweg überproportional viele Dienste bekommen.
|
Diensttypen hinweg ueberproportional viele Dienste bekommen.
|
||||||
|
|
||||||
|
F3 (global): Basierend auf historischen Daten + aktuellem Planungszeitraum
|
||||||
|
F4 (lokal): Nur fuer den aktuellen Planungszeitraum
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
prob: Das LP-Problem
|
prob: Das LP-Problem
|
||||||
x: Die Entscheidungsvariablen
|
x: Die Entscheidungsvariablen
|
||||||
ziel_dienste: Die Zielverteilung (global oder lokal)
|
ziel_dienste: Die Zielverteilung (global oder lokal)
|
||||||
constraint_prefix: Präfix für Constraint-Namen ('lokal' oder 'global')
|
constraint_prefix: Praefix fuer Constraint-Namen ('global' fuer F3, 'lokal' fuer F4)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Dictionary mit Gesamt-Fairness-Abweichungsvariablen
|
Dictionary mit Gesamt-Fairness-Abweichungsvariablen ueber alle Diensttypen
|
||||||
"""
|
"""
|
||||||
fairness_abweichung_gesamt = {}
|
fairness_abweichung_gesamt = {}
|
||||||
|
|
||||||
@ -343,7 +340,7 @@ class Elterndienstplaner:
|
|||||||
f"fair_gesamt_{constraint_prefix}_{eltern.replace(' ', '_')}",
|
f"fair_gesamt_{constraint_prefix}_{eltern.replace(' ', '_')}",
|
||||||
lowBound=0)
|
lowBound=0)
|
||||||
|
|
||||||
# Tatsächliche Gesamtdienste für diesen Elternteil
|
|
||||||
tatsaechliche_dienste_gesamt = pulp.lpSum(
|
tatsaechliche_dienste_gesamt = pulp.lpSum(
|
||||||
x[eltern, tag, dienst]
|
x[eltern, tag, dienst]
|
||||||
for tag in self.daten.planungszeitraum
|
for tag in self.daten.planungszeitraum
|
||||||
@ -351,10 +348,8 @@ class Elterndienstplaner:
|
|||||||
if (eltern, tag, dienst) in x
|
if (eltern, tag, dienst) in x
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ziel-Gesamtdienste für diesen Elternteil (Summe über alle Dienste)
|
|
||||||
ziel_gesamt = sum(ziel_dienste[eltern][dienst] for dienst in self.daten.dienste)
|
ziel_gesamt = sum(ziel_dienste[eltern][dienst] for dienst in self.daten.dienste)
|
||||||
|
|
||||||
# Fairness-Constraints
|
|
||||||
prob += (tatsaechliche_dienste_gesamt - ziel_gesamt <=
|
prob += (tatsaechliche_dienste_gesamt - ziel_gesamt <=
|
||||||
fairness_abweichung_gesamt[eltern])
|
fairness_abweichung_gesamt[eltern])
|
||||||
prob += (ziel_gesamt - tatsaechliche_dienste_gesamt <=
|
prob += (ziel_gesamt - tatsaechliche_dienste_gesamt <=
|
||||||
@ -372,46 +367,41 @@ class Elterndienstplaner:
|
|||||||
fairness_abweichung_gesamt_global: Dict,
|
fairness_abweichung_gesamt_global: Dict,
|
||||||
fairness_abweichung_gesamt_lokal: Dict
|
fairness_abweichung_gesamt_lokal: Dict
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Erstellt die Zielfunktion mit Fairness und Präferenzen"""
|
"""Erstellt die Zielfunktion mit Fairness und Praeferenzen"""
|
||||||
objective_terms = []
|
objective_terms = []
|
||||||
|
|
||||||
# Fairness-Gewichtung
|
|
||||||
gewicht_global = 40
|
gewicht_global = 40
|
||||||
gewicht_lokal = 60
|
gewicht_lokal = 60
|
||||||
gewicht_f1 = gewicht_global
|
gewicht_f1 = gewicht_global
|
||||||
gewicht_f2 = gewicht_lokal
|
gewicht_f2 = gewicht_lokal
|
||||||
gewicht_f3_global = 0.25 * gewicht_global
|
gewicht_f3_global = 0.25 * gewicht_global
|
||||||
gewicht_f3_lokal = 0.25 * gewicht_lokal
|
gewicht_f4_lokal = 0.25 * gewicht_lokal
|
||||||
|
|
||||||
# Fairness-Terme zur Zielfunktion hinzufügen
|
|
||||||
for eltern in self.daten.eltern:
|
for eltern in self.daten.eltern:
|
||||||
for dienst in self.daten.dienste:
|
for dienst in self.daten.dienste:
|
||||||
objective_terms.append(gewicht_f1 * fairness_abweichung_global[eltern, dienst])
|
objective_terms.append(gewicht_f1 * fairness_abweichung_global[eltern, dienst])
|
||||||
objective_terms.append(gewicht_f2 * fairness_abweichung_lokal[eltern, dienst])
|
objective_terms.append(gewicht_f2 * fairness_abweichung_lokal[eltern, dienst])
|
||||||
|
|
||||||
# F3: Gesamtfairness (dienstübergreifend) - global und lokal
|
|
||||||
objective_terms.append(gewicht_f3_global * fairness_abweichung_gesamt_global[eltern])
|
objective_terms.append(gewicht_f3_global * fairness_abweichung_gesamt_global[eltern])
|
||||||
objective_terms.append(gewicht_f3_lokal * fairness_abweichung_gesamt_lokal[eltern])
|
objective_terms.append(gewicht_f4_lokal * fairness_abweichung_gesamt_lokal[eltern])
|
||||||
|
|
||||||
# P1: Bevorzugte Dienste (positiv belohnen)
|
# P1: Bevorzugte Dienste
|
||||||
for (eltern, tag, dienst), präf in self.daten.präferenzen.items():
|
for (eltern, tag, dienst), praef in self.daten.praeferenzen.items():
|
||||||
if (eltern, tag, dienst) in x and präf == 1: # bevorzugt
|
if (eltern, tag, dienst) in x and praef == 1:
|
||||||
objective_terms.append(-5 * x[eltern, tag, dienst])
|
objective_terms.append(-5 * x[eltern, tag, dienst])
|
||||||
|
|
||||||
# P2: Abgelehnte Dienste (bestrafen)
|
# P2: Abgelehnte Dienste
|
||||||
for (eltern, tag, dienst), präf in self.daten.präferenzen.items():
|
for (eltern, tag, dienst), praef in self.daten.praeferenzen.items():
|
||||||
if (eltern, tag, dienst) in x and präf == -1: # abgelehnt
|
if (eltern, tag, dienst) in x and praef == -1:
|
||||||
objective_terms.append(25 * x[eltern, tag, dienst])
|
objective_terms.append(25 * x[eltern, tag, dienst])
|
||||||
|
|
||||||
# Zielfunktion setzen
|
|
||||||
if objective_terms:
|
if objective_terms:
|
||||||
prob += pulp.lpSum(objective_terms)
|
prob += pulp.lpSum(objective_terms)
|
||||||
else:
|
else:
|
||||||
# Fallback: Minimiere Gesamtanzahl Dienste
|
|
||||||
prob += pulp.lpSum([var for var in x.values()])
|
prob += pulp.lpSum([var for var in x.values()])
|
||||||
|
|
||||||
print(f"Verwende Gewichtung: F1 (global) = {gewicht_f1}, F2 (lokal) = {gewicht_f2}, "
|
print(f"Verwende Gewichtung: F1 (global) = {gewicht_f1}, F2 (lokal) = {gewicht_f2}, "
|
||||||
f"F3_global = {gewicht_f3_global}, F3_lokal = {gewicht_f3_lokal}")
|
f"F3 (global) = {gewicht_f3_global}, F4 (lokal) = {gewicht_f4_lokal}")
|
||||||
|
|
||||||
def erstelle_optimierungsmodell(self) -> Tuple[
|
def erstelle_optimierungsmodell(self) -> Tuple[
|
||||||
pulp.LpProblem,
|
pulp.LpProblem,
|
||||||
@ -424,76 +414,67 @@ class Elterndienstplaner:
|
|||||||
"""
|
"""
|
||||||
print("Erstelle Optimierungsmodell...")
|
print("Erstelle Optimierungsmodell...")
|
||||||
|
|
||||||
# Debugging: Verfügbarkeit prüfen
|
print("\nDebug: Verfuegbarkeit analysieren...")
|
||||||
print("\nDebug: Verfügbarkeit analysieren...")
|
for tag in self.daten.planungszeitraum[:5]:
|
||||||
for tag in self.daten.planungszeitraum[:5]: # Erste 5 Tage
|
verfuegbare = [e for e in self.daten.eltern if self.daten.verfuegbarkeit.get((e, tag), True)]
|
||||||
verfügbare = [e for e in self.daten.eltern if self.daten.verfügbarkeit.get((e, tag), True)]
|
benoetigte = self.daten.benoetigte_dienste.get(tag, [])
|
||||||
benötigte = self.daten.benoetigte_dienste.get(tag, [])
|
print(f" {tag}: Benötigt {len(benoetigte)} Dienste {benoetigte}, verfügbar: {verfuegbare}")
|
||||||
print(f" {tag}: Benötigt {len(benötigte)} Dienste {benötigte}, verfügbar: {verfügbare}")
|
|
||||||
|
|
||||||
# LP Problem erstellen
|
|
||||||
prob = pulp.LpProblem("Elterndienstplaner", pulp.LpMinimize)
|
prob = pulp.LpProblem("Elterndienstplaner", pulp.LpMinimize)
|
||||||
|
|
||||||
# Entscheidungsvariablen erstellen
|
|
||||||
x = self._erstelle_entscheidungsvariablen()
|
x = self._erstelle_entscheidungsvariablen()
|
||||||
|
|
||||||
# Grundlegende Constraints hinzufügen
|
|
||||||
self._add_constraint_ein_dienst_pro_woche(prob, x)
|
self._add_constraint_ein_dienst_pro_woche(prob, x)
|
||||||
self._add_constraint_ein_dienst_pro_tag(prob, x)
|
self._add_constraint_ein_dienst_pro_tag(prob, x)
|
||||||
self._add_constraint_verfuegbarkeit(prob, x)
|
self._add_constraint_verfuegbarkeit(prob, x)
|
||||||
self._add_constraint_dienst_bedarf(prob, x)
|
self._add_constraint_dienst_bedarf(prob, x)
|
||||||
|
|
||||||
# Fairness-Constraints
|
|
||||||
ziel_dienste_global = self.berechne_faire_zielverteilung_global()
|
ziel_dienste_global = self.berechne_faire_zielverteilung_global()
|
||||||
ziel_dienste_lokal = self.berechne_faire_zielverteilung_lokal()
|
ziel_dienste_lokal = self.berechne_faire_zielverteilung_lokal()
|
||||||
|
|
||||||
# Observer Pattern: Notify ausgabe about target distributions
|
|
||||||
self.ausgabe.setze_zielverteilungen(ziel_dienste_lokal, ziel_dienste_global)
|
self.ausgabe.setze_zielverteilungen(ziel_dienste_lokal, ziel_dienste_global)
|
||||||
|
|
||||||
# F2: Lokale Fairness-Constraints
|
# F2: Lokale Fairness pro Diensttyp
|
||||||
fairness_abweichung_lokal = self._add_fairness_constraints(
|
fairness_abweichung_lokal = self._add_constraint_fairness_diensttypspezifisch(
|
||||||
prob, x, ziel_dienste_lokal, "lokal"
|
prob, x, ziel_dienste_lokal, "lokal"
|
||||||
)
|
)
|
||||||
|
|
||||||
# F1: Globale Fairness-Constraints
|
# F1: Globale Fairness pro Diensttyp
|
||||||
fairness_abweichung_global = self._add_fairness_constraints(
|
fairness_abweichung_global = self._add_constraint_fairness_diensttypspezifisch(
|
||||||
prob, x, ziel_dienste_global, "global"
|
prob, x, ziel_dienste_global, "global"
|
||||||
)
|
)
|
||||||
|
|
||||||
# F3: Dienstübergreifende Fairness - Global
|
# F3: Diensttypuebergreifende Fairness (global)
|
||||||
fairness_abweichung_gesamt_global = self._add_constraint_gesamtfairness(
|
fairness_abweichung_gesamt_global = self._add_constraint_fairness_typuebergreifend(
|
||||||
prob, x, ziel_dienste_global, "global"
|
prob, x, ziel_dienste_global, "global"
|
||||||
)
|
)
|
||||||
|
|
||||||
# F3: Dienstübergreifende Fairness - Lokal
|
# F4: Diensttypuebergreifende Fairness (lokal)
|
||||||
fairness_abweichung_gesamt_lokal = self._add_constraint_gesamtfairness(
|
fairness_abweichung_gesamt_lokal = self._add_constraint_fairness_typuebergreifend(
|
||||||
prob, x, ziel_dienste_lokal, "lokal"
|
prob, x, ziel_dienste_lokal, "lokal"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Zielfunktion erstellen
|
|
||||||
self._erstelle_zielfunktion(prob, x, fairness_abweichung_lokal, fairness_abweichung_global,
|
self._erstelle_zielfunktion(prob, x, fairness_abweichung_lokal, fairness_abweichung_global,
|
||||||
fairness_abweichung_gesamt_global, fairness_abweichung_gesamt_lokal)
|
fairness_abweichung_gesamt_global, fairness_abweichung_gesamt_lokal)
|
||||||
|
|
||||||
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: pulp.LpProblem,
|
def loese_optimierung(self, prob: pulp.LpProblem,
|
||||||
x: Entscheidungsvariablen) -> Optional[Dict[date, Dict[Dienst, List[Eltern]]]]:
|
x: Entscheidungsvariablen) -> Optional[Dict[date, Dict[Dienst, List[Eltern]]]]:
|
||||||
"""Löst das Optimierungsproblem"""
|
"""Loest das Optimierungsproblem"""
|
||||||
print("Löse Optimierungsproblem...")
|
print("Löse Optimierungsproblem...")
|
||||||
|
|
||||||
# Solver wählen (verfügbare Solver testen)
|
|
||||||
solver = None
|
solver = None
|
||||||
try:
|
try:
|
||||||
print("Versuche CBC Solver...")
|
print("Versuche CBC Solver...")
|
||||||
solver = pulp.PULP_CBC_CMD(msg=0, timeLimit=10) # Standard CBC Solver
|
solver = pulp.PULP_CBC_CMD(msg=0, timeLimit=10)
|
||||||
except:
|
except:
|
||||||
try:
|
try:
|
||||||
print("Versuche GLPK Solver...")
|
print("Versuche GLPK Solver...")
|
||||||
solver = pulp.GLPK_CMD(msg=0) # GLPK falls verfügbar
|
solver = pulp.GLPK_CMD(msg=0)
|
||||||
except:
|
except:
|
||||||
print("Kein spezifizierter Solver verfügbar, verwende Standard.")
|
print("Kein spezifizierter Solver verfügbar, verwende Standard.")
|
||||||
solver = None # Default Solver
|
solver = None
|
||||||
|
|
||||||
prob.solve(solver)
|
prob.solve(solver)
|
||||||
|
|
||||||
@ -504,17 +485,16 @@ class Elterndienstplaner:
|
|||||||
print("WARNUNG: Keine optimale Lösung gefunden!")
|
print("WARNUNG: Keine optimale Lösung gefunden!")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Lösung extrahieren
|
loesung: Dict[date, Dict[Dienst, List[Eltern]]] = {}
|
||||||
lösung: Dict[date, Dict[Dienst, List[Eltern]]] = {}
|
|
||||||
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:
|
||||||
if tag not in lösung:
|
if tag not in loesung:
|
||||||
lösung[tag] = {}
|
loesung[tag] = {}
|
||||||
if dienst not in lösung[tag]:
|
if dienst not in loesung[tag]:
|
||||||
lösung[tag][dienst] = []
|
loesung[tag][dienst] = []
|
||||||
lösung[tag][dienst].append(eltern)
|
loesung[tag][dienst].append(eltern)
|
||||||
|
|
||||||
return lösung
|
return loesung
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
@ -531,28 +511,20 @@ def main() -> None:
|
|||||||
print("="*50)
|
print("="*50)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Create data model and load data
|
|
||||||
daten = ElterndienstplanerDaten()
|
daten = ElterndienstplanerDaten()
|
||||||
daten.lade_daten(eingabe_datei, eltern_datei, vorherige_datei)
|
daten.lade_daten(eingabe_datei, eltern_datei, vorherige_datei)
|
||||||
|
|
||||||
# Create output handler and optimization engine
|
|
||||||
ausgabe = ElterndienstAusgabe(daten)
|
ausgabe = ElterndienstAusgabe(daten)
|
||||||
planer = Elterndienstplaner(daten, ausgabe)
|
planer = Elterndienstplaner(daten, ausgabe)
|
||||||
|
|
||||||
# Optimierung
|
|
||||||
prob, x = planer.erstelle_optimierungsmodell()
|
prob, x = planer.erstelle_optimierungsmodell()
|
||||||
lösung = planer.löse_optimierung(prob, x)
|
loesung = planer.loese_optimierung(prob, x)
|
||||||
|
|
||||||
if lösung is not None:
|
if loesung is not None:
|
||||||
# Ergebnisse ausgeben
|
ausgabe.schreibe_ausgabe_csv(ausgabe_datei, loesung)
|
||||||
ausgabe.schreibe_ausgabe_csv(ausgabe_datei, lösung)
|
ausgabe.drucke_statistiken(loesung)
|
||||||
ausgabe.drucke_statistiken(lösung)
|
ausgabe.visualisiere_verteilungen(loesung)
|
||||||
|
ausgabe.visualisiere_praeferenz_verletzungen(loesung)
|
||||||
# Visualisierung der Verteilungen (uses Observer Pattern targets)
|
|
||||||
ausgabe.visualisiere_verteilungen(lösung)
|
|
||||||
|
|
||||||
# Visualisierung der Präferenz-Verletzungen
|
|
||||||
ausgabe.visualisiere_praeferenz_verletzungen(lösung)
|
|
||||||
|
|
||||||
print("\n✓ Planung erfolgreich abgeschlossen!")
|
print("\n✓ Planung erfolgreich abgeschlossen!")
|
||||||
else:
|
else:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user