shinkan-jinkendo/.claude/docs/technical/TRAINING_FRAMEWORK_SPEC.md
Lars b054c642a3
Some checks failed
Deploy Development / deploy (push) Successful in 33s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / playwright-tests (push) Failing after 40s
chore: update training framework specifications and versioning
- Incremented version to 0.8.8 and updated database schema version to 20260505035.
- Added new entity `training_framework_programs` to manage training frameworks, including goals and slots.
- Enhanced `training_plan_templates` with a visibility attribute and backfilled existing data.
- Updated API to support CRUD operations for training frameworks, ensuring proper authorization similar to existing planning libraries.
- Revised documentation in DOMAIN_MODEL.md, TRAINING_CURRICULUM_AND_GOVERNANCE_CONCEPT.md, and TRAINING_FRAMEWORK_SPEC.md to reflect these changes.
2026-05-05 08:41:43 +02:00

186 lines
13 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Trainingsrahmenprogramm — Technische Spezifikation
**Status:** Zwischenstand dokumentiert · **Stand:** 2026-05-05
**Bindendes Fachkonzept / Entscheide:** `.claude/docs/functional/TRAINING_CURRICULUM_AND_GOVERNANCE_CONCEPT.md` (CURR001 bis CURR013)
**Relevant für nächsten Schritt:** CURR002 **(2)** Trainingsplanung / Rahmen über mehrere Einheiten — der hier dokumentierte **Progressionsgraph Stufe 1** ist bewusst **unterstützend**, keine Pflicht für Slot-Zuordnungen (**CURR013**).
---
## 1. Abgrenzung zu anderen Dokumenten
| Dokument | Rolle · warum **nicht** hier hineinmischen |
|----------|--------------------------------------------|
| `EXERCISES_DATABASE_FINAL.md`, `EXERCISES_ARCHITECTURE.md`, `EXERCISES_API_SPEC.md` | **Übungskatalog** inkl. Varianten-Progression **innerhalb einer Übung** (Migration 014). Kanten **zwischen** Übungen siehe **§3**. |
| `DATABASE_SCHEMA.md` | **Nachgeordnete** Übersicht: Migrationshistorie und Tabellenliste; Detail-DDL primär **hier §2§3** + SQL unter `backend/migrations/`. |
| `functional/DOMAIN_MODEL.md` | Fachliche Begriffe; Kurzverweis auf Progressionsgraph ergänzt. |
| `TRAINING_CURRICULUM_AND_GOVERNANCE_CONCEPT.md` | **Was** und **warum** (Modus A/B, Governance, CURRTabelle). |
**Konsequenz:** Diese Datei bleibt der **technische Arbeitspool** für Rahmenprogramm Stufe 12. Abschnitt **§4** beschreibt explizit den **aktuellen Produktfreigabe-Umfang** und **bekannte Lücken** (damit Trainingsplanung weiter gebaut werden kann ohne falscher Erwartung an „AlternativePakete“ in der UI).
---
## 2. Rahmenprogramm (CURR002 Stufe2) — Checkliste & technische Ausarbeitung
### 2.0 Technische Entscheidung: eine Tabelle + `plan_mode` (Modus A|B)
**Entscheid:** **Eine** Hauptentität `training_framework_programs` mit **`plan_mode` ∈ {`concrete`, `library`}** statt zweier getrennter Tabellentypen.
**Begründung:** Gleiche Lebenszyklus und CRUDForm (Header, Ziele, Slots, Übungen); die fachliche Unterscheidung A/B lässt sich mit **CHECK**- und APIRegeln 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 SupertypTabelle 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` (**CURR009**); `training_plan_templates` unverändert **eineEinheitMikrovorlage** (**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, **CURR010**).
- [x] **Zielliste:** `training_framework_goals`, API erzwingt **1** Ziel beim Anlegen/Ersetzen (**CURR011**).
- [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 (**CURR013**).
- [x] **Konkretkontakt ModusA:** optional **`training_unit_id`** pro Slot; bei gesetzter Rahmen-**`group_id`** muss die Einheit zur gleichen Gruppe gehören. **LiveWrites** zurück in die Bibliotheksvorlage: **nicht** vorgesehen (**CURR006**).
- [x] **Instanziierung (ModusB):** Persistenz der Vorlage (**MVP**); Bulk-Anlage von **`training_units`** aus dem Rahmen — **Ausbauschritt**/zweiter PR (**CURR012** C4a/b).
- [x] **Governance neue Objekte:** `visibility`, `club_id`, `created_by` wie Progressionsgraph (**CURR005**). **`training_plan_templates.visibility`** nachgezogen in derselben Migration **035** mit Backfill **`club`** (**CURR007**, **CURR008**; frühe Installationen).
- [x] **REST Rahmenprogramm:** `/api/training-framework-programs` (**§2.3**); ProgressionsAPI weiter **§3.3**.
### 2.2 DDLSkizze (Migration **035**, Kurzüberblick)
```sql
-- Header
training_framework_programs (
id, title NOT NULL, description,
plan_mode NOT NULL CHECK (IN 'concrete','library'),
group_id FK training_groups NULL,
planned_period_start, planned_period_end NULL, -- reine Meta-/UIHilfe
visibility NOT NULL, club_id, created_by,
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 (**CURR010** Optional).
### 2.3 RESTÜberblick (`router` `training_framework_programs`)
| Methode | Pfad | Zweck |
|---------|------|--------|
| 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. TitelJoins) |
| POST | `/training-framework-programs` | Neu; **Pflicht:** `title`, **`plan_mode`**, **`goals`** (≥1 Eintrag mit `title`); optional `slots` |
| PUT | `/training-framework-programs/{id}` | HeaderFelder; optional volles Ersetzen von **`goals`** und/oder **`slots`** wie bei VorlagenSektionen |
| 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`).
**PayloadHinweise (JSON):**
- `goals`: `[{ sort_order?, title, notes? }, …]`**`sort_order`** default Reihenfolge im Array
- `slots`: `[{ sort_order?, title?, notes?, training_unit_id?, exercises: [{ exercise_id, exercise_variant_id?, order_index? }] }, …]`
- Bei **`library`:** **`training_unit_id`** an Slots → **400**; nach Wechsel auf **`library`** werden bestehende Slot-Verknüpfungen zu **`training_units`** geleert.
**MinimalUI:** im Lieferumfang dieser Iteration nicht enthalten (**OpenAPI `/docs`** / Postman); siehe Funktionskonzept §6.
---
## 3. Progressionsgraph Übung → Übung (implementierter Stand)
### 3.1 Abgrenzung
- **Zwischen Übungen:** gerichtete Kanten auf Ebene **`exercises`** mit optionalen Endpunkten auf konkreten **`exercise_variants`** (Knoten = „Übung“ oder „Übung · Variante“). Migrationen **032034**.
- **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).
Listenqueries liefern JoinFelder **`from_exercise_title`**, **`to_exercise_title`**, **`from_variant_name`**, **`to_variant_name`**.
### 3.3 REST (`/api`, Router `exercise_progression_graphs.py`)
| Methode | Pfad | Zweck |
|---------|------|--------|
| GET | `/exercise-progression-graphs` | Liste (+ `edges_count`); Admin sieht alle, sonst nur eigene. |
| GET | `/exercise-progression-graphs/{id}` | Detail; `?include_edges=true` |
| POST | `/exercise-progression-graphs` | Graph anlegen |
| PUT | `/exercise-progression-graphs/{id}` | Metadaten |
| DELETE | `/exercise-progression-graphs/{id}` | Graph + Kanten |
| GET | `/exercise-progression-graphs/{id}/edges` | Kanten; Query optional `from_exercise_id`, `to_exercise_id` |
| 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 |
### 3.4 Frontend (Stand Code)
- **`ExercisesListPage`:** Tabs **Liste** · **Progressionsgraphen****`ExerciseProgressionGraphPanel`**.
- **`ExerciseFormPage`** (nur Edit): eingeklappter Block **Progressionsgraph** mit Kontext „diese Übung“ + Filter „nur betroffene Kanten“.
- PanelFunktionen: **SequenzEditor** (mehrere Schritte → ein BulkSpeichern), zusammengefasste **ReihenLesart** für `next_exercise`, eigene Liste für **Schwestern**, Einzelkantenbereich, Tab **Alle Kanten (Tabelle)**.
- APIClient: `frontend/src/utils/api.js` (`createExerciseProgressionSequence`, `deleteExerciseProgressionEdgesBatch`, …).
---
## 4. Zwischenstand für Produkt / Trainingsplanung (bewusste Grenzen)
**Freigabe:** Der beschriebene Stand gilt als **ausreichend**, um mit dem **Trainingsplanungsmodul** und später Rahmen/Slots (**CURR002 (2)**) weiterzuarbeiten — ohne dass Pflicht zur Pflege komplexer GraphStrukturen entsteht (**CURR013**).
**Was gut nutzbar ist**
- Lineare **Reihen** mehrerer Übungen (bzw. VariantenKnoten) über **SequenzAPI** bzw. SequenzUI.
- **NachfolgerLesart** als zusammenhängende Kette in der Übersicht.
- **SchwesterKanten** als eigene Liste (Alternative gleicher „Stufe“ zwischen zwei Knoten).
- **Einzelkanten** für Sonderfälle und Verzweigungen.
**Was noch nicht „ein Knopf“ ist (bekannt)**
- **Parallele gleichwertige Alternativen** („Paket B bestehend aus mehreren Übungen als echte Alternative zu Paket A“) sind **nicht** als ersteKlassUX modelliert: mehrere Nachfolger aus einem Knoten sind technisch möglich (mehrere `next_exercise`Kanten), aber **keine** dedizierte Gruppe „AlternativSet“. Pflege kann mehrzeilig und koplastisch wirken.
- **Visualisierung echter Bäume** (JoinPoints, mehrere ausgehende Pfeile in einem Bild) ist nur eingeschränkt über ReihenZusammenfassung + Tabelle abbildbar.
**Nächste sinnvolle Ausbaustufen** (Backlog GraphUX, nicht Blocker Planung)
- Semantik **`alternative_group_id`** oder **Hyperkanten** (ein UXSchritt legt mehrere Kanten mit gemeinsamer Gruppe an).
- Komfort beim Pflegen **symmetrischer Schwestern** (ein Klick für zwei Richtungen / Dedupe).
- Karten-/BaumLayout statt nur Zeilen.
Details weiterhin Diskussionsgrundlage in `TRAINING_CURRICULUM_AND_GOVERNANCE_CONCEPT.md` §6 (Kantentypen).
---
## 5. Changelog (Dokument)
| Datum | Änderung |
|-------|----------|
| 2026-05-05 | **CURR002 (2):** §2 Rahmenprogramm — Entscheid **eine Tabelle + `plan_mode`**, DDLSkizze, RESTÜberblick; Migration **035**; `training_plan_templates.visibility`. |
| 2026-04-30 | **Zwischen-Doku:** §3 auf Migrationen 032034 + API **sequence/delete-batch** + Frontend erweitert; **§4** Produktfreigabe vs. Lücken (parallele Alternativen); Changelog §5. |
| 2026-04-30 | §3: erste Fassung Migration 032 + RESTBasis (CURR002 (1)). |
| 2026-04-28 | Erstanlage Stub mit Checkliste. |