# Shifty > Schichtplaner für kleine Betriebe. Alles, was die Weboberfläche kann, kann ein Agent über dieselben Befehle. Shifty speichert nichts. Ein Plan ist ein JSON-Dokument: Jeder Befehl bekommt den Plan mit und gibt den geänderten Plan zurück. ## Zugänge - HTTP: `POST /api/v1/run` mit `{"command": "person.add", "args": {"name": "Eva"}, "plan": {…}}`. Antwort: `{"plan": {…}, "result": {…}, "report": {…}}`. Fehler: Status 400 mit `{"error": {"code", "message", "field"}}`. - Befehlsliste mit JSON-Schema: `GET /api/v1/commands`. - MCP: `POST /mcp` (Streamable HTTP, ohne Sitzung). Die Werkzeuge heissen wie die Befehle, mit `_` statt `.`. - Im Browser: `window.shifty.run(name, args)` führt denselben Befehl in der offenen Seite aus, `window.shifty.plan()` gibt den Plan zurück. Jede Schaltfläche ist gekennzeichnet: `data-command` nennt den Befehl, den sie ausführt, `data-nav` den Schritt, zu dem sie führt, und `data-ui` steht bei Elementen, die nur die Anzeige ändern (Blatt öffnen, Wochentag in einem Formular wählen). ## Üblicher Ablauf 1. `plan.new` oder `example.load` 2. `role.add`, `person.add`, `shift.add` 3. `roster.solve` 4. `plan.report` lesen, mit `roster.assign` nachbessern, `roster.export` für die Tabelle ## Befehle ### plan.new Beginnt einen leeren Plan. Ohne Angaben: zwei Wochen ab dem nächsten Montag. - `name` (string): Name des Betriebs. - `start` (string): Ein Tag der ersten Woche. Datum als JJJJ-MM-TT, zum Beispiel 2026-11-16. - `weeks` (integer): Anzahl Wochen, 1 bis 6. ### plan.open Öffnet einen gesicherten Plan. Shifty prüft die Datei und lehnt sie ab, wenn sie beschädigt ist. - `document` (object, Pflicht): Der Inhalt einer gesicherten Shifty-Datei. ### example.load Lädt einen fertig ausgefüllten Beispielbetrieb zum Ausprobieren. - `name` (string): Welches Beispiel. ### business.set Setzt den Namen des Betriebs. Er steht über dem Plan und im Dateinamen. - `name` (string, Pflicht): Name des Betriebs. ### period.set Legt fest, welche Wochen geplant werden. Der Zeitraum beginnt immer an einem Montag. - `start` (string): Ein Tag der ersten Woche. Datum als JJJJ-MM-TT, zum Beispiel 2026-11-16. - `weeks` (integer): Anzahl Wochen, 1 bis 6. ### role.add Fügt eine Aufgabe hinzu, zum Beispiel «Verkauf» oder «Backstube». Schichten brauchen eine Aufgabe, Personen übernehmen Aufgaben. - `name` (string, Pflicht): Name der Aufgabe. ### role.rename Ändert den Namen einer Aufgabe. - `id` (string, Pflicht): id der Aufgabe. - `name` (string, Pflicht): Neuer Name. ### role.remove Löscht eine Aufgabe. Das geht nur, solange keine Schicht sie braucht. - `id` (string, Pflicht): id der Aufgabe. ### person.add Nimmt eine Person ins Team auf. - `name` (string, Pflicht): Name der Person. - `roles` (strings): ids der Aufgaben, die die Person übernimmt. - `targetHours` (number): Sollstunden pro Woche. 0 = kein Soll. - `maxHours` (number): Höchststunden pro Woche, die nie überschritten werden. 0 = keine Grenze. ### person.update Ändert Name, Aufgaben oder Stunden einer Person. Nur die genannten Felder ändern sich. - `id` (string, Pflicht): id der Person. - `name` (string): Name der Person. - `roles` (strings): ids der Aufgaben, die die Person übernimmt. - `targetHours` (number): Sollstunden pro Woche. 0 = kein Soll. - `maxHours` (number): Höchststunden pro Woche, die nie überschritten werden. 0 = keine Grenze. ### person.remove Entfernt eine Person aus dem Team und aus dem Plan. - `id` (string, Pflicht): id der Person. ### absence.add Trägt ein, wann eine Person nicht arbeiten kann: an Wochentagen (jede oder jede zweite Woche, ganz oder zu bestimmten Zeiten) oder an bestimmten Daten. - `person` (string, Pflicht): id der Person. - `kind` (string, Pflicht): «weekly» für Wochentage, «dates» für einen Datumsbereich. - `days` (days): Bei «weekly»: Wochentage als Zahlen: 1 = Montag … 7 = Sonntag. - `every` (integer): Bei «weekly»: 1 = jede Woche, 2 = jede zweite Woche. - `anchor` (string): Bei jeder zweiten Woche: ein Tag in einer Woche, die zählt. Datum als JJJJ-MM-TT, zum Beispiel 2026-11-16. - `start` (string): Bei «weekly»: Beginn des Zeitfensters. Leer = ganzer Tag. Uhrzeit als HH:MM, zum Beispiel 06:30. - `end` (string): Bei «weekly»: Ende des Zeitfensters. Uhrzeit als HH:MM, zum Beispiel 06:30. - `from` (string): Bei «dates»: erster Tag. Datum als JJJJ-MM-TT, zum Beispiel 2026-11-16. - `to` (string): Bei «dates»: letzter Tag. Leer = nur ein Tag. - `note` (string): Grund, zum Beispiel «Berufsschule». Erscheint, wenn die Person deshalb nicht eingeplant wird. ### absence.remove Löscht eine Abwesenheit einer Person. - `person` (string, Pflicht): id der Person. - `index` (integer, Pflicht): Stelle in der Liste der Abwesenheiten, beginnend bei 0. ### wish.add Trägt einen Wunsch ein: an welchen Wochentagen eine Person gern oder lieber nicht arbeitet. Wünsche werden berücksichtigt, wenn es geht. - `person` (string, Pflicht): id der Person. - `want` (string, Pflicht): «prefer» = arbeitet gern, «avoid» = lieber nicht. - `days` (days, Pflicht): Wochentage als Zahlen: 1 = Montag … 7 = Sonntag. - `start` (string): Beginn des Zeitfensters. Leer = ganzer Tag. Uhrzeit als HH:MM, zum Beispiel 06:30. - `end` (string): Ende des Zeitfensters. Uhrzeit als HH:MM, zum Beispiel 06:30. - `note` (string): Notiz zum Wunsch. ### wish.remove Löscht einen Wunsch einer Person. - `person` (string, Pflicht): id der Person. - `index` (integer, Pflicht): Stelle in der Liste der Wünsche, beginnend bei 0. ### shift.add Legt eine Schicht an: wann sie ist, an welchen Tagen, welche Aufgabe und wie viele Personen. - `name` (string, Pflicht): Name der Schicht, zum Beispiel «Früh». - `role` (string, Pflicht): id der Aufgabe, die in der Schicht erledigt wird. - `start` (string, Pflicht): Beginn. Uhrzeit als HH:MM, zum Beispiel 06:30. - `end` (string, Pflicht): Ende. Liegt es vor dem Beginn, läuft die Schicht über Mitternacht. Uhrzeit als HH:MM, zum Beispiel 06:30. - `days` (days, Pflicht): An welchen Tagen die Schicht stattfindet. Wochentage als Zahlen: 1 = Montag … 7 = Sonntag. - `staff` (integer): Wie viele Personen gleichzeitig gebraucht werden. Standard: 1. - `breakMinutes` (integer): Unbezahlte Pause in Minuten. Standard: 0. ### shift.update Ändert eine Schicht. Nur die genannten Felder ändern sich. Einsätze, die danach nicht mehr passen, fallen aus dem Plan. - `id` (string, Pflicht): id der Schicht. - `name` (string): Name der Schicht, zum Beispiel «Früh». - `role` (string): id der Aufgabe, die in der Schicht erledigt wird. - `start` (string): Beginn. Uhrzeit als HH:MM, zum Beispiel 06:30. - `end` (string): Ende. Liegt es vor dem Beginn, läuft die Schicht über Mitternacht. Uhrzeit als HH:MM, zum Beispiel 06:30. - `days` (days): An welchen Tagen die Schicht stattfindet. Wochentage als Zahlen: 1 = Montag … 7 = Sonntag. - `staff` (integer): Wie viele Personen gleichzeitig gebraucht werden. Standard: 1. - `breakMinutes` (integer): Unbezahlte Pause in Minuten. Standard: 0. ### shift.remove Löscht eine Schicht und ihre Einsätze. - `id` (string, Pflicht): id der Schicht. ### rules.set Ändert die Regeln, die für alle gelten. Nur die genannten Felder ändern sich. - `minRestHours` (integer): Ruhezeit in Stunden zwischen zwei Arbeitstagen. Standard: 11. - `maxDaysInRow` (integer): Höchstens so viele Arbeitstage am Stück. Standard: 6. - `balanceHours` (boolean): Stunden möglichst nah an den Sollstunden halten. - `balanceWeekends` (boolean): Wochenenden gleichmässig verteilen. ### roster.solve Füllt den Plan. Feste Einsätze bleiben, alle Regeln werden eingehalten, Stunden und Wochenenden werden fair verteilt. Bleibt ein Einsatz offen, steht im Bericht, warum. - `mode` (string): «fresh» plant alles neu ausser festen Einsätzen (Standard). «fill» behält alle Einsätze und füllt nur die offenen. - `seed` (integer): Startwert. Gleicher Plan und gleicher Startwert ergeben denselben Plan. Ein anderer Wert ergibt eine andere, ebenso gültige Variante. - `effort` (number): Sucheinsatz von 0.1 (schneller Entwurf) bis 2. Standard: 1. ### roster.assign Teilt einen Einsatz von Hand einer Person zu oder gibt ihn frei. Von Hand zugeteilte Einsätze sind fest und bleiben beim nächsten «Plan erstellen». Verletzt die Zuteilung eine Regel, gilt sie trotzdem und der Bericht weist darauf hin. - `date` (string, Pflicht): Tag des Einsatzes. Datum als JJJJ-MM-TT, zum Beispiel 2026-11-16. - `shift` (string, Pflicht): id der Schicht. - `slot` (integer): Platz in der Schicht, beginnend bei 0. Standard: 0. - `person` (string): id der Person. Leer = Einsatz freigeben. - `pinned` (boolean): Fest halten. Standard: ja. ### roster.pin Hält einen Einsatz fest oder löst ihn. Feste Einsätze bleiben beim nächsten «Plan erstellen». - `date` (string, Pflicht): Tag des Einsatzes. Datum als JJJJ-MM-TT, zum Beispiel 2026-11-16. - `shift` (string, Pflicht): id der Schicht. - `slot` (integer): Platz in der Schicht, beginnend bei 0. Standard: 0. - `pinned` (boolean, Pflicht): true = fest, false = gelöst. ### roster.clear Gibt alle Einsätze frei. Team, Schichten und Regeln bleiben. - `keepPinned` (boolean): Feste Einsätze behalten. ### roster.candidates Zeigt für einen Einsatz, wer ihn übernehmen kann und warum die anderen nicht. - `date` (string, Pflicht): Tag des Einsatzes. Datum als JJJJ-MM-TT, zum Beispiel 2026-11-16. - `shift` (string, Pflicht): id der Schicht. - `slot` (integer): Platz in der Schicht, beginnend bei 0. Standard: 0. ### roster.export Gibt den Plan als CSV-Tabelle aus, eine Zeile pro Einsatz, zum Öffnen in Excel oder Numbers. - `format` (string): Dateiformat. Standard: csv. ### plan.report Gibt den Bericht zum Plan zurück: Plan nach Tagen, Stunden pro Person und was noch Aufmerksamkeit braucht. Jeder Befehl liefert diesen Bericht mit.