shinkan-jinkendo/.claude/docs/technical/EXERCISES_ARCHITECTURE.md
Lars f5895b6637
Some checks failed
Deploy Development / deploy (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Failing after 41s
chore: update documentation and enhance exercise progression graph details
- Updated CLAUDE.md to reflect the addition of exercise_progression_graphs in the backend routers.
- Revised PROJECT_STATUS.md to document the current project status and recent milestones, including the implementation of the exercise progression graph feature.
- Incremented versioning in DOMAIN_MODEL.md and DATABASE_SCHEMA.md to align with the latest migration updates.
- Enhanced technical specifications in TRAINING_FRAMEWORK_SPEC.md to clarify the implementation details of the exercise progression graph and its integration with the training framework.
2026-05-05 08:30:48 +02:00

19 KiB
Raw Blame History

Exercise System Architecture

Version: 1.1
Datum: 2026-04-30
Status: DRAFT - Awaiting Review
Autor: Claude Code
Änderungen v1.1: Progressionsgraph zwischen Übungen (Migration 032034); Verweis TRAINING_FRAMEWORK_SPEC.md


1. Datenmodell-Entscheidungen

1.1 Übung vs. Variante vs. Block vs. Serie

Definitionen:

  • Übung (Exercise): Atomare Trainingseinheit mit Ziel, Durchführung, Vorbereitung

    • Tabelle: exercises
    • Beispiel: "Maai - Distanzübung"
  • Variante (Variant): Angepasste Ausführung EINER Übung

    • Tabelle: exercise_variants
    • Beispiel: "Maai - Ohne Partner", "Maai - Mit Pratzen"
    • Beziehung: 1:N (eine Übung hat 0-n Varianten)
  • Serie (Series): Progression durch Varianten derselben Übung

    • KEINE eigene Tabelle - Progression via Metadata in exercise_variants
    • Felder: progression_level, sequence_order, prerequisite_variant_id
    • Beispiel: Liegestütz → Level 1 (Knie), Level 2 (Normal), Level 3 (Erhöht), Level 4 (Einarmig)
  • Block (Block): Sammlung UNTERSCHIEDLICHER Übungen als zusammenhängender Trainingsabschnitt

    • Tabelle: exercise_blocks + exercise_block_items
    • Beispiel: "Zirkel Oberkörper" → Liegestütz + Klimmzüge + Dips + Planks
    • Beziehung: M:N (ein Block enthält viele Übungen, eine Übung kann in vielen Blöcken sein)
  • Template-Block: Block mit Platzhaltern für spätere Befüllung

    • Flag: is_template = true in exercise_blocks
    • Beispiel: "Zirkeltraining (6 Stationen)" → Station 1: [Platzhalter: Push-Übung], Station 2: [Platzhalter: Pull-Übung], etc.

Architektur-Diagramm:

Exercise (1) ──────┬──── (N) Variants
                   │       ├── progression_level
                   │       ├── sequence_order
                   │       └── prerequisite_variant_id
                   │
                   └──── (M:N) Skills
                   └──── (M:N) Focus Areas
                   └──── (M:N) Training Styles
                   └──── (M:N) Target Groups
                   └──── (1:N) Media

Exercise Block ──── (N) Block Items ──── (1) Exercise
                                     └── (1) Variant (optional)
                                     └── is_placeholder (for templates)

1.1b Progressionsgraph zwischen Übungen (nicht „Serie“)

Abgrenzung: Separates Konzept von der Varianten-Serie (§1.1): hier geht es um gerichtete Kanten zwischen verschiedenen Übungen (optional mit Varianten als Knoten-Endpunkten), gruppiert in Bibliotheks-Containern (exercise_progression_graphs). Schema, REST, Produktgrenzen und Backlog (parallele Alternativ-Pakete): TRAINING_FRAMEWORK_SPEC.md §3§4.


1.2 Medien-Strategie

Zwei Medien-Typen:

  1. Lokale Medien (eigene Uploads)

    • Storage: /app/media/exercises/{uuid}_{filename}
    • Unterstützte Formate:
      • Bilder: image/jpeg, image/png, image/gif
      • Videos: video/mp4
      • Dokumente: application/pdf (für Skizzen)
    • Max Size: 50MB pro Datei
    • Max Count: 10 Dateien pro Übung
  2. Embeds (externe Plattformen)

    • Unterstützte Plattformen:
      • YouTube: youtube.com/watch?v=..., youtu.be/...
      • Instagram: instagram.com/p/..., instagram.com/reel/...
      • Vimeo: vimeo.com/...
    • Speicherung: Nur URL + Platform-Identifier
    • Rendering: iframe-Embed im Frontend

Medien-Felder:

  • file_path (lokale Medien) XOR embed_url (externe Medien)
  • mime_type (nur bei lokalen Medien)
  • embed_platform (nur bei Embeds: 'youtube', 'instagram', 'vimeo')
  • sort_order (für Reorder via Drag & Drop)
  • is_primary (Hauptmedium, z.B. Vorschau-Bild)
  • context (Kontext: 'ablauf', 'detail', 'trainer_hint')

Storage-Management:

  • Uploads via FastAPI UploadFile
  • Speicherort: Docker Volume /app/media/exercises/
  • Cleanup: Bei Exercise-Deletion automatisch via CASCADE + Cleanup-Job

1.3 M:N Beziehungen (Primary/Secondary Pattern)

Regel: Alle Katalog-Zuordnungen nutzen M:N mit is_primary Flag.

Betroffene Relationen:

  • exercise_focus_areas (Übung ↔ Fokusbereiche)
  • exercise_styles (Übung ↔ Trainingsstile)
  • exercise_target_groups (Übung ↔ Zielgruppen)
  • exercise_training_characters (Übung ↔ Trainingscharaktere)
  • exercise_skills (Übung ↔ Fähigkeiten)

Primary/Secondary Semantik:

  • Primary: Hauptzuordnung, entscheidend für Filter/Suche
  • Secondary: Nebenzuordnung, zusätzlicher Kontext
  • Regel: Genau EINE Primary-Zuordnung pro Dimension
  • UI: Primary wird visuell hervorgehoben (z.B. fett, farbig)

Legacy-Felder (DEPRECATED):

  • exercises.focus_area → Ignorieren, nutze exercise_focus_areas
  • exercises.training_style_id → Ignorieren, nutze exercise_styles
  • exercises.training_character_id → Ignorieren, nutze exercise_training_characters
  • NICHT löschen (Rückwärtskompatibilität), aber nicht verwenden

1.4 Sichtbarkeit & Freigabe

3 Sichtbarkeits-Ebenen:

  • private - Nur Ersteller sieht Übung
  • club - Alle Vereinsmitglieder sehen Übung
  • official - Alle Nutzer sehen Übung (globale Standards)

4 Status-Stufen:

  • draft - Entwurf, in Bearbeitung
  • in_review - Zur Prüfung eingereicht
  • approved - Freigegeben, produktiv nutzbar
  • archived - Archiviert, nicht mehr aktiv

Berechtigungsmatrix:

Aktion Private Club Official
Sehen Nur Owner Alle Club-Member Alle Nutzer
Bearbeiten Nur Owner Nur Owner Nur Superadmin
Löschen Nur Owner Nur Owner + Club-Admin Nur Superadmin
Freigeben Owner → Club Club-Admin → Official Superadmin

Freigabe-Workflow (vereinfacht in Phase 1):

  1. Trainer erstellt Übung (private, draft)
  2. Trainer setzt Status auf in_review
  3. Club-Admin prüft, setzt approved + visibility = 'club'
  4. Superadmin kann auf visibility = 'official' setzen

Phase 2+: Explizite Freigabe-Requests (content_change_requests Tabelle)


2. API-Konventionen

2.1 Naming

URL-Struktur:

  • Collections: Plural (/api/exercises)
  • Items: Singular + ID (/api/exercises/{id})
  • Sub-Resources: Nested (/api/exercises/{id}/variants, /api/exercises/{id}/media)
  • Actions: HTTP-Verben (POST, PUT, DELETE, GET)

HTTP-Methoden:

  • GET - Read (Liste oder Detail)
  • POST - Create (neue Ressource)
  • PUT - Update (bestehende Ressource)
  • DELETE - Delete

Query-Parameter:

  • Snake-case: focus_area, skill_id, search
  • Optional Filter: ?focus_area=karate&status=approved
  • Pagination: ?limit=50&offset=0

2.2 Response-Format

Erfolgreiche Responses:

// Liste (Array)
[
  {"id": 1, "title": "...", ...},
  {"id": 2, "title": "...", ...}
]

// Detail (Object mit Enrichment)
{
  "id": 1,
  "title": "...",
  "skills": [...],          // enriched M:N
  "variants": [...],        // enriched 1:N
  "media": [...],           // enriched 1:N
  "focus_areas": [...],     // enriched M:N
  "training_styles": [...], // enriched M:N
  "target_groups": [...],   // enriched M:N
  "age_groups_catalog": ["Kinder", "Teenager"]
}

// Create/Update (Created Object)
{
  "id": 42,
  ... // full object wie GET Detail
}

// Delete
{"ok": true}

Fehler-Responses:

{
  "detail": "Human-readable error message"
}

2.3 Error-Handling

HTTP-Status-Codes:

  • 200 OK - Erfolgreiche GET/PUT/DELETE
  • 201 Created - Erfolgreiche POST
  • 400 Bad Request - Validierung fehlgeschlagen, fehlende Pflichtfelder
  • 401 Unauthorized - Kein oder ungültiges Auth-Token
  • 403 Forbidden - Keine Berechtigung (Sichtbarkeit/Ownership)
  • 404 Not Found - Ressource nicht gefunden
  • 409 Conflict - Duplikat, Constraint-Verletzung
  • 413 Payload Too Large - Datei zu groß
  • 500 Internal Server Error - Server-Fehler

Error-Detail-Format:

{
  "detail": "Titel, Ziel und Durchführung sind Pflichtfelder"
}

Validation Errors (detailliert):

{
  "detail": "Validation failed",
  "errors": [
    {"field": "title", "message": "Titel muss mindestens 3 Zeichen lang sein"},
    {"field": "goal", "message": "Ziel ist ein Pflichtfeld"}
  ]
}

3. Frontend-Architektur

3.1 Route-Struktur

Übungen-Routes:

  • /exercises → Grid/List mit Filtern (ExercisesPage)
  • /exercises/:id → Detail-Ansicht (ExerciseDetailPage)
  • /exercises/:id/edit → Edit-Formular (ExerciseEditPage - eigene Route, kein Modal)
  • /exercises/new → Create-Formular (ExerciseCreatePage - eigene Route, kein Modal)

Rationale für eigene Routes statt Modals:

  • Bessere Deep-Linking (shareable URLs)
  • Browser-Navigation (Back-Button)
  • State-Management einfacher
  • Mobile-freundlicher

3.2 Komponenten-Hierarchie

pages/
  ExercisesPage.jsx         (Grid + Filters)
  ExerciseDetailPage.jsx    (Formatted View)
  ExerciseEditPage.jsx      (Edit Form)
  ExerciseCreatePage.jsx    (Create Form)

components/
  exercise/
    ExerciseCard.jsx        (Grid-Item)
    ExerciseQuickInfo.jsx   (Summary-Box)
    ExerciseSection.jsx     (Collapsible Section)
    VariantsList.jsx        (Varianten-Akkordeon)
    VariantCard.jsx         (Einzelne Variante)
    MediaGallery.jsx        (Medien-Display)
    MediaUploader.jsx       (Upload/Embed-UI)
    SkillsList.jsx          (Fähigkeiten-Chips)
    CatalogAssignments.jsx  (Zuordnungen-Display)
    
  forms/
    ExerciseForm.jsx        (Haupt-Formular, wiederverwendbar)
    VariantForm.jsx         (Varianten-Formular)
    MediaUploadForm.jsx     (Upload/Embed-Tabs)
    MultiSelect.jsx         (M:N Auswahl mit Primary-Toggle)
    SkillsSelector.jsx      (Skills mit Intensity/Level)
    
  common/
    Chip.jsx                (Badge/Tag)
    Badge.jsx               (Status/Visibility)
    Accordion.jsx           (Collapsible Section)
    Modal.jsx               (Generisches Modal)
    DragDropList.jsx        (Reorder-Support)

3.3 State-Management

Strategie: Minimal, lokal, server-zentriert

  • Local State: useState für UI (filter open/close, modal visible)
  • Server State: useEffect + API-Calls (keine globale Cache)
  • Kein Redux/Zustand: Context nur für Auth + Profile
  • Optimistic Updates: Nur bei unkritischen Actions (reorder)

Beispiel (ExercisesPage):

const [exercises, setExercises] = useState([])
const [filters, setFilters] = useState({focus_area: '', status: ''})
const [loading, setLoading] = useState(true)

useEffect(() => {
  loadExercises(filters)
}, [filters])

const loadExercises = async (filters) => {
  setLoading(true)
  try {
    const data = await api.listExercises(filters)
    setExercises(data)
  } catch (err) {
    alert(err.message)
  } finally {
    setLoading(false)
  }
}

4. Datenbank-Konventionen

4.1 Naming

  • Tabellen: Plural, snake_case (exercises, exercise_variants)
  • Spalten: snake_case (created_at, is_primary)
  • IDs: {table}_id (exercise_id, skill_id)
  • Flags: is_*, has_* (is_primary, is_template, has_media)
  • Timestamps: created_at, updated_at

4.2 Standard-Spalten (IMMER)

Pflicht für alle Tabellen:

id          SERIAL PRIMARY KEY
created_at  TIMESTAMP DEFAULT NOW()
updated_at  TIMESTAMP DEFAULT NOW()  -- nur bei mutablen Tabellen

Mutable vs. Immutable:

  • Mutable: exercises, exercise_variants, exercise_blocks → haben updated_at
  • Immutable: exercise_skills, exercise_focus_areas → kein updated_at

4.3 Foreign Keys

Regeln:

  • Immer mit ON DELETE: CASCADE oder RESTRICT (nie ohne!)
  • Immer mit Index: Für alle FK-Spalten
  • Naming: fk_{from_table}_{to_table} (optional, für Debugging)

Beispiele:

-- CASCADE: Löschen der Parent-Ressource löscht auch Children
exercise_id INT REFERENCES exercises(id) ON DELETE CASCADE

-- RESTRICT: Löschen der Parent-Ressource verhindert wenn Children existieren
skill_id INT REFERENCES skills(id) ON DELETE RESTRICT

-- SET NULL: Löschen setzt FK auf NULL (für optionale Beziehungen)
prerequisite_variant_id INT REFERENCES exercise_variants(id) ON DELETE SET NULL

4.4 JSONB vs. Tabellen

JSONB verwenden wenn:

  • Daten unstrukturiert
  • Selten gefiltert/gesucht
  • Flexible Schema-Änderungen nötig

Beispiele:

  • equipment JSONB - Liste von Geräten (["Matten", "Pratzen"])
  • secondary_method_ids JSONB - Liste von IDs ([2, 5, 7])
  • placeholder_criteria JSONB - Filter-Kriterien für Platzhalter

Tabelle verwenden wenn:

  • Daten strukturiert
  • Oft gefiltert/gesucht
  • Referentielle Integrität nötig (Foreign Keys)

Beispiele:

  • exercise_skills - M:N Beziehung, oft gefiltert
  • exercise_variants - Strukturierte Daten, oft einzeln abgerufen
  • exercise_media - Referentielle Integrität (ON DELETE CASCADE)

5. Migrations-Strategie

5.1 Naming

Format: {NNN}_{descriptive_name}.sql

  • NNN = laufende Nummer (3-stellig mit führenden Nullen)
  • Beschreibend, nicht kryptisch
  • Beispiele:
    • 014_variants_progression.sql
    • 015_media_upload.sql
    • 016_exercise_blocks.sql

5.2 Idempotenz

Regel: Migrationen müssen mehrfach ausführbar sein ohne Fehler.

Immer verwenden:

-- Tabellen
CREATE TABLE IF NOT EXISTS ...

-- Spalten
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM information_schema.columns 
                   WHERE table_name='...' AND column_name='...') THEN
        ALTER TABLE ... ADD COLUMN ...;
    END IF;
END $$;

-- Indizes
CREATE INDEX IF NOT EXISTS ...

-- Constraints
ALTER TABLE ... ADD CONSTRAINT ... IF NOT EXISTS ...

Niemals:

DROP TABLE ... -- ohne IF EXISTS
ALTER TABLE ... DROP COLUMN ... -- ohne Check

5.3 Data Migration

Reihenfolge bei Schema-Änderungen:

  1. Backup: Alte Daten sichern (z.B. _backup Suffix)
  2. Schema-Änderung: Neue Spalten/Tabellen hinzufügen
  3. Data-Migration: Alte Daten in neues Schema überführen
  4. Validation: Daten-Integrität prüfen
  5. Cleanup: Alte Spalten/Tabellen als deprecated markieren (nicht sofort löschen!)

Beispiel (Migration 008):

-- 1. Backup (implizit via alte Spalten behalten)
-- 2. Schema
CREATE TABLE exercise_focus_areas ...

-- 3. Data Migration
INSERT INTO exercise_focus_areas (exercise_id, focus_area_id, is_primary)
SELECT id, focus_area_id, true
FROM exercises
WHERE focus_area_id IS NOT NULL
ON CONFLICT DO NOTHING;

-- 4. Validation (manuell via Query)
-- 5. Cleanup (später, wenn sicher)
-- ALTER TABLE exercises DROP COLUMN focus_area_id; -- NICHT JETZT!

