Skip to content

topse/stundenplanner

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Stundenplanner


| / | / \ / | / ___ \ \ / / \ | | | |
| |/| | / _ | | _ | | | \ \ /\ / / _ \ | | | |
| | | |/ ___ \ |
| || | |
_ \ V V / ___ | || | || |// __|___ _/_// _____|_____|

Der Stundenplan der ganzen Schule — an einer digitalen Wand

MAGIC-Wall ersetzt die Pinnwand mit Karteikärtchen im Lehrerzimmer durch eine digitale, gemeinsam nutzbare Stundenplan-Wand. Mehrere Personen bearbeiten denselben Plan gleichzeitig — mit der Maus am Schreibtisch oder mit dem Finger auf einem großen Touchscreen im Lehrerzimmer. Jede Änderung erscheint sofort bei allen anderen, Konflikte werden live erkannt statt hinterher mühsam gesucht, und ein KI-gestützter Planer kann auf Wunsch ganze Stundenpläne automatisch vorschlagen.

Die folgenden Ausschnitte stammen von einer Beispielschule mit fünf Klassen, sieben Lehrkräften und sechs Räumen — nicht aus der echten Anwendung eines Kunden, aber mit echten Screenshots der laufenden App.

Eine Wand, drei Blickwinkel

Mit einem Klick auf der Werkzeugleiste wechselt die Wand zwischen Klassen-, Lehrer- und Räume-Ansicht — dieselben Daten, einmal nach Klassen, einmal nach Lehrkräften und einmal nach Räumen sortiert. Wer schnell sehen will, ob ein Raum gerade frei ist oder wie voll der Tag einer Lehrkraft ist, wechselt einfach die Ansicht, ohne irgendetwas neu einzugeben.

Klassen-Ansicht

Lehrer-Ansicht

Räume-Ansicht

Konflikte fallen sofort auf

Ist eine Lehrkraft versehentlich in zwei Klassen gleichzeitig eingeplant, oder ist ein Raum doppelt belegt? MAGIC-Wall markiert das sofort mit einem roten Rahmen und nennt das betroffene Fach oder den Raum beim Namen — in allen drei Ansichten gleichermaßen (oben zu sehen: Herr Meyer gleichzeitig in 5a und 5b, die Turnhalle gleichzeitig von 5a und 6a belegt).

Beim Ziehen einer Stunde an eine neue Stelle zeigt die Wand außerdem live, ob das Ziel frei ist, dort etwas ersetzt oder getauscht werden kann, oder ob der Drop einen Konflikt erzeugen würde — nicht erst nach dem Loslassen.

Drag & Drop mit Live-Vorschau

Kopplungen, Doppelstunden und Team-Teaching

Reale Stundenpläne sind selten so einfach wie "eine Klasse, eine Lehrkraft, ein Fach". MAGIC-Wall bildet die üblichen Sonderfälle direkt auf der Wand ab, ohne Sonder-Dialoge:

  • Doppelstunden verschmelzen zu einem großen, durchgehenden Kästchen (siehe "1+2" oben links in der Klassen-Ansicht).
  • Team-Teaching: zwei Lehrkräfte unterrichten dieselbe Klasse zur gleichen Zeit — beide erscheinen nebeneinander in einer geteilten Zelle.
  • Kopplungen: zwei Klassen teilen sich einen Zeit-Slot klassenübergreifend (klassisch: Religion/Ethik) — ein kleines Verbindungssymbol zeigt die Kopplung in beiden Klassen an.

Lehrkräfte-Seitenleiste: die Stundenbilanz auf einen Blick

Wer hat noch Kapazität frei, wer liegt schon über der Soll-Stundenzahl? Die Lehrkräfte-Seitenleiste listet für jede Lehrkraft die Differenz zwischen Soll- und Ist-Stunden auf einen Blick — such- und nach Fach filterbar.

Lehrkräfte-Seitenleiste

Automatische Planung mit KI-Unterstützung

