**Relevant für nächsten Schritt:** CURR‑002 **(2)** Trainingsplanung / Rahmen über mehrere Einheiten — der hier dokumentierte **Progressionsgraph Stufe 1** ist bewusst **unterstützend**, keine Pflicht für Slot-Zuordnungen (**CURR‑013**).
**Konsequenz:** Diese Datei bleibt der **technische Arbeitspool** für Rahmenprogramm Stufe 1–2. Abschnitt **§4** beschreibt explizit den **aktuellen Produktfreigabe-Umfang** und **bekannte Lücken** (damit Trainingsplanung weiter gebaut werden kann ohne falscher Erwartung an „Alternative‑Pakete“ in der UI).
**Begründung:** Gleiche Lebenszyklus‑ und CRUD‑Form (Header, Ziele, Slots, Übungen); die fachliche Unterscheidung A/B lässt sich mit **CHECK**- und API‑Regeln ausdrücken (`library` ⇒ `group_id IS NULL`, keine `training_unit_id` an Slots), ohne doppelte Router/Joins. Zwei physische Typen würden ohne Mehrwert Polymorphismus in der API erzwingen oder eine künstliche Supertyp‑Tabelle nach sich ziehen.
**Abgrenzung Konzept §6:** Dokumentiert hier; Funktionskonzept kann auf diesen Abschnitt verweisen.
### 2.1 Checkliste (Abhak-Stand)
- [x]**Entität(en):** eigene Bibliotheks-Entität `training_framework_programs` (**CURR‑009**); `training_plan_templates` unverändert **eine‑Einheit‑Mikrovorlage** (**C5**).
- [x]**Modus A vs. B:** ein Datensatz + `plan_mode` + Nullables (**§2.0**); `library` erzwingt `group_id` NULL; **`training_plan_template_id` pro Slot** — **deferred** (Technical: MVP ohne Spalte, bis Nutzen geklärt, **CURR‑010**).
- [x]**Zielliste:**`training_framework_goals`, API erzwingt **≥1** Ziel beim Anlegen/Ersetzen (**CURR‑011**).
- [x]**Slots:**`training_framework_slots` mit **`sort_order`**, optional **Titel/Notizen**; Übungen über **`training_framework_slot_exercises`** (Sortierung **`order_index`**, optional **`exercise_variant_id`**).
- [x]**Progressionsgraph:** Stufe1 besteht (**§3–§4**); **kein Pflichtbezug** pro Slot (**CURR‑013**).
- [x]**Konkretkontakt ModusA:** optional **`training_unit_id`** pro Slot; bei gesetzter Rahmen-**`group_id`** muss die Einheit zur gleichen Gruppe gehören. **Live‑Writes** zurück in die Bibliotheksvorlage: **nicht** vorgesehen (**CURR‑006**).
- [x]**Instanziierung (ModusB):** Persistenz der Vorlage (**MVP**); Bulk-Anlage von **`training_units`** aus dem Rahmen — **Ausbauschritt**/zweiter PR (**CURR‑012** C4a/b).
- [x]**Governance neue Objekte:**`visibility`, `club_id`, `created_by` wie Progressionsgraph (**CURR‑005**). **`training_plan_templates.visibility`** nachgezogen in derselben Migration **035** mit Backfill **`club`** (**CURR‑007**, **CURR‑008**; frühe Installationen).
- [x]**REST Rahmenprogramm:**`/api/training-framework-programs` (**§2.3**); Progressions‑API weiter **§3.3**.
CHECK ((plan_mode = 'library' AND group_id IS NULL) OR plan_mode = 'concrete'),
timestamps + update_trigger
)
-- Ziele (≥1 über API beim Speichern)
training_framework_goals (
id, framework_program_id FK CASCADE, sort_order UNIQUE per framework,
title, notes
)
-- Slots
training_framework_slots (
id, framework_program_id FK CASCADE, sort_order UNIQUE per framework,
title, notes, training_unit_id FK training_units SET NULL
)
-- Stückliste pro Slot (Übung → FK CASCADE beim Löschen der Übung)
training_framework_slot_exercises (
id, slot_id FK CASCADE,
exercise_id FK exercises CASCADE,
exercise_variant_id FK exercise_variants NULL,
order_index UNIQUE per slot
)
```
**Löschkaskaden:** löschen eines **Rahmens** ⇒ Ziele + Slots ⇒ Slot‑Übungen (alles **`ON DELETE CASCADE`** vom Rahmen über Slots). löschen eines **`training_unit`** ⇒ FK am Slot **`SET NULL`**. löschen einer **Übung** ⇒ Zeilen in **`training_framework_slot_exercises`** entfallen (`CASCADE`).
**Deferred (nicht im MVP):** `training_plan_templates.id` FK je Slot (**CURR‑010** Optional).
| GET | `/training-framework-programs` | Liste; Admin/Superadmin alle, sonst eigene (`created_by`); Aggregation `goals_count`, `slots_count` |
| GET | `/training-framework-programs/{id}` | Detail inkl. `goals[]`, `slots[]` mit jeweils `exercises[]` (inkl. Titel‑Joins) |
| POST | `/training-framework-programs` | Neu; **Pflicht:**`title`, **`plan_mode`**, **`goals`** (≥1 Eintrag mit `title`); optional `slots` |
| PUT | `/training-framework-programs/{id}` | Header‑Felder; optional volles Ersetzen von **`goals`** und/oder **`slots`** wie bei Vorlagen‑Sektionen |
| DELETE | `/training-framework-programs/{id}` | Rahmen löschen (**CASCADE** Kinder) |
**AuthZ:** Schreibzugriff nur mit **`_has_planning_role`** (wie `training_plan_templates`); Lesen/Ändern/Löschen: **Admin/Superadmin** oder **Ersteller** (`created_by`).
- Bei **`library`:** **`training_unit_id`** an Slots → **400**; nach Wechsel auf **`library`** werden bestehende Slot-Verknüpfungen zu **`training_units`** geleert.
**Minimal‑UI:** im Lieferumfang dieser Iteration nicht enthalten (**OpenAPI `/docs`** / Postman); siehe Funktionskonzept §6.
- **Zwischen Übungen:** gerichtete Kanten auf Ebene **`exercises`** mit optionalen Endpunkten auf konkreten **`exercise_variants`** (Knoten = „Übung“ oder „Übung · Variante“). Migrationen **032–034**.
- **Innerhalb einer Übung:** Reihenfolge / Progressionsmetadaten der Varianten unverändert über **`exercise_variants`** (Migration **014**) — nicht duplizieren.
AuthZ analog **`training_plan_templates`**: Graph nur für **Admin/Superadmin** oder **Ersteller** (`created_by`); Anlegen neuer Graphen mit **`_has_planning_role`**.
### 3.2 Migrationen & Schema (Kurz)
| Mig. | Inhalt |
|------|--------|
| **032** | `exercise_progression_graphs` (Name, Beschreibung, **`visibility`**, **`club_id`**, **`created_by`**); `exercise_progression_edges` (`graph_id`, von/nach Übung, `edge_type` VARCHAR Default `next_exercise`). FK CASCADE zu Graph und Übungen. |
| **033** | `exercise_progression_edges.notes` (freier Text / „Entwicklungsziel“ pro Kante). |
| **034** | `from_exercise_variant_id`, `to_exercise_variant_id` (nullable, FK `exercise_variants`, CASCADE). CHECK: gleiche Übung nur mit **zwei verschiedenen Varianten**. UNIQUE-Index über Graph + Endpunkte inkl. `COALESCE(variant_id,0)` + `edge_type`. |
Kantentypen in Produktnutzung: **`next_exercise`** (Nachfolger), **`sibling`** (Schwester / gleiche „Entwicklungslage“, semantisch oft Paar — weiterhin eine gerichtete Kante in DB).
| POST | `/exercise-progression-graphs/{id}/edges` | Einzelkante; Duplikat/Constraint → **409** |
| POST | `/exercise-progression-graphs/{id}/edges/sequence` | **Bulk:**`{ steps: [{ exercise_id, variant_id? }, …], segment_notes?: [...] }` — nur **`next_exercise`**, Transaktion alle oder keine Zeile |
| PUT | `/exercise-progression-graphs/{id}/edges/{edge_id}` | z.B. **`notes`** |
| DELETE | `/exercise-progression-graphs/{id}/edges/{edge_id}` | eine Kante |
| POST | `/exercise-progression-graphs/{id}/edges/delete-batch` | `{ edge_ids: [...] }` — z.B. gesamte sichtbare „Reihe“ löschen |
- Panel‑Funktionen: **Sequenz‑Editor** (mehrere Schritte → ein Bulk‑Speichern), zusammengefasste **Reihen‑Lesart** für `next_exercise`, eigene Liste für **Schwestern**, Einzelkantenbereich, Tab **Alle Kanten (Tabelle)**.
**Freigabe:** Der beschriebene Stand gilt als **ausreichend**, um mit dem **Trainingsplanungsmodul** und später Rahmen/Slots (**CURR‑002 (2)**) weiterzuarbeiten — ohne dass Pflicht zur Pflege komplexer Graph‑Strukturen entsteht (**CURR‑013**).
- **Parallele gleichwertige Alternativen** („Paket B bestehend aus mehreren Übungen als echte Alternative zu Paket A“) sind **nicht** als erste‑Klass‑UX modelliert: mehrere Nachfolger aus einem Knoten sind technisch möglich (mehrere `next_exercise`‑Kanten), aber **keine** dedizierte Gruppe „Alternativ‑Set“. Pflege kann mehrzeilig und koplastisch wirken.
- **Visualisierung echter Bäume** (Join‑Points, mehrere ausgehende Pfeile in einem Bild) ist nur eingeschränkt über Reihen‑Zusammenfassung + Tabelle abbildbar.