5.4 Rollback-Plan

Jede Migration braucht:

  • Dokumentierter Rollback-Plan (im Kommentar)
  • Test auf Dev-System
  • Validierung nach Ausführung

Beispiel:

-- Migration 014: Variants Progression
-- Rollback: DROP COLUMNS progression_level, sequence_order, prerequisite_variant_id
-- Risk: LOW (nur neue Spalten, kein Data Loss)
-- Validation: SELECT COUNT(*) FROM exercise_variants WHERE progression_level IS NOT NULL;

ALTER TABLE exercise_variants
ADD COLUMN IF NOT EXISTS progression_level INT;

6. Testing-Strategie (Phase 2)

6.1 Backend

Unit-Tests:

  • Resolver-Funktionen (z.B. get_exercise())
  • Validation-Logik (z.B. validate_exercise())
  • Helper-Funktionen (z.B. parse_embed_url())

Integration-Tests:

  • API-Endpoints (Request → Response)
  • DB-Transaktionen (Create → Read → Update → Delete)
  • Permissions (Auth + Visibility)

Migration-Tests:

  • Up/Down-Migrationen
  • Data-Integrity nach Migration

6.2 Frontend

Component-Tests (Jest + React Testing Library):

  • ExerciseCard rendert korrekt
  • ExerciseForm validiert Inputs
  • MultiSelect verhält sich korrekt