Der Planer erstellt auf Wunsch einen kompletten Stundenplan automatisch. Wichtig dabei: Die KI platziert keine einzige Stunde selbst. Das übernimmt ein klassischer Constraint-Solver, der harte Regeln — "keine Freistunden", "Unterricht in Doppelstunden", "an jedem Unterrichtstag mindestens 4 Stunden" und viele mehr — garantiert einhält. Die KI wird nur dazu genutzt, Regeln in normaler Sprache zu verstehen (z. B. „Frau Wolf kann mittwochs nur nachmittags") und Fragen zum bestehenden Plan zu beantworten; ein Regelvorschlag, den die KI nicht sicher einordnen kann, wird nie stillschweigend übernommen, sondern zur Bestätigung vorgelegt.

Planer-Seitenleiste mit Regeln

Der Planer schlägt Lösungen live vor, während im Hintergrund weitergesucht wird: Auf der Wand erscheint sofort eine Vorschau (halb durchsichtige "Geister"-Stunden), inklusive einer kleinen Bewertung, wie gut der Vorschlag die eigenen Wunschregeln erfüllt. Erst ein bewusster Klick auf "Anwenden" übernimmt den Plan — bis dahin ist auf der Wand nichts verändert, und jede Übernahme lässt sich mit Strg+Z rückgängig machen.

Planer-Vorschlag mit Live-Vorschau

Übernommener Plan

Und sonst noch?

Darüber hinaus bringt MAGIC-Wall unter anderem mit: eine frei einstellbare "Eigene Ansicht" mit gemischten Klassen-/Lehrer-/Raum-Spalten, Suchen & Filtern nach Lehrkraft/Raum/Klasse, eine Vorplanung (Soll-Stunden je Klasse unabhängig vom fertigen Plan), Stunden-Attribute für Halbjahr/Wochen-Rhythmus, Undo/Redo für jede Wand-Änderung, und einen Offline-Modus, der auch bei Netzwerkausfall weiterarbeitet und beim Wiederverbinden automatisch abgleicht.


Für Entwicklung und Betrieb

Der folgende Teil richtet sich an Entwicklerinnen und Administratoren (technische Referenz, auf Englisch).

Web Development

Select "Web (Flutter + Proxy)" in the VS Code launch dropdown and press F5.

This starts a reverse proxy (web-deploy-helper/dev-proxy.js) on port 8080 before launching Flutter. Chrome opens at http://localhost:8080 so that /db routes to CouchDB (port 5984) without CORS issues. Flutter hot-reload WebSockets are forwarded transparently.

CouchDB must be running locally on port 5984 before pressing F5.

First run: npm install is called automatically inside web-deploy-helper/.

Testing

Run everything:

flutter test
flutter analyze

dart_test.yaml (project root) pins concurrency: 1. Most suites spin up a per-test LocalDartCouchServer (Drift/SQLite); under high parallel fan-out on many-core machines the SQLite engines starve and throw flaky SqliteException(5): database is locked (even at concurrency: 4). Each test uses its own database file, so this is contention, not a logic bug — serializing the suites keeps flutter test deterministically green for a modest wall-clock cost.

Running tests in the browser (--platform chrome)

Some behaviour only exists under the web compilers — dart2js has JavaScript number semantics (e.g. bitwise shifts truncate to 32 bits) and the solver's web path (solveCooperatively / _webCooperative) only runs with kIsWeb. Browser-compatible suites (no dart:io, e.g. the test/planner/solve_* tests) can be run compiled to JS in headless Chrome:

CHROME_EXECUTABLE="C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" \
  flutter test --platform chrome test/planner/solve_cancel_until_cancelled_test.dart

Set CHROME_EXECUTABLE explicitly on Windows — without it the runner can hang forever in the loading … phase waiting for a browser it never finds (no error is printed). Expect several minutes of silent dart2js compilation before the first test line appears. Suites that read fixture files via dart:io (testdata_replan_test.dart, etechnik_solver_bug_test.dart) cannot run on the browser platform.

What's covered

  • Model / data-layer unit tests (test/wall/timetable_data_model_test.dart, test/wall/multi_teacher_group_test.dart, test/db/coupling_consistency_test.dart, test/repositories/…) — the decision logic (drop classification, coupling invariants, conflict detection) and the reactive model.
  • Undo/redo — model-level journal/replay in test/wall/undo_redo_test.dart
    • undo_redo_extra_test.dart; widget-level wiring (keys, toolbar buttons, cross-preset repaint) in undo_redo_widget_test.dart.
  • Wall widget tests — the full drag/drop interaction matrix and render/tap surface:
    • wall_drag_widget_test.dart — solo drags (move/copy/replace/swap/als-2./ uniform-rule/trash/conflict).
    • wall_drag_coupled_test.dart — coupled (full) + split ("Paare öffnen") drags.
    • wall_drag_group_test.dart — multi-teacher group + collapsed-pair drags.
    • wall_drag_misc_test.dart — Lehrer-sidebar drags, coupling⟂multi-teacher exclusivity, conflict-prediction icons, cross-preset consistency.
    • wall_render_widget_test.dart — Plättchen rendering, badges, onboarding, toolbar toggles, teacher picker / context menu, Delete key.
    • wall_golden_test.dart — one golden (test/wall/goldens/wall.png). Regenerate on intentional visual changes: flutter test --update-goldens test/wall/wall_golden_test.dart.

Wall widget-test harness (test/wall/wall_harness.dart)

pumpWall(tester, …) seeds a LocalDartCouchServer, registers DI, init()s a TimetableDataModel, sizes the surface so the whole wall renders at scale 1.0, and pumps WallScreen.withModel(model) (a @visibleForTesting constructor that skips the async bootstrap). dragPlaettchen / beginDrag / endDrag drive the custom ResponsiveDraggable. Cells are located by cellFinder(CellKey) / plaettchenFinder(stundeId) (stable ValueKeys on _CellView / each teacher body). State assertions read model.stundeById(...); affordances assert the stripe/kind icons; blocked targets assert expectGreyed (Opacity 0.35).

Non-obvious rules baked into the harness (change with care):

  • Inject a testChanges stream. Without it the model subscribes to the live continuous db.changes() feed, which keeps the async loop busy and hangs pumpAndSettle forever. The harness never uses pumpAndSettle (drag/hover states pulse continuously) — it settles a fixed number of frames.
  • Real I/O needs runAsync. LocalDartCouchServer does real file I/O, which doesn't progress on bare awaits inside testWidgets. Boot/seeding, the pointer release on a drop, write-triggering taps/keys (tapAsync / sendKeyAsync), the consistency check, and teardown all run inside tester.runAsync. A drop released during a normal pump would stall its optimistic _db.put and get reverted by the model's fail-safe reload.
  • Sweep the drag, don't jump. Drags move to the target in steps. A single moveTo keeps the hit-tested drop-target list constant, and Flutter's _DragAvatar only re-runs onWillAccept when that list changes — the first evaluation happens at drag-start with the (touch-deferred) session not yet published, so a straight jump leaves the target rejected.
  • Consistency check runs in teardown for every test (SanityChecker.runAll), in addition to explicit assertDbConsistent calls at key points.

Screenshot tour (integration_test/readme_screenshots_test.dart)

The screenshots used above are generated, not hand-captured — a real-window GUI walk-through (integration_test, LiveTestWidgetsFlutterBinding) over a hand-curated demo school, exercised the same way [plan_flow_test.dart] (integration_test/plan_flow_test.dart) exercises the Planer flow. Regenerate after an intentional visual change:

flutter test integration_test/readme_screenshots_test.dart -d windows

Output lands in build/screenshots/*.png; copy the ones you want into docs/screenshots/.

Deployment

The compose stack runs CouchDB, an Ollama LLM container, and an nginx container that serves the Flutter app and proxies /db → CouchDB and /ollama → Ollama. No separate CORS configuration needed (everything is same-origin).

1. Build the image + deploy bundle (dev machine)

./build_docker_image.sh

This runs flutter pub get + flutter build web --release --wasm, builds the Docker image, and assembles everything the target server needs into web_deploy_files/: the image saved as a tarball (stundenplanner-<version>.tar.gz), docker-compose.yml, the CouchDB config template, and the helper scripts from web-deploy-helper/ (load_image.sh, start_containers.sh, stop_containers.sh, update_image_and_restart.sh, check_local_ini.sh, update_local_ini.sh, ollama_entrypoint.sh).

2. Copy web_deploy_files/ to the target server

3. Before the first deploy, edit docker-compose.yml inside the copied bundle (not the repo's — the bundle's copy is what actually runs):

  • COUCHDB_USER / COUCHDB_PASSWORD on the db service — the initial CouchDB admin credentials, applied only on the very first start.
  • OLLAMA_API_KEY on the stundenplanner service — must match whatever gets entered later in the app's Plan-KI settings ("Schulweit geteilt"); this key guards the /ollama endpoint (see below).
  • The exposed port (8110:80), if needed.

4. Install / start (on the server, from inside the bundle directory)

./start_containers.sh

Creates the bind-mounted data directories, seeds couchdb_config/local.ini from the bundled template on the very first run (never overwrites it afterwards — CouchDB writes state, like the hashed admin password, into that file), and starts the stack with docker compose up -d.

5. Updating later: rebuild (./build_docker_image.sh) and ship a fresh web_deploy_files/, then on the server run

./update_image_and_restart.sh

which loads the new image and force-recreates only the stundenplanner container — db/ollama and their data volumes are left untouched. It also warns (non-blocking) if the deployed couchdb_config/local.ini has drifted from the bundled template; merge with sudo ./update_local_ini.sh if it flags a difference (a backup of the old file is kept alongside it).

Stop the stack: ./stop_containers.sh (data volumes are kept).

AI features (Ollama)

The automatic-plan AI talks to a local Ollama model through nginx at /ollama.

  • Model: qwen2.5:7b (set via OLLAMA_MODEL on the ollama service). The first docker compose up downloads it (several GB) — expect a long first start. It's stored in ./ollama_data, so it downloads only once.
  • Warm start: ollama_entrypoint.sh pulls (no-op after the first time) and warms the model into RAM on every container start; OLLAMA_KEEP_ALIVE=-1 keeps it resident, so the first user request is fast even right after a restart.
  • Auth: the /ollama endpoint is protected by the hard-coded OLLAMA_API_KEY from the compose file — nginx rejects any request whose Authorization: Bearer <key> header doesn't match (401). Ollama itself has no auth, so it is never exposed directly (no host port).
  • GPU: CPU-only works but is slow. Uncomment the deploy.resources block on the ollama service (needs the NVIDIA Container Toolkit) for GPU acceleration.

Configure the app once (any admin), under Plan-KI / KI Einstellungen:

Field Value
Anbieter OpenAI-kompatibel
Basis-URL /ollama
Modell qwen2.5:7b
API-Schlüssel Schulweit geteilt → the same value as OLLAMA_API_KEY

/ollama is a same-origin relative URL, so it works regardless of the server's host/port. Use Verbindung testen to confirm before saving.

License

MIT — see LICENSE.

About

Application for Schools to create a Stundenplan for the School. Its made as a digital twin of the old planboard

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors