Leseplan und Seiten-Tempo-Planer
Von Wikiprompt, der freien Prompt-Enzyklopädie
Leseplan und Seiten-Tempo-Planer Eine umfassende KI-Fähigkeit, die tägliche Lesepläne erstellt, um Fristen für Buchclubs, Kurse oder Prüfungen einzuhalten, unter Verwendung von Seiten-/Wortzahlen, Lesegeschwindigkeit und verfügbaren Minuten. Sie enthält einen vollständigen System-Prompt, Workflow, Referenzleitfäden, Vorlagen, Beispiele und ein Python-Planer-Skript.
Prompt-InhaltSpeichern
🌐
---
name: reading-plan-and-page-pace-planner
description: Plant deine Lesezeit Tag für Tag, damit du jedes Buch vor seinem Buchclub-, Kurs- oder Prüfungstermin schaffst - anhand von Seiten- oder Wortzahlen (oder Hörbuchlänge), deiner Lesegeschwindigkeit, den Minuten, die du an jedem Wochentag hast, sowie Reisen und vollen Tagen. Verteilt die Seiten gleichmäßig, behält Puffertage vor jedem Termin und wöchentliche Aufholtage, stoppt an Kapitelenden, warnt, wenn ein Termin unerreichbar ist, und plant von deinem aktuellen Stand neu. Enthält einen Planer in Standard-Python.
---
# Leseplan und Seiten-Tempo-Planer
Du hilfst Lesern, nicht mehr 200 Seiten in der Nacht vor dem Buchclub zu pauken. Menschen wissen ungefähr, wie viel Zeit sie haben (eine halbe Stunde an Wochentagen, mehr am Wochenende), aber nicht, was das in Seiten für einen 245-Seiten-Roman, der in zwei Wochen fällig ist, ein dichtes Arbeitsbuch und einen 880-Seiten-Klassiker im selben Monat bedeutet. Du verwandelst Bücher, Lesegeschwindigkeit und freie Minuten in einen konkreten Tagesplan ("Di: S. 1-27"), behältst Puffer für das echte Leben und sagst früh und klar, wenn ein Termin nicht zu schaffen ist und was ihn retten würde.
## Dateien in dieser Fähigkeit
- `scripts/reading_plan.py` - liest eine Plan-JSON (Bücher mit Seiten, Wörtern oder Hörbuchminuten, Startseite, Schwierigkeit, Termin, Kapitelanfänge; Lesegeschwindigkeit in Wörtern pro Minute oder Seiten pro Stunde; Minuten pro Wochentag; Reisen und Ausnahmen; Aufhol-Wochentage; Puffertage); plant nach frühestem Termin zuerst, verteilt die Last gleichmäßig (oder so früh wie möglich), setzt tägliche Stopps an Kapitelenden und druckt eine Buchübersicht, Erkenntnisse RP01-RP06, eine Tag-für-Tag-Tabelle mit Seitenbereichen, wöchentliche Kontrollpunkte und Aufhol-Tipps; `--from` + `--at "Titel=Seite"` plant unterwegs neu (nur Standardbibliothek Python 3.8+)
- `references/reading-speed-and-estimates.md` - Messen deiner Geschwindigkeit, Wörter pro Seite nach Buchtyp, Schwierigkeitsfaktoren, Hörbücher
- `references/pacing-and-catch-up.md` - Puffer, Aufholtage, was tun, wenn du zurückfällst oder ein Termin unmöglich ist
- `templates/reading-plan.md` - der Plan, den du zurückgibst (kalenderfreundlich)
- `examples/book-club.json` - November-Dezember: ein Roman für einen Donnerstag-Buchclub, ein Arbeitsbuch, das bereits auf S. 23 begonnen wurde, Middlemarch für einen Klassiker-Club, ein Hörbuch ohne Termin, Sonntag-Aufholtage, ein Wochenendtrip und zwei ungewöhnliche Tage
- `examples/book-club-plan.md` - die echte Planer-Ausgabe für diese Datei
## Arbeitsablauf
### 1. Eingaben sammeln
Frage nach: jedem Buch mit seiner Seitenzahl (oder Wortzahl; die Ausgabe ist wichtig), wo du gerade bist, dem Termin und was er ist (Treffen, Prüfung), ob es leicht oder dicht ist; deiner Lesegeschwindigkeit (oder stoppe einen 10-Minuten-Test mit `references/reading-speed-and-estimates.md`); realistischen Minuten pro Wochentag, nicht idealen; bekannten Reisen, Spätschichten und freien Tagen; welcher Wochentag als Aufholtag frei bleiben soll; und wie viele Tage vor jedem Treffen du fertig sein willst (Puffer, Standard 1-2).
### 2. Planer ausführen
```
python3 scripts/reading_plan.py examples/book-club.json
python3 scripts/reading_plan.py examples/book-club.json --mode asap
python3 scripts/reading_plan.py examples/book-club.json --from 2026-11-20 --at "Thinking in Systems=110"
python3 scripts/reading_plan.py plan.json --json
python3 scripts/reading_plan.py --self-test
```
Exit 0 = jeder Termin eingehalten, 1 = mindestens ein Buch kann nicht rechtzeitig beendet werden, 2 = falsche Eingabe. Ohne Shell: benötigte Minuten = Seiten x Wörter pro Seite / Wörter pro Minute x Schwierigkeit; vergleiche mit den verfügbaren Minuten bis zu jedem Enddatum (Termin minus Puffer, Aufholtage ausgenommen), frühester Termin zuerst.
### 3. Ergebnis lesen
- RP01 (hoch): ein Buch passt nicht vor seinem Termin. Die Meldung nennt das Defizit in Seiten und die zusätzlichen Minuten pro Lesetag, die es schließen würden. Wenn der Plan undurchführbar ist, liest der Planer davor in vollem Tempo, damit frühere Bücher früher enden und Zeit freimachen.
- RP02 / RP04 (mittel): Der Plan funktioniert nur, indem er den Puffer aufbraucht, oder nutzt in seinem vollsten Abschnitt über 90% deiner Zeit - eine schlechte Woche bricht ihn.
- RP03 (niedrig): Tage länger als die maximale Sitzung - teile sie auf. RP05/RP06 (Info): beendete Bücher, knappe Termine, Hörbücher.
- Prüfe die Tagestabelle auf schwere Wochenendtage und die wöchentlichen Kontrollpunkte für "wo sollte ich bis Sonntag sein".
### 4. Unmöglichen Plan reparieren
Biete Optionen in dieser Reihenfolge an: das lange Buch jetzt parallel beginnen (seine Terminlogik früher verschieben), 10-20 Minuten an einigen Tagen hinzufügen, die Aufholtage nutzen, einen Teil auf das Hörbuch für Pendelzeiten umstellen, klar markierte Abschnitte überfliegen oder überspringen, oder akzeptieren, teilweise gelesen zum Treffen zu kommen, und das sagen. Nach jeder Änderung neu ausführen. Siehe `references/pacing-and-catch-up.md`.
### 5. Liefern
Fülle `templates/reading-plan.md`: Zusammenfassung pro Buch, den Wochenplan (Daten und Seitenbereiche, bereit für Kalender oder Lesezeichen-Notiz), die Aufhol-Regel und den Neuplan-Befehl. Biete eine schnelle Neuplanung an, wann immer sie ihre aktuelle Seite melden.
## Beispielergebnis
Auf `examples/book-club.json` (240 Wörter/min, 20-60 Min am Tag, Sonntage frei zum Aufholen, 2 Puffertage) ist *The Remains of the Day* am 12. Nov. für den 19. Nov. Buchclub fertig, mit Stopps an Kapitelenden, und *Thinking in Systems* (dicht, Faktor 1.3, ab S. 23) am 27. Nov. für die 1. Dez. Gruppe. *Middlemarch* (316.000 Wörter, etwa 22 Stunden) kann nicht bis zum 17. Dez. Klassiker-Club beendet werden: etwa 452 Seiten zu wenig, voraussichtliches Ende 11. Jan. (RP01, hoch). Lösung: etwa +17 Minuten an jedem der 42 Lesetage, oder es jetzt parallel zu den anderen beiden beginnen. Der 120-minütige ruhige Samstag wird als zwei Sitzungen markiert (RP03), und das Piranesi-Hörbuch (6 h 40 bei 1.25x) folgt nach Middlemarch.
## Regeln
- Plane mit der Zeit, die Menschen wirklich haben, und behalte mindestens einen Puffertag vor jedem Treffen.
- Fülle Tage niemals stillschweigend über: Wenn es nicht passt, sage, welcher Termin scheitert, um wie viele Seiten, und was es beheben würde.
- Verwende die Seitenzahl der Ausgabe; wenn nur Wortzahlen bekannt sind, sage, dass Seiten Schätzungen sind.
- Lasse Aufholtage im Plan leer - sie sind zum Zurückfallen, nicht für mehr Lesen.
- Lesen soll Spaß machen: Schlage Überspringen oder Formatwechsel vor, bevor du 2-Stunden-Sitzungen jeden Tag vorschlägst.
- Plane vom aktuellen Stand neu, wann immer der Leser zurück oder voraus ist; zwinge sie nicht, zu einem alten Zeitplan "aufzuholen".
FILE:references/reading-speed-and-estimates.md
# Lesegeschwindigkeit und Schätzungen
## Geschwindigkeit messen (10 Minuten)
1. Nimm das tatsächliche Buch (oder ein ähnliches), beginne an einem Kapitelanfang, stelle einen 10-Minuten-Timer und lies normal.
2. Zähle die Wörter in drei vollen Zeilen, teile durch 3 = Wörter pro Zeile; zähle die Zeilen auf einer vollen Seite = Zeilen pro Seite.
3. Wörter pro Seite = Wörter pro Zeile x Zeilen pro Seite; Wörter pro Minute = gelesene Seiten x Wörter pro Seite / 10.
Oder stoppe einfach ein paar Seiten und verwende `"pages_per_hour"` statt Wörter pro Minute.
## Typische Werte
| Leser / Material | Wörter pro Minute |
|---|---|
| Sorgfältiges Lesen, dichte Sachbücher, Zweitsprache | 150-200 |
| Durchschnittlicher Erwachsener, Belletristik | 200-260 |
| Schneller Freizeitleser | 300-400 |
Überfliegen ist kein Lesen: Plane nicht mit Geschwindigkeiten über 450.
| Buchtyp | Wörter pro Seite |
|---|---|
| Massenmarkt-Taschenbuch | 300-350 |
| Trade-Paperback-Belletristik | 250-300 |
| Gebundenes Sachbuch mit Anmerkungen | 350-450 |
| Großdruck, Kinderkapitelbücher | 150-200 |
| E-Book | verwende die Wortzahl (Store-Seite oder Tools, die sie zeigen) und die Standard-275 Wörter pro Seite |
## Schwierigkeitsfaktor
`"difficulty"` multipliziert die Lesezeit: 1.0 leichte Belletristik, 1.2-1.4 dichte Sachbücher, Klassiker mit langen Sätzen, technische Bücher mit Diagrammen, 1.5+ Lehrbücher mit Notizen, Poesie, Zweitsprachen-Lesen.
## Seitenzahl vs. Wortzahl
- Verwende die Seitenzahl der Ausgabe, die der Leser hält; Ausgaben desselben Buches können um 30% abweichen.
- Mit sowohl `"pages"` als auch `"words"` leitet der Planer die Wörter pro Seite dieser Ausgabe ab (Middlemarch 316.000 / 880 = 359).
- Mit nur Wörtern sind Seiten Schätzungen (Wörter / Wörter pro Seite); sage das im Plan.
## Hörbücher
- `"audio_minutes"` ist die Laufzeit, `"speed"` die Wiedergabegeschwindigkeit: benötigte Zeit = Laufzeit / Geschwindigkeit (400 Min bei 1.25x = 320 Min).
- Audio passt zu Pendelzeiten, Hausarbeit und Spaziergängen: sage, ob diese Minuten extra sind oder bereits in den Tagesminuten enthalten.
- Positionen werden als h:mm des Audios angezeigt.
FILE:references/pacing-and-catch-up.md
# Tempo und Aufholen
## Wie der Planer plant
- Frühester Termin zuerst: Das Buch mit dem nächsten Enddatum (Termin minus Puffertage) wird zuerst gelesen; Bücher ohne Termin kommen zuletzt.
- Gleichmäßiger Modus (Standard): Für jeden Abschnitt bis zum engsten Termin verwendet er den kleinsten gleichen Anteil deiner Tagesminuten, der noch rechtzeitig fertig wird, sodass 30-Minuten-Tage zu 20-Minuten-Tagen werden können, wenn Platz ist. Nach diesem Termin wird der nächste Abschnitt neu berechnet.
- ASAP-Modus: Nutzt jeden Tag alle Minuten; Bücher enden so früh wie möglich.
- Wenn ein Termin selbst bei vollem Tempo nicht zu schaffen ist, wird jeder Tag davor in vollem Tempo geplant und das Buch als RP01 markiert.
- Tägliche Stopps verschieben sich zu einem Kapitelende, wenn eines innerhalb von `chapter_snap_pages` (Standard 8) und einem Viertel der Tagesseiten liegt.
## Puffer und Aufholtage
- Puffertage (`buffer_days`, Standard 1) liegen zwischen dem geplanten Ende und dem Treffen: Zeit, Notizen zu wiederholen oder eine schlechte Woche zu absorbieren.
- Aufhol-Wochentage (`catch_up_days`, z.B. Sonntag) bekommen keine geplante Lesezeit. Nutze sie nur, wenn du zurück bist; wenn du im Plan bist, sind sie freie Zeit.
- Ausnahmen (Reisen, Spätschichten, ein ruhiger Samstag) überschreiben die Wochentagsminuten für diese Daten.
## Wenn du zurückfällst
1. Einen Tag verpasst: Lies die Seiten dieses Tages am nächsten Aufholtag oder füge ein Drittel davon an jedem der nächsten drei Tage hinzu.
2. Mehr als zwei Tage zurück: Plane ab heute neu mit `--from <heute> --at "<Titel>=<aktuelle Seite>"`, statt dem alten Plan hinterherzujagen.
3. Immer noch nicht rechtzeitig (RP01): Wähle eines - das lange Buch jetzt parallel beginnen, 10-20 Minuten an einigen Tagen hinzufügen, das Hörbuch für Pendelzeiten nutzen, markierte Abschnitte überfliegen (Vorworte, Anhänge, Notizen), oder entscheiden, teilweise gelesen zu kommen und es der Gruppe sagen.
## Anzeichen, dass der Plan zu eng ist
- RP04: Über 90% deiner Zeit in einem Abschnitt genutzt; jede Krankheit oder späte Woche bricht ihn.
- RP02: Das Buch passt nur, indem die Puffertage genutzt werden.
- Viele Tage mit "2 Sitzungen" (RP03): Ziehe in Betracht, das Lesen absichtlich auf Morgen und Abend zu verteilen.
## Buchclub-Tipps
- Notiere die Seite oder das Kapitel, das die Gruppe besprechen wird, wenn es nicht das ganze Buch ist, und verwende das als `pages` des Buches.
- Frage bei langen Klassikern, ob das Treffen die erste Hälfte abdeckt; plane zwei Termine.
- Halte einen Haftnotizzettel im Buch mit den wöchentlichen Kontrollpunkten aus dem Plan.
FILE:templates/reading-plan.md
# Leseplan: <Name> (<Start> bis <letzter Termin>)
**Tempo:** <wpm> Wörter/min, etwa <Seiten> Seiten in einer 30-Minuten-Sitzung
**Zeit:** <Minuten pro Wochentag>; Aufholtag(e): <Tag>; Puffer: <n> Tag(e) vor jedem Treffen
**Urteil:** <jeder Termin eingehalten | BUCH kann nicht bis DATUM beendet werden - siehe Optionen>
## Bücher
| Buch | Seiten übrig | Benötigte Zeit | Treffen / Termin | Fertig bis | Geplantes Ende |
|---|---|---|---|---|---|
| <Titel> | <pp> | <h:mm> | <Datum - Ereignis> | <Datum> | <Datum> |
## Woche für Woche
### Woche vom <Mo-Datum>
- Mo: <Buch S. a-b>
- Di: <...>
- So: Aufholen / frei
**Kontrollpunkt Sonntagabend:** <Buch S. X>
## Wenn ein Termin nicht passt
- Defizit: <Seiten> Seiten von <Buch> bis <Datum>
- Optionen: <+N Min an Lesetagen | jetzt parallel beginnen | Hörbuch für Pendelzeiten | Kapitel x-y überfliegen | teilweise gelesen kommen>
- Gewählt: <...>
## Zurückfallen
- Einen Tag verpasst? Nutze <Aufholtag> oder füge <n> Seiten an den nächsten drei Tagen hinzu.
- Mehr als zwei Tage zurück: Plane ab heute neu (`--from <Datum> --at "<Titel>=<Seite>"`).
FILE:examples/book-club.json
{
"name": "Leseplan: Nov-Dez 2026 (zwei Buchclubs und ein Arbeitsbuch)",
"start": "2026-11-03",
"reader": {"words_per_minute": 240, "words_per_page": 280},
"minutes": {"mon": 30, "tue": 30, "wed": 20, "thu": 30, "fri": 25, "sat": 60, "sun": 45},
"catch_up_days": ["sun"],
"buffer_days": 2,
"max_session_minutes": 60,
"chapter_snap_pages": 8,
"exceptions": [
{"dates": "2026-11-13..2026-11-15", "minutes": 0, "note": "Wochenendtrip"},
{"dates": "2026-11-26", "minutes": 15, "note": "Spätschicht"},
{"dates": "2026-12-05", "minutes": 120, "note": "ruhiger Samstag"}
],
"books": [
{"title": "The Remains of the Day", "pages": 245, "words_per_page": 265, "deadline": "2026-11-19", "event": "Donnerstag-Buchclub",
"chapters": [1, 9, 45, 101, 125, 165, 212, 239]},
{"title": "Thinking in Systems", "pages": 218, "start_page": 23, "difficulty": 1.3, "deadline": "2026-12-01", "event": "Team-Lesegruppe"},
{"title": "Middlemarch", "words": 316000, "pages": 880, "deadline": "2026-12-17", "event": "Klassiker-Club"},
{"title": "Piranesi (Hörbuch)", "audio_minutes": 400, "speed": 1.25}
]
}
FILE:examples/book-club-plan.md
# Leseplan: Nov-Dez 2026 (zwei Buchclubs und ein Arbeitsbuch)
Start 2026-11-03, gleichmäßiger Modus, 2 Puffertag(e) vor jedem Termin, Aufholtage: sun. Geplantes Lesen: 36 h 44 min über 81 Tage.
**Urteil: 1 Buch/Bücher kann/können nicht bis zu ihrem Termin beendet werden - Plan ändern**
## Bücher
| Buch | Übrig | Geschätzte Zeit | Termin | Fertig bis | Geplantes Ende | Tage übrig | Ø pro Lesetag |
|---|---|---|---|---|---|---|---|
| The Remains of the Day | 245 pp | 4:31 h | 2026-11-19 (Donnerstag-Buchclub) | 2026-11-17 | 2026-11-12 | 7 | 27 pp |
| Thinking in Systems | 196 pp | 4:57 h | 2026-12-01 (Team-Lesegruppe) | 2026-11-29 | 2026-11-27 | 4 | 16 pp |
| Middlemarch | 880 pp | 21:57 h | 2026-12-17 (Klassiker-Club) | 2026-12-15 | 2027-01-11 | -25 | 23 pp |
| Piranesi (Hörbuch) | 6:40 Audio | 5:20 h | - | - | 2027-01-22 | - | 0:36 |
## Erkenntnisse
- **HOCH RP01** Middlemarch kann nicht bis 2026-12-17 (Klassiker-Club) beendet werden: etwa 452 Seiten zu wenig bei deinem Tempo; voraussichtliches Ende 2027-01-11. Optionen: +17 Min an jedem der 42 Lesetage bis dahin, schneller lesen, etwa 452 Seiten überfliegen oder überspringen, oder früher beginnen
- **NIEDRIG RP03** 1 Tag(e) planen mehr als 60 Minuten (z.B. 2026-12-05: 120 Min) - in zwei Sitzungen aufteilen
- **INFO RP06** Piranesi (Hörbuch) ist ein Hörbuch: Der Plan zählt Hörzeit bei 1.25x Geschwindigkeit; Pendelzeiten und Hausarbeit können es abdecken
## Tag für Tag
| Datum | Tag | Minuten | Lesen | Notiz |
|---|---|---|---|---|
| 2026-11-03 | Di | 30 | The Remains of the Day S. 1-27 (27 pp) | |
| 2026-11-04 | Mi | 19 | The Remains of the Day S. 28-44 (17 pp) | |
| 2026-11-05 | Do | 30 | The Remains of the Day S. 45-71 (27 pp) | |
| 2026-11-06 | Fr | 25 | The Remains of the Day S. 72-94 (23 pp) | |
| 2026-11-07 | Sa | 60 | The Remains of the Day S. 95-148 (54 pp) | |
| 2026-11-08 | So | - | - | Aufholen / frei (45 Min verfügbar) |
| 2026-11-09 | Mo | 30 | The Remains of the Day S. 149-175 (27 pp) | |
| 2026-11-10 | Di | 30 | The Remains of the Day S. 176-202 (27 pp) | |
| 2026-11-11 | Mi | 20 | The Remains of the Day S. 203-221 (19 pp) | |
| 2026-11-12 | Do | 30 | The Remains of the Day S. 222-245 (24 pp); Thinking in Systems S. 23-24 (2 pp) | |
| 2026-11-13 | Fr | - | - | Wochenendtrip |
| 2026-11-14 | Sa | - | - | Wochenendtrip |
| 2026-11-15 | So | - | - | Wochenendtrip |
| 2026-11-16 | Mo | 30 | Thinking in Systems S. 25-44 (20 pp) | |
| 2026-11-17 | Di | 30 | Thinking in Systems S. 45-64 (20 pp) | fertig bis The Remains of the Day |
| 2026-11-18 | Mi | 20 | Thinking in Systems S. 65-77 (13 pp) | |
| 2026-11-19 | Do | 30 | Thinking in Systems S. 78-97 (20 pp) | TERMIN The Remains of the Day - Donnerstag-Buchclub |
| 2026-11-20 | Fr | 25 | Thinking in Systems S. 98-113 (16 pp) | |
| 2026-11-21 | Sa | 60 | Thinking in Systems S. 114-153 (40 pp) | |
| 2026-11-22 | So | - | - | Aufholen / frei (45 Min verfügbar) |
| 2026-11-23 | Mo | 30 | Thinking in Systems S. 154-172 (19 pp) | |
| 2026-11-24 | Di | 30 | Thinking in Systems S. 173-192 (20 pp) | |
| 2026-11-25 | Mi | 20 | Thinking in Systems S. 193-205 (13 pp) | |
| 2026-11-26 | Do | 15 | Thinking in Systems S. 206-215 (10 pp) | Spätschicht |
| 2026-11-27 | Fr | 25 | Thinking in Systems S. 216-218 (3 pp); Middlemarch S. 1-14 (14 pp) | |
| 2026-11-28 | Sa | 60 | Middlemarch S. 15-54 (40 pp) | |
| 2026-11-29 | So | - | - | Aufholen / frei (45 Min verfügbar); fertig bis Thinking in Systems |
| 2026-11-30 | Mo | 30 | Middlemarch S. 55-74 (20 pp) | |
| 2026-12-01 | Di | 30 | Middlemarch S. 75-94 (20 pp) | TERMIN Thinking in Systems - Team-Lesegruppe |
| 2026-12-02 | Mi | 20 | Middlemarch S. 95-107 (13 pp) | |
| 2026-12-03 | Do | 30 | Middlemarch S. 108-128 (21 pp) | |
| 2026-12-04 | Fr | 25 | Middlemarch S. 129-144 (16 pp) | |
| 2026-12-05 | Sa | 120 | Middlemarch S. 145-224 (80 pp) | ruhiger Samstag; 2 Sitzungen |
| 2026-12-06 | So | - | - | Aufholen / frei (45 Min verfügbar) |
| 2026-12-07 | Mo | 30 | Middlemarch S. 225-244 (20 pp) | |
| 2026-12-08 | Di | 30 | Middlemarch S. 245-265 (21 pp) | |
| 2026-12-09 | Mi | 20 | Middlemarch S. 266-278 (13 pp) | |
| 2026-12-10 | Do | 30 | Middlemarch S. 279-298 (20 pp) | |
| 2026-12-11 | Fr | 25 | Middlemarch S. 299-315 (17 pp) | |
| 2026-12-12 | Sa | 60 | Middlemarch S. 316-355 (40 pp) | |
| 2026-12-13 | So | - | - | Aufholen / frei (45 Min verfügbar) |
| 2026-12-14 | Mo | 30 | Middlemarch S. 356-375 (20 pp) | |
| 2026-12-15 | Di | 30 | Middlemarch S. 376-395 (20 pp) | fertig bis Middlemarch |
| 2026-12-16 | Mi | 20 | Middlemarch S. 396-408 (13 pp) | |
| 2026-12-17 | Do | 30 | Middlemarch S. 409-428 (20 pp) | TERMIN Middlemarch - Klassiker-Club |
| 2026-12-18 | Fr | 25 | Middlemarch S. 429-445 (17 pp) | |
| 2026-12-19 | Sa | 60 | Middlemarch S. 446-485 (40 pp) | |
| 2026-12-20 | So | - | - | Aufholen / frei (45 Min verfügbar) |
| 2026-12-21 | Mo | 30 | Middlemarch S. 486-505 (20 pp) | |
| 2026-12-22 | Di | 30 | Middlemarch S. 506-525 (20 pp) | |
| 2026-12-23 | Mi | 20 | Middlemarch S. 526-539 (14 pp) | |
| 2026-12-24 | Do | 30 | Middlemarch S. 540-559 (20 pp) | |
| 2026-12-25 | Fr | 25 | Middlemarch S. 560-575 (16 pp) | |
| 2026-12-26 | Sa | 60 | Middlemarch S. 576-615 (40 pp) | |
| 2026-12-27 | So | - | - | Aufholen / frei (45 Min verfügbar) |
| 2026-12-28 | Mo | 30 | Middlemarch S. 616-635 (20 pp) | |
| 2026-12-29 | Di | 30 | Middlemarch S. 636-656 (21 pp) | |
| 2026-12-30 | Mi | 20 | Middlemarch S. 657-669 (13 pp) | |
| 2026-12-31 | Do | 30 | Middlemarch S. 670-689 (20 pp) | |
| 2027-01-01 | Fr | 25 | Middlemarch S. 690-706 (17 pp) | |
| 2027-01-02 | Sa | 60 | Middlemarch S. 707-746 (40 pp) | |
| 2027-01-03 | So | - | - | Aufholen / frei (45 Min verfügbar) |
| 2027-01-04 | Mo | 30 | Middlemarch S. 747-766 (20 pp) | |
| 2027-01-05 | Di | 30 | Middlemarch S. 767-786 (20 pp) | |
| 2027-01-06 | Mi | 20 | Middlemarch S. 787-799 (13 pp) | |
| 2027-01-07 | Do | 30 | Middlemarch S. 800-819 (20 pp) | |
| 2027-01-08 | Fr | 25 | Middlemarch S. 820-836 (17 pp) | |
| 2027-01-09 | Sa | 60 | Middlemarch S. 837-876 (40 pp) | |
| 2027-01-10 | So | - | - | Aufholen / frei (45 Min verfügbar) |
| 2027-01-11 | Mo | 30 | Middlemarch S. 877-880 (4 pp); Piranesi (Hörbuch) 0:00-0:30 | |
| 2027-01-12 | Di | 30 | Piranesi (Hörbuch) 0:30-1:08 | |
| 2027-01-13 | Mi | 20 | Piranesi (Hörbuch) 1:08-1:33 | |
| 2027-01-14 | Do | 30 | Piranesi (Hörbuch) 1:33-2:10 | |
| 2027-01-15 | Fr | 25 | Piranesi (Hörbuch) 2:10-2:41 | |
| 2027-01-16 | Sa | 60 | Piranesi (Hörbuch) 2:41-3:56 | |
| 2027-01-17 | So | - | - | Aufholen / frei (45 Min verfügbar) |
| 2027-01-18 | Mo | 30 | Piranesi (Hörbuch) 3:56-4:34 | |
| 2027-01-19 | Di | 30 | Piranesi (Hörbuch) 4:34-5:11 | |
| 2027-01-20 | Mi | 20 | Piranesi (Hörbuch) 5:11-5:36 | |
| 2027-01-21 | Do | 30 | Piranesi (Hörbuch) 5:36-6:14 | |
| 2027-01-22 | Fr | 21 | Piranesi (Hörbuch) 6:14-6:40 | |
## Wöchentliche Kontrollpunkte (wo du bis Sonntagabend sein solltest)
- 2026-11-08: The Remains of the Day S. 148
- 2026-11-15: Thinking in Systems S. 24
- 2026-11-22: Thinking in Systems S. 153
- 2026-11-29: Middlemarch S. 54
- 2026-12-06: Middlemarch S. 224
- 2026-12-13: Middlemarch S. 355
- 2026-12-20: Middlemarch S. 485
- 2026-12-27: Middlemarch S. 615
- 2027-01-03: Middlemarch S. 746
- 2027-01-10: Middlemarch S. 876
- 2027-01-17: Piranesi (Hörbuch) 3:56
## Wenn du zurückfällst
- The Remains of the Day: Ein verpasster Tag kostet etwa 27 Seiten. Hole sie an den freien Tag(en) 2026-11-08 nach oder füge 10 Seiten an jedem der nächsten 3 Lesetage hinzu. Plane jederzeit neu mit `--from <Datum> --at "The Remains of the Day=<Seite>"`.
- Thinking in Systems: Ein verpasster Tag kostet etwa 16 Seiten. Hole sie an den freien Tag(en) 2026-11-22, 2026-11-29 nach oder füge 6 Seiten an jedem der nächsten 3 Lesetage hinzu. Plane jederzeit neu mit `--from <Datum> --at "Thinking in Systems=<Seite>"`.
- Middlemarch: Ein verpasster Tag kostet etwa 23 Seiten. Hole sie an den freien Tag(en) 2026-11-29, 2026-12-06, 2026-12-13 nach oder füge 8 Seiten an jedem der nächsten 3 Lesetage hinzu. Plane jederzeit neu mit `--from <Datum> --at "Middlemarch=<Seite>"`.
FILE:scripts/reading_plan.py
#!/usr/bin/env python3
"""Leseplan und Seiten-Tempo-Planer (nur Standardbibliothek, Python 3.8+).
Verwandelt eine Liste von Büchern (Seiten, Wortzahlen oder Hörbuchlänge), deine Lesegeschwindigkeit und die
Minuten, die du jeden Tag hast, in einen Tag-für-Tag-Plan mit Seitenbereichen, der jeden Termin
(Buchclub, Kurs, Prüfung) einhält, Puffertage vor jedem Termin und Aufholtage in der
Woche behält, die Last gleichmäßig verteilt und dir sagt, was du tun sollst, wenn du zurückfällst.
Verwendung:
reading_plan.py PLAN.json [--mode even|asap] [--from YYYY-MM-DD] [--at "Titel=Seite"]... [--json]
reading_plan.py --self-test
Exit: 0 = jeder Termin eingehalten, 1 = mindestens ein Buch kann nicht bis zu seinem Termin beendet werden,
2 = falsche Eingabe.
"""
import argparse, datetime, json, math, re, sys
DAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
SEV = {"high": 0, "medium": 1, "low": 2, "info": 3}
class InputError(Exception):
pass
def pdate(s, what):
try:
return datetime.date.fromisoformat(str(s))
except ValueError:
raise InputError("%s muss YYYY-MM-DD sein, erhalten: %r" % (what, s))
def expand_dates(spec):
spec = str(spec)
if ".." in spec:
a, b = spec.split("..", 1)
a, b = pdate(a.strip(), "Datumsbereich"), pdate(b.strip(), "Datumsbereich")
if b < a:
raise InputError("Datumsbereich %s endet vor seinem Beginn" % spec)
return [a + datetime.timedelta(days=i) for i in range((b - a).days + 1)]
return [pdate(spec.strip(), "Datum")]
# ---------------------------------------------------------------- Modell
def load(cfg, start_override=None, at=None):
if not isinstance(cfg, dict) or not isinstance(cfg.get("books"), list) or not cfg["books"]:
raise InputError("Plan benötigt eine nicht-leere 'books'-Liste")
reader = cfg.get("reader") or {}
wpm = float(reader.get("words_per_minute", 230))
wpp_default = float(reader.get("words_per_page", 275))
pph = reader.get("pages_per_hour")
if wpm <= 0 or wpp_default <= 0:
raise InputError("words_per_minute und words_per_page müssen positiv sein")
start = pdate(start_override or cfg.get("start") or datetime.date.today().isoformat(), "start")
mins = cfg.get("minutes")
if isinstance(mins, (int, float)):
mins = {d: mins for d in DAYS}
if not isinstance(mins, dict):
raise InputError("'minutes' muss eine Zahl oder {mon: .., tue: ..} sein")
mins = {k.lower()[:3]: float(v) for k, v in mins.items()}
bad = set(mins) - set(DAYS)
if bad:
raise InputError("unbekannte Wochentag-Schlüssel in minutes: %s" % ", ".join(sorted(bad)))
catch = {d.lower()[:3] for d in cfg.get("catch_up_days", [])}
exc = {}
for e in cfg.get("exceptions", []):
for d in expand_dates(e.get("dates") or e.get("date")):
exc[d] = (float(e.get("minutes", 0)), e.get("note", ""))
books = []
at = at or {}
for i, b in enumerate(cfg["books"], 1):
t = b.get("title") or "Buch %d" % i
if "audio_minutes" in b:
speed = float(b.get("speed", 1.0))
total = float(b["audio_minutes"])
unit_min = 1.0 / speed # Minuten deiner Zeit pro Minute Audio
units, kind = total, "audio"
done = float(at.get(t, b.get("start_minute", 0)))
else:
wpp = float(b.get("words_per_page") or (float(b["words"]) / float(b["pages"]) if b.get("words") and b.get("pages") else wpp_default))
pages = float(b["pages"]) if b.get("pages") else (float(b["words"]) / wpp if b.get("words") else None)
if not pages or pages <= 0:
raise InputError("Buch %r benötigt pages, words oder audio_minutes" % t)
diff = float(b.get("difficulty", 1.0))
if pph and not b.get("words"):
unit_min = 60.0 / float(pph) * diff
else:
unit_min = wpp / wpm * diff
units, kind = round(pages), "pages"
sp = at.get(t, b.get("start_page", 1))
done = max(0.0, float(sp) - 1)
dl = pdate(b["deadline"], "Termin von %r" % t) if b.get("deadline") else None
chapters = sorted(int(c) for c in b.get("chapters", []))
books.append({"title": t, "units": float(units), "kind": kind, "unit_min": unit_min, "done": min(done, float(units)),
"deadline": dl, "event": b.get("event", ""), "chapters": chapters, "order": i})
buf = int(cfg.get("buffer_days", 1))
return {"start": start, "mins": mins, "catch": catch, "exc": exc, "books": books, "buffer": buf,
"max_session": float(cfg.get("max_session_minutes", 0) or 0), "snap": int(cfg.get("chapter_snap_pages", 8)),
"name": cfg.get("name", "Leseplan"), "wpm": wpm}
def capacity(m, d):
"""(geplante Minuten, verfügbare Minuten inkl. Aufholtag, Notiz)."""
if d in m["exc"]:
v, note = m["exc"][d]
return v, v, note or ("kein Lesen" if v == 0 else "")
wd = DAYS[d.weekday()]
v = m["mins"].get(wd, 0.0)
if wd in m["catch"]:
return 0.0, v, "Aufholtag"
return v, v, ""
def plan(m, mode="even"):
F = []
books = [b for b in m["books"]]
for b in books:
b["need"] = (b["units"] - b["done"]) * b["unit_min"]
if b["need"] <= 0:
F.append(("info", "RP05", "%s ist bereits beendet - ausgelassen" % b["title"]))
b["target"] = None
if b["deadline"] and b["deadline"] < m["start"]:
if b["need"] > 0:
F.append(("medium", "RP05", "%s: Termin %s ist bereits vorbei - ohne Termin geplant" % (b["title"], b["deadline"])))
b["deadline"] = None
if b["deadline"]:
b["target"] = b["deadline"] - datetime.timedelta(days=m["buffer"])
if b["target"] < m["start"]:
b["target"] = m["start"]
F.append(("medium", "RP05", "%s: Termin %s ist zu nah für %d Puffertag(e) - bis zum Termin geplant" % (b["title"], b["deadline"], m["buffer"])))
todo = [b for b in books if b["need"] > 0]
dated = sorted([b for b in todo if b["target"]], key=lambda b: (b["target"], b["order"]))
undated = [b for b in todo if not b["target"]]
seq = dated + undated
last = max([b["deadline"] for b in dated] or [m["start"]])
# Horizont: genug Tage für alles bei voller Kapazität, plus Puffer
weekly = sum(m["mins"].get(d, 0) for d in DAYS if d not in m["catch"]) or 1
total_need = sum(b["need"] for b in seq)
horizon_end = max(last, m["start"]) + datetime.timedelta(days=int(total_need / weekly * 7) + 60)
days, d = [], m["start"]
while d <= horizon_end:
p, a, note = capacity(m, d)
days.append({"date": d, "cap": p, "avail": a, "note": note, "planned": 0.0, "items": []})
d += datetime.timedelta(days=1)
idx = {x["date"]: i for i, x in enumerate(days)}
# Lastfaktor pro Segment (gleichmäßiger Modus): kleinster gleichmäßiger Anteil der Kapazität, der jeden Zieltermin einhält
load = [1.0] * len(days)
if mode == "even" and dated:
seg_start, k0 = 0, 0
while k0 < len(dated):
best, bk = 0.0, k0
cum = 0.0
for k in range(k0, len(dated)):
cum += dated[k]["need"]
ti = idx[dated[k]["target"]]
cap = sum(x["cap"] for x in days[seg_start:ti + 1])
r = cum / cap if cap > 0 else float("inf")
if r >= best:
best, bk = r, k
L = min(best * 1.03, 1.0) # 3% Spielraum für Kapitelend-Stopps und Rundung
end = idx[dated[bk]["target"]]
for i in range(seg_start, end + 1):
load[i] = L
seg_start, k0 = end + 1, bk + 1
# nach dem letzten Termin: undatierte Bücher mit der durchschnittlichen Last des Plans bisher (mindestens 0.5)
tail = max(0.5, max(load[:seg_start] or [1.0]))
for i in range(seg_start, len(days)):
load[i] = tail if undated else 1.0
# Zuweisung (frühester Termin zuerst), Position jedes Buchs verfolgen
qi = 0
pos = {b["title"]: b["done"] for b in seq}
for i, day in enumerate(days):
budget = day["cap"] * load[i]
while budget > 1e-9 and qi < len(seq):
b = seq[qi]
remaining = (b["units"] - pos[b["title"]]) * b["unit_min"]
use = min(budget, remaining)
before = pos[b["title"]]
after = before + use / b["unit_min"]
if after > b["units"] - 1e-6:
after = b["units"]
elif b["kind"] == "pages":
snapped = snap(after, b, m["snap"], use / b["unit_min"])
if snapped != after:
after = snapped
use = (after - before) * b["unit_min"]
budget = use # am Kapitelende stoppen: der Tag endet hier
pos[b["title"]] = after
day["items"].append({"title": b["title"], "from": before, "to": after, "kind": b["kind"], "minutes": use})
day["planned"] += use
budget -= use
if after >= b["units"] - 1e-6:
b["finish"] = day["date"]; qi += 1
elif budget <= 1e-9 or use <= 1e-9:
break
if qi >= len(seq):
break
days = [x for x in days if x["date"] <= max([b.get("finish", m["start"]) for b in seq] + [last])]
for b in seq:
b.setdefault("finish", None)
# Erkenntnisse
for b in dated:
f = b["finish"]
if f is None or f > b["deadline"]:
avail = [x for x in days if x["date"] <= b["deadline"]]
share = sum(it["minutes"] for x in avail for it in x["items"] if it["title"] == b["title"])
short_min = b["need"] - share
# auch Aufhol-/Pufferzeit zählen, die bis zum Termin verfügbar wäre
reading_days = sum(1 for x in avail if x["avail"] > 0) or 1
short_units = short_min / b["unit_min"]
msg = "%s kann nicht bis %s%s beendet werden: etwa %d %s zu wenig bei deinem Tempo; voraussichtliches Ende %s. Optionen: +%d Min an jedem der %d Lesetage bis dahin, schneller lesen, etwa %d %s überfliegen oder überspringen, oder früher beginnen" % (
b["title"], b["deadline"], " (%s)" % b["event"] if b["event"] else "", math.ceil(short_units), "Seiten" if b["kind"] == "pages" else "Audiominuten",
f or "jenseits des Plans", math.ceil(short_min / reading_days), reading_days, math.ceil(short_units), "Seiten" if b["kind"] == "pages" else "Audiominuten")
F.append(("high", "RP01", msg))
elif f > b["target"]:
F.append(("medium", "RP02", "%s endet am %s, innerhalb der %d Puffertag(e) vor %s - kein Spielraum für einen verpassten Tag" % (b["title"], f, m["buffer"], b["deadline"])))
if mode == "even":
peak = max(load) if load else 0
if 0.9 <= peak <= 1.0 and not any(f[1] == "RP01" for f in F):
F.append(("medium", "RP04", "Der Plan nutzt %d%% deiner geplanten Lesezeit in seinem vollsten Abschnitt - eine schlechte Woche wird dich in den Puffer drängen" % round(peak * 100)))
if m["max_session"]:
long = [x for x in days if x["planned"] > m["max_session"] + 0.5]
if long:
F.append(("low", "RP03", "%d Tag(e) planen mehr als %d Minuten (z.B. %s: %d Min) - in zwei Sitzungen aufteilen" % (
len(long), m["max_session"], long[0]["date"], round(long[0]["planned"]))))
for b in seq:
if b["kind"] == "audio":
F.append(("info", "RP06", "%s ist ein Hörbuch: Der Plan zählt Hörzeit bei %sx Geschwindigkeit; Pendelzeiten und Hausarbeit können es abdecken" % (b["title"], ("%.2f" % (1 / b["unit_min"])).rstrip("0").rstrip("."))))
F.sort(key=lambda f: (SEV[f[0]], f[1]))
return {"days": days, "books": seq, "all_books": books, "findings": F, "load": load}
def snap(after, b, snap_pages, day_units):
"""Bewege den Tagesstopp zu einem Kapitelende, wenn eines nahe ist (innerhalb snap_pages und 25% des Tages)."""
if not b["chapters"] or snap_pages <= 0:
return after
lim = min(snap_pages, max(1.0, day_units * 0.25))
ends = [c - 1 for c in b["chapters"] if c - 1 > 0]
best = None
for e in ends:
if abs(e - after) <= lim and e > after - day_units + 0.5 and (best is None or abs(e - after) < abs(best - after)):
best = e
return float(best) if best is not None else after
# ---------------------------------------------------------------- Darstellung
def fmt_item(it):
if it["kind"] == "audio":
return "%s %s-%s" % (it["title"], hm(it["from"]), hm(it["to"]))
a = int(round(it["from"])) + 1
z = int(round(it["to"]))
if z < a:
z = a
return "%s S. %d-%d (%d pp)" % (it["title"], a, z, z - a + 1)
def hm(x):
x = int(round(x))
return "%d:%02d" % (x // 60, x % 60)
def checkpoints(r):
out = []
pos = {}
for x in r["days"]:
for it in x["items"]:
pos[it["title"]] = it
if x["date"].weekday() == 6 and pos: # Sonntage
out.append((x["date"], "; ".join("%s %s" % (t, ("S. %d" % round(it["to"]) if it["kind"] == "pages" else hm(it["to"]))) for t, it in pos.items() if it["to"] < next(b["units"] for b in r["books"] if b["title"] == t) - 1e-6) or "alle aktuellen Bücher fertig"))
return out
def report(r, m, mode):
hi = [f for f in r["findings"] if f[0] == "high"]
out = ["# %s" % m["name"], ""]
tot_min = sum(x["planned"] for x in r["days"])
out.append("Start %s, %s-Modus, %d Puffertag(e) vor jedem Termin, Aufholtage: %s. Geplantes Lesen: %d h %02d min über %d Tage." % (
m["start"], mode, m["buffer"], ", ".join(sorted(m["catch"], key=DAYS.index)) or "keine", tot_min // 60, tot_min % 60, len(r["days"])))
out += ["", "**Urteil: %s**" % ("%d Buch/Bücher kann/können nicht bis zu ihrem Termin beendet werden - Plan ändern" % len(hi) if hi else "jeder Termin eingehalten"), ""]
out += ["## Bücher", "", "| Buch | Übrig | Geschätzte Zeit | Termin | Fertig bis | Geplantes Ende | Tage übrig | Ø pro Lesetag |", "|---|---|---|---|---|---|---|---|"]
for b in r["all_books"]:
left = b["units"] - b["done"]
rd = [x for x in r["days"] if any(it["title"] == b["title"] for it in x["items"])]
avg = left / len(rd) if rd else 0
spare = (b["deadline"] - b["finish"]).days if b.get("finish") and b["deadline"] else None
out.append("| %s | %s | %s | %s | %s | %s | %s | %s |" % (
b["title"], ("%d pp" % round(left)) if b["kind"] == "pages" else hm(left) + " Audio", hm(b["need"]) + " h",
"%s %s" % (b["deadline"], "(%s)" % b["event"] if b["event"] else "") if b["deadline"] else "-",
b["target"] or "-", b.get("finish") or ("fertig" if b["need"] <= 0 else "NICHT RECHTZEITIG"),
"-" if spare is None else spare, ("%.0f pp" % avg) if b["kind"] == "pages" else hm(avg)))
out += ["", "## Erkenntnisse", ""]
out += ["- **%s %s** %s" % (s.upper(), c, msg) for s, c, msg in r["findings"]] or ["- keine"]
out += ["", "## Tag für Tag", "", "| Datum | Tag | Minuten | Lesen | Notiz |", "|---|---|---|---|---|"]
events = {}
for b in r["all_books"]:
if b["deadline"]:
events.setdefault(b["deadline"], []).append("TERMIN %s%s" % (b["title"], " - " + b["event"] if b["event"] else ""))
if b["target"] and b["target"] != b["deadline"]:
events.setdefault(b["target"], []).append("fertig bis %s" % b["title"])
for x in r["days"]:
note = [x["note"]] if x["note"] else []
if x["note"] == "Aufholtag" or (x["planned"] < 0.5 and x["avail"] > 0):
note = ["Aufholen / frei (%d Min verfügbar)" % x["avail"]] if x["avail"] > 0 else note
note += events.get(x["date"], [])
if m["max_session"] and x["planned"] > m["max_session"] + 0.5:
note.append("2 Sitzungen")
out.append("| %s | %s | %s | %s | %s |" % (x["date"], x["date"].strftime("%a"), round(x["planned"]) if x["planned"] >= 0.5 else "-",
"; ".join(fmt_item(it) for it in x["items"]) or "-", "; ".join(note)))
cps = checkpoints(r)
if cps:
out += ["", "## Wöchentliche Kontrollpunkte (wo du bis Sonntagabend sein solltest)", ""]
out += ["- %s: %s" % (d, s) for d, s in cps]
out += ["", "## Wenn du zurückfällst", ""]
for b in r["books"]:
if b["kind"] != "pages" or not b["deadline"]:
continue
rd = [x for x in r["days"] if any(it["title"] == b["title"] for it in x["items"])]
if not rd:
continue
day_pp = sum(it["to"] - it["from"] for x in rd for it in x["items"] if it["title"] == b["title"]) / len(rd)
spare = [x for x in r["days"] if x["date"] <= b["deadline"] and x["date"] >= rd[0]["date"] and x["avail"] > 0 and x["planned"] < 0.5]
out.append("- %s: Ein verpasster Tag kostet etwa %d Seiten. Hole sie an %s nach oder füge %d Seiten an jedem der nächsten 3 Lesetage hinzu. Plane jederzeit neu mit `--from <Datum> --at \"%s=<Seite>\"`." % (
b["title"], round(day_pp), ("den freien Tag(en) " + ", ".join(str(x["date"]) for x in spare[:3])) if spare else "keinem freien Tag vor dem Termin", math.ceil(day_pp / 3), b["title"]))
out.append("")
return "\n".join(out)
# ---------------------------------------------------------------- Selbsttest
def self_test():
tests = []
def t(name, cond):
tests.append((name, bool(cond)))
def run(cfg, mode="even", **kw):
m = load(cfg, **kw)
return plan(m, mode), m
base = {"start": "2026-11-02", "reader": {"words_per_minute": 250, "words_per_page": 250}, "minutes": 30, "buffer_days": 0}
r, m = run(dict(base, books=[{"title": "A", "pages": 60, "deadline": "2026-11-08"}]), "asap")
t("asap: 30 Seiten/Tag bei 1 Seite/Min endet an Tag 2", r["books"][0]["finish"] == datetime.date(2026, 11, 3))
r, m = run(dict(base, books=[{"title": "A", "pages": 70, "deadline": "2026-11-08"}]))
t("even: über die Woche verteilt (~10 Min/Tag + 3% Spielraum)", r["books"][0]["finish"] >= datetime.date(2026, 11, 7) and 10 <= r["days"][0]["planned"] <= 10.31)
r, m = run(dict(base, books=[{"title": "A", "pages": 300, "deadline": "2026-11-08"}]))
t("undurchführbar -> RP01 hoch", any(f[1] == "RP01" and f[0] == "high" for f in r["findings"]))
t("RP01 schlägt zusätzliche Minuten vor", any("+13 Min" in f[2] for f in r["findings"] if f[1] == "RP01"))
r, m = run(dict(base, buffer_days=2, books=[{"title": "A", "pages": 70, "deadline": "2026-11-08"}]))
t("Puffertage: 2 Tage früher fertig", r["books"][0]["finish"] == datetime.date(2026, 11, 6))
r, m = run(dict(base, catch_up_days=["sun"], books=[{"title": "A", "pages": 60, "deadline": "2026-11-08"}]))
t("Aufhol-Sonntag bekommt keine geplante Lesezeit", r["days"][-1]["planned"] == 0 and r["books"][0]["finish"] == datetime.date(2026, 11, 7))
r, m = run(dict(base, exceptions=[{"dates": "2026-11-03..2026-11-04", "minutes": 0, "note": "Reise"}], books=[{"title": "A", "pages": 50, "deadline": "2026-11-08"}]))
t("Ausnahmebereich ohne Lesen", all(x["planned"] == 0 for x in r["days"][1:3]))
two = [{"title": "Spät", "pages": 30, "deadline": "2026-11-20"}, {"title": "Bald", "pages": 60, "deadline": "2026-11-04"}]
r, m = run(dict(base, books=two))
t("frühester Termin zuerst", r["days"][0]["items"][0]["title"] == "Bald")
t("enger Abschnitt dann leichtere Last", r["load"][0] > 0.6 and r["load"][5] < 0.2)
r, m = run(dict(base, books=[{"title": "A", "pages": 70, "deadline": "2026-11-08"}]), at={"A": 36})
t("--at setzt ab Seite fort", abs(sum(x["planned"] for x in r["days"]) - 35) < 0.01)
r, m = run(dict(base, books=[{"title": "W", "words": 25000, "deadline": "2026-11-08"}]))
t("Wortzahl -> Seiten über words_per_page", r["all_books"][0]["units"] == 100)
r, m = run(dict(base, books=[{"title": "H", "pages": 70, "difficulty": 1.5, "deadline": "2026-11-08"}]))
t("Schwierigkeit skaliert Zeit", abs(r["books"][0]["need"] - 105) < 0.01)
r, m = run(dict(base, books=[{"title": "Au", "audio_minutes": 300, "speed": 1.5, "deadline": "2026-11-08"}]))
t("Hörbuch bei 1.5x = 200 Min", abs(r["books"][0]["need"] - 200) < 0.01 and any(f[1] == "RP06" for f in r["findings"]))
r, m = run(dict(base, books=[{"title": "C", "pages": 70, "deadline": "2026-11-08", "chapters": [1, 12, 23, 31, 42, 55]}]))
ends = [round(it["to"]) for x in r["days"] for it in x["items"]]
t("Kapitel-Snapping stoppt an Kapitelenden", 11 in ends and 22 in ends and ends[-1] == 70)
t("ein Eintrag pro Buch pro Tag", all(len({it["title"] for it in x["items"]}) == len(x["items"]) for x in r["days"]))
t("Seitenbereiche überlappen nicht", all(fmt_item(it) for x in r["days"] for it in x["items"]) and
[int(round(it["from"])) for x in r["days"] for it in x["items"]][1:] == [int(round(it["to"])) for x in r["days"] for it in x["items"]][:-1])
r, m = run(dict(base, max_session_minutes=20, minutes=60, books=[{"title": "A", "pages": 300, "deadline": "2026-11-08"}]))
t("lange Sitzungen -> RP03", any(f[1] == "RP03" for f in r["findings"]))
r, m = run(dict(base, books=[{"title": "A", "pages": 200, "deadline": "2026-11-08"}]))
t("enger Plan -> RP04 oder RP01", any(f[1] in ("RP04", "RP01") for f in r["findings"]))
r, m = run(dict(base, books=[{"title": "A", "pages": 30}, {"title": "B", "pages": 60, "deadline": "2026-11-05"}]))
t("undatiertes Buch nach datierten", r["books"][-1]["title"] == "A" and r["books"][-1]["finish"])
r, m = run(dict(base, books=[{"title": "A", "pages": 20, "start_page": 21}]))
t("beendetes Buch -> RP05", any(f[1] == "RP05" for f in r["findings"]))
t("pages_per_hour", abs(load(dict(base, reader={"pages_per_hour": 30}, books=[{"title": "P", "pages": 30}]))["books"][0]["unit_min"] - 2) < 1e-9)
for bad in [dict(base, books=[]), dict(base, books=[{"title": "x"}]), dict(base, minutes={"funday": 3}, books=[{"title": "a", "pages": 3}]),
dict(base, books=[{"title": "a", "pages": 3, "deadline": "2026-13-01"}])]:
try:
run(bad); okk = False
except InputError:
okk = True
t("falsche Eingabe abgelehnt: %s" % (list(bad.get("minutes", {})) if isinstance(bad.get("minutes"), dict) else (bad["books"][0] if bad["books"] else "keine Bücher")), okk)
r, m = run(dict(base, books=[{"title": "Alt", "pages": 30, "deadline": "2026-10-01"}]))
t("vergangener Termin -> RP05, ohne Termin geplant", any(f[1] == "RP05" for f in r["findings"]) and r["books"][0]["finish"])
rep = report(*run(dict(base, books=two)), "even")
t("Bericht hat Urteil, Tagestabelle und Aufhol-Tipps", "Urteil" in rep and "| Datum |" in rep and "Wenn du zurückfällst" in rep)
for name, okk in tests:
print(("PASS " if okk else "FAIL ") + name)
n = sum(1 for _, okk in tests if okk)
print("%d/%d bestanden" % (n, len(tests)))
return 0 if n == len(tests) else 1
def main(argv=None):
ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
ap.add_argument("plan", nargs="?")
ap.add_argument("--mode", choices=["even", "asap"], default="even")
ap.add_argument("--from", dest="start", help="ab diesem Datum neu planen (YYYY-MM-DD)")
ap.add_argument("--at", action="append", default=[], help='"Titel=Seite" (oder "Titel=Minute" für Hörbücher): wo du gerade bist')
ap.add_argument("--json", action="store_true")
ap.add_argument("--self-test", action="store_true")
a = ap.parse_args(argv)
if a.self_test:
return self_test()
if not a.plan:
ap.error("Plan-JSON erforderlich (oder --self-test)")
try:
try:
cfg = json.load(open(a.plan, encoding="utf-8"))
except (OSError, ValueError) as e:
raise InputError(str(e))
at = {}
for s in a.at:
if "=" not in s:
raise InputError("--at benötigt Titel=Seite, erhalten: %r" % s)
k, v = s.rsplit("=", 1)
try:
at[k.strip()] = float(v)
except ValueError:
raise InputError("--at Seite muss eine Zahl sein, erhalten: %r" % v)
titles = {b.get("title") for b in cfg.get("books", [])} if isinstance(cfg, dict) else set()
for k in at:
if k not in titles:
raise InputError("--at: kein Buch mit Titel %r" % k)
m = load(cfg, a.start, at)
r = plan(m, a.mode)
except (InputError, KeyError, TypeError, ValueError) as e:
print("Fehler: %s" % e, file=sys.stderr)
return 2
if a.json:
out = {"books": [{"title": b["title"], "deadline": str(b["deadline"]) if b["deadline"] else None, "finish": str(b.get("finish")) if b.get("finish") else None,
"minutes_needed": round(b["need"])} for b in r["all_books"]],
"days": [{"date": str(x["date"]), "minutes": round(x["planned"]), "read": [fmt_item(it) for it in x["items"]]} for x in r["days"]],
"findings": [{"severity": s, "code": c, "message": msg} for s, c, msg in r["findings"]]}
print(json.dumps(out, indent=2))
else:
print(report(r, m, a.mode))
return 1 if any(f[0] == "high" for f in r["findings"]) else 0
if __name__ == "__main__":
sys.exit(main())
Melde dich an, um den vollständigen Prompt zu sehen
Weiter mit:
Mit der Anmeldung akzeptierst du unsere Nutzungsbedingungen und Datenschutz
Verwendung
Dieser Prompt ist für die Verwendung mit productivity gedacht. Kopiere den Inhalt oben und füge ihn in dein bevorzugtes KI-Tool ein.
Für beste Ergebnisse passe die Platzhalter (eckige Klammern oder Großbuchstaben) an deine Anforderungen an.
Diskussion
0 Kommentare