E2E-Tests (Playwright):

  • User kann Übung erstellen
  • User kann Übung bearbeiten
  • User kann Varianten hinzufügen
  • User kann Medien hochladen

Visual Regression (optional):

  • Chromatic / Percy
  • Screenshots vergleichen

7. Dokumentations-Konventionen

7.1 Code-Kommentare

Python Docstrings:

def get_exercise(exercise_id: int, session: dict) -> dict:
    """
    Get exercise by ID with all enriched data.
    
    Args:
        exercise_id: Exercise ID
        session: Auth session from require_auth()
    
    Returns:
        dict: Exercise object with skills, variants, media, catalog assignments
    
    Raises:
        HTTPException: 404 if not found, 403 if forbidden
    """

JSDoc (für komplexe Komponenten):

/**
 * ExerciseCard - Grid item für Übungsdarstellung
 * 
 * @param {Object} exercise - Exercise object from API
 * @param {Function} onClick - Handler für Detail-Klick
 * @param {Function} onEdit - Handler für Edit-Klick
 * @param {Function} onDelete - Handler für Delete-Klick
 * @param {boolean} compact - Kompakte Darstellung (optional)
 */
export function ExerciseCard({ exercise, onClick, onEdit, onDelete, compact = false }) {
  // ...
}

Inline-Kommentare:

  • Nur für nicht-offensichtliche Logik
  • Warum, nicht Was
  • Beispiel: // Prerequisite muss zur gleichen Übung gehören

7.2 Markdown-Docs

Struktur:

  • functional/ - Fachliche Specs (WAS soll gebaut werden)
  • technical/ - Technische Specs (WIE wird es gebaut)
  • working/ - Temporäre Analysen, Notizen (kann veraltet sein)
  • audit/ - Reviews, Matrizen, Checklisten

Update-Regel:

  • Nach jedem Feature: Doku aktualisieren
  • Version im Header erhöhen
  • Änderungslog im Dokument

8. Entscheidungs-Log

Datum Entscheidung Rationale Impact
2026-04-24 M:N statt 1:1 für Katalog-Zuordnungen Flexibilität, keine Duplikate, bessere Filter Alle Übungen müssen migriert werden (Migration 008)
2026-04-24 Varianten für Progression statt eigene Serie-Tabelle Einfacher, weniger Joins, direkte Beziehung Variants-Tabelle erweitern (Migration 014)
2026-04-24 Lokale Medien + Embeds (nicht Cloud) Kostenkontrolle, Datenschutz, keine Vendor-Lock-in Storage-Management nötig, Backup-Strategie
2026-04-24 Eigene Routes statt Modals für Edit/Create Deep-Linking, Browser-Navigation, Mobile-UX Mehr Routes, aber bessere UX
2026-04-24 MediaWiki API statt Export Live-Sync möglich, aktuellere Daten API-Client implementieren, Semantic MediaWiki verstehen
2026-04-24 JSONB für Equipment, nicht eigene Tabelle Flexibel, selten gefiltert, keine Overhead Keine Referentielle Integrität, Full-Text-Suche schwieriger

9. Offene Punkte / TODOs

Für Review:

  • Sichtbarkeits-Matrix OK? (private/club/official)
  • Freigabe-Workflow ausreichend einfach?
  • Medien-Limits OK? (50MB, 10 Files)
  • JSONB vs. Tabellen für Equipment richtig entschieden?

Für Phase 2:

  • Explizite Freigabe-Requests (content_change_requests)
  • Saved Searches
  • Advanced Filters (Kombination mehrerer Kriterien)
  • Batch-Operations (Multi-Select + Delete/Archive)

Version: 1.0
Letzte Änderung: 2026-04-24
Status: DRAFT - Awaiting Review