From 6b4ed9bc361d56644a06c447a9ba7ebd34eaa4ed Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 24 Jul 2026 21:37:03 +0200 Subject: [PATCH] chore: initial import --- .gitignore | 30 + README.md | 10 + android/.gitignore | 11 + android/APK_BUILD_BLOCKED.txt | 9 + android/BUILD.md | 67 + android/DELIVERY_MANIFEST.md | 26 + android/KNOWN_LIMITATIONS.md | 9 + android/README.md | 14 + android/TEST-REPORT.md | 125 + android/app/build.gradle.kts | 101 + android/app/proguard-rules.pro | 3 + android/app/schemas/.gitkeep | 0 .../boehmitools/training/HiltTestRunner.kt | 12 + .../training/ui/CompactEditorUiTest.kt | 44 + android/app/src/debug/AndroidManifest.xml | 6 + .../res/xml/network_security_config_debug.xml | 6 + android/app/src/main/AndroidManifest.xml | 35 + .../boehmitools/training/MainActivity.kt | 28 + .../training/TrainingApplication.kt | 17 + .../boehmitools/training/di/AppModule.kt | 36 + .../boehmitools/training/domain/JsonDomain.kt | 513 + .../de/drazz/boehmitools/training/ui/App.kt | 72 + .../boehmitools/training/ui/AppViewModel.kt | 30 + .../training/ui/editor/EditorScreen.kt | 530 + .../training/ui/editor/EditorViewModel.kt | 429 + .../training/ui/setup/SettingsScreen.kt | 105 + .../training/ui/setup/SettingsViewModel.kt | 63 + .../boehmitools/training/ui/theme/Theme.kt | 47 + .../training/ui/tracker/TrackerScreen.kt | 614 + .../training/ui/tracker/TrackerViewModel.kt | 433 + .../res/drawable/ic_launcher_foreground.xml | 4 + android/app/src/main/res/values/strings.xml | 3 + android/app/src/main/res/values/themes.xml | 9 + .../main/res/xml/network_security_config.xml | 8 + .../training/domain/FixtureContractTest.kt | 238 + .../samples/analyses/phase-1/overall.json | 1517 ++ .../samples/analyses/phase-1/week-01.json | 2493 +++ .../plans/calihoss-newbie-8-wochen.json | 11135 ++++++++++ .../test/resources/samples/plans/phase-1.json | 16995 ++++++++++++++++ .../resources/samples/proposals/phase-1.json | 43 + .../sessions/calihoss-newbie-8-wochen.json | 612 + .../resources/samples/sessions/phase-1.json | 1393 ++ android/build-logs/backend-plugin-tests.log | 2 + android/build-logs/build-environment.log | 16 + android/build-logs/gradle-assembleDebug.log | 22 + android/build-logs/gradle-lintDebug.log | 22 + android/build-logs/gradle-test.log | 22 + android/build-logs/gradle-version.log | 20 + android/build-logs/kotlinc-static.log | 8017 ++++++++ android/build-logs/kotlinc-syntax-summary.txt | 6 + android/build-logs/resource-validation.log | 1 + android/build-logs/security-scan.log | 11 + android/build-logs/source-audit.json | 148 + android/build-logs/source-diff.log | 11 + android/build-logs/source-files.txt | 130 + android/build-logs/static-acceptance.log | 2 + .../build-logs/wrapper-bootstrap-sha256.log | 2 + android/build.gradle.kts | 8 + android/docs/API_CONTRACT.md | 280 + android/docs/DOMAIN_MODEL.md | 62 + android/docs/IMPLEMENTATION_PLAN.md | 25 + android/docs/OFFLINE_AND_CONFLICTS.md | 27 + android/docs/SCREEN_MAP.md | 36 + android/docs/SOURCE_ANALYSIS.md | 85 + android/gradle.properties | 4 + android/gradle/libs.versions.toml | 74 + .../org/gradle/wrapper/GradleWrapperMain.java | 174 + .../wrapper/gradle-8.11.1-bin.zip.sha256 | 1 + android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 6084 bytes .../gradle/wrapper/gradle-wrapper.properties | 9 + android/gradlew | 4 + android/gradlew.bat | 2 + android/settings.gradle.kts | 16 + android/tools/static_acceptance.py | 72 + deploy/local/boehmitools-training.service | 21 + deploy/local/instance.env.example | 5 + deploy/local/start-training.sh.example | 11 + docs/MANIFEST.json | 156 + docs/MIGRATION.md | 63 + docs/RELEASE-NOTES.md | 41 + plugins/trainingsplan/TOOL-README.md | 107 + plugins/trainingsplan/app.py | 450 + plugins/trainingsplan/backend.py | 38 + plugins/trainingsplan/plan_builder.py | 669 + plugins/trainingsplan/plugin.json | 30 + .../trainingsplan/presets/couchsnacks.json | 1607 ++ .../trainingsplan/presets/fruehstueck.json | 1240 ++ plugins/trainingsplan/presets/granola.json | 530 + .../trainingsplan/presets/nachmittags.json | 1450 ++ plugins/trainingsplan/recipe_builder.py | 399 + plugins/trainingsplan/schema_contract.py | 391 + plugins/trainingsplan/static/index.html | 1118 + plugins/trainingsplan/storage.py | 27 + plugins/trainingsplan/tests/test_contract.py | 70 + plugins/trainingstracker/README.md | 85 + plugins/trainingstracker/analysis_store.py | 24 + plugins/trainingstracker/app.py | 2964 +++ plugins/trainingstracker/backend.py | 39 + plugins/trainingstracker/contract.py | 9 + plugins/trainingstracker/plugin.json | 37 + plugins/trainingstracker/session_store.py | 28 + plugins/trainingstracker/static/index.html | 1634 ++ plugins/trainingstracker/tests/test_app.py | 460 + .../trainingstracker/tests/test_stability.py | 54 + testdata/analyses/phase-1/overall.json | 1517 ++ testdata/analyses/phase-1/week-01.json | 2493 +++ testdata/plans/calihoss-newbie-8-wochen.json | 11135 ++++++++++ testdata/plans/phase-1.json | 16995 ++++++++++++++++ testdata/proposals/phase-1.json | 43 + .../sessions/calihoss-newbie-8-wochen.json | 612 + testdata/sessions/phase-1.json | 1393 ++ 111 files changed, 95131 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 android/.gitignore create mode 100644 android/APK_BUILD_BLOCKED.txt create mode 100644 android/BUILD.md create mode 100644 android/DELIVERY_MANIFEST.md create mode 100644 android/KNOWN_LIMITATIONS.md create mode 100644 android/README.md create mode 100644 android/TEST-REPORT.md create mode 100644 android/app/build.gradle.kts create mode 100644 android/app/proguard-rules.pro create mode 100644 android/app/schemas/.gitkeep create mode 100644 android/app/src/androidTest/java/de/drazz/boehmitools/training/HiltTestRunner.kt create mode 100644 android/app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt create mode 100644 android/app/src/debug/AndroidManifest.xml create mode 100644 android/app/src/debug/res/xml/network_security_config_debug.xml create mode 100644 android/app/src/main/AndroidManifest.xml create mode 100644 android/app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt create mode 100644 android/app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt create mode 100644 android/app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt create mode 100644 android/app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt create mode 100644 android/app/src/main/java/de/drazz/boehmitools/training/ui/App.kt create mode 100644 android/app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt create mode 100644 android/app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt create mode 100644 android/app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt create mode 100644 android/app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt create mode 100644 android/app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt create mode 100644 android/app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt create mode 100644 android/app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt create mode 100644 android/app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt create mode 100644 android/app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 android/app/src/main/res/values/strings.xml create mode 100644 android/app/src/main/res/values/themes.xml create mode 100644 android/app/src/main/res/xml/network_security_config.xml create mode 100644 android/app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt create mode 100644 android/app/src/test/resources/samples/analyses/phase-1/overall.json create mode 100644 android/app/src/test/resources/samples/analyses/phase-1/week-01.json create mode 100644 android/app/src/test/resources/samples/plans/calihoss-newbie-8-wochen.json create mode 100644 android/app/src/test/resources/samples/plans/phase-1.json create mode 100644 android/app/src/test/resources/samples/proposals/phase-1.json create mode 100644 android/app/src/test/resources/samples/sessions/calihoss-newbie-8-wochen.json create mode 100644 android/app/src/test/resources/samples/sessions/phase-1.json create mode 100644 android/build-logs/backend-plugin-tests.log create mode 100644 android/build-logs/build-environment.log create mode 100644 android/build-logs/gradle-assembleDebug.log create mode 100644 android/build-logs/gradle-lintDebug.log create mode 100644 android/build-logs/gradle-test.log create mode 100644 android/build-logs/gradle-version.log create mode 100644 android/build-logs/kotlinc-static.log create mode 100644 android/build-logs/kotlinc-syntax-summary.txt create mode 100644 android/build-logs/resource-validation.log create mode 100644 android/build-logs/security-scan.log create mode 100644 android/build-logs/source-audit.json create mode 100644 android/build-logs/source-diff.log create mode 100644 android/build-logs/source-files.txt create mode 100644 android/build-logs/static-acceptance.log create mode 100644 android/build-logs/wrapper-bootstrap-sha256.log create mode 100644 android/build.gradle.kts create mode 100644 android/docs/API_CONTRACT.md create mode 100644 android/docs/DOMAIN_MODEL.md create mode 100644 android/docs/IMPLEMENTATION_PLAN.md create mode 100644 android/docs/OFFLINE_AND_CONFLICTS.md create mode 100644 android/docs/SCREEN_MAP.md create mode 100644 android/docs/SOURCE_ANALYSIS.md create mode 100644 android/gradle.properties create mode 100644 android/gradle/libs.versions.toml create mode 100644 android/gradle/wrapper/bootstrap-src/org/gradle/wrapper/GradleWrapperMain.java create mode 100644 android/gradle/wrapper/gradle-8.11.1-bin.zip.sha256 create mode 100644 android/gradle/wrapper/gradle-wrapper.jar create mode 100644 android/gradle/wrapper/gradle-wrapper.properties create mode 100755 android/gradlew create mode 100644 android/gradlew.bat create mode 100644 android/settings.gradle.kts create mode 100755 android/tools/static_acceptance.py create mode 100644 deploy/local/boehmitools-training.service create mode 100644 deploy/local/instance.env.example create mode 100755 deploy/local/start-training.sh.example create mode 100644 docs/MANIFEST.json create mode 100644 docs/MIGRATION.md create mode 100644 docs/RELEASE-NOTES.md create mode 100644 plugins/trainingsplan/TOOL-README.md create mode 100644 plugins/trainingsplan/app.py create mode 100644 plugins/trainingsplan/backend.py create mode 100644 plugins/trainingsplan/plan_builder.py create mode 100644 plugins/trainingsplan/plugin.json create mode 100644 plugins/trainingsplan/presets/couchsnacks.json create mode 100644 plugins/trainingsplan/presets/fruehstueck.json create mode 100644 plugins/trainingsplan/presets/granola.json create mode 100644 plugins/trainingsplan/presets/nachmittags.json create mode 100644 plugins/trainingsplan/recipe_builder.py create mode 100644 plugins/trainingsplan/schema_contract.py create mode 100644 plugins/trainingsplan/static/index.html create mode 100644 plugins/trainingsplan/storage.py create mode 100644 plugins/trainingsplan/tests/test_contract.py create mode 100644 plugins/trainingstracker/README.md create mode 100644 plugins/trainingstracker/analysis_store.py create mode 100644 plugins/trainingstracker/app.py create mode 100644 plugins/trainingstracker/backend.py create mode 100644 plugins/trainingstracker/contract.py create mode 100644 plugins/trainingstracker/plugin.json create mode 100644 plugins/trainingstracker/session_store.py create mode 100644 plugins/trainingstracker/static/index.html create mode 100644 plugins/trainingstracker/tests/test_app.py create mode 100644 plugins/trainingstracker/tests/test_stability.py create mode 100644 testdata/analyses/phase-1/overall.json create mode 100644 testdata/analyses/phase-1/week-01.json create mode 100644 testdata/plans/calihoss-newbie-8-wochen.json create mode 100644 testdata/plans/phase-1.json create mode 100644 testdata/proposals/phase-1.json create mode 100644 testdata/sessions/calihoss-newbie-8-wochen.json create mode 100644 testdata/sessions/phase-1.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b4c081 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Python +__pycache__/ +*.py[cod] +.pytest_cache/ +.venv/ +venv/ +.env +.env.* +!.env.example +data/ +logs/ +backups/ +*.pem +*.key +secrets/ + +# Android +android/.gradle/ +android/.idea/ +android/local.properties +android/**/build/ +*.apk +*.aab +*.jks +*.keystore + +# IDE / OS +.idea/ +.vscode/ +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 0000000..ee5fa64 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# boehmitools-training + +Gemeinsame Entwicklungseinheit für: + +- plugins/trainingsplan +- plugins/trainingstracker +- android/ +- Trainingsverträge und bereinigte Testdaten + +Das gemeinsame Server-Grundgerüst kommt ausschließlich aus boehmitools-core. diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..3cc702c --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,11 @@ +.gradle/ +.idea/ +local.properties +**/build/ +*.iml +*.jks +*.keystore +*.properties.local +captures/ +.externalNativeBuild/ +.cxx/ diff --git a/android/APK_BUILD_BLOCKED.txt b/android/APK_BUILD_BLOCKED.txt new file mode 100644 index 0000000..111ff28 --- /dev/null +++ b/android/APK_BUILD_BLOCKED.txt @@ -0,0 +1,9 @@ +Es wurde bewusst keine Datei app-debug.apk erzeugt. + +Die drei vorgeschriebenen Gradle-Befehle wurden am 24.07.2026 ausgeführt, +scheiterten jedoch bereits beim Download von Gradle 8.11.1, weil die +Laufumgebung services.gradle.org nicht per DNS auflösen kann. Zusätzlich ist +kein Android SDK installiert. Eine leere, umbenannte oder anderweitig +vorgetäuschte APK wäre nicht installierbar und wurde deshalb nicht geliefert. + +Siehe TEST-REPORT.md, BUILD.md und build-logs/gradle-assembleDebug.log. diff --git a/android/BUILD.md b/android/BUILD.md new file mode 100644 index 0000000..47e9309 --- /dev/null +++ b/android/BUILD.md @@ -0,0 +1,67 @@ +# Lokaler Build + +## Voraussetzungen + +- Android Studio mit JDK 17 +- Android SDK Platform 35 +- Android SDK Build-Tools für API 35 +- Internetzugriff auf Google Maven, Maven Central und die Gradle-Distribution beim ersten Build + +Minimum SDK der App ist 26. Zugangsdaten gehören nicht in `local.properties`, Gradle-Dateien oder Quellcode; sie werden erst in der App eingegeben. + +## Projekt öffnen + +1. Das Projektverzeichnis in Android Studio öffnen. +2. Falls nötig, in `local.properties` ausschließlich den lokalen SDK-Pfad setzen, zum Beispiel: + +```properties +sdk.dir=/home/user/Android/Sdk +``` + +3. Gradle synchronisieren. +4. Die Build-Variante `debug` wählen. + +## Kommandozeile + +Linux/macOS: + +```bash +./gradlew test +./gradlew lintDebug +./gradlew assembleDebug +``` + +Windows: + +```bat +gradlew.bat test +gradlew.bat lintDebug +gradlew.bat assembleDebug +``` + +Das Debug-APK liegt anschließend unter: + +```text +app/build/outputs/apk/debug/app-debug.apk +``` + +## Installation + +Bei verbundenem Gerät oder Emulator: + +```bash +adb install -r app/build/outputs/apk/debug/app-debug.apk +``` + +## Ersteinrichtung + +1. App öffnen und `Einstellungen` wählen. +2. Server-URL, Basic-Auth-Benutzername und Passwort eingeben. +3. `Speichern & Verbindung testen` ausführen. +4. Die App prüft zuerst den Host-Health-Endpunkt und danach den authentifizierten Tracker-Health-Endpunkt. + +Debug-Builds erlauben für lokale Entwicklung auch HTTP. Release-Builds akzeptieren durch Manifest, Network Security Configuration und Laufzeitprüfung ausschließlich HTTPS. + +## Gradle Wrapper + +`gradle/wrapper/gradle-wrapper.jar` ist ein kleines, im Repository enthaltenes Bootstrap-Programm. Es lädt Gradle 8.11.1 in den üblichen Wrapper-Cache, prüft die Distribution anhand der mitgelieferten SHA-256-Datei und startet anschließend Gradle. Dadurch enthält das Projekt keine lokal installierte Gradle-Abhängigkeit. diff --git a/android/DELIVERY_MANIFEST.md b/android/DELIVERY_MANIFEST.md new file mode 100644 index 0000000..e51fabf --- /dev/null +++ b/android/DELIVERY_MANIFEST.md @@ -0,0 +1,26 @@ +# Lieferumfang + +Stand: 24. Juli 2026 + +## Enthalten + +- vollständiges Android-Studio-Projekt für `de.drazz.boehmitools.training` +- native Kotlin-/Jetpack-Compose-/Material-3-App ohne WebView +- Tracker, Analyse, Planeditor, Offline-Queue und Konfliktbehandlung +- beide gelieferten Plan-JSONs und beide Session-JSONs als Test-Fixtures +- Analyse- und Vorschlags-Fixtures +- Vertrags- und Architekturdokumentation unter `docs/` +- Unit-, MockWebServer-, Repository- und Compose-UI-Tests +- reproduzierbare Prüfwerkzeuge und vollständige Buildprotokolle unter `build-logs/` + +## Nicht enthalten + +Eine `app-debug.apk` konnte in der bereitgestellten Laufumgebung nicht erzeugt werden. Gradle 8.11.1 konnte wegen fehlender DNS-Auflösung für `services.gradle.org` nicht geladen werden; zusätzlich ist kein Android SDK installiert. Es wurde bewusst keine Attrappe oder ungeprüfte Datei als APK ausgegeben. + +Der lokale Build ist in `BUILD.md` beschrieben. Der vollständige Prüfstatus steht in `TEST-REPORT.md`, der Infrastrukturblocker zusätzlich in `APK_BUILD_BLOCKED.txt`. + +## Erwartete APK nach lokalem Build + +```text +app/build/outputs/apk/debug/app-debug.apk +``` diff --git a/android/KNOWN_LIMITATIONS.md b/android/KNOWN_LIMITATIONS.md new file mode 100644 index 0000000..4506618 --- /dev/null +++ b/android/KNOWN_LIMITATIONS.md @@ -0,0 +1,9 @@ +# Bekannte Einschränkungen + +1. **Kein erzeugtes APK in dieser Ausführungsumgebung:** Der Container besitzt weder Android SDK noch eine vorinstallierte Gradle-Distribution und kann `services.gradle.org` per Shell-DNS nicht erreichen. Daher konnten die Android-Abhängigkeiten nicht aufgelöst und `app-debug.apk` hier nicht seriös erzeugt werden. Der genaue Befund und alle Befehlsprotokolle stehen in `TEST-REPORT.md`. +2. **Keine gelieferten Screenshots:** In den drei Archiven sind keine Bilddateien enthalten. Die visuelle Umsetzung basiert auf den mobilen HTML-UIs der Plugins; Pixelgleichheit zu nicht vorhandenen Screenshots kann nicht geprüft werden. +3. **Keine Geräte-/Emulatorausführung im Container:** Compose-Instrumentation, Keystore-Verhalten und reale 360-dp-Layouts konnten nur als Tests implementiert, nicht ausgeführt werden. +4. **Offline-Mutationen sind bewusst auf Session-Patches begrenzt:** Planentwürfe sind im Cache lesbar, Editoränderungen werden aber nicht offline veröffentlicht oder in eine Mutationsqueue aufgenommen. Dafür existiert im Serververtrag kein sicherer Patch-/Merge-Mechanismus. +5. **Konflikte werden nicht automatisch gemergt:** Bei `409` bleiben lokale Daten beziehungsweise Queue-Einträge sichtbar. Die App bietet bewusst nur Neuladen und explizites Verwerfen, kein riskantes Rebase. +6. **Optionale Verwaltungsendpunkte nicht als Hauptscreens:** Planimport/-export, PDF-Erzeugung, Reset und globale Kompatibilitätskonfiguration existieren serverseitig, gehören aber nicht zu den geforderten Kernflüssen der beiden nativen Plugins und wurden nicht als eigene Android-Oberflächen umgesetzt. +7. **Kein Timer:** Auch beim gelieferten Tabata-Plan wird nur das Trainingsformat angezeigt. Ein Tabata- oder Trainingstimer ist ausdrücklich nicht implementiert. diff --git a/android/README.md b/android/README.md new file mode 100644 index 0000000..c600ea4 --- /dev/null +++ b/android/README.md @@ -0,0 +1,14 @@ +# boehmitools Training + +Native Android-App für die boehmitools-Plugins `trainingsplan` und `trainingstracker` 2.0. + +- Kotlin, Jetpack Compose und Material 3 +- Application ID `de.drazz.boehmitools.training` +- Minimum SDK 26 +- konfigurierbarer Server, Standard `https://tools.d-razz.de/` +- HTTP Basic Auth mit Android-Keystore-geschütztem Passwort +- Room-Cache und FIFO-Queue für Session-Patches +- WorkManager-Synchronisation mit sichtbarer `409`-Konfliktbehandlung +- nativer Tracker, Fortschritt/Analyse und Planeditor + +Der technische Vertrag steht in `docs/API_CONTRACT.md`, die Quellenanalyse in `docs/SOURCE_ANALYSIS.md`. Lokale Buildschritte stehen in `BUILD.md`. diff --git a/android/TEST-REPORT.md b/android/TEST-REPORT.md new file mode 100644 index 0000000..d64478e --- /dev/null +++ b/android/TEST-REPORT.md @@ -0,0 +1,125 @@ +# Testbericht + +Stand: 24. Juli 2026 + +## Ergebnisübersicht + +| Prüfung | Ergebnis | Nachweis | +|---|---|---| +| Vollständiger Quellen- und Fixture-Audit | bestanden | `docs/SOURCE_ANALYSIS.md`, `build-logs/source-audit.json` | +| Vergleich aktueller Plugins gegen v2.0-Bundle | bestanden | nur erzeugte `__pycache__`-Dateien unterscheiden sich; `build-logs/source-diff.log` | +| Vorhandene Backend-Plugin-Tests | **11 bestanden, 8 übersprungen** | `build-logs/backend-plugin-tests.log` | +| Statische Android-Akzeptanzprüfung | **728 bestanden** | `build-logs/static-acceptance.log` | +| Ressourcenprüfung | 8 JSON- und 7 XML-Dateien parsebar | `build-logs/resource-validation.log` | +| Kotlin-Parserprüfung ohne Android-Classpath | keine Syntax-/Parserdiagnosen | `build-logs/kotlinc-syntax-summary.txt` | +| `./gradlew test` | **nicht ausführbar in dieser Umgebung** | Wrapper-Download scheitert an nicht auflösbarem `services.gradle.org`; `build-logs/gradle-test.log` | +| `./gradlew lintDebug` | **nicht ausführbar in dieser Umgebung** | gleicher Infrastrukturfehler; `build-logs/gradle-lintDebug.log` | +| `./gradlew assembleDebug` | **nicht ausführbar in dieser Umgebung** | gleicher Infrastrukturfehler; `build-logs/gradle-assembleDebug.log` | +| Debug-APK | **nicht erzeugt** | ohne Gradle-Distribution, Android SDK und aufgelöste Abhängigkeiten wäre ein APK nicht verifizierbar | + +## Ausgeführte Backendtests + +Ausgeführt im vollständigen boehmitools-Quellstand: + +```bash +python3 -m pytest -q \ + plugins/trainingsplan/tests \ + plugins/trainingstracker/tests +``` + +Ergebnis: + +```text +11 passed, 8 skipped in 0.05s +``` + +Die übersprungenen Tests stammen aus den gelieferten Testsets und wurden nicht von der Android-Implementierung ausgeblendet. + +## Android-Tests im Projekt + +### Unit- und Fixture-Tests + +`FixtureContractTest` prüft unter anderem: + +- beide gelieferten Pläne mit `schema_version = 3` und `contract_version = 2`, +- vollständige und entitätsspezifisch eindeutige stabile IDs, +- explizite `result_schema`-Definitionen aller Übungen und Progressionsstufen, +- Laden beider Planvarianten, +- Migration beider alten Sessiondateien ohne Itemverlust, +- Priorität manuelle Anpassung → Progressionsstufe → Übung → Fallback, +- Vererbung nicht überschriebener result_schema-Felder, +- sichere Löschung inkompatibler Messwerte bei Einheit-/Seitenwechsel, +- Normalisierung alter Freitextergebnisse zu `result_data` Version 2, +- FIFO-Overlay ausstehender Session-Patches nach App-Neustart, +- Überschreibmodell für genau eine Wochen- beziehungsweise Gesamtanalyse. + +`ApiContractTest` verwendet MockWebServer und prüft: + +- exakten Session-Patch-Pfad, `PATCH`, Basic-Auth-Header und `expected_revision`, +- `202 Accepted` sowie `200` am selben Analyse-Endpunkt, +- unveränderte Weitergabe von `401` und `409 revision_conflict`, +- dokumentierte Editor-, Publish- und Vorschlagsstatus-Pfade. + +### Repository- und Instrumentationstests + +`RepositoryIntegrationTest` prüft mit Room, WorkManager und MockWebServer: + +- Netzantwort und Room-Cache, +- sichtbare `409`-Weitergabe ohne Umwandlung in einen scheinbaren Offline-Erfolg, +- Erhalt des HTTP-Status `202` für die dauerhafte Analyse-Sperre. + +`CompactEditorUiTest` setzt eine 360-dp-breite Compose-Oberfläche auf und prüft, dass lange Übungsnamen und strukturierte Ergebnisse innerhalb der verfügbaren Breite bleiben. + +Diese Android-Tests sind vollständig im Projekt vorhanden, konnten in diesem Container aber nicht gestartet werden, weil bereits der Gradle-Wrapper-Download vor der Projektauswertung fehlschlägt. + +## Statische Akzeptanzprüfung + +Ausgeführt: + +```bash +python3 tools/static_acceptance.py +``` + +Ergebnis: + +```text +PASS: 728 Prüfungen +``` + +Die Prüfung deckt Buildparameter, Release-Netzwerksicherheit, Standardserver, verbotene WebView/OpenAI-Verbindungen, Basic Auth, zentrale API-Routen sowie alle gelieferten Plan- und Session-Fixtures ab. + +## Kotlin-Parserprüfung + +Alle Kotlin-Dateien wurden zusätzlich mit dem vorhandenen standalone `kotlinc` eingelesen. Da Android SDK und Maven-/Gradle-Abhängigkeiten fehlen, entstehen erwartungsgemäß nicht auflösbare Android-, Compose-, Hilt-, Retrofit- und Serialization-Symbole. Die separat gefilterte Prüfung meldet jedoch keine Parserfehler wie ungeschlossene Blöcke, illegale Escapes oder unerwartete Tokens. + +Dies ersetzt keinen echten Gradle-Compile-Lauf. Das vollständige Rohprotokoll ist deshalb nur Diagnosematerial und kein Bestehensnachweis. + +## Blocker der geforderten Gradle-Läufe + +Alle drei vorgeschriebenen Befehle wurden separat ausgeführt und protokolliert. Die Laufumgebung enthält: + +- keine installierte Gradle-Distribution, +- kein Android SDK, kein `sdkmanager`, kein `adb`, +- keinen vorgefüllten Gradle- oder Maven-Cache, +- keine DNS-Auflösung für `services.gradle.org` aus Shell-Prozessen. + +Der mitgelieferte Wrapper versucht korrekt Gradle 8.11.1 zu laden und beendet sich mit `UnknownHostException`. Der Fehler tritt vor der Auswertung von `settings.gradle.kts` und damit vor Kompilierung, Tests, Lint oder APK-Erzeugung auf. Details stehen in `build-logs/build-environment.log` und den drei Gradle-Protokollen. + +## Lokal nachzuholende Abnahme + +In einer normalen Android-Entwicklungsumgebung mit JDK 17, Android SDK 35 und Internetzugang: + +```bash +./gradlew test +./gradlew lintDebug +./gradlew assembleDebug +``` + +Anschließend ist zusätzlich empfehlenswert: + +```bash +./gradlew connectedDebugAndroidTest +adb install -r app/build/outputs/apk/debug/app-debug.apk +``` + +Die erwartete APK-Ausgabe ist `app/build/outputs/apk/debug/app-debug.apk`. Bis diese Befehle erfolgreich gelaufen sind, darf das Projekt nicht als vollständig buildverifiziert bezeichnet werden. diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts new file mode 100644 index 0000000..3fb041e --- /dev/null +++ b/android/app/build.gradle.kts @@ -0,0 +1,101 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.kotlin.kapt) + alias(libs.plugins.hilt) +} + +android { + namespace = "de.drazz.boehmitools.training" + compileSdk = 35 + + defaultConfig { + applicationId = "de.drazz.boehmitools.training" + minSdk = 26 + targetSdk = 35 + versionCode = 1 + versionName = "2.0.0" + testInstrumentationRunner = "de.drazz.boehmitools.training.HiltTestRunner" + vectorDrawables.useSupportLibrary = true + } + + buildTypes { + debug { + versionNameSuffix = "-debug" + } + release { + isMinifyEnabled = true + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { jvmTarget = "17" } + buildFeatures { + compose = true + buildConfig = true + } + packaging.resources.excludes += "/META-INF/{AL2.0,LGPL2.1}" + testOptions.unitTests.isIncludeAndroidResources = true +} + +kapt { + correctErrorTypes = true + arguments { arg("room.schemaLocation", "$projectDir/schemas") } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.lifecycle.runtime.compose) + implementation(libs.androidx.lifecycle.viewmodel.compose) + implementation(libs.androidx.navigation.compose) + + implementation(platform(libs.compose.bom)) + implementation(libs.compose.ui) + implementation(libs.compose.ui.tooling.preview) + implementation(libs.compose.material3) + implementation(libs.compose.icons) + debugImplementation(libs.compose.ui.tooling) + + implementation(libs.hilt.android) + kapt(libs.hilt.compiler) + implementation(libs.hilt.navigation.compose) + implementation(libs.hilt.work) + kapt(libs.hilt.work.compiler) + + implementation(libs.room.runtime) + implementation(libs.room.ktx) + kapt(libs.room.compiler) + implementation(libs.datastore.preferences) + implementation(libs.work.runtime.ktx) + + implementation(libs.retrofit) + implementation(libs.retrofit.kotlinx) + implementation(libs.okhttp) + implementation(libs.okhttp.logging) + implementation(libs.kotlinx.serialization.json) + implementation(libs.kotlinx.coroutines.android) + + testImplementation(libs.junit) + testImplementation(libs.mockwebserver) + testImplementation(libs.kotlinx.coroutines.test) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.test.core.ktx) + androidTestImplementation(libs.espresso.core) + androidTestImplementation(libs.androidx.test.runner) + androidTestImplementation(libs.hilt.android.testing) + androidTestImplementation(libs.room.testing) + androidTestImplementation(libs.work.testing) + androidTestImplementation(libs.mockwebserver) + kaptAndroidTest(libs.hilt.compiler) + androidTestImplementation(platform(libs.compose.bom)) + androidTestImplementation(libs.compose.ui.test.junit4) + debugImplementation(libs.compose.ui.test.manifest) +} diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..aa498d4 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1,3 @@ +-keepattributes Signature,*Annotation* +-keep class kotlinx.serialization.** { *; } +-dontwarn javax.annotation.** diff --git a/android/app/schemas/.gitkeep b/android/app/schemas/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/android/app/src/androidTest/java/de/drazz/boehmitools/training/HiltTestRunner.kt b/android/app/src/androidTest/java/de/drazz/boehmitools/training/HiltTestRunner.kt new file mode 100644 index 0000000..962229d --- /dev/null +++ b/android/app/src/androidTest/java/de/drazz/boehmitools/training/HiltTestRunner.kt @@ -0,0 +1,12 @@ +package de.drazz.boehmitools.training + +import android.app.Application +import android.content.Context +import androidx.test.runner.AndroidJUnitRunner +import dagger.hilt.android.testing.HiltTestApplication + +class HiltTestRunner : AndroidJUnitRunner() { + override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application { + return super.newApplication(cl, HiltTestApplication::class.java.name, context) + } +} diff --git a/android/app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt b/android/app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt new file mode 100644 index 0000000..039cd3c --- /dev/null +++ b/android/app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt @@ -0,0 +1,44 @@ +package de.drazz.boehmitools.training.ui + +import androidx.activity.ComponentActivity +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertWidthIsAtMost +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.unit.dp +import org.junit.Rule +import org.junit.Test + +class CompactEditorUiTest { + @get:Rule val compose = createAndroidComposeRule() + + @Test + fun longExerciseNamesWrapWithin360Dp() { + compose.setContent { + MaterialTheme { + Column(Modifier.width(360.dp).testTag("root")) { + Text( + "Sehr langer Übungsname mit Progressionshinweis und mehreren Ergebniswerten, der sicher umbrechen muss", + modifier = Modifier.fillMaxWidth().testTag("long-name"), + ) + OutlinedTextField( + value = "8/8/7 Wiederholungen je Seite bei optionalem Gewicht", + onValueChange = {}, + modifier = Modifier.fillMaxWidth().testTag("field"), + label = { Text("Strukturiertes Ergebnis") }, + ) + } + } + } + compose.onNodeWithTag("long-name").assertIsDisplayed().assertWidthIsAtMost(360.dp) + compose.onNodeWithTag("field").assertIsDisplayed().assertWidthIsAtMost(360.dp) + } +} diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..f7d35a9 --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + diff --git a/android/app/src/debug/res/xml/network_security_config_debug.xml b/android/app/src/debug/res/xml/network_security_config_debug.xml new file mode 100644 index 0000000..345889e --- /dev/null +++ b/android/app/src/debug/res/xml/network_security_config_debug.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f573f72 --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt b/android/app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt new file mode 100644 index 0000000..2aaa706 --- /dev/null +++ b/android/app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt @@ -0,0 +1,28 @@ +package de.drazz.boehmitools.training + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.runtime.getValue +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import dagger.hilt.android.AndroidEntryPoint +import de.drazz.boehmitools.training.ui.AppRoot +import de.drazz.boehmitools.training.ui.AppViewModel +import de.drazz.boehmitools.training.ui.theme.TrainingTheme + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + val vm: AppViewModel = hiltViewModel() + val settings by vm.settings.collectAsStateWithLifecycle() + TrainingTheme(settings.theme) { + AppRoot(settings = settings, onTheme = vm::setTheme) + } + } + } +} diff --git a/android/app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt b/android/app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt new file mode 100644 index 0000000..71652a3 --- /dev/null +++ b/android/app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt @@ -0,0 +1,17 @@ +package de.drazz.boehmitools.training + +import android.app.Application +import androidx.hilt.work.HiltWorkerFactory +import androidx.work.Configuration +import dagger.hilt.android.HiltAndroidApp +import javax.inject.Inject + +@HiltAndroidApp +class TrainingApplication : Application(), Configuration.Provider { + @Inject lateinit var workerFactory: HiltWorkerFactory + + override val workManagerConfiguration: Configuration + get() = Configuration.Builder() + .setWorkerFactory(workerFactory) + .build() +} diff --git a/android/app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt b/android/app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt new file mode 100644 index 0000000..a635ec5 --- /dev/null +++ b/android/app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt @@ -0,0 +1,36 @@ +package de.drazz.boehmitools.training.di + +import android.content.Context +import androidx.room.Room +import androidx.work.WorkManager +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import de.drazz.boehmitools.training.data.local.TrainingDatabase +import javax.inject.Singleton +import kotlinx.serialization.json.Json + +@Module +@InstallIn(SingletonComponent::class) +object AppModule { + @Provides + @Singleton + fun json(): Json = Json { + ignoreUnknownKeys = true + explicitNulls = false + encodeDefaults = true + isLenient = true + } + + @Provides + @Singleton + fun database(@ApplicationContext context: Context): TrainingDatabase = + Room.databaseBuilder(context, TrainingDatabase::class.java, "training.db") + .build() + + @Provides + @Singleton + fun workManager(@ApplicationContext context: Context): WorkManager = WorkManager.getInstance(context) +} diff --git a/android/app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt b/android/app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt new file mode 100644 index 0000000..35e4440 --- /dev/null +++ b/android/app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt @@ -0,0 +1,513 @@ +package de.drazz.boehmitools.training.domain + +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.booleanOrNull +import kotlinx.serialization.json.buildJsonArray +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.contentOrNull +import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.intOrNull +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put + +fun JsonObject.string(key: String, fallback: String = ""): String = this[key]?.jsonPrimitive?.contentOrNull ?: fallback +fun JsonObject.int(key: String, fallback: Int = 0): Int = this[key]?.jsonPrimitive?.intOrNull ?: fallback +fun JsonObject.bool(key: String, fallback: Boolean = false): Boolean = this[key]?.jsonPrimitive?.booleanOrNull ?: fallback +fun JsonObject.obj(key: String): JsonObject = this[key] as? JsonObject ?: JsonObject(emptyMap()) +fun JsonObject.array(key: String): JsonArray = this[key] as? JsonArray ?: JsonArray(emptyList()) + +fun JsonObject.updated(key: String, value: JsonElement?): JsonObject = JsonObject(toMutableMap().apply { + if (value == null) remove(key) else put(key, value) +}) + +fun JsonObject.updatedString(key: String, value: String): JsonObject = updated(key, JsonPrimitive(value)) +fun JsonObject.updatedInt(key: String, value: Int): JsonObject = updated(key, JsonPrimitive(value)) +fun JsonObject.updatedBool(key: String, value: Boolean): JsonObject = updated(key, JsonPrimitive(value)) + +data class ResultSchemaUi( + val mode: String = "reps", + val weightMode: String = "none", + val laterality: String = "bilateral", + val sidesMode: String = "same", + val defaultSets: Int = 3, + val lockedSets: Boolean = false, + val source: String = "fallback", +) + +data class ProgressionStepUi( + val id: String, + val name: String, + val resultSchema: ResultSchemaUi?, + val raw: JsonObject, +) + +data class ProgressionUi( + val id: String, + val key: String, + val name: String, + val steps: List, + val raw: JsonObject, +) + +data class ExerciseUi( + val id: String, + val legacyId: String, + val exerciseId: String, + val progressionId: String, + val name: String, + val cue: String, + val resultSchema: ResultSchemaUi?, + val raw: JsonObject, +) + +data class RotationUi( + val id: String, + val label: String, + val exercises: List, + val raw: JsonObject, +) + +data class DayUi( + val id: String, + val num: Int, + val focus: String, + val light: Boolean, + val warmup: List, + val cooldown: List, + val stretch: List, + val rotations: List, + val raw: JsonObject, +) + +data class PlanUi( + val sourceFile: String, + val planId: String, + val title: String, + val subtitle: String, + val weeks: Int, + val publishedRevision: Int, + val days: List, + val progressions: Map, + val trainingFormat: JsonObject, + val exerciseCatalog: JsonObject, + val front: JsonObject, + val raw: JsonObject, +) + +fun parsePlan(plan: JsonObject): PlanUi { + val progressions = parseProgressions(plan["stages"]) + val prepost = plan.obj("prepost") + val meta = plan.obj("meta") + val days = plan.array("days").mapNotNull { it as? JsonObject }.map { day -> + val dayNum = day.int("num", 1) + val rotations = day.array("rotations").mapNotNull { it as? JsonObject }.mapIndexed { ri, rotation -> + val exercises = rotation.array("exercises").mapNotNull { it as? JsonObject }.mapIndexed { ei, exercise -> + ExerciseUi( + id = exercise.string("id", "exercise-$dayNum-$ri-$ei"), + legacyId = exercise.string("legacy_id", "d$dayNum-r$ri-e$ei"), + exerciseId = exercise.string("exercise_id"), + progressionId = exercise.string("progression_id", exercise.string("key")), + name = exercise.string("name", "Übung ${ei + 1}"), + cue = exercise.string("cue"), + resultSchema = parseResultSchema(exercise["result_schema"] as? JsonObject, "exercise"), + raw = exercise, + ) + } + RotationUi( + id = rotation.string("id", "rotation-$dayNum-$ri"), + label = rotation.string("label", "Block ${ri + 1}"), + exercises = exercises, + raw = rotation, + ) + } + val legacyPrePost = prepost.obj(dayNum.toString()) + fun sequence(name: String): List { + val normalized = day.obj(name).array("items").mapNotNull { it.jsonPrimitive.contentOrNull } + if (normalized.isNotEmpty()) return normalized + return legacyPrePost.string(name) + .split(Regex("""|\n""", RegexOption.IGNORE_CASE)) + .map { it.replace(Regex("<[^>]+>"), "").trim() } + .filter { it.isNotBlank() } + } + DayUi( + id = day.string("id", "day-$dayNum"), + num = dayNum, + focus = day.string("focus", "Tag $dayNum"), + light = day.bool("light"), + warmup = sequence("warmup"), + cooldown = sequence("cooldown"), + stretch = sequence("stretch"), + rotations = rotations, + raw = day, + ) + }.sortedBy { it.num } + return PlanUi( + sourceFile = plan.string("source_file"), + planId = plan.string("plan_id"), + title = plan.string("title", meta.string("title", plan.string("name"))), + subtitle = plan.string("subtitle", meta.string("subtitle")), + weeks = plan.int("weeks", meta.int("weeks", 1)).coerceAtLeast(1), + publishedRevision = plan.int("published_revision", 1), + days = days, + progressions = progressions, + trainingFormat = plan.obj("training_format"), + exerciseCatalog = plan.obj("exercise_catalog"), + front = plan.obj("front"), + raw = plan, + ) +} + +private fun parseProgressions(element: JsonElement?): Map { + val entries: List> = when (element) { + is JsonArray -> element.mapNotNull { value -> + val stage = value as? JsonObject ?: return@mapNotNull null + val key = stage.string("key", stage.string("id")) + key to stage + } + is JsonObject -> element.mapNotNull { (mapKey, value) -> + val stage = value as? JsonObject ?: return@mapNotNull null + mapKey to stage + } + else -> emptyList() + } + return entries.associate { (mapKey, stage) -> + val key = stage.string("key", mapKey.ifBlank { stage.string("id") }) + val steps = stage.array("steps").mapNotNull { it as? JsonObject }.map { step -> + ProgressionStepUi( + id = step.string("id"), + name = step.string("name"), + resultSchema = parseResultSchema(step["result_schema"] as? JsonObject, "progression"), + raw = step, + ) + } + key to ProgressionUi( + id = stage.string("id", key), + key = key, + name = stage.string("name", key), + steps = steps, + raw = stage, + ) + } +} + +fun parseResultSchema(raw: JsonObject?, source: String): ResultSchemaUi? { + if (raw == null || raw.isEmpty()) return null + val mode = raw.string("mode", "auto") + val normalizedMode = if (mode in setOf("reps", "seconds", "minutes", "none", "auto")) mode else "auto" + return ResultSchemaUi( + mode = normalizedMode, + weightMode = raw.string("weight_mode", "none").takeIf { it in setOf("none", "optional", "required") } ?: "none", + laterality = raw.string("laterality", "bilateral").takeIf { it in setOf("bilateral", "unilateral") } ?: "bilateral", + sidesMode = raw.string("sides_mode", "same").takeIf { it in setOf("same", "separate") } ?: "same", + defaultSets = (raw.int("default_sets", raw.int("sets", 3))).coerceIn(1, 20), + lockedSets = raw.bool("locked_sets"), + source = source, + ) +} + +fun mergeResultSchemaLayers( + exerciseSchema: JsonObject?, + progressionSchema: JsonObject?, + manualSchema: JsonObject?, +): ResultSchemaUi { + fun apply(base: ResultSchemaUi, raw: JsonObject?, source: String): ResultSchemaUi { + if (raw == null || raw.isEmpty()) return base + val mode = raw["mode"]?.jsonPrimitive?.contentOrNull + ?.takeIf { it in setOf("reps", "seconds", "minutes", "none", "auto") } + ?: base.mode + val weightMode = raw["weight_mode"]?.jsonPrimitive?.contentOrNull + ?.takeIf { it in setOf("none", "optional", "required") } + ?: base.weightMode + val laterality = raw["laterality"]?.jsonPrimitive?.contentOrNull + ?.takeIf { it in setOf("bilateral", "unilateral") } + ?: base.laterality + val sidesMode = raw["sides_mode"]?.jsonPrimitive?.contentOrNull + ?.takeIf { it in setOf("same", "separate") } + ?: base.sidesMode + val sets = when { + raw["sets"]?.jsonPrimitive?.intOrNull != null -> raw["sets"]!!.jsonPrimitive.intOrNull + raw["default_sets"]?.jsonPrimitive?.intOrNull != null -> raw["default_sets"]!!.jsonPrimitive.intOrNull + else -> null + }?.coerceIn(1, 20) ?: base.defaultSets + val locked = raw["locked_sets"]?.jsonPrimitive?.booleanOrNull ?: base.lockedSets + return base.copy( + mode = mode, + weightMode = weightMode, + laterality = laterality, + sidesMode = if (laterality == "bilateral") "same" else sidesMode, + defaultSets = sets, + lockedSets = locked, + source = source, + ) + } + + var merged = ResultSchemaUi() + merged = apply(merged, exerciseSchema, "exercise") + merged = apply(merged, progressionSchema, "progression") + merged = apply(merged, manualSchema, "session") + if ((exerciseSchema == null || exerciseSchema.isEmpty()) && + (progressionSchema == null || progressionSchema.isEmpty()) && + (manualSchema == null || manualSchema.isEmpty()) + ) { + merged = merged.copy(source = "fallback") + } + return merged +} + +fun resolveResultSchema( + exercise: ExerciseUi, + item: JsonObject, + progressionText: String, + plan: PlanUi, +): ResultSchemaUi { + val manualRaw = item["result_format"] as? JsonObject + val progression = plan.progressions[exercise.progressionId] + val stepId = item.string("progression_step_id") + val step = progression?.steps?.firstOrNull { it.id == stepId } + ?: progression?.steps?.firstOrNull { it.name.equals(progressionText, ignoreCase = true) } + val stepRaw = step?.raw?.get("result_schema") as? JsonObject + val exerciseRaw = exercise.raw["result_schema"] as? JsonObject + val base = mergeResultSchemaLayers(exerciseRaw, stepRaw, manualRaw) + + val text = "${exercise.name} $progressionText ${exercise.cue}".lowercase() + val static = Regex("hold|plank|hang|wall sit|wandsitz|isometr|stützposition").containsMatchIn(text) + val dynamic = Regex("push.?up|pull.?up|klimmzug|liegestütz|senkung|negativ|reps|rudern|row|squat|kniebeuge|dip|curl|deadlift|rdl|hinge|swing|raise|heben|climber|crunch|twist|lunge|ausfallschritt").containsMatchIn(progressionText.lowercase()) + val mode = when { + base.mode == "auto" -> if (static && !dynamic) "seconds" else "reps" + base.source == "exercise" && base.mode !in setOf("none", "minutes") && static && !dynamic -> "seconds" + else -> base.mode + } + val inferredLaterality = when { + base.source !in setOf("progression", "session") && Regex("einarm|einbein|je seite|pro seite").containsMatchIn(progressionText.lowercase()) -> "unilateral" + base.source !in setOf("progression", "session") && Regex("beidbeinig|zweiarmig|beide seiten gemeinsam").containsMatchIn(progressionText.lowercase()) -> "bilateral" + else -> base.laterality + } + val inferredWeight = if (base.source in setOf("progression", "session")) base.weightMode else { + val low = progressionText.lowercase() + val unweighted = Regex("ohne gewicht|ungewichtet|körpergewicht|koerpergewicht|\\bbw\\b").containsMatchIn(low) + val loaded = Regex("gewicht|gewichtet|zusatzlast|\\blast\\b|\\bkg\\b|kettlebell|\\bkb\\b|rucksack").containsMatchIn(low) + when { + unweighted && loaded -> "optional" + unweighted -> "none" + loaded -> if (Regex("möglich|optional|bei 8").containsMatchIn(low)) "optional" else "required" + else -> base.weightMode + } + } + return base.copy( + mode = mode, + laterality = inferredLaterality, + sidesMode = if (inferredLaterality == "unilateral") base.sidesMode else "same", + weightMode = inferredWeight, + ) +} + +fun sessionKey(week: Int, day: Int): String = "w%02d-d%02d".format(week, day) + +fun blankSession(plan: PlanUi): JsonObject = buildJsonObject { + put("status", "planned") + put("plan_id", plan.planId) + put("plan_revision", plan.publishedRevision) + put("items", JsonObject(emptyMap())) + put("note", "") +} + +fun trackerSession(tracker: JsonObject, key: String, plan: PlanUi): JsonObject { + return (tracker.obj("sessions")[key] as? JsonObject) ?: blankSession(plan) +} + +fun migrateLegacyItems(session: JsonObject, day: DayUi): Pair { + val items = session.obj("items").toMutableMap() + var changed = false + day.rotations.flatMap { it.exercises }.forEach { exercise -> + if (items[exercise.id] == null && exercise.legacyId.isNotBlank() && items[exercise.legacyId] != null) { + items[exercise.id] = items.getValue(exercise.legacyId) + items.remove(exercise.legacyId) + changed = true + } + } + return session.updated("items", JsonObject(items)) to changed +} + +fun itemFor(session: JsonObject, exerciseId: String): JsonObject = session.obj("items")[exerciseId] as? JsonObject + ?: buildJsonObject { + put("completion_status", "planned") + put("done", false) + put("progression", "") + put("result", "") + put("note", "") + } + +fun updateItem(session: JsonObject, exerciseId: String, item: JsonObject): JsonObject { + val items = session.obj("items").toMutableMap().apply { put(exerciseId, item) } + return session.updated("items", JsonObject(items)) +} + +fun updateTrackerSession(tracker: JsonObject, key: String, session: JsonObject): JsonObject { + val sessions = tracker.obj("sessions").toMutableMap().apply { put(key, session) } + return tracker.updated("sessions", JsonObject(sessions)) +} + +fun sessionPatch(tracker: JsonObject, key: String, session: JsonObject): JsonObject = buildJsonObject { + put("expected_revision", tracker.int("revision", 1)) + put("profile", tracker.obj("profile")) + put("week_statuses", tracker.obj("week_statuses")) + put("session_key", key) + put("session", session) +} + +fun overlaySessionPatches(base: JsonObject, patches: List): JsonObject { + var tracker = base + patches.forEach { patch -> + patch["profile"]?.let { tracker = tracker.updated("profile", it) } + patch["week_statuses"]?.let { tracker = tracker.updated("week_statuses", it) } + val key = patch.string("session_key") + val session = patch["session"] as? JsonObject + if (key.isNotBlank() && session != null) tracker = updateTrackerSession(tracker, key, session) + val localRevision = patch.int("expected_revision", tracker.int("revision", 1)) + 1 + if (localRevision > tracker.int("revision", 1)) tracker = tracker.updatedInt("revision", localRevision) + } + return tracker +} + +fun adaptResultDataToSchema(current: JsonObject, schema: ResultSchemaUi): JsonObject? { + if (schema.mode == "none") return null + val oldMode = current.string("mode", schema.mode) + val oldLaterality = current.string("laterality", schema.laterality) + val oldSides = current.string("sides_mode", schema.sidesMode) + val incompatible = oldMode != schema.mode || oldLaterality != schema.laterality || + (schema.laterality == "unilateral" && oldSides != schema.sidesMode) + var data = current + .updatedString("mode", schema.mode) + .updatedString("laterality", schema.laterality) + .updatedString("sides_mode", if (schema.laterality == "unilateral") schema.sidesMode else "same") + if (incompatible) { + data = data + .updated("values", JsonArray(emptyList())) + .updated("left_values", JsonArray(emptyList())) + .updated("right_values", JsonArray(emptyList())) + } + if (schema.lockedSets) data = resultDataWithSets(data, schema.defaultSets) + if (schema.weightMode == "none") data = data.updated("weight_kg", null) + return normalizeResultData(data, schema) +} + +fun emptyResultData(schema: ResultSchemaUi): JsonObject { + val sets = schema.defaultSets.coerceIn(1, 20) + return buildJsonObject { + put("version", 2) + put("mode", schema.mode) + put("laterality", schema.laterality) + put("sides_mode", if (schema.laterality == "unilateral") schema.sidesMode else "same") + put("sets", sets) + put("weight_kg", JsonNull) + put("values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + put("left_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + put("right_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + } +} + +fun normalizedResultData(item: JsonObject, schema: ResultSchemaUi): JsonObject { + val structured = item["result_data"] as? JsonObject + if (structured != null) return normalizeResultData(structured, schema) + return parseLegacyResult(item.string("result"), schema) ?: emptyResultData(schema) +} + +private fun normalizeResultData(raw: JsonObject, schema: ResultSchemaUi): JsonObject { + val sets = raw.int("sets", schema.defaultSets).coerceIn(1, 20) + fun normalizedArray(key: String): JsonArray { + val values = raw.array(key).take(sets).toMutableList() + while (values.size < sets) values.add(JsonNull) + return JsonArray(values) + } + return buildJsonObject { + put("version", 2) + put("mode", raw.string("mode", schema.mode)) + put("laterality", raw.string("laterality", schema.laterality)) + put("sides_mode", raw.string("sides_mode", schema.sidesMode)) + put("sets", sets) + put("weight_kg", raw["weight_kg"] ?: JsonNull) + put("values", normalizedArray("values")) + put("left_values", normalizedArray("left_values")) + put("right_values", normalizedArray("right_values")) + } +} + +fun parseLegacyResult(text: String, schema: ResultSchemaUi): JsonObject? { + val raw = text.trim() + if (raw.isBlank()) return null + val weight = Regex("(\\d+(?:[.,]\\d+)?)\\s*kg", RegexOption.IGNORE_CASE) + .find(raw)?.groupValues?.getOrNull(1)?.replace(',', '.')?.toDoubleOrNull() + val repeatedSeconds = Regex("(\\d+)\\s*[x×]\\s*(\\d+(?:[.,]\\d+)?)\\s*s", RegexOption.IGNORE_CASE).find(raw) + val values: List = when { + repeatedSeconds != null -> { + val count = repeatedSeconds.groupValues[1].toIntOrNull()?.coerceIn(1, 20) ?: 1 + val value = repeatedSeconds.groupValues[2].replace(',', '.').toDoubleOrNull() ?: return null + List(count) { value } + } + else -> raw.replace(Regex("\\d+(?:[.,]\\d+)?\\s*kg", RegexOption.IGNORE_CASE), "") + .split('/', ';') + .mapNotNull { Regex("-?\\d+(?:[.,]\\d+)?").find(it)?.value?.replace(',', '.')?.toDoubleOrNull() } + .take(20) + } + if (values.isEmpty() && weight == null) return null + val mode = when { + repeatedSeconds != null || Regex("\\bsek|\\bs\\b|second", RegexOption.IGNORE_CASE).containsMatchIn(raw) -> "seconds" + Regex("min", RegexOption.IGNORE_CASE).containsMatchIn(raw) -> "minutes" + else -> schema.mode.takeIf { it in setOf("reps", "seconds", "minutes") } ?: "reps" + } + val sets = values.size.coerceAtLeast(1) + return buildJsonObject { + put("version", 2) + put("mode", mode) + put("laterality", schema.laterality) + put("sides_mode", schema.sidesMode) + put("sets", sets) + put("weight_kg", weight?.let(::JsonPrimitive) ?: JsonNull) + put("values", JsonArray(values.map(::JsonPrimitive))) + put("left_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + put("right_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + } +} + +fun formatResultData(data: JsonObject): String { + fun num(element: JsonElement?): String = element?.jsonPrimitive?.doubleOrNull?.let { + if (it % 1.0 == 0.0) it.toInt().toString() else it.toString().trimEnd('0').trimEnd('.') + } ?: "–" + val mode = data.string("mode", "reps") + val unit = when (mode) { "seconds" -> " s"; "minutes" -> " min"; else -> " Reps" } + val weight = data["weight_kg"]?.jsonPrimitive?.doubleOrNull?.let { "${num(JsonPrimitive(it))} kg · " }.orEmpty() + val laterality = data.string("laterality", "bilateral") + val separate = laterality == "unilateral" && data.string("sides_mode") == "separate" + return if (separate) { + val left = data.array("left_values").joinToString("/") { num(it) } + val right = data.array("right_values").joinToString("/") { num(it) } + "$weight L $left · R $right$unit" + } else { + val values = data.array("values").joinToString("/") { num(it) } + "$weight$values$unit${if (laterality == "unilateral") " je Seite" else ""}" + }.trim() +} + +fun resultDataWithValue(data: JsonObject, field: String, index: Int, value: Double?): JsonObject { + val list = data.array(field).toMutableList() + while (list.size <= index) list.add(JsonNull) + list[index] = value?.let(::JsonPrimitive) ?: JsonNull + return data.updated(field, JsonArray(list)) +} + +fun resultDataWithSets(data: JsonObject, sets: Int): JsonObject { + val count = sets.coerceIn(1, 20) + var result = data.updatedInt("sets", count) + for (field in listOf("values", "left_values", "right_values")) { + val list = result.array(field).take(count).toMutableList() + while (list.size < count) list.add(JsonNull) + result = result.updated(field, JsonArray(list)) + } + return result +} diff --git a/android/app/src/main/java/de/drazz/boehmitools/training/ui/App.kt b/android/app/src/main/java/de/drazz/boehmitools/training/ui/App.kt new file mode 100644 index 0000000..91d9a04 --- /dev/null +++ b/android/app/src/main/java/de/drazz/boehmitools/training/ui/App.kt @@ -0,0 +1,72 @@ +package de.drazz.boehmitools.training.ui + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material.icons.filled.FitnessCenter +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material3.Icon +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.navigation.NavDestination.Companion.hierarchy +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.currentBackStackEntryAsState +import androidx.navigation.compose.rememberNavController +import de.drazz.boehmitools.training.data.settings.SettingsRepository +import de.drazz.boehmitools.training.ui.editor.EditorScreen +import de.drazz.boehmitools.training.ui.setup.SettingsScreen +import de.drazz.boehmitools.training.ui.tracker.TrackerScreen + +private data class AppDestination(val route: String, val label: String, val icon: @Composable () -> Unit) + +@Composable +fun AppRoot( + settings: SettingsRepository.PublicSettings, + onTheme: (String) -> Unit, +) { + val navController = rememberNavController() + val destinations = listOf( + AppDestination("tracker", "Tracker") { Icon(Icons.Default.FitnessCenter, null) }, + AppDestination("editor", "Planeditor") { Icon(Icons.Default.Edit, null) }, + AppDestination("settings", "Einstellungen") { Icon(Icons.Default.Settings, null) }, + ) + val backStack by navController.currentBackStackEntryAsState() + Scaffold( + bottomBar = { + NavigationBar { + destinations.forEach { destination -> + val selected = backStack?.destination?.hierarchy?.any { it.route == destination.route } == true + NavigationBarItem( + selected = selected, + onClick = { + navController.navigate(destination.route) { + popUpTo("tracker") { saveState = true } + launchSingleTop = true + restoreState = true + } + }, + icon = destination.icon, + label = { Text(destination.label) }, + ) + } + } + }, + ) { padding -> + NavHost(navController = navController, startDestination = "tracker", modifier = Modifier) { + composable("tracker") { TrackerScreen(contentPadding = padding) } + composable("editor") { EditorScreen(contentPadding = padding) } + composable("settings") { + SettingsScreen( + contentPadding = padding, + current = settings, + onTheme = onTheme, + ) + } + } + } +} diff --git a/android/app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt b/android/app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt new file mode 100644 index 0000000..532024a --- /dev/null +++ b/android/app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt @@ -0,0 +1,30 @@ +package de.drazz.boehmitools.training.ui + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import de.drazz.boehmitools.training.data.settings.SettingsRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch + +@HiltViewModel +class AppViewModel @Inject constructor( + private val settingsRepository: SettingsRepository, +) : ViewModel() { + val settings = settingsRepository.publicSettings.stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(5_000), + SettingsRepository.PublicSettings( + SettingsRepository.DEFAULT_SERVER, + "admin", + false, + "system", + ), + ) + + fun setTheme(theme: String) { + viewModelScope.launch { settingsRepository.setTheme(theme) } + } +} diff --git a/android/app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt b/android/app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt new file mode 100644 index 0000000..6e04eb8 --- /dev/null +++ b/android/app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt @@ -0,0 +1,530 @@ +package de.drazz.boehmitools.training.ui.editor + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.Delete +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.AssistChip +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilterChip +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.ScrollableTabRow +import androidx.compose.material3.Tab +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import de.drazz.boehmitools.training.domain.array +import de.drazz.boehmitools.training.domain.bool +import de.drazz.boehmitools.training.domain.int +import de.drazz.boehmitools.training.domain.obj +import de.drazz.boehmitools.training.domain.string +import de.drazz.boehmitools.training.domain.updatedBool +import de.drazz.boehmitools.training.domain.updatedInt +import de.drazz.boehmitools.training.domain.updatedString +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive + +private val editorTabs = listOf("Grunddaten", "Tage", "Progressionen", "Bibliothek", "Prüfen", "Vorschläge") + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EditorScreen( + contentPadding: PaddingValues, + viewModel: EditorViewModel = hiltViewModel(), +) { + val state by viewModel.state.collectAsStateWithLifecycle() + var selectedTab by remember { mutableIntStateOf(0) } + var planMenu by remember { mutableStateOf(false) } + var createDialog by remember { mutableStateOf(false) } + var renameDialog by remember { mutableStateOf(false) } + var deleteDialog by remember { mutableStateOf(false) } + + state.conflict?.let { conflict -> + AlertDialog( + onDismissRequest = viewModel::dismissConflict, + title = { Text("Revisionskonflikt") }, + text = { Text("Der Serverplan liegt inzwischen bei Revision ${conflict.currentRevision}. Deine Änderungen wurden nicht überschrieben.") }, + confirmButton = { TextButton(onClick = viewModel::reloadAfterConflict) { Text("Serverstand neu laden") } }, + dismissButton = { TextButton(onClick = viewModel::dismissConflict) { Text("Bearbeitung behalten") } }, + ) + } + + if (createDialog) NameDialog( + title = "Neuen Plan anlegen", + initial = "", + allowClone = state.selectedPlanId.isNotBlank(), + onDismiss = { createDialog = false }, + onConfirm = { name, clone -> createDialog = false; viewModel.createPlan(name, clone) }, + ) + if (renameDialog) NameDialog( + title = "Plan umbenennen", + initial = state.wrapper.string("name"), + allowClone = false, + onDismiss = { renameDialog = false }, + onConfirm = { name, _ -> renameDialog = false; viewModel.renamePlan(name) }, + ) + if (deleteDialog) AlertDialog( + onDismissRequest = { deleteDialog = false }, + title = { Text("Plan löschen?") }, + text = { Text("Der Server legt vor dem Löschen ein Backup an. Der letzte verbleibende Plan kann nicht gelöscht werden.") }, + confirmButton = { TextButton(onClick = { deleteDialog = false; viewModel.deletePlan() }) { Text("Löschen") } }, + dismissButton = { TextButton(onClick = { deleteDialog = false }) { Text("Abbrechen") } }, + ) + + Scaffold( + modifier = Modifier.padding(contentPadding), + topBar = { + TopAppBar( + title = { + Box { + TextButton(onClick = { planMenu = true }) { + Text(state.wrapper.string("name", "Planeditor"), maxLines = 2) + } + DropdownMenu(expanded = planMenu, onDismissRequest = { planMenu = false }) { + state.plans.forEach { plan -> + DropdownMenuItem( + text = { Text("${plan.name}${if (plan.hasUnpublishedChanges) " •" else ""}") }, + onClick = { planMenu = false; viewModel.selectPlan(plan.id) }, + ) + } + } + } + }, + actions = { + IconButton(onClick = { createDialog = true }) { Icon(Icons.Default.Add, "Plan anlegen") } + Box { + IconButton(onClick = { planMenu = true }) { Icon(Icons.Default.MoreVert, "Planaktionen") } + } + }, + ) + }, + ) { inner -> + Column(Modifier.fillMaxSize().padding(inner)) { + ScrollableTabRow(selectedTabIndex = selectedTab, edgePadding = 8.dp) { + editorTabs.forEachIndexed { index, title -> + Tab(selected = index == selectedTab, onClick = { selectedTab = index }, text = { Text(title) }) + } + } + if (state.loading) { + Box(Modifier.fillMaxSize().padding(32.dp)) { CircularProgressIndicator() } + } else if (state.selectedPlanId.isBlank()) { + EmptyEditor(onCreate = { createDialog = true }) + } else { + Column(Modifier.fillMaxSize()) { + StatusStrip(state) + when (selectedTab) { + 0 -> BasicsPane(state, viewModel, onRename = { renameDialog = true }, onDelete = { deleteDialog = true }) + 1 -> DaysPane(state, viewModel) + 2 -> ProgressionsPane(state, viewModel) + 3 -> LibraryPane(state) + 4 -> ValidationPane(state, viewModel) + else -> ProposalsPane(state, viewModel) { selectedTab = 1 } + } + } + } + } + } +} + +@Composable +private fun StatusStrip(state: EditorUiState) { + Column(Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 6.dp), verticalArrangement = Arrangement.spacedBy(3.dp)) { + Text( + "Entwurf R${state.wrapper.int("revision", 1)} · veröffentlicht R${state.wrapper.int("published_revision", 1)}${if (state.dirty) " · ungespeichert" else ""}", + style = MaterialTheme.typography.bodySmall, + ) + when { + state.error.isNotBlank() -> Text(state.error, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall) + state.message.isNotBlank() -> Text(state.message, style = MaterialTheme.typography.bodySmall) + } + } +} + +@Composable +private fun EmptyEditor(onCreate: () -> Unit) { + Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Text("Noch kein Trainingsplan verfügbar.", style = MaterialTheme.typography.headlineSmall) + Button(onClick = onCreate) { Text("Plan anlegen") } + } +} + +@Composable +private fun BasicsPane(state: EditorUiState, vm: EditorViewModel, onRename: () -> Unit, onDelete: () -> Unit) { + val meta = state.config.obj("meta") + Column( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Text("Plan und Entwurf", style = MaterialTheme.typography.headlineSmall) + OutlinedTextField(meta.string("title"), { vm.updateMeta("title", it) }, Modifier.fillMaxWidth(), label = { Text("Titel") }) + OutlinedTextField(meta.string("subtitle"), { vm.updateMeta("subtitle", it) }, Modifier.fillMaxWidth(), label = { Text("Untertitel") }, minLines = 2) + OutlinedTextField( + value = meta.int("weeks", 1).toString(), + onValueChange = { it.toIntOrNull()?.let(vm::updateWeeks) }, + modifier = Modifier.fillMaxWidth(), + label = { Text("Wochen") }, + singleLine = true, + ) + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text("Stabile Identität", fontWeight = FontWeight.Bold) + Text("Plan-ID: ${state.wrapper.string("plan_id", state.selectedPlanId)}") + Text("Datei-ID: ${state.selectedPlanId}") + Text("Vorhandene Sessions: ${state.wrapper.int("tracked_sessions")}") + } + } + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Button(onClick = vm::save, enabled = !state.saving && state.dirty, modifier = Modifier.weight(1f)) { Text("Entwurf speichern") } + OutlinedButton(onClick = vm::validate, enabled = !state.saving, modifier = Modifier.weight(1f)) { Text("Prüfen") } + } + Button(onClick = vm::publish, enabled = !state.saving && !state.dirty, modifier = Modifier.fillMaxWidth()) { Text("Entwurf veröffentlichen") } + OutlinedButton(onClick = onRename, modifier = Modifier.fillMaxWidth()) { Text("Plan umbenennen") } + TextButton(onClick = onDelete, modifier = Modifier.fillMaxWidth()) { Text("Plan löschen", color = MaterialTheme.colorScheme.error) } + Spacer(Modifier.height(24.dp)) + } +} + +@Composable +private fun DaysPane(state: EditorUiState, vm: EditorViewModel) { + val target = state.proposalTargetId + val prepost = state.config.obj("prepost") + Column( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp).testTag("editor-days"), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Text("Trainingstage", style = MaterialTheme.typography.headlineSmall) + state.config.array("days").mapNotNull { it as? JsonObject }.forEach { day -> + DayEditor(day, prepost.obj(day.int("num").toString()), vm, highlightExerciseId = target) + } + OutlinedButton(onClick = vm::addDay, modifier = Modifier.fillMaxWidth()) { Icon(Icons.Default.Add, null); Text(" Tag hinzufügen") } + Spacer(Modifier.height(80.dp)) + } +} + +@Composable +private fun DayEditor(day: JsonObject, prepost: JsonObject, vm: EditorViewModel, highlightExerciseId: String) { + val dayId = day.string("id") + OutlinedCard(Modifier.fillMaxWidth()) { + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text("Tag ${day.int("num", 1)}", style = MaterialTheme.typography.titleLarge) + IconButton(onClick = { vm.removeDay(dayId) }) { Icon(Icons.Default.Delete, "Tag löschen") } + } + OutlinedTextField(day.string("focus"), { value -> vm.updateDay(dayId) { it.updatedString("focus", value) } }, Modifier.fillMaxWidth(), label = { Text("Fokus") }) + Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { + Checkbox(day.bool("light"), { value -> vm.updateDay(dayId) { it.updatedBool("light", value) } }) + Text("Leichter Trainingstag") + } + OutlinedTextField( + prepost.string("warmup"), + { vm.updatePrePost(day.int("num"), "warmup", it) }, + Modifier.fillMaxWidth(), + label = { Text("Warm-up") }, + minLines = 2, + ) + OutlinedTextField( + prepost.string("cooldown"), + { vm.updatePrePost(day.int("num"), "cooldown", it) }, + Modifier.fillMaxWidth(), + label = { Text("Cool-down") }, + minLines = 2, + ) + OutlinedTextField( + prepost.string("stretch"), + { vm.updatePrePost(day.int("num"), "stretch", it) }, + Modifier.fillMaxWidth(), + label = { Text("Stretching") }, + minLines = 2, + ) + day.array("rotations").mapNotNull { it as? JsonObject }.forEach { rotation -> + RotationEditor(dayId, rotation, vm, highlightExerciseId) + } + OutlinedButton(onClick = { vm.addRotation(dayId) }, modifier = Modifier.fillMaxWidth()) { Text("Block hinzufügen") } + } + } +} + +@Composable +private fun RotationEditor(dayId: String, rotation: JsonObject, vm: EditorViewModel, highlightExerciseId: String) { + val rotationId = rotation.string("id") + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp)) { + OutlinedTextField( + rotation.string("label"), + { value -> vm.updateRotation(dayId, rotationId) { it.updatedString("label", value) } }, + Modifier.weight(1f), + label = { Text("Block") }, + ) + IconButton(onClick = { vm.removeRotation(dayId, rotationId) }) { Icon(Icons.Default.Delete, "Block löschen") } + } + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> + ExerciseEditor(dayId, rotationId, exercise, vm, highlightExerciseId == exercise.string("exercise_id")) + } + OutlinedButton(onClick = { vm.addExercise(dayId, rotationId) }, modifier = Modifier.fillMaxWidth()) { Text("Übung hinzufügen") } + } + } +} + +@Composable +private fun ExerciseEditor(dayId: String, rotationId: String, exercise: JsonObject, vm: EditorViewModel, highlighted: Boolean) { + val placementId = exercise.string("id") + OutlinedCard(Modifier.fillMaxWidth().testTag("exercise-$placementId")) { + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + if (highlighted) AssistChip(onClick = {}, label = { Text("Ziel des ausgewählten Vorschlags") }) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text(exercise.string("name", "Übung"), style = MaterialTheme.typography.titleMedium) + IconButton(onClick = { vm.removeExercise(dayId, rotationId, placementId) }) { Icon(Icons.Default.Delete, "Übung löschen") } + } + OutlinedTextField(exercise.string("name"), { value -> vm.updateExercise(dayId, rotationId, placementId) { it.updatedString("name", value) } }, Modifier.fillMaxWidth(), label = { Text("Name") }) + OutlinedTextField(exercise.string("cue"), { value -> vm.updateExercise(dayId, rotationId, placementId) { it.updatedString("cue", value) } }, Modifier.fillMaxWidth(), label = { Text("Hinweis") }, minLines = 2) + OutlinedTextField(exercise.string("exercise_id"), {}, Modifier.fillMaxWidth(), readOnly = true, label = { Text("Stabile Bewegungs-ID") }) + OutlinedTextField(exercise.string("progression_id"), { value -> vm.updateExercise(dayId, rotationId, placementId) { it.updatedString("progression_id", value) } }, Modifier.fillMaxWidth(), label = { Text("Progressions-ID") }) + ResultSchemaEditor( + schema = exercise.obj("result_schema"), + onField = { field, value -> vm.updateExerciseSchema(dayId, rotationId, placementId, field, value) }, + ) + } + } +} + +@Composable +private fun ProgressionsPane(state: EditorUiState, vm: EditorViewModel) { + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Text("Progressionsstufen", style = MaterialTheme.typography.headlineSmall) + state.config.obj("stages").forEach { (key, element) -> + val stage = element as? JsonObject ?: return@forEach + OutlinedCard(Modifier.fillMaxWidth()) { + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + Text(stage.string("name", key), style = MaterialTheme.typography.titleLarge) + Text("ID: ${stage.string("id", key)} · Schlüssel: $key", style = MaterialTheme.typography.bodySmall) + stage.array("steps").mapNotNull { it as? JsonObject }.forEach { step -> + val stepId = step.string("id") + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(step.string("name"), { value -> vm.updateProgressionStep(key, stepId) { it.updatedString("name", value) } }, Modifier.fillMaxWidth(), label = { Text("Stufe") }) + OutlinedTextField(step.string("phase_id"), { value -> vm.updateProgressionStep(key, stepId) { it.updatedString("phase_id", value) } }, Modifier.fillMaxWidth(), label = { Text("Phasen-ID") }) + ResultSchemaEditor(step.obj("result_schema")) { field, value -> vm.updateStepSchema(key, stepId, field, value) } + } + } + } + OutlinedButton(onClick = { vm.addProgressionStep(key) }, modifier = Modifier.fillMaxWidth()) { Text("Stufe hinzufügen") } + } + } + } + Spacer(Modifier.height(80.dp)) + } +} + +@Composable +private fun ResultSchemaEditor(schema: JsonObject, onField: (String, JsonPrimitive) -> Unit) { + Column(verticalArrangement = Arrangement.spacedBy(7.dp)) { + Text("Ergebnisschema", fontWeight = FontWeight.Bold) + ChoiceRow("Modus", listOf("reps", "seconds", "minutes", "none"), schema.string("mode", "reps")) { onField("mode", JsonPrimitive(it)) } + ChoiceRow("Gewicht", listOf("none", "optional", "required"), schema.string("weight_mode", "none")) { onField("weight_mode", JsonPrimitive(it)) } + ChoiceRow("Seiten", listOf("bilateral", "unilateral"), schema.string("laterality", "bilateral")) { onField("laterality", JsonPrimitive(it)) } + if (schema.string("laterality") == "unilateral") { + ChoiceRow("Seitenwerte", listOf("same", "separate"), schema.string("sides_mode", "same")) { onField("sides_mode", JsonPrimitive(it)) } + } + OutlinedTextField( + schema.int("sets", 3).toString(), + { it.toIntOrNull()?.coerceIn(1, 20)?.let { count -> onField("sets", JsonPrimitive(count)) } }, + Modifier.fillMaxWidth(), + label = { Text("Standardsätze") }, + singleLine = true, + ) + Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { + Checkbox(schema.bool("locked_sets"), { onField("locked_sets", JsonPrimitive(it)) }) + Text("Satzanzahl sperren") + } + } +} + +@Composable +private fun ChoiceRow(label: String, choices: List, selected: String, onSelect: (String) -> Unit) { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + Text(label, style = MaterialTheme.typography.labelMedium) + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + choices.forEach { value -> + FilterChip( + selected = selected == value, + onClick = { onSelect(value) }, + label = { Text(value) }, + modifier = Modifier.fillMaxWidth(), + ) + } + } + } +} + +@Composable +private fun LibraryPane(state: EditorUiState) { + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + Text("Übungsbibliothek", style = MaterialTheme.typography.headlineSmall) + Text("Die Bibliothek wird beim Speichern serverseitig aus stabilen Bewegungs- und Progressions-IDs neu aufgebaut.") + val catalog = state.config.obj("exercise_catalog") + if (catalog.isEmpty()) Text("Noch kein Katalog im Entwurf. Speichern oder validieren erzeugt ihn.") + catalog.forEach { (id, element) -> + val entry = element as? JsonObject ?: return@forEach + OutlinedCard(Modifier.fillMaxWidth()) { + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { + Text(entry.string("movement_label", entry.string("name", id)), fontWeight = FontWeight.Bold) + Text(id, style = MaterialTheme.typography.bodySmall) + Text("Cluster: ${entry.string("movement_cluster", "general")}") + Text("Varianten: ${entry.array("variants").size}") + } + } + } + Spacer(Modifier.height(80.dp)) + } +} + +@Composable +private fun ValidationPane(state: EditorUiState, vm: EditorViewModel) { + val validation = state.validation + val errors = validation.array("errors") + val warnings = validation.array("warnings") + val diff = validation.obj("diff") + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Text("Validierung und Vorschau", style = MaterialTheme.typography.headlineSmall) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Button(onClick = vm::validate, enabled = !state.saving, modifier = Modifier.weight(1f)) { Text("Validieren") } + OutlinedButton(onClick = vm::save, enabled = !state.saving && state.dirty, modifier = Modifier.weight(1f)) { Text("Speichern") } + } + if (validation.isEmpty()) Text("Noch keine Prüfung für den aktuellen Entwurf ausgeführt.") + errors.forEach { Text("Fehler: ${it.toString().trim('"')}", color = MaterialTheme.colorScheme.error) } + warnings.forEach { Text("Warnung: ${it.toString().trim('"')}") } + if (validation.isNotEmpty() && errors.isEmpty()) AssistChip(onClick = {}, label = { Text("Keine Validierungsfehler") }) + if (diff.isNotEmpty()) { + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + Text("Änderungsübersicht", style = MaterialTheme.typography.titleMedium) + Text("Bestehende Sessions: ${diff.int("tracked_sessions")}") + listOf("days" to "Tage", "rotations" to "Blöcke", "exercises" to "Übungen", "progression_steps" to "Progressionsstufen").forEach { (key, label) -> + val section = diff.obj(key) + Text("$label: +${section.array("added").size} / −${section.array("removed").size} / ~${section.array("changed").size}") + } + } + } + } + TrackerPreview(state.config) + Button(onClick = vm::publish, enabled = !state.saving && !state.dirty && errors.isEmpty(), modifier = Modifier.fillMaxWidth()) { Text("Geprüften Entwurf veröffentlichen") } + Spacer(Modifier.height(80.dp)) + } +} + +@Composable +private fun TrackerPreview(config: JsonObject) { + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text("Tracker-Vorschau", style = MaterialTheme.typography.titleMedium) + config.array("days").mapNotNull { it as? JsonObject }.forEach { day -> + Text("Tag ${day.int("num")}: ${day.string("focus")}", fontWeight = FontWeight.Bold) + day.array("rotations").mapNotNull { it as? JsonObject }.forEach { rotation -> + Text(rotation.string("label")) + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> Text("• ${exercise.string("name")}") } + } + } + } + } +} + +@Composable +private fun ProposalsPane(state: EditorUiState, vm: EditorViewModel, showDays: () -> Unit) { + val proposals = state.proposals.array("proposals").mapNotNull { it as? JsonObject } + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Text("KI-Vorschläge", style = MaterialTheme.typography.headlineSmall) + Text("Vorschläge verändern den Plan niemals automatisch. Annahme und Ablehnung markieren ausschließlich den Prüfstatus.") + if (proposals.isEmpty()) Text("Keine Vorschläge vorhanden.") + proposals.forEach { proposal -> + OutlinedCard(Modifier.fillMaxWidth()) { + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text(proposal.string("target_label", proposal.string("target")), style = MaterialTheme.typography.titleMedium, modifier = Modifier.weight(1f)) + AssistChip(onClick = {}, label = { Text(proposal.string("status", "open")) }) + } + Text(proposal.string("suggested_change"), fontWeight = FontWeight.Bold) + Text(proposal.string("reason")) + if (proposal.string("condition").isNotBlank()) Text("Bedingung: ${proposal.string("condition")}") + if (proposal.string("manual_step").isNotBlank()) Text("Manueller Schritt: ${proposal.string("manual_step")}") + OutlinedButton( + onClick = { + vm.navigateToProposal(proposal.string("target_exercise_id")) + showDays() + }, + modifier = Modifier.fillMaxWidth(), + ) { Text("Zum Ziel navigieren") } + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Button(onClick = { vm.setProposalStatus(proposal.string("id"), "accepted") }, modifier = Modifier.weight(1f)) { Text("Annehmen") } + OutlinedButton(onClick = { vm.setProposalStatus(proposal.string("id"), "rejected") }, modifier = Modifier.weight(1f)) { Text("Ablehnen") } + } + } + } + } + Spacer(Modifier.height(80.dp)) + } +} + +@Composable +private fun NameDialog( + title: String, + initial: String, + allowClone: Boolean, + onDismiss: () -> Unit, + onConfirm: (String, Boolean) -> Unit, +) { + var name by remember(initial) { mutableStateOf(initial) } + var clone by remember { mutableStateOf(false) } + AlertDialog( + onDismissRequest = onDismiss, + title = { Text(title) }, + text = { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text("Name") }) + if (allowClone) Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { + Checkbox(clone, { clone = it }) + Text("Aktuellen Plan als Vorlage verwenden") + } + } + }, + confirmButton = { TextButton(onClick = { onConfirm(name, clone) }, enabled = name.isNotBlank()) { Text("Übernehmen") } }, + dismissButton = { TextButton(onClick = onDismiss) { Text("Abbrechen") } }, + ) +} diff --git a/android/app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt b/android/app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt new file mode 100644 index 0000000..36342f2 --- /dev/null +++ b/android/app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt @@ -0,0 +1,429 @@ +package de.drazz.boehmitools.training.ui.editor + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import de.drazz.boehmitools.training.data.repository.RevisionConflictException +import de.drazz.boehmitools.training.data.repository.TrainingRepository +import de.drazz.boehmitools.training.data.settings.SettingsRepository +import de.drazz.boehmitools.training.domain.array +import de.drazz.boehmitools.training.domain.int +import de.drazz.boehmitools.training.domain.obj +import de.drazz.boehmitools.training.domain.string +import de.drazz.boehmitools.training.domain.updated +import de.drazz.boehmitools.training.domain.updatedBool +import de.drazz.boehmitools.training.domain.updatedInt +import de.drazz.boehmitools.training.domain.updatedString +import java.util.UUID +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.put + +data class EditorPlanSummary( + val id: String, + val name: String, + val revision: Int, + val publishedRevision: Int, + val active: Boolean, + val hasUnpublishedChanges: Boolean, +) + +data class EditorUiState( + val loading: Boolean = true, + val saving: Boolean = false, + val plans: List = emptyList(), + val selectedPlanId: String = "", + val wrapper: JsonObject = JsonObject(emptyMap()), + val config: JsonObject = JsonObject(emptyMap()), + val validation: JsonObject = JsonObject(emptyMap()), + val proposals: JsonObject = JsonObject(emptyMap()), + val dirty: Boolean = false, + val message: String = "", + val error: String = "", + val conflict: RevisionConflictException? = null, + val proposalTargetId: String = "", +) + +@HiltViewModel +class EditorViewModel @Inject constructor( + private val repository: TrainingRepository, + private val settings: SettingsRepository, +) : ViewModel() { + private val _state = MutableStateFlow(EditorUiState()) + val state: StateFlow = _state.asStateFlow() + + init { loadPlans() } + + fun loadPlans(force: Boolean = false) { + viewModelScope.launch { + _state.value = _state.value.copy(loading = true, error = "", message = "") + runCatching { repository.editorPlans(force) } + .onSuccess { body -> + val plans = body.array("plans").mapNotNull { it as? JsonObject }.map { + EditorPlanSummary( + id = it.string("id"), + name = it.string("name", it.string("id")), + revision = it.int("revision", 1), + publishedRevision = it.int("published_revision", 1), + active = it["active"]?.toString() == "true", + hasUnpublishedChanges = it["has_unpublished_changes"]?.toString() == "true", + ) + } + val stored = settings.selectedEditorPlan() + val selected = _state.value.selectedPlanId.takeIf { id -> plans.any { it.id == id } } + ?: stored?.takeIf { id -> plans.any { it.id == id } } + ?: body.string("active").takeIf { id -> plans.any { it.id == id } } + ?: plans.firstOrNull()?.id.orEmpty() + _state.value = _state.value.copy(loading = false, plans = plans) + if (selected.isNotBlank()) selectPlan(selected, persistServer = false) + } + .onFailure { error -> + _state.value = _state.value.copy(loading = false, error = error.message ?: "Planliste konnte nicht geladen werden.") + } + } + } + + fun selectPlan(planId: String, persistServer: Boolean = true) { + viewModelScope.launch { + _state.value = _state.value.copy(loading = true, selectedPlanId = planId, error = "", conflict = null) + runCatching { + if (persistServer) repository.selectEditorPlan(planId) + settings.setSelectedEditorPlan(planId) + val wrapper = repository.editorPlan(planId, force = true) + val proposals = repository.proposals(planId) + wrapper to proposals + }.onSuccess { (wrapper, proposals) -> + _state.value = _state.value.copy( + loading = false, + wrapper = wrapper, + config = wrapper.obj("config"), + proposals = proposals, + validation = JsonObject(emptyMap()), + dirty = false, + message = "", + ) + }.onFailure { error -> + _state.value = _state.value.copy(loading = false, error = error.message ?: "Plan konnte nicht geladen werden.") + } + } + } + + fun createPlan(name: String, cloneCurrent: Boolean) { + if (name.isBlank()) return + viewModelScope.launch { + _state.value = _state.value.copy(saving = true, error = "") + runCatching { repository.createPlan(name.trim(), _state.value.selectedPlanId.takeIf { cloneCurrent }) } + .onSuccess { created -> + _state.value = _state.value.copy( + saving = false, + selectedPlanId = created.string("id"), + wrapper = created, + config = created.obj("config"), + message = "Plan angelegt.", + ) + loadPlans(force = true) + } + .onFailure { error -> _state.value = _state.value.copy(saving = false, error = error.message ?: "Plan konnte nicht angelegt werden.") } + } + } + + fun renamePlan(name: String) { + val id = _state.value.selectedPlanId + if (id.isBlank() || name.isBlank()) return + viewModelScope.launch { + runCatching { repository.renamePlan(id, name.trim()) } + .onSuccess { + _state.value = _state.value.copy(wrapper = _state.value.wrapper.updatedString("name", name.trim()), message = "Name geändert.") + loadPlans(force = true) + } + .onFailure { error -> _state.value = _state.value.copy(error = error.message ?: "Umbenennen fehlgeschlagen.") } + } + } + + fun deletePlan() { + val id = _state.value.selectedPlanId + if (id.isBlank()) return + viewModelScope.launch { + runCatching { repository.deletePlan(id) } + .onSuccess { + _state.value = EditorUiState(message = "Plan gelöscht.") + loadPlans(force = true) + } + .onFailure { error -> _state.value = _state.value.copy(error = error.message ?: "Löschen fehlgeschlagen.") } + } + } + + fun updateMeta(field: String, value: String) { + val meta = _state.value.config.obj("meta").updatedString(field, value) + mutateConfig(_state.value.config.updated("meta", meta)) + } + + fun updateWeeks(value: Int) { + val meta = _state.value.config.obj("meta").updatedInt("weeks", value.coerceAtLeast(1)) + mutateConfig(_state.value.config.updated("meta", meta)) + } + + fun updatePrePost(dayNumber: Int, field: String, value: String) { + val config = _state.value.config + val prepost = config.obj("prepost").toMutableMap() + val dayKey = dayNumber.toString() + val entry = (prepost[dayKey] as? JsonObject ?: JsonObject(emptyMap())).updatedString(field, value) + prepost[dayKey] = entry + mutateConfig(config.updated("prepost", JsonObject(prepost))) + } + + fun updateDay(dayId: String, transform: (JsonObject) -> JsonObject) { + val days = _state.value.config.array("days").map { element -> + val day = element as? JsonObject ?: return@map element + if (day.string("id") == dayId) transform(day) else day + } + mutateConfig(_state.value.config.updated("days", JsonArray(days))) + } + + fun addDay() { + val config = _state.value.config + val days = config.array("days").toMutableList() + val number = (days.mapNotNull { (it as? JsonObject)?.int("num") }.maxOrNull() ?: 0) + 1 + days += buildJsonObject { + put("id", stableId("day")) + put("num", number) + put("focus", "Neuer Trainingstag") + put("light", false) + put("rotations", JsonArray(emptyList())) + } + val prepost = config.obj("prepost").toMutableMap() + prepost[number.toString()] = buildJsonObject { + put("warmup", "") + put("cooldown", "") + put("stretch", "") + } + mutateConfig(config.updated("days", JsonArray(days)).updated("prepost", JsonObject(prepost))) + } + + fun removeDay(dayId: String) { + val config = _state.value.config + val removed = config.array("days").mapNotNull { it as? JsonObject }.firstOrNull { it.string("id") == dayId } + val days = config.array("days").filterNot { (it as? JsonObject)?.string("id") == dayId } + val prepost = config.obj("prepost").toMutableMap() + removed?.let { prepost.remove(it.int("num").toString()) } + mutateConfig(config.updated("days", JsonArray(days)).updated("prepost", JsonObject(prepost))) + } + + fun addRotation(dayId: String) { + updateDay(dayId) { day -> + val rotations = day.array("rotations").toMutableList() + rotations += buildJsonObject { + put("id", stableId("rotation")) + put("label", "Neuer Block") + put("exercises", JsonArray(emptyList())) + } + day.updated("rotations", JsonArray(rotations)) + } + } + + fun updateRotation(dayId: String, rotationId: String, transform: (JsonObject) -> JsonObject) { + updateDay(dayId) { day -> + val rotations = day.array("rotations").map { element -> + val rotation = element as? JsonObject ?: return@map element + if (rotation.string("id") == rotationId) transform(rotation) else rotation + } + day.updated("rotations", JsonArray(rotations)) + } + } + + fun removeRotation(dayId: String, rotationId: String) { + updateDay(dayId) { day -> + day.updated("rotations", JsonArray(day.array("rotations").filterNot { (it as? JsonObject)?.string("id") == rotationId })) + } + } + + fun addExercise(dayId: String, rotationId: String) { + updateRotation(dayId, rotationId) { rotation -> + val exercises = rotation.array("exercises").toMutableList() + val movementId = stableId("movement") + exercises += buildJsonObject { + put("id", stableId("exercise-placement")) + put("legacy_id", "") + put("exercise_id", movementId) + put("progression_id", "") + put("name", "Neue Übung") + put("cue", "") + put("movement_cluster", "general") + put("result_schema", defaultSchema()) + } + rotation.updated("exercises", JsonArray(exercises)) + } + } + + fun updateExercise(dayId: String, rotationId: String, exerciseId: String, transform: (JsonObject) -> JsonObject) { + updateRotation(dayId, rotationId) { rotation -> + val exercises = rotation.array("exercises").map { element -> + val exercise = element as? JsonObject ?: return@map element + if (exercise.string("id") == exerciseId) transform(exercise) else exercise + } + rotation.updated("exercises", JsonArray(exercises)) + } + } + + fun removeExercise(dayId: String, rotationId: String, exerciseId: String) { + updateRotation(dayId, rotationId) { rotation -> + rotation.updated("exercises", JsonArray(rotation.array("exercises").filterNot { (it as? JsonObject)?.string("id") == exerciseId })) + } + } + + fun updateExerciseSchema(dayId: String, rotationId: String, exerciseId: String, field: String, value: JsonElement) { + updateExercise(dayId, rotationId, exerciseId) { exercise -> + val schema = (exercise["result_schema"] as? JsonObject ?: defaultSchema()).updated(field, value) + exercise.updated("result_schema", schema) + } + } + + fun updateProgressionStep(progressionKey: String, stepId: String, transform: (JsonObject) -> JsonObject) { + val stages = _state.value.config.obj("stages") + val stage = stages[progressionKey] as? JsonObject ?: return + val steps = stage.array("steps").map { element -> + val step = element as? JsonObject ?: return@map element + if (step.string("id") == stepId) transform(step) else step + } + val nextStages = stages.toMutableMap().apply { put(progressionKey, stage.updated("steps", JsonArray(steps))) } + mutateConfig(_state.value.config.updated("stages", JsonObject(nextStages))) + } + + fun addProgressionStep(progressionKey: String) { + val stages = _state.value.config.obj("stages") + val stage = stages[progressionKey] as? JsonObject ?: return + val steps = stage.array("steps").toMutableList() + steps += buildJsonObject { + put("id", stableId("step")) + put("name", "Neue Stufe") + put("phase_id", "") + put("factor", 1.0) + put("movement_cluster", "general") + put("result_schema", defaultSchema()) + } + val nextStages = stages.toMutableMap().apply { put(progressionKey, stage.updated("steps", JsonArray(steps))) } + mutateConfig(_state.value.config.updated("stages", JsonObject(nextStages))) + } + + fun updateStepSchema(progressionKey: String, stepId: String, field: String, value: JsonElement) { + updateProgressionStep(progressionKey, stepId) { step -> + val schema = (step["result_schema"] as? JsonObject ?: defaultSchema()).updated(field, value) + step.updated("result_schema", schema) + } + } + + fun save() { + val s = _state.value + if (s.selectedPlanId.isBlank()) return + viewModelScope.launch { + _state.value = s.copy(saving = true, error = "", conflict = null) + runCatching { repository.savePlan(s.selectedPlanId, s.wrapper.int("revision", 1), s.config) } + .onSuccess { body -> + _state.value = _state.value.copy( + saving = false, + wrapper = body, + config = body.obj("config"), + validation = body.obj("validation"), + dirty = false, + message = "Entwurf gespeichert.", + ) + loadPlans(force = true) + } + .onFailure(::handleFailure) + } + } + + fun validate() { + val s = _state.value + if (s.selectedPlanId.isBlank()) return + viewModelScope.launch { + _state.value = s.copy(saving = true, error = "") + runCatching { repository.validatePlan(s.selectedPlanId, s.config) } + .onSuccess { validation -> + _state.value = _state.value.copy( + saving = false, + validation = validation, + config = validation.obj("config").takeIf { it.isNotEmpty() } ?: s.config, + message = if (validation.array("errors").isEmpty()) "Validierung erfolgreich." else "Validierung enthält Fehler.", + ) + } + .onFailure(::handleFailure) + } + } + + fun publish() { + val s = _state.value + if (s.selectedPlanId.isBlank()) return + viewModelScope.launch { + _state.value = s.copy(saving = true, error = "", conflict = null) + runCatching { repository.publishPlan(s.selectedPlanId, s.wrapper.int("revision", 1)) } + .onSuccess { body -> + _state.value = _state.value.copy( + saving = false, + wrapper = body, + config = body.obj("config"), + validation = body.obj("validation"), + dirty = false, + message = "Entwurf veröffentlicht.", + ) + loadPlans(force = true) + } + .onFailure(::handleFailure) + } + } + + fun setProposalStatus(proposalId: String, status: String) { + val planId = _state.value.selectedPlanId + if (planId.isBlank()) return + viewModelScope.launch { + runCatching { repository.setProposalStatus(planId, proposalId, status) } + .onSuccess { body -> + val proposals = _state.value.proposals.updated("proposals", body["proposals"] ?: JsonArray(emptyList())) + _state.value = _state.value.copy(proposals = proposals, message = "Vorschlag als $status markiert.") + } + .onFailure { error -> _state.value = _state.value.copy(error = error.message ?: "Vorschlag konnte nicht aktualisiert werden.") } + } + } + + fun navigateToProposal(targetExerciseId: String) { + _state.value = _state.value.copy(proposalTargetId = targetExerciseId) + } + + fun clearProposalTarget() { _state.value = _state.value.copy(proposalTargetId = "") } + fun dismissConflict() { _state.value = _state.value.copy(conflict = null) } + fun reloadAfterConflict() { selectPlan(_state.value.selectedPlanId) } + + private fun mutateConfig(config: JsonObject) { + _state.value = _state.value.copy(config = config, dirty = true, message = "Ungespeicherte Änderungen", error = "") + } + + private fun handleFailure(error: Throwable) { + _state.value = if (error is RevisionConflictException) { + _state.value.copy(saving = false, conflict = error, error = "") + } else { + _state.value.copy(saving = false, error = error.message ?: "Aktion fehlgeschlagen.") + } + } + + private fun stableId(prefix: String): String = "$prefix-${UUID.randomUUID().toString().replace("-", "").take(12)}" + + private fun defaultSchema(): JsonObject = buildJsonObject { + put("mode", "reps") + put("weight_mode", "none") + put("laterality", "bilateral") + put("sides_mode", "same") + put("sets", 3) + put("locked_sets", false) + } +} diff --git a/android/app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt b/android/app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt new file mode 100644 index 0000000..879a58e --- /dev/null +++ b/android/app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt @@ -0,0 +1,105 @@ +package de.drazz.boehmitools.training.ui.setup + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CloudDone +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.FilterChip +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import de.drazz.boehmitools.training.data.settings.SettingsRepository + +@Composable +fun SettingsScreen( + contentPadding: PaddingValues, + current: SettingsRepository.PublicSettings, + onTheme: (String) -> Unit, + viewModel: SettingsViewModel = hiltViewModel(), +) { + val state by viewModel.state.collectAsStateWithLifecycle() + Column( + modifier = Modifier + .fillMaxSize() + .padding(contentPadding) + .verticalScroll(rememberScrollState()) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Text("Einstellungen", style = MaterialTheme.typography.headlineMedium) + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Text("Server & Anmeldung", style = MaterialTheme.typography.titleLarge) + Text( + "Basic-Auth-Zugangsdaten werden mit einem Android-Keystore-Schlüssel verschlüsselt. Release-Builds akzeptieren ausschließlich HTTPS.", + style = MaterialTheme.typography.bodyMedium, + ) + OutlinedTextField( + value = state.serverUrl, + onValueChange = viewModel::updateServer, + modifier = Modifier.fillMaxWidth(), + label = { Text("Server-URL") }, + singleLine = true, + ) + OutlinedTextField( + value = state.username, + onValueChange = viewModel::updateUsername, + modifier = Modifier.fillMaxWidth(), + label = { Text("Benutzername") }, + singleLine = true, + ) + OutlinedTextField( + value = state.password, + onValueChange = viewModel::updatePassword, + modifier = Modifier.fillMaxWidth(), + label = { Text("Passwort") }, + visualTransformation = PasswordVisualTransformation(), + singleLine = true, + ) + Button(onClick = viewModel::saveAndTest, enabled = !state.loading) { + if (state.loading) CircularProgressIndicator(modifier = Modifier.height(18.dp), strokeWidth = 2.dp) + else Icon(Icons.Default.CloudDone, null) + Spacer(Modifier.padding(horizontal = 4.dp)) + Text("Speichern & Verbindung testen") + } + if (state.message.isNotBlank()) Text(state.message, color = MaterialTheme.colorScheme.primary) + if (state.error.isNotBlank()) Text(state.error, color = MaterialTheme.colorScheme.error) + } + } + + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Text("Darstellung", style = MaterialTheme.typography.titleLarge) + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + listOf("system" to "System", "light" to "Hell", "dark" to "Dunkel").forEach { (value, label) -> + FilterChip( + selected = current.theme == value, + onClick = { onTheme(value) }, + label = { Text(label) }, + ) + } + } + } + } + } +} diff --git a/android/app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt b/android/app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt new file mode 100644 index 0000000..27f612e --- /dev/null +++ b/android/app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt @@ -0,0 +1,63 @@ +package de.drazz.boehmitools.training.ui.setup + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import de.drazz.boehmitools.training.data.repository.TrainingRepository +import de.drazz.boehmitools.training.data.settings.SettingsRepository +import javax.inject.Inject +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +data class SettingsUiState( + val serverUrl: String = SettingsRepository.DEFAULT_SERVER, + val username: String = "admin", + val password: String = "", + val loading: Boolean = false, + val message: String = "", + val error: String = "", +) + +@HiltViewModel +class SettingsViewModel @Inject constructor( + private val settings: SettingsRepository, + private val repository: TrainingRepository, +) : ViewModel() { + private val _state = MutableStateFlow(SettingsUiState()) + val state: StateFlow = _state.asStateFlow() + + init { + viewModelScope.launch { + val snapshot = settings.snapshot() + _state.value = _state.value.copy( + serverUrl = snapshot.serverUrl, + username = snapshot.username, + password = snapshot.password, + ) + } + } + + fun updateServer(value: String) { _state.value = _state.value.copy(serverUrl = value, message = "", error = "") } + fun updateUsername(value: String) { _state.value = _state.value.copy(username = value, message = "", error = "") } + fun updatePassword(value: String) { _state.value = _state.value.copy(password = value, message = "", error = "") } + + fun saveAndTest() { + viewModelScope.launch { + _state.value = _state.value.copy(loading = true, message = "", error = "") + runCatching { + settings.saveConnection(_state.value.serverUrl, _state.value.username, _state.value.password) + repository.testConnection() + }.onSuccess { health -> + val versions = health["versions"]?.toString().orEmpty() + _state.value = _state.value.copy( + loading = false, + message = if (versions.isBlank()) "Verbindung erfolgreich." else "Verbindung erfolgreich · Vertrag erkannt.", + ) + }.onFailure { error -> + _state.value = _state.value.copy(loading = false, error = error.message ?: "Verbindung fehlgeschlagen.") + } + } + } +} diff --git a/android/app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt b/android/app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt new file mode 100644 index 0000000..3c5a184 --- /dev/null +++ b/android/app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt @@ -0,0 +1,47 @@ +package de.drazz.boehmitools.training.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext + +private val Light = lightColorScheme( + primary = Color(0xFF566600), + onPrimary = Color.White, + primaryContainer = Color(0xFFD9F26A), + onPrimaryContainer = Color(0xFF172000), + secondary = Color(0xFF53643C), + tertiary = Color(0xFF00696C), + background = Color(0xFFF9FAF2), + surface = Color(0xFFF9FAF2), + surfaceVariant = Color(0xFFE5E8D8), +) + +private val Dark = darkColorScheme( + primary = Color(0xFFC0D852), + onPrimary = Color(0xFF2B3400), + primaryContainer = Color(0xFF414D00), + onPrimaryContainer = Color(0xFFD9F26A), + secondary = Color(0xFFBACCA2), + tertiary = Color(0xFF80D4D6), + background = Color(0xFF11140E), + surface = Color(0xFF11140E), + surfaceVariant = Color(0xFF41443A), +) + +@Composable +fun TrainingTheme(theme: String, content: @Composable () -> Unit) { + val dark = when (theme) { + "dark" -> true + "light" -> false + else -> isSystemInDarkTheme() + } + MaterialTheme(colorScheme = if (dark) Dark else Light, content = content) +} diff --git a/android/app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt b/android/app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt new file mode 100644 index 0000000..f16150e --- /dev/null +++ b/android/app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt @@ -0,0 +1,614 @@ +package de.drazz.boehmitools.training.ui.tracker + +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.weight +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.selection.selectable +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.CheckCircle +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material.icons.filled.CloudOff +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.Refresh +import androidx.compose.material.icons.filled.Stop +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.AssistChip +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Divider +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilterChip +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.ScrollableTabRow +import androidx.compose.material3.Tab +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import de.drazz.boehmitools.training.data.local.PendingMutation +import de.drazz.boehmitools.training.domain.DayUi +import de.drazz.boehmitools.training.domain.ExerciseUi +import de.drazz.boehmitools.training.domain.PlanUi +import de.drazz.boehmitools.training.domain.ResultSchemaUi +import de.drazz.boehmitools.training.domain.array +import de.drazz.boehmitools.training.domain.bool +import de.drazz.boehmitools.training.domain.emptyResultData +import de.drazz.boehmitools.training.domain.formatResultData +import de.drazz.boehmitools.training.domain.int +import de.drazz.boehmitools.training.domain.itemFor +import de.drazz.boehmitools.training.domain.normalizedResultData +import de.drazz.boehmitools.training.domain.obj +import de.drazz.boehmitools.training.domain.resolveResultSchema +import de.drazz.boehmitools.training.domain.resultDataWithSets +import de.drazz.boehmitools.training.domain.resultDataWithValue +import de.drazz.boehmitools.training.domain.string +import de.drazz.boehmitools.training.domain.trackerSession +import de.drazz.boehmitools.training.domain.updated +import de.drazz.boehmitools.training.domain.updatedString +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonNull +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.doubleOrNull +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TrackerScreen( + contentPadding: PaddingValues, + viewModel: TrackerViewModel = hiltViewModel(), +) { + val state by viewModel.state.collectAsStateWithLifecycle() + val pending by viewModel.pending.collectAsStateWithLifecycle() + val currentPending = pending.filter { it.planId == state.selectedPlanId } + var planMenu by remember { mutableStateOf(false) } + + Scaffold( + modifier = Modifier.padding(contentPadding), + topBar = { + TopAppBar( + title = { + Column { + Text(state.plan?.title ?: "boehmitools Training", maxLines = 2) + if (state.saveState.isNotBlank()) Text(state.saveState, style = MaterialTheme.typography.labelSmall) + } + }, + actions = { + Box { + IconButton(onClick = { planMenu = true }) { + Icon(Icons.Default.MoreVert, contentDescription = "Plan wechseln") + } + DropdownMenu(expanded = planMenu, onDismissRequest = { planMenu = false }) { + state.plans.forEach { plan -> + DropdownMenuItem( + text = { Text(plan.title) }, + onClick = { planMenu = false; viewModel.selectPlan(plan.id) }, + ) + } + } + } + }, + ) + }, + ) { inner -> + Column(Modifier.fillMaxSize().padding(inner)) { + val tabs = listOf("session" to "Session", "progress" to "Fortschritt", "plan" to "Plan", "faq" to "FAQ") + ScrollableTabRow(selectedTabIndex = tabs.indexOfFirst { it.first == state.tab }.coerceAtLeast(0)) { + tabs.forEach { (id, label) -> + Tab(selected = state.tab == id, onClick = { viewModel.setTab(id) }, text = { Text(label) }) + } + } + when { + state.loading && state.plan == null -> Box(Modifier.fillMaxSize().padding(32.dp)) { CircularProgressIndicator() } + state.error.isNotBlank() && state.plan == null -> ErrorPane(state.error) { viewModel.loadPlans(force = true) } + state.plan == null -> ErrorPane("Kein Trainingsplan verfügbar.") { viewModel.loadPlans(force = true) } + else -> when (state.tab) { + "progress" -> ProgressPane(state, currentPending, viewModel) + "plan" -> PlanPane(state.plan!!) + "faq" -> FaqPane(state) + else -> SessionPane(state, currentPending, viewModel) + } + } + } + } + + state.conflict?.let { conflict -> + AlertDialog( + onDismissRequest = viewModel::dismissConflict, + title = { Text("Revisionskonflikt") }, + text = { Text("Der Server steht bereits auf Revision ${conflict.currentRevision}. Deine lokale Änderung wird nicht automatisch überschrieben. Lade den Serverstand neu und wende die Änderung bewusst erneut an.") }, + confirmButton = { TextButton(onClick = viewModel::reloadAfterConflict) { Text("Serverstand neu laden") } }, + dismissButton = { TextButton(onClick = viewModel::dismissConflict) { Text("Lokale Ansicht behalten") } }, + ) + } +} + +@Composable +private fun ErrorPane(message: String, retry: () -> Unit) { + Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Text(message, color = MaterialTheme.colorScheme.error) + Button(onClick = retry) { Icon(Icons.Default.Refresh, null); Spacer(Modifier.width(8.dp)); Text("Neu laden") } + } +} + +@Composable +private fun SessionPane(state: TrackerUiState, pending: List, vm: TrackerViewModel) { + val plan = state.plan ?: return + val day = plan.days.firstOrNull { it.num == state.day } ?: return + val session = trackerSession(state.tracker, de.drazz.boehmitools.training.domain.sessionKey(state.week, state.day), plan) + Column( + Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + if (state.error.isNotBlank()) Text(state.error, color = MaterialTheme.colorScheme.error) + if (pending.isNotEmpty()) PendingQueue(pending, vm) + WeekDayPicker(plan, state.week, state.day, vm) + SessionHeader(plan, day, session, state.week, vm) + + SequenceCard("Aufwärmen", "warmup", day.warmup, session, vm) + day.rotations.forEach { rotation -> + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Text(rotation.label, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + rotation.exercises.forEach { exercise -> + ExerciseCard(exercise, session, plan, vm) + Divider() + } + } + } + } + SequenceCard("Cooldown", "cooldown", day.cooldown, session, vm) + SequenceCard("Stretch", "stretch", day.stretch, session, vm) + Spacer(Modifier.height(24.dp)) + } +} + +@Composable +private fun PendingQueue(pending: List, vm: TrackerViewModel) { + OutlinedCard(Modifier.fillMaxWidth()) { + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Icon(Icons.Default.CloudOff, null) + Text("${pending.size} ausstehende Änderung(en)", fontWeight = FontWeight.Bold) + } + pending.take(3).forEach { item -> + Text("${item.sessionKey} · Revision ${item.expectedRevision} · ${item.state}", style = MaterialTheme.typography.bodySmall) + if (item.state == PendingMutation.STATE_CONFLICT) { + Text(item.lastError, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall) + TextButton(onClick = { vm.discardQueuedConflict(item.id) }) { Text("Konflikt-Patch verwerfen") } + } + } + } + } +} + +@Composable +private fun WeekDayPicker(plan: PlanUi, week: Int, day: Int, vm: TrackerViewModel) { + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + (1..plan.weeks).forEach { value -> FilterChip(selected = value == week, onClick = { vm.setWeek(value) }, label = { Text("W$value") }) } + } + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + plan.days.forEach { value -> FilterChip(selected = value.num == day, onClick = { vm.setDay(value.num) }, label = { Text("Tag ${value.num}") }) } + } + } +} + +@Composable +private fun SessionHeader(plan: PlanUi, day: DayUi, session: JsonObject, week: Int, vm: TrackerViewModel) { + val status = session.string("status", "planned") + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + Text("Woche $week · Tag ${day.num}", style = MaterialTheme.typography.labelLarge) + Text(day.focus, style = MaterialTheme.typography.headlineSmall) + Text("Planrevision R${session.int("plan_revision", plan.publishedRevision)} · Status: $status", style = MaterialTheme.typography.bodySmall) + when (status) { + "in_progress" -> Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Button(onClick = { vm.setSessionStatus("completed") }) { Icon(Icons.Default.CheckCircle, null); Spacer(Modifier.width(6.dp)); Text("Abschließen") } + OutlinedButton(onClick = { vm.setSessionStatus("stopped") }) { Icon(Icons.Default.Stop, null); Spacer(Modifier.width(6.dp)); Text("Stoppen") } + } + "stopped" -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Fortsetzen") } + "completed" -> OutlinedButton(onClick = { vm.setSessionStatus("in_progress") }) { Text("Wieder öffnen") } + else -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Session starten") } + } + TextButton(onClick = vm::resetSession) { Text("Session zurücksetzen") } + } + } +} + +@Composable +private fun SequenceCard(title: String, prefix: String, items: List, session: JsonObject, vm: TrackerViewModel) { + if (items.isEmpty()) return + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { + Text(title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + items.forEachIndexed { index, label -> + val id = "$prefix-$index" + val item = itemFor(session, id) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + Checkbox(checked = item.string("completion_status") == "completed" || item.bool("done"), onCheckedChange = { vm.setSequenceStatus(id, it) }) + Text(label, modifier = Modifier.padding(top = 12.dp)) + } + } + } + } +} + +@Composable +private fun ExerciseCard(exercise: ExerciseUi, session: JsonObject, plan: PlanUi, vm: TrackerViewModel) { + val item = itemFor(session, exercise.id) + val status = item.string("completion_status", if (item.bool("done")) "completed" else "planned") + val progression = item.string("progression") + val progressionModel = plan.progressions[exercise.progressionId] + val schema = resolveResultSchema(exercise, item, progression, plan) + val data = normalizedResultData(item, schema) + var progressionMenu by remember(exercise.id, progression) { mutableStateOf(false) } + + Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text(exercise.name, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold) + if (exercise.cue.isNotBlank()) Text(exercise.cue, style = MaterialTheme.typography.bodySmall) + Text("ID ${exercise.id}", style = MaterialTheme.typography.labelSmall) + Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { + Text("Status", style = MaterialTheme.typography.labelMedium) + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(6.dp)) { + listOf("planned" to "Offen", "completed" to "Erledigt", "partial" to "Teilweise", "skipped" to "Übersprungen").forEach { (value, label) -> + FilterChip(selected = status == value, onClick = { vm.setItemStatus(exercise.id, value) }, label = { Text(label) }) + } + } + } + if (status == "skipped") { + OutlinedTextField( + value = item.string("skip_reason"), + onValueChange = { vm.setItemText(exercise.id, "skip_reason", it) }, + modifier = Modifier.fillMaxWidth(), + label = { Text("Grund") }, + ) + } else { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + OutlinedTextField( + value = progression, + onValueChange = { vm.setItemProgression(exercise.id, it) }, + modifier = Modifier.fillMaxWidth(), + label = { Text("Progressionsstufe") }, + supportingText = { Text("Stufe überschreibt bei Bedarf das Ergebnisschema") }, + ) + if (progressionModel?.steps?.isNotEmpty() == true) { + Box { + OutlinedButton(onClick = { progressionMenu = true }, modifier = Modifier.fillMaxWidth()) { + Text("Stufe aus Plan auswählen") + } + DropdownMenu(expanded = progressionMenu, onDismissRequest = { progressionMenu = false }) { + progressionModel.steps.forEach { step -> + DropdownMenuItem( + text = { Text(step.name) }, + onClick = { progressionMenu = false; vm.setItemProgression(exercise.id, step.name, step.id) }, + ) + } + } + } + } + } + ManualResultFormatEditor( + inherited = schema, + manualOverride = item["result_format"] as? JsonObject, + onChange = { vm.setItemResultFormat(exercise.id, it) }, + ) + ResultEditor(schema, data) { vm.setItemResultData(exercise.id, it) } + OutlinedTextField( + value = item.string("note"), + onValueChange = { vm.setItemText(exercise.id, "note", it) }, + modifier = Modifier.fillMaxWidth(), + label = { Text("Sessionnotiz") }, + minLines = 2, + ) + } + } +} + + +@Composable +private fun ManualResultFormatEditor( + inherited: ResultSchemaUi, + manualOverride: JsonObject?, + onChange: (JsonObject?) -> Unit, +) { + var expanded by remember(manualOverride) { mutableStateOf(false) } + val mode = manualOverride?.string("mode", inherited.mode) ?: inherited.mode + val weightMode = manualOverride?.string("weight_mode", inherited.weightMode) ?: inherited.weightMode + val laterality = manualOverride?.string("laterality", inherited.laterality) ?: inherited.laterality + val sidesMode = if (laterality == "unilateral") { + manualOverride?.string("sides_mode", inherited.sidesMode) ?: inherited.sidesMode + } else { + "same" + } + + fun changed(field: String, value: String): JsonObject { + val values = linkedMapOf( + "mode" to JsonPrimitive(mode), + "weight_mode" to JsonPrimitive(weightMode), + "laterality" to JsonPrimitive(laterality), + "sides_mode" to JsonPrimitive(sidesMode), + ) + values[field] = JsonPrimitive(value) + if ((values["laterality"] as JsonPrimitive).content == "bilateral") { + values["sides_mode"] = JsonPrimitive("same") + } + return JsonObject(values) + } + + OutlinedCard(Modifier.fillMaxWidth()) { + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Column(Modifier.weight(1f)) { + Text("Manuelle Session-Anpassung", style = MaterialTheme.typography.labelLarge) + Text( + if (manualOverride == null) "Vererbt: ${inherited.source}" else "Überschreibt Progressionsstufe und Übung", + style = MaterialTheme.typography.bodySmall, + ) + } + TextButton(onClick = { expanded = !expanded }) { Text(if (expanded) "Schließen" else "Anpassen") } + } + if (expanded) { + ChoiceChips("Messwert", listOf("reps", "seconds", "minutes", "none"), mode) { onChange(changed("mode", it)) } + ChoiceChips("Gewicht", listOf("none", "optional", "required"), weightMode) { onChange(changed("weight_mode", it)) } + ChoiceChips("Ausführung", listOf("bilateral", "unilateral"), laterality) { onChange(changed("laterality", it)) } + if (laterality == "unilateral") { + ChoiceChips("Seitenwerte", listOf("same", "separate"), sidesMode) { onChange(changed("sides_mode", it)) } + } + if (manualOverride != null) { + OutlinedButton(onClick = { onChange(null) }, modifier = Modifier.fillMaxWidth()) { + Text("Manuelle Anpassung zurücksetzen") + } + } + } + } + } +} + +@Composable +private fun ChoiceChips(label: String, choices: List, selected: String, onSelect: (String) -> Unit) { + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + Text(label, style = MaterialTheme.typography.labelMedium) + choices.forEach { value -> + FilterChip( + selected = selected == value, + onClick = { onSelect(value) }, + label = { Text(value) }, + modifier = Modifier.fillMaxWidth(), + ) + } + } +} + +@Composable +private fun ResultEditor(schema: ResultSchemaUi, data: JsonObject, onChange: (JsonObject) -> Unit) { + OutlinedCard(Modifier.fillMaxWidth()) { + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text("Ergebnis · ${schema.source}", style = MaterialTheme.typography.labelLarge) + Text("${schema.mode}, ${schema.laterality}, Gewicht ${schema.weightMode}", style = MaterialTheme.typography.bodySmall) + if (schema.mode == "none") { + Text("Für diese Stufe ist kein numerischer Messwert vorgesehen.") + return@Column + } + if (schema.weightMode != "none") { + val weight = data["weight_kg"]?.jsonPrimitive?.doubleOrNull?.toString().orEmpty() + OutlinedTextField( + value = weight, + onValueChange = { raw -> onChange(data.updated("weight_kg", raw.replace(',', '.').toDoubleOrNull()?.let(::JsonPrimitive) ?: JsonNull)) }, + modifier = Modifier.fillMaxWidth(), + label = { Text(if (schema.weightMode == "required") "Gewicht kg (Pflicht)" else "Gewicht kg (optional)") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + singleLine = true, + ) + } + if (!schema.lockedSets) { + OutlinedTextField( + value = data.int("sets", schema.defaultSets).toString(), + onValueChange = { raw -> raw.toIntOrNull()?.let { onChange(resultDataWithSets(data, it)) } }, + modifier = Modifier.fillMaxWidth(), + label = { Text("Sätze") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + singleLine = true, + ) + } + val count = data.int("sets", schema.defaultSets).coerceIn(1, 20) + repeat(count) { index -> + if (data.string("laterality", schema.laterality) == "unilateral" && data.string("sides_mode", schema.sidesMode) == "separate") { + OutlinedTextField( + value = data.array("left_values").getOrNull(index)?.jsonPrimitive?.doubleOrNull?.toString().orEmpty(), + onValueChange = { raw -> onChange(resultDataWithValue(data, "left_values", index, raw.replace(',', '.').toDoubleOrNull())) }, + modifier = Modifier.fillMaxWidth(), + label = { Text("Satz ${index + 1} links") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + singleLine = true, + ) + OutlinedTextField( + value = data.array("right_values").getOrNull(index)?.jsonPrimitive?.doubleOrNull?.toString().orEmpty(), + onValueChange = { raw -> onChange(resultDataWithValue(data, "right_values", index, raw.replace(',', '.').toDoubleOrNull())) }, + modifier = Modifier.fillMaxWidth(), + label = { Text("Satz ${index + 1} rechts") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + singleLine = true, + ) + } else { + OutlinedTextField( + value = data.array("values").getOrNull(index)?.jsonPrimitive?.doubleOrNull?.toString().orEmpty(), + onValueChange = { raw -> onChange(resultDataWithValue(data, "values", index, raw.replace(',', '.').toDoubleOrNull())) }, + modifier = Modifier.fillMaxWidth(), + label = { Text("Satz ${index + 1}${if (schema.laterality == "unilateral") " je Seite" else ""}") }, + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + singleLine = true, + ) + } + } + Text("Gespeichert als: ${formatResultData(data)}", style = MaterialTheme.typography.bodySmall) + } + } +} + +@Composable +private fun ProgressPane(state: TrackerUiState, pending: List, vm: TrackerViewModel) { + val plan = state.plan ?: return + val sessions = state.tracker.obj("sessions").values.mapNotNull { it as? JsonObject } + val completed = sessions.count { it.string("status") == "completed" } + val stopped = sessions.count { it.string("status") == "stopped" } + val analysisState = state.response.obj("analysis_state").takeIf { it.isNotEmpty() } + ?: state.tracker.obj("analysis_state") + val running = analysisState.string("status") == "running" + val cache = state.response.obj("analysis_cache").takeIf { it.isNotEmpty() } + ?: state.tracker.obj("analysis_cache") + val weekRecord = cache.obj("weeks")[state.week.toString()] as? JsonObject + val overall = cache["overall"] as? JsonObject + + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Text("Fortschritt", style = MaterialTheme.typography.headlineSmall) + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + Text("Lokale Übersicht", style = MaterialTheme.typography.titleMedium) + Text("$completed abgeschlossene Sessions · $stopped gestoppte Sessions · ${sessions.size} angelegte Sessions") + Text("Trackerrevision ${state.tracker.int("revision", 1)} · Planrevision ${plan.publishedRevision}") + } + } + if (pending.isNotEmpty()) PendingQueue(pending, vm) + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + Text("Wochenstatus", style = MaterialTheme.typography.titleMedium) + val closed = state.tracker.obj("week_statuses").obj(state.week.toString()).string("status") == "closed" + FilterChip(selected = closed, onClick = { vm.setWeekClosed(!closed) }, label = { Text(if (closed) "Woche ${state.week} abgeschlossen" else "Woche ${state.week} läuft") }) + Text("Eine laufende Woche erzeugt eine Zwischenanalyse, eine geschlossene Woche eine Abschlussanalyse.", style = MaterialTheme.typography.bodySmall) + } + } + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + Text("Manuelle Analyse", style = MaterialTheme.typography.titleMedium) + Button(onClick = { vm.startAnalysis("week", state.week) }, enabled = !running) { Text("Woche ${state.week} analysieren") } + OutlinedButton(onClick = { vm.startAnalysis("overall") }, enabled = !running) { Text("Gesamtanalyse") } + if (running) { + CircularProgressIndicator() + Text(analysisState.string("message", "Analysejob läuft …")) + } else if (state.analysisMessage.isNotBlank()) Text(state.analysisMessage) + if (state.error.isNotBlank()) Text(state.error, color = MaterialTheme.colorScheme.error) + } + } + AnalysisCard("Analyse Woche ${state.week}", weekRecord) + AnalysisCard("Gesamtanalyse", overall) + Spacer(Modifier.height(24.dp)) + } +} + +@Composable +private fun AnalysisCard(title: String, record: JsonObject?) { + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text(title, style = MaterialTheme.typography.titleMedium) + if (record == null) { + Text("Noch keine aktuelle Analyse vorhanden.") + } else { + val response = record.obj("response") + Text(response.string("headline", "Analyse"), style = MaterialTheme.typography.titleLarge) + Text(response.string("summary")) + response.array("metrics").mapNotNull { it as? JsonObject }.forEach { metric -> + AssistChip(onClick = {}, label = { Text("${metric.string("label")}: ${metric.string("value")}") }) + } + response.array("exercise_updates").mapNotNull { it as? JsonObject }.take(6).forEach { update -> + OutlinedCard(Modifier.fillMaxWidth()) { + Column(Modifier.padding(10.dp)) { + Text(update.string("name"), fontWeight = FontWeight.Bold) + Text(update.string("next_action")) + if (update.string("criterion").isNotBlank()) Text("Kriterium: ${update.string("criterion")}", style = MaterialTheme.typography.bodySmall) + } + } + } + } + } + } +} + +@Composable +private fun PlanPane(plan: PlanUi) { + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Text(plan.title, style = MaterialTheme.typography.headlineSmall) + Text(plan.subtitle) + Text("${plan.weeks} Wochen · veröffentlichte Revision ${plan.publishedRevision}") + plan.days.forEach { day -> + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text("Tag ${day.num}: ${day.focus}", style = MaterialTheme.typography.titleMedium) + day.rotations.forEach { rotation -> + Text(rotation.label, fontWeight = FontWeight.Bold) + rotation.exercises.forEach { exercise -> Text("• ${exercise.name}") } + } + } + } + } + } +} + +@Composable +private fun FaqPane(state: TrackerUiState) { + val plan = state.plan ?: return + val guide = state.response.obj("equivalence_guide") + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + Text("FAQ & Variantencluster", style = MaterialTheme.typography.headlineSmall) + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + Text("Trainingsformat", style = MaterialTheme.typography.titleMedium) + Text("Modus: ${plan.trainingFormat.string("mode", "sets_reps")}") + if (plan.trainingFormat.bool("fixed_interval")) { + Text("Festes Intervall: ${plan.trainingFormat.int("work_seconds")} s Arbeit / ${plan.trainingFormat.int("rest_seconds")} s Pause · ${plan.trainingFormat.int("rounds")} Intervalle pro Block") + Text("Die App implementiert bewusst keinen Timer und verändert dieses Format nicht.") + } + } + } + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + Text("Planbezogene Übungsbibliothek", style = MaterialTheme.typography.titleMedium) + plan.exerciseCatalog.values.mapNotNull { it as? JsonObject }.take(100).forEach { entry -> + Text("${entry.string("movement_label", entry.string("name"))} · ${entry.array("variants").size} Variante(n)") + } + } + } + if (guide.isNotEmpty()) { + Card(Modifier.fillMaxWidth()) { + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + Text("Fallbackregeln des Servers", style = MaterialTheme.typography.titleMedium) + Text(guide.toString(), style = MaterialTheme.typography.bodySmall) + } + } + } + } +} diff --git a/android/app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt b/android/app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt new file mode 100644 index 0000000..3ac5a44 --- /dev/null +++ b/android/app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt @@ -0,0 +1,433 @@ +package de.drazz.boehmitools.training.ui.tracker + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import de.drazz.boehmitools.training.data.local.PendingMutation +import de.drazz.boehmitools.training.data.repository.ApiException +import de.drazz.boehmitools.training.data.repository.RevisionConflictException +import de.drazz.boehmitools.training.data.repository.SaveOutcome +import de.drazz.boehmitools.training.data.repository.TrainingRepository +import de.drazz.boehmitools.training.data.settings.SettingsRepository +import de.drazz.boehmitools.training.domain.DayUi +import de.drazz.boehmitools.training.domain.adaptResultDataToSchema +import de.drazz.boehmitools.training.domain.PlanUi +import de.drazz.boehmitools.training.domain.blankSession +import de.drazz.boehmitools.training.domain.formatResultData +import de.drazz.boehmitools.training.domain.int +import de.drazz.boehmitools.training.domain.migrateLegacyItems +import de.drazz.boehmitools.training.domain.mergeResultSchemaLayers +import de.drazz.boehmitools.training.domain.obj +import de.drazz.boehmitools.training.domain.overlaySessionPatches +import de.drazz.boehmitools.training.domain.parsePlan +import de.drazz.boehmitools.training.domain.resolveResultSchema +import de.drazz.boehmitools.training.domain.sessionKey +import de.drazz.boehmitools.training.domain.sessionPatch +import de.drazz.boehmitools.training.domain.string +import de.drazz.boehmitools.training.domain.trackerSession +import de.drazz.boehmitools.training.domain.updateItem +import de.drazz.boehmitools.training.domain.updateTrackerSession +import de.drazz.boehmitools.training.domain.updated +import de.drazz.boehmitools.training.domain.updatedBool +import de.drazz.boehmitools.training.domain.updatedInt +import de.drazz.boehmitools.training.domain.updatedString +import java.time.Instant +import javax.inject.Inject +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive +import kotlinx.serialization.json.put + +data class TrackerPlanSummary( + val id: String, + val title: String, + val subtitle: String, + val weeks: Int, + val completedSessions: Int, + val sourceChanged: Boolean, +) + +data class TrackerUiState( + val loading: Boolean = true, + val error: String = "", + val plans: List = emptyList(), + val selectedPlanId: String = "", + val response: JsonObject = JsonObject(emptyMap()), + val plan: PlanUi? = null, + val tracker: JsonObject = JsonObject(emptyMap()), + val tab: String = "session", + val week: Int = 1, + val day: Int = 1, + val saveState: String = "", + val conflict: RevisionConflictException? = null, + val analysisMessage: String = "", +) + +@HiltViewModel +class TrackerViewModel @Inject constructor( + private val repository: TrainingRepository, + private val settings: SettingsRepository, +) : ViewModel() { + private val _state = MutableStateFlow(TrackerUiState()) + val state: StateFlow = _state.asStateFlow() + val pending = repository.pendingMutations().stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + + private var saveJob: Job? = null + private var pollJob: Job? = null + private val saveMutex = Mutex() + private val wireJson = Json { ignoreUnknownKeys = true; explicitNulls = false } + + init { loadPlans() } + + fun loadPlans(force: Boolean = false) { + viewModelScope.launch { + _state.value = _state.value.copy(loading = true, error = "") + runCatching { repository.trackerPlans(force) } + .onSuccess { payload -> + val plans = payload["plans"]?.jsonArray?.mapNotNull { it as? JsonObject }?.map { item -> + TrackerPlanSummary( + id = item.string("id"), + title = item.string("title", item.string("name")), + subtitle = item.string("subtitle"), + weeks = item.int("weeks", 1), + completedSessions = item.int("completed_sessions"), + sourceChanged = item["source_changed"]?.jsonPrimitive?.content == "true", + ) + }.orEmpty() + val preferred = settings.selectedTrackerPlan() + val selected = preferred?.takeIf { id -> plans.any { it.id == id } } + ?: payload.string("selected").takeIf { id -> plans.any { it.id == id } } + ?: plans.firstOrNull()?.id.orEmpty() + _state.value = _state.value.copy(plans = plans, loading = false) + if (selected.isNotBlank()) selectPlan(selected, persistServer = false) + } + .onFailure { error -> _state.value = _state.value.copy(loading = false, error = error.message ?: "Planliste konnte nicht geladen werden.") } + } + } + + fun selectPlan(planId: String, persistServer: Boolean = true) { + viewModelScope.launch { + _state.value = _state.value.copy(loading = true, error = "", conflict = null) + runCatching { + if (persistServer) repository.selectTrackerPlan(planId) + settings.setSelectedTrackerPlan(planId) + val ui = settings.trackerUi(planId) + Triple(repository.trackerPlan(planId, force = true), ui, repository.pendingForPlan(planId)) + }.onSuccess { (response, ui, queued) -> + val plan = parsePlan(response.obj("plan")) + var tracker = overlayPendingPatches(response.obj("tracker"), queued) + val week = ui.week.coerceIn(1, plan.weeks) + val day = ui.day.takeIf { candidate -> plan.days.any { it.num == candidate } } ?: plan.days.firstOrNull()?.num ?: 1 + val currentDay = plan.days.firstOrNull { it.num == day } + if (currentDay != null) { + val key = sessionKey(week, day) + val (migrated, changed) = migrateLegacyItems(trackerSession(tracker, key, plan), currentDay) + if (changed) tracker = updateTrackerSession(tracker, key, migrated) + } + _state.value = _state.value.copy( + loading = false, + selectedPlanId = planId, + response = response, + plan = plan, + tracker = tracker, + tab = ui.tab, + week = week, + day = day, + saveState = "", + ) + if (response.obj("tracker").obj("analysis_state").string("status") == "running") startPolling() + }.onFailure { error -> _state.value = _state.value.copy(loading = false, error = error.message ?: "Plan konnte nicht geladen werden.") } + } + } + + fun setTab(tab: String) { + _state.value = _state.value.copy(tab = tab) + persistUi() + } + + fun setWeek(week: Int) { + val plan = _state.value.plan ?: return + _state.value = _state.value.copy(week = week.coerceIn(1, plan.weeks)) + migrateCurrentLegacy() + persistUi() + } + + fun setDay(day: Int) { + val plan = _state.value.plan ?: return + if (plan.days.none { it.num == day }) return + _state.value = _state.value.copy(day = day) + migrateCurrentLegacy() + persistUi() + } + + private fun persistUi() { + val s = _state.value + if (s.selectedPlanId.isBlank()) return + viewModelScope.launch { settings.saveTrackerUi(s.selectedPlanId, s.tab, s.week, s.day) } + } + + fun currentDay(): DayUi? = _state.value.plan?.days?.firstOrNull { it.num == _state.value.day } + fun currentSession(): JsonObject? { + val s = _state.value + val plan = s.plan ?: return null + return trackerSession(s.tracker, sessionKey(s.week, s.day), plan) + } + + fun setSessionStatus(status: String) { + mutateSession { session -> + var next = session.updatedString("status", status) + val now = Instant.now().toString() + when (status) { + "in_progress" -> if (session.string("started_at").isBlank()) next = next.updatedString("started_at", now) + "stopped" -> next = next.updatedString("stopped_at", now) + "completed" -> next = next.updatedString("completed_at", now) + } + next + } + } + + fun resetSession() { + val plan = _state.value.plan ?: return + val key = sessionKey(_state.value.week, _state.value.day) + val tracker = updateTrackerSession(_state.value.tracker, key, blankSession(plan)) + _state.value = _state.value.copy(tracker = tracker) + scheduleSave() + } + + fun setSequenceStatus(id: String, done: Boolean) { + mutateSession { session -> + val item = de.drazz.boehmitools.training.domain.itemFor(session, id) + .updatedString("completion_status", if (done) "completed" else "planned") + .updatedBool("done", done) + updateItem(session, id, item) + } + } + + fun setItemStatus(id: String, status: String) { + mutateSession { session -> + var item = de.drazz.boehmitools.training.domain.itemFor(session, id) + .updatedString("completion_status", status) + .updatedBool("done", status == "completed" || status == "partial") + if (status != "skipped") item = item.updated("skip_reason", null) + updateItem(session, id, item) + } + } + + fun setItemProgression(id: String, value: String, stepId: String = "") { + val plan = _state.value.plan ?: return + val exercise = currentDay()?.rotations?.flatMap { it.exercises }?.firstOrNull { it.id == id } + mutateSession { session -> + var item = de.drazz.boehmitools.training.domain.itemFor(session, id).updatedString("progression", value) + item = if (stepId.isBlank()) item.updated("progression_step_id", null) else item.updatedString("progression_step_id", stepId) + val current = item["result_data"] as? JsonObject + if (exercise != null && current != null) { + val schema = resolveResultSchema(exercise, item, value, plan) + val adapted = adaptResultDataToSchema(current, schema) + item = if (adapted == null) { + item.updated("result_data", null).updatedString("result", "") + } else { + item.updated("result_data", adapted).updatedString("result", formatResultData(adapted)) + } + } + updateItem(session, id, item) + } + } + + fun setItemText(id: String, field: String, value: String) { + mutateSession { session -> + val item = de.drazz.boehmitools.training.domain.itemFor(session, id).updatedString(field, value) + updateItem(session, id, item) + } + } + + fun setItemResultData(id: String, data: JsonObject) { + mutateSession { session -> + val item = de.drazz.boehmitools.training.domain.itemFor(session, id) + .updated("result_data", data) + .updatedString("result", de.drazz.boehmitools.training.domain.formatResultData(data)) + updateItem(session, id, item) + } + } + + fun setItemResultFormat(id: String, format: JsonObject?) { + mutateSession { session -> + var item = de.drazz.boehmitools.training.domain.itemFor(session, id) + .updated("result_format", format) + val current = item["result_data"] as? JsonObject + if (format == null) { + item = item.updated("result_data", null).updatedString("result", "") + return@mutateSession updateItem(session, id, item) + } + + val nextMode = format.string("mode", "reps") + if (nextMode == "none") { + item = item.updated("result_data", null).updatedString("result", "") + return@mutateSession updateItem(session, id, item) + } + + if (current != null) { + val schema = mergeResultSchemaLayers(null, null, format) + val adapted = adaptResultDataToSchema(current, schema) + item = if (adapted == null) { + item.updated("result_data", null).updatedString("result", "") + } else { + item.updated("result_data", adapted).updatedString("result", formatResultData(adapted)) + } + } + updateItem(session, id, item) + } + } + + fun setWeekClosed(closed: Boolean) { + val s = _state.value + val statuses = s.tracker.obj("week_statuses").toMutableMap() + statuses[s.week.toString()] = buildJsonObject { + put("status", if (closed) "closed" else "open") + put("updated_at", Instant.now().toString()) + } + val tracker = s.tracker.updated("week_statuses", JsonObject(statuses)) + _state.value = s.copy(tracker = tracker) + scheduleSave() + } + + private fun mutateSession(block: (JsonObject) -> JsonObject) { + val s = _state.value + val plan = s.plan ?: return + val key = sessionKey(s.week, s.day) + val nextSession = block(trackerSession(s.tracker, key, plan)) + _state.value = s.copy(tracker = updateTrackerSession(s.tracker, key, nextSession), saveState = "Ungespeichert") + scheduleSave() + } + + private fun migrateCurrentLegacy() { + val s = _state.value + val plan = s.plan ?: return + val day = plan.days.firstOrNull { it.num == s.day } ?: return + val key = sessionKey(s.week, s.day) + val (session, changed) = migrateLegacyItems(trackerSession(s.tracker, key, plan), day) + if (changed) { + _state.value = s.copy(tracker = updateTrackerSession(s.tracker, key, session)) + scheduleSave() + } + } + + private fun scheduleSave() { + saveJob?.cancel() + saveJob = viewModelScope.launch { + delay(650) + saveCurrent() + } + } + + fun saveCurrent() { + viewModelScope.launch { + saveMutex.withLock { + val snapshot = _state.value + val plan = snapshot.plan ?: return@withLock + val key = sessionKey(snapshot.week, snapshot.day) + val session = trackerSession(snapshot.tracker, key, plan) + val patch = sessionPatch(snapshot.tracker, key, session) + _state.value = _state.value.copy(saveState = "Speichert …") + runCatching { repository.patchSession(snapshot.selectedPlanId, key, patch) } + .onSuccess { outcome -> + when (outcome) { + is SaveOutcome.Saved -> { + val revision = outcome.body.int("revision", snapshot.tracker.int("revision", 1) + 1) + _state.value = _state.value.copy( + tracker = _state.value.tracker.updatedInt("revision", revision), + saveState = "Gespeichert", + response = _state.value.response + .updated("analysis_catalog", outcome.body["analysis_catalog"]) + .updated("analysis_state", outcome.body["analysis_state"]), + ) + } + is SaveOutcome.Queued -> { + _state.value = _state.value.copy( + tracker = _state.value.tracker.updatedInt("revision", snapshot.tracker.int("revision", 1) + 1), + saveState = "Offline vorgemerkt", + ) + } + } + } + .onFailure { error -> + if (error is RevisionConflictException) { + _state.value = _state.value.copy(conflict = error, saveState = "Konflikt") + } else { + _state.value = _state.value.copy(saveState = "Fehler", error = error.message ?: "Speichern fehlgeschlagen") + } + } + } + } + } + + private fun overlayPendingPatches(base: JsonObject, queued: List): JsonObject { + val patches = queued.mapNotNull { mutation -> + runCatching { wireJson.parseToJsonElement(mutation.payloadJson).jsonObject }.getOrNull() + } + return overlaySessionPatches(base, patches) + } + + fun dismissConflict() { _state.value = _state.value.copy(conflict = null) } + fun reloadAfterConflict() { selectPlan(_state.value.selectedPlanId, persistServer = false) } + fun discardQueuedConflict(id: Long) { viewModelScope.launch { repository.discardConflict(id) } } + + fun startAnalysis(scope: String, week: Int? = null) { + val planId = _state.value.selectedPlanId + if (planId.isBlank()) return + viewModelScope.launch { + _state.value = _state.value.copy(analysisMessage = "Analyse wird gestartet …", error = "") + runCatching { repository.analysisStart(planId, scope, week) } + .onSuccess { outcome -> + _state.value = _state.value.copy( + response = _state.value.response + .updated("analysis_state", outcome.body["analysis_state"]) + .updated("analysis_cache", outcome.body["analysis_cache"]) + .updated("analysis_catalog", outcome.body["analysis_catalog"]), + analysisMessage = if (outcome.statusCode == 202) "Serverjob läuft …" else "Aktuelle Analyse aus Cache geladen.", + ) + if (outcome.statusCode == 202) startPolling() + } + .onFailure { error -> _state.value = _state.value.copy(analysisMessage = "", error = error.message ?: "Analyse fehlgeschlagen") } + } + } + + private fun startPolling() { + if (pollJob?.isActive == true) return + val planId = _state.value.selectedPlanId + pollJob = viewModelScope.launch { + while (true) { + delay(2_000) + val body = runCatching { repository.analysisStatus(planId) }.getOrElse { + _state.value = _state.value.copy(error = it.message ?: "Analysestatus konnte nicht gelesen werden") + return@launch + } + _state.value = _state.value.copy( + response = _state.value.response + .updated("analysis_state", body["analysis_state"]) + .updated("analysis_cache", body["analysis_cache"]) + .updated("analysis_catalog", body["analysis_catalog"]), + ) + val status = body.obj("analysis_state").string("status", "idle") + _state.value = _state.value.copy(analysisMessage = body.obj("analysis_state").string("message")) + if (status != "running") { + selectPlan(planId, persistServer = false) + return@launch + } + } + } + } +} diff --git a/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..00e7135 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,4 @@ + + + + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..6576291 --- /dev/null +++ b/android/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + boehmitools Training + diff --git a/android/app/src/main/res/values/themes.xml b/android/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..246fc51 --- /dev/null +++ b/android/app/src/main/res/values/themes.xml @@ -0,0 +1,9 @@ + + + diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..683208f --- /dev/null +++ b/android/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/android/app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt b/android/app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt new file mode 100644 index 0000000..c5d6315 --- /dev/null +++ b/android/app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt @@ -0,0 +1,238 @@ +package de.drazz.boehmitools.training.domain + +import java.io.InputStreamReader +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.buildJsonObject +import kotlinx.serialization.json.put +import kotlinx.serialization.json.jsonObject +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class FixtureContractTest { + private val json = Json { ignoreUnknownKeys = true } + + @Test + fun bothPlansRespectVersionAndStableIdContracts() { + listOf("phase-1", "calihoss-newbie-8-wochen").forEach { name -> + val wrapper = fixture("samples/plans/$name.json") + val config = wrapper.obj("config") + assertEquals(3, config.int("schema_version")) + assertEquals(2, config.int("contract_version")) + assertEquals(name, config.string("plan_id")) + + val dayIds = mutableListOf() + val rotationIds = mutableListOf() + val placementIds = mutableListOf() + val stepIds = mutableListOf() + config.array("days").mapNotNull { it as? JsonObject }.forEach { day -> + dayIds += day.string("id") + day.array("rotations").mapNotNull { it as? JsonObject }.forEach { rotation -> + rotationIds += rotation.string("id") + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> + placementIds += exercise.string("id") + assertTrue(exercise.string("legacy_id").matches(Regex("d\\d+-r\\d+-e\\d+"))) + assertTrue(exercise.string("exercise_id").startsWith("movement-")) + assertTrue(exercise.string("progression_id").isNotBlank()) + assertFalse(exercise.obj("result_schema").isEmpty()) + } + } + } + config.obj("stages").values.mapNotNull { it as? JsonObject }.forEach { stage -> + stage.array("steps").mapNotNull { it as? JsonObject }.forEach { step -> + stepIds += step.string("id") + assertFalse(step.obj("result_schema").isEmpty()) + } + } + listOf(dayIds, rotationIds, placementIds, stepIds).forEach { ids -> + assertTrue(ids.all { it.isNotBlank() }) + assertEquals(ids.size, ids.toSet().size) + } + } + } + + @Test + fun parsePlanSupportsBothProvidedEditorConfigs() { + listOf("phase-1", "calihoss-newbie-8-wochen").forEach { name -> + val config = fixture("samples/plans/$name.json").obj("config") + val plan = parsePlan(config) + assertEquals(8, plan.weeks) + assertEquals(6, plan.days.size) + assertEquals(31, plan.progressions.size) + assertTrue(plan.days.flatMap { it.rotations }.flatMap { it.exercises }.isNotEmpty()) + assertTrue(plan.title.isNotBlank()) + } + } + + @Test + fun bothLegacySessionsLoadAndMigrateWithoutDroppingItems() { + listOf("phase-1", "calihoss-newbie-8-wochen").forEach { name -> + val plan = parsePlan(fixture("samples/plans/$name.json").obj("config")) + val tracker = fixture("samples/sessions/$name.json") + assertTrue(tracker.int("version") in 1..7) + val firstSessionEntry = tracker.obj("sessions").entries.first() + val day = plan.days.first { it.num == firstSessionEntry.key.substringAfter("-d").toInt() } + val original = firstSessionEntry.value.jsonObject + val originalCount = original.obj("items").size + val (migrated, changed) = migrateLegacyItems(original, day) + assertTrue(changed) + assertEquals(originalCount, migrated.obj("items").size) + day.rotations.flatMap { it.exercises }.forEach { exercise -> + if (original.obj("items").containsKey(exercise.legacyId)) { + assertTrue(migrated.obj("items").containsKey(exercise.id)) + assertFalse(migrated.obj("items").containsKey(exercise.legacyId)) + } + } + } + } + + @Test + fun resultSchemaPriorityIsManualThenStepThenExerciseThenFallback() { + val config = fixture("samples/plans/phase-1.json").obj("config") + val plan = parsePlan(config) + val exercise = plan.days.flatMap { it.rotations }.flatMap { it.exercises } + .first { it.progressionId == "pike" } + val progression = plan.progressions.getValue("pike") + val step = progression.steps.first { it.resultSchema != null } + + val fromStep = resolveResultSchema( + exercise = exercise, + item = buildJsonObject { + put("progression_step_id", step.id) + }, + progressionText = step.name, + plan = plan, + ) + assertEquals("progression", fromStep.source) + assertEquals(step.resultSchema?.mode, fromStep.mode) + + val manual = resolveResultSchema( + exercise = exercise, + item = buildJsonObject { + put("result_format", buildJsonObject { + put("mode", "minutes") + put("weight_mode", "none") + put("laterality", "bilateral") + put("sides_mode", "same") + }) + }, + progressionText = step.name, + plan = plan, + ) + assertEquals("session", manual.source) + assertEquals("minutes", manual.mode) + assertNotEquals(fromStep.mode, manual.mode) + } + + @Test + fun partialProgressionSchemaInheritsUnspecifiedExerciseFields() { + val exercise = buildJsonObject { + put("mode", "reps") + put("weight_mode", "required") + put("laterality", "unilateral") + put("sides_mode", "separate") + put("sets", 4) + put("locked_sets", true) + } + val progression = buildJsonObject { put("mode", "seconds") } + val merged = mergeResultSchemaLayers(exercise, progression, null) + assertEquals("seconds", merged.mode) + assertEquals("required", merged.weightMode) + assertEquals("unilateral", merged.laterality) + assertEquals("separate", merged.sidesMode) + assertEquals(4, merged.defaultSets) + assertTrue(merged.lockedSets) + assertEquals("progression", merged.source) + } + + @Test + fun progressionUnitChangeNeverReinterpretsOldNumbers() { + val current = buildJsonObject { + put("version", 2) + put("mode", "reps") + put("laterality", "bilateral") + put("sides_mode", "same") + put("sets", 3) + put("values", kotlinx.serialization.json.buildJsonArray { add(8); add(7); add(6) }) + put("left_values", kotlinx.serialization.json.JsonArray(emptyList())) + put("right_values", kotlinx.serialization.json.JsonArray(emptyList())) + put("weight_kg", 8.0) + } + val adapted = adaptResultDataToSchema( + current, + ResultSchemaUi(mode = "seconds", weightMode = "none", defaultSets = 2, lockedSets = true), + )!! + assertEquals("seconds", adapted.string("mode")) + assertEquals(2, adapted.int("sets")) + assertTrue(adapted.array("values").all { it == kotlinx.serialization.json.JsonNull }) + assertTrue(adapted["weight_kg"] == kotlinx.serialization.json.JsonNull) + } + + @Test + fun resultDataV2NormalizesLegacyTextAndSetCount() { + val schema = ResultSchemaUi(mode = "seconds", defaultSets = 3) + val parsed = parseLegacyResult("3 x 20 s, 8 kg", schema)!! + assertEquals(2, parsed.int("version")) + assertEquals("seconds", parsed.string("mode")) + assertEquals(3, parsed.int("sets")) + assertEquals(3, parsed.array("values").size) + assertTrue(formatResultData(parsed).contains("20")) + assertTrue(formatResultData(parsed).contains("8 kg")) + + val resized = resultDataWithSets(parsed, 20) + assertEquals(20, resized.int("sets")) + assertEquals(20, resized.array("values").size) + assertEquals(20, resultDataWithSets(resized, 99).int("sets")) + } + + @Test + fun queuedPatchesOverlayInFifoOrderAfterRestart() { + val base = buildJsonObject { + put("revision", 5) + put("profile", buildJsonObject { put("display_name", "Server") }) + put("week_statuses", JsonObject(emptyMap())) + put("sessions", JsonObject(emptyMap())) + } + val first = buildJsonObject { + put("expected_revision", 5) + put("profile", buildJsonObject { put("display_name", "Lokal") }) + put("session_key", "w01-d01") + put("session", buildJsonObject { put("status", "in_progress") }) + } + val second = buildJsonObject { + put("expected_revision", 6) + put("week_statuses", buildJsonObject { put("1", buildJsonObject { put("status", "closed") }) }) + put("session_key", "w01-d02") + put("session", buildJsonObject { put("status", "completed") }) + } + val overlaid = overlaySessionPatches(base, listOf(first, second)) + assertEquals(7, overlaid.int("revision")) + assertEquals("Lokal", overlaid.obj("profile").string("display_name")) + assertEquals("closed", overlaid.obj("week_statuses").obj("1").string("status")) + assertEquals("in_progress", overlaid.obj("sessions").obj("w01-d01").string("status")) + assertEquals("completed", overlaid.obj("sessions").obj("w01-d02").string("status")) + } + + @Test + fun analysesAndProposalsMatchOverwriteOnlyModel() { + val week = fixture("samples/analyses/phase-1/week-01.json") + val overall = fixture("samples/analyses/phase-1/overall.json") + val proposals = fixture("samples/proposals/phase-1.json") + assertEquals("week", week.string("type")) + assertEquals("overall", overall.string("type")) + assertTrue(proposals.array("proposals").isNotEmpty()) + proposals.array("proposals").mapNotNull { it as? JsonObject }.forEach { + assertTrue(it.string("id").isNotBlank()) + assertTrue(it.string("target_exercise_id").startsWith("movement-")) + assertEquals("open", it.string("status")) + } + } + + private fun fixture(path: String): JsonObject { + val stream = checkNotNull(javaClass.classLoader?.getResourceAsStream(path)) { "Fixture fehlt: $path" } + return InputStreamReader(stream).use { json.parseToJsonElement(it.readText()).jsonObject } + } +} diff --git a/android/app/src/test/resources/samples/analyses/phase-1/overall.json b/android/app/src/test/resources/samples/analyses/phase-1/overall.json new file mode 100644 index 0000000..c7a241c --- /dev/null +++ b/android/app/src/test/resources/samples/analyses/phase-1/overall.json @@ -0,0 +1,1517 @@ +{ + "id": "overall_20260723T120133714864Z", + "type": "overall", + "created_at": "2026-07-23T12:01:33+00:00", + "model": "gpt-5.5", + "source_hash": "2df1ea003a9bb659e84030ca8aa958f1d5bfbb37b04c097cf98a29b68c0bc683", + "sessions_considered": 6, + "weeks_considered": [ + 1 + ], + "request": { + "scope": "overall", + "schema": { + "headline": "Kurzer Titel", + "summary": "Kompakte Einordnung in höchstens 4 Sätzen", + "data_quality": "Ein kurzer Satz zur Aussagekraft", + "metrics": [ + { + "label": "Kennzahl", + "value": "Wert", + "detail": "kurze Einordnung" + } + ], + "exercise_updates": [ + { + "name": "Übung", + "trend": "up|stable|down|unclear", + "current_level": "dokumentierter Stand", + "evidence": "ein konkreter Beleg", + "next_action": "nächster planverträglicher Schritt", + "criterion": "messbares Kriterium" + } + ], + "plan_adjustments": [ + { + "target": "Woche/Tag/Übung oder Block", + "suggested_change": "konkreter Vorschlag", + "reason": "kurze Begründung", + "manual_step": "was im Trainingsplan-Modul manuell zu ändern wäre", + "condition": "nur wenn/sonst beibehalten" + } + ], + "warnings": [ + "nur echte Auffälligkeiten" + ], + "conclusion": "ein knapper Abschlusssatz" + }, + "messages": [ + { + "role": "system", + "content": "Du analysierst ein Trainingsprotokoll auf Deutsch. Der übergebene Trainingsplan ist bindend und muss die Empfehlungen bestimmen. Verändere niemals eigenmächtig Sessionformat, Arbeitsintervall, Pausen, Rundenzahl, Satzanzahl oder Progressionslogik. Bei Tabata oder anderen festen Intervallen darfst du insbesondere keine längeren Holds oder längere Arbeitszeiten empfehlen. Progression erfolgt dann nur planverträglich, etwa über die vorgesehene schwierigere Variante, Gewicht, ROM, Tempo oder Technik. Nutze ausschließlich die Daten im Input, erfinde keine Leistungen und gib keine medizinische Diagnose. Die Analyse soll kompakt bleiben: maximal 4 Kennzahlen, 4 Übungsupdates, 3 Plananpassungen und 2 Warnungen. Plananpassungen sind nur Vorschläge zur manuellen Übernahme im Trainingsplan-Modul; behaupte nie, den Plan geändert zu haben. Bewerte die Entwicklung über die vorhandenen Wochen und formuliere höchstens drei konkrete, optionale Anpassungen für die nächste Woche beziehungsweise den nächsten Planabschnitt. Antworte ausschließlich als JSON im vorgegebenen Schema." + }, + { + "role": "user", + "content": "Gewünschtes JSON-Schema:\n{\"headline\": \"Kurzer Titel\", \"summary\": \"Kompakte Einordnung in höchstens 4 Sätzen\", \"data_quality\": \"Ein kurzer Satz zur Aussagekraft\", \"metrics\": [{\"label\": \"Kennzahl\", \"value\": \"Wert\", \"detail\": \"kurze Einordnung\"}], \"exercise_updates\": [{\"name\": \"Übung\", \"trend\": \"up|stable|down|unclear\", \"current_level\": \"dokumentierter Stand\", \"evidence\": \"ein konkreter Beleg\", \"next_action\": \"nächster planverträglicher Schritt\", \"criterion\": \"messbares Kriterium\"}], \"plan_adjustments\": [{\"target\": \"Woche/Tag/Übung oder Block\", \"suggested_change\": \"konkreter Vorschlag\", \"reason\": \"kurze Begründung\", \"manual_step\": \"was im Trainingsplan-Modul manuell zu ändern wäre\", \"condition\": \"nur wenn/sonst beibehalten\"}], \"warnings\": [\"nur echte Auffälligkeiten\"], \"conclusion\": \"ein knapper Abschlusssatz\"}\n\nAnalyse-Datensatz:\n{\"analysis_type\": \"overall\", \"plan\": {\"title\": \"Calisthenics-Basis – 8-Wochen Komplettplan\", \"subtitle\": \"Blutiger Anfänger · 1,90 m · 95 kg · Rucksack + Couch + Kettlebell · tägliche ~30-Min Tabata-Session\", \"weeks\": 8, \"selected_week\": null, \"selected_phase\": null, \"training_format\": {\"mode\": \"tabata\", \"is_tabata\": true, \"fixed_interval\": true, \"work_seconds\": 20, \"rest_seconds\": 10, \"rounds\": 8, \"session_method\": \"Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.\", \"timer_note\": \"Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.\", \"constraints\": [\"Arbeitsintervalle bleiben fest bei 20 Sekunden; keine längeren Holds oder verlängerten Arbeitsintervalle empfehlen.\", \"Progression nur innerhalb des Planformats: planmäßige schwierigere Variante, mehr Gewicht, saubererer ROM/Tempo oder bessere Technik; Pausen und Rundenzahl nicht eigenmächtig ändern.\"]}, \"guidance\": {\"goals\": \"Du startest bei 2–3 Liegestützen und 0 Dips. Realistisch und ehrlich nach 8 Wochen sauberer Arbeit:\\n•  Liegestütze: von 2–3 auf erste saubere volle Reps (Rest in Knie-/Negativ-Form).\\n•  Bank-Dips: saubere Reps mit Füßen etwas weiter weg.   Pike Push-up: am Boden mit kleinem ROM.\\n•  Plank ~45–60 s, Wall Sit ~60 s, viele saubere Goblet-Kniebeugen, sauberer Swing + spürbar bessere Griffkraft.\\n•  Noch NICHT dran: Diamond Push-ups, L-Sit, Handstand, schwerer Turkish Get-Up, Snatch – Stoff für Woche 12+.\", \"goals_note\": \"Wichtig: Die Wochen-Stufen sind ein Rahmen, kein Muss. Bleib auf einer Stufe, solange sie fordert. Aufstiegs-Regel: erst hoch (oder schwerere Glocke), wenn du eine Übung 2 Sessions in Folge in allen Sätzen sauber über die vollen 20s schaffst. KB = Kettlebell bevorzugt; Rucksack bleibt als Alternative.\", \"session_method\": \"Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.\", \"timer_note\": \"Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.\", \"reminders\": [\"Form vor Reps. Wähle die Stufe / Glocke, bei der du die 20s kontrolliert durcharbeitest – mit 1–2 Reps Reserve.\", \"Swing-Technik first. Der Swing ist ein Hüft-Schnapp, KEIN Kniebeuge und KEIN Armheben. Erst Dead-Stop-Swings, bis die Hüftstreckung sitzt.\", \"Einseitige Übungen (Tag 2): Durchgang 1 = links, Durchgang 2 = rechts – so ist jede Seite automatisch ausbalanciert.\", \"Täglich ist viel. Tag 5 leicht, Tag 7 frei. Bei starkem Muskelkater einen Extra-Ruhetag – gehört zum Aufbau.\", \"Schlaf & Eiweiß. 7–9 h Schlaf, ~1,6 g Eiweiß pro kg. Stechender Schmerz = stoppen.\"]}, \"days\": [{\"day\": 1, \"focus\": \"Drücken + Beine\", \"light\": false, \"note\": \"\", \"warmup\": [\"Handflächen-Wippen\", \"Armkreisen/Wand-Slides\", \"Hüftkreisen + Beinschwünge\", \"Knie-über-Zeh an der Wand\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Türrahmen (Brust/Schulter, je 30–40s)\", \"Trizeps (je 30s)\", \"Seitheben/Lat-Seitdehnung (je 30s)\", \"Quad im Stand (je 30s)\", \"Knieender Hüftbeuger (je 30–40s)\", \"Wade an der Wand (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Liegestütze\", \"progression_id\": \"ls\", \"cue\": \"Brust kontrolliert senken & drücken\"}, {\"name\": \"Bank-Dips (Couch)\", \"progression_id\": \"dips\", \"cue\": \"Po vor der Couch senken, hochdrücken\"}, {\"name\": \"Pike\", \"progression_id\": \"pike\", \"cue\": \"umgek. V, Kopf senken (Schultern)\"}, {\"name\": \"Seitheben (KB/Rucksack)\", \"progression_id\": \"lraise\", \"cue\": \"Arme seitlich bis Schulterhöhe, kontrolliert\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Goblet Squat (KB/Rucksack)\", \"progression_id\": \"gobletkb\", \"cue\": \"Last umarmen, tief\"}, {\"name\": \"Ausfallschritt rückw.\", \"progression_id\": \"lunge\", \"cue\": \"großer Schritt, Knie Richtung Boden\"}, {\"name\": \"Glute Bridge\", \"progression_id\": \"bridge\", \"cue\": \"Hüfte hochdrücken, Po fest\"}, {\"name\": \"Wadenheben\", \"progression_id\": \"calf\", \"cue\": \"auf Zehenspitzen hoch/runter\"}]}]}, {\"day\": 2, \"focus\": \"Ziehen + Core\", \"light\": false, \"note\": \"\", \"warmup\": [\"Handflächen-Wippen\", \"Handtuch-Dislocates (8–10×)\", \"Cat-Cow\", \"Hüftkreisen + Beinschwünge\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Lat-/Seitdehnung (je 30s)\", \"Türrahmen (je 30–40s)\", \"BWS über die Couch (30–40s)\", \"Cobra/Sphinx (Bauch, 30s)\", \"Unterarm (je 20s)\", \"Knieender Hüftbeuger (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Einarm-Rudern ENG (KB)\", \"progression_id\": \"rowe\", \"cue\": \"1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts\"}, {\"name\": \"Bizeps-Curls (KB/Rucksack)\", \"progression_id\": \"load\", \"cue\": \"Oberarme fix, zu den Schultern\"}, {\"name\": \"Einarm-Rudern BREIT (KB)\", \"progression_id\": \"row2\", \"cue\": \"1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts\"}, {\"name\": \"Superman / Y-T-W\", \"progression_id\": \"sup\", \"cue\": \"bäuchlings Arme+Beine heben\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Plank\", \"progression_id\": \"plank\", \"cue\": \"gerade Linie halten\"}, {\"name\": \"Mountain Climbers\", \"progression_id\": \"mtn\", \"cue\": \"Knie zügig zur Brust\"}, {\"name\": \"Dead Bug\", \"progression_id\": \"db\", \"cue\": \"gegengleich, Rücken am Boden\"}, {\"name\": \"Beinheben\", \"progression_id\": \"lr\", \"cue\": \"Beine heben/senken\"}]}]}, {\"day\": 3, \"focus\": \"Beine + Core\", \"light\": false, \"note\": \"\", \"warmup\": [\"Hüftkreisen + Beinschwünge\", \"Knie-über-Zeh an der Wand\", \"Cat-Cow\", \"Handflächen-Wippen\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Hamstring-Dehnung (Couch, je 30–40s)\", \"Quad im Stand (je 30s)\", \"Knieender Hüftbeuger (je 30–40s)\", \"Figure-4/Taube (je 30–40s)\", \"Cobra + liegende Wirbelsäulen-Rotation (je 30s)\", \"Wade an der Wand (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Goblet Squat (KB/Rucksack)\", \"progression_id\": \"gobletkb\", \"cue\": \"Last umarmen, tief\"}, {\"name\": \"RDL / Hip Hinge (KB/Rucksack)\", \"progression_id\": \"load\", \"cue\": \"Hüfte zurück, Rücken gerade, Hamstrings\"}, {\"name\": \"Glute Bridge 1-beinig\", \"progression_id\": \"bridge\", \"cue\": \"ein Bein gestreckt\"}, {\"name\": \"Wall Sit\", \"progression_id\": \"wall\", \"cue\": \"Rücken an die Wand, halten\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Hollow Hold\", \"progression_id\": \"holl\", \"cue\": \"unterer Rücken am Boden\"}, {\"name\": \"Side Plank (Wechsel)\", \"progression_id\": \"sp\", \"cue\": \"seitlich auf einem Unterarm\"}, {\"name\": \"Bicycle Crunch\", \"progression_id\": \"bic\", \"cue\": \"Ellbogen zum Gegenknie\"}, {\"name\": \"Russian Twist (KB)\", \"progression_id\": \"tw\", \"cue\": \"sitzend Rumpf drehen\"}]}]}, {\"day\": 4, \"focus\": \"Oberkörper: Drücken + Ziehen\", \"light\": false, \"note\": \"\", \"warmup\": [\"Handflächen-Wippen\", \"Handtuch-Dislocates (8–10×)\", \"Armkreisen/Wand-Slides\", \"Cat-Cow\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Türrahmen (je 30–40s)\", \"Trizeps (je 30s)\", \"Lat-/Seitdehnung (je 30s)\", \"BWS über die Couch (30–40s)\", \"Unterarm (je 20s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Liegestütze\", \"progression_id\": \"ls\", \"cue\": \"Brust kontrolliert senken & drücken\"}, {\"name\": \"Bank-Dips (Couch)\", \"progression_id\": \"dips\", \"cue\": \"Po vor der Couch senken\"}, {\"name\": \"Pike\", \"progression_id\": \"pike\", \"cue\": \"umgek. V, Kopf senken\"}, {\"name\": \"KB Floor Press\", \"progression_id\": \"press\", \"cue\": \"Glocke vom Boden weg drücken\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Einarm-Rudern ENG (KB)\", \"progression_id\": \"rowe\", \"cue\": \"1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts\"}, {\"name\": \"Bizeps-Curls (KB/Rucksack)\", \"progression_id\": \"load\", \"cue\": \"Oberarme fix, zu den Schultern\"}, {\"name\": \"Einarm-Rudern BREIT (KB)\", \"progression_id\": \"row2\", \"cue\": \"1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts\"}, {\"name\": \"Y-T-W / Superman\", \"progression_id\": \"sup\", \"cue\": \"bäuchlings, Rücken\"}]}]}, {\"day\": 5, \"focus\": \"Ganzkörper LEICHT + Mobility\", \"light\": true, \"note\": \"\", \"warmup\": [\"Cat-Cow\", \"Hüftkreisen + Beinschwünge\", \"Armkreisen/Wand-Slides\", \"Knie-über-Zeh an der Wand\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Türrahmen (je 30s)\", \"Knieender Hüftbeuger (je 30s)\", \"BWS über die Couch (30–40s)\", \"Figure-4/Taube (je 30s)\", \"Wade an der Wand (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Kniebeuge (leicht)\", \"progression_id\": \"name:kniebeuge-leicht\", \"cue\": \"locker, sauber\"}, {\"name\": \"Liegestütze (1 Stufe leichter)\", \"progression_id\": \"name:liegestuetze-1-stufe-leichter\", \"cue\": \"leichtere Variante\"}, {\"name\": \"Rudern (leicht)\", \"progression_id\": \"name:rudern-leicht\", \"cue\": \"wenig Gewicht\"}, {\"name\": \"Mountain Climbers (locker)\", \"progression_id\": \"name:mountain-climbers-locker\", \"cue\": \"ruhiges Tempo\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Inchworm\", \"progression_id\": \"name:inchworm\", \"cue\": \"in den Stütz wandern, zurück\"}, {\"name\": \"Cat-Cow\", \"progression_id\": \"name:cat-cow\", \"cue\": \"Rücken ründen/strecken\"}, {\"name\": \"Bird Dog\", \"progression_id\": \"name:bird-dog\", \"cue\": \"gegengleich Arm+Bein, kurz halten\"}, {\"name\": \"KB-Halo (leicht)\", \"progression_id\": \"name:kb-halo-leicht\", \"cue\": \"langsam um den Kopf kreisen\"}]}]}, {\"day\": 6, \"focus\": \"KETTLEBELL Ganzkörper\", \"light\": false, \"note\": \"Reine Kettlebell-Session. Swing = Hüft-Schnapp, kein Armheben. Glocke so wählen, dass alle Reps sauber sind.\", \"warmup\": [\"Halo (leicht)\", \"Hüftkreisen + Beinschwünge\", \"Cat-Cow\", \"Handflächen-Wippen\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Hamstring-Dehnung (Couch, je 30–40s)\", \"Knieender Hüftbeuger (je 30–40s)\", \"Figure-4/Taube (je 30–40s)\", \"Unterarm (je 20s)\", \"BWS über die Couch (30–40s)\", \"Lat-/Seitdehnung (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"KB Swing (zweiarmig)\", \"progression_id\": \"swing\", \"cue\": \"Hüft-Schnapp, kein Armheben\"}, {\"name\": \"Goblet Squat (KB)\", \"progression_id\": \"gobletkb\", \"cue\": \"Glocke vor der Brust, tief\"}, {\"name\": \"KB-Deadlift (beidbeinig)\", \"progression_id\": \"kbdl\", \"cue\": \"Glocke zwischen den Füßen, Hüfte hinge\"}, {\"name\": \"Halo\", \"progression_id\": \"halo\", \"cue\": \"eng um den Kopf kreisen\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"KB Floor Press\", \"progression_id\": \"press\", \"cue\": \"Glocke vom Boden weg drücken\"}, {\"name\": \"Einarmiges Rudern (KB)\", \"progression_id\": \"row1\", \"cue\": \"Rücken gerade, zum Bauch\"}, {\"name\": \"Bottoms-up KB-Hold (Griff)\", \"progression_id\": \"carry\", \"cue\": \"Glocke Boden nach oben, Unterarm senkrecht, still halten\"}, {\"name\": \"Around-the-World + Uppercut (KB)\", \"progression_id\": \"atw\", \"cue\": \"Glocke um den Körper, dann diagonal zur Gegen-Schulter, andere Hand fängt\"}]}]}], \"phases\": [{\"name\": \"Phase 1 · Fundament\", \"instructions\": \"Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.\", \"start_week\": 1, \"end_week\": 2}, {\"name\": \"Phase 2 · Aufbau\", \"instructions\": \"Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.\", \"start_week\": 3, \"end_week\": 4}, {\"name\": \"Phase 3 · Steigerung\", \"instructions\": \"Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.\", \"start_week\": 5, \"end_week\": 6}, {\"name\": \"Phase 4 · Festigen\", \"instructions\": \"Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.\", \"start_week\": 7, \"end_week\": 8}], \"progression_stages\": {\"atw\": {\"name\": \"Around-the-World + Uppercut (KB)\", \"steps\": [\"nur Around-the-World, leicht\", \"Around-the-World, mehr Reps\", \"+ Uppercut, leicht\", \"+ Uppercut, flüssig\"]}, \"bic\": {\"name\": \"Bicycle Crunch\", \"steps\": [\"langsam\", \"moderat\", \"moderat\", \"zügig\"]}, \"bridge\": {\"name\": \"Glute Bridge\", \"steps\": [\"beidbeinig\", \"beidbeinig, 2s halten\", \"einbeinig\", \"einbeinig, 2s halten\"]}, \"calf\": {\"name\": \"Wadenheben\", \"steps\": [\"Körpergewicht\", \"langsam\", \"langsam, mehr Reps\", \"1-beinig\"]}, \"carry\": {\"name\": \"Bottoms-up KB-Hold (Griff)\", \"steps\": [\"im Stand 10–15s\", \"15–20s, ruhig\", \"bis Schulterhöhe 15s\", \"länger / ein paar Schritte\"]}, \"db\": {\"name\": \"Dead Bug\", \"steps\": [\"Arme ODER Beine einzeln\", \"gegengleich\", \"gegengleich + Pause\", \"Beine weiter gestreckt\"]}, \"dips\": {\"name\": \"Bank-Dips (Couch)\", \"steps\": [\"Knie stark gebeugt, klein\", \"Knie gebeugt, voll\", \"Füße etwas weiter\", \"Füße weiter weg\"]}, \"gobletkb\": {\"name\": \"Goblet Squat (KB/Rucksack)\", \"steps\": [\"leicht, Tiefe lernen\", \"leicht, mehr Reps\", \"mittel\", \"mittel, 3s runter\"]}, \"halo\": {\"name\": \"Halo\", \"steps\": [\"leicht, langsam\", \"leicht\", \"mittel\", \"mittel, langsam\"]}, \"holl\": {\"name\": \"Hollow Hold\", \"steps\": [\"Tuck (Knie an)\", \"Tuck, länger\", \"halb gestreckt\", \"halb/voll\"]}, \"kbdl\": {\"name\": \"KB-Deadlift (beidbeinig)\", \"steps\": [\"leicht, Technik\", \"leicht, mehr Reps\", \"mittel\", \"schwerer, kontrolliert\"]}, \"load\": {\"name\": \"Bizeps-Curls (KB/Rucksack)\", \"steps\": [\"leicht\", \"etwas mehr\", \"mehr\", \"fordernd\"]}, \"lr\": {\"name\": \"Beinheben\", \"steps\": [\"Knie gebeugt\", \"Knie gebeugt, langsam\", \"halb gestreckt\", \"gestreckt\"]}, \"lraise\": {\"name\": \"Seitheben (KB/Rucksack)\", \"steps\": [\"leicht (Technik)\", \"leicht, mehr Reps\", \"mittel\", \"mittel, langsam\"]}, \"ls\": {\"name\": \"Liegestütze\", \"steps\": [\"Inkline (hoch)\", \"Inkline tiefer / Knie\", \"Knie / Negativ\", \"Negativ + erste Volle\"]}, \"lseng\": {\"name\": \"Inkline (hoch)\", \"steps\": [\"Inkline (hoch)\", \"Inkline mittel\", \"Inkline tief\", \"Knie-Liegestütze\"]}, \"lunge\": {\"name\": \"Ausfallschritt rückw.\", \"steps\": [\"Körpergewicht (ggf. festhalten)\", \"Körpergewicht\", \"mehr Reps\", \"+ Last leicht\"]}, \"mtn\": {\"name\": \"Mountain Climbers\", \"steps\": [\"moderat\", \"moderat\", \"zügig\", \"zügig\"]}, \"pike\": {\"name\": \"Pike\", \"steps\": [\"Pike Hold\", \"Hold + kleine Senkung\", \"Push-up Hände erhöht\", \"Push-up Boden, klein\"]}, \"plank\": {\"name\": \"Plank\", \"steps\": [\"auf Knien 15–20 s\", \"voll 20–30 s\", \"voll + Schulter-Taps\", \"voll, länger\"]}, \"press\": {\"name\": \"KB Floor Press\", \"steps\": [\"leicht (Floor Press)\", \"Floor Press mehr\", \"Overhead leicht\", \"Overhead mittel\"]}, \"row1\": {\"name\": \"Einarmiges Rudern (KB)\", \"steps\": [\"leicht\", \"etwas mehr\", \"einarmig leicht\", \"einarmig mittel\"]}, \"row2\": {\"name\": \"Einarm-Rudern BREIT (KB)\", \"steps\": [\"leicht (Technik)\", \"leicht, mehr Reps\", \"mittel\", \"mittel, Pause oben\"]}, \"rowe\": {\"name\": \"Einarm-Rudern ENG (KB)\", \"steps\": [\"leicht\", \"etwas mehr\", \"mehr\", \"fordernd\"]}, \"sp\": {\"name\": \"Side Plank (Wechsel)\", \"steps\": [\"auf Knien\", \"auf Knien, länger\", \"voll\", \"voll, länger\"]}, \"squat\": {\"name\": \"Körpergewicht\", \"steps\": [\"Körpergewicht\", \"Körpergewicht, mehr Reps\", \"Goblet leicht\", \"Goblet mittel\"]}, \"sup\": {\"name\": \"Superman / Y-T-W\", \"steps\": [\"Superman Hold\", \"Superman Hold länger\", \"Reverse Snow Angels\", \"Y-T-W Raises\"]}, \"swing\": {\"name\": \"KB Swing (zweiarmig)\", \"steps\": [\"Dead-Stop, leicht (Technik)\", \"Dead-Stop, mehr Reps\", \"durchgehend, mittel\", \"durchgehend, schwerer\"]}, \"tsquat\": {\"name\": \"3s runter\", \"steps\": [\"3s runter\", \"3s + Pause unten\", \"Goblet 3s\", \"Goblet 3s + Pause\"]}, \"tw\": {\"name\": \"Russian Twist (KB)\", \"steps\": [\"ohne Gewicht\", \"ohne Gewicht\", \"KB leicht\", \"KB\"]}, \"wall\": {\"name\": \"Wall Sit\", \"steps\": [\"15–25 s\", \"25–40 s\", \"40–55 s\", \"55–70 s\"]}}}, \"weekly_analyses\": [{\"week\": 1, \"source_hash\": \"815dc58aad775b90e6302fb9a687d2c80c8c76f19b9008d0072953d1a160dee8\", \"headline\": \"Woche 1: starker Einstieg, Progression vorsichtig\", \"summary\": \"Die Woche wurde sehr konsequent gestartet; effektiv sind 5 von 6 geplanten Trainingstagen mit Übungen dokumentiert. Die meisten Übungen liegen sinnvoll auf den vorgesehenen Anfänger-Stufen der Phase 1. Erste stabile Muster zeigen sich bei Pike Holds, Rudern und Core-Holds, während Liegestütze noch klar auf Konsolidierung statt Progression stehen. Für Woche 2 sollten nur planverträgliche Variantenwechsel genutzt werden, keine längeren Arbeitsintervalle.\", \"metrics\": [{\"label\": \"Durchführung\", \"value\": \"System 6/6, effektiv 5/6\", \"detail\": \"Tag 5 hat die Notiz „Nicht durchgeführt“ und keine erledigten Übungen.\"}, {\"label\": \"Dokumentationsumfang\", \"value\": \"36 dokumentierte Übungen\", \"detail\": \"Genug Daten für erste Übungstendenzen, aber noch keine langfristigen Trends.\"}, {\"label\": \"Tabata-Format\", \"value\": \"20/10 beibehalten\", \"detail\": \"Die dokumentierten Holds bleiben im festen 20-Sekunden-Arbeitsintervall.\"}, {\"label\": \"Belastungsniveau\", \"value\": \"überwiegend Startstufen\", \"detail\": \"Passt zur Phase 1 mit Technikfokus und leichter Last.\"}], \"exercise_updates\": [{\"name\": \"Liegestütze\", \"trend\": \"stable\", \"current_level\": \"Inkline hoch\", \"evidence\": \"Tag 1: 8/7/7/7/6/6/5; Tag 4: 8/7/7/6/6/6\", \"next_action\": \"Stufe beibehalten und sauberen ROM sowie gleichmäßigere Wiederholungen priorisieren.\", \"criterion\": \"Erst auf Inkline tiefer/Knie wechseln, wenn 2 Sessions in Folge alle vorgesehenen 20s-Intervalle sauber mit stabiler Körperspannung geschafft werden.\"}, {\"name\": \"Pike\", \"trend\": \"up\", \"current_level\": \"Pike Hold\", \"evidence\": \"Tag 1 und Tag 4 jeweils 6x20 s dokumentiert.\", \"next_action\": \"Optional nächste planmäßige Stufe „Hold + kleine Senkung“ innerhalb der 20s testen.\", \"criterion\": \"Nur übernehmen, wenn 6/6 Intervalle ohne Hüftabsacken und ohne Technikverlust sauber bleiben.\"}, {\"name\": \"Einarm-Rudern ENG (KB)\", \"trend\": \"up\", \"current_level\": \"leicht, 4 kg\", \"evidence\": \"Tag 2: 4kg, 9/9/8/8/8/9; Tag 4: 4kg, 9/9/9/8/8/9\", \"next_action\": \"Bei sauberem Zug zur Hüfte planverträglich auf „etwas mehr“ bzw. leicht höhere Last wechseln.\", \"criterion\": \"2 Sessions in Folge alle dokumentierten Intervalle je Seite kontrolliert mit mindestens ähnlicher Wiederholungszahl abschließen.\"}, {\"name\": \"KB Swing (zweiarmig)\", \"trend\": \"unclear\", \"current_level\": \"Dead-Stop, leicht; 10 kg\", \"evidence\": \"Tag 6: 10kg, 15 dokumentiert.\", \"next_action\": \"Dead-Stop-Technik beibehalten und pro 20s-Intervall genauer dokumentieren.\", \"criterion\": \"Erst steigern, wenn alle Swing-Intervalle in 2 Sessions sauber als Hüft-Schnapp ohne Armheben dokumentiert sind.\"}], \"plan_adjustments\": [{\"target\": \"Woche 2 / Pike\", \"suggested_change\": \"Progressionsstufe von „Pike Hold“ auf „Hold + kleine Senkung“ setzen.\", \"reason\": \"Zwei dokumentierte Einheiten mit 6x20 s Pike Hold sprechen für einen vorsichtigen Variantenwechsel.\", \"manual_step\": \"Im Trainingsplan-Modul bei Pike die nächste Progressionsstufe auswählen.\", \"condition\": \"Nur wenn beide Pike-Einheiten technisch sauber waren; sonst Pike Hold beibehalten.\"}, {\"target\": \"Woche 2 / Einarm-Rudern ENG\", \"suggested_change\": \"Von „leicht“ auf „etwas mehr“ wechseln, z.B. etwas höhere La" + } + ], + "dataset": { + "analysis_type": "overall", + "plan": { + "title": "Calisthenics-Basis – 8-Wochen Komplettplan", + "subtitle": "Blutiger Anfänger · 1,90 m · 95 kg · Rucksack + Couch + Kettlebell · tägliche ~30-Min Tabata-Session", + "weeks": 8, + "selected_week": null, + "selected_phase": null, + "training_format": { + "mode": "tabata", + "is_tabata": true, + "fixed_interval": true, + "work_seconds": 20, + "rest_seconds": 10, + "rounds": 8, + "session_method": "Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.", + "timer_note": "Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.", + "constraints": [ + "Arbeitsintervalle bleiben fest bei 20 Sekunden; keine längeren Holds oder verlängerten Arbeitsintervalle empfehlen.", + "Progression nur innerhalb des Planformats: planmäßige schwierigere Variante, mehr Gewicht, saubererer ROM/Tempo oder bessere Technik; Pausen und Rundenzahl nicht eigenmächtig ändern." + ] + }, + "guidance": { + "goals": "Du startest bei 2–3 Liegestützen und 0 Dips. Realistisch und ehrlich nach 8 Wochen sauberer Arbeit:\n•  Liegestütze: von 2–3 auf erste saubere volle Reps (Rest in Knie-/Negativ-Form).\n•  Bank-Dips: saubere Reps mit Füßen etwas weiter weg.   Pike Push-up: am Boden mit kleinem ROM.\n•  Plank ~45–60 s, Wall Sit ~60 s, viele saubere Goblet-Kniebeugen, sauberer Swing + spürbar bessere Griffkraft.\n•  Noch NICHT dran: Diamond Push-ups, L-Sit, Handstand, schwerer Turkish Get-Up, Snatch – Stoff für Woche 12+.", + "goals_note": "Wichtig: Die Wochen-Stufen sind ein Rahmen, kein Muss. Bleib auf einer Stufe, solange sie fordert. Aufstiegs-Regel: erst hoch (oder schwerere Glocke), wenn du eine Übung 2 Sessions in Folge in allen Sätzen sauber über die vollen 20s schaffst. KB = Kettlebell bevorzugt; Rucksack bleibt als Alternative.", + "session_method": "Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.", + "timer_note": "Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.", + "reminders": [ + "Form vor Reps. Wähle die Stufe / Glocke, bei der du die 20s kontrolliert durcharbeitest – mit 1–2 Reps Reserve.", + "Swing-Technik first. Der Swing ist ein Hüft-Schnapp, KEIN Kniebeuge und KEIN Armheben. Erst Dead-Stop-Swings, bis die Hüftstreckung sitzt.", + "Einseitige Übungen (Tag 2): Durchgang 1 = links, Durchgang 2 = rechts – so ist jede Seite automatisch ausbalanciert.", + "Täglich ist viel. Tag 5 leicht, Tag 7 frei. Bei starkem Muskelkater einen Extra-Ruhetag – gehört zum Aufbau.", + "Schlaf & Eiweiß. 7–9 h Schlaf, ~1,6 g Eiweiß pro kg. Stechender Schmerz = stoppen." + ] + }, + "days": [ + { + "day": 1, + "focus": "Drücken + Beine", + "light": false, + "note": "", + "warmup": [ + "Handflächen-Wippen", + "Armkreisen/Wand-Slides", + "Hüftkreisen + Beinschwünge", + "Knie-über-Zeh an der Wand" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Türrahmen (Brust/Schulter, je 30–40s)", + "Trizeps (je 30s)", + "Seitheben/Lat-Seitdehnung (je 30s)", + "Quad im Stand (je 30s)", + "Knieender Hüftbeuger (je 30–40s)", + "Wade an der Wand (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Liegestütze", + "progression_id": "ls", + "cue": "Brust kontrolliert senken & drücken" + }, + { + "name": "Bank-Dips (Couch)", + "progression_id": "dips", + "cue": "Po vor der Couch senken, hochdrücken" + }, + { + "name": "Pike", + "progression_id": "pike", + "cue": "umgek. V, Kopf senken (Schultern)" + }, + { + "name": "Seitheben (KB/Rucksack)", + "progression_id": "lraise", + "cue": "Arme seitlich bis Schulterhöhe, kontrolliert" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "cue": "Last umarmen, tief" + }, + { + "name": "Ausfallschritt rückw.", + "progression_id": "lunge", + "cue": "großer Schritt, Knie Richtung Boden" + }, + { + "name": "Glute Bridge", + "progression_id": "bridge", + "cue": "Hüfte hochdrücken, Po fest" + }, + { + "name": "Wadenheben", + "progression_id": "calf", + "cue": "auf Zehenspitzen hoch/runter" + } + ] + } + ] + }, + { + "day": 2, + "focus": "Ziehen + Core", + "light": false, + "note": "", + "warmup": [ + "Handflächen-Wippen", + "Handtuch-Dislocates (8–10×)", + "Cat-Cow", + "Hüftkreisen + Beinschwünge" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Lat-/Seitdehnung (je 30s)", + "Türrahmen (je 30–40s)", + "BWS über die Couch (30–40s)", + "Cobra/Sphinx (Bauch, 30s)", + "Unterarm (je 20s)", + "Knieender Hüftbeuger (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts" + }, + { + "name": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "cue": "Oberarme fix, zu den Schultern" + }, + { + "name": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts" + }, + { + "name": "Superman / Y-T-W", + "progression_id": "sup", + "cue": "bäuchlings Arme+Beine heben" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Plank", + "progression_id": "plank", + "cue": "gerade Linie halten" + }, + { + "name": "Mountain Climbers", + "progression_id": "mtn", + "cue": "Knie zügig zur Brust" + }, + { + "name": "Dead Bug", + "progression_id": "db", + "cue": "gegengleich, Rücken am Boden" + }, + { + "name": "Beinheben", + "progression_id": "lr", + "cue": "Beine heben/senken" + } + ] + } + ] + }, + { + "day": 3, + "focus": "Beine + Core", + "light": false, + "note": "", + "warmup": [ + "Hüftkreisen + Beinschwünge", + "Knie-über-Zeh an der Wand", + "Cat-Cow", + "Handflächen-Wippen" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Hamstring-Dehnung (Couch, je 30–40s)", + "Quad im Stand (je 30s)", + "Knieender Hüftbeuger (je 30–40s)", + "Figure-4/Taube (je 30–40s)", + "Cobra + liegende Wirbelsäulen-Rotation (je 30s)", + "Wade an der Wand (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "cue": "Last umarmen, tief" + }, + { + "name": "RDL / Hip Hinge (KB/Rucksack)", + "progression_id": "load", + "cue": "Hüfte zurück, Rücken gerade, Hamstrings" + }, + { + "name": "Glute Bridge 1-beinig", + "progression_id": "bridge", + "cue": "ein Bein gestreckt" + }, + { + "name": "Wall Sit", + "progression_id": "wall", + "cue": "Rücken an die Wand, halten" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Hollow Hold", + "progression_id": "holl", + "cue": "unterer Rücken am Boden" + }, + { + "name": "Side Plank (Wechsel)", + "progression_id": "sp", + "cue": "seitlich auf einem Unterarm" + }, + { + "name": "Bicycle Crunch", + "progression_id": "bic", + "cue": "Ellbogen zum Gegenknie" + }, + { + "name": "Russian Twist (KB)", + "progression_id": "tw", + "cue": "sitzend Rumpf drehen" + } + ] + } + ] + }, + { + "day": 4, + "focus": "Oberkörper: Drücken + Ziehen", + "light": false, + "note": "", + "warmup": [ + "Handflächen-Wippen", + "Handtuch-Dislocates (8–10×)", + "Armkreisen/Wand-Slides", + "Cat-Cow" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Türrahmen (je 30–40s)", + "Trizeps (je 30s)", + "Lat-/Seitdehnung (je 30s)", + "BWS über die Couch (30–40s)", + "Unterarm (je 20s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Liegestütze", + "progression_id": "ls", + "cue": "Brust kontrolliert senken & drücken" + }, + { + "name": "Bank-Dips (Couch)", + "progression_id": "dips", + "cue": "Po vor der Couch senken" + }, + { + "name": "Pike", + "progression_id": "pike", + "cue": "umgek. V, Kopf senken" + }, + { + "name": "KB Floor Press", + "progression_id": "press", + "cue": "Glocke vom Boden weg drücken" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts" + }, + { + "name": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "cue": "Oberarme fix, zu den Schultern" + }, + { + "name": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts" + }, + { + "name": "Y-T-W / Superman", + "progression_id": "sup", + "cue": "bäuchlings, Rücken" + } + ] + } + ] + }, + { + "day": 5, + "focus": "Ganzkörper LEICHT + Mobility", + "light": true, + "note": "", + "warmup": [ + "Cat-Cow", + "Hüftkreisen + Beinschwünge", + "Armkreisen/Wand-Slides", + "Knie-über-Zeh an der Wand" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Türrahmen (je 30s)", + "Knieender Hüftbeuger (je 30s)", + "BWS über die Couch (30–40s)", + "Figure-4/Taube (je 30s)", + "Wade an der Wand (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Kniebeuge (leicht)", + "progression_id": "name:kniebeuge-leicht", + "cue": "locker, sauber" + }, + { + "name": "Liegestütze (1 Stufe leichter)", + "progression_id": "name:liegestuetze-1-stufe-leichter", + "cue": "leichtere Variante" + }, + { + "name": "Rudern (leicht)", + "progression_id": "name:rudern-leicht", + "cue": "wenig Gewicht" + }, + { + "name": "Mountain Climbers (locker)", + "progression_id": "name:mountain-climbers-locker", + "cue": "ruhiges Tempo" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Inchworm", + "progression_id": "name:inchworm", + "cue": "in den Stütz wandern, zurück" + }, + { + "name": "Cat-Cow", + "progression_id": "name:cat-cow", + "cue": "Rücken ründen/strecken" + }, + { + "name": "Bird Dog", + "progression_id": "name:bird-dog", + "cue": "gegengleich Arm+Bein, kurz halten" + }, + { + "name": "KB-Halo (leicht)", + "progression_id": "name:kb-halo-leicht", + "cue": "langsam um den Kopf kreisen" + } + ] + } + ] + }, + { + "day": 6, + "focus": "KETTLEBELL Ganzkörper", + "light": false, + "note": "Reine Kettlebell-Session. Swing = Hüft-Schnapp, kein Armheben. Glocke so wählen, dass alle Reps sauber sind.", + "warmup": [ + "Halo (leicht)", + "Hüftkreisen + Beinschwünge", + "Cat-Cow", + "Handflächen-Wippen" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Hamstring-Dehnung (Couch, je 30–40s)", + "Knieender Hüftbeuger (je 30–40s)", + "Figure-4/Taube (je 30–40s)", + "Unterarm (je 20s)", + "BWS über die Couch (30–40s)", + "Lat-/Seitdehnung (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "KB Swing (zweiarmig)", + "progression_id": "swing", + "cue": "Hüft-Schnapp, kein Armheben" + }, + { + "name": "Goblet Squat (KB)", + "progression_id": "gobletkb", + "cue": "Glocke vor der Brust, tief" + }, + { + "name": "KB-Deadlift (beidbeinig)", + "progression_id": "kbdl", + "cue": "Glocke zwischen den Füßen, Hüfte hinge" + }, + { + "name": "Halo", + "progression_id": "halo", + "cue": "eng um den Kopf kreisen" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "KB Floor Press", + "progression_id": "press", + "cue": "Glocke vom Boden weg drücken" + }, + { + "name": "Einarmiges Rudern (KB)", + "progression_id": "row1", + "cue": "Rücken gerade, zum Bauch" + }, + { + "name": "Bottoms-up KB-Hold (Griff)", + "progression_id": "carry", + "cue": "Glocke Boden nach oben, Unterarm senkrecht, still halten" + }, + { + "name": "Around-the-World + Uppercut (KB)", + "progression_id": "atw", + "cue": "Glocke um den Körper, dann diagonal zur Gegen-Schulter, andere Hand fängt" + } + ] + } + ] + } + ], + "phases": [ + { + "name": "Phase 1 · Fundament", + "instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "start_week": 1, + "end_week": 2 + }, + { + "name": "Phase 2 · Aufbau", + "instructions": "Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.", + "start_week": 3, + "end_week": 4 + }, + { + "name": "Phase 3 · Steigerung", + "instructions": "Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.", + "start_week": 5, + "end_week": 6 + }, + { + "name": "Phase 4 · Festigen", + "instructions": "Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.", + "start_week": 7, + "end_week": 8 + } + ], + "progression_stages": { + "atw": { + "name": "Around-the-World + Uppercut (KB)", + "steps": [ + "nur Around-the-World, leicht", + "Around-the-World, mehr Reps", + "+ Uppercut, leicht", + "+ Uppercut, flüssig" + ] + }, + "bic": { + "name": "Bicycle Crunch", + "steps": [ + "langsam", + "moderat", + "moderat", + "zügig" + ] + }, + "bridge": { + "name": "Glute Bridge", + "steps": [ + "beidbeinig", + "beidbeinig, 2s halten", + "einbeinig", + "einbeinig, 2s halten" + ] + }, + "calf": { + "name": "Wadenheben", + "steps": [ + "Körpergewicht", + "langsam", + "langsam, mehr Reps", + "1-beinig" + ] + }, + "carry": { + "name": "Bottoms-up KB-Hold (Griff)", + "steps": [ + "im Stand 10–15s", + "15–20s, ruhig", + "bis Schulterhöhe 15s", + "länger / ein paar Schritte" + ] + }, + "db": { + "name": "Dead Bug", + "steps": [ + "Arme ODER Beine einzeln", + "gegengleich", + "gegengleich + Pause", + "Beine weiter gestreckt" + ] + }, + "dips": { + "name": "Bank-Dips (Couch)", + "steps": [ + "Knie stark gebeugt, klein", + "Knie gebeugt, voll", + "Füße etwas weiter", + "Füße weiter weg" + ] + }, + "gobletkb": { + "name": "Goblet Squat (KB/Rucksack)", + "steps": [ + "leicht, Tiefe lernen", + "leicht, mehr Reps", + "mittel", + "mittel, 3s runter" + ] + }, + "halo": { + "name": "Halo", + "steps": [ + "leicht, langsam", + "leicht", + "mittel", + "mittel, langsam" + ] + }, + "holl": { + "name": "Hollow Hold", + "steps": [ + "Tuck (Knie an)", + "Tuck, länger", + "halb gestreckt", + "halb/voll" + ] + }, + "kbdl": { + "name": "KB-Deadlift (beidbeinig)", + "steps": [ + "leicht, Technik", + "leicht, mehr Reps", + "mittel", + "schwerer, kontrolliert" + ] + }, + "load": { + "name": "Bizeps-Curls (KB/Rucksack)", + "steps": [ + "leicht", + "etwas mehr", + "mehr", + "fordernd" + ] + }, + "lr": { + "name": "Beinheben", + "steps": [ + "Knie gebeugt", + "Knie gebeugt, langsam", + "halb gestreckt", + "gestreckt" + ] + }, + "lraise": { + "name": "Seitheben (KB/Rucksack)", + "steps": [ + "leicht (Technik)", + "leicht, mehr Reps", + "mittel", + "mittel, langsam" + ] + }, + "ls": { + "name": "Liegestütze", + "steps": [ + "Inkline (hoch)", + "Inkline tiefer / Knie", + "Knie / Negativ", + "Negativ + erste Volle" + ] + }, + "lseng": { + "name": "Inkline (hoch)", + "steps": [ + "Inkline (hoch)", + "Inkline mittel", + "Inkline tief", + "Knie-Liegestütze" + ] + }, + "lunge": { + "name": "Ausfallschritt rückw.", + "steps": [ + "Körpergewicht (ggf. festhalten)", + "Körpergewicht", + "mehr Reps", + "+ Last leicht" + ] + }, + "mtn": { + "name": "Mountain Climbers", + "steps": [ + "moderat", + "moderat", + "zügig", + "zügig" + ] + }, + "pike": { + "name": "Pike", + "steps": [ + "Pike Hold", + "Hold + kleine Senkung", + "Push-up Hände erhöht", + "Push-up Boden, klein" + ] + }, + "plank": { + "name": "Plank", + "steps": [ + "auf Knien 15–20 s", + "voll 20–30 s", + "voll + Schulter-Taps", + "voll, länger" + ] + }, + "press": { + "name": "KB Floor Press", + "steps": [ + "leicht (Floor Press)", + "Floor Press mehr", + "Overhead leicht", + "Overhead mittel" + ] + }, + "row1": { + "name": "Einarmiges Rudern (KB)", + "steps": [ + "leicht", + "etwas mehr", + "einarmig leicht", + "einarmig mittel" + ] + }, + "row2": { + "name": "Einarm-Rudern BREIT (KB)", + "steps": [ + "leicht (Technik)", + "leicht, mehr Reps", + "mittel", + "mittel, Pause oben" + ] + }, + "rowe": { + "name": "Einarm-Rudern ENG (KB)", + "steps": [ + "leicht", + "etwas mehr", + "mehr", + "fordernd" + ] + }, + "sp": { + "name": "Side Plank (Wechsel)", + "steps": [ + "auf Knien", + "auf Knien, länger", + "voll", + "voll, länger" + ] + }, + "squat": { + "name": "Körpergewicht", + "steps": [ + "Körpergewicht", + "Körpergewicht, mehr Reps", + "Goblet leicht", + "Goblet mittel" + ] + }, + "sup": { + "name": "Superman / Y-T-W", + "steps": [ + "Superman Hold", + "Superman Hold länger", + "Reverse Snow Angels", + "Y-T-W Raises" + ] + }, + "swing": { + "name": "KB Swing (zweiarmig)", + "steps": [ + "Dead-Stop, leicht (Technik)", + "Dead-Stop, mehr Reps", + "durchgehend, mittel", + "durchgehend, schwerer" + ] + }, + "tsquat": { + "name": "3s runter", + "steps": [ + "3s runter", + "3s + Pause unten", + "Goblet 3s", + "Goblet 3s + Pause" + ] + }, + "tw": { + "name": "Russian Twist (KB)", + "steps": [ + "ohne Gewicht", + "ohne Gewicht", + "KB leicht", + "KB" + ] + }, + "wall": { + "name": "Wall Sit", + "steps": [ + "15–25 s", + "25–40 s", + "40–55 s", + "55–70 s" + ] + } + } + }, + "weekly_analyses": [ + { + "week": 1, + "source_hash": "815dc58aad775b90e6302fb9a687d2c80c8c76f19b9008d0072953d1a160dee8", + "headline": "Woche 1: starker Einstieg, Progression vorsichtig", + "summary": "Die Woche wurde sehr konsequent gestartet; effektiv sind 5 von 6 geplanten Trainingstagen mit Übungen dokumentiert. Die meisten Übungen liegen sinnvoll auf den vorgesehenen Anfänger-Stufen der Phase 1. Erste stabile Muster zeigen sich bei Pike Holds, Rudern und Core-Holds, während Liegestütze noch klar auf Konsolidierung statt Progression stehen. Für Woche 2 sollten nur planverträgliche Variantenwechsel genutzt werden, keine längeren Arbeitsintervalle.", + "metrics": [ + { + "label": "Durchführung", + "value": "System 6/6, effektiv 5/6", + "detail": "Tag 5 hat die Notiz „Nicht durchgeführt“ und keine erledigten Übungen." + }, + { + "label": "Dokumentationsumfang", + "value": "36 dokumentierte Übungen", + "detail": "Genug Daten für erste Übungstendenzen, aber noch keine langfristigen Trends." + }, + { + "label": "Tabata-Format", + "value": "20/10 beibehalten", + "detail": "Die dokumentierten Holds bleiben im festen 20-Sekunden-Arbeitsintervall." + }, + { + "label": "Belastungsniveau", + "value": "überwiegend Startstufen", + "detail": "Passt zur Phase 1 mit Technikfokus und leichter Last." + } + ], + "exercise_updates": [ + { + "name": "Liegestütze", + "trend": "stable", + "current_level": "Inkline hoch", + "evidence": "Tag 1: 8/7/7/7/6/6/5; Tag 4: 8/7/7/6/6/6", + "next_action": "Stufe beibehalten und sauberen ROM sowie gleichmäßigere Wiederholungen priorisieren.", + "criterion": "Erst auf Inkline tiefer/Knie wechseln, wenn 2 Sessions in Folge alle vorgesehenen 20s-Intervalle sauber mit stabiler Körperspannung geschafft werden." + }, + { + "name": "Pike", + "trend": "up", + "current_level": "Pike Hold", + "evidence": "Tag 1 und Tag 4 jeweils 6x20 s dokumentiert.", + "next_action": "Optional nächste planmäßige Stufe „Hold + kleine Senkung“ innerhalb der 20s testen.", + "criterion": "Nur übernehmen, wenn 6/6 Intervalle ohne Hüftabsacken und ohne Technikverlust sauber bleiben." + }, + { + "name": "Einarm-Rudern ENG (KB)", + "trend": "up", + "current_level": "leicht, 4 kg", + "evidence": "Tag 2: 4kg, 9/9/8/8/8/9; Tag 4: 4kg, 9/9/9/8/8/9", + "next_action": "Bei sauberem Zug zur Hüfte planverträglich auf „etwas mehr“ bzw. leicht höhere Last wechseln.", + "criterion": "2 Sessions in Folge alle dokumentierten Intervalle je Seite kontrolliert mit mindestens ähnlicher Wiederholungszahl abschließen." + }, + { + "name": "KB Swing (zweiarmig)", + "trend": "unclear", + "current_level": "Dead-Stop, leicht; 10 kg", + "evidence": "Tag 6: 10kg, 15 dokumentiert.", + "next_action": "Dead-Stop-Technik beibehalten und pro 20s-Intervall genauer dokumentieren.", + "criterion": "Erst steigern, wenn alle Swing-Intervalle in 2 Sessions sauber als Hüft-Schnapp ohne Armheben dokumentiert sind." + } + ], + "plan_adjustments": [ + { + "target": "Woche 2 / Pike", + "suggested_change": "Progressionsstufe von „Pike Hold“ auf „Hold + kleine Senkung“ setzen.", + "reason": "Zwei dokumentierte Einheiten mit 6x20 s Pike Hold sprechen für einen vorsichtigen Variantenwechsel.", + "manual_step": "Im Trainingsplan-Modul bei Pike die nächste Progressionsstufe auswählen.", + "condition": "Nur wenn beide Pike-Einheiten technisch sauber waren; sonst Pike Hold beibehalten." + }, + { + "target": "Woche 2 / Einarm-Rudern ENG", + "suggested_change": "Von „leicht“ auf „etwas mehr“ wechseln, z.B. etwas höhere Last oder kontrollierterer Zug bei gleicher Tabata-Struktur.", + "reason": "Die Wiederholungen lagen in beiden Einheiten stabil bei etwa 8–9 pro Intervall.", + "manual_step": "Im Trainingsplan-Modul die nächste Rudern-ENG-Progression bzw. passende Last eintragen.", + "condition": "Nur wenn Rückenposition und Zugweg sauber bleiben; sonst 4 kg beibehalten." + }, + { + "target": "Woche 2 / Tag 5 leicht", + "suggested_change": "Tag 5 entweder wirklich leicht durchführen oder klar als nicht durchgeführt/Ruhe dokumentieren.", + "reason": "Diese Woche ist Tag 5 als completed markiert, enthält aber keine Übungen.", + "manual_step": "Im Trainingsplan-Modul den Status/Notiz für Tag 5 konsistent setzen.", + "condition": "Nur relevant, wenn Tag 5 erneut ausfällt; sonst normalen leichten Mobility-Tag beibehalten." + } + ], + "warnings": [ + "Tag 5 ist als abgeschlossen markiert, obwohl „Nicht durchgeführt“ notiert ist und alle Übungen offen sind.", + "Beim KB Swing reicht die Angabe „10kg, 15“ noch nicht, um Intervallqualität oder Technikprogression sicher zu bewerten." + ] + } + ], + "local_aggregates": { + "coverage_by_week": [ + { + "week": 1, + "sessions_with_data": 6, + "completed_sessions": 6, + "planned_sessions": 6, + "completion_rate_pct": 100, + "documented_exercises": 36 + } + ], + "overall": { + "sessions_with_data": 6, + "completed_sessions": 6, + "planned_sessions": 6, + "completion_rate_pct": 100, + "documented_exercises": 36 + }, + "current_progressions": [ + { + "progression_id": "ls", + "exercise": "Liegestütze", + "progression": "Inkline (hoch)", + "result": "8/7/7/6/6/6", + "note": "", + "session_key": "w01-d04" + }, + { + "progression_id": "dips", + "exercise": "Bank-Dips (Couch)", + "progression": "Knie stark gebeugt, klein", + "result": "8/7/7/7/6/6", + "note": "", + "session_key": "w01-d04" + }, + { + "progression_id": "pike", + "exercise": "Pike", + "progression": "Pike Hold", + "result": "6x20 s", + "note": "", + "session_key": "w01-d04" + }, + { + "progression_id": "lraise", + "exercise": "Seitheben (KB/Rucksack)", + "progression": "leicht (Technik)", + "result": "4kg, 6/6/5 je Seite", + "note": "", + "session_key": "w01-d01" + }, + { + "progression_id": "gobletkb", + "exercise": "Goblet Squat (KB)", + "progression": "leicht, Tiefe lernen", + "result": "8kg, 6/6/7/8/6/6", + "note": "", + "session_key": "w01-d06" + }, + { + "progression_id": "lunge", + "exercise": "Ausfallschritt rückw.", + "progression": "Körpergewicht (ggf. festhalten)", + "result": "3x4 je Seite", + "note": "", + "session_key": "w01-d01" + }, + { + "progression_id": "bridge", + "exercise": "Glute Bridge 1-beinig", + "progression": "beidbeinig", + "result": "6x20 s", + "note": "", + "session_key": "w01-d03" + }, + { + "progression_id": "calf", + "exercise": "Wadenheben", + "progression": "Körpergewicht", + "result": "10kg, 10/10/10/10/10/10", + "note": "", + "session_key": "w01-d01" + }, + { + "progression_id": "rowe", + "exercise": "Einarm-Rudern ENG (KB)", + "progression": "leicht", + "result": "4kg, 9/9/9/8/8/9", + "note": "", + "session_key": "w01-d04" + }, + { + "progression_id": "load", + "exercise": "Bizeps-Curls (KB/Rucksack)", + "progression": "leicht", + "result": "8kg, 7/7/7/7/7/8", + "note": "", + "session_key": "w01-d04" + }, + { + "progression_id": "row2", + "exercise": "Einarm-Rudern BREIT (KB)", + "progression": "leicht (Technik)", + "result": "4kg, 7/7/6/7/7/8", + "note": "", + "session_key": "w01-d04" + }, + { + "progression_id": "sup", + "exercise": "Y-T-W / Superman", + "progression": "Superman Hold", + "result": "6x20 s", + "note": "", + "session_key": "w01-d04" + }, + { + "progression_id": "plank", + "exercise": "Plank", + "progression": "auf Knien 15–20 s", + "result": "6x20 s", + "note": "", + "session_key": "w01-d02" + }, + { + "progression_id": "mtn", + "exercise": "Mountain Climbers", + "progression": "moderat", + "result": "6x20 s", + "note": "", + "session_key": "w01-d02" + }, + { + "progression_id": "db", + "exercise": "Dead Bug", + "progression": "Arme ODER Beine einzeln", + "result": "6x20 s", + "note": "", + "session_key": "w01-d02" + }, + { + "progression_id": "lr", + "exercise": "Beinheben", + "progression": "Knie gebeugt", + "result": "5/5/4/4/4/3", + "note": "", + "session_key": "w01-d02" + }, + { + "progression_id": "wall", + "exercise": "Wall Sit", + "progression": "15–25 s", + "result": "6x20 s", + "note": "", + "session_key": "w01-d03" + }, + { + "progression_id": "holl", + "exercise": "Hollow Hold", + "progression": "Tuck (Knie an)", + "result": "6x20 s", + "note": "", + "session_key": "w01-d03" + }, + { + "progression_id": "sp", + "exercise": "Side Plank (Wechsel)", + "progression": "auf Knien", + "result": "6x20s", + "note": "", + "session_key": "w01-d03" + }, + { + "progression_id": "bic", + "exercise": "Bicycle Crunch", + "progression": "langsam", + "result": "6x20s", + "note": "", + "session_key": "w01-d03" + }, + { + "progression_id": "tw", + "exercise": "Russian Twist (KB)", + "progression": "ohne Gewicht", + "result": "4kg, 4/4/4 je Seite", + "note": "", + "session_key": "w01-d03" + }, + { + "progression_id": "press", + "exercise": "KB Floor Press", + "progression": "leicht (Floor Press)", + "result": "10kg, 7/7/8/6/5/6", + "note": "", + "session_key": "w01-d06" + }, + { + "progression_id": "name:kniebeuge-leicht", + "exercise": "Kniebeuge (leicht)", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "name:liegestuetze-1-stufe-leichter", + "exercise": "Liegestütze (1 Stufe leichter)", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "name:rudern-leicht", + "exercise": "Rudern (leicht)", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "name:mountain-climbers-locker", + "exercise": "Mountain Climbers (locker)", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "name:inchworm", + "exercise": "Inchworm", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "name:cat-cow", + "exercise": "Cat-Cow", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "name:bird-dog", + "exercise": "Bird Dog", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "name:kb-halo-leicht", + "exercise": "KB-Halo (leicht)", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "swing", + "exercise": "KB Swing (zweiarmig)", + "progression": "Dead-Stop, leicht (Technik)", + "result": "10kg, 15", + "note": "", + "session_key": "w01-d06" + }, + { + "progression_id": "kbdl", + "exercise": "KB-Deadlift (beidbeinig)", + "progression": "leicht, Technik", + "result": "10kg, 5/5/6/6/6/5", + "note": "", + "session_key": "w01-d06" + }, + { + "progression_id": "halo", + "exercise": "Halo", + "progression": "leicht, langsam", + "result": "4kg, 5/4/4 je Seite", + "note": "", + "session_key": "w01-d06" + }, + { + "progression_id": "row1", + "exercise": "Einarmiges Rudern (KB)", + "progression": "leicht", + "result": "6kg, 6/7/8 je Seite", + "note": "", + "session_key": "w01-d06" + }, + { + "progression_id": "carry", + "exercise": "Bottoms-up KB-Hold (Griff)", + "progression": "im Stand 10–15s", + "result": "4kg, 3x20 s je Seite", + "note": "", + "session_key": "w01-d06" + }, + { + "progression_id": "atw", + "exercise": "Around-the-World + Uppercut (KB)", + "progression": "nur Around-the-World, leicht", + "result": "4kg, 5/5/5 je Seite", + "note": "", + "session_key": "w01-d06" + } + ] + } + } + }, + "response": { + "headline": "Woche 1: konsequenter Start, Progression vorsichtig", + "summary": "Der 8-Wochen-Plan wurde in Woche 1 breit dokumentiert und überwiegend passend zur Fundament-Phase umgesetzt. Effektiv sind 5 von 6 geplanten Trainingstagen mit Leistungsdaten belegt, da Tag 5 zwar als abgeschlossen erscheint, aber ohne Übungen dokumentiert ist. Mehrere Holds und Zugübungen wirken stabil, während Liegestütze und Swing-Technik noch klar konsolidiert werden sollten. Das feste Tabata-Format 20/10 bleibt unverändert; Progression sollte nur über Varianten, Last, ROM, Tempo oder Technik erfolgen.", + "data_quality": "Die Aussagekraft ist für Woche 1 solide, aber wegen nur einer Trainingswoche und unvollständigem Tag 5 noch begrenzt.", + "metrics": [ + { + "label": "Effektive Durchführung", + "value": "5/6 Trainingstage mit Übungsdaten", + "detail": "Tag 5 ist ohne erledigte Übungen dokumentiert." + }, + { + "label": "Dokumentationsumfang", + "value": "36 dokumentierte Übungen", + "detail": "Genug für erste Tendenzen, noch nicht für langfristige Trends." + }, + { + "label": "Format-Treue", + "value": "Tabata 20/10", + "detail": "Die dokumentierten Holds und Intervalle passen zum festen Arbeitsintervall." + }, + { + "label": "Aktuelle Phase", + "value": "Phase 1 · Fundament", + "detail": "Startstufen und Technikfokus passen zum Planrahmen." + } + ], + "exercise_updates": [ + { + "name": "Liegestütze", + "trend": "stable", + "current_level": "Inkline hoch", + "evidence": "Tag 4: 8/7/7/6/6/6 nach Tag 1: 8/7/7/7/6/6/5.", + "next_action": "Stufe beibehalten und auf sauberen ROM sowie stabile Körperspannung in jedem 20s-Intervall achten.", + "criterion": "Erst auf Inkline tiefer/Knie wechseln, wenn 2 Sessions in Folge alle vorgesehenen Intervalle sauber über 20s gelingen." + }, + { + "name": "Pike", + "trend": "up", + "current_level": "Pike Hold", + "evidence": "Tag 1 und Tag 4 jeweils 6x20 s dokumentiert.", + "next_action": "Optional planmäßig „Hold + kleine Senkung“ innerhalb der 20s testen.", + "criterion": "Nur übernehmen, wenn 6/6 Intervalle ohne Hüftabsacken und ohne Technikverlust sauber bleiben." + }, + { + "name": "Einarm-Rudern ENG (KB)", + "trend": "up", + "current_level": "leicht, 4 kg", + "evidence": "Tag 4: 4kg, 9/9/9/8/8/9 nach Tag 2: 4kg, 9/9/8/8/8/9.", + "next_action": "Bei sauberem Zug zur Hüfte optional auf „etwas mehr“ bzw. leicht höhere Last wechseln.", + "criterion": "2 Sessions in Folge je Seite kontrolliert arbeiten und mindestens ähnliche Wiederholungszahlen halten." + }, + { + "name": "KB Swing (zweiarmig)", + "trend": "unclear", + "current_level": "Dead-Stop, leicht; 10 kg", + "evidence": "Tag 6: 10kg, 15 dokumentiert.", + "next_action": "Dead-Stop-Technik beibehalten und pro 20s-Intervall genauer dokumentieren.", + "criterion": "Erst steigern, wenn alle Swing-Intervalle in 2 Sessions sauber als Hüft-Schnapp ohne Armheben dokumentiert sind." + } + ], + "plan_adjustments": [ + { + "target": "Woche 2 / Pike", + "suggested_change": "Progressionsstufe von „Pike Hold“ auf „Hold + kleine Senkung“ setzen.", + "reason": "Zwei Einheiten mit 6x20 s Pike Hold sprechen für einen vorsichtigen Variantenwechsel.", + "manual_step": "Im Trainingsplan-Modul bei Pike die nächste Progressionsstufe auswählen.", + "condition": "Nur wenn beide Pike-Einheiten technisch sauber waren; sonst Pike Hold beibehalten." + }, + { + "target": "Woche 2 / Einarm-Rudern ENG", + "suggested_change": "Von „leicht“ auf „etwas mehr“ wechseln, z.B. leicht höhere Last oder kontrollierterer Zug bei gleicher Tabata-Struktur.", + "reason": "Die Wiederholungen lagen in beiden Einheiten stabil bei etwa 8–9 pro Intervall.", + "manual_step": "Im Trainingsplan-Modul die nächste Rudern-ENG-Progression oder passende Last eintragen.", + "condition": "Nur wenn Rückenposition und Zugweg sauber bleiben; sonst 4 kg beibehalten." + }, + { + "target": "Woche 2 / Tag 5 leicht", + "suggested_change": "Tag 5 entweder als leichten Mobility-Tag durchführen oder klar als Ruhetag/nicht durchgeführt dokumentieren.", + "reason": "In Woche 1 ist Tag 5 als completed markiert, enthält aber keine Übungsdaten.", + "manual_step": "Im Trainingsplan-Modul Status und Notiz für Tag 5 konsistent setzen.", + "condition": "Nur relevant, wenn Tag 5 erneut ausfällt; sonst den normalen leichten Tag beibehalten." + } + ], + "warnings": [ + "Tag 5 ist als abgeschlossen markiert, obwohl „Nicht durchgeführt“ notiert ist und keine Übungen erledigt sind.", + "Beim KB Swing reicht die Angabe „10kg, 15“ noch nicht, um Intervallqualität oder Technikprogression sicher zu bewerten." + ], + "conclusion": "Für Woche 2: solide weitermachen, nur einzelne saubere Progressionen testen und das 20/10-Format unverändert lassen." + }, + "visuals": { + "charts": [ + { + "title": "Liegestütze · Verlauf", + "type": "line", + "unit": "Reps", + "labels": [ + "W1/T1", + "W1/T4" + ], + "series": [ + { + "name": "Liegestütze", + "values": [ + 6.57, + 6.67 + ] + } + ], + "insight": "Aus den dokumentierten Ergebnisfeldern berechnet." + }, + { + "title": "Bank-Dips (Couch) · Verlauf", + "type": "line", + "unit": "Reps", + "labels": [ + "W1/T1", + "W1/T4" + ], + "series": [ + { + "name": "Bank-Dips (Couch)", + "values": [ + 6.83, + 6.83 + ] + } + ], + "insight": "Aus den dokumentierten Ergebnisfeldern berechnet." + } + ], + "flowcharts": [ + { + "title": "Liegestütze", + "nodes": [ + { + "id": "n1", + "label": "Inkline (hoch)", + "status": "current" + }, + { + "id": "n2", + "label": "Inkline tiefer / Knie", + "status": "next" + }, + { + "id": "n3", + "label": "Knie / Negativ", + "status": "" + }, + { + "id": "n4", + "label": "Negativ + erste Volle", + "status": "" + } + ], + "edges": [ + { + "from": "n1", + "to": "n2" + }, + { + "from": "n2", + "to": "n3" + }, + { + "from": "n3", + "to": "n4" + } + ], + "insight": "Dokumentierter Stand: Inkline (hoch)" + }, + { + "title": "Bank-Dips (Couch)", + "nodes": [ + { + "id": "n1", + "label": "Knie stark gebeugt, klein", + "status": "current" + }, + { + "id": "n2", + "label": "Knie gebeugt, voll", + "status": "next" + }, + { + "id": "n3", + "label": "Füße etwas weiter", + "status": "" + }, + { + "id": "n4", + "label": "Füße weiter weg", + "status": "" + } + ], + "edges": [ + { + "from": "n1", + "to": "n2" + }, + { + "from": "n2", + "to": "n3" + }, + { + "from": "n3", + "to": "n4" + } + ], + "insight": "Dokumentierter Stand: Knie stark gebeugt, klein" + }, + { + "title": "Pike", + "nodes": [ + { + "id": "n1", + "label": "Pike Hold", + "status": "current" + }, + { + "id": "n2", + "label": "Hold + kleine Senkung", + "status": "next" + }, + { + "id": "n3", + "label": "Push-up Hände erhöht", + "status": "" + }, + { + "id": "n4", + "label": "Push-up Boden, klein", + "status": "" + } + ], + "edges": [ + { + "from": "n1", + "to": "n2" + }, + { + "from": "n2", + "to": "n3" + }, + { + "from": "n3", + "to": "n4" + } + ], + "insight": "Dokumentierter Stand: Pike Hold" + } + ] + }, + "version": 1, + "status": "success", + "source_file": "phase-1.json", + "archive_file": "overall.json", + "sample_note": "Beispieldatensatz für die Android-Implementierung; in Version 2.0 wird diese Datei bei einer Neuanalyse überschrieben." +} diff --git a/android/app/src/test/resources/samples/analyses/phase-1/week-01.json b/android/app/src/test/resources/samples/analyses/phase-1/week-01.json new file mode 100644 index 0000000..5e4b02a --- /dev/null +++ b/android/app/src/test/resources/samples/analyses/phase-1/week-01.json @@ -0,0 +1,2493 @@ +{ + "id": "week-01_20260723T120111028996Z", + "type": "week", + "week": 1, + "created_at": "2026-07-23T12:01:11+00:00", + "model": "gpt-5.5", + "source_hash": "815dc58aad775b90e6302fb9a687d2c80c8c76f19b9008d0072953d1a160dee8", + "sessions_considered": 6, + "request": { + "scope": "week", + "schema": { + "headline": "Kurzer Titel", + "summary": "Kompakte Einordnung in höchstens 4 Sätzen", + "data_quality": "Ein kurzer Satz zur Aussagekraft", + "metrics": [ + { + "label": "Kennzahl", + "value": "Wert", + "detail": "kurze Einordnung" + } + ], + "exercise_updates": [ + { + "name": "Übung", + "trend": "up|stable|down|unclear", + "current_level": "dokumentierter Stand", + "evidence": "ein konkreter Beleg", + "next_action": "nächster planverträglicher Schritt", + "criterion": "messbares Kriterium" + } + ], + "plan_adjustments": [ + { + "target": "Woche/Tag/Übung oder Block", + "suggested_change": "konkreter Vorschlag", + "reason": "kurze Begründung", + "manual_step": "was im Trainingsplan-Modul manuell zu ändern wäre", + "condition": "nur wenn/sonst beibehalten" + } + ], + "warnings": [ + "nur echte Auffälligkeiten" + ], + "conclusion": "ein knapper Abschlusssatz" + }, + "messages": [ + { + "role": "system", + "content": "Du analysierst ein Trainingsprotokoll auf Deutsch. Der übergebene Trainingsplan ist bindend und muss die Empfehlungen bestimmen. Verändere niemals eigenmächtig Sessionformat, Arbeitsintervall, Pausen, Rundenzahl, Satzanzahl oder Progressionslogik. Bei Tabata oder anderen festen Intervallen darfst du insbesondere keine längeren Holds oder längere Arbeitszeiten empfehlen. Progression erfolgt dann nur planverträglich, etwa über die vorgesehene schwierigere Variante, Gewicht, ROM, Tempo oder Technik. Nutze ausschließlich die Daten im Input, erfinde keine Leistungen und gib keine medizinische Diagnose. Die Analyse soll kompakt bleiben: maximal 4 Kennzahlen, 4 Übungsupdates, 3 Plananpassungen und 2 Warnungen. Plananpassungen sind nur Vorschläge zur manuellen Übernahme im Trainingsplan-Modul; behaupte nie, den Plan geändert zu haben. Bewerte genau diese Planwoche und formuliere höchstens drei konkrete, optionale Anpassungen für die nächste Woche. Antworte ausschließlich als JSON im vorgegebenen Schema." + }, + { + "role": "user", + "content": "Gewünschtes JSON-Schema:\n{\"headline\": \"Kurzer Titel\", \"summary\": \"Kompakte Einordnung in höchstens 4 Sätzen\", \"data_quality\": \"Ein kurzer Satz zur Aussagekraft\", \"metrics\": [{\"label\": \"Kennzahl\", \"value\": \"Wert\", \"detail\": \"kurze Einordnung\"}], \"exercise_updates\": [{\"name\": \"Übung\", \"trend\": \"up|stable|down|unclear\", \"current_level\": \"dokumentierter Stand\", \"evidence\": \"ein konkreter Beleg\", \"next_action\": \"nächster planverträglicher Schritt\", \"criterion\": \"messbares Kriterium\"}], \"plan_adjustments\": [{\"target\": \"Woche/Tag/Übung oder Block\", \"suggested_change\": \"konkreter Vorschlag\", \"reason\": \"kurze Begründung\", \"manual_step\": \"was im Trainingsplan-Modul manuell zu ändern wäre\", \"condition\": \"nur wenn/sonst beibehalten\"}], \"warnings\": [\"nur echte Auffälligkeiten\"], \"conclusion\": \"ein knapper Abschlusssatz\"}\n\nAnalyse-Datensatz:\n{\"analysis_type\": \"week\", \"week\": 1, \"plan\": {\"title\": \"Calisthenics-Basis – 8-Wochen Komplettplan\", \"subtitle\": \"Blutiger Anfänger · 1,90 m · 95 kg · Rucksack + Couch + Kettlebell · tägliche ~30-Min Tabata-Session\", \"weeks\": 8, \"selected_week\": 1, \"selected_phase\": {\"name\": \"Phase 1 · Fundament\", \"instructions\": \"Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.\"}, \"training_format\": {\"mode\": \"tabata\", \"is_tabata\": true, \"fixed_interval\": true, \"work_seconds\": 20, \"rest_seconds\": 10, \"rounds\": 8, \"session_method\": \"Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.\", \"timer_note\": \"Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.\", \"constraints\": [\"Arbeitsintervalle bleiben fest bei 20 Sekunden; keine längeren Holds oder verlängerten Arbeitsintervalle empfehlen.\", \"Progression nur innerhalb des Planformats: planmäßige schwierigere Variante, mehr Gewicht, saubererer ROM/Tempo oder bessere Technik; Pausen und Rundenzahl nicht eigenmächtig ändern.\"]}, \"guidance\": {\"goals\": \"Du startest bei 2–3 Liegestützen und 0 Dips. Realistisch und ehrlich nach 8 Wochen sauberer Arbeit:\\n•  Liegestütze: von 2–3 auf erste saubere volle Reps (Rest in Knie-/Negativ-Form).\\n•  Bank-Dips: saubere Reps mit Füßen etwas weiter weg.   Pike Push-up: am Boden mit kleinem ROM.\\n•  Plank ~45–60 s, Wall Sit ~60 s, viele saubere Goblet-Kniebeugen, sauberer Swing + spürbar bessere Griffkraft.\\n•  Noch NICHT dran: Diamond Push-ups, L-Sit, Handstand, schwerer Turkish Get-Up, Snatch – Stoff für Woche 12+.\", \"goals_note\": \"Wichtig: Die Wochen-Stufen sind ein Rahmen, kein Muss. Bleib auf einer Stufe, solange sie fordert. Aufstiegs-Regel: erst hoch (oder schwerere Glocke), wenn du eine Übung 2 Sessions in Folge in allen Sätzen sauber über die vollen 20s schaffst. KB = Kettlebell bevorzugt; Rucksack bleibt als Alternative.\", \"session_method\": \"Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.\", \"timer_note\": \"Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.\", \"reminders\": [\"Form vor Reps. Wähle die Stufe / Glocke, bei der du die 20s kontrolliert durcharbeitest – mit 1–2 Reps Reserve.\", \"Swing-Technik first. Der Swing ist ein Hüft-Schnapp, KEIN Kniebeuge und KEIN Armheben. Erst Dead-Stop-Swings, bis die Hüftstreckung sitzt.\", \"Einseitige Übungen (Tag 2): Durchgang 1 = links, Durchgang 2 = rechts – so ist jede Seite automatisch ausbalanciert.\", \"Täglich ist viel. Tag 5 leicht, Tag 7 frei. Bei starkem Muskelkater einen Extra-Ruhetag – gehört zum Aufbau.\", \"Schlaf & Eiweiß. 7–9 h Schlaf, ~1,6 g Eiweiß pro kg. Stechender Schmerz = stoppen.\"]}, \"days\": [{\"day\": 1, \"focus\": \"Drücken + Beine\", \"light\": false, \"note\": \"\", \"warmup\": [\"Handflächen-Wippen\", \"Armkreisen/Wand-Slides\", \"Hüftkreisen + Beinschwünge\", \"Knie-über-Zeh an der Wand\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Türrahmen (Brust/Schulter, je 30–40s)\", \"Trizeps (je 30s)\", \"Seitheben/Lat-Seitdehnung (je 30s)\", \"Quad im Stand (je 30s)\", \"Knieender Hüftbeuger (je 30–40s)\", \"Wade an der Wand (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Liegestütze\", \"progression_id\": \"ls\", \"cue\": \"Brust kontrolliert senken & drücken\"}, {\"name\": \"Bank-Dips (Couch)\", \"progression_id\": \"dips\", \"cue\": \"Po vor der Couch senken, hochdrücken\"}, {\"name\": \"Pike\", \"progression_id\": \"pike\", \"cue\": \"umgek. V, Kopf senken (Schultern)\"}, {\"name\": \"Seitheben (KB/Rucksack)\", \"progression_id\": \"lraise\", \"cue\": \"Arme seitlich bis Schulterhöhe, kontrolliert\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Goblet Squat (KB/Rucksack)\", \"progression_id\": \"gobletkb\", \"cue\": \"Last umarmen, tief\"}, {\"name\": \"Ausfallschritt rückw.\", \"progression_id\": \"lunge\", \"cue\": \"großer Schritt, Knie Richtung Boden\"}, {\"name\": \"Glute Bridge\", \"progression_id\": \"bridge\", \"cue\": \"Hüfte hochdrücken, Po fest\"}, {\"name\": \"Wadenheben\", \"progression_id\": \"calf\", \"cue\": \"auf Zehenspitzen hoch/runter\"}]}]}, {\"day\": 2, \"focus\": \"Ziehen + Core\", \"light\": false, \"note\": \"\", \"warmup\": [\"Handflächen-Wippen\", \"Handtuch-Dislocates (8–10×)\", \"Cat-Cow\", \"Hüftkreisen + Beinschwünge\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Lat-/Seitdehnung (je 30s)\", \"Türrahmen (je 30–40s)\", \"BWS über die Couch (30–40s)\", \"Cobra/Sphinx (Bauch, 30s)\", \"Unterarm (je 20s)\", \"Knieender Hüftbeuger (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Einarm-Rudern ENG (KB)\", \"progression_id\": \"rowe\", \"cue\": \"1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts\"}, {\"name\": \"Bizeps-Curls (KB/Rucksack)\", \"progression_id\": \"load\", \"cue\": \"Oberarme fix, zu den Schultern\"}, {\"name\": \"Einarm-Rudern BREIT (KB)\", \"progression_id\": \"row2\", \"cue\": \"1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts\"}, {\"name\": \"Superman / Y-T-W\", \"progression_id\": \"sup\", \"cue\": \"bäuchlings Arme+Beine heben\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Plank\", \"progression_id\": \"plank\", \"cue\": \"gerade Linie halten\"}, {\"name\": \"Mountain Climbers\", \"progression_id\": \"mtn\", \"cue\": \"Knie zügig zur Brust\"}, {\"name\": \"Dead Bug\", \"progression_id\": \"db\", \"cue\": \"gegengleich, Rücken am Boden\"}, {\"name\": \"Beinheben\", \"progression_id\": \"lr\", \"cue\": \"Beine heben/senken\"}]}]}, {\"day\": 3, \"focus\": \"Beine + Core\", \"light\": false, \"note\": \"\", \"warmup\": [\"Hüftkreisen + Beinschwünge\", \"Knie-über-Zeh an der Wand\", \"Cat-Cow\", \"Handflächen-Wippen\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Hamstring-Dehnung (Couch, je 30–40s)\", \"Quad im Stand (je 30s)\", \"Knieender Hüftbeuger (je 30–40s)\", \"Figure-4/Taube (je 30–40s)\", \"Cobra + liegende Wirbelsäulen-Rotation (je 30s)\", \"Wade an der Wand (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Goblet Squat (KB/Rucksack)\", \"progression_id\": \"gobletkb\", \"cue\": \"Last umarmen, tief\"}, {\"name\": \"RDL / Hip Hinge (KB/Rucksack)\", \"progression_id\": \"load\", \"cue\": \"Hüfte zurück, Rücken gerade, Hamstrings\"}, {\"name\": \"Glute Bridge 1-beinig\", \"progression_id\": \"bridge\", \"cue\": \"ein Bein gestreckt\"}, {\"name\": \"Wall Sit\", \"progression_id\": \"wall\", \"cue\": \"Rücken an die Wand, halten\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Hollow Hold\", \"progression_id\": \"holl\", \"cue\": \"unterer Rücken am Boden\"}, {\"name\": \"Side Plank (Wechsel)\", \"progression_id\": \"sp\", \"cue\": \"seitlich auf einem Unterarm\"}, {\"name\": \"Bicycle Crunch\", \"progression_id\": \"bic\", \"cue\": \"Ellbogen zum Gegenknie\"}, {\"name\": \"Russian Twist (KB)\", \"progression_id\": \"tw\", \"cue\": \"sitzend Rumpf drehen\"}]}]}, {\"day\": 4, \"focus\": \"Oberkörper: Drücken + Ziehen\", \"light\": false, \"note\": \"\", \"warmup\": [\"Handflächen-Wippen\", \"Handtuch-Dislocates (8–10×)\", \"Armkreisen/Wand-Slides\", \"Cat-Cow\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Türrahmen (je 30–40s)\", \"Trizeps (je 30s)\", \"Lat-/Seitdehnung (je 30s)\", \"BWS über die Couch (30–40s)\", \"Unterarm (je 20s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Liegestütze\", \"progression_id\": \"ls\", \"cue\": \"Brust kontrolliert senken & drücken\"}, {\"name\": \"Bank-Dips (Couch)\", \"progression_id\": \"dips\", \"cue\": \"Po vor der Couch senken\"}, {\"name\": \"Pike\", \"progression_id\": \"pike\", \"cue\": \"umgek. V, Kopf senken\"}, {\"name\": \"KB Floor Press\", \"progression_id\": \"press\", \"cue\": \"Glocke vom Boden weg drücken\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Einarm-Rudern ENG (KB)\", \"progression_id\": \"rowe\", \"cue\": \"1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts\"}, {\"name\": \"Bizeps-Curls (KB/Rucksack)\", \"progression_id\": \"load\", \"cue\": \"Oberarme fix, zu den Schultern\"}, {\"name\": \"Einarm-Rudern BREIT (KB)\", \"progression_id\": \"row2\", \"cue\": \"1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts\"}, {\"name\": \"Y-T-W / Superman\", \"progression_id\": \"sup\", \"cue\": \"bäuchlings, Rücken\"}]}]}, {\"day\": 5, \"focus\": \"Ganzkörper LEICHT + Mobility\", \"light\": true, \"note\": \"\", \"warmup\": [\"Cat-Cow\", \"Hüftkreisen + Beinschwünge\", \"Armkreisen/Wand-Slides\", \"Knie-über-Zeh an der Wand\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Türrahmen (je 30s)\", \"Knieender Hüftbeuger (je 30s)\", \"BWS über die Couch (30–40s)\", \"Figure-4/Taube (je 30s)\", \"Wade an der Wand (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Kniebeuge (leicht)\", \"progression_id\": \"name:kniebeuge-leicht\", \"cue\": \"locker, sauber\"}, {\"name\": \"Liegestütze (1 Stufe leichter)\", \"progression_id\": \"name:liegestuetze-1-stufe-leichter\", \"cue\": \"leichtere Variante\"}, {\"name\": \"Rudern (leicht)\", \"progression_id\": \"name:rudern-leicht\", \"cue\": \"wenig Gewicht\"}, {\"name\": \"Mountain Climbers (locker)\", \"progression_id\": \"name:mountain-climbers-locker\", \"cue\": \"ruhiges Tempo\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Inchworm\", \"progression_id\": \"name:inchworm\", \"cue\": \"in den Stütz wandern, zurück\"}, {\"name\": \"Cat-Cow\", \"progression_id\": \"name:cat-cow\", \"cue\": \"Rücken ründen/strecken\"}, {\"name\": \"Bird Dog\", \"progression_id\": \"name:bird-dog\", \"cue\": \"gegengleich Arm+Bein, kurz halten\"}, {\"name\": \"KB-Halo (leicht)\", \"progression_id\": \"name:kb-halo-leicht\", \"cue\": \"langsam um den Kopf kreisen\"}]}]}, {\"day\": 6, \"focus\": \"KETTLEBELL Ganzkörper\", \"light\": false, \"note\": \"Reine Kettlebell-Session. Swing = Hüft-Schnapp, kein Armheben. Glocke so wählen, dass alle Reps sauber sind.\", \"warmup\": [\"Halo (leicht)\", \"Hüftkreisen + Beinschwünge\", \"Cat-Cow\", \"Handflächen-Wippen\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Hamstring-Dehnung (Couch, je 30–40s)\", \"Knieender Hüftbeuger (je 30–40s)\", \"Figure-4/Taube (je 30–40s)\", \"Unterarm (je 20s)\", \"BWS über die Couch (30–40s)\", \"Lat-/Seitdehnung (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"KB Swing (zweiarmig)\", \"progression_id\": \"swing\", \"cue\": \"Hüft-Schnapp, kein Armheben\"}, {\"name\": \"Goblet Squat (KB)\", \"progression_id\": \"gobletkb\", \"cue\": \"Glocke vor der Brust, tief\"}, {\"name\": \"KB-Deadlift (beidbeinig)\", \"progression_id\": \"kbdl\", \"cue\": \"Glocke zwischen den Füßen, Hüfte hinge\"}, {\"name\": \"Halo\", \"progression_id\": \"halo\", \"cue\": \"eng um den Kopf kreisen\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"KB Floor Press\", \"progression_id\": \"press\", \"cue\": \"Glocke vom Boden weg drücken\"}, {\"name\": \"Einarmiges Rudern (KB)\", \"progression_id\": \"row1\", \"cue\": \"Rücken gerade, zum Bauch\"}, {\"name\": \"Bottoms-up KB-Hold (Griff)\", \"progression_id\": \"carry\", \"cue\": \"Glocke Boden nach oben, Unterarm senkrecht, still halten\"}, {\"name\": \"Around-the-World + Uppercut (KB)\", \"progression_id\": \"atw\", \"cue\": \"Glocke um den Körper, dann diagonal zur Gegen-Schulter, andere Hand fängt\"}]}]}], \"phases\": [{\"name\": \"Phase 1 · Fundament\", \"instructions\": \"Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.\", \"start_week\": 1, \"end_week\": 2}, {\"name\": \"Phase 2 · Aufbau\", \"instructions\": \"Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.\", \"start_week\": 3, \"end_week\": 4}, {\"name\": \"Phase 3 · Steigerung\", \"instructions\": \"Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.\", \"start_week\": 5, \"end_week\": 6}, {\"name\": \"Phase 4 · Festigen\", \"instructions\": \"Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.\", \"start_week\": 7, \"end_week\": 8}], \"progression_stages\": {\"atw\": {\"name\": \"Around-the-World + Uppercut (KB)\", \"steps\": [\"nur Around-the-World, leicht\", \"Around-the-World, mehr Reps\", \"+ Uppercut, leicht\", \"+ Uppercut, flüssig\"]}, \"bic\": {\"name\": \"Bicycle Crunch\", \"steps\": [\"langsam\", \"moderat\", \"moderat\", \"zügig\"]}, \"bridge\": {\"name\": \"Glute Bridge\", \"steps\": [\"beidbeinig\", \"beidbeinig, 2s halten\", \"einbeinig\", \"einbeinig, 2s halten\"]}, \"calf\": {\"name\": \"Wadenheben\", \"steps\": [\"Körpergewicht\", \"langsam\", \"langsam, mehr Reps\", \"1-beinig\"]}, \"carry\": {\"name\": \"Bottoms-up KB-Hold (Griff)\", \"steps\": [\"im Stand 10–15s\", \"15–20s, ruhig\", \"bis Schulterhöhe 15s\", \"länger / ein paar Schritte\"]}, \"db\": {\"name\": \"Dead Bug\", \"steps\": [\"Arme ODER Beine einzeln\", \"gegengleich\", \"gegengleich + Pause\", \"Beine weiter gestreckt\"]}, \"dips\": {\"name\": \"Bank-Dips (Couch)\", \"steps\": [\"Knie stark gebeugt, klein\", \"Knie gebeugt, voll\", \"Füße etwas weiter\", \"Füße weiter weg\"]}, \"gobletkb\": {\"name\": \"Goblet Squat (KB/Rucksack)\", \"steps\": [\"leicht, Tiefe lernen\", \"leicht, mehr Reps\", \"mittel\", \"mittel, 3s runter\"]}, \"halo\": {\"name\": \"Halo\", \"steps\": [\"leicht, langsam\", \"leicht\", \"mittel\", \"mittel, langsam\"]}, \"holl\": {\"name\": \"Hollow Hold\", \"steps\": [\"Tuck (Knie an)\", \"Tuck, länger\", \"halb gestreckt\", \"halb/voll\"]}, \"kbdl\": {\"name\": \"KB-Deadlift (beidbeinig)\", \"steps\": [\"leicht, Technik\", \"leicht, mehr Reps\", \"mittel\", \"schwerer, kontrolliert\"]}, \"load\": {\"name\": \"Bizeps-Curls (KB/Rucksack)\", \"steps\": [\"leicht\", \"etwas mehr\", \"mehr\", \"fordernd\"]}, \"lr\": {\"name\": \"Beinheben\", \"steps\": [\"Knie gebeugt\", \"Knie gebeugt, langsam\", \"halb gestreckt\", \"gestreckt\"]}, \"lraise\": {\"name\": \"Seitheben (KB/Rucksack)\", \"steps\": [\"leicht (Technik)\", \"leicht, mehr Reps\", \"mittel\", \"mittel, langsam\"]}, \"ls\": {\"name\": \"Liegestütze\", \"steps\": [\"Inkline (hoch)\", \"Inkline tiefer / Knie\", \"Knie / Negativ\", \"Negativ + erste Volle\"]}, \"lunge\": {\"name\": \"Ausfallschritt rückw.\", \"steps\": [\"Körpergewicht (ggf. festhalten)\", \"Körpergewicht\", \"mehr Reps\", \"+ Last leicht\"]}, \"mtn\": {\"name\": \"Mountain Climbers\", \"steps\": [\"moderat\", \"moderat\", \"zügig\", \"zügig\"]}, \"pike\": {\"name\": \"Pike\", \"steps\": [\"Pike Hold\", \"Hold + kleine Senkung\", \"Push-up Hände erhöht\", \"Push-up Boden, klein\"]}, \"plank\": {\"name\": \"Plank\", \"steps\": [\"auf Knien 15–20 s\", \"voll 20–30 s\", \"voll + Schulter-Taps\", \"voll, länger\"]}, \"press\": {\"name\": \"KB Floor Press\", \"steps\": [\"leicht (Floor Press)\", \"Floor Press mehr\", \"Overhead leicht\", \"Overhead mittel\"]}, \"row1\": {\"name\": \"Einarmiges Rudern (KB)\", \"steps\": [\"leicht\", \"etwas mehr\", \"einarmig leicht\", \"einarmig mittel\"]}, \"row2\": {\"name\": \"Einarm-Rudern BREIT (KB)\", \"steps\": [\"leicht (Technik)\", \"leicht, mehr Reps\", \"mittel\", \"mittel, Pause oben\"]}, \"rowe\": {\"name\": \"Einarm-Rudern ENG (KB)\", \"steps\": [\"leicht\", \"etwas mehr\", \"mehr\", \"fordernd\"]}, \"sp\": {\"name\": \"Side Plank (Wechsel)\", \"steps\": [\"auf Knien\", \"auf Knien, länger\", \"voll\", \"voll, länger\"]}, \"sup\": {\"name\": \"Superman / Y-T-W\", \"steps\": [\"Superman Hold\", \"Superman Hold länger\", \"Reverse Snow Angels\", \"Y-T-W Raises\"]}, \"swing\": {\"name\": \"KB Swing (zweiarmig)\", \"steps\": [\"Dead-Stop, leicht (Technik)\", \"Dead-Stop, mehr Reps\", \"durchgehend, mittel\", \"durchgehend, schwerer\"]}, \"tw\": {\"name\": \"Russian Twist (KB)\", \"steps\": [\"ohne Gewicht\", \"ohne Gewicht\", \"KB leicht\", \"KB\"]}, \"wall\": {\"name\": \"Wall Sit\", \"steps\": [\"15–25 s\", \"25–40 s\", \"40–55 s\", \"55–70 s\"]}}}, \"profile\": {\"display_name\": \"\", \"start_date\": \"\", \"plan_notes\": \"\"}, \"previous_end_state\": [], \"local_metrics\": {\"sessions_with_data\": 6, \"completed_sessions\": 6, \"planned_sessions\": 6, \"completion_rate_pct\": 100, \"documented_exercises\": 36}, \"sessions\": [{\"session_key\": \"w01-d01\", \"week\": 1, \"day\": 1, \"date\": \"\", \"focus\": \"Drücken + Beine\", \"light\": false, \"phase\": \"Phase 1 · Fundament\", \"phase_instructions\": \"Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.\", \"status\": \"completed\", \"started_at\": \"2026-07-23T10:01:58.783Z\", \"completed_at\": \"2026-07-23T10:01:58.783Z\", \"session_note\": \"\", \"section_notes\": {}, \"items\": [{\"item_id\": \"cooldown-0\", \"exercise\": \"Ausschütteln + Gehen (60–90s)\", \"progression_id\": \"\", \"progression\": \"\", \"result\": \"\", \"note\": \"\", \"done\": true, \"cue\": \"\", \"rotation\": \"cooldown\", \"kind\": \"cooldown\"}, {\"item_id\": \"cooldown-1\", \"exercise\": \"Box-Atmung (4–5 Runden)\", \"progression_id\": \"\", \"progression\": \"\", \"result\": \"\", \"note\": \"\", \"done\": true, \"cue\": \"\", \"rotation\": \"cooldown\", \"kind\": \"cooldown\"}, {\"item_id\": \"d1-r0-e0\", \"exercise\": \"Liegestütze\", \"progression_id\": \"ls\", \"progression\": \"Inkline (hoch)\", \"result\": \"8/7/7/7/6/6/5\", \"note\": \"\", \"done\": true, \"cue\": \"Brust kontrolliert senken & drücken\", \"rotation\": \"ROTATION A — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"d1-r0-e1\", \"exercise\": \"Bank-Dips (Couch)\", \"progression_id\": \"dips\", \"progression\": \"Knie stark gebeugt, klein\", \"result\": \"8/7/7/7/6/6\", \"note\": \"\", \"done\": true, \"cue\": \"Po vor der Couch senken, hochdrücken\", \"rotation\": \"ROTATION A — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"d1-r0-e2\", \"exercise\": \"Pike\", \"progression_id\": \"pike\", \"progression\": \"Pike Hold\", \"result\": \"6x20 s\", \"note\": \"\", \"done\": true, \"cue\": \"umgek. V, Kopf senken (Schultern)\", \"rotation\": \"ROTATION A — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"d1-r0-e3\", \"exercise\": \"Seitheben (KB/Rucksack)\", \"progression_id\": \"lraise\", \"progression\": \"leicht (Technik)\", \"result\": \"4kg, 6/6/5 je Seite\", \"note\": \"\", \"done\": true, \"cue\": \"Arme seitlich bis Schulterhöhe, kontrolliert\", \"rotation\": \"ROTATION A — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"d1-r1-e0\", \"exercise\": \"Goblet Squat (KB/Rucksack)\", \"progression_id\": \"gobletkb\", \"progression\": \"leicht, Tiefe lernen\", \"result\": \"0kg, 8/8/7/7/6/7\", \"note\": \"\", \"done\": true, \"cue\": \"Last umarmen, tief\", \"rotation\": \"ROTATION B — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"d1-r1-e1\", \"exercise\": \"Ausfallschritt rückw.\", \"progression_id\": \"lunge\", \"progression\": \"Körpergewicht (ggf. festhalten)\", \"result\": \"3x4 je Seite\", \"note\": \"\", \"done\": true, \"cue\": \"großer Schritt, Knie Richtung Boden\", \"rotation\": \"ROTATION B — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"d1-r1-e2\", \"exercise\": \"Glute Bridge\", \"progression_id\": \"bridge\", \"progression\": \"beidbeinig\", \"result\": \"6x 20s\", \"note\": \"\", \"done\": true, \"cue\": \"Hüfte hochdrücken, Po fest\", \"rotation\": \"ROTATION B — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"d1-r1-e3\", \"exercise\": \"Wadenheben\", \"progression_id\": \"calf\", \"progression\": \"Körpergewicht\", \"result\": \"10kg, 10/10/10/10/10/10\", \"note\": \"\", \"done\": true, \"cue\": \"auf Zehenspitzen hoch/runter\", \"rotation\": \"ROTATION B — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"stretch-0\", \"exercise\": \"Türrahmen (Brust/Schulter, je 30–40s)\", \"progression_id\": \"\", \"progression\": \"\", \"result\": \"\", \"note\": \"\", \"done\": true, \"cue\": \"\", \"rotation\": \"stretch\", \"kind\": \"stretch\"}, {\"item_id\"" + } + ], + "dataset": { + "analysis_type": "week", + "week": 1, + "plan": { + "title": "Calisthenics-Basis – 8-Wochen Komplettplan", + "subtitle": "Blutiger Anfänger · 1,90 m · 95 kg · Rucksack + Couch + Kettlebell · tägliche ~30-Min Tabata-Session", + "weeks": 8, + "selected_week": 1, + "selected_phase": { + "name": "Phase 1 · Fundament", + "instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation." + }, + "training_format": { + "mode": "tabata", + "is_tabata": true, + "fixed_interval": true, + "work_seconds": 20, + "rest_seconds": 10, + "rounds": 8, + "session_method": "Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.", + "timer_note": "Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.", + "constraints": [ + "Arbeitsintervalle bleiben fest bei 20 Sekunden; keine längeren Holds oder verlängerten Arbeitsintervalle empfehlen.", + "Progression nur innerhalb des Planformats: planmäßige schwierigere Variante, mehr Gewicht, saubererer ROM/Tempo oder bessere Technik; Pausen und Rundenzahl nicht eigenmächtig ändern." + ] + }, + "guidance": { + "goals": "Du startest bei 2–3 Liegestützen und 0 Dips. Realistisch und ehrlich nach 8 Wochen sauberer Arbeit:\n•  Liegestütze: von 2–3 auf erste saubere volle Reps (Rest in Knie-/Negativ-Form).\n•  Bank-Dips: saubere Reps mit Füßen etwas weiter weg.   Pike Push-up: am Boden mit kleinem ROM.\n•  Plank ~45–60 s, Wall Sit ~60 s, viele saubere Goblet-Kniebeugen, sauberer Swing + spürbar bessere Griffkraft.\n•  Noch NICHT dran: Diamond Push-ups, L-Sit, Handstand, schwerer Turkish Get-Up, Snatch – Stoff für Woche 12+.", + "goals_note": "Wichtig: Die Wochen-Stufen sind ein Rahmen, kein Muss. Bleib auf einer Stufe, solange sie fordert. Aufstiegs-Regel: erst hoch (oder schwerere Glocke), wenn du eine Übung 2 Sessions in Folge in allen Sätzen sauber über die vollen 20s schaffst. KB = Kettlebell bevorzugt; Rucksack bleibt als Alternative.", + "session_method": "Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.", + "timer_note": "Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.", + "reminders": [ + "Form vor Reps. Wähle die Stufe / Glocke, bei der du die 20s kontrolliert durcharbeitest – mit 1–2 Reps Reserve.", + "Swing-Technik first. Der Swing ist ein Hüft-Schnapp, KEIN Kniebeuge und KEIN Armheben. Erst Dead-Stop-Swings, bis die Hüftstreckung sitzt.", + "Einseitige Übungen (Tag 2): Durchgang 1 = links, Durchgang 2 = rechts – so ist jede Seite automatisch ausbalanciert.", + "Täglich ist viel. Tag 5 leicht, Tag 7 frei. Bei starkem Muskelkater einen Extra-Ruhetag – gehört zum Aufbau.", + "Schlaf & Eiweiß. 7–9 h Schlaf, ~1,6 g Eiweiß pro kg. Stechender Schmerz = stoppen." + ] + }, + "days": [ + { + "day": 1, + "focus": "Drücken + Beine", + "light": false, + "note": "", + "warmup": [ + "Handflächen-Wippen", + "Armkreisen/Wand-Slides", + "Hüftkreisen + Beinschwünge", + "Knie-über-Zeh an der Wand" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Türrahmen (Brust/Schulter, je 30–40s)", + "Trizeps (je 30s)", + "Seitheben/Lat-Seitdehnung (je 30s)", + "Quad im Stand (je 30s)", + "Knieender Hüftbeuger (je 30–40s)", + "Wade an der Wand (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Liegestütze", + "progression_id": "ls", + "cue": "Brust kontrolliert senken & drücken" + }, + { + "name": "Bank-Dips (Couch)", + "progression_id": "dips", + "cue": "Po vor der Couch senken, hochdrücken" + }, + { + "name": "Pike", + "progression_id": "pike", + "cue": "umgek. V, Kopf senken (Schultern)" + }, + { + "name": "Seitheben (KB/Rucksack)", + "progression_id": "lraise", + "cue": "Arme seitlich bis Schulterhöhe, kontrolliert" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "cue": "Last umarmen, tief" + }, + { + "name": "Ausfallschritt rückw.", + "progression_id": "lunge", + "cue": "großer Schritt, Knie Richtung Boden" + }, + { + "name": "Glute Bridge", + "progression_id": "bridge", + "cue": "Hüfte hochdrücken, Po fest" + }, + { + "name": "Wadenheben", + "progression_id": "calf", + "cue": "auf Zehenspitzen hoch/runter" + } + ] + } + ] + }, + { + "day": 2, + "focus": "Ziehen + Core", + "light": false, + "note": "", + "warmup": [ + "Handflächen-Wippen", + "Handtuch-Dislocates (8–10×)", + "Cat-Cow", + "Hüftkreisen + Beinschwünge" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Lat-/Seitdehnung (je 30s)", + "Türrahmen (je 30–40s)", + "BWS über die Couch (30–40s)", + "Cobra/Sphinx (Bauch, 30s)", + "Unterarm (je 20s)", + "Knieender Hüftbeuger (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts" + }, + { + "name": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "cue": "Oberarme fix, zu den Schultern" + }, + { + "name": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts" + }, + { + "name": "Superman / Y-T-W", + "progression_id": "sup", + "cue": "bäuchlings Arme+Beine heben" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Plank", + "progression_id": "plank", + "cue": "gerade Linie halten" + }, + { + "name": "Mountain Climbers", + "progression_id": "mtn", + "cue": "Knie zügig zur Brust" + }, + { + "name": "Dead Bug", + "progression_id": "db", + "cue": "gegengleich, Rücken am Boden" + }, + { + "name": "Beinheben", + "progression_id": "lr", + "cue": "Beine heben/senken" + } + ] + } + ] + }, + { + "day": 3, + "focus": "Beine + Core", + "light": false, + "note": "", + "warmup": [ + "Hüftkreisen + Beinschwünge", + "Knie-über-Zeh an der Wand", + "Cat-Cow", + "Handflächen-Wippen" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Hamstring-Dehnung (Couch, je 30–40s)", + "Quad im Stand (je 30s)", + "Knieender Hüftbeuger (je 30–40s)", + "Figure-4/Taube (je 30–40s)", + "Cobra + liegende Wirbelsäulen-Rotation (je 30s)", + "Wade an der Wand (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "cue": "Last umarmen, tief" + }, + { + "name": "RDL / Hip Hinge (KB/Rucksack)", + "progression_id": "load", + "cue": "Hüfte zurück, Rücken gerade, Hamstrings" + }, + { + "name": "Glute Bridge 1-beinig", + "progression_id": "bridge", + "cue": "ein Bein gestreckt" + }, + { + "name": "Wall Sit", + "progression_id": "wall", + "cue": "Rücken an die Wand, halten" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Hollow Hold", + "progression_id": "holl", + "cue": "unterer Rücken am Boden" + }, + { + "name": "Side Plank (Wechsel)", + "progression_id": "sp", + "cue": "seitlich auf einem Unterarm" + }, + { + "name": "Bicycle Crunch", + "progression_id": "bic", + "cue": "Ellbogen zum Gegenknie" + }, + { + "name": "Russian Twist (KB)", + "progression_id": "tw", + "cue": "sitzend Rumpf drehen" + } + ] + } + ] + }, + { + "day": 4, + "focus": "Oberkörper: Drücken + Ziehen", + "light": false, + "note": "", + "warmup": [ + "Handflächen-Wippen", + "Handtuch-Dislocates (8–10×)", + "Armkreisen/Wand-Slides", + "Cat-Cow" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Türrahmen (je 30–40s)", + "Trizeps (je 30s)", + "Lat-/Seitdehnung (je 30s)", + "BWS über die Couch (30–40s)", + "Unterarm (je 20s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Liegestütze", + "progression_id": "ls", + "cue": "Brust kontrolliert senken & drücken" + }, + { + "name": "Bank-Dips (Couch)", + "progression_id": "dips", + "cue": "Po vor der Couch senken" + }, + { + "name": "Pike", + "progression_id": "pike", + "cue": "umgek. V, Kopf senken" + }, + { + "name": "KB Floor Press", + "progression_id": "press", + "cue": "Glocke vom Boden weg drücken" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts" + }, + { + "name": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "cue": "Oberarme fix, zu den Schultern" + }, + { + "name": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts" + }, + { + "name": "Y-T-W / Superman", + "progression_id": "sup", + "cue": "bäuchlings, Rücken" + } + ] + } + ] + }, + { + "day": 5, + "focus": "Ganzkörper LEICHT + Mobility", + "light": true, + "note": "", + "warmup": [ + "Cat-Cow", + "Hüftkreisen + Beinschwünge", + "Armkreisen/Wand-Slides", + "Knie-über-Zeh an der Wand" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Türrahmen (je 30s)", + "Knieender Hüftbeuger (je 30s)", + "BWS über die Couch (30–40s)", + "Figure-4/Taube (je 30s)", + "Wade an der Wand (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Kniebeuge (leicht)", + "progression_id": "name:kniebeuge-leicht", + "cue": "locker, sauber" + }, + { + "name": "Liegestütze (1 Stufe leichter)", + "progression_id": "name:liegestuetze-1-stufe-leichter", + "cue": "leichtere Variante" + }, + { + "name": "Rudern (leicht)", + "progression_id": "name:rudern-leicht", + "cue": "wenig Gewicht" + }, + { + "name": "Mountain Climbers (locker)", + "progression_id": "name:mountain-climbers-locker", + "cue": "ruhiges Tempo" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Inchworm", + "progression_id": "name:inchworm", + "cue": "in den Stütz wandern, zurück" + }, + { + "name": "Cat-Cow", + "progression_id": "name:cat-cow", + "cue": "Rücken ründen/strecken" + }, + { + "name": "Bird Dog", + "progression_id": "name:bird-dog", + "cue": "gegengleich Arm+Bein, kurz halten" + }, + { + "name": "KB-Halo (leicht)", + "progression_id": "name:kb-halo-leicht", + "cue": "langsam um den Kopf kreisen" + } + ] + } + ] + }, + { + "day": 6, + "focus": "KETTLEBELL Ganzkörper", + "light": false, + "note": "Reine Kettlebell-Session. Swing = Hüft-Schnapp, kein Armheben. Glocke so wählen, dass alle Reps sauber sind.", + "warmup": [ + "Halo (leicht)", + "Hüftkreisen + Beinschwünge", + "Cat-Cow", + "Handflächen-Wippen" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Hamstring-Dehnung (Couch, je 30–40s)", + "Knieender Hüftbeuger (je 30–40s)", + "Figure-4/Taube (je 30–40s)", + "Unterarm (je 20s)", + "BWS über die Couch (30–40s)", + "Lat-/Seitdehnung (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "KB Swing (zweiarmig)", + "progression_id": "swing", + "cue": "Hüft-Schnapp, kein Armheben" + }, + { + "name": "Goblet Squat (KB)", + "progression_id": "gobletkb", + "cue": "Glocke vor der Brust, tief" + }, + { + "name": "KB-Deadlift (beidbeinig)", + "progression_id": "kbdl", + "cue": "Glocke zwischen den Füßen, Hüfte hinge" + }, + { + "name": "Halo", + "progression_id": "halo", + "cue": "eng um den Kopf kreisen" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "KB Floor Press", + "progression_id": "press", + "cue": "Glocke vom Boden weg drücken" + }, + { + "name": "Einarmiges Rudern (KB)", + "progression_id": "row1", + "cue": "Rücken gerade, zum Bauch" + }, + { + "name": "Bottoms-up KB-Hold (Griff)", + "progression_id": "carry", + "cue": "Glocke Boden nach oben, Unterarm senkrecht, still halten" + }, + { + "name": "Around-the-World + Uppercut (KB)", + "progression_id": "atw", + "cue": "Glocke um den Körper, dann diagonal zur Gegen-Schulter, andere Hand fängt" + } + ] + } + ] + } + ], + "phases": [ + { + "name": "Phase 1 · Fundament", + "instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "start_week": 1, + "end_week": 2 + }, + { + "name": "Phase 2 · Aufbau", + "instructions": "Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.", + "start_week": 3, + "end_week": 4 + }, + { + "name": "Phase 3 · Steigerung", + "instructions": "Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.", + "start_week": 5, + "end_week": 6 + }, + { + "name": "Phase 4 · Festigen", + "instructions": "Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.", + "start_week": 7, + "end_week": 8 + } + ], + "progression_stages": { + "atw": { + "name": "Around-the-World + Uppercut (KB)", + "steps": [ + "nur Around-the-World, leicht", + "Around-the-World, mehr Reps", + "+ Uppercut, leicht", + "+ Uppercut, flüssig" + ] + }, + "bic": { + "name": "Bicycle Crunch", + "steps": [ + "langsam", + "moderat", + "moderat", + "zügig" + ] + }, + "bridge": { + "name": "Glute Bridge", + "steps": [ + "beidbeinig", + "beidbeinig, 2s halten", + "einbeinig", + "einbeinig, 2s halten" + ] + }, + "calf": { + "name": "Wadenheben", + "steps": [ + "Körpergewicht", + "langsam", + "langsam, mehr Reps", + "1-beinig" + ] + }, + "carry": { + "name": "Bottoms-up KB-Hold (Griff)", + "steps": [ + "im Stand 10–15s", + "15–20s, ruhig", + "bis Schulterhöhe 15s", + "länger / ein paar Schritte" + ] + }, + "db": { + "name": "Dead Bug", + "steps": [ + "Arme ODER Beine einzeln", + "gegengleich", + "gegengleich + Pause", + "Beine weiter gestreckt" + ] + }, + "dips": { + "name": "Bank-Dips (Couch)", + "steps": [ + "Knie stark gebeugt, klein", + "Knie gebeugt, voll", + "Füße etwas weiter", + "Füße weiter weg" + ] + }, + "gobletkb": { + "name": "Goblet Squat (KB/Rucksack)", + "steps": [ + "leicht, Tiefe lernen", + "leicht, mehr Reps", + "mittel", + "mittel, 3s runter" + ] + }, + "halo": { + "name": "Halo", + "steps": [ + "leicht, langsam", + "leicht", + "mittel", + "mittel, langsam" + ] + }, + "holl": { + "name": "Hollow Hold", + "steps": [ + "Tuck (Knie an)", + "Tuck, länger", + "halb gestreckt", + "halb/voll" + ] + }, + "kbdl": { + "name": "KB-Deadlift (beidbeinig)", + "steps": [ + "leicht, Technik", + "leicht, mehr Reps", + "mittel", + "schwerer, kontrolliert" + ] + }, + "load": { + "name": "Bizeps-Curls (KB/Rucksack)", + "steps": [ + "leicht", + "etwas mehr", + "mehr", + "fordernd" + ] + }, + "lr": { + "name": "Beinheben", + "steps": [ + "Knie gebeugt", + "Knie gebeugt, langsam", + "halb gestreckt", + "gestreckt" + ] + }, + "lraise": { + "name": "Seitheben (KB/Rucksack)", + "steps": [ + "leicht (Technik)", + "leicht, mehr Reps", + "mittel", + "mittel, langsam" + ] + }, + "ls": { + "name": "Liegestütze", + "steps": [ + "Inkline (hoch)", + "Inkline tiefer / Knie", + "Knie / Negativ", + "Negativ + erste Volle" + ] + }, + "lunge": { + "name": "Ausfallschritt rückw.", + "steps": [ + "Körpergewicht (ggf. festhalten)", + "Körpergewicht", + "mehr Reps", + "+ Last leicht" + ] + }, + "mtn": { + "name": "Mountain Climbers", + "steps": [ + "moderat", + "moderat", + "zügig", + "zügig" + ] + }, + "pike": { + "name": "Pike", + "steps": [ + "Pike Hold", + "Hold + kleine Senkung", + "Push-up Hände erhöht", + "Push-up Boden, klein" + ] + }, + "plank": { + "name": "Plank", + "steps": [ + "auf Knien 15–20 s", + "voll 20–30 s", + "voll + Schulter-Taps", + "voll, länger" + ] + }, + "press": { + "name": "KB Floor Press", + "steps": [ + "leicht (Floor Press)", + "Floor Press mehr", + "Overhead leicht", + "Overhead mittel" + ] + }, + "row1": { + "name": "Einarmiges Rudern (KB)", + "steps": [ + "leicht", + "etwas mehr", + "einarmig leicht", + "einarmig mittel" + ] + }, + "row2": { + "name": "Einarm-Rudern BREIT (KB)", + "steps": [ + "leicht (Technik)", + "leicht, mehr Reps", + "mittel", + "mittel, Pause oben" + ] + }, + "rowe": { + "name": "Einarm-Rudern ENG (KB)", + "steps": [ + "leicht", + "etwas mehr", + "mehr", + "fordernd" + ] + }, + "sp": { + "name": "Side Plank (Wechsel)", + "steps": [ + "auf Knien", + "auf Knien, länger", + "voll", + "voll, länger" + ] + }, + "sup": { + "name": "Superman / Y-T-W", + "steps": [ + "Superman Hold", + "Superman Hold länger", + "Reverse Snow Angels", + "Y-T-W Raises" + ] + }, + "swing": { + "name": "KB Swing (zweiarmig)", + "steps": [ + "Dead-Stop, leicht (Technik)", + "Dead-Stop, mehr Reps", + "durchgehend, mittel", + "durchgehend, schwerer" + ] + }, + "tw": { + "name": "Russian Twist (KB)", + "steps": [ + "ohne Gewicht", + "ohne Gewicht", + "KB leicht", + "KB" + ] + }, + "wall": { + "name": "Wall Sit", + "steps": [ + "15–25 s", + "25–40 s", + "40–55 s", + "55–70 s" + ] + } + } + }, + "profile": { + "display_name": "", + "start_date": "", + "plan_notes": "" + }, + "previous_end_state": [], + "local_metrics": { + "sessions_with_data": 6, + "completed_sessions": 6, + "planned_sessions": 6, + "completion_rate_pct": 100, + "documented_exercises": 36 + }, + "sessions": [ + { + "session_key": "w01-d01", + "week": 1, + "day": 1, + "date": "", + "focus": "Drücken + Beine", + "light": false, + "phase": "Phase 1 · Fundament", + "phase_instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "status": "completed", + "started_at": "2026-07-23T10:01:58.783Z", + "completed_at": "2026-07-23T10:01:58.783Z", + "session_note": "", + "section_notes": {}, + "items": [ + { + "item_id": "cooldown-0", + "exercise": "Ausschütteln + Gehen (60–90s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "cooldown-1", + "exercise": "Box-Atmung (4–5 Runden)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "d1-r0-e0", + "exercise": "Liegestütze", + "progression_id": "ls", + "progression": "Inkline (hoch)", + "result": "8/7/7/7/6/6/5", + "note": "", + "done": true, + "cue": "Brust kontrolliert senken & drücken", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d1-r0-e1", + "exercise": "Bank-Dips (Couch)", + "progression_id": "dips", + "progression": "Knie stark gebeugt, klein", + "result": "8/7/7/7/6/6", + "note": "", + "done": true, + "cue": "Po vor der Couch senken, hochdrücken", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d1-r0-e2", + "exercise": "Pike", + "progression_id": "pike", + "progression": "Pike Hold", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "umgek. V, Kopf senken (Schultern)", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d1-r0-e3", + "exercise": "Seitheben (KB/Rucksack)", + "progression_id": "lraise", + "progression": "leicht (Technik)", + "result": "4kg, 6/6/5 je Seite", + "note": "", + "done": true, + "cue": "Arme seitlich bis Schulterhöhe, kontrolliert", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d1-r1-e0", + "exercise": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "progression": "leicht, Tiefe lernen", + "result": "0kg, 8/8/7/7/6/7", + "note": "", + "done": true, + "cue": "Last umarmen, tief", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d1-r1-e1", + "exercise": "Ausfallschritt rückw.", + "progression_id": "lunge", + "progression": "Körpergewicht (ggf. festhalten)", + "result": "3x4 je Seite", + "note": "", + "done": true, + "cue": "großer Schritt, Knie Richtung Boden", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d1-r1-e2", + "exercise": "Glute Bridge", + "progression_id": "bridge", + "progression": "beidbeinig", + "result": "6x 20s", + "note": "", + "done": true, + "cue": "Hüfte hochdrücken, Po fest", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d1-r1-e3", + "exercise": "Wadenheben", + "progression_id": "calf", + "progression": "Körpergewicht", + "result": "10kg, 10/10/10/10/10/10", + "note": "", + "done": true, + "cue": "auf Zehenspitzen hoch/runter", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "stretch-0", + "exercise": "Türrahmen (Brust/Schulter, je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-1", + "exercise": "Trizeps (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-2", + "exercise": "Seitheben/Lat-Seitdehnung (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-3", + "exercise": "Quad im Stand (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-4", + "exercise": "Knieender Hüftbeuger (je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-5", + "exercise": "Wade an der Wand (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "warmup-0", + "exercise": "Handflächen-Wippen", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-1", + "exercise": "Armkreisen/Wand-Slides", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-2", + "exercise": "Hüftkreisen + Beinschwünge", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-3", + "exercise": "Knie-über-Zeh an der Wand", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + } + ] + }, + { + "session_key": "w01-d02", + "week": 1, + "day": 2, + "date": "", + "focus": "Ziehen + Core", + "light": false, + "phase": "Phase 1 · Fundament", + "phase_instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "status": "completed", + "started_at": "2026-07-23T10:03:32.468Z", + "completed_at": "2026-07-23T10:03:32.468Z", + "session_note": "", + "section_notes": {}, + "items": [ + { + "item_id": "cooldown-0", + "exercise": "Ausschütteln + Gehen (60–90s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "cooldown-1", + "exercise": "Box-Atmung (4–5 Runden)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "d2-r0-e0", + "exercise": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "progression": "leicht", + "result": "4kg, 9/9/8/8/8/9", + "note": "", + "done": true, + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d2-r0-e1", + "exercise": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "progression": "leicht", + "result": "4kg, 8/8/8/8/8/7", + "note": "", + "done": true, + "cue": "Oberarme fix, zu den Schultern", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d2-r0-e2", + "exercise": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "progression": "leicht (Technik)", + "result": "4kg, 7/7/6/7/6/8", + "note": "", + "done": true, + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d2-r0-e3", + "exercise": "Superman / Y-T-W", + "progression_id": "sup", + "progression": "Superman Hold", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "bäuchlings Arme+Beine heben", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d2-r1-e0", + "exercise": "Plank", + "progression_id": "plank", + "progression": "auf Knien 15–20 s", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "gerade Linie halten", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d2-r1-e1", + "exercise": "Mountain Climbers", + "progression_id": "mtn", + "progression": "moderat", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "Knie zügig zur Brust", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d2-r1-e2", + "exercise": "Dead Bug", + "progression_id": "db", + "progression": "Arme ODER Beine einzeln", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "gegengleich, Rücken am Boden", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d2-r1-e3", + "exercise": "Beinheben", + "progression_id": "lr", + "progression": "Knie gebeugt", + "result": "5/5/4/4/4/3", + "note": "", + "done": true, + "cue": "Beine heben/senken", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "stretch-0", + "exercise": "Lat-/Seitdehnung (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-1", + "exercise": "Türrahmen (je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-2", + "exercise": "BWS über die Couch (30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-3", + "exercise": "Cobra/Sphinx (Bauch, 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-4", + "exercise": "Unterarm (je 20s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-5", + "exercise": "Knieender Hüftbeuger (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "warmup-0", + "exercise": "Handflächen-Wippen", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-1", + "exercise": "Handtuch-Dislocates (8–10×)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-2", + "exercise": "Cat-Cow", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-3", + "exercise": "Hüftkreisen + Beinschwünge", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + } + ] + }, + { + "session_key": "w01-d03", + "week": 1, + "day": 3, + "date": "", + "focus": "Beine + Core", + "light": false, + "phase": "Phase 1 · Fundament", + "phase_instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "status": "completed", + "started_at": "2026-07-23T10:06:49.678Z", + "completed_at": "2026-07-23T10:06:49.678Z", + "session_note": "", + "section_notes": {}, + "items": [ + { + "item_id": "cooldown-0", + "exercise": "Ausschütteln + Gehen (60–90s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "cooldown-1", + "exercise": "Box-Atmung (4–5 Runden)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "d3-r0-e0", + "exercise": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "progression": "leicht, Tiefe lernen", + "result": "6kg, 8/8/7/7/6/7", + "note": "", + "done": true, + "cue": "Last umarmen, tief", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d3-r0-e1", + "exercise": "RDL / Hip Hinge (KB/Rucksack)", + "progression_id": "load", + "progression": "leicht", + "result": "8kg, 7/7/7/7/7/7", + "note": "", + "done": true, + "cue": "Hüfte zurück, Rücken gerade, Hamstrings", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d3-r0-e2", + "exercise": "Glute Bridge 1-beinig", + "progression_id": "bridge", + "progression": "beidbeinig", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "ein Bein gestreckt", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d3-r0-e3", + "exercise": "Wall Sit", + "progression_id": "wall", + "progression": "15–25 s", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "Rücken an die Wand, halten", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d3-r1-e0", + "exercise": "Hollow Hold", + "progression_id": "holl", + "progression": "Tuck (Knie an)", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "unterer Rücken am Boden", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d3-r1-e1", + "exercise": "Side Plank (Wechsel)", + "progression_id": "sp", + "progression": "auf Knien", + "result": "6x20s", + "note": "", + "done": true, + "cue": "seitlich auf einem Unterarm", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d3-r1-e2", + "exercise": "Bicycle Crunch", + "progression_id": "bic", + "progression": "langsam", + "result": "6x20s", + "note": "", + "done": true, + "cue": "Ellbogen zum Gegenknie", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d3-r1-e3", + "exercise": "Russian Twist (KB)", + "progression_id": "tw", + "progression": "ohne Gewicht", + "result": "4kg, 4/4/4 je Seite", + "note": "", + "done": true, + "cue": "sitzend Rumpf drehen", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "stretch-0", + "exercise": "Hamstring-Dehnung (Couch, je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-1", + "exercise": "Quad im Stand (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-2", + "exercise": "Knieender Hüftbeuger (je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-3", + "exercise": "Figure-4/Taube (je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-4", + "exercise": "Cobra + liegende Wirbelsäulen-Rotation (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-5", + "exercise": "Wade an der Wand (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "warmup-0", + "exercise": "Hüftkreisen + Beinschwünge", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-1", + "exercise": "Knie-über-Zeh an der Wand", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-2", + "exercise": "Cat-Cow", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-3", + "exercise": "Handflächen-Wippen", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + } + ] + }, + { + "session_key": "w01-d04", + "week": 1, + "day": 4, + "date": "", + "focus": "Oberkörper: Drücken + Ziehen", + "light": false, + "phase": "Phase 1 · Fundament", + "phase_instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "status": "completed", + "started_at": "2026-07-23T10:11:52.028Z", + "completed_at": "2026-07-23T10:11:52.028Z", + "session_note": "", + "section_notes": {}, + "items": [ + { + "item_id": "warmup-0", + "exercise": "Handflächen-Wippen", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-1", + "exercise": "Handtuch-Dislocates (8–10×)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-2", + "exercise": "Armkreisen/Wand-Slides", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-3", + "exercise": "Cat-Cow", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "d4-r0-e0", + "exercise": "Liegestütze", + "progression_id": "ls", + "progression": "Inkline (hoch)", + "result": "8/7/7/6/6/6", + "note": "", + "done": true, + "cue": "Brust kontrolliert senken & drücken", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d4-r0-e1", + "exercise": "Bank-Dips (Couch)", + "progression_id": "dips", + "progression": "Knie stark gebeugt, klein", + "result": "8/7/7/7/6/6", + "note": "", + "done": true, + "cue": "Po vor der Couch senken", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d4-r0-e2", + "exercise": "Pike", + "progression_id": "pike", + "progression": "Pike Hold", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "umgek. V, Kopf senken", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d4-r0-e3", + "exercise": "KB Floor Press", + "progression_id": "press", + "progression": "leicht (Floor Press)", + "result": "6x", + "note": "", + "done": true, + "cue": "Glocke vom Boden weg drücken", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d4-r1-e0", + "exercise": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "progression": "leicht", + "result": "4kg, 9/9/9/8/8/9", + "note": "", + "done": true, + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d4-r1-e1", + "exercise": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "progression": "leicht", + "result": "8kg, 7/7/7/7/7/8", + "note": "", + "done": true, + "cue": "Oberarme fix, zu den Schultern", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d4-r1-e2", + "exercise": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "progression": "leicht (Technik)", + "result": "4kg, 7/7/6/7/7/8", + "note": "", + "done": true, + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d4-r1-e3", + "exercise": "Y-T-W / Superman", + "progression_id": "sup", + "progression": "Superman Hold", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "bäuchlings, Rücken", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "cooldown-0", + "exercise": "Ausschütteln + Gehen (60–90s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "cooldown-1", + "exercise": "Box-Atmung (4–5 Runden)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "stretch-0", + "exercise": "Türrahmen (je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-1", + "exercise": "Trizeps (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-2", + "exercise": "Lat-/Seitdehnung (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-3", + "exercise": "BWS über die Couch (30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-4", + "exercise": "Unterarm (je 20s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + } + ] + }, + { + "session_key": "w01-d05", + "week": 1, + "day": 5, + "date": "", + "focus": "Ganzkörper LEICHT + Mobility", + "light": true, + "phase": "Phase 1 · Fundament", + "phase_instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "status": "completed", + "started_at": "2026-07-23T10:12:40.782Z", + "completed_at": "2026-07-23T10:12:40.782Z", + "session_note": "Nicht durchgeführt", + "section_notes": {}, + "items": [ + { + "item_id": "d5-r0-e0", + "exercise": "Kniebeuge (leicht)", + "progression_id": "name:kniebeuge-leicht", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "locker, sauber", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d5-r0-e1", + "exercise": "Liegestütze (1 Stufe leichter)", + "progression_id": "name:liegestuetze-1-stufe-leichter", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "leichtere Variante", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d5-r0-e2", + "exercise": "Rudern (leicht)", + "progression_id": "name:rudern-leicht", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "wenig Gewicht", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d5-r0-e3", + "exercise": "Mountain Climbers (locker)", + "progression_id": "name:mountain-climbers-locker", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "ruhiges Tempo", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d5-r1-e0", + "exercise": "Inchworm", + "progression_id": "name:inchworm", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "in den Stütz wandern, zurück", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d5-r1-e1", + "exercise": "Cat-Cow", + "progression_id": "name:cat-cow", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "Rücken ründen/strecken", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d5-r1-e2", + "exercise": "Bird Dog", + "progression_id": "name:bird-dog", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "gegengleich Arm+Bein, kurz halten", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d5-r1-e3", + "exercise": "KB-Halo (leicht)", + "progression_id": "name:kb-halo-leicht", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "langsam um den Kopf kreisen", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + } + ] + }, + { + "session_key": "w01-d06", + "week": 1, + "day": 6, + "date": "", + "focus": "KETTLEBELL Ganzkörper", + "light": false, + "phase": "Phase 1 · Fundament", + "phase_instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "status": "completed", + "started_at": "2026-07-23T10:14:51.035Z", + "completed_at": "2026-07-23T10:14:51.035Z", + "session_note": "", + "section_notes": {}, + "items": [ + { + "item_id": "warmup-0", + "exercise": "Halo (leicht)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-1", + "exercise": "Hüftkreisen + Beinschwünge", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-2", + "exercise": "Cat-Cow", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-3", + "exercise": "Handflächen-Wippen", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "d6-r0-e0", + "exercise": "KB Swing (zweiarmig)", + "progression_id": "swing", + "progression": "Dead-Stop, leicht (Technik)", + "result": "10kg, 15", + "note": "", + "done": true, + "cue": "Hüft-Schnapp, kein Armheben", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d6-r0-e1", + "exercise": "Goblet Squat (KB)", + "progression_id": "gobletkb", + "progression": "leicht, Tiefe lernen", + "result": "8kg, 6/6/7/8/6/6", + "note": "", + "done": true, + "cue": "Glocke vor der Brust, tief", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d6-r0-e2", + "exercise": "KB-Deadlift (beidbeinig)", + "progression_id": "kbdl", + "progression": "leicht, Technik", + "result": "10kg, 5/5/6/6/6/5", + "note": "", + "done": true, + "cue": "Glocke zwischen den Füßen, Hüfte hinge", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d6-r0-e3", + "exercise": "Halo", + "progression_id": "halo", + "progression": "leicht, langsam", + "result": "4kg, 5/4/4 je Seite", + "note": "", + "done": true, + "cue": "eng um den Kopf kreisen", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d6-r1-e0", + "exercise": "KB Floor Press", + "progression_id": "press", + "progression": "leicht (Floor Press)", + "result": "10kg, 7/7/8/6/5/6", + "note": "", + "done": true, + "cue": "Glocke vom Boden weg drücken", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d6-r1-e1", + "exercise": "Einarmiges Rudern (KB)", + "progression_id": "row1", + "progression": "leicht", + "result": "6kg, 6/7/8 je Seite", + "note": "", + "done": true, + "cue": "Rücken gerade, zum Bauch", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d6-r1-e2", + "exercise": "Bottoms-up KB-Hold (Griff)", + "progression_id": "carry", + "progression": "im Stand 10–15s", + "result": "4kg, 3x20 s je Seite", + "note": "", + "done": true, + "cue": "Glocke Boden nach oben, Unterarm senkrecht, still halten", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d6-r1-e3", + "exercise": "Around-the-World + Uppercut (KB)", + "progression_id": "atw", + "progression": "nur Around-the-World, leicht", + "result": "4kg, 5/5/5 je Seite", + "note": "", + "done": true, + "cue": "Glocke um den Körper, dann diagonal zur Gegen-Schulter, andere Hand fängt", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "cooldown-0", + "exercise": "Ausschütteln + Gehen (60–90s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "cooldown-1", + "exercise": "Box-Atmung (4–5 Runden)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "stretch-0", + "exercise": "Hamstring-Dehnung (Couch, je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-1", + "exercise": "Knieender Hüftbeuger (je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-2", + "exercise": "Figure-4/Taube (je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-3", + "exercise": "Unterarm (je 20s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-4", + "exercise": "BWS über die Couch (30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-5", + "exercise": "Lat-/Seitdehnung (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + } + ] + } + ] + } + }, + "response": { + "headline": "Woche 1: starker Einstieg, Progression vorsichtig", + "summary": "Die Woche wurde sehr konsequent gestartet; effektiv sind 5 von 6 geplanten Trainingstagen mit Übungen dokumentiert. Die meisten Übungen liegen sinnvoll auf den vorgesehenen Anfänger-Stufen der Phase 1. Erste stabile Muster zeigen sich bei Pike Holds, Rudern und Core-Holds, während Liegestütze noch klar auf Konsolidierung statt Progression stehen. Für Woche 2 sollten nur planverträgliche Variantenwechsel genutzt werden, keine längeren Arbeitsintervalle.", + "data_quality": "Aussagekraft gut für Woche 1, aber Tag 5 ist widersprüchlich als abgeschlossen markiert, obwohl alle Übungen fehlen.", + "metrics": [ + { + "label": "Durchführung", + "value": "System 6/6, effektiv 5/6", + "detail": "Tag 5 hat die Notiz „Nicht durchgeführt“ und keine erledigten Übungen." + }, + { + "label": "Dokumentationsumfang", + "value": "36 dokumentierte Übungen", + "detail": "Genug Daten für erste Übungstendenzen, aber noch keine langfristigen Trends." + }, + { + "label": "Tabata-Format", + "value": "20/10 beibehalten", + "detail": "Die dokumentierten Holds bleiben im festen 20-Sekunden-Arbeitsintervall." + }, + { + "label": "Belastungsniveau", + "value": "überwiegend Startstufen", + "detail": "Passt zur Phase 1 mit Technikfokus und leichter Last." + } + ], + "exercise_updates": [ + { + "name": "Liegestütze", + "trend": "stable", + "current_level": "Inkline hoch", + "evidence": "Tag 1: 8/7/7/7/6/6/5; Tag 4: 8/7/7/6/6/6", + "next_action": "Stufe beibehalten und sauberen ROM sowie gleichmäßigere Wiederholungen priorisieren.", + "criterion": "Erst auf Inkline tiefer/Knie wechseln, wenn 2 Sessions in Folge alle vorgesehenen 20s-Intervalle sauber mit stabiler Körperspannung geschafft werden." + }, + { + "name": "Pike", + "trend": "up", + "current_level": "Pike Hold", + "evidence": "Tag 1 und Tag 4 jeweils 6x20 s dokumentiert.", + "next_action": "Optional nächste planmäßige Stufe „Hold + kleine Senkung“ innerhalb der 20s testen.", + "criterion": "Nur übernehmen, wenn 6/6 Intervalle ohne Hüftabsacken und ohne Technikverlust sauber bleiben." + }, + { + "name": "Einarm-Rudern ENG (KB)", + "trend": "up", + "current_level": "leicht, 4 kg", + "evidence": "Tag 2: 4kg, 9/9/8/8/8/9; Tag 4: 4kg, 9/9/9/8/8/9", + "next_action": "Bei sauberem Zug zur Hüfte planverträglich auf „etwas mehr“ bzw. leicht höhere Last wechseln.", + "criterion": "2 Sessions in Folge alle dokumentierten Intervalle je Seite kontrolliert mit mindestens ähnlicher Wiederholungszahl abschließen." + }, + { + "name": "KB Swing (zweiarmig)", + "trend": "unclear", + "current_level": "Dead-Stop, leicht; 10 kg", + "evidence": "Tag 6: 10kg, 15 dokumentiert.", + "next_action": "Dead-Stop-Technik beibehalten und pro 20s-Intervall genauer dokumentieren.", + "criterion": "Erst steigern, wenn alle Swing-Intervalle in 2 Sessions sauber als Hüft-Schnapp ohne Armheben dokumentiert sind." + } + ], + "plan_adjustments": [ + { + "target": "Woche 2 / Pike", + "suggested_change": "Progressionsstufe von „Pike Hold“ auf „Hold + kleine Senkung“ setzen.", + "reason": "Zwei dokumentierte Einheiten mit 6x20 s Pike Hold sprechen für einen vorsichtigen Variantenwechsel.", + "manual_step": "Im Trainingsplan-Modul bei Pike die nächste Progressionsstufe auswählen.", + "condition": "Nur wenn beide Pike-Einheiten technisch sauber waren; sonst Pike Hold beibehalten." + }, + { + "target": "Woche 2 / Einarm-Rudern ENG", + "suggested_change": "Von „leicht“ auf „etwas mehr“ wechseln, z.B. etwas höhere Last oder kontrollierterer Zug bei gleicher Tabata-Struktur.", + "reason": "Die Wiederholungen lagen in beiden Einheiten stabil bei etwa 8–9 pro Intervall.", + "manual_step": "Im Trainingsplan-Modul die nächste Rudern-ENG-Progression bzw. passende Last eintragen.", + "condition": "Nur wenn Rückenposition und Zugweg sauber bleiben; sonst 4 kg beibehalten." + }, + { + "target": "Woche 2 / Tag 5 leicht", + "suggested_change": "Tag 5 entweder wirklich leicht durchführen oder klar als nicht durchgeführt/Ruhe dokumentieren.", + "reason": "Diese Woche ist Tag 5 als completed markiert, enthält aber keine Übungen.", + "manual_step": "Im Trainingsplan-Modul den Status/Notiz für Tag 5 konsistent setzen.", + "condition": "Nur relevant, wenn Tag 5 erneut ausfällt; sonst normalen leichten Mobility-Tag beibehalten." + } + ], + "warnings": [ + "Tag 5 ist als abgeschlossen markiert, obwohl „Nicht durchgeführt“ notiert ist und alle Übungen offen sind.", + "Beim KB Swing reicht die Angabe „10kg, 15“ noch nicht, um Intervallqualität oder Technikprogression sicher zu bewerten." + ], + "conclusion": "Woche 1 ist ein solider Start; für Woche 2 nur dort steigern, wo Technik über komplette 20s-Intervalle stabil bleibt." + }, + "visuals": { + "charts": [ + { + "title": "Liegestütze · Verlauf", + "type": "line", + "unit": "Reps", + "labels": [ + "T1", + "T4" + ], + "series": [ + { + "name": "Liegestütze", + "values": [ + 6.57, + 6.67 + ] + } + ], + "insight": "Aus den dokumentierten Ergebnisfeldern berechnet." + }, + { + "title": "Bank-Dips (Couch) · Verlauf", + "type": "line", + "unit": "Reps", + "labels": [ + "T1", + "T4" + ], + "series": [ + { + "name": "Bank-Dips (Couch)", + "values": [ + 6.83, + 6.83 + ] + } + ], + "insight": "Aus den dokumentierten Ergebnisfeldern berechnet." + } + ], + "flowcharts": [ + { + "title": "Liegestütze", + "nodes": [ + { + "id": "n1", + "label": "Inkline (hoch)", + "status": "current" + }, + { + "id": "n2", + "label": "Inkline tiefer / Knie", + "status": "next" + }, + { + "id": "n3", + "label": "Knie / Negativ", + "status": "" + }, + { + "id": "n4", + "label": "Negativ + erste Volle", + "status": "" + } + ], + "edges": [ + { + "from": "n1", + "to": "n2" + }, + { + "from": "n2", + "to": "n3" + }, + { + "from": "n3", + "to": "n4" + } + ], + "insight": "Dokumentierter Stand: Inkline (hoch)" + }, + { + "title": "Bank-Dips (Couch)", + "nodes": [ + { + "id": "n1", + "label": "Knie stark gebeugt, klein", + "status": "current" + }, + { + "id": "n2", + "label": "Knie gebeugt, voll", + "status": "next" + }, + { + "id": "n3", + "label": "Füße etwas weiter", + "status": "" + }, + { + "id": "n4", + "label": "Füße weiter weg", + "status": "" + } + ], + "edges": [ + { + "from": "n1", + "to": "n2" + }, + { + "from": "n2", + "to": "n3" + }, + { + "from": "n3", + "to": "n4" + } + ], + "insight": "Dokumentierter Stand: Knie stark gebeugt, klein" + }, + { + "title": "Pike", + "nodes": [ + { + "id": "n1", + "label": "Pike Hold", + "status": "current" + }, + { + "id": "n2", + "label": "Hold + kleine Senkung", + "status": "next" + }, + { + "id": "n3", + "label": "Push-up Hände erhöht", + "status": "" + }, + { + "id": "n4", + "label": "Push-up Boden, klein", + "status": "" + } + ], + "edges": [ + { + "from": "n1", + "to": "n2" + }, + { + "from": "n2", + "to": "n3" + }, + { + "from": "n3", + "to": "n4" + } + ], + "insight": "Dokumentierter Stand: Pike Hold" + } + ] + }, + "version": 1, + "status": "success", + "source_file": "phase-1.json", + "archive_file": "week-01.json", + "sample_note": "Beispieldatensatz für die Android-Implementierung; in Version 2.0 wird diese Datei bei einer Neuanalyse überschrieben." +} diff --git a/android/app/src/test/resources/samples/plans/calihoss-newbie-8-wochen.json b/android/app/src/test/resources/samples/plans/calihoss-newbie-8-wochen.json new file mode 100644 index 0000000..e861e5b --- /dev/null +++ b/android/app/src/test/resources/samples/plans/calihoss-newbie-8-wochen.json @@ -0,0 +1,11135 @@ +{ + "plan_id": "calihoss-newbie-8-wochen", + "name": "CaliHoss Newbie · 8 Wochen", + "revision": 1, + "published_revision": 1, + "created_at": "2026-07-24T09:43:48+00:00", + "updated_at": "2026-07-24T09:43:48+00:00", + "published_at": "2026-07-24T09:43:48+00:00", + "config": { + "colors": { + "ACC2": "#0F766E", + "ACCENT": "#C2410C", + "DARK": "#23201C", + "GOALBG": "#FBF1E9", + "GRID": "#CFC9BF", + "INK": "#1A1A1A", + "KBBG": "#F1ECFB", + "KBCOL": "#6D28D9", + "LIGHT": "#F4F1EC", + "LINK": "#1565C0", + "MUTE": "#6B6256", + "WARMBG": "#EAF1F0" + }, + "days": [ + { + "badge": { + "color": "#0F766E", + "text": "" + }, + "focus": "Ganzkörper A · Push + Pull + Beine + Core", + "num": 1, + "rotations": [ + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-e16264eee6", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d1-r0-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-e944165e56", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d1-r0-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-b91cc1e59f", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r0-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-5ec1c8e378", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d1-r0-e3" + } + ], + "label": "ARBEITSSATZ 1 — 4–8 Reps / Holds nach persönlicher Stufe", + "id": "rotation-arbeitssatz-1-4-8-reps-holds-n-93a7b5d3a5" + }, + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-afde40398a", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d1-r1-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-9ec914e9e2", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d1-r1-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-1ae469095c", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r1-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-750dd86258", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d1-r1-e3" + } + ], + "label": "ARBEITSSATZ 2 — gleiche persönliche Stufe", + "id": "rotation-arbeitssatz-2-gleiche-persoenl-9ea5c29794" + } + ], + "id": "day-ganzkoerper-a-push-pull-beine--425f3fef77" + }, + { + "badge": { + "color": "#6B6256", + "text": "" + }, + "focus": "Regeneration · optional Schulter Conditioning", + "num": 2, + "rotations": [ + { + "exercises": [ + { + "cue": "Playlist 5 exakt videogegeführt; bei Trainingsstart bis zu 3×/Woche, Volumen leicht halten", + "key": "name:bulletproof-shoulders", + "name": "Bulletproof Shoulders", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:bulletproof-shoulders", + "id": "exercise-item-9805179e68", + "exercise_id": "movement-bulletproof-shoulders-c0a078d108", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e0" + }, + { + "cue": "Nur kontrolliert und schmerzfrei; kein Schwung, Nacken lang", + "key": "name:prone-y-t-w-technik", + "name": "Prone Y-T-W Technik", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:prone-y-t-w-technik", + "id": "exercise-1-941443ade3", + "exercise_id": "movement-prone-y-t-w-technik-80560abe41", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e1" + }, + { + "cue": "20–40 Min optional; Gesprächs-Tempo, kein Intervalltraining", + "key": "name:lockerer-spaziergang", + "name": "Lockerer Spaziergang", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:lockerer-spaziergang", + "id": "exercise-2-1156e4d54e", + "exercise_id": "movement-lockerer-spaziergang-cd93978ea1", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e2" + }, + { + "cue": "Schlaf, Muskelkater, Handgelenke, Ellbogen und Schultern beurteilen", + "key": "name:regeneration-pruefen", + "name": "Regeneration prüfen", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:regeneration-pruefen", + "id": "exercise-3-730e1ffe5e", + "exercise_id": "movement-regeneration-pruefen-3d83efff8f", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e3" + } + ], + "label": "KEIN KRAFTTRAINING — OPTIONAL", + "id": "rotation-kein-krafttraining-optional-81a22c40d8" + }, + { + "exercises": [ + { + "cue": "Playlist 4: vier Warm-up-Drills; Finger Plank ergänzen", + "key": "name:wrist-conditioning", + "name": "Wrist Conditioning", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:wrist-conditioning", + "id": "exercise-item-434ab0d6cd", + "exercise_id": "movement-wrist-conditioning-ee8f71f37e", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e0" + }, + { + "cue": "10–30 s in passender Variante; <10 s leichter, bei 30 s schwerer", + "key": "name:finger-plank", + "name": "Finger Plank", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:finger-plank", + "id": "exercise-1-2fe41cea5a", + "exercise_id": "movement-finger-plank-456618869c", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d2-r1-e1" + }, + { + "cue": "Playlist 8 optional; separat und ruhig, nicht als Kraftzirkel", + "key": "name:full-body-mobility", + "name": "Full Body Mobility", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:full-body-mobility", + "id": "exercise-2-9ecfe09b02", + "exercise_id": "movement-full-body-mobility-f4ab17f25a", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e2" + }, + { + "cue": "Keine Zusatz-Sätze für Push, Pull, Beine oder Core", + "key": "name:sonst-vollstaendig-pausieren", + "name": "Sonst vollständig pausieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:sonst-vollstaendig-pausieren", + "id": "exercise-3-aa830acb25", + "exercise_id": "movement-sonst-vollstaendig-pausieren-b291d0cf69", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e3" + } + ], + "label": "OPTIONALE PFLEGE — NICHT BIS ZUR ERMÜDUNG", + "id": "rotation-optionale-pflege-nicht-bis-zur-5f931f935f" + } + ], + "id": "day-regeneration-optional-schulter-8a933efccd" + }, + { + "badge": { + "color": "#0F766E", + "text": "" + }, + "focus": "Ganzkörper B · bessere Ausführung", + "num": 3, + "rotations": [ + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-ef06de6209", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d3-r0-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-e91b0eb7ea", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d3-r0-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-b83bb1b189", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r0-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-3ef2e70f5f", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r0-e3" + } + ], + "label": "ARBEITSSATZ 1 — 4–8 Reps / Holds nach persönlicher Stufe", + "id": "rotation-arbeitssatz-1-4-8-reps-holds-n-31d3e1cf81" + }, + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-7e9d3b8778", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d3-r1-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-0576793b2d", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d3-r1-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-2cc9126f3c", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r1-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-442575f355", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r1-e3" + } + ], + "label": "ARBEITSSATZ 2 — gleiche persönliche Stufe", + "id": "rotation-arbeitssatz-2-gleiche-persoenl-3cf874b60c" + } + ], + "id": "day-ganzkoerper-b-bessere-ausfuehr-cbccb5a18c" + }, + { + "badge": { + "color": "#6B6256", + "text": "" + }, + "focus": "Regeneration · optional Mobility", + "num": 4, + "rotations": [ + { + "exercises": [ + { + "cue": "Videoroutine der Playlist 8; ruhig, kontrolliert, ohne Kraftziel", + "key": "name:full-body-mobility", + "name": "Full Body Mobility", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:full-body-mobility", + "id": "exercise-item-61699773e3", + "exercise_id": "movement-full-body-mobility-f4ab17f25a", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e0" + }, + { + "cue": "Nur schmerzfreier ROM; unterstützt eine saubere Kniebeuge", + "key": "name:sprunggelenk-mobilitaet", + "name": "Sprunggelenk-Mobilität", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:sprunggelenk-mobilitaet", + "id": "exercise-1-9a77e1bd9b", + "exercise_id": "movement-sprunggelenk-mobilitaet-ef05527f38", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e1" + }, + { + "cue": "Kontrollierte Bewegungen, kein aggressives Dehnen", + "key": "name:hueft-mobilitaet", + "name": "Hüft-Mobilität", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:hueft-mobilitaet", + "id": "exercise-2-e78ed44a90", + "exercise_id": "movement-hueft-mobilitaet-7836e93f5c", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e2" + }, + { + "cue": "Sanft bewegen; keine Zusatzermüdung für die nächste Einheit", + "key": "name:brustwirbelsaeule-schultern", + "name": "Brustwirbelsäule / Schultern", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:brustwirbelsaeule-schultern", + "id": "exercise-3-e8e41d6352", + "exercise_id": "movement-brustwirbelsaeule-schultern-02622f8d13", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e3" + } + ], + "label": "OPTIONAL: PLAYLIST 8 — MOBILITY", + "id": "rotation-optional-playlist-8-mobility-5e1ae6ee55" + }, + { + "exercises": [ + { + "cue": "Optional locker bewegen", + "key": "name:spaziergang", + "name": "Spaziergang", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:spaziergang", + "id": "exercise-item-67777b2931", + "exercise_id": "movement-spaziergang-feb087e265", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e0" + }, + { + "cue": "Regeneration ist Teil der Progression", + "key": "name:schlaf-priorisieren", + "name": "Schlaf priorisieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:schlaf-priorisieren", + "id": "exercise-1-72400ba5c9", + "exercise_id": "movement-schlaf-priorisieren-b6845205e5", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e1" + }, + { + "cue": "War die letzte Einheit näher am Versagen, nächste Einheit leichter wählen", + "key": "name:1-2-reps-reserve-pruefen", + "name": "1–2 Reps Reserve prüfen", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:1-2-reps-reserve-pruefen", + "id": "exercise-2-db67a28ad8", + "exercise_id": "movement-1-2-reps-reserve-pruefen-85e3b5af37", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e2" + }, + { + "cue": "Stechender oder zunehmender Gelenkschmerz: Übung anpassen oder pausieren", + "key": "name:schmerzcheck", + "name": "Schmerzcheck", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:schmerzcheck", + "id": "exercise-3-b06e0c5872", + "exercise_id": "movement-schmerzcheck-f658f713e7", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e3" + } + ], + "label": "REGENERATION — MINIMAL HALTEN", + "id": "rotation-regeneration-minimal-halten-c450e97a0b" + } + ], + "id": "day-regeneration-optional-mobility-0f3d34036d" + }, + { + "badge": { + "color": "#0F766E", + "text": "" + }, + "focus": "Ganzkörper C · Wochenabschluss", + "light": false, + "num": 5, + "rotations": [ + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-cbbb28ae57", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d5-r0-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-b5cdc5acfc", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d5-r0-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-c8c3bf00a5", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d5-r0-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-72a4e18f04", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d5-r0-e3" + } + ], + "label": "ARBEITSSATZ 1 — 4–8 Reps / Holds nach persönlicher Stufe", + "id": "rotation-arbeitssatz-1-4-8-reps-holds-n-a937682d98" + }, + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-3f953adfdb", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d5-r1-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-5fda83a424", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d5-r1-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-b40c4b6a58", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d5-r1-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-ce921d5b08", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d5-r1-e3" + } + ], + "label": "ARBEITSSATZ 2 — gleiche persönliche Stufe", + "id": "rotation-arbeitssatz-2-gleiche-persoenl-c713162cb9" + } + ], + "id": "day-ganzkoerper-c-wochenabschluss-f1d536f095" + }, + { + "badge": { + "color": "#6B6256", + "text": "" + }, + "color": "#6B6256", + "focus": "Vollständige Erholung · Wochencheck", + "note": "Kein Krafttraining. Notiere pro Hauptübung die persönliche Stufe, Reps/Haltezeit, Unterstützung und eine technische Verbesserung. Sonntag bleibt ebenfalls frei bzw. sehr locker.", + "note_bg": "#F4F1EC", + "num": 6, + "rotations": [ + { + "exercises": [ + { + "cue": "Stufe, 2 Sätze, Reps/Haltezeit und Techniknotiz", + "key": "name:push-protokollieren", + "name": "Push protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:push-protokollieren", + "id": "exercise-item-5e98211225", + "exercise_id": "movement-push-protokollieren-e0770774a1", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r0-e0" + }, + { + "cue": "Stufe, Unterstützung/Band/Fußhilfe und Techniknotiz", + "key": "name:pull-protokollieren", + "name": "Pull protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:pull-protokollieren", + "id": "exercise-1-4ffe56788d", + "exercise_id": "movement-pull-protokollieren-71a9be182e", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r0-e1" + }, + { + "cue": "Stufe, Tiefe, Unterstützung bzw. Kettlebell-Gewicht", + "key": "name:beine-protokollieren", + "name": "Beine protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:beine-protokollieren", + "id": "exercise-2-d163fb171a", + "exercise_id": "movement-beine-protokollieren-712a79f464", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r0-e2" + }, + { + "cue": "Plank-Variante und saubere Haltezeit", + "key": "name:core-protokollieren", + "name": "Core protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:core-protokollieren", + "id": "exercise-3-a6dff34f5c", + "exercise_id": "movement-core-protokollieren-279940b6d0", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d6-r0-e3" + } + ], + "label": "WOCHENCHECK — NUR DOKUMENTIEREN", + "id": "rotation-wochencheck-nur-dokumentieren-46027202a0" + }, + { + "exercises": [ + { + "cue": "Mindestens 10 s; bis 30 s halten, sonst Schwierigkeit senken", + "key": "name:startposition-pruefen", + "name": "Startposition prüfen", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:startposition-pruefen", + "id": "exercise-item-faff57b16d", + "exercise_id": "movement-startposition-pruefen-0676749716", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e0" + }, + { + "cue": "Bis 8 saubere Wiederholungen; vorher nicht überspringen", + "key": "name:negative-pruefen", + "name": "Negative prüfen", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:negative-pruefen", + "id": "exercise-1-2c33847f25", + "exercise_id": "movement-negative-pruefen-363284950d", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e1" + }, + { + "cue": "Unter 4: assistiert bevorzugen; ab 4 volle Reps trainieren", + "key": "name:volle-reps-pruefen", + "name": "Volle Reps prüfen", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:volle-reps-pruefen", + "id": "exercise-2-f432054dde", + "exercise_id": "movement-volle-reps-pruefen-4955104cc2", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e2" + }, + { + "cue": "Erst nach 8 sauberen vollen Reps; dann Zyklus neu starten", + "key": "name:schwierigkeit-erhoehen", + "name": "Schwierigkeit erhöhen", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:schwierigkeit-erhoehen", + "id": "exercise-3-00836121d2", + "exercise_id": "movement-schwierigkeit-erhoehen-2b4e278d42", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e3" + } + ], + "label": "ENTSCHEIDUNG FÜR NÄCHSTE WOCHE", + "id": "rotation-entscheidung-fuer-naechste-woc-51b5dca3e8" + } + ], + "id": "day-vollstaendige-erholung-wochenc-b3998121a6" + } + ], + "front": { + "baseline_head": "Standortbestimmung & Tracking", + "baseline_intro": "Ermittle vor Einheit 1 für jede Kategorie die erste nicht bestandene Stufe des Flowcharts. In Woche 8 unter identischen Bedingungen wiederholen.", + "baseline_rows": [ + [ + "Test", + "Tag 0", + "Woche 8" + ], + [ + "Push: aktuelle Stufe + saubere Reps/Haltezeit", + "", + "" + ], + [ + "High Plank (saubere Sekunden, max. 30 notieren)", + "", + "" + ], + [ + "Pull: aktuelle Stufe + Unterstützung", + "", + "" + ], + [ + "Active Hang (saubere Sekunden, max. 30 notieren)", + "", + "" + ], + [ + "Beine: aktuelle Stufe + saubere Reps/Haltezeit", + "", + "" + ], + [ + "Plank: Variante + saubere Sekunden", + "", + "" + ], + [ + "Absolvierte Kraftsessions (von 24)", + "0", + "" + ], + [ + "Körpergewicht (kg)", + "95", + "" + ] + ], + "flow_rows": [ + [ + "Teil", + "Inhalt", + "Zeit" + ], + [ + "Warm-up", + "Ganzkörper + Wrists + Scapula, nicht ermüdend", + "5–10 Min" + ], + [ + "Arbeitssatz 1", + "Push · Pull · Beine · Core", + "individuell" + ], + [ + "Arbeitssatz 2", + "gleiche Stufen; volle Erholung zulassen", + "individuell" + ], + [ + "Optional", + "Playlist 5 Shoulders / Playlist 8 Mobility separat", + "nach Bedarf" + ], + [ + "", + "Gesamt Krafttraining", + "~25–40 Min" + ] + ], + "goals_box": "Nach 8 Wochen zählt kein künstliches Wiederholungsziel, sondern die Newbie-Kriterien:
•  3 Ganzkörpereinheiten pro Woche sind zur stabilen Gewohnheit geworden.
•  Du beherrschst je eine saubere, wiederholbare Push-, Pull-, Bein- und Core-Variante.
•  Du kennst bei jeder Bewegung deine aktuelle Stufe und kannst sie über Technik, Reps, Haltezeit, ROM oder weniger Unterstützung steigern.
•  Ein Wechsel zur Beginner Stage erfolgt nur, wenn die Grundbewegungen nicht mehr unbeholfen wirken und die Regeneration stabil bleibt.", + "goals_head": "Ziel der 8 Wochen: Newbie Stage sauber abschließen", + "goals_note": "Flowchart-Regel für jede Übung separat: Startposition mindestens 10 s, bis 30 s ausbauen. Ausnahme Beine: Deep Squat Hold jeden 2. Tag, 1–2×60 s ungewichtet oder 1–2×30 s gewichtet; erst in der tiefen Position sicher und komfortabel werden. Dann bis 8 saubere Negative. Danach volle Reps testen: unter 4 volle Reps assistiert bevorzugen, Teil-Reps nur alternativ; ab 4 volle Reps trainieren, bis 8 erreicht sind. Erst dann Schwierigkeit erhöhen und den Zyklus neu starten. Die Kalenderwoche erzwingt keinen Aufstieg.", + "kb_box": "Die Kettlebell bleibt in dieser Newbie Stage ein Progressionswerkzeug, kein zweites Programm:
•  Hauptübung Beine bleibt die persönliche Squat-Stufe.
•  Sobald 8 saubere Bodyweight Squats kontrolliert möglich sind, kann ein leichter Goblet Squat die Schwierigkeit erhöhen.
•  Keine zusätzlichen Swings, Presses, Rows oder Kettlebell-Zirkel in diesem Block. So bleiben Umfang und Übungsauswahl beim CaliHoss-Newbie-Schema.", + "kb_head": "Rolle der Kettlebell", + "lib_head": "CaliHoss-Playlists & Übungsbibliothek", + "lib_intro": "Die Links führen direkt zur Kursseite bzw. zum passenden Playlist-Abschnitt. Die Videos enthalten die eigentliche Technikschulung; dieses Dokument strukturiert die Anwendung.", + "reminder_head": "Unveränderliche Regeln", + "reminders": [ + "3× pro Woche Ganzkörper. Je Einheit genau eine Hauptübung aus Push, Pull, Beine und Core.", + "2 Arbeitssätze. Dynamisch 4–8 Reps; isometrisch normalerweise 10–30 s; Deep Squat Hold als Sonderregel 1–2×60 s ungewichtet oder 1–2×30 s gewichtet jeden 2. Tag. Etwa 1–2 saubere Reps vor dem Versagen stoppen.", + "Technik vor Zahlen. Pro Einheit mindestens eine Variable verbessern: Position, Alignment, ROM, Tempo, Spannung, Reps, Haltezeit oder Unterstützung.", + "Kein Kalenderaufstieg. Jede Kategorie folgt unabhängig dem Flowchart und bleibt auf der ersten nicht bestandenen Stufe.", + "Nach 2 stagnierenden Sessions prüfen: Erholung, Ermüdung, Übungsstufe und Unterstützung anpassen." + ], + "session_how_body": "Kein Tabata. Nach dem 5–10-minütigen Ganzkörper-Warm-up absolvierst du Arbeitssatz 1 für Push, Pull, Beine und Core mit ausreichenden Pausen. Danach folgt Arbeitssatz 2 derselben vier persönlichen Stufen. Dynamische Übungen: 4–8 saubere Reps; Holds normalerweise 10–30 s; Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet jeden 2. Tag. Intensität: 80–90 % bzw. 1–2 Reps Reserve. Pausiere so lange, bis die nächste Ausführung sauber ist.", + "session_how_head": "So funktioniert eine Ganzkörpereinheit", + "sheets_note": "Wochenrhythmus: Tag 1, 3 und 5 Krafttraining; dazwischen Regeneration. Tag 2/4 enthalten nur optionale Conditioning-/Mobility-Hinweise, Tag 6 ist vollständig frei. Alle Hauptübungen folgen dem persönlichen Progressions-Flowchart.", + "timer_note": "Timer: normale Stoppuhr für Holds und Pausen; kein 20/10-Intervalltimer." + }, + "library": [ + { + "color": "ACC2", + "group": "KURS & PROGRAMM", + "items": [ + { + "desc": "Programmstufen, Grundübungen, Begriffe und 5–10-minütiges bewegungsspezifisches Warm-up.", + "name": "Playlist 1: Foundation", + "url": "https://calihoss.me/learn/calisthenics/#playlist-1-foundation" + }, + { + "desc": "Technik, Bewegungsentwicklung, Volumen, Mikroprogression und das Progression Decision Flowchart.", + "name": "Playlist 2: Progression", + "url": "https://calihoss.me/learn/calisthenics/#playlist-2-progression" + }, + { + "desc": "3× Ganzkörper pro Woche; je eine Übung aus Push, Pull, Beine und Core; 2×4–8 bei 80–90 % Intensität.", + "name": "Newbie Stage", + "url": "https://calihoss.me/learn/calisthenics/#newbie-stage" + }, + { + "desc": "Klimmzugstangen, Unterstützungsmöglichkeiten und niedrige Stangen/Parallettes.", + "name": "Playlist 3: Equipment", + "url": "https://calihoss.me/learn/calisthenics/#playlist-3-equipment" + } + ] + }, + { + "color": "ACC2", + "group": "WARM-UP · WRISTS · SHOULDERS · SCAPULA", + "items": [ + { + "desc": "Vier Wrist-Warm-up-Übungen in jeder Einheit; Finger Planks ausdrücklich ergänzen.", + "name": "Playlist 4: Wrists and Forearms", + "url": "https://calihoss.me/learn/calisthenics/#playlist-4-wrists-and-forearms" + }, + { + "desc": "Passende Variante 10–30 s. Erst nach 30 s pro Position zu schwereren Varianten oder Combos wechseln.", + "name": "Finger Plank", + "url": "https://calihoss.me/learn/calisthenics/#wrist-warm-up-routine" + }, + { + "desc": "Bulletproof Shoulders, Prone Y-T-W und Serratus-/Scapula-Kontrolle; bei Trainingsstart bis zu 3×/Woche.", + "name": "Playlist 5: Shoulders", + "url": "https://calihoss.me/learn/calisthenics/#playlist-5-shoulders" + }, + { + "desc": "Push-up: Protraktion. Row: Retraction. Dip: Depression. Pike/Handstand: Elevation.", + "name": "Scapula-Grundrichtungen", + "url": "https://calihoss.me/learn/calisthenics/#the-scapula" + } + ] + }, + { + "color": "ACC2", + "group": "PUSH · PUSH-UP-PROGRESSION", + "items": [ + { + "desc": "Auswahl, erste Push-up-Progressionen, Knee Push-up, Form, Handposition und Tempo.", + "name": "Playlist 15: Push-ups", + "url": "https://calihoss.me/learn/calisthenics/#playlist-15-push-ups" + }, + { + "desc": "10 s beherrschen, dann bis 30 s ausbauen. Bei <10 s Hände erhöhen oder Knie verwenden.", + "name": "Startposition: High Plank", + "url": "https://calihoss.me/learn/calisthenics/#progression-decision-flowchart" + }, + { + "desc": "Langsam durch den kontrollierbaren vollen ROM absenken; Ziel 4–8, Übergang nach 8 sauberen Negativen.", + "name": "Negative Push-ups", + "url": "https://calihoss.me/learn/calisthenics/#beginner-push-up-workout" + }, + { + "desc": "Unter 4 volle Reps: Knie-/erhöhte Push-ups bevorzugen. Ab 4 volle Reps trainieren; bei 8 erschweren.", + "name": "Assistiert → volle Push-ups", + "url": "https://calihoss.me/learn/calisthenics/#first-push-up-progressions" + } + ] + }, + { + "color": "ACC2", + "group": "PULL · PULL-UP-PROGRESSION", + "items": [ + { + "desc": "Erster Pull-up, Scapular Pull-ups, Hangs und Unterstützung. Für Full Body werden assistierte Pull-ups empfohlen.", + "name": "Playlist 14: Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#playlist-14-pull-ups" + }, + { + "desc": "Lats durch Außenrotation aktivieren; leichte Scapula-Depression, nicht maximal nach unten ziehen.", + "name": "Startposition: Active Hang", + "url": "https://calihoss.me/learn/calisthenics/#hangs-technique" + }, + { + "desc": "Erste Pull-up-Progression: Ellbogen gestreckt, Schulterblätter kontrolliert depressieren, Lats aktivieren.", + "name": "Scapular Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#scapular-pull-ups-technique" + }, + { + "desc": "Obere Position per Hocker erreichen, langsam absenken; 4–8 saubere Wiederholungen.", + "name": "Negative Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#first-pull-up-progression" + }, + { + "desc": "Fuß- oder Bandhilfe so wählen, dass 4–8 volle kontrollierte Reps mit 1–2 Reps Reserve möglich sind.", + "name": "Assistierte Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#first-pull-up-progression" + }, + { + "desc": "Ab 4 sauberen vollen Reps diese trainieren; bei 8 Schwierigkeit erhöhen und Flowchart neu beginnen.", + "name": "Volle Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#progression-decision-flowchart" + }, + { + "desc": "Nur als Alternative, falls keine Pull-up-Möglichkeit vorhanden ist; Newbies dürfen zunächst eine Pull-Bewegung wählen.", + "name": "Playlist 13: Inverted Rows", + "url": "https://calihoss.me/learn/calisthenics/#playlist-13-inverted-rows" + } + ] + }, + { + "color": "ACC2", + "group": "BEINE · CORE", + "items": [ + { + "desc": "Beintraining als Fundament; im Full-Body-Plan mindestens Squat oder Hinge abdecken.", + "name": "Playlist 10: Legs", + "url": "https://calihoss.me/learn/calisthenics/#playlist-10-legs" + }, + { + "desc": "Zuerst Deep Squat Hold jeden 2. Tag: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet. Erst komfortabel, dann Negative, assistierte und volle Squats 4–8.", + "name": "Squat-Progression", + "url": "https://calihoss.me/learn/calisthenics/#progression-decision-flowchart" + }, + { + "desc": "Erst nach 8 sauberen Bodyweight Squats als Schwierigkeitserhöhung; leichte Glocke und voller kontrollierter ROM.", + "name": "Goblet Squat mit Kettlebell", + "url": "https://calihoss.me/learn/calisthenics/#develop-the-movement" + }, + { + "desc": "Core als Stabilisation und Anti-Bewegung; für Full Body kann eine Core-Übung je Einheit ergänzt werden.", + "name": "Playlist 9: Core", + "url": "https://calihoss.me/learn/calisthenics/#playlist-9-core" + }, + { + "desc": "Plank als fundamentale Core-Übung; 10–30 s, danach Schwierigkeit oder Widerstand erhöhen.", + "name": "Playlist 12: Planks", + "url": "https://calihoss.me/learn/calisthenics/#playlist-12-planks" + } + ] + }, + { + "color": "ACC2", + "group": "OPTIONAL · SPÄTER", + "items": [ + { + "desc": "Separate Full-Body-Mobility-Routine 2–4×/Woche möglich; nicht als zusätzliche Kraftsession behandeln.", + "name": "Playlist 8: Mobility", + "url": "https://calihoss.me/learn/calisthenics/#playlist-8-mobility" + }, + { + "desc": "Noch nicht Hauptübung dieses Blocks. Später: Dip Support Hold → Scapular Dips → Negative → assistierte → volle Dips.", + "name": "Playlist 16: Dips", + "url": "https://calihoss.me/learn/calisthenics/#playlist-16-dips" + } + ] + } + ], + "meta": { + "days": 6, + "doc_title": "CaliHoss Newbie Stage · 8-Wochen-Plan", + "footer_left": "CaliHoss Newbie Stage · 3× Ganzkörper · Progression per Flowchart", + "output_name": "CaliHoss-Newbie-8-Wochen.pdf", + "subtitle": "Blutiger Anfänger · 3× Ganzkörper/Woche · 2×4–8 Reps · Holds meist 10–30 s · Deep Squat 60/30 s · Kettlebell nur als Progression", + "title": "CaliHoss Newbie Stage – 8 Wochen", + "weeks": 8 + }, + "phases": { + "items": [ + { + "name": "Phase 1 · Level finden & Technik", + "params": "Für jede Kategorie die erste nicht bestandene Flowchart-Stufe bestimmen. 2 Sätze, 4–8 Reps bzw. meist 10–30 s, 1–2 Reps Reserve. Deep Squat Hold separat jeden 2. Tag: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet. Kein automatischer Aufstieg.", + "weeks": 1, + "id": "phase-phase-1-level-finden-amp-techn-ae1454813c" + }, + { + "name": "Phase 2 · Level finden & Technik", + "params": "Für jede Kategorie die erste nicht bestandene Flowchart-Stufe bestimmen. 2 Sätze, 4–8 Reps bzw. meist 10–30 s, 1–2 Reps Reserve. Deep Squat Hold separat jeden 2. Tag: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet. Kein automatischer Aufstieg.", + "weeks": 1, + "id": "phase-phase-2-level-finden-amp-techn-bc8640a305" + }, + { + "name": "Phase 3 · Stabilisieren & mikroprogressieren", + "params": "Persönliche Stufen beibehalten und pro Session mindestens eine Variable verbessern: Position, Alignment, ROM, Tempo, Spannung, Reps, Haltezeit oder weniger Hilfe.", + "weeks": 1, + "id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec" + }, + { + "name": "Phase 4 · Stabilisieren & mikroprogressieren", + "params": "Persönliche Stufen beibehalten und pro Session mindestens eine Variable verbessern: Position, Alignment, ROM, Tempo, Spannung, Reps, Haltezeit oder weniger Hilfe.", + "weeks": 1, + "id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9" + }, + { + "name": "Phase 5 · Kriterienbasiert aufsteigen", + "params": "Nur nach erfülltem Kriterium wechseln: 30 s Startposition, 8 Negative, 4 volle Reps zum Einstieg, 8 volle Reps vor der nächsten Schwierigkeit. Jede Übung separat.", + "weeks": 1, + "id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734" + }, + { + "name": "Phase 6 · Kriterienbasiert aufsteigen", + "params": "Nur nach erfülltem Kriterium wechseln: 30 s Startposition, 8 Negative, 4 volle Reps zum Einstieg, 8 volle Reps vor der nächsten Schwierigkeit. Jede Übung separat.", + "weeks": 1, + "id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8" + }, + { + "name": "Phase 7 · Festigen & neu testen", + "params": "Aktuelle Varianten sauber wiederholen. Ende Woche 8 dieselben Tests wie an Tag 0. Wechsel zur Beginner Stage nur bei sauberer, wiederholbarer Technik und stabiler 3×/Woche-Gewohnheit.", + "weeks": 1, + "id": "phase-phase-7-festigen-amp-neu-teste-65823eedde" + }, + { + "name": "Phase 8 · Festigen & neu testen", + "params": "Aktuelle Varianten sauber wiederholen. Ende Woche 8 dieselben Tests wie an Tag 0. Wechsel zur Beginner Stage nur bei sauberer, wiederholbarer Technik und stabiler 3×/Woche-Gewohnheit.", + "weeks": 1, + "id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3" + } + ], + "param_light": "Regenerationstag: kein Krafttraining; optional leichte Mobility oder Conditioning ohne Ermüdungsziel." + }, + "prepost": { + "1": { + "cooldown": "optional 2–5 Min locker gehen und Atmung beruhigen", + "stretch": "Optional separat: Playlist 8 Full Body Mobility; nicht bis zur Ermüdung. Bulletproof Shoulders nicht hier, sondern nur als eigener Conditioning-Block am Regenerationstag.", + "warmup": "1–2 Min locker bewegen · Playlist 4 Wrist Conditioning (4 Übungen) + Finger Plank · Wall Scapular Push-ups 4–8 · Active Hang/Scapular Pull-up sehr leicht · 1–2 kontrollierte Squat-Rehearsals; Deep Squat Hold ggf. als Bein-Hauptübung, nicht als Warm-up" + }, + "2": { + "cooldown": "Regeneration · optional lockerer Spaziergang", + "stretch": "Kein zweiter Schulterdurchgang im Cool-down: Bulletproof Shoulders nur einmal als separater Conditioning-Block oben; Wrist Conditioning bei Bedarf leicht.", + "warmup": "Kein Warm-up nötig · kein Krafttraining" + }, + "3": { + "cooldown": "optional 2–5 Min locker gehen und Atmung beruhigen", + "stretch": "Optional separat: Playlist 8 Full Body Mobility; nicht bis zur Ermüdung. Bulletproof Shoulders nicht hier, sondern nur als eigener Conditioning-Block am Regenerationstag.", + "warmup": "1–2 Min locker bewegen · Playlist 4 Wrist Conditioning (4 Übungen) + Finger Plank · Wall Scapular Push-ups 4–8 · Active Hang/Scapular Pull-up sehr leicht · 1–2 kontrollierte Squat-Rehearsals; Deep Squat Hold ggf. als Bein-Hauptübung, nicht als Warm-up" + }, + "4": { + "cooldown": "Ruhig ausklingen, keine Zusatzbelastung", + "stretch": "Optional: Playlist 8 Full Body Mobility, kontrolliert und schmerzfrei", + "warmup": "Kein Warm-up nötig · Mobility bei Bedarf kurz vorbereiten" + }, + "5": { + "cooldown": "optional 2–5 Min locker gehen und Atmung beruhigen", + "stretch": "Optional separat: Playlist 8 Full Body Mobility; nicht bis zur Ermüdung. Bulletproof Shoulders nicht hier, sondern nur als eigener Conditioning-Block am Regenerationstag.", + "warmup": "1–2 Min locker bewegen · Playlist 4 Wrist Conditioning (4 Übungen) + Finger Plank · Wall Scapular Push-ups 4–8 · Active Hang/Scapular Pull-up sehr leicht · 1–2 kontrollierte Squat-Rehearsals; Deep Squat Hold ggf. als Bein-Hauptübung, nicht als Warm-up" + }, + "6": { + "cooldown": "Vollständige Erholung", + "stretch": "Nur Wochenprotokoll und Flowchart-Entscheidung für die nächste Woche", + "warmup": "Kein Training" + } + }, + "stages": { + "atw": { + "name": "Progressionsregel", + "steps": [ + { + "name": "Startposition meist 10–30 s; Deep Squat siehe Sonderregel", + "id": "step-startposition-meist-10-30-s-de-cf24fe6b0a", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Startposition meist 10–30 s; Deep Squat siehe Sonderregel", + "id": "step-startposition-meist-10-30-s-de-7f8eec1c9e", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Negative bis 8 sauber", + "id": "step-negative-bis-8-sauber-2ee7d2ec3a", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Negative bis 8 sauber", + "id": "step-negative-bis-8-sauber-5aa0d823f3", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "assistiert bevorzugt / Teil-Reps alternativ", + "id": "step-assistiert-bevorzugt-teil-reps-3cfdfbf0c9", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "assistiert bevorzugt / Teil-Reps alternativ", + "id": "step-assistiert-bevorzugt-teil-reps-7086055dcf", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "volle Reps 4–8, dann erschweren", + "id": "step-volle-reps-4-8-dann-erschweren-29ef936c03", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "volle Reps 4–8, dann erschweren", + "id": "step-volle-reps-4-8-dann-erschweren-1d2e3ec8a8", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-progressionsregel-2fbf7c1952", + "key": "atw" + }, + "bic": { + "name": "Technik-Fokus", + "steps": [ + { + "name": "Position & Alignment", + "id": "step-position-amp-alignment-ccfec71b67", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Position & Alignment", + "id": "step-position-amp-alignment-a0240a6a9f", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ROM & Bewegungspfad", + "id": "step-rom-amp-bewegungspfad-113bd351b3", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ROM & Bewegungspfad", + "id": "step-rom-amp-bewegungspfad-9739ca3bcd", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Tempo & Spannung", + "id": "step-tempo-amp-spannung-788af93895", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Tempo & Spannung", + "id": "step-tempo-amp-spannung-66e06e922b", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bracing, Atmung & Fokus", + "id": "step-bracing-atmung-amp-fokus-fcc6cb89d5", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bracing, Atmung & Fokus", + "id": "step-bracing-atmung-amp-fokus-76bffcdf46", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-technik-fokus-e875e1dfde", + "key": "bic" + }, + "bridge": { + "name": "Mikroprogression", + "steps": [ + { + "name": "Technik verbessern", + "id": "step-technik-verbessern-18e45c232f", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Technik verbessern", + "id": "step-technik-verbessern-af64a8d335", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+1 saubere Rep / +Sekunden", + "id": "step-1-saubere-rep-sekunden-bea0a6f0f2", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+1 saubere Rep / +Sekunden", + "id": "step-1-saubere-rep-sekunden-9df740632e", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "etwas weniger Unterstützung", + "id": "step-etwas-weniger-unterstuetzung-18e44cfff5", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "etwas weniger Unterstützung", + "id": "step-etwas-weniger-unterstuetzung-a927afae34", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "schwierigere Variante", + "id": "step-schwierigere-variante-75dc6896d9", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "schwierigere Variante", + "id": "step-schwierigere-variante-7856fc9277", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-mikroprogression-b0dc3add0c", + "key": "bridge" + }, + "calf": { + "name": "Stagnationscheck", + "steps": [ + { + "name": "Schlaf & Erholung prüfen", + "id": "step-schlaf-amp-erholung-pruefen-84e12e7461", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Schlaf & Erholung prüfen", + "id": "step-schlaf-amp-erholung-pruefen-06ccaffb49", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Fatigue / Zusatztraining prüfen", + "id": "step-fatigue-zusatztraining-pruefen-6a01f09acf", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Fatigue / Zusatztraining prüfen", + "id": "step-fatigue-zusatztraining-pruefen-cc528ace63", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "leichtere Stufe oder mehr Hilfe", + "id": "step-leichtere-stufe-oder-mehr-hilf-ea4b6f2f82", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "leichtere Stufe oder mehr Hilfe", + "id": "step-leichtere-stufe-oder-mehr-hilf-53b23ce8bd", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "bei 2 Sessions ohne Fortschritt anpassen", + "id": "step-bei-2-sessions-ohne-fortschrit-551809a978", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "bei 2 Sessions ohne Fortschritt anpassen", + "id": "step-bei-2-sessions-ohne-fortschrit-609e7a3a40", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + } + ], + "id": "progression-stagnationscheck-5158f16cab", + "key": "calf" + }, + "carry": { + "name": "Finger Plank", + "steps": [ + { + "name": "Wand/Tisch 10–30 s", + "id": "step-wand-tisch-10-30-s-4193ed5643", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Wand/Tisch 10–30 s", + "id": "step-wand-tisch-10-30-s-a021af05f8", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Knievariante 10–30 s", + "id": "step-knievariante-10-30-s-1110175d1e", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Knievariante 10–30 s", + "id": "step-knievariante-10-30-s-3d1f995c08", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Boden 10–30 s", + "id": "step-boden-10-30-s-14d5aa23b6", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Boden 10–30 s", + "id": "step-boden-10-30-s-569d7dc2a6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Combos erst nach 30 s je Position", + "id": "step-combos-erst-nach-30-s-je-posit-05ff31d1fa", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Combos erst nach 30 s je Position", + "id": "step-combos-erst-nach-30-s-je-posit-ba186d50fe", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-finger-plank-f9d5a3010c", + "key": "carry" + }, + "db": { + "name": "Wrist Conditioning", + "steps": [ + { + "name": "4 Warm-up-Drills", + "id": "step-4-warm-up-drills-80a8839e87", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "4 Warm-up-Drills", + "id": "step-4-warm-up-drills-c1d52d55ad", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+ Finger Plank", + "id": "step-finger-plank-84c96599d8", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+ Finger Plank", + "id": "step-finger-plank-6646fd3c94", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ruhig, nicht ermüdend", + "id": "step-ruhig-nicht-ermuedend-a03b9ca9ea", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ruhig, nicht ermüdend", + "id": "step-ruhig-nicht-ermuedend-eb30e4ad28", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bei Schmerz Variante anpassen", + "id": "step-bei-schmerz-variante-anpassen-96b23d3c5d", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bei Schmerz Variante anpassen", + "id": "step-bei-schmerz-variante-anpassen-bad6a9c798", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-wrist-conditioning-67c4bfa8a0", + "key": "db" + }, + "dips": { + "name": "Dip-Progression · später", + "steps": [ + { + "name": "Dip Support Hold 10–30 s", + "id": "step-dip-support-hold-10-30-s-7db82c14d2", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Dip Support Hold 10–30 s", + "id": "step-dip-support-hold-10-30-s-38a65bd556", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Scapular Dips 4–8", + "id": "step-scapular-dips-4-8-54e50e0deb", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Scapular Dips 4–8", + "id": "step-scapular-dips-4-8-9861d79df3", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Negative / assistierte Dips 4–8", + "id": "step-negative-assistierte-dips-4-8-51cef25e2f", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.6 + }, + { + "name": "Negative / assistierte Dips 4–8", + "id": "step-negative-assistierte-dips-4-8-26038f8ce6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.6 + }, + { + "name": "volle Dips 4–8", + "id": "step-volle-dips-4-8-97ebbfbf20", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "volle Dips 4–8", + "id": "step-volle-dips-4-8-9e3f715ddd", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-dip-progression-spaeter-b76ba51f6d", + "key": "dips" + }, + "gobletkb": { + "name": "Goblet Squat · KB", + "steps": [ + { + "name": "erst nach 8 BW-Squats", + "id": "step-erst-nach-8-bw-squats-cb5e6f79b4", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "erst nach 8 BW-Squats", + "id": "step-erst-nach-8-bw-squats-5fb4c9827c", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, voller ROM", + "id": "step-leicht-voller-rom-223d7ba7df", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, voller ROM", + "id": "step-leicht-voller-rom-9cc35c41ef", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 saubere Reps", + "id": "step-4-8-saubere-reps-9cc21ffc71", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 saubere Reps", + "id": "step-4-8-saubere-reps-05d850b4a5", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "bei 8 Schwierigkeit erhöhen", + "id": "step-bei-8-schwierigkeit-erhoehen-77b0f8ad55", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "bei 8 Schwierigkeit erhöhen", + "id": "step-bei-8-schwierigkeit-erhoehen-ea22a8e15c", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-goblet-squat-kb-9d21334b6f", + "key": "gobletkb" + }, + "halo": { + "name": "Bulletproof Shoulders", + "steps": [ + { + "name": "videogeführt leicht", + "id": "step-videogefuehrt-leicht-56eeb28b68", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "videogeführt leicht", + "id": "step-videogefuehrt-leicht-9826cc9337", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis 3×/Woche bei Trainingsstart", + "id": "step-bis-3-woche-bei-trainingsstart-00cccb34ea", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis 3×/Woche bei Trainingsstart", + "id": "step-bis-3-woche-bei-trainingsstart-78b10cb42c", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "kein Zusatzversagen", + "id": "step-kein-zusatzversagen-0c124a2d71", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "kein Zusatzversagen", + "id": "step-kein-zusatzversagen-09f1f363ad", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Volumen an Erholung anpassen", + "id": "step-volumen-an-erholung-anpassen-23680fda4b", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Volumen an Erholung anpassen", + "id": "step-volumen-an-erholung-anpassen-4d39ebbf93", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-bulletproof-shoulders-baa51fb7a5", + "key": "halo" + }, + "holl": { + "name": "Bracing", + "steps": [ + { + "name": "Rumpfspannung finden", + "id": "step-rumpfspannung-finden-715833661a", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Rumpfspannung finden", + "id": "step-rumpfspannung-finden-286734cc2c", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Atmung koordinieren", + "id": "step-atmung-koordinieren-356cc152d1", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Atmung koordinieren", + "id": "step-atmung-koordinieren-7ca2ee55df", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Alignment halten", + "id": "step-alignment-halten-0c7f26b946", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Alignment halten", + "id": "step-alignment-halten-3e1b54d2ac", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Spannung unter Last behalten", + "id": "step-spannung-unter-last-behalten-a0bbb8fda1", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Spannung unter Last behalten", + "id": "step-spannung-unter-last-behalten-a0e1b7b5a5", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-bracing-4a444af3ab", + "key": "holl" + }, + "kbdl": { + "name": "Hinge · erst Beginner Stage", + "steps": [ + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-e6ff14d812", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-62775896bf", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Squat-Stufe priorisieren", + "id": "step-squat-stufe-priorisieren-2eb20659e1", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Squat-Stufe priorisieren", + "id": "step-squat-stufe-priorisieren-f2390d80ab", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "später Hinge ergänzen", + "id": "step-spaeter-hinge-ergaenzen-490b1761f0", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "später Hinge ergänzen", + "id": "step-spaeter-hinge-ergaenzen-91c1162140", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Beginner: Squat + Hinge abdecken", + "id": "step-beginner-squat-hinge-abdecken-2ece0ba9ff", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Beginner: Squat + Hinge abdecken", + "id": "step-beginner-squat-hinge-abdecken-5ab250ae94", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-hinge-erst-beginner-stage-b36fdf9122", + "key": "kbdl" + }, + "load": { + "name": "Intensität", + "steps": [ + { + "name": "2 Reps Reserve", + "id": "step-2-reps-reserve-b3f06959cb", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "2 Reps Reserve", + "id": "step-2-reps-reserve-9e04b18fde", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "1–2 Reps Reserve", + "id": "step-1-2-reps-reserve-3ee69feb2c", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "1–2 Reps Reserve", + "id": "step-1-2-reps-reserve-4277bee1f0", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "80–90 % Intensität", + "id": "step-80-90-intensitaet-f631fb515d", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "80–90 % Intensität", + "id": "step-80-90-intensitaet-ba7745a692", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nie technische Reps erzwingen", + "id": "step-nie-technische-reps-erzwingen-01e23f5285", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nie technische Reps erzwingen", + "id": "step-nie-technische-reps-erzwingen-2fc20f3242", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-intensitaet-ffaebae137", + "key": "load" + }, + "lr": { + "name": "Teilwiederholungen", + "steps": [ + { + "name": "nur messbarer ROM", + "id": "step-nur-messbarer-rom-bb1e5eeeb1", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "nur messbarer ROM", + "id": "step-nur-messbarer-rom-45a26027a1", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "assistiert bevorzugen", + "id": "step-assistiert-bevorzugen-c93f4daab4", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "assistiert bevorzugen", + "id": "step-assistiert-bevorzugen-1e4bdf997d", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "ROM schrittweise erweitern", + "id": "step-rom-schrittweise-erweitern-02942f4f3c", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "ROM schrittweise erweitern", + "id": "step-rom-schrittweise-erweitern-0ac01e22f3", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Reps übergehen", + "id": "step-zu-vollen-reps-uebergehen-7b9eb05593", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Reps übergehen", + "id": "step-zu-vollen-reps-uebergehen-8f44aa56b7", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-teilwiederholungen-ce2394a937", + "key": "lr" + }, + "lraise": { + "name": "Scapular Protraction", + "steps": [ + { + "name": "Wall Scapular Push-up", + "id": "step-wall-scapular-push-up-fb0d9aa50e", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Wall Scapular Push-up", + "id": "step-wall-scapular-push-up-285b10eed8", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Ellbogen gestreckt", + "id": "step-ellbogen-gestreckt-665588a205", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Ellbogen gestreckt", + "id": "step-ellbogen-gestreckt-d3fc2e4a06", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schulterblätter spreizen", + "id": "step-schulterblaetter-spreizen-81e80cf408", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schulterblätter spreizen", + "id": "step-schulterblaetter-spreizen-81a36d2150", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "auf Push-up übertragen", + "id": "step-auf-push-up-uebertragen-cb7a342eb7", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "auf Push-up übertragen", + "id": "step-auf-push-up-uebertragen-26e3e559ac", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-scapular-protraction-39e62dc680", + "key": "lraise" + }, + "ls": { + "name": "Push-up-Progression", + "steps": [ + { + "name": "High Plank 10–30 s", + "id": "step-high-plank-10-30-s-cdd4db3e1a", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "High Plank 10–30 s", + "id": "step-high-plank-10-30-s-32710670d3", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "Negative Push-ups 4–8", + "id": "step-negative-push-ups-4-8-32248836e0", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "Negative Push-ups 4–8", + "id": "step-negative-push-ups-4-8-8375d11117", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "assistiert 4–8; Teil-Reps alternativ", + "id": "step-assistiert-4-8-teil-reps-alter-221168d839", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.55 + }, + { + "name": "assistiert 4–8; Teil-Reps alternativ", + "id": "step-assistiert-4-8-teil-reps-alter-be5708f622", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.55 + }, + { + "name": "volle Push-ups 4–8; bei 8 erschweren", + "id": "step-volle-push-ups-4-8-bei-8-ersch-293be75f2a", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "volle Push-ups 4–8; bei 8 erschweren", + "id": "step-volle-push-ups-4-8-bei-8-ersch-d000d16e0e", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + } + ], + "id": "progression-push-up-progression-359ecd088e", + "key": "ls" + }, + "lseng": { + "name": "Push-up-Startposition", + "steps": [ + { + "name": "Hände hoch / Knie, falls <10 s", + "id": "step-haende-hoch-knie-falls-10-s-1f36cd4622", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.45 + }, + { + "name": "Hände hoch / Knie, falls <10 s", + "id": "step-haende-hoch-knie-falls-10-s-b6c830cc39", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.45 + }, + { + "name": "High Plank bis 30 s", + "id": "step-high-plank-bis-30-s-23bc4b6dd7", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "High Plank bis 30 s", + "id": "step-high-plank-bis-30-s-8b154532b9", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "volle Körperspannung", + "id": "step-volle-koerperspannung-8633516312", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "volle Körperspannung", + "id": "step-volle-koerperspannung-276c3bf86e", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "danach Negative beginnen", + "id": "step-danach-negative-beginnen-26cc88afec", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "danach Negative beginnen", + "id": "step-danach-negative-beginnen-e608d8c7cf", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + } + ], + "id": "progression-push-up-startposition-90c54c14cd", + "key": "lseng" + }, + "lunge": { + "name": "Squat-Unterstützung", + "steps": [ + { + "name": "Türrahmen / Stange", + "id": "step-tuerrahmen-stange-4c3df7c28f", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Türrahmen / Stange", + "id": "step-tuerrahmen-stange-caf422d72a", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "volle kontrollierte Tiefe", + "id": "step-volle-kontrollierte-tiefe-6f677356fa", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "volle kontrollierte Tiefe", + "id": "step-volle-kontrollierte-tiefe-c04f6daa26", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Hilfe schrittweise reduzieren", + "id": "step-hilfe-schrittweise-reduzieren-51ef8c056f", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Hilfe schrittweise reduzieren", + "id": "step-hilfe-schrittweise-reduzieren-54e5047e9e", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Squats wechseln", + "id": "step-zu-vollen-squats-wechseln-82d34ee4f5", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Squats wechseln", + "id": "step-zu-vollen-squats-wechseln-e10129b753", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-squat-unterstuetzung-222f2deff0", + "key": "lunge" + }, + "mtn": { + "name": "Regeneration", + "steps": [ + { + "name": "kein Zusatztraining", + "id": "step-kein-zusatztraining-fabc8decb1", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kein Zusatztraining", + "id": "step-kein-zusatztraining-da35555351", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "locker bewegen", + "id": "step-locker-bewegen-7e2aef635d", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "locker bewegen", + "id": "step-locker-bewegen-3094be8801", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schmerz / Müdigkeit prüfen", + "id": "step-schmerz-muedigkeit-pruefen-350663ec9f", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schmerz / Müdigkeit prüfen", + "id": "step-schmerz-muedigkeit-pruefen-a7e0aa4799", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bereit für nächste Einheit", + "id": "step-bereit-fuer-naechste-einheit-72427afb09", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bereit für nächste Einheit", + "id": "step-bereit-fuer-naechste-einheit-9d674da4d6", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-regeneration-f844bb7be6", + "key": "mtn" + }, + "pike": { + "name": "Scapular Elevation · später", + "steps": [ + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-140468d1e4", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-40a4ff1fd3", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "erst Schulterkontrolle", + "id": "step-erst-schulterkontrolle-2403f03a7d", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "erst Schulterkontrolle", + "id": "step-erst-schulterkontrolle-5cbef19162", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Pike gehört zur Handstandlinie", + "id": "step-pike-gehoert-zur-handstandlini-8e1b9bc569", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Pike gehört zur Handstandlinie", + "id": "step-pike-gehoert-zur-handstandlini-51b7deea32", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "später als zweite Push-Bewegung", + "id": "step-spaeter-als-zweite-push-bewegu-06cdaa4a23", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "später als zweite Push-Bewegung", + "id": "step-spaeter-als-zweite-push-bewegu-6ef900cf7a", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-scapular-elevation-spaeter-8e6c8a5e64", + "key": "pike" + }, + "plank": { + "name": "Plank-Progression", + "steps": [ + { + "name": "erhöht/Knie 10–30 s", + "id": "step-erhoeht-knie-10-30-s-9156743958", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "erhöht/Knie 10–30 s", + "id": "step-erhoeht-knie-10-30-s-90f0f841d9", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "volle Plank 10–30 s", + "id": "step-volle-plank-10-30-s-6995092df7", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "volle Plank 10–30 s", + "id": "step-volle-plank-10-30-s-7bdfab3260", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "schwierigere Hebelposition", + "id": "step-schwierigere-hebelposition-7929e48e37", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "schwierigere Hebelposition", + "id": "step-schwierigere-hebelposition-78087749e1", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Widerstand, stets 10–30 s", + "id": "step-widerstand-stets-10-30-s-60e1148a07", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Widerstand, stets 10–30 s", + "id": "step-widerstand-stets-10-30-s-e8b858d1c6", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-plank-progression-7010c23671", + "key": "plank" + }, + "press": { + "name": "Ganzkörper-Warm-up", + "steps": [ + { + "name": "5–10 Min", + "id": "step-5-10-min-93bfa2e9b4", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "5–10 Min", + "id": "step-5-10-min-5b6351f175", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Wrists vorbereiten", + "id": "step-wrists-vorbereiten-3bcfe9cd93", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Wrists vorbereiten", + "id": "step-wrists-vorbereiten-c9ff45ae10", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Scapula aktivieren", + "id": "step-scapula-aktivieren-358d0c340e", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Scapula aktivieren", + "id": "step-scapula-aktivieren-aff239eab9", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bewegungen kurz rehearsen", + "id": "step-bewegungen-kurz-rehearsen-ff239741c8", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bewegungen kurz rehearsen", + "id": "step-bewegungen-kurz-rehearsen-61010dbc8f", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-ganzkoerper-warm-up-eb6587906d", + "key": "press" + }, + "row1": { + "name": "Inverted Row · Alternative", + "steps": [ + { + "name": "Scapular Retraction", + "id": "step-scapular-retraction-30fef1c0b2", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Scapular Retraction", + "id": "step-scapular-retraction-5f45079e2d", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "aufrechte assistierte Row", + "id": "step-aufrechte-assistierte-row-4bf42c3506", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 0.25 + }, + { + "name": "aufrechte assistierte Row", + "id": "step-aufrechte-assistierte-row-e76285811f", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 0.25 + }, + { + "name": "schrägere Row 4–8", + "id": "step-schraegere-row-4-8-4a327eb423", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "schrägere Row 4–8", + "id": "step-schraegere-row-4-8-ac387863bc", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "volle Row 4–8", + "id": "step-volle-row-4-8-c9b44ddc9b", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "volle Row 4–8", + "id": "step-volle-row-4-8-068ddaea25", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-inverted-row-alternative-fe88e8737a", + "key": "row1" + }, + "row2": { + "name": "Scapular Retraction", + "steps": [ + { + "name": "Schulterblätter zusammenführen", + "id": "step-schulterblaetter-zusammenfuehr-24fe3466f7", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Schulterblätter zusammenführen", + "id": "step-schulterblaetter-zusammenfuehr-f269293661", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Ellbogen zunächst gestreckt", + "id": "step-ellbogen-zunaechst-gestreckt-8127b3e7fc", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Ellbogen zunächst gestreckt", + "id": "step-ellbogen-zunaechst-gestreckt-8ec16a7674", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "kontrolliert lösen", + "id": "step-kontrolliert-loesen-f6a1697ea4", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "kontrolliert lösen", + "id": "step-kontrolliert-loesen-8cdb5043d6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "auf Rows übertragen", + "id": "step-auf-rows-uebertragen-ae42ecf07c", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "auf Rows übertragen", + "id": "step-auf-rows-uebertragen-7b13c2423a", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-scapular-retraction-07a7ad4883", + "key": "row2" + }, + "rowe": { + "name": "Pull-up-Progression", + "steps": [ + { + "name": "Active Hang 10–30 s", + "id": "step-active-hang-10-30-s-5f0fb67e39", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.2 + }, + { + "name": "Active Hang 10–30 s", + "id": "step-active-hang-10-30-s-17217b9335", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.2 + }, + { + "name": "Scapular Pull-ups / Negative 4–8", + "id": "step-scapular-pull-ups-negative-4-8-9f93cb86a6", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.25 + }, + { + "name": "Scapular Pull-ups / Negative 4–8", + "id": "step-scapular-pull-ups-negative-4-8-aae37fad91", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.25 + }, + { + "name": "assistierte Pull-ups 4–8", + "id": "step-assistierte-pull-ups-4-8-0f04d54425", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.55 + }, + { + "name": "assistierte Pull-ups 4–8", + "id": "step-assistierte-pull-ups-4-8-75a3804b03", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.55 + }, + { + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "id": "step-volle-pull-ups-4-8-bei-8-ersch-13657948ce", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "id": "step-volle-pull-ups-4-8-bei-8-ersch-a12929a96c", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + } + ], + "id": "progression-pull-up-progression-e4ad50ac31", + "key": "rowe" + }, + "sp": { + "name": "Side Plank · später", + "steps": [ + { + "name": "Knie 10–30 s", + "id": "step-knie-10-30-s-a04fda4182", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Knie 10–30 s", + "id": "step-knie-10-30-s-fa5c3f0156", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "volle Side Plank 10–30 s", + "id": "step-volle-side-plank-10-30-s-34820f11a3", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "volle Side Plank 10–30 s", + "id": "step-volle-side-plank-10-30-s-be34794e30", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Hebel erschweren", + "id": "step-hebel-erschweren-e870313a11", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Hebel erschweren", + "id": "step-hebel-erschweren-4db785c23f", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "lateral chain ergänzen", + "id": "step-lateral-chain-ergaenzen-d5bf33ed6e", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "lateral chain ergänzen", + "id": "step-lateral-chain-ergaenzen-91ce5310d7", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-side-plank-spaeter-6dfa6a3b0d", + "key": "sp" + }, + "squat": { + "name": "Squat-Progression", + "steps": [ + { + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "id": "step-deep-squat-hold-60-s-bw-30-s-g-5829d21464", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "id": "step-deep-squat-hold-60-s-bw-30-s-g-ccda7124ba", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-a42ec5fa5c", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-97729a1ee3", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Negative / assistierte Squats 4–8", + "id": "step-negative-assistierte-squats-4--599708fcee", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.75 + }, + { + "name": "Negative / assistierte Squats 4–8", + "id": "step-negative-assistierte-squats-4--2141de14f1", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.75 + }, + { + "name": "volle Squats 4–8; bei 8 KB möglich", + "id": "step-volle-squats-4-8-bei-8-kb-moeg-42273d4c69", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "volle Squats 4–8; bei 8 KB möglich", + "id": "step-volle-squats-4-8-bei-8-kb-moeg-4bad8f3b1f", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-squat-progression-5cf9160e5b", + "key": "squat" + }, + "sup": { + "name": "Prone Y-T-W", + "steps": [ + { + "name": "saubere Y-Position", + "id": "step-saubere-y-position-529d1cd763", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere Y-Position", + "id": "step-saubere-y-position-a9b851db2a", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere T-Position", + "id": "step-saubere-t-position-83a7145889", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere T-Position", + "id": "step-saubere-t-position-435f2fd83b", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere W-Position", + "id": "step-saubere-w-position-87966ac5b9", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere W-Position", + "id": "step-saubere-w-position-3e6795ac07", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter Y-T-W-Durchgang", + "id": "step-kontrollierter-y-t-w-durchgang-8a7eec7df8", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter Y-T-W-Durchgang", + "id": "step-kontrollierter-y-t-w-durchgang-b9b672cb47", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-prone-y-t-w-3a24eaf55c", + "key": "sup" + }, + "swing": { + "name": "Kettlebell in diesem Block", + "steps": [ + { + "name": "kein Swing-Zirkel", + "id": "step-kein-swing-zirkel-1cb890712e", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "kein Swing-Zirkel", + "id": "step-kein-swing-zirkel-f528b2dc38", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "nur Squat ggf. beladen", + "id": "step-nur-squat-ggf-beladen-7b222c3c81", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "nur Squat ggf. beladen", + "id": "step-nur-squat-ggf-beladen-37e386b7bc", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Volumen minimal halten", + "id": "step-volumen-minimal-halten-09fd077fa1", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Volumen minimal halten", + "id": "step-volumen-minimal-halten-0e5b8af5c0", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "KB bleibt Progressionswerkzeug", + "id": "step-kb-bleibt-progressionswerkzeug-8593f7b221", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "KB bleibt Progressionswerkzeug", + "id": "step-kb-bleibt-progressionswerkzeug-64625bcf5d", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-kettlebell-in-diesem-block-c5679bd0d4", + "key": "swing" + }, + "tsquat": { + "name": "Negative Squats", + "steps": [ + { + "name": "langsam absenken", + "id": "step-langsam-absenken-b250a57257", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "langsam absenken", + "id": "step-langsam-absenken-acd3a9f13b", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "unterstützt aufstehen", + "id": "step-unterstuetzt-aufstehen-e3f0b8a12a", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "unterstützt aufstehen", + "id": "step-unterstuetzt-aufstehen-47f8c015d9", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 sauber", + "id": "step-4-8-sauber-bc48af55e6", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 sauber", + "id": "step-4-8-sauber-68c4019fd2", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "nach 8 assistierte/volle Reps", + "id": "step-nach-8-assistierte-volle-reps-5cfb95beec", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "nach 8 assistierte/volle Reps", + "id": "step-nach-8-assistierte-volle-reps-a9e8b3c9b0", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + } + ], + "id": "progression-negative-squats-83b51fc468", + "key": "tsquat" + }, + "tw": { + "name": "Playlist 7 Mobility", + "steps": [ + { + "name": "optional separat", + "id": "step-optional-separat-7b29ebf83e", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "optional separat", + "id": "step-optional-separat-11ae49b683", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter ROM", + "id": "step-kontrollierter-rom-cb99075262", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter ROM", + "id": "step-kontrollierter-rom-b2993f6eaf", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "2–4×/Woche möglich", + "id": "step-2-4-woche-moeglich-1d1637eb3b", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "2–4×/Woche möglich", + "id": "step-2-4-woche-moeglich-2fbef46ba6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nicht als Krafttraining", + "id": "step-nicht-als-krafttraining-6da59933d1", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nicht als Krafttraining", + "id": "step-nicht-als-krafttraining-6f87b4cdd0", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-playlist-7-mobility-cf0245d08b", + "key": "tw" + }, + "wall": { + "name": "Active Hang", + "steps": [ + { + "name": "mit Fußhilfe, falls <10 s", + "id": "step-mit-fusshilfe-falls-10-s-51b27519a1", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "mit Fußhilfe, falls <10 s", + "id": "step-mit-fusshilfe-falls-10-s-d6138fdd57", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "10–30 s halten", + "id": "step-10-30-s-halten-bec653e06c", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "10–30 s halten", + "id": "step-10-30-s-halten-099397e967", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "Außenrotation + Lat-Aktivierung", + "id": "step-aussenrotation-lat-aktivierung-c0b5612e97", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "Außenrotation + Lat-Aktivierung", + "id": "step-aussenrotation-lat-aktivierung-9b17309383", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "keine maximale Depression", + "id": "step-keine-maximale-depression-6eff5e8f20", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "keine maximale Depression", + "id": "step-keine-maximale-depression-cfa68fca9a", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + } + ], + "id": "progression-active-hang-ae19c88465", + "key": "wall" + } + }, + "training_format": { + "mode": "sets_reps", + "fixed_interval": false, + "work_seconds": null, + "rest_seconds": null, + "rounds": null, + "rounds_scope": "block", + "schema_version": 1 + }, + "schema_version": 3, + "contract_version": 2, + "plan_id": "calihoss-newbie-8-wochen", + "exercise_catalog": { + "movement-push-up-progression-118a55be62": { + "id": "movement-push-up-progression-118a55be62", + "name": "Push-up-Progression", + "progression_id": "ls", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-high-plank-10-30-s-cdd4db3e1a", + "name": "High Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-high-plank-10-30-s-32710670d3", + "name": "High Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-push-ups-4-8-32248836e0", + "name": "Negative Push-ups 4–8", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-push-ups-4-8-8375d11117", + "name": "Negative Push-ups 4–8", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistiert-4-8-teil-reps-alter-221168d839", + "name": "assistiert 4–8; Teil-Reps alternativ", + "factor": 0.55, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistiert-4-8-teil-reps-alter-be5708f622", + "name": "assistiert 4–8; Teil-Reps alternativ", + "factor": 0.55, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-push-ups-4-8-bei-8-ersch-293be75f2a", + "name": "volle Push-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-push-ups-4-8-bei-8-ersch-d000d16e0e", + "name": "volle Push-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-pull-up-progression-68d78ccd63": { + "id": "movement-pull-up-progression-68d78ccd63", + "name": "Pull-up-Progression", + "progression_id": "rowe", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-active-hang-10-30-s-5f0fb67e39", + "name": "Active Hang 10–30 s", + "factor": 0.2, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-active-hang-10-30-s-17217b9335", + "name": "Active Hang 10–30 s", + "factor": 0.2, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-scapular-pull-ups-negative-4-8-9f93cb86a6", + "name": "Scapular Pull-ups / Negative 4–8", + "factor": 0.25, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-scapular-pull-ups-negative-4-8-aae37fad91", + "name": "Scapular Pull-ups / Negative 4–8", + "factor": 0.25, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistierte-pull-ups-4-8-0f04d54425", + "name": "assistierte Pull-ups 4–8", + "factor": 0.55, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistierte-pull-ups-4-8-75a3804b03", + "name": "assistierte Pull-ups 4–8", + "factor": 0.55, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-pull-ups-4-8-bei-8-ersch-13657948ce", + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-pull-ups-4-8-bei-8-ersch-a12929a96c", + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-squat-progression-b93d6a593b": { + "id": "movement-squat-progression-b93d6a593b", + "name": "Squat-Progression", + "progression_id": "squat", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-deep-squat-hold-60-s-bw-30-s-g-5829d21464", + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-deep-squat-hold-60-s-bw-30-s-g-ccda7124ba", + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-a42ec5fa5c", + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-97729a1ee3", + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-assistierte-squats-4--599708fcee", + "name": "Negative / assistierte Squats 4–8", + "factor": 0.75, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-assistierte-squats-4--2141de14f1", + "name": "Negative / assistierte Squats 4–8", + "factor": 0.75, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-squats-4-8-bei-8-kb-moeg-42273d4c69", + "name": "volle Squats 4–8; bei 8 KB möglich", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-squats-4-8-bei-8-kb-moeg-4bad8f3b1f", + "name": "volle Squats 4–8; bei 8 KB möglich", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-plank-progression-31156b972b": { + "id": "movement-plank-progression-31156b972b", + "name": "Plank-Progression", + "progression_id": "plank", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-erhoeht-knie-10-30-s-9156743958", + "name": "erhöht/Knie 10–30 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-erhoeht-knie-10-30-s-90f0f841d9", + "name": "erhöht/Knie 10–30 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-plank-10-30-s-6995092df7", + "name": "volle Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-plank-10-30-s-7bdfab3260", + "name": "volle Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwierigere-hebelposition-7929e48e37", + "name": "schwierigere Hebelposition", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwierigere-hebelposition-78087749e1", + "name": "schwierigere Hebelposition", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-widerstand-stets-10-30-s-60e1148a07", + "name": "Widerstand, stets 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-widerstand-stets-10-30-s-e8b858d1c6", + "name": "Widerstand, stets 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-bulletproof-shoulders-c0a078d108": { + "id": "movement-bulletproof-shoulders-c0a078d108", + "name": "Bulletproof Shoulders", + "progression_id": "name:bulletproof-shoulders", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-prone-y-t-w-technik-80560abe41": { + "id": "movement-prone-y-t-w-technik-80560abe41", + "name": "Prone Y-T-W Technik", + "progression_id": "name:prone-y-t-w-technik", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-lockerer-spaziergang-cd93978ea1": { + "id": "movement-lockerer-spaziergang-cd93978ea1", + "name": "Lockerer Spaziergang", + "progression_id": "name:lockerer-spaziergang", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-regeneration-pruefen-3d83efff8f": { + "id": "movement-regeneration-pruefen-3d83efff8f", + "name": "Regeneration prüfen", + "progression_id": "name:regeneration-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-wrist-conditioning-ee8f71f37e": { + "id": "movement-wrist-conditioning-ee8f71f37e", + "name": "Wrist Conditioning", + "progression_id": "name:wrist-conditioning", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-finger-plank-456618869c": { + "id": "movement-finger-plank-456618869c", + "name": "Finger Plank", + "progression_id": "name:finger-plank", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-full-body-mobility-f4ab17f25a": { + "id": "movement-full-body-mobility-f4ab17f25a", + "name": "Full Body Mobility", + "progression_id": "name:full-body-mobility", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-sonst-vollstaendig-pausieren-b291d0cf69": { + "id": "movement-sonst-vollstaendig-pausieren-b291d0cf69", + "name": "Sonst vollständig pausieren", + "progression_id": "name:sonst-vollstaendig-pausieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-sprunggelenk-mobilitaet-ef05527f38": { + "id": "movement-sprunggelenk-mobilitaet-ef05527f38", + "name": "Sprunggelenk-Mobilität", + "progression_id": "name:sprunggelenk-mobilitaet", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-hueft-mobilitaet-7836e93f5c": { + "id": "movement-hueft-mobilitaet-7836e93f5c", + "name": "Hüft-Mobilität", + "progression_id": "name:hueft-mobilitaet", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-brustwirbelsaeule-schultern-02622f8d13": { + "id": "movement-brustwirbelsaeule-schultern-02622f8d13", + "name": "Brustwirbelsäule / Schultern", + "progression_id": "name:brustwirbelsaeule-schultern", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-spaziergang-feb087e265": { + "id": "movement-spaziergang-feb087e265", + "name": "Spaziergang", + "progression_id": "name:spaziergang", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-schlaf-priorisieren-b6845205e5": { + "id": "movement-schlaf-priorisieren-b6845205e5", + "name": "Schlaf priorisieren", + "progression_id": "name:schlaf-priorisieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-1-2-reps-reserve-pruefen-85e3b5af37": { + "id": "movement-1-2-reps-reserve-pruefen-85e3b5af37", + "name": "1–2 Reps Reserve prüfen", + "progression_id": "name:1-2-reps-reserve-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-schmerzcheck-f658f713e7": { + "id": "movement-schmerzcheck-f658f713e7", + "name": "Schmerzcheck", + "progression_id": "name:schmerzcheck", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-push-protokollieren-e0770774a1": { + "id": "movement-push-protokollieren-e0770774a1", + "name": "Push protokollieren", + "progression_id": "name:push-protokollieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-pull-protokollieren-71a9be182e": { + "id": "movement-pull-protokollieren-71a9be182e", + "name": "Pull protokollieren", + "progression_id": "name:pull-protokollieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-beine-protokollieren-712a79f464": { + "id": "movement-beine-protokollieren-712a79f464", + "name": "Beine protokollieren", + "progression_id": "name:beine-protokollieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-core-protokollieren-279940b6d0": { + "id": "movement-core-protokollieren-279940b6d0", + "name": "Core protokollieren", + "progression_id": "name:core-protokollieren", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-startposition-pruefen-0676749716": { + "id": "movement-startposition-pruefen-0676749716", + "name": "Startposition prüfen", + "progression_id": "name:startposition-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-negative-pruefen-363284950d": { + "id": "movement-negative-pruefen-363284950d", + "name": "Negative prüfen", + "progression_id": "name:negative-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-volle-reps-pruefen-4955104cc2": { + "id": "movement-volle-reps-pruefen-4955104cc2", + "name": "Volle Reps prüfen", + "progression_id": "name:volle-reps-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-schwierigkeit-erhoehen-2b4e278d42": { + "id": "movement-schwierigkeit-erhoehen-2b4e278d42", + "name": "Schwierigkeit erhöhen", + "progression_id": "name:schwierigkeit-erhoehen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + } + } + }, + "draft": { + "colors": { + "ACC2": "#0F766E", + "ACCENT": "#C2410C", + "DARK": "#23201C", + "GOALBG": "#FBF1E9", + "GRID": "#CFC9BF", + "INK": "#1A1A1A", + "KBBG": "#F1ECFB", + "KBCOL": "#6D28D9", + "LIGHT": "#F4F1EC", + "LINK": "#1565C0", + "MUTE": "#6B6256", + "WARMBG": "#EAF1F0" + }, + "days": [ + { + "badge": { + "color": "#0F766E", + "text": "" + }, + "focus": "Ganzkörper A · Push + Pull + Beine + Core", + "num": 1, + "rotations": [ + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-e16264eee6", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d1-r0-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-e944165e56", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d1-r0-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-b91cc1e59f", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r0-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-5ec1c8e378", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d1-r0-e3" + } + ], + "label": "ARBEITSSATZ 1 — 4–8 Reps / Holds nach persönlicher Stufe", + "id": "rotation-arbeitssatz-1-4-8-reps-holds-n-93a7b5d3a5" + }, + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-afde40398a", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d1-r1-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-9ec914e9e2", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d1-r1-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-1ae469095c", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r1-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-750dd86258", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d1-r1-e3" + } + ], + "label": "ARBEITSSATZ 2 — gleiche persönliche Stufe", + "id": "rotation-arbeitssatz-2-gleiche-persoenl-9ea5c29794" + } + ], + "id": "day-ganzkoerper-a-push-pull-beine--425f3fef77" + }, + { + "badge": { + "color": "#6B6256", + "text": "" + }, + "focus": "Regeneration · optional Schulter Conditioning", + "num": 2, + "rotations": [ + { + "exercises": [ + { + "cue": "Playlist 5 exakt videogegeführt; bei Trainingsstart bis zu 3×/Woche, Volumen leicht halten", + "key": "name:bulletproof-shoulders", + "name": "Bulletproof Shoulders", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:bulletproof-shoulders", + "id": "exercise-item-9805179e68", + "exercise_id": "movement-bulletproof-shoulders-c0a078d108", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e0" + }, + { + "cue": "Nur kontrolliert und schmerzfrei; kein Schwung, Nacken lang", + "key": "name:prone-y-t-w-technik", + "name": "Prone Y-T-W Technik", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:prone-y-t-w-technik", + "id": "exercise-1-941443ade3", + "exercise_id": "movement-prone-y-t-w-technik-80560abe41", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e1" + }, + { + "cue": "20–40 Min optional; Gesprächs-Tempo, kein Intervalltraining", + "key": "name:lockerer-spaziergang", + "name": "Lockerer Spaziergang", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:lockerer-spaziergang", + "id": "exercise-2-1156e4d54e", + "exercise_id": "movement-lockerer-spaziergang-cd93978ea1", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e2" + }, + { + "cue": "Schlaf, Muskelkater, Handgelenke, Ellbogen und Schultern beurteilen", + "key": "name:regeneration-pruefen", + "name": "Regeneration prüfen", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:regeneration-pruefen", + "id": "exercise-3-730e1ffe5e", + "exercise_id": "movement-regeneration-pruefen-3d83efff8f", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e3" + } + ], + "label": "KEIN KRAFTTRAINING — OPTIONAL", + "id": "rotation-kein-krafttraining-optional-81a22c40d8" + }, + { + "exercises": [ + { + "cue": "Playlist 4: vier Warm-up-Drills; Finger Plank ergänzen", + "key": "name:wrist-conditioning", + "name": "Wrist Conditioning", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:wrist-conditioning", + "id": "exercise-item-434ab0d6cd", + "exercise_id": "movement-wrist-conditioning-ee8f71f37e", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e0" + }, + { + "cue": "10–30 s in passender Variante; <10 s leichter, bei 30 s schwerer", + "key": "name:finger-plank", + "name": "Finger Plank", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:finger-plank", + "id": "exercise-1-2fe41cea5a", + "exercise_id": "movement-finger-plank-456618869c", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d2-r1-e1" + }, + { + "cue": "Playlist 8 optional; separat und ruhig, nicht als Kraftzirkel", + "key": "name:full-body-mobility", + "name": "Full Body Mobility", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:full-body-mobility", + "id": "exercise-2-9ecfe09b02", + "exercise_id": "movement-full-body-mobility-f4ab17f25a", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e2" + }, + { + "cue": "Keine Zusatz-Sätze für Push, Pull, Beine oder Core", + "key": "name:sonst-vollstaendig-pausieren", + "name": "Sonst vollständig pausieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:sonst-vollstaendig-pausieren", + "id": "exercise-3-aa830acb25", + "exercise_id": "movement-sonst-vollstaendig-pausieren-b291d0cf69", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e3" + } + ], + "label": "OPTIONALE PFLEGE — NICHT BIS ZUR ERMÜDUNG", + "id": "rotation-optionale-pflege-nicht-bis-zur-5f931f935f" + } + ], + "id": "day-regeneration-optional-schulter-8a933efccd" + }, + { + "badge": { + "color": "#0F766E", + "text": "" + }, + "focus": "Ganzkörper B · bessere Ausführung", + "num": 3, + "rotations": [ + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-ef06de6209", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d3-r0-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-e91b0eb7ea", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d3-r0-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-b83bb1b189", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r0-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-3ef2e70f5f", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r0-e3" + } + ], + "label": "ARBEITSSATZ 1 — 4–8 Reps / Holds nach persönlicher Stufe", + "id": "rotation-arbeitssatz-1-4-8-reps-holds-n-31d3e1cf81" + }, + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-7e9d3b8778", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d3-r1-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-0576793b2d", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d3-r1-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-2cc9126f3c", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r1-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-442575f355", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r1-e3" + } + ], + "label": "ARBEITSSATZ 2 — gleiche persönliche Stufe", + "id": "rotation-arbeitssatz-2-gleiche-persoenl-3cf874b60c" + } + ], + "id": "day-ganzkoerper-b-bessere-ausfuehr-cbccb5a18c" + }, + { + "badge": { + "color": "#6B6256", + "text": "" + }, + "focus": "Regeneration · optional Mobility", + "num": 4, + "rotations": [ + { + "exercises": [ + { + "cue": "Videoroutine der Playlist 8; ruhig, kontrolliert, ohne Kraftziel", + "key": "name:full-body-mobility", + "name": "Full Body Mobility", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:full-body-mobility", + "id": "exercise-item-61699773e3", + "exercise_id": "movement-full-body-mobility-f4ab17f25a", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e0" + }, + { + "cue": "Nur schmerzfreier ROM; unterstützt eine saubere Kniebeuge", + "key": "name:sprunggelenk-mobilitaet", + "name": "Sprunggelenk-Mobilität", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:sprunggelenk-mobilitaet", + "id": "exercise-1-9a77e1bd9b", + "exercise_id": "movement-sprunggelenk-mobilitaet-ef05527f38", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e1" + }, + { + "cue": "Kontrollierte Bewegungen, kein aggressives Dehnen", + "key": "name:hueft-mobilitaet", + "name": "Hüft-Mobilität", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:hueft-mobilitaet", + "id": "exercise-2-e78ed44a90", + "exercise_id": "movement-hueft-mobilitaet-7836e93f5c", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e2" + }, + { + "cue": "Sanft bewegen; keine Zusatzermüdung für die nächste Einheit", + "key": "name:brustwirbelsaeule-schultern", + "name": "Brustwirbelsäule / Schultern", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:brustwirbelsaeule-schultern", + "id": "exercise-3-e8e41d6352", + "exercise_id": "movement-brustwirbelsaeule-schultern-02622f8d13", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e3" + } + ], + "label": "OPTIONAL: PLAYLIST 8 — MOBILITY", + "id": "rotation-optional-playlist-8-mobility-5e1ae6ee55" + }, + { + "exercises": [ + { + "cue": "Optional locker bewegen", + "key": "name:spaziergang", + "name": "Spaziergang", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:spaziergang", + "id": "exercise-item-67777b2931", + "exercise_id": "movement-spaziergang-feb087e265", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e0" + }, + { + "cue": "Regeneration ist Teil der Progression", + "key": "name:schlaf-priorisieren", + "name": "Schlaf priorisieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:schlaf-priorisieren", + "id": "exercise-1-72400ba5c9", + "exercise_id": "movement-schlaf-priorisieren-b6845205e5", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e1" + }, + { + "cue": "War die letzte Einheit näher am Versagen, nächste Einheit leichter wählen", + "key": "name:1-2-reps-reserve-pruefen", + "name": "1–2 Reps Reserve prüfen", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:1-2-reps-reserve-pruefen", + "id": "exercise-2-db67a28ad8", + "exercise_id": "movement-1-2-reps-reserve-pruefen-85e3b5af37", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e2" + }, + { + "cue": "Stechender oder zunehmender Gelenkschmerz: Übung anpassen oder pausieren", + "key": "name:schmerzcheck", + "name": "Schmerzcheck", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:schmerzcheck", + "id": "exercise-3-b06e0c5872", + "exercise_id": "movement-schmerzcheck-f658f713e7", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e3" + } + ], + "label": "REGENERATION — MINIMAL HALTEN", + "id": "rotation-regeneration-minimal-halten-c450e97a0b" + } + ], + "id": "day-regeneration-optional-mobility-0f3d34036d" + }, + { + "badge": { + "color": "#0F766E", + "text": "" + }, + "focus": "Ganzkörper C · Wochenabschluss", + "light": false, + "num": 5, + "rotations": [ + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-cbbb28ae57", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d5-r0-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-b5cdc5acfc", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d5-r0-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-c8c3bf00a5", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d5-r0-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-72a4e18f04", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d5-r0-e3" + } + ], + "label": "ARBEITSSATZ 1 — 4–8 Reps / Holds nach persönlicher Stufe", + "id": "rotation-arbeitssatz-1-4-8-reps-holds-n-a937682d98" + }, + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-3f953adfdb", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d5-r1-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-5fda83a424", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d5-r1-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-b40c4b6a58", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d5-r1-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-ce921d5b08", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d5-r1-e3" + } + ], + "label": "ARBEITSSATZ 2 — gleiche persönliche Stufe", + "id": "rotation-arbeitssatz-2-gleiche-persoenl-c713162cb9" + } + ], + "id": "day-ganzkoerper-c-wochenabschluss-f1d536f095" + }, + { + "badge": { + "color": "#6B6256", + "text": "" + }, + "color": "#6B6256", + "focus": "Vollständige Erholung · Wochencheck", + "note": "Kein Krafttraining. Notiere pro Hauptübung die persönliche Stufe, Reps/Haltezeit, Unterstützung und eine technische Verbesserung. Sonntag bleibt ebenfalls frei bzw. sehr locker.", + "note_bg": "#F4F1EC", + "num": 6, + "rotations": [ + { + "exercises": [ + { + "cue": "Stufe, 2 Sätze, Reps/Haltezeit und Techniknotiz", + "key": "name:push-protokollieren", + "name": "Push protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:push-protokollieren", + "id": "exercise-item-5e98211225", + "exercise_id": "movement-push-protokollieren-e0770774a1", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r0-e0" + }, + { + "cue": "Stufe, Unterstützung/Band/Fußhilfe und Techniknotiz", + "key": "name:pull-protokollieren", + "name": "Pull protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:pull-protokollieren", + "id": "exercise-1-4ffe56788d", + "exercise_id": "movement-pull-protokollieren-71a9be182e", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r0-e1" + }, + { + "cue": "Stufe, Tiefe, Unterstützung bzw. Kettlebell-Gewicht", + "key": "name:beine-protokollieren", + "name": "Beine protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:beine-protokollieren", + "id": "exercise-2-d163fb171a", + "exercise_id": "movement-beine-protokollieren-712a79f464", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r0-e2" + }, + { + "cue": "Plank-Variante und saubere Haltezeit", + "key": "name:core-protokollieren", + "name": "Core protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:core-protokollieren", + "id": "exercise-3-a6dff34f5c", + "exercise_id": "movement-core-protokollieren-279940b6d0", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d6-r0-e3" + } + ], + "label": "WOCHENCHECK — NUR DOKUMENTIEREN", + "id": "rotation-wochencheck-nur-dokumentieren-46027202a0" + }, + { + "exercises": [ + { + "cue": "Mindestens 10 s; bis 30 s halten, sonst Schwierigkeit senken", + "key": "name:startposition-pruefen", + "name": "Startposition prüfen", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:startposition-pruefen", + "id": "exercise-item-faff57b16d", + "exercise_id": "movement-startposition-pruefen-0676749716", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e0" + }, + { + "cue": "Bis 8 saubere Wiederholungen; vorher nicht überspringen", + "key": "name:negative-pruefen", + "name": "Negative prüfen", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:negative-pruefen", + "id": "exercise-1-2c33847f25", + "exercise_id": "movement-negative-pruefen-363284950d", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e1" + }, + { + "cue": "Unter 4: assistiert bevorzugen; ab 4 volle Reps trainieren", + "key": "name:volle-reps-pruefen", + "name": "Volle Reps prüfen", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:volle-reps-pruefen", + "id": "exercise-2-f432054dde", + "exercise_id": "movement-volle-reps-pruefen-4955104cc2", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e2" + }, + { + "cue": "Erst nach 8 sauberen vollen Reps; dann Zyklus neu starten", + "key": "name:schwierigkeit-erhoehen", + "name": "Schwierigkeit erhöhen", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:schwierigkeit-erhoehen", + "id": "exercise-3-00836121d2", + "exercise_id": "movement-schwierigkeit-erhoehen-2b4e278d42", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e3" + } + ], + "label": "ENTSCHEIDUNG FÜR NÄCHSTE WOCHE", + "id": "rotation-entscheidung-fuer-naechste-woc-51b5dca3e8" + } + ], + "id": "day-vollstaendige-erholung-wochenc-b3998121a6" + } + ], + "front": { + "baseline_head": "Standortbestimmung & Tracking", + "baseline_intro": "Ermittle vor Einheit 1 für jede Kategorie die erste nicht bestandene Stufe des Flowcharts. In Woche 8 unter identischen Bedingungen wiederholen.", + "baseline_rows": [ + [ + "Test", + "Tag 0", + "Woche 8" + ], + [ + "Push: aktuelle Stufe + saubere Reps/Haltezeit", + "", + "" + ], + [ + "High Plank (saubere Sekunden, max. 30 notieren)", + "", + "" + ], + [ + "Pull: aktuelle Stufe + Unterstützung", + "", + "" + ], + [ + "Active Hang (saubere Sekunden, max. 30 notieren)", + "", + "" + ], + [ + "Beine: aktuelle Stufe + saubere Reps/Haltezeit", + "", + "" + ], + [ + "Plank: Variante + saubere Sekunden", + "", + "" + ], + [ + "Absolvierte Kraftsessions (von 24)", + "0", + "" + ], + [ + "Körpergewicht (kg)", + "95", + "" + ] + ], + "flow_rows": [ + [ + "Teil", + "Inhalt", + "Zeit" + ], + [ + "Warm-up", + "Ganzkörper + Wrists + Scapula, nicht ermüdend", + "5–10 Min" + ], + [ + "Arbeitssatz 1", + "Push · Pull · Beine · Core", + "individuell" + ], + [ + "Arbeitssatz 2", + "gleiche Stufen; volle Erholung zulassen", + "individuell" + ], + [ + "Optional", + "Playlist 5 Shoulders / Playlist 8 Mobility separat", + "nach Bedarf" + ], + [ + "", + "Gesamt Krafttraining", + "~25–40 Min" + ] + ], + "goals_box": "Nach 8 Wochen zählt kein künstliches Wiederholungsziel, sondern die Newbie-Kriterien:
•  3 Ganzkörpereinheiten pro Woche sind zur stabilen Gewohnheit geworden.
•  Du beherrschst je eine saubere, wiederholbare Push-, Pull-, Bein- und Core-Variante.
•  Du kennst bei jeder Bewegung deine aktuelle Stufe und kannst sie über Technik, Reps, Haltezeit, ROM oder weniger Unterstützung steigern.
•  Ein Wechsel zur Beginner Stage erfolgt nur, wenn die Grundbewegungen nicht mehr unbeholfen wirken und die Regeneration stabil bleibt.", + "goals_head": "Ziel der 8 Wochen: Newbie Stage sauber abschließen", + "goals_note": "Flowchart-Regel für jede Übung separat: Startposition mindestens 10 s, bis 30 s ausbauen. Ausnahme Beine: Deep Squat Hold jeden 2. Tag, 1–2×60 s ungewichtet oder 1–2×30 s gewichtet; erst in der tiefen Position sicher und komfortabel werden. Dann bis 8 saubere Negative. Danach volle Reps testen: unter 4 volle Reps assistiert bevorzugen, Teil-Reps nur alternativ; ab 4 volle Reps trainieren, bis 8 erreicht sind. Erst dann Schwierigkeit erhöhen und den Zyklus neu starten. Die Kalenderwoche erzwingt keinen Aufstieg.", + "kb_box": "Die Kettlebell bleibt in dieser Newbie Stage ein Progressionswerkzeug, kein zweites Programm:
•  Hauptübung Beine bleibt die persönliche Squat-Stufe.
•  Sobald 8 saubere Bodyweight Squats kontrolliert möglich sind, kann ein leichter Goblet Squat die Schwierigkeit erhöhen.
•  Keine zusätzlichen Swings, Presses, Rows oder Kettlebell-Zirkel in diesem Block. So bleiben Umfang und Übungsauswahl beim CaliHoss-Newbie-Schema.", + "kb_head": "Rolle der Kettlebell", + "lib_head": "CaliHoss-Playlists & Übungsbibliothek", + "lib_intro": "Die Links führen direkt zur Kursseite bzw. zum passenden Playlist-Abschnitt. Die Videos enthalten die eigentliche Technikschulung; dieses Dokument strukturiert die Anwendung.", + "reminder_head": "Unveränderliche Regeln", + "reminders": [ + "3× pro Woche Ganzkörper. Je Einheit genau eine Hauptübung aus Push, Pull, Beine und Core.", + "2 Arbeitssätze. Dynamisch 4–8 Reps; isometrisch normalerweise 10–30 s; Deep Squat Hold als Sonderregel 1–2×60 s ungewichtet oder 1–2×30 s gewichtet jeden 2. Tag. Etwa 1–2 saubere Reps vor dem Versagen stoppen.", + "Technik vor Zahlen. Pro Einheit mindestens eine Variable verbessern: Position, Alignment, ROM, Tempo, Spannung, Reps, Haltezeit oder Unterstützung.", + "Kein Kalenderaufstieg. Jede Kategorie folgt unabhängig dem Flowchart und bleibt auf der ersten nicht bestandenen Stufe.", + "Nach 2 stagnierenden Sessions prüfen: Erholung, Ermüdung, Übungsstufe und Unterstützung anpassen." + ], + "session_how_body": "Kein Tabata. Nach dem 5–10-minütigen Ganzkörper-Warm-up absolvierst du Arbeitssatz 1 für Push, Pull, Beine und Core mit ausreichenden Pausen. Danach folgt Arbeitssatz 2 derselben vier persönlichen Stufen. Dynamische Übungen: 4–8 saubere Reps; Holds normalerweise 10–30 s; Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet jeden 2. Tag. Intensität: 80–90 % bzw. 1–2 Reps Reserve. Pausiere so lange, bis die nächste Ausführung sauber ist.", + "session_how_head": "So funktioniert eine Ganzkörpereinheit", + "sheets_note": "Wochenrhythmus: Tag 1, 3 und 5 Krafttraining; dazwischen Regeneration. Tag 2/4 enthalten nur optionale Conditioning-/Mobility-Hinweise, Tag 6 ist vollständig frei. Alle Hauptübungen folgen dem persönlichen Progressions-Flowchart.", + "timer_note": "Timer: normale Stoppuhr für Holds und Pausen; kein 20/10-Intervalltimer." + }, + "library": [ + { + "color": "ACC2", + "group": "KURS & PROGRAMM", + "items": [ + { + "desc": "Programmstufen, Grundübungen, Begriffe und 5–10-minütiges bewegungsspezifisches Warm-up.", + "name": "Playlist 1: Foundation", + "url": "https://calihoss.me/learn/calisthenics/#playlist-1-foundation" + }, + { + "desc": "Technik, Bewegungsentwicklung, Volumen, Mikroprogression und das Progression Decision Flowchart.", + "name": "Playlist 2: Progression", + "url": "https://calihoss.me/learn/calisthenics/#playlist-2-progression" + }, + { + "desc": "3× Ganzkörper pro Woche; je eine Übung aus Push, Pull, Beine und Core; 2×4–8 bei 80–90 % Intensität.", + "name": "Newbie Stage", + "url": "https://calihoss.me/learn/calisthenics/#newbie-stage" + }, + { + "desc": "Klimmzugstangen, Unterstützungsmöglichkeiten und niedrige Stangen/Parallettes.", + "name": "Playlist 3: Equipment", + "url": "https://calihoss.me/learn/calisthenics/#playlist-3-equipment" + } + ] + }, + { + "color": "ACC2", + "group": "WARM-UP · WRISTS · SHOULDERS · SCAPULA", + "items": [ + { + "desc": "Vier Wrist-Warm-up-Übungen in jeder Einheit; Finger Planks ausdrücklich ergänzen.", + "name": "Playlist 4: Wrists and Forearms", + "url": "https://calihoss.me/learn/calisthenics/#playlist-4-wrists-and-forearms" + }, + { + "desc": "Passende Variante 10–30 s. Erst nach 30 s pro Position zu schwereren Varianten oder Combos wechseln.", + "name": "Finger Plank", + "url": "https://calihoss.me/learn/calisthenics/#wrist-warm-up-routine" + }, + { + "desc": "Bulletproof Shoulders, Prone Y-T-W und Serratus-/Scapula-Kontrolle; bei Trainingsstart bis zu 3×/Woche.", + "name": "Playlist 5: Shoulders", + "url": "https://calihoss.me/learn/calisthenics/#playlist-5-shoulders" + }, + { + "desc": "Push-up: Protraktion. Row: Retraction. Dip: Depression. Pike/Handstand: Elevation.", + "name": "Scapula-Grundrichtungen", + "url": "https://calihoss.me/learn/calisthenics/#the-scapula" + } + ] + }, + { + "color": "ACC2", + "group": "PUSH · PUSH-UP-PROGRESSION", + "items": [ + { + "desc": "Auswahl, erste Push-up-Progressionen, Knee Push-up, Form, Handposition und Tempo.", + "name": "Playlist 15: Push-ups", + "url": "https://calihoss.me/learn/calisthenics/#playlist-15-push-ups" + }, + { + "desc": "10 s beherrschen, dann bis 30 s ausbauen. Bei <10 s Hände erhöhen oder Knie verwenden.", + "name": "Startposition: High Plank", + "url": "https://calihoss.me/learn/calisthenics/#progression-decision-flowchart" + }, + { + "desc": "Langsam durch den kontrollierbaren vollen ROM absenken; Ziel 4–8, Übergang nach 8 sauberen Negativen.", + "name": "Negative Push-ups", + "url": "https://calihoss.me/learn/calisthenics/#beginner-push-up-workout" + }, + { + "desc": "Unter 4 volle Reps: Knie-/erhöhte Push-ups bevorzugen. Ab 4 volle Reps trainieren; bei 8 erschweren.", + "name": "Assistiert → volle Push-ups", + "url": "https://calihoss.me/learn/calisthenics/#first-push-up-progressions" + } + ] + }, + { + "color": "ACC2", + "group": "PULL · PULL-UP-PROGRESSION", + "items": [ + { + "desc": "Erster Pull-up, Scapular Pull-ups, Hangs und Unterstützung. Für Full Body werden assistierte Pull-ups empfohlen.", + "name": "Playlist 14: Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#playlist-14-pull-ups" + }, + { + "desc": "Lats durch Außenrotation aktivieren; leichte Scapula-Depression, nicht maximal nach unten ziehen.", + "name": "Startposition: Active Hang", + "url": "https://calihoss.me/learn/calisthenics/#hangs-technique" + }, + { + "desc": "Erste Pull-up-Progression: Ellbogen gestreckt, Schulterblätter kontrolliert depressieren, Lats aktivieren.", + "name": "Scapular Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#scapular-pull-ups-technique" + }, + { + "desc": "Obere Position per Hocker erreichen, langsam absenken; 4–8 saubere Wiederholungen.", + "name": "Negative Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#first-pull-up-progression" + }, + { + "desc": "Fuß- oder Bandhilfe so wählen, dass 4–8 volle kontrollierte Reps mit 1–2 Reps Reserve möglich sind.", + "name": "Assistierte Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#first-pull-up-progression" + }, + { + "desc": "Ab 4 sauberen vollen Reps diese trainieren; bei 8 Schwierigkeit erhöhen und Flowchart neu beginnen.", + "name": "Volle Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#progression-decision-flowchart" + }, + { + "desc": "Nur als Alternative, falls keine Pull-up-Möglichkeit vorhanden ist; Newbies dürfen zunächst eine Pull-Bewegung wählen.", + "name": "Playlist 13: Inverted Rows", + "url": "https://calihoss.me/learn/calisthenics/#playlist-13-inverted-rows" + } + ] + }, + { + "color": "ACC2", + "group": "BEINE · CORE", + "items": [ + { + "desc": "Beintraining als Fundament; im Full-Body-Plan mindestens Squat oder Hinge abdecken.", + "name": "Playlist 10: Legs", + "url": "https://calihoss.me/learn/calisthenics/#playlist-10-legs" + }, + { + "desc": "Zuerst Deep Squat Hold jeden 2. Tag: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet. Erst komfortabel, dann Negative, assistierte und volle Squats 4–8.", + "name": "Squat-Progression", + "url": "https://calihoss.me/learn/calisthenics/#progression-decision-flowchart" + }, + { + "desc": "Erst nach 8 sauberen Bodyweight Squats als Schwierigkeitserhöhung; leichte Glocke und voller kontrollierter ROM.", + "name": "Goblet Squat mit Kettlebell", + "url": "https://calihoss.me/learn/calisthenics/#develop-the-movement" + }, + { + "desc": "Core als Stabilisation und Anti-Bewegung; für Full Body kann eine Core-Übung je Einheit ergänzt werden.", + "name": "Playlist 9: Core", + "url": "https://calihoss.me/learn/calisthenics/#playlist-9-core" + }, + { + "desc": "Plank als fundamentale Core-Übung; 10–30 s, danach Schwierigkeit oder Widerstand erhöhen.", + "name": "Playlist 12: Planks", + "url": "https://calihoss.me/learn/calisthenics/#playlist-12-planks" + } + ] + }, + { + "color": "ACC2", + "group": "OPTIONAL · SPÄTER", + "items": [ + { + "desc": "Separate Full-Body-Mobility-Routine 2–4×/Woche möglich; nicht als zusätzliche Kraftsession behandeln.", + "name": "Playlist 8: Mobility", + "url": "https://calihoss.me/learn/calisthenics/#playlist-8-mobility" + }, + { + "desc": "Noch nicht Hauptübung dieses Blocks. Später: Dip Support Hold → Scapular Dips → Negative → assistierte → volle Dips.", + "name": "Playlist 16: Dips", + "url": "https://calihoss.me/learn/calisthenics/#playlist-16-dips" + } + ] + } + ], + "meta": { + "days": 6, + "doc_title": "CaliHoss Newbie Stage · 8-Wochen-Plan", + "footer_left": "CaliHoss Newbie Stage · 3× Ganzkörper · Progression per Flowchart", + "output_name": "CaliHoss-Newbie-8-Wochen.pdf", + "subtitle": "Blutiger Anfänger · 3× Ganzkörper/Woche · 2×4–8 Reps · Holds meist 10–30 s · Deep Squat 60/30 s · Kettlebell nur als Progression", + "title": "CaliHoss Newbie Stage – 8 Wochen", + "weeks": 8 + }, + "phases": { + "items": [ + { + "name": "Phase 1 · Level finden & Technik", + "params": "Für jede Kategorie die erste nicht bestandene Flowchart-Stufe bestimmen. 2 Sätze, 4–8 Reps bzw. meist 10–30 s, 1–2 Reps Reserve. Deep Squat Hold separat jeden 2. Tag: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet. Kein automatischer Aufstieg.", + "weeks": 1, + "id": "phase-phase-1-level-finden-amp-techn-ae1454813c" + }, + { + "name": "Phase 2 · Level finden & Technik", + "params": "Für jede Kategorie die erste nicht bestandene Flowchart-Stufe bestimmen. 2 Sätze, 4–8 Reps bzw. meist 10–30 s, 1–2 Reps Reserve. Deep Squat Hold separat jeden 2. Tag: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet. Kein automatischer Aufstieg.", + "weeks": 1, + "id": "phase-phase-2-level-finden-amp-techn-bc8640a305" + }, + { + "name": "Phase 3 · Stabilisieren & mikroprogressieren", + "params": "Persönliche Stufen beibehalten und pro Session mindestens eine Variable verbessern: Position, Alignment, ROM, Tempo, Spannung, Reps, Haltezeit oder weniger Hilfe.", + "weeks": 1, + "id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec" + }, + { + "name": "Phase 4 · Stabilisieren & mikroprogressieren", + "params": "Persönliche Stufen beibehalten und pro Session mindestens eine Variable verbessern: Position, Alignment, ROM, Tempo, Spannung, Reps, Haltezeit oder weniger Hilfe.", + "weeks": 1, + "id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9" + }, + { + "name": "Phase 5 · Kriterienbasiert aufsteigen", + "params": "Nur nach erfülltem Kriterium wechseln: 30 s Startposition, 8 Negative, 4 volle Reps zum Einstieg, 8 volle Reps vor der nächsten Schwierigkeit. Jede Übung separat.", + "weeks": 1, + "id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734" + }, + { + "name": "Phase 6 · Kriterienbasiert aufsteigen", + "params": "Nur nach erfülltem Kriterium wechseln: 30 s Startposition, 8 Negative, 4 volle Reps zum Einstieg, 8 volle Reps vor der nächsten Schwierigkeit. Jede Übung separat.", + "weeks": 1, + "id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8" + }, + { + "name": "Phase 7 · Festigen & neu testen", + "params": "Aktuelle Varianten sauber wiederholen. Ende Woche 8 dieselben Tests wie an Tag 0. Wechsel zur Beginner Stage nur bei sauberer, wiederholbarer Technik und stabiler 3×/Woche-Gewohnheit.", + "weeks": 1, + "id": "phase-phase-7-festigen-amp-neu-teste-65823eedde" + }, + { + "name": "Phase 8 · Festigen & neu testen", + "params": "Aktuelle Varianten sauber wiederholen. Ende Woche 8 dieselben Tests wie an Tag 0. Wechsel zur Beginner Stage nur bei sauberer, wiederholbarer Technik und stabiler 3×/Woche-Gewohnheit.", + "weeks": 1, + "id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3" + } + ], + "param_light": "Regenerationstag: kein Krafttraining; optional leichte Mobility oder Conditioning ohne Ermüdungsziel." + }, + "prepost": { + "1": { + "cooldown": "optional 2–5 Min locker gehen und Atmung beruhigen", + "stretch": "Optional separat: Playlist 8 Full Body Mobility; nicht bis zur Ermüdung. Bulletproof Shoulders nicht hier, sondern nur als eigener Conditioning-Block am Regenerationstag.", + "warmup": "1–2 Min locker bewegen · Playlist 4 Wrist Conditioning (4 Übungen) + Finger Plank · Wall Scapular Push-ups 4–8 · Active Hang/Scapular Pull-up sehr leicht · 1–2 kontrollierte Squat-Rehearsals; Deep Squat Hold ggf. als Bein-Hauptübung, nicht als Warm-up" + }, + "2": { + "cooldown": "Regeneration · optional lockerer Spaziergang", + "stretch": "Kein zweiter Schulterdurchgang im Cool-down: Bulletproof Shoulders nur einmal als separater Conditioning-Block oben; Wrist Conditioning bei Bedarf leicht.", + "warmup": "Kein Warm-up nötig · kein Krafttraining" + }, + "3": { + "cooldown": "optional 2–5 Min locker gehen und Atmung beruhigen", + "stretch": "Optional separat: Playlist 8 Full Body Mobility; nicht bis zur Ermüdung. Bulletproof Shoulders nicht hier, sondern nur als eigener Conditioning-Block am Regenerationstag.", + "warmup": "1–2 Min locker bewegen · Playlist 4 Wrist Conditioning (4 Übungen) + Finger Plank · Wall Scapular Push-ups 4–8 · Active Hang/Scapular Pull-up sehr leicht · 1–2 kontrollierte Squat-Rehearsals; Deep Squat Hold ggf. als Bein-Hauptübung, nicht als Warm-up" + }, + "4": { + "cooldown": "Ruhig ausklingen, keine Zusatzbelastung", + "stretch": "Optional: Playlist 8 Full Body Mobility, kontrolliert und schmerzfrei", + "warmup": "Kein Warm-up nötig · Mobility bei Bedarf kurz vorbereiten" + }, + "5": { + "cooldown": "optional 2–5 Min locker gehen und Atmung beruhigen", + "stretch": "Optional separat: Playlist 8 Full Body Mobility; nicht bis zur Ermüdung. Bulletproof Shoulders nicht hier, sondern nur als eigener Conditioning-Block am Regenerationstag.", + "warmup": "1–2 Min locker bewegen · Playlist 4 Wrist Conditioning (4 Übungen) + Finger Plank · Wall Scapular Push-ups 4–8 · Active Hang/Scapular Pull-up sehr leicht · 1–2 kontrollierte Squat-Rehearsals; Deep Squat Hold ggf. als Bein-Hauptübung, nicht als Warm-up" + }, + "6": { + "cooldown": "Vollständige Erholung", + "stretch": "Nur Wochenprotokoll und Flowchart-Entscheidung für die nächste Woche", + "warmup": "Kein Training" + } + }, + "stages": { + "atw": { + "name": "Progressionsregel", + "steps": [ + { + "name": "Startposition meist 10–30 s; Deep Squat siehe Sonderregel", + "id": "step-startposition-meist-10-30-s-de-cf24fe6b0a", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Startposition meist 10–30 s; Deep Squat siehe Sonderregel", + "id": "step-startposition-meist-10-30-s-de-7f8eec1c9e", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Negative bis 8 sauber", + "id": "step-negative-bis-8-sauber-2ee7d2ec3a", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Negative bis 8 sauber", + "id": "step-negative-bis-8-sauber-5aa0d823f3", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "assistiert bevorzugt / Teil-Reps alternativ", + "id": "step-assistiert-bevorzugt-teil-reps-3cfdfbf0c9", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "assistiert bevorzugt / Teil-Reps alternativ", + "id": "step-assistiert-bevorzugt-teil-reps-7086055dcf", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "volle Reps 4–8, dann erschweren", + "id": "step-volle-reps-4-8-dann-erschweren-29ef936c03", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "volle Reps 4–8, dann erschweren", + "id": "step-volle-reps-4-8-dann-erschweren-1d2e3ec8a8", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-progressionsregel-2fbf7c1952", + "key": "atw" + }, + "bic": { + "name": "Technik-Fokus", + "steps": [ + { + "name": "Position & Alignment", + "id": "step-position-amp-alignment-ccfec71b67", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Position & Alignment", + "id": "step-position-amp-alignment-a0240a6a9f", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ROM & Bewegungspfad", + "id": "step-rom-amp-bewegungspfad-113bd351b3", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ROM & Bewegungspfad", + "id": "step-rom-amp-bewegungspfad-9739ca3bcd", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Tempo & Spannung", + "id": "step-tempo-amp-spannung-788af93895", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Tempo & Spannung", + "id": "step-tempo-amp-spannung-66e06e922b", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bracing, Atmung & Fokus", + "id": "step-bracing-atmung-amp-fokus-fcc6cb89d5", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bracing, Atmung & Fokus", + "id": "step-bracing-atmung-amp-fokus-76bffcdf46", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-technik-fokus-e875e1dfde", + "key": "bic" + }, + "bridge": { + "name": "Mikroprogression", + "steps": [ + { + "name": "Technik verbessern", + "id": "step-technik-verbessern-18e45c232f", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Technik verbessern", + "id": "step-technik-verbessern-af64a8d335", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+1 saubere Rep / +Sekunden", + "id": "step-1-saubere-rep-sekunden-bea0a6f0f2", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+1 saubere Rep / +Sekunden", + "id": "step-1-saubere-rep-sekunden-9df740632e", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "etwas weniger Unterstützung", + "id": "step-etwas-weniger-unterstuetzung-18e44cfff5", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "etwas weniger Unterstützung", + "id": "step-etwas-weniger-unterstuetzung-a927afae34", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "schwierigere Variante", + "id": "step-schwierigere-variante-75dc6896d9", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "schwierigere Variante", + "id": "step-schwierigere-variante-7856fc9277", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-mikroprogression-b0dc3add0c", + "key": "bridge" + }, + "calf": { + "name": "Stagnationscheck", + "steps": [ + { + "name": "Schlaf & Erholung prüfen", + "id": "step-schlaf-amp-erholung-pruefen-84e12e7461", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Schlaf & Erholung prüfen", + "id": "step-schlaf-amp-erholung-pruefen-06ccaffb49", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Fatigue / Zusatztraining prüfen", + "id": "step-fatigue-zusatztraining-pruefen-6a01f09acf", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Fatigue / Zusatztraining prüfen", + "id": "step-fatigue-zusatztraining-pruefen-cc528ace63", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "leichtere Stufe oder mehr Hilfe", + "id": "step-leichtere-stufe-oder-mehr-hilf-ea4b6f2f82", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "leichtere Stufe oder mehr Hilfe", + "id": "step-leichtere-stufe-oder-mehr-hilf-53b23ce8bd", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "bei 2 Sessions ohne Fortschritt anpassen", + "id": "step-bei-2-sessions-ohne-fortschrit-551809a978", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "bei 2 Sessions ohne Fortschritt anpassen", + "id": "step-bei-2-sessions-ohne-fortschrit-609e7a3a40", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + } + ], + "id": "progression-stagnationscheck-5158f16cab", + "key": "calf" + }, + "carry": { + "name": "Finger Plank", + "steps": [ + { + "name": "Wand/Tisch 10–30 s", + "id": "step-wand-tisch-10-30-s-4193ed5643", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Wand/Tisch 10–30 s", + "id": "step-wand-tisch-10-30-s-a021af05f8", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Knievariante 10–30 s", + "id": "step-knievariante-10-30-s-1110175d1e", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Knievariante 10–30 s", + "id": "step-knievariante-10-30-s-3d1f995c08", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Boden 10–30 s", + "id": "step-boden-10-30-s-14d5aa23b6", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Boden 10–30 s", + "id": "step-boden-10-30-s-569d7dc2a6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Combos erst nach 30 s je Position", + "id": "step-combos-erst-nach-30-s-je-posit-05ff31d1fa", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Combos erst nach 30 s je Position", + "id": "step-combos-erst-nach-30-s-je-posit-ba186d50fe", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-finger-plank-f9d5a3010c", + "key": "carry" + }, + "db": { + "name": "Wrist Conditioning", + "steps": [ + { + "name": "4 Warm-up-Drills", + "id": "step-4-warm-up-drills-80a8839e87", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "4 Warm-up-Drills", + "id": "step-4-warm-up-drills-c1d52d55ad", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+ Finger Plank", + "id": "step-finger-plank-84c96599d8", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+ Finger Plank", + "id": "step-finger-plank-6646fd3c94", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ruhig, nicht ermüdend", + "id": "step-ruhig-nicht-ermuedend-a03b9ca9ea", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ruhig, nicht ermüdend", + "id": "step-ruhig-nicht-ermuedend-eb30e4ad28", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bei Schmerz Variante anpassen", + "id": "step-bei-schmerz-variante-anpassen-96b23d3c5d", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bei Schmerz Variante anpassen", + "id": "step-bei-schmerz-variante-anpassen-bad6a9c798", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-wrist-conditioning-67c4bfa8a0", + "key": "db" + }, + "dips": { + "name": "Dip-Progression · später", + "steps": [ + { + "name": "Dip Support Hold 10–30 s", + "id": "step-dip-support-hold-10-30-s-7db82c14d2", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Dip Support Hold 10–30 s", + "id": "step-dip-support-hold-10-30-s-38a65bd556", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Scapular Dips 4–8", + "id": "step-scapular-dips-4-8-54e50e0deb", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Scapular Dips 4–8", + "id": "step-scapular-dips-4-8-9861d79df3", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Negative / assistierte Dips 4–8", + "id": "step-negative-assistierte-dips-4-8-51cef25e2f", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.6 + }, + { + "name": "Negative / assistierte Dips 4–8", + "id": "step-negative-assistierte-dips-4-8-26038f8ce6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.6 + }, + { + "name": "volle Dips 4–8", + "id": "step-volle-dips-4-8-97ebbfbf20", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "volle Dips 4–8", + "id": "step-volle-dips-4-8-9e3f715ddd", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-dip-progression-spaeter-b76ba51f6d", + "key": "dips" + }, + "gobletkb": { + "name": "Goblet Squat · KB", + "steps": [ + { + "name": "erst nach 8 BW-Squats", + "id": "step-erst-nach-8-bw-squats-cb5e6f79b4", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "erst nach 8 BW-Squats", + "id": "step-erst-nach-8-bw-squats-5fb4c9827c", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, voller ROM", + "id": "step-leicht-voller-rom-223d7ba7df", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, voller ROM", + "id": "step-leicht-voller-rom-9cc35c41ef", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 saubere Reps", + "id": "step-4-8-saubere-reps-9cc21ffc71", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 saubere Reps", + "id": "step-4-8-saubere-reps-05d850b4a5", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "bei 8 Schwierigkeit erhöhen", + "id": "step-bei-8-schwierigkeit-erhoehen-77b0f8ad55", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "bei 8 Schwierigkeit erhöhen", + "id": "step-bei-8-schwierigkeit-erhoehen-ea22a8e15c", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-goblet-squat-kb-9d21334b6f", + "key": "gobletkb" + }, + "halo": { + "name": "Bulletproof Shoulders", + "steps": [ + { + "name": "videogeführt leicht", + "id": "step-videogefuehrt-leicht-56eeb28b68", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "videogeführt leicht", + "id": "step-videogefuehrt-leicht-9826cc9337", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis 3×/Woche bei Trainingsstart", + "id": "step-bis-3-woche-bei-trainingsstart-00cccb34ea", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis 3×/Woche bei Trainingsstart", + "id": "step-bis-3-woche-bei-trainingsstart-78b10cb42c", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "kein Zusatzversagen", + "id": "step-kein-zusatzversagen-0c124a2d71", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "kein Zusatzversagen", + "id": "step-kein-zusatzversagen-09f1f363ad", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Volumen an Erholung anpassen", + "id": "step-volumen-an-erholung-anpassen-23680fda4b", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Volumen an Erholung anpassen", + "id": "step-volumen-an-erholung-anpassen-4d39ebbf93", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-bulletproof-shoulders-baa51fb7a5", + "key": "halo" + }, + "holl": { + "name": "Bracing", + "steps": [ + { + "name": "Rumpfspannung finden", + "id": "step-rumpfspannung-finden-715833661a", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Rumpfspannung finden", + "id": "step-rumpfspannung-finden-286734cc2c", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Atmung koordinieren", + "id": "step-atmung-koordinieren-356cc152d1", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Atmung koordinieren", + "id": "step-atmung-koordinieren-7ca2ee55df", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Alignment halten", + "id": "step-alignment-halten-0c7f26b946", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Alignment halten", + "id": "step-alignment-halten-3e1b54d2ac", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Spannung unter Last behalten", + "id": "step-spannung-unter-last-behalten-a0bbb8fda1", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Spannung unter Last behalten", + "id": "step-spannung-unter-last-behalten-a0e1b7b5a5", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-bracing-4a444af3ab", + "key": "holl" + }, + "kbdl": { + "name": "Hinge · erst Beginner Stage", + "steps": [ + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-e6ff14d812", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-62775896bf", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Squat-Stufe priorisieren", + "id": "step-squat-stufe-priorisieren-2eb20659e1", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Squat-Stufe priorisieren", + "id": "step-squat-stufe-priorisieren-f2390d80ab", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "später Hinge ergänzen", + "id": "step-spaeter-hinge-ergaenzen-490b1761f0", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "später Hinge ergänzen", + "id": "step-spaeter-hinge-ergaenzen-91c1162140", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Beginner: Squat + Hinge abdecken", + "id": "step-beginner-squat-hinge-abdecken-2ece0ba9ff", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Beginner: Squat + Hinge abdecken", + "id": "step-beginner-squat-hinge-abdecken-5ab250ae94", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-hinge-erst-beginner-stage-b36fdf9122", + "key": "kbdl" + }, + "load": { + "name": "Intensität", + "steps": [ + { + "name": "2 Reps Reserve", + "id": "step-2-reps-reserve-b3f06959cb", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "2 Reps Reserve", + "id": "step-2-reps-reserve-9e04b18fde", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "1–2 Reps Reserve", + "id": "step-1-2-reps-reserve-3ee69feb2c", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "1–2 Reps Reserve", + "id": "step-1-2-reps-reserve-4277bee1f0", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "80–90 % Intensität", + "id": "step-80-90-intensitaet-f631fb515d", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "80–90 % Intensität", + "id": "step-80-90-intensitaet-ba7745a692", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nie technische Reps erzwingen", + "id": "step-nie-technische-reps-erzwingen-01e23f5285", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nie technische Reps erzwingen", + "id": "step-nie-technische-reps-erzwingen-2fc20f3242", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-intensitaet-ffaebae137", + "key": "load" + }, + "lr": { + "name": "Teilwiederholungen", + "steps": [ + { + "name": "nur messbarer ROM", + "id": "step-nur-messbarer-rom-bb1e5eeeb1", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "nur messbarer ROM", + "id": "step-nur-messbarer-rom-45a26027a1", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "assistiert bevorzugen", + "id": "step-assistiert-bevorzugen-c93f4daab4", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "assistiert bevorzugen", + "id": "step-assistiert-bevorzugen-1e4bdf997d", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "ROM schrittweise erweitern", + "id": "step-rom-schrittweise-erweitern-02942f4f3c", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "ROM schrittweise erweitern", + "id": "step-rom-schrittweise-erweitern-0ac01e22f3", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Reps übergehen", + "id": "step-zu-vollen-reps-uebergehen-7b9eb05593", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Reps übergehen", + "id": "step-zu-vollen-reps-uebergehen-8f44aa56b7", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-teilwiederholungen-ce2394a937", + "key": "lr" + }, + "lraise": { + "name": "Scapular Protraction", + "steps": [ + { + "name": "Wall Scapular Push-up", + "id": "step-wall-scapular-push-up-fb0d9aa50e", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Wall Scapular Push-up", + "id": "step-wall-scapular-push-up-285b10eed8", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Ellbogen gestreckt", + "id": "step-ellbogen-gestreckt-665588a205", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Ellbogen gestreckt", + "id": "step-ellbogen-gestreckt-d3fc2e4a06", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schulterblätter spreizen", + "id": "step-schulterblaetter-spreizen-81e80cf408", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schulterblätter spreizen", + "id": "step-schulterblaetter-spreizen-81a36d2150", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "auf Push-up übertragen", + "id": "step-auf-push-up-uebertragen-cb7a342eb7", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "auf Push-up übertragen", + "id": "step-auf-push-up-uebertragen-26e3e559ac", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-scapular-protraction-39e62dc680", + "key": "lraise" + }, + "ls": { + "name": "Push-up-Progression", + "steps": [ + { + "name": "High Plank 10–30 s", + "id": "step-high-plank-10-30-s-cdd4db3e1a", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "High Plank 10–30 s", + "id": "step-high-plank-10-30-s-32710670d3", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "Negative Push-ups 4–8", + "id": "step-negative-push-ups-4-8-32248836e0", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "Negative Push-ups 4–8", + "id": "step-negative-push-ups-4-8-8375d11117", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "assistiert 4–8; Teil-Reps alternativ", + "id": "step-assistiert-4-8-teil-reps-alter-221168d839", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.55 + }, + { + "name": "assistiert 4–8; Teil-Reps alternativ", + "id": "step-assistiert-4-8-teil-reps-alter-be5708f622", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.55 + }, + { + "name": "volle Push-ups 4–8; bei 8 erschweren", + "id": "step-volle-push-ups-4-8-bei-8-ersch-293be75f2a", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "volle Push-ups 4–8; bei 8 erschweren", + "id": "step-volle-push-ups-4-8-bei-8-ersch-d000d16e0e", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + } + ], + "id": "progression-push-up-progression-359ecd088e", + "key": "ls" + }, + "lseng": { + "name": "Push-up-Startposition", + "steps": [ + { + "name": "Hände hoch / Knie, falls <10 s", + "id": "step-haende-hoch-knie-falls-10-s-1f36cd4622", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.45 + }, + { + "name": "Hände hoch / Knie, falls <10 s", + "id": "step-haende-hoch-knie-falls-10-s-b6c830cc39", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.45 + }, + { + "name": "High Plank bis 30 s", + "id": "step-high-plank-bis-30-s-23bc4b6dd7", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "High Plank bis 30 s", + "id": "step-high-plank-bis-30-s-8b154532b9", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "volle Körperspannung", + "id": "step-volle-koerperspannung-8633516312", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "volle Körperspannung", + "id": "step-volle-koerperspannung-276c3bf86e", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "danach Negative beginnen", + "id": "step-danach-negative-beginnen-26cc88afec", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "danach Negative beginnen", + "id": "step-danach-negative-beginnen-e608d8c7cf", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + } + ], + "id": "progression-push-up-startposition-90c54c14cd", + "key": "lseng" + }, + "lunge": { + "name": "Squat-Unterstützung", + "steps": [ + { + "name": "Türrahmen / Stange", + "id": "step-tuerrahmen-stange-4c3df7c28f", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Türrahmen / Stange", + "id": "step-tuerrahmen-stange-caf422d72a", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "volle kontrollierte Tiefe", + "id": "step-volle-kontrollierte-tiefe-6f677356fa", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "volle kontrollierte Tiefe", + "id": "step-volle-kontrollierte-tiefe-c04f6daa26", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Hilfe schrittweise reduzieren", + "id": "step-hilfe-schrittweise-reduzieren-51ef8c056f", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Hilfe schrittweise reduzieren", + "id": "step-hilfe-schrittweise-reduzieren-54e5047e9e", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Squats wechseln", + "id": "step-zu-vollen-squats-wechseln-82d34ee4f5", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Squats wechseln", + "id": "step-zu-vollen-squats-wechseln-e10129b753", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-squat-unterstuetzung-222f2deff0", + "key": "lunge" + }, + "mtn": { + "name": "Regeneration", + "steps": [ + { + "name": "kein Zusatztraining", + "id": "step-kein-zusatztraining-fabc8decb1", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kein Zusatztraining", + "id": "step-kein-zusatztraining-da35555351", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "locker bewegen", + "id": "step-locker-bewegen-7e2aef635d", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "locker bewegen", + "id": "step-locker-bewegen-3094be8801", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schmerz / Müdigkeit prüfen", + "id": "step-schmerz-muedigkeit-pruefen-350663ec9f", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schmerz / Müdigkeit prüfen", + "id": "step-schmerz-muedigkeit-pruefen-a7e0aa4799", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bereit für nächste Einheit", + "id": "step-bereit-fuer-naechste-einheit-72427afb09", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bereit für nächste Einheit", + "id": "step-bereit-fuer-naechste-einheit-9d674da4d6", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-regeneration-f844bb7be6", + "key": "mtn" + }, + "pike": { + "name": "Scapular Elevation · später", + "steps": [ + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-140468d1e4", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-40a4ff1fd3", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "erst Schulterkontrolle", + "id": "step-erst-schulterkontrolle-2403f03a7d", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "erst Schulterkontrolle", + "id": "step-erst-schulterkontrolle-5cbef19162", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Pike gehört zur Handstandlinie", + "id": "step-pike-gehoert-zur-handstandlini-8e1b9bc569", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Pike gehört zur Handstandlinie", + "id": "step-pike-gehoert-zur-handstandlini-51b7deea32", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "später als zweite Push-Bewegung", + "id": "step-spaeter-als-zweite-push-bewegu-06cdaa4a23", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "später als zweite Push-Bewegung", + "id": "step-spaeter-als-zweite-push-bewegu-6ef900cf7a", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-scapular-elevation-spaeter-8e6c8a5e64", + "key": "pike" + }, + "plank": { + "name": "Plank-Progression", + "steps": [ + { + "name": "erhöht/Knie 10–30 s", + "id": "step-erhoeht-knie-10-30-s-9156743958", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "erhöht/Knie 10–30 s", + "id": "step-erhoeht-knie-10-30-s-90f0f841d9", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "volle Plank 10–30 s", + "id": "step-volle-plank-10-30-s-6995092df7", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "volle Plank 10–30 s", + "id": "step-volle-plank-10-30-s-7bdfab3260", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "schwierigere Hebelposition", + "id": "step-schwierigere-hebelposition-7929e48e37", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "schwierigere Hebelposition", + "id": "step-schwierigere-hebelposition-78087749e1", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Widerstand, stets 10–30 s", + "id": "step-widerstand-stets-10-30-s-60e1148a07", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Widerstand, stets 10–30 s", + "id": "step-widerstand-stets-10-30-s-e8b858d1c6", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-plank-progression-7010c23671", + "key": "plank" + }, + "press": { + "name": "Ganzkörper-Warm-up", + "steps": [ + { + "name": "5–10 Min", + "id": "step-5-10-min-93bfa2e9b4", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "5–10 Min", + "id": "step-5-10-min-5b6351f175", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Wrists vorbereiten", + "id": "step-wrists-vorbereiten-3bcfe9cd93", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Wrists vorbereiten", + "id": "step-wrists-vorbereiten-c9ff45ae10", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Scapula aktivieren", + "id": "step-scapula-aktivieren-358d0c340e", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Scapula aktivieren", + "id": "step-scapula-aktivieren-aff239eab9", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bewegungen kurz rehearsen", + "id": "step-bewegungen-kurz-rehearsen-ff239741c8", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bewegungen kurz rehearsen", + "id": "step-bewegungen-kurz-rehearsen-61010dbc8f", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-ganzkoerper-warm-up-eb6587906d", + "key": "press" + }, + "row1": { + "name": "Inverted Row · Alternative", + "steps": [ + { + "name": "Scapular Retraction", + "id": "step-scapular-retraction-30fef1c0b2", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Scapular Retraction", + "id": "step-scapular-retraction-5f45079e2d", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "aufrechte assistierte Row", + "id": "step-aufrechte-assistierte-row-4bf42c3506", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 0.25 + }, + { + "name": "aufrechte assistierte Row", + "id": "step-aufrechte-assistierte-row-e76285811f", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 0.25 + }, + { + "name": "schrägere Row 4–8", + "id": "step-schraegere-row-4-8-4a327eb423", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "schrägere Row 4–8", + "id": "step-schraegere-row-4-8-ac387863bc", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "volle Row 4–8", + "id": "step-volle-row-4-8-c9b44ddc9b", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "volle Row 4–8", + "id": "step-volle-row-4-8-068ddaea25", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-inverted-row-alternative-fe88e8737a", + "key": "row1" + }, + "row2": { + "name": "Scapular Retraction", + "steps": [ + { + "name": "Schulterblätter zusammenführen", + "id": "step-schulterblaetter-zusammenfuehr-24fe3466f7", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Schulterblätter zusammenführen", + "id": "step-schulterblaetter-zusammenfuehr-f269293661", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Ellbogen zunächst gestreckt", + "id": "step-ellbogen-zunaechst-gestreckt-8127b3e7fc", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Ellbogen zunächst gestreckt", + "id": "step-ellbogen-zunaechst-gestreckt-8ec16a7674", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "kontrolliert lösen", + "id": "step-kontrolliert-loesen-f6a1697ea4", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "kontrolliert lösen", + "id": "step-kontrolliert-loesen-8cdb5043d6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "auf Rows übertragen", + "id": "step-auf-rows-uebertragen-ae42ecf07c", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "auf Rows übertragen", + "id": "step-auf-rows-uebertragen-7b13c2423a", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-scapular-retraction-07a7ad4883", + "key": "row2" + }, + "rowe": { + "name": "Pull-up-Progression", + "steps": [ + { + "name": "Active Hang 10–30 s", + "id": "step-active-hang-10-30-s-5f0fb67e39", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.2 + }, + { + "name": "Active Hang 10–30 s", + "id": "step-active-hang-10-30-s-17217b9335", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.2 + }, + { + "name": "Scapular Pull-ups / Negative 4–8", + "id": "step-scapular-pull-ups-negative-4-8-9f93cb86a6", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.25 + }, + { + "name": "Scapular Pull-ups / Negative 4–8", + "id": "step-scapular-pull-ups-negative-4-8-aae37fad91", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.25 + }, + { + "name": "assistierte Pull-ups 4–8", + "id": "step-assistierte-pull-ups-4-8-0f04d54425", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.55 + }, + { + "name": "assistierte Pull-ups 4–8", + "id": "step-assistierte-pull-ups-4-8-75a3804b03", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.55 + }, + { + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "id": "step-volle-pull-ups-4-8-bei-8-ersch-13657948ce", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "id": "step-volle-pull-ups-4-8-bei-8-ersch-a12929a96c", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + } + ], + "id": "progression-pull-up-progression-e4ad50ac31", + "key": "rowe" + }, + "sp": { + "name": "Side Plank · später", + "steps": [ + { + "name": "Knie 10–30 s", + "id": "step-knie-10-30-s-a04fda4182", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Knie 10–30 s", + "id": "step-knie-10-30-s-fa5c3f0156", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "volle Side Plank 10–30 s", + "id": "step-volle-side-plank-10-30-s-34820f11a3", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "volle Side Plank 10–30 s", + "id": "step-volle-side-plank-10-30-s-be34794e30", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Hebel erschweren", + "id": "step-hebel-erschweren-e870313a11", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Hebel erschweren", + "id": "step-hebel-erschweren-4db785c23f", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "lateral chain ergänzen", + "id": "step-lateral-chain-ergaenzen-d5bf33ed6e", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "lateral chain ergänzen", + "id": "step-lateral-chain-ergaenzen-91ce5310d7", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-side-plank-spaeter-6dfa6a3b0d", + "key": "sp" + }, + "squat": { + "name": "Squat-Progression", + "steps": [ + { + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "id": "step-deep-squat-hold-60-s-bw-30-s-g-5829d21464", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "id": "step-deep-squat-hold-60-s-bw-30-s-g-ccda7124ba", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-a42ec5fa5c", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-97729a1ee3", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Negative / assistierte Squats 4–8", + "id": "step-negative-assistierte-squats-4--599708fcee", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.75 + }, + { + "name": "Negative / assistierte Squats 4–8", + "id": "step-negative-assistierte-squats-4--2141de14f1", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.75 + }, + { + "name": "volle Squats 4–8; bei 8 KB möglich", + "id": "step-volle-squats-4-8-bei-8-kb-moeg-42273d4c69", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "volle Squats 4–8; bei 8 KB möglich", + "id": "step-volle-squats-4-8-bei-8-kb-moeg-4bad8f3b1f", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-squat-progression-5cf9160e5b", + "key": "squat" + }, + "sup": { + "name": "Prone Y-T-W", + "steps": [ + { + "name": "saubere Y-Position", + "id": "step-saubere-y-position-529d1cd763", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere Y-Position", + "id": "step-saubere-y-position-a9b851db2a", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere T-Position", + "id": "step-saubere-t-position-83a7145889", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere T-Position", + "id": "step-saubere-t-position-435f2fd83b", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere W-Position", + "id": "step-saubere-w-position-87966ac5b9", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere W-Position", + "id": "step-saubere-w-position-3e6795ac07", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter Y-T-W-Durchgang", + "id": "step-kontrollierter-y-t-w-durchgang-8a7eec7df8", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter Y-T-W-Durchgang", + "id": "step-kontrollierter-y-t-w-durchgang-b9b672cb47", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-prone-y-t-w-3a24eaf55c", + "key": "sup" + }, + "swing": { + "name": "Kettlebell in diesem Block", + "steps": [ + { + "name": "kein Swing-Zirkel", + "id": "step-kein-swing-zirkel-1cb890712e", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "kein Swing-Zirkel", + "id": "step-kein-swing-zirkel-f528b2dc38", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "nur Squat ggf. beladen", + "id": "step-nur-squat-ggf-beladen-7b222c3c81", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "nur Squat ggf. beladen", + "id": "step-nur-squat-ggf-beladen-37e386b7bc", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Volumen minimal halten", + "id": "step-volumen-minimal-halten-09fd077fa1", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Volumen minimal halten", + "id": "step-volumen-minimal-halten-0e5b8af5c0", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "KB bleibt Progressionswerkzeug", + "id": "step-kb-bleibt-progressionswerkzeug-8593f7b221", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "KB bleibt Progressionswerkzeug", + "id": "step-kb-bleibt-progressionswerkzeug-64625bcf5d", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-kettlebell-in-diesem-block-c5679bd0d4", + "key": "swing" + }, + "tsquat": { + "name": "Negative Squats", + "steps": [ + { + "name": "langsam absenken", + "id": "step-langsam-absenken-b250a57257", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "langsam absenken", + "id": "step-langsam-absenken-acd3a9f13b", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "unterstützt aufstehen", + "id": "step-unterstuetzt-aufstehen-e3f0b8a12a", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "unterstützt aufstehen", + "id": "step-unterstuetzt-aufstehen-47f8c015d9", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 sauber", + "id": "step-4-8-sauber-bc48af55e6", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 sauber", + "id": "step-4-8-sauber-68c4019fd2", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "nach 8 assistierte/volle Reps", + "id": "step-nach-8-assistierte-volle-reps-5cfb95beec", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "nach 8 assistierte/volle Reps", + "id": "step-nach-8-assistierte-volle-reps-a9e8b3c9b0", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + } + ], + "id": "progression-negative-squats-83b51fc468", + "key": "tsquat" + }, + "tw": { + "name": "Playlist 7 Mobility", + "steps": [ + { + "name": "optional separat", + "id": "step-optional-separat-7b29ebf83e", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "optional separat", + "id": "step-optional-separat-11ae49b683", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter ROM", + "id": "step-kontrollierter-rom-cb99075262", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter ROM", + "id": "step-kontrollierter-rom-b2993f6eaf", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "2–4×/Woche möglich", + "id": "step-2-4-woche-moeglich-1d1637eb3b", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "2–4×/Woche möglich", + "id": "step-2-4-woche-moeglich-2fbef46ba6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nicht als Krafttraining", + "id": "step-nicht-als-krafttraining-6da59933d1", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nicht als Krafttraining", + "id": "step-nicht-als-krafttraining-6f87b4cdd0", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-playlist-7-mobility-cf0245d08b", + "key": "tw" + }, + "wall": { + "name": "Active Hang", + "steps": [ + { + "name": "mit Fußhilfe, falls <10 s", + "id": "step-mit-fusshilfe-falls-10-s-51b27519a1", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "mit Fußhilfe, falls <10 s", + "id": "step-mit-fusshilfe-falls-10-s-d6138fdd57", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "10–30 s halten", + "id": "step-10-30-s-halten-bec653e06c", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "10–30 s halten", + "id": "step-10-30-s-halten-099397e967", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "Außenrotation + Lat-Aktivierung", + "id": "step-aussenrotation-lat-aktivierung-c0b5612e97", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "Außenrotation + Lat-Aktivierung", + "id": "step-aussenrotation-lat-aktivierung-9b17309383", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "keine maximale Depression", + "id": "step-keine-maximale-depression-6eff5e8f20", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "keine maximale Depression", + "id": "step-keine-maximale-depression-cfa68fca9a", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + } + ], + "id": "progression-active-hang-ae19c88465", + "key": "wall" + } + }, + "training_format": { + "mode": "sets_reps", + "fixed_interval": false, + "work_seconds": null, + "rest_seconds": null, + "rounds": null, + "rounds_scope": "block", + "schema_version": 1 + }, + "schema_version": 3, + "contract_version": 2, + "plan_id": "calihoss-newbie-8-wochen", + "exercise_catalog": { + "movement-push-up-progression-118a55be62": { + "id": "movement-push-up-progression-118a55be62", + "name": "Push-up-Progression", + "progression_id": "ls", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-high-plank-10-30-s-cdd4db3e1a", + "name": "High Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-high-plank-10-30-s-32710670d3", + "name": "High Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-push-ups-4-8-32248836e0", + "name": "Negative Push-ups 4–8", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-push-ups-4-8-8375d11117", + "name": "Negative Push-ups 4–8", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistiert-4-8-teil-reps-alter-221168d839", + "name": "assistiert 4–8; Teil-Reps alternativ", + "factor": 0.55, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistiert-4-8-teil-reps-alter-be5708f622", + "name": "assistiert 4–8; Teil-Reps alternativ", + "factor": 0.55, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-push-ups-4-8-bei-8-ersch-293be75f2a", + "name": "volle Push-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-push-ups-4-8-bei-8-ersch-d000d16e0e", + "name": "volle Push-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-pull-up-progression-68d78ccd63": { + "id": "movement-pull-up-progression-68d78ccd63", + "name": "Pull-up-Progression", + "progression_id": "rowe", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-active-hang-10-30-s-5f0fb67e39", + "name": "Active Hang 10–30 s", + "factor": 0.2, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-active-hang-10-30-s-17217b9335", + "name": "Active Hang 10–30 s", + "factor": 0.2, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-scapular-pull-ups-negative-4-8-9f93cb86a6", + "name": "Scapular Pull-ups / Negative 4–8", + "factor": 0.25, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-scapular-pull-ups-negative-4-8-aae37fad91", + "name": "Scapular Pull-ups / Negative 4–8", + "factor": 0.25, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistierte-pull-ups-4-8-0f04d54425", + "name": "assistierte Pull-ups 4–8", + "factor": 0.55, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistierte-pull-ups-4-8-75a3804b03", + "name": "assistierte Pull-ups 4–8", + "factor": 0.55, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-pull-ups-4-8-bei-8-ersch-13657948ce", + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-pull-ups-4-8-bei-8-ersch-a12929a96c", + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-squat-progression-b93d6a593b": { + "id": "movement-squat-progression-b93d6a593b", + "name": "Squat-Progression", + "progression_id": "squat", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-deep-squat-hold-60-s-bw-30-s-g-5829d21464", + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-deep-squat-hold-60-s-bw-30-s-g-ccda7124ba", + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-a42ec5fa5c", + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-97729a1ee3", + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-assistierte-squats-4--599708fcee", + "name": "Negative / assistierte Squats 4–8", + "factor": 0.75, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-assistierte-squats-4--2141de14f1", + "name": "Negative / assistierte Squats 4–8", + "factor": 0.75, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-squats-4-8-bei-8-kb-moeg-42273d4c69", + "name": "volle Squats 4–8; bei 8 KB möglich", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-squats-4-8-bei-8-kb-moeg-4bad8f3b1f", + "name": "volle Squats 4–8; bei 8 KB möglich", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-plank-progression-31156b972b": { + "id": "movement-plank-progression-31156b972b", + "name": "Plank-Progression", + "progression_id": "plank", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-erhoeht-knie-10-30-s-9156743958", + "name": "erhöht/Knie 10–30 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-erhoeht-knie-10-30-s-90f0f841d9", + "name": "erhöht/Knie 10–30 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-plank-10-30-s-6995092df7", + "name": "volle Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-plank-10-30-s-7bdfab3260", + "name": "volle Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwierigere-hebelposition-7929e48e37", + "name": "schwierigere Hebelposition", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwierigere-hebelposition-78087749e1", + "name": "schwierigere Hebelposition", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-widerstand-stets-10-30-s-60e1148a07", + "name": "Widerstand, stets 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-widerstand-stets-10-30-s-e8b858d1c6", + "name": "Widerstand, stets 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-bulletproof-shoulders-c0a078d108": { + "id": "movement-bulletproof-shoulders-c0a078d108", + "name": "Bulletproof Shoulders", + "progression_id": "name:bulletproof-shoulders", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-prone-y-t-w-technik-80560abe41": { + "id": "movement-prone-y-t-w-technik-80560abe41", + "name": "Prone Y-T-W Technik", + "progression_id": "name:prone-y-t-w-technik", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-lockerer-spaziergang-cd93978ea1": { + "id": "movement-lockerer-spaziergang-cd93978ea1", + "name": "Lockerer Spaziergang", + "progression_id": "name:lockerer-spaziergang", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-regeneration-pruefen-3d83efff8f": { + "id": "movement-regeneration-pruefen-3d83efff8f", + "name": "Regeneration prüfen", + "progression_id": "name:regeneration-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-wrist-conditioning-ee8f71f37e": { + "id": "movement-wrist-conditioning-ee8f71f37e", + "name": "Wrist Conditioning", + "progression_id": "name:wrist-conditioning", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-finger-plank-456618869c": { + "id": "movement-finger-plank-456618869c", + "name": "Finger Plank", + "progression_id": "name:finger-plank", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-full-body-mobility-f4ab17f25a": { + "id": "movement-full-body-mobility-f4ab17f25a", + "name": "Full Body Mobility", + "progression_id": "name:full-body-mobility", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-sonst-vollstaendig-pausieren-b291d0cf69": { + "id": "movement-sonst-vollstaendig-pausieren-b291d0cf69", + "name": "Sonst vollständig pausieren", + "progression_id": "name:sonst-vollstaendig-pausieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-sprunggelenk-mobilitaet-ef05527f38": { + "id": "movement-sprunggelenk-mobilitaet-ef05527f38", + "name": "Sprunggelenk-Mobilität", + "progression_id": "name:sprunggelenk-mobilitaet", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-hueft-mobilitaet-7836e93f5c": { + "id": "movement-hueft-mobilitaet-7836e93f5c", + "name": "Hüft-Mobilität", + "progression_id": "name:hueft-mobilitaet", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-brustwirbelsaeule-schultern-02622f8d13": { + "id": "movement-brustwirbelsaeule-schultern-02622f8d13", + "name": "Brustwirbelsäule / Schultern", + "progression_id": "name:brustwirbelsaeule-schultern", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-spaziergang-feb087e265": { + "id": "movement-spaziergang-feb087e265", + "name": "Spaziergang", + "progression_id": "name:spaziergang", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-schlaf-priorisieren-b6845205e5": { + "id": "movement-schlaf-priorisieren-b6845205e5", + "name": "Schlaf priorisieren", + "progression_id": "name:schlaf-priorisieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-1-2-reps-reserve-pruefen-85e3b5af37": { + "id": "movement-1-2-reps-reserve-pruefen-85e3b5af37", + "name": "1–2 Reps Reserve prüfen", + "progression_id": "name:1-2-reps-reserve-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-schmerzcheck-f658f713e7": { + "id": "movement-schmerzcheck-f658f713e7", + "name": "Schmerzcheck", + "progression_id": "name:schmerzcheck", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-push-protokollieren-e0770774a1": { + "id": "movement-push-protokollieren-e0770774a1", + "name": "Push protokollieren", + "progression_id": "name:push-protokollieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-pull-protokollieren-71a9be182e": { + "id": "movement-pull-protokollieren-71a9be182e", + "name": "Pull protokollieren", + "progression_id": "name:pull-protokollieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-beine-protokollieren-712a79f464": { + "id": "movement-beine-protokollieren-712a79f464", + "name": "Beine protokollieren", + "progression_id": "name:beine-protokollieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-core-protokollieren-279940b6d0": { + "id": "movement-core-protokollieren-279940b6d0", + "name": "Core protokollieren", + "progression_id": "name:core-protokollieren", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-startposition-pruefen-0676749716": { + "id": "movement-startposition-pruefen-0676749716", + "name": "Startposition prüfen", + "progression_id": "name:startposition-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-negative-pruefen-363284950d": { + "id": "movement-negative-pruefen-363284950d", + "name": "Negative prüfen", + "progression_id": "name:negative-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-volle-reps-pruefen-4955104cc2": { + "id": "movement-volle-reps-pruefen-4955104cc2", + "name": "Volle Reps prüfen", + "progression_id": "name:volle-reps-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-schwierigkeit-erhoehen-2b4e278d42": { + "id": "movement-schwierigkeit-erhoehen-2b4e278d42", + "name": "Schwierigkeit erhöhen", + "progression_id": "name:schwierigkeit-erhoehen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + } + } + } +} diff --git a/android/app/src/test/resources/samples/plans/phase-1.json b/android/app/src/test/resources/samples/plans/phase-1.json new file mode 100644 index 0000000..c06dd15 --- /dev/null +++ b/android/app/src/test/resources/samples/plans/phase-1.json @@ -0,0 +1,16995 @@ +{ + "plan_id": "phase-1", + "name": "Phase 1", + "revision": 1, + "published_revision": 1, + "created_at": "2026-07-24T09:43:48+00:00", + "updated_at": "2026-07-24T09:43:48+00:00", + "published_at": "2026-07-24T09:43:48+00:00", + "config": { + "colors": { + "ACC2": "#0F766E", + "ACCENT": "#C2410C", + "DARK": "#23201C", + "GOALBG": "#FBF1E9", + "GRID": "#CFC9BF", + "INK": "#1A1A1A", + "KBBG": "#F1ECFB", + "KBCOL": "#6D28D9", + "LIGHT": "#F4F1EC", + "LINK": "#1565C0", + "MUTE": "#6B6256", + "WARMBG": "#EAF1F0" + }, + "days": [ + { + "focus": "Drücken + Beine", + "num": 1, + "rotations": [ + { + "exercises": [ + { + "cue": "Brust kontrolliert senken & drücken", + "key": "ls", + "name": "Liegestütze", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-b5c3e6155f", + "exercise_id": "movement-liegestuetze-57eacf74ee", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d1-r0-e0" + }, + { + "cue": "Po vor der Couch senken, hochdrücken", + "key": "dips", + "name": "Bank-Dips (Couch)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "dips", + "id": "exercise-1-ada8cf48d3", + "exercise_id": "movement-bank-dips-couch-ad6c829e1b", + "movement_cluster": "vertical_push", + "movement_label": "Dip", + "legacy_id": "d1-r0-e1" + }, + { + "cue": "umgek. V, Kopf senken (Schultern)", + "key": "pike", + "name": "Pike", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "pike", + "id": "exercise-2-1cb8b9bbd7", + "exercise_id": "movement-pike-17f7716e00", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d1-r0-e2" + }, + { + "cue": "Arme seitlich bis Schulterhöhe, kontrolliert", + "key": "lraise", + "name": "Seitheben (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "lraise", + "id": "exercise-3-2908dafdf9", + "exercise_id": "movement-seitheben-kb-rucksack-91de5dc2f5", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d1-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-7bd8374b98" + }, + { + "exercises": [ + { + "cue": "Last umarmen, tief", + "key": "gobletkb", + "name": "Goblet Squat (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "gobletkb", + "id": "exercise-item-7816b10b95", + "exercise_id": "movement-goblet-squat-kb-rucksack-6ff9f90901", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r1-e0" + }, + { + "cue": "großer Schritt, Knie Richtung Boden", + "key": "lunge", + "name": "Ausfallschritt rückw.", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "lunge", + "id": "exercise-1-232e3c9065", + "exercise_id": "movement-ausfallschritt-rueckw-2a4d06b799", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r1-e1" + }, + { + "cue": "Hüfte hochdrücken, Po fest", + "key": "bridge", + "name": "Glute Bridge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "bridge", + "id": "exercise-2-0a8da65f1b", + "exercise_id": "movement-glute-bridge-1c0a7bf699", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d1-r1-e2" + }, + { + "cue": "auf Zehenspitzen hoch/runter", + "key": "calf", + "name": "Wadenheben", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "calf", + "id": "exercise-3-bfc4e8f9c3", + "exercise_id": "movement-wadenheben-0c7839551b", + "movement_cluster": "calf", + "movement_label": "Waden", + "legacy_id": "d1-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-b9e0a052af" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-druecken-beine-9f04ffe00b" + }, + { + "focus": "Ziehen + Core", + "num": 2, + "rotations": [ + { + "exercises": [ + { + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts", + "key": "rowe", + "name": "Einarm-Rudern ENG (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "rowe", + "id": "exercise-item-8b823b51cb", + "exercise_id": "movement-einarm-rudern-eng-kb-4907fbb5c7", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d2-r0-e0" + }, + { + "cue": "Oberarme fix, zu den Schultern", + "key": "load", + "name": "Bizeps-Curls (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "load", + "id": "exercise-1-1a66ac153c", + "exercise_id": "movement-bizeps-curls-kb-rucksack-67944f00d5", + "movement_cluster": "elbow_flexion", + "movement_label": "Bizeps", + "legacy_id": "d2-r0-e1" + }, + { + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts", + "key": "row2", + "name": "Einarm-Rudern BREIT (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "row2", + "id": "exercise-2-ab58589731", + "exercise_id": "movement-einarm-rudern-breit-kb-0146abcfbb", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d2-r0-e2" + }, + { + "cue": "bäuchlings Arme+Beine heben", + "key": "sup", + "name": "Superman / Y-T-W", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "sup", + "id": "exercise-3-8974cdd4a3", + "exercise_id": "movement-superman-y-t-w-279f03a544", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-024cf0041d" + }, + { + "exercises": [ + { + "cue": "gerade Linie halten", + "key": "plank", + "name": "Plank", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-item-40e37731b0", + "exercise_id": "movement-plank-c8449f2937", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d2-r1-e0" + }, + { + "cue": "Knie zügig zur Brust", + "key": "mtn", + "name": "Mountain Climbers", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "mtn", + "id": "exercise-1-69ed72c9c3", + "exercise_id": "movement-mountain-climbers-79efdb62dd", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e1" + }, + { + "cue": "gegengleich, Rücken am Boden", + "key": "db", + "name": "Dead Bug", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "db", + "id": "exercise-2-0fc9fa4d38", + "exercise_id": "movement-dead-bug-21d4b207d5", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d2-r1-e2" + }, + { + "cue": "Beine heben/senken", + "key": "lr", + "name": "Beinheben", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "lr", + "id": "exercise-3-1373648dc9", + "exercise_id": "movement-beinheben-faf19a629b", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-f767e75f57" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-ziehen-core-26dd9ab93d" + }, + { + "focus": "Beine + Core", + "num": 3, + "rotations": [ + { + "exercises": [ + { + "cue": "Last umarmen, tief", + "key": "gobletkb", + "name": "Goblet Squat (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "gobletkb", + "id": "exercise-item-0264c1e44a", + "exercise_id": "movement-goblet-squat-kb-rucksack-6ff9f90901", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r0-e0" + }, + { + "cue": "Hüfte zurück, Rücken gerade, Hamstrings", + "key": "load", + "name": "RDL / Hip Hinge (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "load", + "id": "exercise-1-9d276268ca", + "exercise_id": "movement-rdl-hip-hinge-kb-rucksack-9ada5a67c3", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d3-r0-e1" + }, + { + "cue": "ein Bein gestreckt", + "key": "bridge", + "name": "Glute Bridge 1-beinig", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "bridge", + "id": "exercise-2-4b6e8a5bc6", + "exercise_id": "movement-glute-bridge-1-beinig-ccc8c9fb53", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d3-r0-e2" + }, + { + "cue": "Rücken an die Wand, halten", + "key": "wall", + "name": "Wall Sit", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "wall", + "id": "exercise-3-add8004d7e", + "exercise_id": "movement-wall-sit-6b5f695f6a", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-de121c91a0" + }, + { + "exercises": [ + { + "cue": "unterer Rücken am Boden", + "key": "holl", + "name": "Hollow Hold", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "holl", + "id": "exercise-item-072e8d32be", + "exercise_id": "movement-hollow-hold-54c452f77b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r1-e0" + }, + { + "cue": "seitlich auf einem Unterarm", + "key": "sp", + "name": "Side Plank (Wechsel)", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "sp", + "id": "exercise-1-cedec1156e", + "exercise_id": "movement-side-plank-wechsel-309b0bc82c", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r1-e1" + }, + { + "cue": "Ellbogen zum Gegenknie", + "key": "bic", + "name": "Bicycle Crunch", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "bic", + "id": "exercise-2-fe1a6ad3ca", + "exercise_id": "movement-bicycle-crunch-15c87e28c5", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d3-r1-e2" + }, + { + "cue": "sitzend Rumpf drehen", + "key": "tw", + "name": "Russian Twist (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "tw", + "id": "exercise-3-bd5b54584a", + "exercise_id": "movement-russian-twist-kb-d6e382fb3c", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d3-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-017be520c7" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-beine-core-13963d0eea" + }, + { + "focus": "Oberkörper: Drücken + Ziehen", + "num": 4, + "rotations": [ + { + "exercises": [ + { + "cue": "Brust kontrolliert senken & drücken", + "key": "ls", + "name": "Liegestütze", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-3cc877a2d9", + "exercise_id": "movement-liegestuetze-57eacf74ee", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d4-r0-e0" + }, + { + "cue": "Po vor der Couch senken", + "key": "dips", + "name": "Bank-Dips (Couch)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "dips", + "id": "exercise-1-57f3db08a5", + "exercise_id": "movement-bank-dips-couch-ad6c829e1b", + "movement_cluster": "vertical_push", + "movement_label": "Dip", + "legacy_id": "d4-r0-e1" + }, + { + "cue": "umgek. V, Kopf senken", + "key": "pike", + "name": "Pike", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "pike", + "id": "exercise-2-f8e46033f7", + "exercise_id": "movement-pike-17f7716e00", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d4-r0-e2" + }, + { + "cue": "Glocke vom Boden weg drücken", + "key": "press", + "name": "KB Floor Press", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "press", + "id": "exercise-3-1bfa657543", + "exercise_id": "movement-kb-floor-press-060586ebd6", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d4-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-761392c073" + }, + { + "exercises": [ + { + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts", + "key": "rowe", + "name": "Einarm-Rudern ENG (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "rowe", + "id": "exercise-item-3eecbcd916", + "exercise_id": "movement-einarm-rudern-eng-kb-4907fbb5c7", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d4-r1-e0" + }, + { + "cue": "Oberarme fix, zu den Schultern", + "key": "load", + "name": "Bizeps-Curls (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "load", + "id": "exercise-1-5503931f9a", + "exercise_id": "movement-bizeps-curls-kb-rucksack-67944f00d5", + "movement_cluster": "elbow_flexion", + "movement_label": "Bizeps", + "legacy_id": "d4-r1-e1" + }, + { + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts", + "key": "row2", + "name": "Einarm-Rudern BREIT (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "row2", + "id": "exercise-2-7d45b03b10", + "exercise_id": "movement-einarm-rudern-breit-kb-0146abcfbb", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d4-r1-e2" + }, + { + "cue": "bäuchlings, Rücken", + "key": "sup", + "name": "Y-T-W / Superman", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "sup", + "id": "exercise-3-ffc825e874", + "exercise_id": "movement-y-t-w-superman-1d331ef219", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-62363a4e87" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-oberkoerper-druecken-ziehen-15d0e7f677" + }, + { + "focus": "Ganzkörper LEICHT + Mobility", + "light": true, + "num": 5, + "rotations": [ + { + "exercises": [ + { + "cue": "locker, sauber", + "key": "name:kniebeuge-leicht", + "name": "Kniebeuge (leicht)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:kniebeuge-leicht", + "id": "exercise-item-4b0e2395e9", + "exercise_id": "movement-kniebeuge-leicht-4ae3fb9fc0", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d5-r0-e0" + }, + { + "cue": "leichtere Variante", + "key": "name:liegestuetze-1-stufe-leichter", + "name": "Liegestütze (1 Stufe leichter)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:liegestuetze-1-stufe-leichter", + "id": "exercise-1-9303fa85bc", + "exercise_id": "movement-liegestuetze-1-stufe-leichter-6033b9cb92", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d5-r0-e1" + }, + { + "cue": "wenig Gewicht", + "key": "name:rudern-leicht", + "name": "Rudern (leicht)", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:rudern-leicht", + "id": "exercise-2-5224ba2082", + "exercise_id": "movement-rudern-leicht-566f8de8f2", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d5-r0-e2" + }, + { + "cue": "ruhiges Tempo", + "key": "name:mountain-climbers-locker", + "name": "Mountain Climbers (locker)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:mountain-climbers-locker", + "id": "exercise-3-3f1a039ad3", + "exercise_id": "movement-mountain-climbers-locker-e4a7854ab9", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-1750a46419" + }, + { + "exercises": [ + { + "cue": "in den Stütz wandern, zurück", + "key": "name:inchworm", + "name": "Inchworm", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:inchworm", + "id": "exercise-item-03be6e8387", + "exercise_id": "movement-inchworm-50016ff2a4", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r1-e0" + }, + { + "cue": "Rücken ründen/strecken", + "key": "name:cat-cow", + "name": "Cat-Cow", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:cat-cow", + "id": "exercise-1-92862f7d6a", + "exercise_id": "movement-cat-cow-d8e060c65f", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r1-e1" + }, + { + "cue": "gegengleich Arm+Bein, kurz halten", + "key": "name:bird-dog", + "name": "Bird Dog", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "name:bird-dog", + "id": "exercise-2-46d9abeab7", + "exercise_id": "movement-bird-dog-f2c9d49c80", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r1-e2" + }, + { + "cue": "langsam um den Kopf kreisen", + "key": "name:kb-halo-leicht", + "name": "KB-Halo (leicht)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "name:kb-halo-leicht", + "id": "exercise-3-e6cefad215", + "exercise_id": "movement-kb-halo-leicht-e3c156d79e", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d5-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-dde0c1adc1" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-ganzkoerper-leicht-mobility-ff8134fa48" + }, + { + "color": "#6D28D9", + "focus": "KETTLEBELL Ganzkörper", + "note": "Reine Kettlebell-Session. Swing = Hüft-Schnapp, kein Armheben. Glocke so wählen, dass alle Reps sauber sind.", + "note_bg": "#F1ECFB", + "num": 6, + "rotations": [ + { + "exercises": [ + { + "cue": "Hüft-Schnapp, kein Armheben", + "key": "swing", + "name": "KB Swing (zweiarmig)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "swing", + "id": "exercise-item-2fbf3bf70d", + "exercise_id": "movement-kb-swing-zweiarmig-327d7b5ad6", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d6-r0-e0" + }, + { + "cue": "Glocke vor der Brust, tief", + "key": "gobletkb", + "name": "Goblet Squat (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "gobletkb", + "id": "exercise-1-8542eb2e51", + "exercise_id": "movement-goblet-squat-kb-f065dfd5b6", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d6-r0-e1" + }, + { + "cue": "Glocke zwischen den Füßen, Hüfte hinge", + "key": "kbdl", + "name": "KB-Deadlift (beidbeinig)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "kbdl", + "id": "exercise-2-f072cf1841", + "exercise_id": "movement-kb-deadlift-beidbeinig-6f7c8bca76", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d6-r0-e2" + }, + { + "cue": "eng um den Kopf kreisen", + "key": "halo", + "name": "Halo", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "halo", + "id": "exercise-3-031200e160", + "exercise_id": "movement-halo-faca9a5cc9", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d6-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-1165003f5a" + }, + { + "exercises": [ + { + "cue": "Glocke vom Boden weg drücken", + "key": "press", + "name": "KB Floor Press", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "press", + "id": "exercise-item-04a3fceec5", + "exercise_id": "movement-kb-floor-press-060586ebd6", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d6-r1-e0" + }, + { + "cue": "Rücken gerade, zum Bauch", + "key": "row1", + "name": "Einarmiges Rudern (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "row1", + "id": "exercise-1-01087258d2", + "exercise_id": "movement-einarmiges-rudern-kb-0ec1c11b35", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d6-r1-e1" + }, + { + "cue": "Glocke Boden nach oben, Unterarm senkrecht, still halten", + "key": "carry", + "name": "Bottoms-up KB-Hold (Griff)", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "carry", + "id": "exercise-2-0a835e4b76", + "exercise_id": "movement-bottoms-up-kb-hold-griff-4a5f657f41", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d6-r1-e2" + }, + { + "cue": "Glocke um den Körper, dann diagonal zur Gegen-Schulter, andere Hand fängt", + "key": "atw", + "name": "Around-the-World + Uppercut (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "atw", + "id": "exercise-3-6386738afd", + "exercise_id": "movement-around-the-world-uppercut-kb-7ea726bbde", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d6-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-c851db6efb" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-kettlebell-ganzkoerper-e53128a814" + } + ], + "front": { + "baseline_head": "Standortbestimmung & Tracking", + "baseline_intro": "Mach die Tests vor Tag 1 und in Woche 8 identisch nochmal – dein Vorher/Nachher.", + "baseline_rows": [ + [ + "Test", + "Tag 0", + "Woche 8" + ], + [ + "Liegestütze (max, saubere Reps)", + "", + "" + ], + [ + "Bank-Dips an der Couch (max)", + "", + "" + ], + [ + "Kniebeugen in 60 Sekunden", + "", + "" + ], + [ + "Ausfallschritte (max gesamt)", + "", + "" + ], + [ + "Plank (max Sekunden)", + "", + "" + ], + [ + "Wall Sit (max Sekunden)", + "", + "" + ], + [ + "KB Farmer-Hold (max Sekunden, Griff)", + "", + "" + ], + [ + "Körpergewicht (kg)", + "95", + "" + ] + ], + "flow_rows": [ + [ + "Teil", + "Inhalt", + "Zeit" + ], + [ + "Warm-up", + "Gelenk-Prep (dynamisch)", + "~3 Min" + ], + [ + "Rotation A", + "4 Übungen, 3 Sätze (kurze Pausen)", + "~14 Min" + ], + [ + "Rotation B", + "4 Übungen, 3 Sätze (kurze Pausen)", + "~14 Min" + ], + [ + "Cool-down", + "Ausklingen + Dehnen", + "~6 Min" + ], + [ + "", + "Gesamt", + "~35 Min" + ] + ], + "goals_box": "Du startest bei 2–3 Liegestützen und 0 Dips. Realistisch und ehrlich nach 8 Wochen sauberer Arbeit:
•  Liegestütze: von 2–3 auf erste saubere volle Reps (Rest in Knie-/Negativ-Form).
•  Bank-Dips: saubere Reps mit Füßen etwas weiter weg.   Pike Push-up: am Boden mit kleinem ROM.
•  Plank ~45–60 s, Wall Sit ~60 s, viele saubere Goblet-Kniebeugen, sauberer Swing + spürbar bessere Griffkraft.
•  Noch NICHT dran: Diamond Push-ups, L-Sit, Handstand, schwerer Turkish Get-Up, Snatch – Stoff für Woche 12+.", + "goals_head": "Realistische Ziele nach 8 Wochen", + "goals_note": "Wichtig: Die Wochen-Stufen sind ein Rahmen, kein Muss. Bleib auf einer Stufe, solange sie fordert. Aufstiegs-Regel: erst hoch (oder schwerere Glocke), wenn du eine Übung 2 Sessions in Folge in allen Sätzen sauber über die vollen 20s schaffst. KB = Kettlebell bevorzugt; Rucksack bleibt als Alternative.", + "kb_box": "Du trainierst für Calisthenics, nicht für Maximalkraft. Genau da liegen die belegten Stärken der Kettlebell – ohne dass dich ihr dokumentierter Schwachpunkt (Maximalkraft/Hypertrophie wegen fixer Gewichtssprünge) trifft:
•  Griffkraft (Studie BELL: +6–7 kg) – dein größter Mehrwert auf dem Weg zu Klimmzug & Dip.
•  Power / ballistische Hüftstreckung (Swing) – explosive Basis für spätere Skills.
•  Kondition & Körperfett – wirksam belegt; du startest mit wenig Ausdauer.
•  Ganzkörper, ein Gerät, zeiteffizient – passt ideal ins 30-Min-Tabata-Format; niedriges Einstiegs-Verletzungsrisiko bei sauberer Technik.
Ehrlich: Reines KB ersetzt kein Maximalkraft-/Muskelaufbau-Programm. Für dein Ziel (Basis-Fundament) liegt das ohnehin unter der relevanten Schwelle.", + "kb_head": "Warum Kettlebell – und warum es zu deinem Ziel passt", + "lib_head": "Übungs-Bibliothek (mit Anleitungen)", + "lib_intro": "Für Übungen, die du noch nicht kennst, ist ein Link zu einer bebilderten Anleitung dabei. Lila Gruppe = Kettlebell-Übungen.", + "reminder_head": "Kurz-Reminder", + "reminders": [ + "Form vor Reps. Wähle die Stufe / Glocke, bei der du die 20s kontrolliert durcharbeitest – mit 1–2 Reps Reserve.", + "Swing-Technik first. Der Swing ist ein Hüft-Schnapp, KEIN Kniebeuge und KEIN Armheben. Erst Dead-Stop-Swings, bis die Hüftstreckung sitzt.", + "Einseitige Übungen (Tag 2): Durchgang 1 = links, Durchgang 2 = rechts – so ist jede Seite automatisch ausbalanciert.", + "Täglich ist viel. Tag 5 leicht, Tag 7 frei. Bei starkem Muskelkater einen Extra-Ruhetag – gehört zum Aufbau.", + "Schlaf & Eiweiß. 7–9 h Schlaf, ~1,6 g Eiweiß pro kg. Stechender Schmerz = stoppen." + ], + "session_how_body": "Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.", + "session_how_head": "So funktioniert eine Session", + "sheets_note": "Ab der nächsten Seite: 1 Druckblatt pro Session (Woche 1–8). Tag 6 = reine Kettlebell-Session. Warm-up steht über, Cool-down + Dehnen unter den Übungen. Bei Vorerkrankungen/Verletzungen vorher ärztlich abklären.", + "timer_note": "Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10." + }, + "library": [ + { + "color": "ACC2", + "group": "DRÜCKEN (Brust / Schultern / Trizeps)", + "items": [ + { + "desc": "Stütz, Körper gerade, Brust kontrolliert senken & drücken. Stufen: Inkline (Couch), Knie, Negativ, Voll.", + "name": "Liegestütze", + "url": "https://blog.calimove.com/2023/01/25/push-up-variations-beginner-expert/" + }, + { + "desc": "Rücken zur Couch, Hände auf der Kante, Po davor absenken (Ellbogen nach hinten), hochdrücken. Knie beugen = leichter.", + "name": "Bank-Dips (Couch)", + "url": "https://www.healthline.com/health/fitness-exercise/bench-dips" + }, + { + "desc": "Umgekehrtes V (Hintern hoch), Kopf Richtung Boden senken, hochdrücken. Schulter-Fokus. Hände erhöht = leichter.", + "name": "Pike Push-up / Hold", + "url": "https://www.hingehealth.com/resources/articles/pike-push-up/" + }, + { + "desc": "Arme seitlich bis Schulterhöhe heben, Ellbogen leicht gebeugt, kontrolliert senken. Seitlicher Deltamuskel.", + "name": "Seitheben (KB/Rucksack)", + "url": "" + } + ] + }, + { + "color": "ACC2", + "group": "ZIEHEN (Rücken / Bizeps – KB oder Rucksack + Boden)", + "items": [ + { + "desc": "Hüfte beugen, Rücken GERADE, abgestützt. Ellbogen an den Rippen, Zug zur Hüfte – Lat.", + "name": "Einarm-Rudern ENG", + "url": "https://www.puregym.com/exercises/back/rows/dumbbell-bent-over-row/" + }, + { + "desc": "Gleiche Position, aber Ellbogen ~45° geöffnet, Zug zum Brustkorb – oberer Rücken / hinterer Delta.", + "name": "Einarm-Rudern BREIT", + "url": "https://www.puregym.com/exercises/back/rows/dumbbell-bent-over-row/" + }, + { + "desc": "Last unten greifen, Oberarme fix am Körper, kontrolliert zu den Schultern curlen.", + "name": "Bizeps-Curls (KB/Rucksack)", + "url": "" + }, + { + "desc": "Bäuchlings Arme+Beine heben (Superman). Snow Angels = Arme am Boden auf/ab. Rücken ohne Stange.", + "name": "Superman / Reverse Snow Angels / Y-T-W", + "url": "https://www.calixpert.com/exercises/hollow-body-to-superman-hold" + } + ] + }, + { + "color": "ACC2", + "group": "BEINE", + "items": [ + { + "desc": "Hüfte zurück, Oberschenkel parallel, Fersen am Boden. Goblet = Glocke/Rucksack vor der Brust umarmen.", + "name": "Kniebeuge / Goblet Squat (KB/Rucksack)", + "url": "" + }, + { + "desc": "Großer Schritt (rückwärts = knieschonend), hinteres Knie Richtung Boden. Anfangs ggf. festhalten.", + "name": "Ausfallschritte", + "url": "" + }, + { + "desc": "Knie leicht gebeugt, Hüfte nach hinten, Rücken gerade, Last entlang der Beine senken bis Dehnung hinten (Hamstrings).", + "name": "RDL / Hip Hinge (KB/Rucksack)", + "url": "" + }, + { + "desc": "Bridge: Hüfte hochdrücken (1-beinig steigern). Waden: Zehenspitzen hoch/runter. Wall Sit: Rücken an die Wand, halten.", + "name": "Glute Bridge / Wadenheben / Wall Sit", + "url": "" + } + ] + }, + { + "color": "KBCOL", + "group": "KETTLEBELL (Power / Griff / Ganzkörper)", + "items": [ + { + "desc": "Wichtigste KB-Übung. Hüfthinge, Glocke aus der Hüfte explosiv auf Brusthöhe schnappen (kein Armheben), oben Po+Bauch fest. Dead-Stop zum Lernen.", + "name": "Swing (zweiarmig, Hardstyle)", + "url": "https://www.onepeloton.com/blog/kettlebell-swing/" + }, + { + "desc": "Glocke an den Hörnern vor der Brust umarmen, tief und aufrecht hocken. Beste Einsteiger-KB-Kniebeuge.", + "name": "Goblet Squat (KB)", + "url": "https://www.onepeloton.com/blog/goblet-squat/" + }, + { + "desc": "Glocke zwischen den Füßen, Hüfte hinge, Rücken gerade, vom Boden heben und Hüfte durchstrecken. Stabile Swing-Vorbereitung.", + "name": "KB-Deadlift (beidbeinig)", + "url": "https://www.onepeloton.com/blog/kettlebell-deadlift/" + }, + { + "desc": "Rücklings am Boden, Glocke vom Boden weg nach oben drücken. Schulterschonend, gut für Anfänger.", + "name": "KB Floor Press", + "url": "https://www.masterclass.com/articles/kettlebell-floor-press-guide" + }, + { + "desc": "Glocke (oder Rucksack) vor der Brust, langsam eng um den Kopf kreisen. Schulter-Mobility + Core.", + "name": "Halo", + "url": "https://www.tomsguide.com/how-to/kettlebell-halo-exercise-how-to-do-it-and-the-benefits-for-your-core-and-shoulder-muscles" + }, + { + "desc": "Glocke am Griff, Boden nach oben, Unterarm senkrecht, still halten. Schwierigkeit kommt aus der Balance – fordert Griff + Handgelenk auch mit leichter Glocke.", + "name": "Bottoms-up KB-Hold / Carry", + "url": "https://www.onepeloton.com/blog/farmers-carry/" + }, + { + "desc": "Glocke kreist um den Körper (Around-the-World); danach diagonal nach oben zur gegenüberliegenden Schulter/Brust führen, die andere Hand fängt ab (wie ein Uppercut von unten). Rotatorischer Core + Koordination. Erst nur das Umkreisen lernen, Uppercut später.", + "name": "Around-the-World + Uppercut", + "url": "" + } + ] + }, + { + "color": "ACC2", + "group": "CORE", + "items": [ + { + "desc": "Unterarmstütz, Körper gerade. Anfangs auf den Knien. Side Plank seitlich auf einem Unterarm.", + "name": "Plank / Side Plank", + "url": "" + }, + { + "desc": "Auf dem Rücken, unterer Rücken am Boden, Arme+Beine leicht angehoben. Anfänger: angewinkelt (Tuck).", + "name": "Hollow Hold", + "url": "https://www.calixpert.com/exercises/hollow-body-to-superman-hold" + }, + { + "desc": "Auf dem Rücken, Arme+Knie 90°, gegengleich Arm+Bein langsam ausstrecken, unterer Rücken bleibt am Boden.", + "name": "Dead Bug", + "url": "https://dieringe.com/exercises/dead-bug" + }, + { + "desc": "Vierfüßler, gegengleich Arm+Bein ausstrecken, Rumpf stabil, kurz halten.", + "name": "Bird Dog", + "url": "https://www.hingehealth.com/resources/articles/bird-dog/" + }, + { + "desc": "Climbers: Knie zügig zur Brust. Bicycle: Ellbogen zum Gegenknie. Twist: sitzend drehen (KB). Beinheben: Knie gebeugt = leichter.", + "name": "Mountain Climbers / Bicycle / Russian Twist / Beinheben", + "url": "" + } + ] + }, + { + "color": "ACC2", + "group": "WARM-UP & MOBILITY", + "items": [ + { + "desc": "Aus dem Stand vorbeugen, Hände nach vorne in den Stütz wandern, dann zurück.", + "name": "Inchworm", + "url": "https://www.onepeloton.com/blog/inchworm-exercise" + }, + { + "desc": "Klassische dynamische Aufwärm-Bewegungen – locker, zum Warmwerden.", + "name": "Jacks / High Knees / Kreisen / Cat-Cow", + "url": "" + } + ] + } + ], + "meta": { + "days": 6, + "doc_title": "Calisthenics 8-Wochen Komplettplan (mit Kettlebell)", + "footer_left": "Calisthenics 8-Wochen Komplettplan · mit Kettlebell", + "output_name": "Calisthenics-8-Wochen-Komplett.pdf", + "subtitle": "Blutiger Anfänger · 1,90 m · 95 kg · Rucksack + Couch + Kettlebell · tägliche ~30-Min Tabata-Session", + "title": "Calisthenics-Basis – 8-Wochen Komplettplan", + "weeks": 8 + }, + "phases": { + "items": [ + { + "name": "Phase 1 · Fundament", + "params": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "weeks": 1, + "id": "phase-phase-1-fundament-9d3418c792" + }, + { + "name": "Phase 2 · Fundament", + "params": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "weeks": 1, + "id": "phase-phase-2-fundament-f815c541ec" + }, + { + "name": "Phase 3 · Aufbau", + "params": "Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.", + "weeks": 1, + "id": "phase-phase-3-aufbau-fa21a3469e" + }, + { + "name": "Phase 4 · Aufbau", + "params": "Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.", + "weeks": 1, + "id": "phase-phase-4-aufbau-98dc807ce2" + }, + { + "name": "Phase 5 · Steigerung", + "params": "Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.", + "weeks": 1, + "id": "phase-phase-5-steigerung-f023eda6a2" + }, + { + "name": "Phase 6 · Steigerung", + "params": "Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.", + "weeks": 1, + "id": "phase-phase-6-steigerung-3dc7eb5c32" + }, + { + "name": "Phase 7 · Festigen", + "params": "Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.", + "weeks": 1, + "id": "phase-phase-7-festigen-d8284c58fc" + }, + { + "name": "Phase 8 · Festigen", + "params": "Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.", + "weeks": 1, + "id": "phase-phase-8-festigen-bfaebd57b4" + } + ], + "param_light": "Bewusst locker – aktive Erholung. Saubere, leichte Wiederholungen, keine Belastung bis zum Limit." + }, + "prepost": { + "1": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Türrahmen (Brust/Schulter, je 30–40s) · Trizeps (je 30s) · Seitheben/Lat-Seitdehnung (je 30s) · Quad im Stand (je 30s) · Knieender Hüftbeuger (je 30–40s) · Wade an der Wand (je 30s)", + "warmup": "Handflächen-Wippen · Armkreisen/Wand-Slides · Hüftkreisen + Beinschwünge · Knie-über-Zeh an der Wand" + }, + "2": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Lat-/Seitdehnung (je 30s) · Türrahmen (je 30–40s) · BWS über die Couch (30–40s) · Cobra/Sphinx (Bauch, 30s) · Unterarm (je 20s) · Knieender Hüftbeuger (je 30s)", + "warmup": "Handflächen-Wippen · Handtuch-Dislocates (8–10×) · Cat-Cow · Hüftkreisen + Beinschwünge" + }, + "3": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Hamstring-Dehnung (Couch, je 30–40s) · Quad im Stand (je 30s) · Knieender Hüftbeuger (je 30–40s) · Figure-4/Taube (je 30–40s) · Cobra + liegende Wirbelsäulen-Rotation (je 30s) · Wade an der Wand (je 30s)", + "warmup": "Hüftkreisen + Beinschwünge · Knie-über-Zeh an der Wand · Cat-Cow · Handflächen-Wippen" + }, + "4": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Türrahmen (je 30–40s) · Trizeps (je 30s) · Lat-/Seitdehnung (je 30s) · BWS über die Couch (30–40s) · Unterarm (je 20s)", + "warmup": "Handflächen-Wippen · Handtuch-Dislocates (8–10×) · Armkreisen/Wand-Slides · Cat-Cow" + }, + "5": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Türrahmen (je 30s) · Knieender Hüftbeuger (je 30s) · BWS über die Couch (30–40s) · Figure-4/Taube (je 30s) · Wade an der Wand (je 30s)", + "warmup": "Cat-Cow · Hüftkreisen + Beinschwünge · Armkreisen/Wand-Slides · Knie-über-Zeh an der Wand" + }, + "6": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Hamstring-Dehnung (Couch, je 30–40s) · Knieender Hüftbeuger (je 30–40s) · Figure-4/Taube (je 30–40s) · Unterarm (je 20s) · BWS über die Couch (30–40s) · Lat-/Seitdehnung (je 30s)", + "warmup": "Halo (leicht) · Hüftkreisen + Beinschwünge · Cat-Cow · Handflächen-Wippen" + } + }, + "stages": { + "atw": { + "name": "Around-the-World + Uppercut (KB)", + "steps": [ + { + "name": "nur Around-the-World, leicht", + "id": "step-nur-around-the-world-leicht-97c90b17c5", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "nur Around-the-World, leicht", + "id": "step-nur-around-the-world-leicht-fd2fab13cb", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Around-the-World, mehr Reps", + "id": "step-around-the-world-mehr-reps-7af159bd81", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Around-the-World, mehr Reps", + "id": "step-around-the-world-mehr-reps-43f1762f06", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, leicht", + "id": "step-uppercut-leicht-cb2ce4fe29", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, leicht", + "id": "step-uppercut-leicht-db86827e21", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, flüssig", + "id": "step-uppercut-fluessig-4b3453d5f9", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, flüssig", + "id": "step-uppercut-fluessig-2463ac3c0e", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-around-the-world-uppercut-kb-3cca10157e", + "key": "atw" + }, + "bic": { + "name": "Bicycle Crunch", + "steps": [ + { + "name": "langsam", + "id": "step-langsam-b16ce156f7", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "langsam", + "id": "step-langsam-3bc865247d", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-9b37c0536d", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-6a0ed59bbd", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-1a29fed611", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-60b7d1ac93", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-85e9262405", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-2b4933822c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-bicycle-crunch-026823904f", + "key": "bic" + }, + "bridge": { + "name": "Glute Bridge", + "steps": [ + { + "name": "beidbeinig", + "id": "step-beidbeinig-36eb2634d6", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "beidbeinig", + "id": "step-beidbeinig-8a52d360c3", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "beidbeinig, 2s halten", + "id": "step-beidbeinig-2s-halten-1ad3dfd97b", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "beidbeinig, 2s halten", + "id": "step-beidbeinig-2s-halten-6087f8f6d1", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig", + "id": "step-einbeinig-3a0c0abdfa", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig", + "id": "step-einbeinig-43807e1138", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig, 2s halten", + "id": "step-einbeinig-2s-halten-4e42109f20", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig, 2s halten", + "id": "step-einbeinig-2s-halten-6335420028", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-glute-bridge-830b37f200", + "key": "bridge" + }, + "calf": { + "name": "Wadenheben", + "steps": [ + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-e8311d7913", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-95b36efc85", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam", + "id": "step-langsam-9e41650da8", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam", + "id": "step-langsam-74db877975", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam, mehr Reps", + "id": "step-langsam-mehr-reps-16ced31e33", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam, mehr Reps", + "id": "step-langsam-mehr-reps-bd0e8e15c4", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "1-beinig", + "id": "step-1-beinig-0c2e9de2f7", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "1-beinig", + "id": "step-1-beinig-f823fd2149", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "calf", + "factor": 1.0 + } + ], + "id": "progression-wadenheben-3833cd235f", + "key": "calf" + }, + "carry": { + "name": "Bottoms-up KB-Hold (Griff)", + "steps": [ + { + "name": "im Stand 10–15s", + "id": "step-im-stand-10-15s-e90dd57ff1", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "im Stand 10–15s", + "id": "step-im-stand-10-15s-db8eec9df0", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "15–20s, ruhig", + "id": "step-15-20s-ruhig-321e582ab2", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "15–20s, ruhig", + "id": "step-15-20s-ruhig-7db6eb1a01", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis Schulterhöhe 15s", + "id": "step-bis-schulterhoehe-15s-f1763ea9e5", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis Schulterhöhe 15s", + "id": "step-bis-schulterhoehe-15s-21c8fd4d01", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "länger / ein paar Schritte", + "id": "step-laenger-ein-paar-schritte-fa5838da65", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "länger / ein paar Schritte", + "id": "step-laenger-ein-paar-schritte-ec93f04473", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-bottoms-up-kb-hold-griff-7c46cda772", + "key": "carry" + }, + "db": { + "name": "Dead Bug", + "steps": [ + { + "name": "Arme ODER Beine einzeln", + "id": "step-arme-oder-beine-einzeln-7a6a9d581b", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Arme ODER Beine einzeln", + "id": "step-arme-oder-beine-einzeln-94f8e1785d", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich", + "id": "step-gegengleich-41e02caee7", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich", + "id": "step-gegengleich-31e878a2f9", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich + Pause", + "id": "step-gegengleich-pause-9e84e14ecc", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich + Pause", + "id": "step-gegengleich-pause-bf8ebc0b7e", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Beine weiter gestreckt", + "id": "step-beine-weiter-gestreckt-453d185cf7", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Beine weiter gestreckt", + "id": "step-beine-weiter-gestreckt-1c5f566001", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-dead-bug-e390771cf1", + "key": "db" + }, + "dips": { + "name": "Bank-Dips (Couch)", + "steps": [ + { + "name": "Knie stark gebeugt, klein", + "id": "step-knie-stark-gebeugt-klein-72a7bffc76", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Knie stark gebeugt, klein", + "id": "step-knie-stark-gebeugt-klein-56482dde46", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, voll", + "id": "step-knie-gebeugt-voll-07cc34cd51", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, voll", + "id": "step-knie-gebeugt-voll-80070f8ecf", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße etwas weiter", + "id": "step-fuesse-etwas-weiter-42e0983b7f", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße etwas weiter", + "id": "step-fuesse-etwas-weiter-7b887f1140", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße weiter weg", + "id": "step-fuesse-weiter-weg-60c057769a", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße weiter weg", + "id": "step-fuesse-weiter-weg-2959fb77fb", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-bank-dips-couch-64352512e0", + "key": "dips" + }, + "gobletkb": { + "name": "Goblet Squat (KB/Rucksack)", + "steps": [ + { + "name": "leicht, Tiefe lernen", + "id": "step-leicht-tiefe-lernen-77201f2b6b", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, Tiefe lernen", + "id": "step-leicht-tiefe-lernen-70ea0f2e9b", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-57415830b2", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-abf827246a", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-2d542b9c09", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-eda93ad7ea", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel, 3s runter", + "id": "step-mittel-3s-runter-9dd7fec573", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel, 3s runter", + "id": "step-mittel-3s-runter-3f76b2391b", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-goblet-squat-kb-rucksack-0e2a5969bc", + "key": "gobletkb" + }, + "halo": { + "name": "Halo", + "steps": [ + { + "name": "leicht, langsam", + "id": "step-leicht-langsam-5d15ba8134", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "leicht, langsam", + "id": "step-leicht-langsam-c2a5c59e62", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-81a736180a", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-fe775a4a23", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-c5c0bf8989", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-3898a7cea7", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-c9db464d00", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-e1a2e46fd3", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-halo-8470d68816", + "key": "halo" + }, + "holl": { + "name": "Hollow Hold", + "steps": [ + { + "name": "Tuck (Knie an)", + "id": "step-tuck-knie-an-be15e1c436", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Tuck (Knie an)", + "id": "step-tuck-knie-an-5f62fcb360", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Tuck, länger", + "id": "step-tuck-laenger-b05dd43bd8", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Tuck, länger", + "id": "step-tuck-laenger-c70e4ba401", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-39bccf6e6b", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-9147433141", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + }, + { + "name": "halb/voll", + "id": "step-halb-voll-13f8ebb8c0", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + }, + { + "name": "halb/voll", + "id": "step-halb-voll-cde344cba9", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + } + ], + "id": "progression-hollow-hold-7e44b4d78e", + "key": "holl" + }, + "kbdl": { + "name": "KB-Deadlift (beidbeinig)", + "steps": [ + { + "name": "leicht, Technik", + "id": "step-leicht-technik-54ca22fe3d", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "leicht, Technik", + "id": "step-leicht-technik-fcd2dc3979", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-db29d97bd7", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-fd7e87b98b", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-7ed7284a10", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-e9887f19f9", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "schwerer, kontrolliert", + "id": "step-schwerer-kontrolliert-dd0f4f4538", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "schwerer, kontrolliert", + "id": "step-schwerer-kontrolliert-ab6af6caea", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-kb-deadlift-beidbeinig-0e27bd039a", + "key": "kbdl" + }, + "load": { + "name": "Bizeps-Curls (KB/Rucksack)", + "steps": [ + { + "name": "leicht", + "id": "step-leicht-273b789e0f", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-93f0046a45", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-bd37c72d4b", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-31ffb19393", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-ca55180c8e", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-d15ecf67a9", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-b1f6c26c72", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-b575d29101", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + } + ], + "id": "progression-bizeps-curls-kb-rucksack-b6ad39301a", + "key": "load" + }, + "lr": { + "name": "Beinheben", + "steps": [ + { + "name": "Knie gebeugt", + "id": "step-knie-gebeugt-1855b4de0f", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie gebeugt", + "id": "step-knie-gebeugt-863780bc32", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, langsam", + "id": "step-knie-gebeugt-langsam-425f61fdb6", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, langsam", + "id": "step-knie-gebeugt-langsam-aad675fda3", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-c84cc4988c", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-298f78b046", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "gestreckt", + "id": "step-gestreckt-a4f6fc11b8", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "gestreckt", + "id": "step-gestreckt-d43210faa3", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-beinheben-9e54736d11", + "key": "lr" + }, + "lraise": { + "name": "Seitheben (KB/Rucksack)", + "steps": [ + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-a0504bbbb1", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-a691c813e1", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-82a36d8e86", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-c736c1e695", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-3c1675e579", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-82cacf1f60", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-6b417be46f", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-b3e2812063", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-seitheben-kb-rucksack-98cf022c83", + "key": "lraise" + }, + "ls": { + "name": "Liegestütze", + "steps": [ + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-7495b7e9cc", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.1 + }, + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-231f998085", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.1 + }, + { + "name": "Inkline tiefer / Knie", + "id": "step-inkline-tiefer-knie-631d725178", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.35 + }, + { + "name": "Inkline tiefer / Knie", + "id": "step-inkline-tiefer-knie-2f351c5335", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.35 + }, + { + "name": "Knie / Negativ", + "id": "step-knie-negativ-0cf75923cb", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "Knie / Negativ", + "id": "step-knie-negativ-d225638aef", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "Negativ + erste Volle", + "id": "step-negativ-erste-volle-d68abebd8f", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.85 + }, + { + "name": "Negativ + erste Volle", + "id": "step-negativ-erste-volle-d93d10d94d", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.85 + } + ], + "id": "progression-liegestuetze-8c8a5e07de", + "key": "ls" + }, + "lseng": { + "name": "Inkline (hoch)", + "steps": [ + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-7d971a7291", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-59da475437", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline mittel", + "id": "step-inkline-mittel-821fadec71", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline mittel", + "id": "step-inkline-mittel-199e2aaf70", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline tief", + "id": "step-inkline-tief-6173a85a04", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline tief", + "id": "step-inkline-tief-54d7a4a0ff", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie-Liegestütze", + "id": "step-knie-liegestuetze-7de6b6dcfb", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie-Liegestütze", + "id": "step-knie-liegestuetze-2d4d23db17", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-inkline-hoch-e770797129", + "key": "lseng" + }, + "lunge": { + "name": "Ausfallschritt rückw.", + "steps": [ + { + "name": "Körpergewicht (ggf. festhalten)", + "id": "step-koerpergewicht-ggf-festhalten-3d6301d1bd", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "Körpergewicht (ggf. festhalten)", + "id": "step-koerpergewicht-ggf-festhalten-0d19269674", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-180853b5dd", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-0f7d4a63bd", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mehr Reps", + "id": "step-mehr-reps-d19d6c0eab", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mehr Reps", + "id": "step-mehr-reps-9235507680", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "+ Last leicht", + "id": "step-last-leicht-10d2acd532", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "+ Last leicht", + "id": "step-last-leicht-6ecdd4c919", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-ausfallschritt-rueckw-52ab461616", + "key": "lunge" + }, + "mtn": { + "name": "Mountain Climbers", + "steps": [ + { + "name": "moderat", + "id": "step-moderat-f76617e319", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-a73e3b0549", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-99c93498d6", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-a3b745efcb", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-c304accace", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-23a81c4905", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-25e355b703", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-4d2a2fd39c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-mountain-climbers-97f6359644", + "key": "mtn" + }, + "pike": { + "name": "Pike", + "steps": [ + { + "name": "Pike Hold", + "id": "step-pike-hold-d46aa2e93f", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Pike Hold", + "id": "step-pike-hold-cc334c7fc6", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Hold + kleine Senkung", + "id": "step-hold-kleine-senkung-4f3bfac4b2", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Hold + kleine Senkung", + "id": "step-hold-kleine-senkung-b93d503cb1", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Push-up Hände erhöht", + "id": "step-push-up-haende-erhoeht-7c94ab03f7", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Push-up Hände erhöht", + "id": "step-push-up-haende-erhoeht-5af7a607db", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Push-up Boden, klein", + "id": "step-push-up-boden-klein-0a27dd0a33", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Push-up Boden, klein", + "id": "step-push-up-boden-klein-44741d722a", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-pike-9726e02fef", + "key": "pike" + }, + "plank": { + "name": "Plank", + "steps": [ + { + "name": "auf Knien 15–20 s", + "id": "step-auf-knien-15-20-s-66aa989041", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien 15–20 s", + "id": "step-auf-knien-15-20-s-28c28cffdc", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "voll 20–30 s", + "id": "step-voll-20-30-s-ebbb758203", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll 20–30 s", + "id": "step-voll-20-30-s-465691f146", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll + Schulter-Taps", + "id": "step-voll-schulter-taps-9fbac9d915", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll + Schulter-Taps", + "id": "step-voll-schulter-taps-738cd33982", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-3da43cf026", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-827f28be3a", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-plank-88a9d6bafb", + "key": "plank" + }, + "press": { + "name": "KB Floor Press", + "steps": [ + { + "name": "leicht (Floor Press)", + "id": "step-leicht-floor-press-de744d6e05", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "leicht (Floor Press)", + "id": "step-leicht-floor-press-5987ddc1d2", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Floor Press mehr", + "id": "step-floor-press-mehr-3b454ea446", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Floor Press mehr", + "id": "step-floor-press-mehr-b34710868b", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead leicht", + "id": "step-overhead-leicht-7f9274fd29", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead leicht", + "id": "step-overhead-leicht-3413a54a3a", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead mittel", + "id": "step-overhead-mittel-4b236720a7", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead mittel", + "id": "step-overhead-mittel-ea724f9a42", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-kb-floor-press-0f387c469f", + "key": "press" + }, + "row1": { + "name": "Einarmiges Rudern (KB)", + "steps": [ + { + "name": "leicht", + "id": "step-leicht-db8c75b358", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-d1e4feaafa", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-19ded398d6", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-0ce270eb41", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig leicht", + "id": "step-einarmig-leicht-a6f388b349", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig leicht", + "id": "step-einarmig-leicht-a9539fbadb", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig mittel", + "id": "step-einarmig-mittel-74f0fe3e1e", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig mittel", + "id": "step-einarmig-mittel-3f961280f6", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-einarmiges-rudern-kb-c142975b5b", + "key": "row1" + }, + "row2": { + "name": "Einarm-Rudern BREIT (KB)", + "steps": [ + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-32d83f79d0", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-76c26b9c7f", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-3bbb6ef617", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-1fea9ba00a", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-5f7bc5489c", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-d4c65b81c6", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel, Pause oben", + "id": "step-mittel-pause-oben-d5cdfdfe94", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel, Pause oben", + "id": "step-mittel-pause-oben-8f7a255ad4", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-einarm-rudern-breit-kb-a06708cc5d", + "key": "row2" + }, + "rowe": { + "name": "Einarm-Rudern ENG (KB)", + "steps": [ + { + "name": "leicht", + "id": "step-leicht-7638fa3210", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-d1490519d3", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-4f2775725c", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-35098ff9fa", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-cd74b90448", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-558d6deb5b", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-fec615da4c", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-60a8a2aedd", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-einarm-rudern-eng-kb-b622a3fbfc", + "key": "rowe" + }, + "sp": { + "name": "Side Plank (Wechsel)", + "steps": [ + { + "name": "auf Knien", + "id": "step-auf-knien-3ed08acc27", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien", + "id": "step-auf-knien-000b6c864b", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien, länger", + "id": "step-auf-knien-laenger-d1c3028780", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien, länger", + "id": "step-auf-knien-laenger-227ec9b5f7", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "voll", + "id": "step-voll-6702e0693f", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll", + "id": "step-voll-727fcc161a", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-8c287bb42a", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-ef549f755a", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-side-plank-wechsel-335152ff85", + "key": "sp" + }, + "squat": { + "name": "Körpergewicht", + "steps": [ + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-e4bfd724af", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-cf13a813b5", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht, mehr Reps", + "id": "step-koerpergewicht-mehr-reps-0156c8a851", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht, mehr Reps", + "id": "step-koerpergewicht-mehr-reps-9fccf7b6ed", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet leicht", + "id": "step-goblet-leicht-e52d2635ab", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet leicht", + "id": "step-goblet-leicht-758736b15a", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet mittel", + "id": "step-goblet-mittel-50a2e4ec3e", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet mittel", + "id": "step-goblet-mittel-0c01361ea0", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-koerpergewicht-6b7808a004", + "key": "squat" + }, + "sup": { + "name": "Superman / Y-T-W", + "steps": [ + { + "name": "Superman Hold", + "id": "step-superman-hold-06efa2683a", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Superman Hold", + "id": "step-superman-hold-34af38f543", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Superman Hold länger", + "id": "step-superman-hold-laenger-b383f0e540", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Superman Hold länger", + "id": "step-superman-hold-laenger-6b326b0a11", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Reverse Snow Angels", + "id": "step-reverse-snow-angels-871916bcee", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Reverse Snow Angels", + "id": "step-reverse-snow-angels-78c24f8835", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Y-T-W Raises", + "id": "step-y-t-w-raises-b7c40c3579", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Y-T-W Raises", + "id": "step-y-t-w-raises-a6d76cb15c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-superman-y-t-w-06938b356b", + "key": "sup" + }, + "swing": { + "name": "KB Swing (zweiarmig)", + "steps": [ + { + "name": "Dead-Stop, leicht (Technik)", + "id": "step-dead-stop-leicht-technik-02548f1a05", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Dead-Stop, leicht (Technik)", + "id": "step-dead-stop-leicht-technik-3b9715b104", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Dead-Stop, mehr Reps", + "id": "step-dead-stop-mehr-reps-7be430a756", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Dead-Stop, mehr Reps", + "id": "step-dead-stop-mehr-reps-5c68075818", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, mittel", + "id": "step-durchgehend-mittel-183cc7dfa6", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, mittel", + "id": "step-durchgehend-mittel-7762c8f240", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, schwerer", + "id": "step-durchgehend-schwerer-6da670bed8", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, schwerer", + "id": "step-durchgehend-schwerer-a1609b5bad", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-kb-swing-zweiarmig-b9b86d8de1", + "key": "swing" + }, + "tsquat": { + "name": "3s runter", + "steps": [ + { + "name": "3s runter", + "id": "step-3s-runter-1cafcb49f1", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "3s runter", + "id": "step-3s-runter-c29275cc81", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "3s + Pause unten", + "id": "step-3s-pause-unten-8b4f16c542", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "3s + Pause unten", + "id": "step-3s-pause-unten-d51468b370", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s", + "id": "step-goblet-3s-ebd1293f2a", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s", + "id": "step-goblet-3s-06003d3bdd", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s + Pause", + "id": "step-goblet-3s-pause-a70c738b23", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s + Pause", + "id": "step-goblet-3s-pause-40369412a4", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-3s-runter-b85ef7f990", + "key": "tsquat" + }, + "tw": { + "name": "Russian Twist (KB)", + "steps": [ + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-3bd115d2ec", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-478be9efcd", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-32fc20afc1", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-fbe33d8a04", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB leicht", + "id": "step-kb-leicht-cf37c395cd", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB leicht", + "id": "step-kb-leicht-5cca613a86", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB", + "id": "step-kb-f50c52e3e3", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB", + "id": "step-kb-eb15afb22c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-russian-twist-kb-4ae08fd89a", + "key": "tw" + }, + "wall": { + "name": "Wall Sit", + "steps": [ + { + "name": "15–25 s", + "id": "step-15-25-s-c7aec770d4", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "15–25 s", + "id": "step-15-25-s-dbdcddfcac", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "25–40 s", + "id": "step-25-40-s-7a71cfd7e1", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "25–40 s", + "id": "step-25-40-s-851e536e20", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "40–55 s", + "id": "step-40-55-s-96e1d7939b", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "40–55 s", + "id": "step-40-55-s-ebe34a6bbd", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "55–70 s", + "id": "step-55-70-s-3ddd9ace4a", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "55–70 s", + "id": "step-55-70-s-19c7e1d6d2", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-wall-sit-e5b793fbdf", + "key": "wall" + } + }, + "training_format": { + "mode": "tabata", + "fixed_interval": true, + "work_seconds": 20, + "rest_seconds": 10, + "rounds": 8, + "rounds_scope": "block", + "schema_version": 1 + }, + "schema_version": 3, + "contract_version": 2, + "plan_id": "phase-1", + "exercise_catalog": { + "movement-liegestuetze-57eacf74ee": { + "id": "movement-liegestuetze-57eacf74ee", + "name": "Liegestütze", + "progression_id": "ls", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-inkline-hoch-7495b7e9cc", + "name": "Inkline (hoch)", + "factor": 0.1, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-inkline-hoch-231f998085", + "name": "Inkline (hoch)", + "factor": 0.1, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-inkline-tiefer-knie-631d725178", + "name": "Inkline tiefer / Knie", + "factor": 0.35, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-inkline-tiefer-knie-2f351c5335", + "name": "Inkline tiefer / Knie", + "factor": 0.35, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-negativ-0cf75923cb", + "name": "Knie / Negativ", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-negativ-d225638aef", + "name": "Knie / Negativ", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negativ-erste-volle-d68abebd8f", + "name": "Negativ + erste Volle", + "factor": 0.85, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negativ-erste-volle-d93d10d94d", + "name": "Negativ + erste Volle", + "factor": 0.85, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-bank-dips-couch-ad6c829e1b": { + "id": "movement-bank-dips-couch-ad6c829e1b", + "name": "Bank-Dips (Couch)", + "progression_id": "dips", + "movement_cluster": "vertical_push", + "movement_label": "Dip", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-knie-stark-gebeugt-klein-72a7bffc76", + "name": "Knie stark gebeugt, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-stark-gebeugt-klein-56482dde46", + "name": "Knie stark gebeugt, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-voll-07cc34cd51", + "name": "Knie gebeugt, voll", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-voll-80070f8ecf", + "name": "Knie gebeugt, voll", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-etwas-weiter-42e0983b7f", + "name": "Füße etwas weiter", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-etwas-weiter-7b887f1140", + "name": "Füße etwas weiter", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-weiter-weg-60c057769a", + "name": "Füße weiter weg", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-weiter-weg-2959fb77fb", + "name": "Füße weiter weg", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-pike-17f7716e00": { + "id": "movement-pike-17f7716e00", + "name": "Pike", + "progression_id": "pike", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-pike-hold-d46aa2e93f", + "name": "Pike Hold", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-pike-hold-cc334c7fc6", + "name": "Pike Hold", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-hold-kleine-senkung-4f3bfac4b2", + "name": "Hold + kleine Senkung", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-hold-kleine-senkung-b93d503cb1", + "name": "Hold + kleine Senkung", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-haende-erhoeht-7c94ab03f7", + "name": "Push-up Hände erhöht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-haende-erhoeht-5af7a607db", + "name": "Push-up Hände erhöht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-boden-klein-0a27dd0a33", + "name": "Push-up Boden, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-boden-klein-44741d722a", + "name": "Push-up Boden, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-seitheben-kb-rucksack-91de5dc2f5": { + "id": "movement-seitheben-kb-rucksack-91de5dc2f5", + "name": "Seitheben (KB/Rucksack)", + "progression_id": "lraise", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-technik-a0504bbbb1", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-technik-a691c813e1", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-82a36d8e86", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-c736c1e695", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-3c1675e579", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-82cacf1f60", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-6b417be46f", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-b3e2812063", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-goblet-squat-kb-rucksack-6ff9f90901": { + "id": "movement-goblet-squat-kb-rucksack-6ff9f90901", + "name": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-tiefe-lernen-77201f2b6b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-tiefe-lernen-70ea0f2e9b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-57415830b2", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-abf827246a", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-2d542b9c09", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-eda93ad7ea", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-9dd7fec573", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-3f76b2391b", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-ausfallschritt-rueckw-2a4d06b799": { + "id": "movement-ausfallschritt-rueckw-2a4d06b799", + "name": "Ausfallschritt rückw.", + "progression_id": "lunge", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-koerpergewicht-ggf-festhalten-3d6301d1bd", + "name": "Körpergewicht (ggf. festhalten)", + "factor": 0.55, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-koerpergewicht-ggf-festhalten-0d19269674", + "name": "Körpergewicht (ggf. festhalten)", + "factor": 0.55, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-koerpergewicht-180853b5dd", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-koerpergewicht-0f7d4a63bd", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-reps-d19d6c0eab", + "name": "mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-reps-9235507680", + "name": "mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-last-leicht-10d2acd532", + "name": "+ Last leicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-last-leicht-6ecdd4c919", + "name": "+ Last leicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-glute-bridge-1c0a7bf699": { + "id": "movement-glute-bridge-1c0a7bf699", + "name": "Glute Bridge", + "progression_id": "bridge", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-beidbeinig-36eb2634d6", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-8a52d360c3", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-1ad3dfd97b", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-6087f8f6d1", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-einbeinig-3a0c0abdfa", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-43807e1138", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-4e42109f20", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-6335420028", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + } + ] + }, + "movement-wadenheben-0c7839551b": { + "id": "movement-wadenheben-0c7839551b", + "name": "Wadenheben", + "progression_id": "calf", + "movement_cluster": "calf", + "movement_label": "Waden", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-koerpergewicht-e8311d7913", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-koerpergewicht-95b36efc85", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-9e41650da8", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-74db877975", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-mehr-reps-16ced31e33", + "name": "langsam, mehr Reps", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-mehr-reps-bd0e8e15c4", + "name": "langsam, mehr Reps", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-1-beinig-0c2e9de2f7", + "name": "1-beinig", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-1-beinig-f823fd2149", + "name": "1-beinig", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + } + ] + }, + "movement-einarm-rudern-eng-kb-4907fbb5c7": { + "id": "movement-einarm-rudern-eng-kb-4907fbb5c7", + "name": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-7638fa3210", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-d1490519d3", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-4f2775725c", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-35098ff9fa", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-cd74b90448", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-558d6deb5b", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-fordernd-fec615da4c", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-fordernd-60a8a2aedd", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-bizeps-curls-kb-rucksack-67944f00d5": { + "id": "movement-bizeps-curls-kb-rucksack-67944f00d5", + "name": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "movement_cluster": "elbow_flexion", + "movement_label": "Bizeps", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-273b789e0f", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-93f0046a45", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-bd37c72d4b", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-31ffb19393", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-ca55180c8e", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-d15ecf67a9", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b1f6c26c72", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b575d29101", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-einarm-rudern-breit-kb-0146abcfbb": { + "id": "movement-einarm-rudern-breit-kb-0146abcfbb", + "name": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-technik-32d83f79d0", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-technik-76c26b9c7f", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-3bbb6ef617", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-1fea9ba00a", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-5f7bc5489c", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-d4c65b81c6", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-pause-oben-d5cdfdfe94", + "name": "mittel, Pause oben", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-pause-oben-8f7a255ad4", + "name": "mittel, Pause oben", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-superman-y-t-w-279f03a544": { + "id": "movement-superman-y-t-w-279f03a544", + "name": "Superman / Y-T-W", + "progression_id": "sup", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-superman-hold-06efa2683a", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-34af38f543", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-b383f0e540", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-6b326b0a11", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-871916bcee", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-78c24f8835", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-b7c40c3579", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-a6d76cb15c", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-plank-c8449f2937": { + "id": "movement-plank-c8449f2937", + "name": "Plank", + "progression_id": "plank", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-auf-knien-15-20-s-66aa989041", + "name": "auf Knien 15–20 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-auf-knien-15-20-s-28c28cffdc", + "name": "auf Knien 15–20 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-20-30-s-ebbb758203", + "name": "voll 20–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-20-30-s-465691f146", + "name": "voll 20–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-schulter-taps-9fbac9d915", + "name": "voll + Schulter-Taps", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-schulter-taps-738cd33982", + "name": "voll + Schulter-Taps", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-laenger-3da43cf026", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-laenger-827f28be3a", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-mountain-climbers-79efdb62dd": { + "id": "movement-mountain-climbers-79efdb62dd", + "name": "Mountain Climbers", + "progression_id": "mtn", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-moderat-f76617e319", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-a73e3b0549", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-99c93498d6", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-a3b745efcb", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-c304accace", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-23a81c4905", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-25e355b703", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-4d2a2fd39c", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-dead-bug-21d4b207d5": { + "id": "movement-dead-bug-21d4b207d5", + "name": "Dead Bug", + "progression_id": "db", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-arme-oder-beine-einzeln-7a6a9d581b", + "name": "Arme ODER Beine einzeln", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-arme-oder-beine-einzeln-94f8e1785d", + "name": "Arme ODER Beine einzeln", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-41e02caee7", + "name": "gegengleich", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-31e878a2f9", + "name": "gegengleich", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-pause-9e84e14ecc", + "name": "gegengleich + Pause", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-pause-bf8ebc0b7e", + "name": "gegengleich + Pause", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beine-weiter-gestreckt-453d185cf7", + "name": "Beine weiter gestreckt", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beine-weiter-gestreckt-1c5f566001", + "name": "Beine weiter gestreckt", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-beinheben-faf19a629b": { + "id": "movement-beinheben-faf19a629b", + "name": "Beinheben", + "progression_id": "lr", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-knie-gebeugt-1855b4de0f", + "name": "Knie gebeugt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-863780bc32", + "name": "Knie gebeugt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-langsam-425f61fdb6", + "name": "Knie gebeugt, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-langsam-aad675fda3", + "name": "Knie gebeugt, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-c84cc4988c", + "name": "halb gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-298f78b046", + "name": "halb gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gestreckt-a4f6fc11b8", + "name": "gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gestreckt-d43210faa3", + "name": "gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-rdl-hip-hinge-kb-rucksack-9ada5a67c3": { + "id": "movement-rdl-hip-hinge-kb-rucksack-9ada5a67c3", + "name": "RDL / Hip Hinge (KB/Rucksack)", + "progression_id": "load", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-273b789e0f", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-93f0046a45", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-bd37c72d4b", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-31ffb19393", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-ca55180c8e", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-d15ecf67a9", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b1f6c26c72", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b575d29101", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-glute-bridge-1-beinig-ccc8c9fb53": { + "id": "movement-glute-bridge-1-beinig-ccc8c9fb53", + "name": "Glute Bridge 1-beinig", + "progression_id": "bridge", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-beidbeinig-36eb2634d6", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-8a52d360c3", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-1ad3dfd97b", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-6087f8f6d1", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-einbeinig-3a0c0abdfa", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-43807e1138", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-4e42109f20", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-6335420028", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + } + ] + }, + "movement-wall-sit-6b5f695f6a": { + "id": "movement-wall-sit-6b5f695f6a", + "name": "Wall Sit", + "progression_id": "wall", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-15-25-s-c7aec770d4", + "name": "15–25 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-15-25-s-dbdcddfcac", + "name": "15–25 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-25-40-s-7a71cfd7e1", + "name": "25–40 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-25-40-s-851e536e20", + "name": "25–40 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-40-55-s-96e1d7939b", + "name": "40–55 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-40-55-s-ebe34a6bbd", + "name": "40–55 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-55-70-s-3ddd9ace4a", + "name": "55–70 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-55-70-s-19c7e1d6d2", + "name": "55–70 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-hollow-hold-54c452f77b": { + "id": "movement-hollow-hold-54c452f77b", + "name": "Hollow Hold", + "progression_id": "holl", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-tuck-knie-an-be15e1c436", + "name": "Tuck (Knie an)", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-tuck-knie-an-5f62fcb360", + "name": "Tuck (Knie an)", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-tuck-laenger-b05dd43bd8", + "name": "Tuck, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-tuck-laenger-c70e4ba401", + "name": "Tuck, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-39bccf6e6b", + "name": "halb gestreckt", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-9147433141", + "name": "halb gestreckt", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-voll-13f8ebb8c0", + "name": "halb/voll", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-voll-cde344cba9", + "name": "halb/voll", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-side-plank-wechsel-309b0bc82c": { + "id": "movement-side-plank-wechsel-309b0bc82c", + "name": "Side Plank (Wechsel)", + "progression_id": "sp", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-auf-knien-3ed08acc27", + "name": "auf Knien", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-auf-knien-000b6c864b", + "name": "auf Knien", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-auf-knien-laenger-d1c3028780", + "name": "auf Knien, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-auf-knien-laenger-227ec9b5f7", + "name": "auf Knien, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-6702e0693f", + "name": "voll", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-727fcc161a", + "name": "voll", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-laenger-8c287bb42a", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-laenger-ef549f755a", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-bicycle-crunch-15c87e28c5": { + "id": "movement-bicycle-crunch-15c87e28c5", + "name": "Bicycle Crunch", + "progression_id": "bic", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-langsam-b16ce156f7", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-3bc865247d", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-9b37c0536d", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-6a0ed59bbd", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-1a29fed611", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-60b7d1ac93", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-85e9262405", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-2b4933822c", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-russian-twist-kb-d6e382fb3c": { + "id": "movement-russian-twist-kb-d6e382fb3c", + "name": "Russian Twist (KB)", + "progression_id": "tw", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-ohne-gewicht-3bd115d2ec", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-ohne-gewicht-478be9efcd", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-ohne-gewicht-32fc20afc1", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-ohne-gewicht-fbe33d8a04", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-leicht-cf37c395cd", + "name": "KB leicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-leicht-5cca613a86", + "name": "KB leicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-f50c52e3e3", + "name": "KB", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-eb15afb22c", + "name": "KB", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-kb-floor-press-060586ebd6": { + "id": "movement-kb-floor-press-060586ebd6", + "name": "KB Floor Press", + "progression_id": "press", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-floor-press-de744d6e05", + "name": "leicht (Floor Press)", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-floor-press-5987ddc1d2", + "name": "leicht (Floor Press)", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-floor-press-mehr-3b454ea446", + "name": "Floor Press mehr", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-floor-press-mehr-b34710868b", + "name": "Floor Press mehr", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-leicht-7f9274fd29", + "name": "Overhead leicht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-leicht-3413a54a3a", + "name": "Overhead leicht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-mittel-4b236720a7", + "name": "Overhead mittel", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-mittel-ea724f9a42", + "name": "Overhead mittel", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-y-t-w-superman-1d331ef219": { + "id": "movement-y-t-w-superman-1d331ef219", + "name": "Y-T-W / Superman", + "progression_id": "sup", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-superman-hold-06efa2683a", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-34af38f543", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-b383f0e540", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-6b326b0a11", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-871916bcee", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-78c24f8835", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-b7c40c3579", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-a6d76cb15c", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-kniebeuge-leicht-4ae3fb9fc0": { + "id": "movement-kniebeuge-leicht-4ae3fb9fc0", + "name": "Kniebeuge (leicht)", + "progression_id": "name:kniebeuge-leicht", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-liegestuetze-1-stufe-leichter-6033b9cb92": { + "id": "movement-liegestuetze-1-stufe-leichter-6033b9cb92", + "name": "Liegestütze (1 Stufe leichter)", + "progression_id": "name:liegestuetze-1-stufe-leichter", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-rudern-leicht-566f8de8f2": { + "id": "movement-rudern-leicht-566f8de8f2", + "name": "Rudern (leicht)", + "progression_id": "name:rudern-leicht", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-mountain-climbers-locker-e4a7854ab9": { + "id": "movement-mountain-climbers-locker-e4a7854ab9", + "name": "Mountain Climbers (locker)", + "progression_id": "name:mountain-climbers-locker", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-inchworm-50016ff2a4": { + "id": "movement-inchworm-50016ff2a4", + "name": "Inchworm", + "progression_id": "name:inchworm", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-cat-cow-d8e060c65f": { + "id": "movement-cat-cow-d8e060c65f", + "name": "Cat-Cow", + "progression_id": "name:cat-cow", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-bird-dog-f2c9d49c80": { + "id": "movement-bird-dog-f2c9d49c80", + "name": "Bird Dog", + "progression_id": "name:bird-dog", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-kb-halo-leicht-e3c156d79e": { + "id": "movement-kb-halo-leicht-e3c156d79e", + "name": "KB-Halo (leicht)", + "progression_id": "name:kb-halo-leicht", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [] + }, + "movement-kb-swing-zweiarmig-327d7b5ad6": { + "id": "movement-kb-swing-zweiarmig-327d7b5ad6", + "name": "KB Swing (zweiarmig)", + "progression_id": "swing", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-dead-stop-leicht-technik-02548f1a05", + "name": "Dead-Stop, leicht (Technik)", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-dead-stop-leicht-technik-3b9715b104", + "name": "Dead-Stop, leicht (Technik)", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-dead-stop-mehr-reps-7be430a756", + "name": "Dead-Stop, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-dead-stop-mehr-reps-5c68075818", + "name": "Dead-Stop, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-mittel-183cc7dfa6", + "name": "durchgehend, mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-mittel-7762c8f240", + "name": "durchgehend, mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-schwerer-6da670bed8", + "name": "durchgehend, schwerer", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-schwerer-a1609b5bad", + "name": "durchgehend, schwerer", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-goblet-squat-kb-f065dfd5b6": { + "id": "movement-goblet-squat-kb-f065dfd5b6", + "name": "Goblet Squat (KB)", + "progression_id": "gobletkb", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-tiefe-lernen-77201f2b6b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-tiefe-lernen-70ea0f2e9b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-57415830b2", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-abf827246a", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-2d542b9c09", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-eda93ad7ea", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-9dd7fec573", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-3f76b2391b", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-kb-deadlift-beidbeinig-6f7c8bca76": { + "id": "movement-kb-deadlift-beidbeinig-6f7c8bca76", + "name": "KB-Deadlift (beidbeinig)", + "progression_id": "kbdl", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-technik-54ca22fe3d", + "name": "leicht, Technik", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-technik-fcd2dc3979", + "name": "leicht, Technik", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-db29d97bd7", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-fd7e87b98b", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-7ed7284a10", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-e9887f19f9", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwerer-kontrolliert-dd0f4f4538", + "name": "schwerer, kontrolliert", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwerer-kontrolliert-ab6af6caea", + "name": "schwerer, kontrolliert", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-halo-faca9a5cc9": { + "id": "movement-halo-faca9a5cc9", + "name": "Halo", + "progression_id": "halo", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-langsam-5d15ba8134", + "name": "leicht, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-langsam-c2a5c59e62", + "name": "leicht, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-81a736180a", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-fe775a4a23", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-c5c0bf8989", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-3898a7cea7", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-c9db464d00", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-e1a2e46fd3", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-einarmiges-rudern-kb-0ec1c11b35": { + "id": "movement-einarmiges-rudern-kb-0ec1c11b35", + "name": "Einarmiges Rudern (KB)", + "progression_id": "row1", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-db8c75b358", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-d1e4feaafa", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-19ded398d6", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-0ce270eb41", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-leicht-a6f388b349", + "name": "einarmig leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-leicht-a9539fbadb", + "name": "einarmig leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-mittel-74f0fe3e1e", + "name": "einarmig mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-mittel-3f961280f6", + "name": "einarmig mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-bottoms-up-kb-hold-griff-4a5f657f41": { + "id": "movement-bottoms-up-kb-hold-griff-4a5f657f41", + "name": "Bottoms-up KB-Hold (Griff)", + "progression_id": "carry", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-im-stand-10-15s-e90dd57ff1", + "name": "im Stand 10–15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-im-stand-10-15s-db8eec9df0", + "name": "im Stand 10–15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-15-20s-ruhig-321e582ab2", + "name": "15–20s, ruhig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-15-20s-ruhig-7db6eb1a01", + "name": "15–20s, ruhig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-bis-schulterhoehe-15s-f1763ea9e5", + "name": "bis Schulterhöhe 15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-bis-schulterhoehe-15s-21c8fd4d01", + "name": "bis Schulterhöhe 15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-laenger-ein-paar-schritte-fa5838da65", + "name": "länger / ein paar Schritte", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-laenger-ein-paar-schritte-ec93f04473", + "name": "länger / ein paar Schritte", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-around-the-world-uppercut-kb-7ea726bbde": { + "id": "movement-around-the-world-uppercut-kb-7ea726bbde", + "name": "Around-the-World + Uppercut (KB)", + "progression_id": "atw", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-nur-around-the-world-leicht-97c90b17c5", + "name": "nur Around-the-World, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-nur-around-the-world-leicht-fd2fab13cb", + "name": "nur Around-the-World, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-around-the-world-mehr-reps-7af159bd81", + "name": "Around-the-World, mehr Reps", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-around-the-world-mehr-reps-43f1762f06", + "name": "Around-the-World, mehr Reps", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-leicht-cb2ce4fe29", + "name": "+ Uppercut, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-leicht-db86827e21", + "name": "+ Uppercut, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-fluessig-4b3453d5f9", + "name": "+ Uppercut, flüssig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-fluessig-2463ac3c0e", + "name": "+ Uppercut, flüssig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + } + } + }, + "draft": { + "colors": { + "ACC2": "#0F766E", + "ACCENT": "#C2410C", + "DARK": "#23201C", + "GOALBG": "#FBF1E9", + "GRID": "#CFC9BF", + "INK": "#1A1A1A", + "KBBG": "#F1ECFB", + "KBCOL": "#6D28D9", + "LIGHT": "#F4F1EC", + "LINK": "#1565C0", + "MUTE": "#6B6256", + "WARMBG": "#EAF1F0" + }, + "days": [ + { + "focus": "Drücken + Beine", + "num": 1, + "rotations": [ + { + "exercises": [ + { + "cue": "Brust kontrolliert senken & drücken", + "key": "ls", + "name": "Liegestütze", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-b5c3e6155f", + "exercise_id": "movement-liegestuetze-57eacf74ee", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d1-r0-e0" + }, + { + "cue": "Po vor der Couch senken, hochdrücken", + "key": "dips", + "name": "Bank-Dips (Couch)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "dips", + "id": "exercise-1-ada8cf48d3", + "exercise_id": "movement-bank-dips-couch-ad6c829e1b", + "movement_cluster": "vertical_push", + "movement_label": "Dip", + "legacy_id": "d1-r0-e1" + }, + { + "cue": "umgek. V, Kopf senken (Schultern)", + "key": "pike", + "name": "Pike", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "pike", + "id": "exercise-2-1cb8b9bbd7", + "exercise_id": "movement-pike-17f7716e00", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d1-r0-e2" + }, + { + "cue": "Arme seitlich bis Schulterhöhe, kontrolliert", + "key": "lraise", + "name": "Seitheben (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "lraise", + "id": "exercise-3-2908dafdf9", + "exercise_id": "movement-seitheben-kb-rucksack-91de5dc2f5", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d1-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-7bd8374b98" + }, + { + "exercises": [ + { + "cue": "Last umarmen, tief", + "key": "gobletkb", + "name": "Goblet Squat (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "gobletkb", + "id": "exercise-item-7816b10b95", + "exercise_id": "movement-goblet-squat-kb-rucksack-6ff9f90901", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r1-e0" + }, + { + "cue": "großer Schritt, Knie Richtung Boden", + "key": "lunge", + "name": "Ausfallschritt rückw.", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "lunge", + "id": "exercise-1-232e3c9065", + "exercise_id": "movement-ausfallschritt-rueckw-2a4d06b799", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r1-e1" + }, + { + "cue": "Hüfte hochdrücken, Po fest", + "key": "bridge", + "name": "Glute Bridge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "bridge", + "id": "exercise-2-0a8da65f1b", + "exercise_id": "movement-glute-bridge-1c0a7bf699", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d1-r1-e2" + }, + { + "cue": "auf Zehenspitzen hoch/runter", + "key": "calf", + "name": "Wadenheben", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "calf", + "id": "exercise-3-bfc4e8f9c3", + "exercise_id": "movement-wadenheben-0c7839551b", + "movement_cluster": "calf", + "movement_label": "Waden", + "legacy_id": "d1-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-b9e0a052af" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-druecken-beine-9f04ffe00b" + }, + { + "focus": "Ziehen + Core", + "num": 2, + "rotations": [ + { + "exercises": [ + { + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts", + "key": "rowe", + "name": "Einarm-Rudern ENG (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "rowe", + "id": "exercise-item-8b823b51cb", + "exercise_id": "movement-einarm-rudern-eng-kb-4907fbb5c7", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d2-r0-e0" + }, + { + "cue": "Oberarme fix, zu den Schultern", + "key": "load", + "name": "Bizeps-Curls (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "load", + "id": "exercise-1-1a66ac153c", + "exercise_id": "movement-bizeps-curls-kb-rucksack-67944f00d5", + "movement_cluster": "elbow_flexion", + "movement_label": "Bizeps", + "legacy_id": "d2-r0-e1" + }, + { + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts", + "key": "row2", + "name": "Einarm-Rudern BREIT (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "row2", + "id": "exercise-2-ab58589731", + "exercise_id": "movement-einarm-rudern-breit-kb-0146abcfbb", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d2-r0-e2" + }, + { + "cue": "bäuchlings Arme+Beine heben", + "key": "sup", + "name": "Superman / Y-T-W", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "sup", + "id": "exercise-3-8974cdd4a3", + "exercise_id": "movement-superman-y-t-w-279f03a544", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-024cf0041d" + }, + { + "exercises": [ + { + "cue": "gerade Linie halten", + "key": "plank", + "name": "Plank", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-item-40e37731b0", + "exercise_id": "movement-plank-c8449f2937", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d2-r1-e0" + }, + { + "cue": "Knie zügig zur Brust", + "key": "mtn", + "name": "Mountain Climbers", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "mtn", + "id": "exercise-1-69ed72c9c3", + "exercise_id": "movement-mountain-climbers-79efdb62dd", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e1" + }, + { + "cue": "gegengleich, Rücken am Boden", + "key": "db", + "name": "Dead Bug", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "db", + "id": "exercise-2-0fc9fa4d38", + "exercise_id": "movement-dead-bug-21d4b207d5", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d2-r1-e2" + }, + { + "cue": "Beine heben/senken", + "key": "lr", + "name": "Beinheben", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "lr", + "id": "exercise-3-1373648dc9", + "exercise_id": "movement-beinheben-faf19a629b", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-f767e75f57" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-ziehen-core-26dd9ab93d" + }, + { + "focus": "Beine + Core", + "num": 3, + "rotations": [ + { + "exercises": [ + { + "cue": "Last umarmen, tief", + "key": "gobletkb", + "name": "Goblet Squat (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "gobletkb", + "id": "exercise-item-0264c1e44a", + "exercise_id": "movement-goblet-squat-kb-rucksack-6ff9f90901", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r0-e0" + }, + { + "cue": "Hüfte zurück, Rücken gerade, Hamstrings", + "key": "load", + "name": "RDL / Hip Hinge (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "load", + "id": "exercise-1-9d276268ca", + "exercise_id": "movement-rdl-hip-hinge-kb-rucksack-9ada5a67c3", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d3-r0-e1" + }, + { + "cue": "ein Bein gestreckt", + "key": "bridge", + "name": "Glute Bridge 1-beinig", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "bridge", + "id": "exercise-2-4b6e8a5bc6", + "exercise_id": "movement-glute-bridge-1-beinig-ccc8c9fb53", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d3-r0-e2" + }, + { + "cue": "Rücken an die Wand, halten", + "key": "wall", + "name": "Wall Sit", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "wall", + "id": "exercise-3-add8004d7e", + "exercise_id": "movement-wall-sit-6b5f695f6a", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-de121c91a0" + }, + { + "exercises": [ + { + "cue": "unterer Rücken am Boden", + "key": "holl", + "name": "Hollow Hold", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "holl", + "id": "exercise-item-072e8d32be", + "exercise_id": "movement-hollow-hold-54c452f77b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r1-e0" + }, + { + "cue": "seitlich auf einem Unterarm", + "key": "sp", + "name": "Side Plank (Wechsel)", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "sp", + "id": "exercise-1-cedec1156e", + "exercise_id": "movement-side-plank-wechsel-309b0bc82c", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r1-e1" + }, + { + "cue": "Ellbogen zum Gegenknie", + "key": "bic", + "name": "Bicycle Crunch", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "bic", + "id": "exercise-2-fe1a6ad3ca", + "exercise_id": "movement-bicycle-crunch-15c87e28c5", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d3-r1-e2" + }, + { + "cue": "sitzend Rumpf drehen", + "key": "tw", + "name": "Russian Twist (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "tw", + "id": "exercise-3-bd5b54584a", + "exercise_id": "movement-russian-twist-kb-d6e382fb3c", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d3-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-017be520c7" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-beine-core-13963d0eea" + }, + { + "focus": "Oberkörper: Drücken + Ziehen", + "num": 4, + "rotations": [ + { + "exercises": [ + { + "cue": "Brust kontrolliert senken & drücken", + "key": "ls", + "name": "Liegestütze", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-3cc877a2d9", + "exercise_id": "movement-liegestuetze-57eacf74ee", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d4-r0-e0" + }, + { + "cue": "Po vor der Couch senken", + "key": "dips", + "name": "Bank-Dips (Couch)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "dips", + "id": "exercise-1-57f3db08a5", + "exercise_id": "movement-bank-dips-couch-ad6c829e1b", + "movement_cluster": "vertical_push", + "movement_label": "Dip", + "legacy_id": "d4-r0-e1" + }, + { + "cue": "umgek. V, Kopf senken", + "key": "pike", + "name": "Pike", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "pike", + "id": "exercise-2-f8e46033f7", + "exercise_id": "movement-pike-17f7716e00", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d4-r0-e2" + }, + { + "cue": "Glocke vom Boden weg drücken", + "key": "press", + "name": "KB Floor Press", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "press", + "id": "exercise-3-1bfa657543", + "exercise_id": "movement-kb-floor-press-060586ebd6", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d4-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-761392c073" + }, + { + "exercises": [ + { + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts", + "key": "rowe", + "name": "Einarm-Rudern ENG (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "rowe", + "id": "exercise-item-3eecbcd916", + "exercise_id": "movement-einarm-rudern-eng-kb-4907fbb5c7", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d4-r1-e0" + }, + { + "cue": "Oberarme fix, zu den Schultern", + "key": "load", + "name": "Bizeps-Curls (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "load", + "id": "exercise-1-5503931f9a", + "exercise_id": "movement-bizeps-curls-kb-rucksack-67944f00d5", + "movement_cluster": "elbow_flexion", + "movement_label": "Bizeps", + "legacy_id": "d4-r1-e1" + }, + { + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts", + "key": "row2", + "name": "Einarm-Rudern BREIT (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "row2", + "id": "exercise-2-7d45b03b10", + "exercise_id": "movement-einarm-rudern-breit-kb-0146abcfbb", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d4-r1-e2" + }, + { + "cue": "bäuchlings, Rücken", + "key": "sup", + "name": "Y-T-W / Superman", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "sup", + "id": "exercise-3-ffc825e874", + "exercise_id": "movement-y-t-w-superman-1d331ef219", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-62363a4e87" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-oberkoerper-druecken-ziehen-15d0e7f677" + }, + { + "focus": "Ganzkörper LEICHT + Mobility", + "light": true, + "num": 5, + "rotations": [ + { + "exercises": [ + { + "cue": "locker, sauber", + "key": "name:kniebeuge-leicht", + "name": "Kniebeuge (leicht)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:kniebeuge-leicht", + "id": "exercise-item-4b0e2395e9", + "exercise_id": "movement-kniebeuge-leicht-4ae3fb9fc0", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d5-r0-e0" + }, + { + "cue": "leichtere Variante", + "key": "name:liegestuetze-1-stufe-leichter", + "name": "Liegestütze (1 Stufe leichter)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:liegestuetze-1-stufe-leichter", + "id": "exercise-1-9303fa85bc", + "exercise_id": "movement-liegestuetze-1-stufe-leichter-6033b9cb92", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d5-r0-e1" + }, + { + "cue": "wenig Gewicht", + "key": "name:rudern-leicht", + "name": "Rudern (leicht)", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:rudern-leicht", + "id": "exercise-2-5224ba2082", + "exercise_id": "movement-rudern-leicht-566f8de8f2", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d5-r0-e2" + }, + { + "cue": "ruhiges Tempo", + "key": "name:mountain-climbers-locker", + "name": "Mountain Climbers (locker)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:mountain-climbers-locker", + "id": "exercise-3-3f1a039ad3", + "exercise_id": "movement-mountain-climbers-locker-e4a7854ab9", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-1750a46419" + }, + { + "exercises": [ + { + "cue": "in den Stütz wandern, zurück", + "key": "name:inchworm", + "name": "Inchworm", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:inchworm", + "id": "exercise-item-03be6e8387", + "exercise_id": "movement-inchworm-50016ff2a4", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r1-e0" + }, + { + "cue": "Rücken ründen/strecken", + "key": "name:cat-cow", + "name": "Cat-Cow", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:cat-cow", + "id": "exercise-1-92862f7d6a", + "exercise_id": "movement-cat-cow-d8e060c65f", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r1-e1" + }, + { + "cue": "gegengleich Arm+Bein, kurz halten", + "key": "name:bird-dog", + "name": "Bird Dog", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "name:bird-dog", + "id": "exercise-2-46d9abeab7", + "exercise_id": "movement-bird-dog-f2c9d49c80", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r1-e2" + }, + { + "cue": "langsam um den Kopf kreisen", + "key": "name:kb-halo-leicht", + "name": "KB-Halo (leicht)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "name:kb-halo-leicht", + "id": "exercise-3-e6cefad215", + "exercise_id": "movement-kb-halo-leicht-e3c156d79e", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d5-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-dde0c1adc1" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-ganzkoerper-leicht-mobility-ff8134fa48" + }, + { + "color": "#6D28D9", + "focus": "KETTLEBELL Ganzkörper", + "note": "Reine Kettlebell-Session. Swing = Hüft-Schnapp, kein Armheben. Glocke so wählen, dass alle Reps sauber sind.", + "note_bg": "#F1ECFB", + "num": 6, + "rotations": [ + { + "exercises": [ + { + "cue": "Hüft-Schnapp, kein Armheben", + "key": "swing", + "name": "KB Swing (zweiarmig)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "swing", + "id": "exercise-item-2fbf3bf70d", + "exercise_id": "movement-kb-swing-zweiarmig-327d7b5ad6", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d6-r0-e0" + }, + { + "cue": "Glocke vor der Brust, tief", + "key": "gobletkb", + "name": "Goblet Squat (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "gobletkb", + "id": "exercise-1-8542eb2e51", + "exercise_id": "movement-goblet-squat-kb-f065dfd5b6", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d6-r0-e1" + }, + { + "cue": "Glocke zwischen den Füßen, Hüfte hinge", + "key": "kbdl", + "name": "KB-Deadlift (beidbeinig)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "kbdl", + "id": "exercise-2-f072cf1841", + "exercise_id": "movement-kb-deadlift-beidbeinig-6f7c8bca76", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d6-r0-e2" + }, + { + "cue": "eng um den Kopf kreisen", + "key": "halo", + "name": "Halo", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "halo", + "id": "exercise-3-031200e160", + "exercise_id": "movement-halo-faca9a5cc9", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d6-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-1165003f5a" + }, + { + "exercises": [ + { + "cue": "Glocke vom Boden weg drücken", + "key": "press", + "name": "KB Floor Press", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "press", + "id": "exercise-item-04a3fceec5", + "exercise_id": "movement-kb-floor-press-060586ebd6", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d6-r1-e0" + }, + { + "cue": "Rücken gerade, zum Bauch", + "key": "row1", + "name": "Einarmiges Rudern (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "row1", + "id": "exercise-1-01087258d2", + "exercise_id": "movement-einarmiges-rudern-kb-0ec1c11b35", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d6-r1-e1" + }, + { + "cue": "Glocke Boden nach oben, Unterarm senkrecht, still halten", + "key": "carry", + "name": "Bottoms-up KB-Hold (Griff)", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "carry", + "id": "exercise-2-0a835e4b76", + "exercise_id": "movement-bottoms-up-kb-hold-griff-4a5f657f41", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d6-r1-e2" + }, + { + "cue": "Glocke um den Körper, dann diagonal zur Gegen-Schulter, andere Hand fängt", + "key": "atw", + "name": "Around-the-World + Uppercut (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "atw", + "id": "exercise-3-6386738afd", + "exercise_id": "movement-around-the-world-uppercut-kb-7ea726bbde", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d6-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-c851db6efb" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-kettlebell-ganzkoerper-e53128a814" + } + ], + "front": { + "baseline_head": "Standortbestimmung & Tracking", + "baseline_intro": "Mach die Tests vor Tag 1 und in Woche 8 identisch nochmal – dein Vorher/Nachher.", + "baseline_rows": [ + [ + "Test", + "Tag 0", + "Woche 8" + ], + [ + "Liegestütze (max, saubere Reps)", + "", + "" + ], + [ + "Bank-Dips an der Couch (max)", + "", + "" + ], + [ + "Kniebeugen in 60 Sekunden", + "", + "" + ], + [ + "Ausfallschritte (max gesamt)", + "", + "" + ], + [ + "Plank (max Sekunden)", + "", + "" + ], + [ + "Wall Sit (max Sekunden)", + "", + "" + ], + [ + "KB Farmer-Hold (max Sekunden, Griff)", + "", + "" + ], + [ + "Körpergewicht (kg)", + "95", + "" + ] + ], + "flow_rows": [ + [ + "Teil", + "Inhalt", + "Zeit" + ], + [ + "Warm-up", + "Gelenk-Prep (dynamisch)", + "~3 Min" + ], + [ + "Rotation A", + "4 Übungen, 3 Sätze (kurze Pausen)", + "~14 Min" + ], + [ + "Rotation B", + "4 Übungen, 3 Sätze (kurze Pausen)", + "~14 Min" + ], + [ + "Cool-down", + "Ausklingen + Dehnen", + "~6 Min" + ], + [ + "", + "Gesamt", + "~35 Min" + ] + ], + "goals_box": "Du startest bei 2–3 Liegestützen und 0 Dips. Realistisch und ehrlich nach 8 Wochen sauberer Arbeit:
•  Liegestütze: von 2–3 auf erste saubere volle Reps (Rest in Knie-/Negativ-Form).
•  Bank-Dips: saubere Reps mit Füßen etwas weiter weg.   Pike Push-up: am Boden mit kleinem ROM.
•  Plank ~45–60 s, Wall Sit ~60 s, viele saubere Goblet-Kniebeugen, sauberer Swing + spürbar bessere Griffkraft.
•  Noch NICHT dran: Diamond Push-ups, L-Sit, Handstand, schwerer Turkish Get-Up, Snatch – Stoff für Woche 12+.", + "goals_head": "Realistische Ziele nach 8 Wochen", + "goals_note": "Wichtig: Die Wochen-Stufen sind ein Rahmen, kein Muss. Bleib auf einer Stufe, solange sie fordert. Aufstiegs-Regel: erst hoch (oder schwerere Glocke), wenn du eine Übung 2 Sessions in Folge in allen Sätzen sauber über die vollen 20s schaffst. KB = Kettlebell bevorzugt; Rucksack bleibt als Alternative.", + "kb_box": "Du trainierst für Calisthenics, nicht für Maximalkraft. Genau da liegen die belegten Stärken der Kettlebell – ohne dass dich ihr dokumentierter Schwachpunkt (Maximalkraft/Hypertrophie wegen fixer Gewichtssprünge) trifft:
•  Griffkraft (Studie BELL: +6–7 kg) – dein größter Mehrwert auf dem Weg zu Klimmzug & Dip.
•  Power / ballistische Hüftstreckung (Swing) – explosive Basis für spätere Skills.
•  Kondition & Körperfett – wirksam belegt; du startest mit wenig Ausdauer.
•  Ganzkörper, ein Gerät, zeiteffizient – passt ideal ins 30-Min-Tabata-Format; niedriges Einstiegs-Verletzungsrisiko bei sauberer Technik.
Ehrlich: Reines KB ersetzt kein Maximalkraft-/Muskelaufbau-Programm. Für dein Ziel (Basis-Fundament) liegt das ohnehin unter der relevanten Schwelle.", + "kb_head": "Warum Kettlebell – und warum es zu deinem Ziel passt", + "lib_head": "Übungs-Bibliothek (mit Anleitungen)", + "lib_intro": "Für Übungen, die du noch nicht kennst, ist ein Link zu einer bebilderten Anleitung dabei. Lila Gruppe = Kettlebell-Übungen.", + "reminder_head": "Kurz-Reminder", + "reminders": [ + "Form vor Reps. Wähle die Stufe / Glocke, bei der du die 20s kontrolliert durcharbeitest – mit 1–2 Reps Reserve.", + "Swing-Technik first. Der Swing ist ein Hüft-Schnapp, KEIN Kniebeuge und KEIN Armheben. Erst Dead-Stop-Swings, bis die Hüftstreckung sitzt.", + "Einseitige Übungen (Tag 2): Durchgang 1 = links, Durchgang 2 = rechts – so ist jede Seite automatisch ausbalanciert.", + "Täglich ist viel. Tag 5 leicht, Tag 7 frei. Bei starkem Muskelkater einen Extra-Ruhetag – gehört zum Aufbau.", + "Schlaf & Eiweiß. 7–9 h Schlaf, ~1,6 g Eiweiß pro kg. Stechender Schmerz = stoppen." + ], + "session_how_body": "Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.", + "session_how_head": "So funktioniert eine Session", + "sheets_note": "Ab der nächsten Seite: 1 Druckblatt pro Session (Woche 1–8). Tag 6 = reine Kettlebell-Session. Warm-up steht über, Cool-down + Dehnen unter den Übungen. Bei Vorerkrankungen/Verletzungen vorher ärztlich abklären.", + "timer_note": "Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10." + }, + "library": [ + { + "color": "ACC2", + "group": "DRÜCKEN (Brust / Schultern / Trizeps)", + "items": [ + { + "desc": "Stütz, Körper gerade, Brust kontrolliert senken & drücken. Stufen: Inkline (Couch), Knie, Negativ, Voll.", + "name": "Liegestütze", + "url": "https://blog.calimove.com/2023/01/25/push-up-variations-beginner-expert/" + }, + { + "desc": "Rücken zur Couch, Hände auf der Kante, Po davor absenken (Ellbogen nach hinten), hochdrücken. Knie beugen = leichter.", + "name": "Bank-Dips (Couch)", + "url": "https://www.healthline.com/health/fitness-exercise/bench-dips" + }, + { + "desc": "Umgekehrtes V (Hintern hoch), Kopf Richtung Boden senken, hochdrücken. Schulter-Fokus. Hände erhöht = leichter.", + "name": "Pike Push-up / Hold", + "url": "https://www.hingehealth.com/resources/articles/pike-push-up/" + }, + { + "desc": "Arme seitlich bis Schulterhöhe heben, Ellbogen leicht gebeugt, kontrolliert senken. Seitlicher Deltamuskel.", + "name": "Seitheben (KB/Rucksack)", + "url": "" + } + ] + }, + { + "color": "ACC2", + "group": "ZIEHEN (Rücken / Bizeps – KB oder Rucksack + Boden)", + "items": [ + { + "desc": "Hüfte beugen, Rücken GERADE, abgestützt. Ellbogen an den Rippen, Zug zur Hüfte – Lat.", + "name": "Einarm-Rudern ENG", + "url": "https://www.puregym.com/exercises/back/rows/dumbbell-bent-over-row/" + }, + { + "desc": "Gleiche Position, aber Ellbogen ~45° geöffnet, Zug zum Brustkorb – oberer Rücken / hinterer Delta.", + "name": "Einarm-Rudern BREIT", + "url": "https://www.puregym.com/exercises/back/rows/dumbbell-bent-over-row/" + }, + { + "desc": "Last unten greifen, Oberarme fix am Körper, kontrolliert zu den Schultern curlen.", + "name": "Bizeps-Curls (KB/Rucksack)", + "url": "" + }, + { + "desc": "Bäuchlings Arme+Beine heben (Superman). Snow Angels = Arme am Boden auf/ab. Rücken ohne Stange.", + "name": "Superman / Reverse Snow Angels / Y-T-W", + "url": "https://www.calixpert.com/exercises/hollow-body-to-superman-hold" + } + ] + }, + { + "color": "ACC2", + "group": "BEINE", + "items": [ + { + "desc": "Hüfte zurück, Oberschenkel parallel, Fersen am Boden. Goblet = Glocke/Rucksack vor der Brust umarmen.", + "name": "Kniebeuge / Goblet Squat (KB/Rucksack)", + "url": "" + }, + { + "desc": "Großer Schritt (rückwärts = knieschonend), hinteres Knie Richtung Boden. Anfangs ggf. festhalten.", + "name": "Ausfallschritte", + "url": "" + }, + { + "desc": "Knie leicht gebeugt, Hüfte nach hinten, Rücken gerade, Last entlang der Beine senken bis Dehnung hinten (Hamstrings).", + "name": "RDL / Hip Hinge (KB/Rucksack)", + "url": "" + }, + { + "desc": "Bridge: Hüfte hochdrücken (1-beinig steigern). Waden: Zehenspitzen hoch/runter. Wall Sit: Rücken an die Wand, halten.", + "name": "Glute Bridge / Wadenheben / Wall Sit", + "url": "" + } + ] + }, + { + "color": "KBCOL", + "group": "KETTLEBELL (Power / Griff / Ganzkörper)", + "items": [ + { + "desc": "Wichtigste KB-Übung. Hüfthinge, Glocke aus der Hüfte explosiv auf Brusthöhe schnappen (kein Armheben), oben Po+Bauch fest. Dead-Stop zum Lernen.", + "name": "Swing (zweiarmig, Hardstyle)", + "url": "https://www.onepeloton.com/blog/kettlebell-swing/" + }, + { + "desc": "Glocke an den Hörnern vor der Brust umarmen, tief und aufrecht hocken. Beste Einsteiger-KB-Kniebeuge.", + "name": "Goblet Squat (KB)", + "url": "https://www.onepeloton.com/blog/goblet-squat/" + }, + { + "desc": "Glocke zwischen den Füßen, Hüfte hinge, Rücken gerade, vom Boden heben und Hüfte durchstrecken. Stabile Swing-Vorbereitung.", + "name": "KB-Deadlift (beidbeinig)", + "url": "https://www.onepeloton.com/blog/kettlebell-deadlift/" + }, + { + "desc": "Rücklings am Boden, Glocke vom Boden weg nach oben drücken. Schulterschonend, gut für Anfänger.", + "name": "KB Floor Press", + "url": "https://www.masterclass.com/articles/kettlebell-floor-press-guide" + }, + { + "desc": "Glocke (oder Rucksack) vor der Brust, langsam eng um den Kopf kreisen. Schulter-Mobility + Core.", + "name": "Halo", + "url": "https://www.tomsguide.com/how-to/kettlebell-halo-exercise-how-to-do-it-and-the-benefits-for-your-core-and-shoulder-muscles" + }, + { + "desc": "Glocke am Griff, Boden nach oben, Unterarm senkrecht, still halten. Schwierigkeit kommt aus der Balance – fordert Griff + Handgelenk auch mit leichter Glocke.", + "name": "Bottoms-up KB-Hold / Carry", + "url": "https://www.onepeloton.com/blog/farmers-carry/" + }, + { + "desc": "Glocke kreist um den Körper (Around-the-World); danach diagonal nach oben zur gegenüberliegenden Schulter/Brust führen, die andere Hand fängt ab (wie ein Uppercut von unten). Rotatorischer Core + Koordination. Erst nur das Umkreisen lernen, Uppercut später.", + "name": "Around-the-World + Uppercut", + "url": "" + } + ] + }, + { + "color": "ACC2", + "group": "CORE", + "items": [ + { + "desc": "Unterarmstütz, Körper gerade. Anfangs auf den Knien. Side Plank seitlich auf einem Unterarm.", + "name": "Plank / Side Plank", + "url": "" + }, + { + "desc": "Auf dem Rücken, unterer Rücken am Boden, Arme+Beine leicht angehoben. Anfänger: angewinkelt (Tuck).", + "name": "Hollow Hold", + "url": "https://www.calixpert.com/exercises/hollow-body-to-superman-hold" + }, + { + "desc": "Auf dem Rücken, Arme+Knie 90°, gegengleich Arm+Bein langsam ausstrecken, unterer Rücken bleibt am Boden.", + "name": "Dead Bug", + "url": "https://dieringe.com/exercises/dead-bug" + }, + { + "desc": "Vierfüßler, gegengleich Arm+Bein ausstrecken, Rumpf stabil, kurz halten.", + "name": "Bird Dog", + "url": "https://www.hingehealth.com/resources/articles/bird-dog/" + }, + { + "desc": "Climbers: Knie zügig zur Brust. Bicycle: Ellbogen zum Gegenknie. Twist: sitzend drehen (KB). Beinheben: Knie gebeugt = leichter.", + "name": "Mountain Climbers / Bicycle / Russian Twist / Beinheben", + "url": "" + } + ] + }, + { + "color": "ACC2", + "group": "WARM-UP & MOBILITY", + "items": [ + { + "desc": "Aus dem Stand vorbeugen, Hände nach vorne in den Stütz wandern, dann zurück.", + "name": "Inchworm", + "url": "https://www.onepeloton.com/blog/inchworm-exercise" + }, + { + "desc": "Klassische dynamische Aufwärm-Bewegungen – locker, zum Warmwerden.", + "name": "Jacks / High Knees / Kreisen / Cat-Cow", + "url": "" + } + ] + } + ], + "meta": { + "days": 6, + "doc_title": "Calisthenics 8-Wochen Komplettplan (mit Kettlebell)", + "footer_left": "Calisthenics 8-Wochen Komplettplan · mit Kettlebell", + "output_name": "Calisthenics-8-Wochen-Komplett.pdf", + "subtitle": "Blutiger Anfänger · 1,90 m · 95 kg · Rucksack + Couch + Kettlebell · tägliche ~30-Min Tabata-Session", + "title": "Calisthenics-Basis – 8-Wochen Komplettplan", + "weeks": 8 + }, + "phases": { + "items": [ + { + "name": "Phase 1 · Fundament", + "params": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "weeks": 1, + "id": "phase-phase-1-fundament-9d3418c792" + }, + { + "name": "Phase 2 · Fundament", + "params": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "weeks": 1, + "id": "phase-phase-2-fundament-f815c541ec" + }, + { + "name": "Phase 3 · Aufbau", + "params": "Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.", + "weeks": 1, + "id": "phase-phase-3-aufbau-fa21a3469e" + }, + { + "name": "Phase 4 · Aufbau", + "params": "Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.", + "weeks": 1, + "id": "phase-phase-4-aufbau-98dc807ce2" + }, + { + "name": "Phase 5 · Steigerung", + "params": "Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.", + "weeks": 1, + "id": "phase-phase-5-steigerung-f023eda6a2" + }, + { + "name": "Phase 6 · Steigerung", + "params": "Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.", + "weeks": 1, + "id": "phase-phase-6-steigerung-3dc7eb5c32" + }, + { + "name": "Phase 7 · Festigen", + "params": "Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.", + "weeks": 1, + "id": "phase-phase-7-festigen-d8284c58fc" + }, + { + "name": "Phase 8 · Festigen", + "params": "Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.", + "weeks": 1, + "id": "phase-phase-8-festigen-bfaebd57b4" + } + ], + "param_light": "Bewusst locker – aktive Erholung. Saubere, leichte Wiederholungen, keine Belastung bis zum Limit." + }, + "prepost": { + "1": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Türrahmen (Brust/Schulter, je 30–40s) · Trizeps (je 30s) · Seitheben/Lat-Seitdehnung (je 30s) · Quad im Stand (je 30s) · Knieender Hüftbeuger (je 30–40s) · Wade an der Wand (je 30s)", + "warmup": "Handflächen-Wippen · Armkreisen/Wand-Slides · Hüftkreisen + Beinschwünge · Knie-über-Zeh an der Wand" + }, + "2": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Lat-/Seitdehnung (je 30s) · Türrahmen (je 30–40s) · BWS über die Couch (30–40s) · Cobra/Sphinx (Bauch, 30s) · Unterarm (je 20s) · Knieender Hüftbeuger (je 30s)", + "warmup": "Handflächen-Wippen · Handtuch-Dislocates (8–10×) · Cat-Cow · Hüftkreisen + Beinschwünge" + }, + "3": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Hamstring-Dehnung (Couch, je 30–40s) · Quad im Stand (je 30s) · Knieender Hüftbeuger (je 30–40s) · Figure-4/Taube (je 30–40s) · Cobra + liegende Wirbelsäulen-Rotation (je 30s) · Wade an der Wand (je 30s)", + "warmup": "Hüftkreisen + Beinschwünge · Knie-über-Zeh an der Wand · Cat-Cow · Handflächen-Wippen" + }, + "4": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Türrahmen (je 30–40s) · Trizeps (je 30s) · Lat-/Seitdehnung (je 30s) · BWS über die Couch (30–40s) · Unterarm (je 20s)", + "warmup": "Handflächen-Wippen · Handtuch-Dislocates (8–10×) · Armkreisen/Wand-Slides · Cat-Cow" + }, + "5": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Türrahmen (je 30s) · Knieender Hüftbeuger (je 30s) · BWS über die Couch (30–40s) · Figure-4/Taube (je 30s) · Wade an der Wand (je 30s)", + "warmup": "Cat-Cow · Hüftkreisen + Beinschwünge · Armkreisen/Wand-Slides · Knie-über-Zeh an der Wand" + }, + "6": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Hamstring-Dehnung (Couch, je 30–40s) · Knieender Hüftbeuger (je 30–40s) · Figure-4/Taube (je 30–40s) · Unterarm (je 20s) · BWS über die Couch (30–40s) · Lat-/Seitdehnung (je 30s)", + "warmup": "Halo (leicht) · Hüftkreisen + Beinschwünge · Cat-Cow · Handflächen-Wippen" + } + }, + "stages": { + "atw": { + "name": "Around-the-World + Uppercut (KB)", + "steps": [ + { + "name": "nur Around-the-World, leicht", + "id": "step-nur-around-the-world-leicht-97c90b17c5", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "nur Around-the-World, leicht", + "id": "step-nur-around-the-world-leicht-fd2fab13cb", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Around-the-World, mehr Reps", + "id": "step-around-the-world-mehr-reps-7af159bd81", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Around-the-World, mehr Reps", + "id": "step-around-the-world-mehr-reps-43f1762f06", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, leicht", + "id": "step-uppercut-leicht-cb2ce4fe29", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, leicht", + "id": "step-uppercut-leicht-db86827e21", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, flüssig", + "id": "step-uppercut-fluessig-4b3453d5f9", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, flüssig", + "id": "step-uppercut-fluessig-2463ac3c0e", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-around-the-world-uppercut-kb-3cca10157e", + "key": "atw" + }, + "bic": { + "name": "Bicycle Crunch", + "steps": [ + { + "name": "langsam", + "id": "step-langsam-b16ce156f7", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "langsam", + "id": "step-langsam-3bc865247d", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-9b37c0536d", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-6a0ed59bbd", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-1a29fed611", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-60b7d1ac93", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-85e9262405", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-2b4933822c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-bicycle-crunch-026823904f", + "key": "bic" + }, + "bridge": { + "name": "Glute Bridge", + "steps": [ + { + "name": "beidbeinig", + "id": "step-beidbeinig-36eb2634d6", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "beidbeinig", + "id": "step-beidbeinig-8a52d360c3", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "beidbeinig, 2s halten", + "id": "step-beidbeinig-2s-halten-1ad3dfd97b", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "beidbeinig, 2s halten", + "id": "step-beidbeinig-2s-halten-6087f8f6d1", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig", + "id": "step-einbeinig-3a0c0abdfa", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig", + "id": "step-einbeinig-43807e1138", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig, 2s halten", + "id": "step-einbeinig-2s-halten-4e42109f20", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig, 2s halten", + "id": "step-einbeinig-2s-halten-6335420028", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-glute-bridge-830b37f200", + "key": "bridge" + }, + "calf": { + "name": "Wadenheben", + "steps": [ + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-e8311d7913", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-95b36efc85", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam", + "id": "step-langsam-9e41650da8", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam", + "id": "step-langsam-74db877975", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam, mehr Reps", + "id": "step-langsam-mehr-reps-16ced31e33", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam, mehr Reps", + "id": "step-langsam-mehr-reps-bd0e8e15c4", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "1-beinig", + "id": "step-1-beinig-0c2e9de2f7", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "1-beinig", + "id": "step-1-beinig-f823fd2149", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "calf", + "factor": 1.0 + } + ], + "id": "progression-wadenheben-3833cd235f", + "key": "calf" + }, + "carry": { + "name": "Bottoms-up KB-Hold (Griff)", + "steps": [ + { + "name": "im Stand 10–15s", + "id": "step-im-stand-10-15s-e90dd57ff1", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "im Stand 10–15s", + "id": "step-im-stand-10-15s-db8eec9df0", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "15–20s, ruhig", + "id": "step-15-20s-ruhig-321e582ab2", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "15–20s, ruhig", + "id": "step-15-20s-ruhig-7db6eb1a01", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis Schulterhöhe 15s", + "id": "step-bis-schulterhoehe-15s-f1763ea9e5", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis Schulterhöhe 15s", + "id": "step-bis-schulterhoehe-15s-21c8fd4d01", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "länger / ein paar Schritte", + "id": "step-laenger-ein-paar-schritte-fa5838da65", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "länger / ein paar Schritte", + "id": "step-laenger-ein-paar-schritte-ec93f04473", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-bottoms-up-kb-hold-griff-7c46cda772", + "key": "carry" + }, + "db": { + "name": "Dead Bug", + "steps": [ + { + "name": "Arme ODER Beine einzeln", + "id": "step-arme-oder-beine-einzeln-7a6a9d581b", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Arme ODER Beine einzeln", + "id": "step-arme-oder-beine-einzeln-94f8e1785d", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich", + "id": "step-gegengleich-41e02caee7", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich", + "id": "step-gegengleich-31e878a2f9", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich + Pause", + "id": "step-gegengleich-pause-9e84e14ecc", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich + Pause", + "id": "step-gegengleich-pause-bf8ebc0b7e", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Beine weiter gestreckt", + "id": "step-beine-weiter-gestreckt-453d185cf7", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Beine weiter gestreckt", + "id": "step-beine-weiter-gestreckt-1c5f566001", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-dead-bug-e390771cf1", + "key": "db" + }, + "dips": { + "name": "Bank-Dips (Couch)", + "steps": [ + { + "name": "Knie stark gebeugt, klein", + "id": "step-knie-stark-gebeugt-klein-72a7bffc76", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Knie stark gebeugt, klein", + "id": "step-knie-stark-gebeugt-klein-56482dde46", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, voll", + "id": "step-knie-gebeugt-voll-07cc34cd51", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, voll", + "id": "step-knie-gebeugt-voll-80070f8ecf", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße etwas weiter", + "id": "step-fuesse-etwas-weiter-42e0983b7f", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße etwas weiter", + "id": "step-fuesse-etwas-weiter-7b887f1140", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße weiter weg", + "id": "step-fuesse-weiter-weg-60c057769a", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße weiter weg", + "id": "step-fuesse-weiter-weg-2959fb77fb", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-bank-dips-couch-64352512e0", + "key": "dips" + }, + "gobletkb": { + "name": "Goblet Squat (KB/Rucksack)", + "steps": [ + { + "name": "leicht, Tiefe lernen", + "id": "step-leicht-tiefe-lernen-77201f2b6b", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, Tiefe lernen", + "id": "step-leicht-tiefe-lernen-70ea0f2e9b", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-57415830b2", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-abf827246a", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-2d542b9c09", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-eda93ad7ea", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel, 3s runter", + "id": "step-mittel-3s-runter-9dd7fec573", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel, 3s runter", + "id": "step-mittel-3s-runter-3f76b2391b", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-goblet-squat-kb-rucksack-0e2a5969bc", + "key": "gobletkb" + }, + "halo": { + "name": "Halo", + "steps": [ + { + "name": "leicht, langsam", + "id": "step-leicht-langsam-5d15ba8134", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "leicht, langsam", + "id": "step-leicht-langsam-c2a5c59e62", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-81a736180a", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-fe775a4a23", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-c5c0bf8989", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-3898a7cea7", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-c9db464d00", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-e1a2e46fd3", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-halo-8470d68816", + "key": "halo" + }, + "holl": { + "name": "Hollow Hold", + "steps": [ + { + "name": "Tuck (Knie an)", + "id": "step-tuck-knie-an-be15e1c436", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Tuck (Knie an)", + "id": "step-tuck-knie-an-5f62fcb360", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Tuck, länger", + "id": "step-tuck-laenger-b05dd43bd8", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Tuck, länger", + "id": "step-tuck-laenger-c70e4ba401", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-39bccf6e6b", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-9147433141", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + }, + { + "name": "halb/voll", + "id": "step-halb-voll-13f8ebb8c0", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + }, + { + "name": "halb/voll", + "id": "step-halb-voll-cde344cba9", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + } + ], + "id": "progression-hollow-hold-7e44b4d78e", + "key": "holl" + }, + "kbdl": { + "name": "KB-Deadlift (beidbeinig)", + "steps": [ + { + "name": "leicht, Technik", + "id": "step-leicht-technik-54ca22fe3d", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "leicht, Technik", + "id": "step-leicht-technik-fcd2dc3979", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-db29d97bd7", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-fd7e87b98b", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-7ed7284a10", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-e9887f19f9", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "schwerer, kontrolliert", + "id": "step-schwerer-kontrolliert-dd0f4f4538", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "schwerer, kontrolliert", + "id": "step-schwerer-kontrolliert-ab6af6caea", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-kb-deadlift-beidbeinig-0e27bd039a", + "key": "kbdl" + }, + "load": { + "name": "Bizeps-Curls (KB/Rucksack)", + "steps": [ + { + "name": "leicht", + "id": "step-leicht-273b789e0f", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-93f0046a45", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-bd37c72d4b", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-31ffb19393", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-ca55180c8e", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-d15ecf67a9", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-b1f6c26c72", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-b575d29101", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + } + ], + "id": "progression-bizeps-curls-kb-rucksack-b6ad39301a", + "key": "load" + }, + "lr": { + "name": "Beinheben", + "steps": [ + { + "name": "Knie gebeugt", + "id": "step-knie-gebeugt-1855b4de0f", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie gebeugt", + "id": "step-knie-gebeugt-863780bc32", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, langsam", + "id": "step-knie-gebeugt-langsam-425f61fdb6", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, langsam", + "id": "step-knie-gebeugt-langsam-aad675fda3", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-c84cc4988c", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-298f78b046", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "gestreckt", + "id": "step-gestreckt-a4f6fc11b8", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "gestreckt", + "id": "step-gestreckt-d43210faa3", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-beinheben-9e54736d11", + "key": "lr" + }, + "lraise": { + "name": "Seitheben (KB/Rucksack)", + "steps": [ + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-a0504bbbb1", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-a691c813e1", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-82a36d8e86", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-c736c1e695", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-3c1675e579", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-82cacf1f60", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-6b417be46f", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-b3e2812063", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-seitheben-kb-rucksack-98cf022c83", + "key": "lraise" + }, + "ls": { + "name": "Liegestütze", + "steps": [ + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-7495b7e9cc", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.1 + }, + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-231f998085", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.1 + }, + { + "name": "Inkline tiefer / Knie", + "id": "step-inkline-tiefer-knie-631d725178", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.35 + }, + { + "name": "Inkline tiefer / Knie", + "id": "step-inkline-tiefer-knie-2f351c5335", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.35 + }, + { + "name": "Knie / Negativ", + "id": "step-knie-negativ-0cf75923cb", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "Knie / Negativ", + "id": "step-knie-negativ-d225638aef", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "Negativ + erste Volle", + "id": "step-negativ-erste-volle-d68abebd8f", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.85 + }, + { + "name": "Negativ + erste Volle", + "id": "step-negativ-erste-volle-d93d10d94d", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.85 + } + ], + "id": "progression-liegestuetze-8c8a5e07de", + "key": "ls" + }, + "lseng": { + "name": "Inkline (hoch)", + "steps": [ + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-7d971a7291", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-59da475437", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline mittel", + "id": "step-inkline-mittel-821fadec71", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline mittel", + "id": "step-inkline-mittel-199e2aaf70", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline tief", + "id": "step-inkline-tief-6173a85a04", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline tief", + "id": "step-inkline-tief-54d7a4a0ff", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie-Liegestütze", + "id": "step-knie-liegestuetze-7de6b6dcfb", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie-Liegestütze", + "id": "step-knie-liegestuetze-2d4d23db17", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-inkline-hoch-e770797129", + "key": "lseng" + }, + "lunge": { + "name": "Ausfallschritt rückw.", + "steps": [ + { + "name": "Körpergewicht (ggf. festhalten)", + "id": "step-koerpergewicht-ggf-festhalten-3d6301d1bd", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "Körpergewicht (ggf. festhalten)", + "id": "step-koerpergewicht-ggf-festhalten-0d19269674", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-180853b5dd", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-0f7d4a63bd", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mehr Reps", + "id": "step-mehr-reps-d19d6c0eab", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mehr Reps", + "id": "step-mehr-reps-9235507680", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "+ Last leicht", + "id": "step-last-leicht-10d2acd532", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "+ Last leicht", + "id": "step-last-leicht-6ecdd4c919", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-ausfallschritt-rueckw-52ab461616", + "key": "lunge" + }, + "mtn": { + "name": "Mountain Climbers", + "steps": [ + { + "name": "moderat", + "id": "step-moderat-f76617e319", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-a73e3b0549", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-99c93498d6", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-a3b745efcb", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-c304accace", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-23a81c4905", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-25e355b703", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-4d2a2fd39c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-mountain-climbers-97f6359644", + "key": "mtn" + }, + "pike": { + "name": "Pike", + "steps": [ + { + "name": "Pike Hold", + "id": "step-pike-hold-d46aa2e93f", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Pike Hold", + "id": "step-pike-hold-cc334c7fc6", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Hold + kleine Senkung", + "id": "step-hold-kleine-senkung-4f3bfac4b2", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Hold + kleine Senkung", + "id": "step-hold-kleine-senkung-b93d503cb1", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Push-up Hände erhöht", + "id": "step-push-up-haende-erhoeht-7c94ab03f7", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Push-up Hände erhöht", + "id": "step-push-up-haende-erhoeht-5af7a607db", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Push-up Boden, klein", + "id": "step-push-up-boden-klein-0a27dd0a33", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Push-up Boden, klein", + "id": "step-push-up-boden-klein-44741d722a", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-pike-9726e02fef", + "key": "pike" + }, + "plank": { + "name": "Plank", + "steps": [ + { + "name": "auf Knien 15–20 s", + "id": "step-auf-knien-15-20-s-66aa989041", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien 15–20 s", + "id": "step-auf-knien-15-20-s-28c28cffdc", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "voll 20–30 s", + "id": "step-voll-20-30-s-ebbb758203", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll 20–30 s", + "id": "step-voll-20-30-s-465691f146", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll + Schulter-Taps", + "id": "step-voll-schulter-taps-9fbac9d915", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll + Schulter-Taps", + "id": "step-voll-schulter-taps-738cd33982", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-3da43cf026", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-827f28be3a", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-plank-88a9d6bafb", + "key": "plank" + }, + "press": { + "name": "KB Floor Press", + "steps": [ + { + "name": "leicht (Floor Press)", + "id": "step-leicht-floor-press-de744d6e05", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "leicht (Floor Press)", + "id": "step-leicht-floor-press-5987ddc1d2", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Floor Press mehr", + "id": "step-floor-press-mehr-3b454ea446", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Floor Press mehr", + "id": "step-floor-press-mehr-b34710868b", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead leicht", + "id": "step-overhead-leicht-7f9274fd29", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead leicht", + "id": "step-overhead-leicht-3413a54a3a", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead mittel", + "id": "step-overhead-mittel-4b236720a7", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead mittel", + "id": "step-overhead-mittel-ea724f9a42", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-kb-floor-press-0f387c469f", + "key": "press" + }, + "row1": { + "name": "Einarmiges Rudern (KB)", + "steps": [ + { + "name": "leicht", + "id": "step-leicht-db8c75b358", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-d1e4feaafa", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-19ded398d6", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-0ce270eb41", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig leicht", + "id": "step-einarmig-leicht-a6f388b349", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig leicht", + "id": "step-einarmig-leicht-a9539fbadb", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig mittel", + "id": "step-einarmig-mittel-74f0fe3e1e", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig mittel", + "id": "step-einarmig-mittel-3f961280f6", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-einarmiges-rudern-kb-c142975b5b", + "key": "row1" + }, + "row2": { + "name": "Einarm-Rudern BREIT (KB)", + "steps": [ + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-32d83f79d0", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-76c26b9c7f", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-3bbb6ef617", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-1fea9ba00a", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-5f7bc5489c", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-d4c65b81c6", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel, Pause oben", + "id": "step-mittel-pause-oben-d5cdfdfe94", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel, Pause oben", + "id": "step-mittel-pause-oben-8f7a255ad4", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-einarm-rudern-breit-kb-a06708cc5d", + "key": "row2" + }, + "rowe": { + "name": "Einarm-Rudern ENG (KB)", + "steps": [ + { + "name": "leicht", + "id": "step-leicht-7638fa3210", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-d1490519d3", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-4f2775725c", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-35098ff9fa", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-cd74b90448", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-558d6deb5b", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-fec615da4c", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-60a8a2aedd", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-einarm-rudern-eng-kb-b622a3fbfc", + "key": "rowe" + }, + "sp": { + "name": "Side Plank (Wechsel)", + "steps": [ + { + "name": "auf Knien", + "id": "step-auf-knien-3ed08acc27", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien", + "id": "step-auf-knien-000b6c864b", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien, länger", + "id": "step-auf-knien-laenger-d1c3028780", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien, länger", + "id": "step-auf-knien-laenger-227ec9b5f7", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "voll", + "id": "step-voll-6702e0693f", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll", + "id": "step-voll-727fcc161a", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-8c287bb42a", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-ef549f755a", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-side-plank-wechsel-335152ff85", + "key": "sp" + }, + "squat": { + "name": "Körpergewicht", + "steps": [ + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-e4bfd724af", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-cf13a813b5", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht, mehr Reps", + "id": "step-koerpergewicht-mehr-reps-0156c8a851", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht, mehr Reps", + "id": "step-koerpergewicht-mehr-reps-9fccf7b6ed", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet leicht", + "id": "step-goblet-leicht-e52d2635ab", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet leicht", + "id": "step-goblet-leicht-758736b15a", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet mittel", + "id": "step-goblet-mittel-50a2e4ec3e", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet mittel", + "id": "step-goblet-mittel-0c01361ea0", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-koerpergewicht-6b7808a004", + "key": "squat" + }, + "sup": { + "name": "Superman / Y-T-W", + "steps": [ + { + "name": "Superman Hold", + "id": "step-superman-hold-06efa2683a", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Superman Hold", + "id": "step-superman-hold-34af38f543", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Superman Hold länger", + "id": "step-superman-hold-laenger-b383f0e540", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Superman Hold länger", + "id": "step-superman-hold-laenger-6b326b0a11", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Reverse Snow Angels", + "id": "step-reverse-snow-angels-871916bcee", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Reverse Snow Angels", + "id": "step-reverse-snow-angels-78c24f8835", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Y-T-W Raises", + "id": "step-y-t-w-raises-b7c40c3579", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Y-T-W Raises", + "id": "step-y-t-w-raises-a6d76cb15c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-superman-y-t-w-06938b356b", + "key": "sup" + }, + "swing": { + "name": "KB Swing (zweiarmig)", + "steps": [ + { + "name": "Dead-Stop, leicht (Technik)", + "id": "step-dead-stop-leicht-technik-02548f1a05", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Dead-Stop, leicht (Technik)", + "id": "step-dead-stop-leicht-technik-3b9715b104", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Dead-Stop, mehr Reps", + "id": "step-dead-stop-mehr-reps-7be430a756", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Dead-Stop, mehr Reps", + "id": "step-dead-stop-mehr-reps-5c68075818", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, mittel", + "id": "step-durchgehend-mittel-183cc7dfa6", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, mittel", + "id": "step-durchgehend-mittel-7762c8f240", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, schwerer", + "id": "step-durchgehend-schwerer-6da670bed8", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, schwerer", + "id": "step-durchgehend-schwerer-a1609b5bad", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-kb-swing-zweiarmig-b9b86d8de1", + "key": "swing" + }, + "tsquat": { + "name": "3s runter", + "steps": [ + { + "name": "3s runter", + "id": "step-3s-runter-1cafcb49f1", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "3s runter", + "id": "step-3s-runter-c29275cc81", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "3s + Pause unten", + "id": "step-3s-pause-unten-8b4f16c542", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "3s + Pause unten", + "id": "step-3s-pause-unten-d51468b370", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s", + "id": "step-goblet-3s-ebd1293f2a", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s", + "id": "step-goblet-3s-06003d3bdd", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s + Pause", + "id": "step-goblet-3s-pause-a70c738b23", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s + Pause", + "id": "step-goblet-3s-pause-40369412a4", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-3s-runter-b85ef7f990", + "key": "tsquat" + }, + "tw": { + "name": "Russian Twist (KB)", + "steps": [ + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-3bd115d2ec", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-478be9efcd", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-32fc20afc1", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-fbe33d8a04", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB leicht", + "id": "step-kb-leicht-cf37c395cd", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB leicht", + "id": "step-kb-leicht-5cca613a86", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB", + "id": "step-kb-f50c52e3e3", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB", + "id": "step-kb-eb15afb22c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-russian-twist-kb-4ae08fd89a", + "key": "tw" + }, + "wall": { + "name": "Wall Sit", + "steps": [ + { + "name": "15–25 s", + "id": "step-15-25-s-c7aec770d4", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "15–25 s", + "id": "step-15-25-s-dbdcddfcac", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "25–40 s", + "id": "step-25-40-s-7a71cfd7e1", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "25–40 s", + "id": "step-25-40-s-851e536e20", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "40–55 s", + "id": "step-40-55-s-96e1d7939b", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "40–55 s", + "id": "step-40-55-s-ebe34a6bbd", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "55–70 s", + "id": "step-55-70-s-3ddd9ace4a", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "55–70 s", + "id": "step-55-70-s-19c7e1d6d2", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-wall-sit-e5b793fbdf", + "key": "wall" + } + }, + "training_format": { + "mode": "tabata", + "fixed_interval": true, + "work_seconds": 20, + "rest_seconds": 10, + "rounds": 8, + "rounds_scope": "block", + "schema_version": 1 + }, + "schema_version": 3, + "contract_version": 2, + "plan_id": "phase-1", + "exercise_catalog": { + "movement-liegestuetze-57eacf74ee": { + "id": "movement-liegestuetze-57eacf74ee", + "name": "Liegestütze", + "progression_id": "ls", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-inkline-hoch-7495b7e9cc", + "name": "Inkline (hoch)", + "factor": 0.1, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-inkline-hoch-231f998085", + "name": "Inkline (hoch)", + "factor": 0.1, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-inkline-tiefer-knie-631d725178", + "name": "Inkline tiefer / Knie", + "factor": 0.35, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-inkline-tiefer-knie-2f351c5335", + "name": "Inkline tiefer / Knie", + "factor": 0.35, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-negativ-0cf75923cb", + "name": "Knie / Negativ", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-negativ-d225638aef", + "name": "Knie / Negativ", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negativ-erste-volle-d68abebd8f", + "name": "Negativ + erste Volle", + "factor": 0.85, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negativ-erste-volle-d93d10d94d", + "name": "Negativ + erste Volle", + "factor": 0.85, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-bank-dips-couch-ad6c829e1b": { + "id": "movement-bank-dips-couch-ad6c829e1b", + "name": "Bank-Dips (Couch)", + "progression_id": "dips", + "movement_cluster": "vertical_push", + "movement_label": "Dip", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-knie-stark-gebeugt-klein-72a7bffc76", + "name": "Knie stark gebeugt, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-stark-gebeugt-klein-56482dde46", + "name": "Knie stark gebeugt, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-voll-07cc34cd51", + "name": "Knie gebeugt, voll", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-voll-80070f8ecf", + "name": "Knie gebeugt, voll", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-etwas-weiter-42e0983b7f", + "name": "Füße etwas weiter", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-etwas-weiter-7b887f1140", + "name": "Füße etwas weiter", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-weiter-weg-60c057769a", + "name": "Füße weiter weg", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-weiter-weg-2959fb77fb", + "name": "Füße weiter weg", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-pike-17f7716e00": { + "id": "movement-pike-17f7716e00", + "name": "Pike", + "progression_id": "pike", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-pike-hold-d46aa2e93f", + "name": "Pike Hold", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-pike-hold-cc334c7fc6", + "name": "Pike Hold", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-hold-kleine-senkung-4f3bfac4b2", + "name": "Hold + kleine Senkung", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-hold-kleine-senkung-b93d503cb1", + "name": "Hold + kleine Senkung", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-haende-erhoeht-7c94ab03f7", + "name": "Push-up Hände erhöht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-haende-erhoeht-5af7a607db", + "name": "Push-up Hände erhöht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-boden-klein-0a27dd0a33", + "name": "Push-up Boden, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-boden-klein-44741d722a", + "name": "Push-up Boden, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-seitheben-kb-rucksack-91de5dc2f5": { + "id": "movement-seitheben-kb-rucksack-91de5dc2f5", + "name": "Seitheben (KB/Rucksack)", + "progression_id": "lraise", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-technik-a0504bbbb1", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-technik-a691c813e1", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-82a36d8e86", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-c736c1e695", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-3c1675e579", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-82cacf1f60", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-6b417be46f", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-b3e2812063", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-goblet-squat-kb-rucksack-6ff9f90901": { + "id": "movement-goblet-squat-kb-rucksack-6ff9f90901", + "name": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-tiefe-lernen-77201f2b6b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-tiefe-lernen-70ea0f2e9b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-57415830b2", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-abf827246a", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-2d542b9c09", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-eda93ad7ea", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-9dd7fec573", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-3f76b2391b", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-ausfallschritt-rueckw-2a4d06b799": { + "id": "movement-ausfallschritt-rueckw-2a4d06b799", + "name": "Ausfallschritt rückw.", + "progression_id": "lunge", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-koerpergewicht-ggf-festhalten-3d6301d1bd", + "name": "Körpergewicht (ggf. festhalten)", + "factor": 0.55, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-koerpergewicht-ggf-festhalten-0d19269674", + "name": "Körpergewicht (ggf. festhalten)", + "factor": 0.55, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-koerpergewicht-180853b5dd", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-koerpergewicht-0f7d4a63bd", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-reps-d19d6c0eab", + "name": "mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-reps-9235507680", + "name": "mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-last-leicht-10d2acd532", + "name": "+ Last leicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-last-leicht-6ecdd4c919", + "name": "+ Last leicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-glute-bridge-1c0a7bf699": { + "id": "movement-glute-bridge-1c0a7bf699", + "name": "Glute Bridge", + "progression_id": "bridge", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-beidbeinig-36eb2634d6", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-8a52d360c3", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-1ad3dfd97b", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-6087f8f6d1", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-einbeinig-3a0c0abdfa", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-43807e1138", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-4e42109f20", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-6335420028", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + } + ] + }, + "movement-wadenheben-0c7839551b": { + "id": "movement-wadenheben-0c7839551b", + "name": "Wadenheben", + "progression_id": "calf", + "movement_cluster": "calf", + "movement_label": "Waden", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-koerpergewicht-e8311d7913", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-koerpergewicht-95b36efc85", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-9e41650da8", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-74db877975", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-mehr-reps-16ced31e33", + "name": "langsam, mehr Reps", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-mehr-reps-bd0e8e15c4", + "name": "langsam, mehr Reps", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-1-beinig-0c2e9de2f7", + "name": "1-beinig", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-1-beinig-f823fd2149", + "name": "1-beinig", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + } + ] + }, + "movement-einarm-rudern-eng-kb-4907fbb5c7": { + "id": "movement-einarm-rudern-eng-kb-4907fbb5c7", + "name": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-7638fa3210", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-d1490519d3", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-4f2775725c", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-35098ff9fa", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-cd74b90448", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-558d6deb5b", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-fordernd-fec615da4c", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-fordernd-60a8a2aedd", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-bizeps-curls-kb-rucksack-67944f00d5": { + "id": "movement-bizeps-curls-kb-rucksack-67944f00d5", + "name": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "movement_cluster": "elbow_flexion", + "movement_label": "Bizeps", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-273b789e0f", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-93f0046a45", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-bd37c72d4b", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-31ffb19393", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-ca55180c8e", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-d15ecf67a9", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b1f6c26c72", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b575d29101", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-einarm-rudern-breit-kb-0146abcfbb": { + "id": "movement-einarm-rudern-breit-kb-0146abcfbb", + "name": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-technik-32d83f79d0", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-technik-76c26b9c7f", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-3bbb6ef617", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-1fea9ba00a", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-5f7bc5489c", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-d4c65b81c6", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-pause-oben-d5cdfdfe94", + "name": "mittel, Pause oben", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-pause-oben-8f7a255ad4", + "name": "mittel, Pause oben", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-superman-y-t-w-279f03a544": { + "id": "movement-superman-y-t-w-279f03a544", + "name": "Superman / Y-T-W", + "progression_id": "sup", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-superman-hold-06efa2683a", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-34af38f543", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-b383f0e540", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-6b326b0a11", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-871916bcee", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-78c24f8835", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-b7c40c3579", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-a6d76cb15c", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-plank-c8449f2937": { + "id": "movement-plank-c8449f2937", + "name": "Plank", + "progression_id": "plank", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-auf-knien-15-20-s-66aa989041", + "name": "auf Knien 15–20 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-auf-knien-15-20-s-28c28cffdc", + "name": "auf Knien 15–20 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-20-30-s-ebbb758203", + "name": "voll 20–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-20-30-s-465691f146", + "name": "voll 20–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-schulter-taps-9fbac9d915", + "name": "voll + Schulter-Taps", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-schulter-taps-738cd33982", + "name": "voll + Schulter-Taps", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-laenger-3da43cf026", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-laenger-827f28be3a", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-mountain-climbers-79efdb62dd": { + "id": "movement-mountain-climbers-79efdb62dd", + "name": "Mountain Climbers", + "progression_id": "mtn", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-moderat-f76617e319", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-a73e3b0549", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-99c93498d6", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-a3b745efcb", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-c304accace", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-23a81c4905", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-25e355b703", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-4d2a2fd39c", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-dead-bug-21d4b207d5": { + "id": "movement-dead-bug-21d4b207d5", + "name": "Dead Bug", + "progression_id": "db", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-arme-oder-beine-einzeln-7a6a9d581b", + "name": "Arme ODER Beine einzeln", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-arme-oder-beine-einzeln-94f8e1785d", + "name": "Arme ODER Beine einzeln", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-41e02caee7", + "name": "gegengleich", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-31e878a2f9", + "name": "gegengleich", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-pause-9e84e14ecc", + "name": "gegengleich + Pause", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-pause-bf8ebc0b7e", + "name": "gegengleich + Pause", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beine-weiter-gestreckt-453d185cf7", + "name": "Beine weiter gestreckt", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beine-weiter-gestreckt-1c5f566001", + "name": "Beine weiter gestreckt", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-beinheben-faf19a629b": { + "id": "movement-beinheben-faf19a629b", + "name": "Beinheben", + "progression_id": "lr", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-knie-gebeugt-1855b4de0f", + "name": "Knie gebeugt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-863780bc32", + "name": "Knie gebeugt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-langsam-425f61fdb6", + "name": "Knie gebeugt, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-langsam-aad675fda3", + "name": "Knie gebeugt, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-c84cc4988c", + "name": "halb gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-298f78b046", + "name": "halb gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gestreckt-a4f6fc11b8", + "name": "gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gestreckt-d43210faa3", + "name": "gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-rdl-hip-hinge-kb-rucksack-9ada5a67c3": { + "id": "movement-rdl-hip-hinge-kb-rucksack-9ada5a67c3", + "name": "RDL / Hip Hinge (KB/Rucksack)", + "progression_id": "load", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-273b789e0f", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-93f0046a45", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-bd37c72d4b", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-31ffb19393", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-ca55180c8e", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-d15ecf67a9", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b1f6c26c72", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b575d29101", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-glute-bridge-1-beinig-ccc8c9fb53": { + "id": "movement-glute-bridge-1-beinig-ccc8c9fb53", + "name": "Glute Bridge 1-beinig", + "progression_id": "bridge", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-beidbeinig-36eb2634d6", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-8a52d360c3", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-1ad3dfd97b", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-6087f8f6d1", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-einbeinig-3a0c0abdfa", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-43807e1138", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-4e42109f20", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-6335420028", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + } + ] + }, + "movement-wall-sit-6b5f695f6a": { + "id": "movement-wall-sit-6b5f695f6a", + "name": "Wall Sit", + "progression_id": "wall", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-15-25-s-c7aec770d4", + "name": "15–25 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-15-25-s-dbdcddfcac", + "name": "15–25 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-25-40-s-7a71cfd7e1", + "name": "25–40 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-25-40-s-851e536e20", + "name": "25–40 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-40-55-s-96e1d7939b", + "name": "40–55 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-40-55-s-ebe34a6bbd", + "name": "40–55 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-55-70-s-3ddd9ace4a", + "name": "55–70 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-55-70-s-19c7e1d6d2", + "name": "55–70 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-hollow-hold-54c452f77b": { + "id": "movement-hollow-hold-54c452f77b", + "name": "Hollow Hold", + "progression_id": "holl", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-tuck-knie-an-be15e1c436", + "name": "Tuck (Knie an)", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-tuck-knie-an-5f62fcb360", + "name": "Tuck (Knie an)", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-tuck-laenger-b05dd43bd8", + "name": "Tuck, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-tuck-laenger-c70e4ba401", + "name": "Tuck, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-39bccf6e6b", + "name": "halb gestreckt", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-9147433141", + "name": "halb gestreckt", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-voll-13f8ebb8c0", + "name": "halb/voll", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-voll-cde344cba9", + "name": "halb/voll", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-side-plank-wechsel-309b0bc82c": { + "id": "movement-side-plank-wechsel-309b0bc82c", + "name": "Side Plank (Wechsel)", + "progression_id": "sp", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-auf-knien-3ed08acc27", + "name": "auf Knien", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-auf-knien-000b6c864b", + "name": "auf Knien", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-auf-knien-laenger-d1c3028780", + "name": "auf Knien, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-auf-knien-laenger-227ec9b5f7", + "name": "auf Knien, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-6702e0693f", + "name": "voll", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-727fcc161a", + "name": "voll", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-laenger-8c287bb42a", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-laenger-ef549f755a", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-bicycle-crunch-15c87e28c5": { + "id": "movement-bicycle-crunch-15c87e28c5", + "name": "Bicycle Crunch", + "progression_id": "bic", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-langsam-b16ce156f7", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-3bc865247d", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-9b37c0536d", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-6a0ed59bbd", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-1a29fed611", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-60b7d1ac93", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-85e9262405", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-2b4933822c", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-russian-twist-kb-d6e382fb3c": { + "id": "movement-russian-twist-kb-d6e382fb3c", + "name": "Russian Twist (KB)", + "progression_id": "tw", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-ohne-gewicht-3bd115d2ec", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-ohne-gewicht-478be9efcd", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-ohne-gewicht-32fc20afc1", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-ohne-gewicht-fbe33d8a04", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-leicht-cf37c395cd", + "name": "KB leicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-leicht-5cca613a86", + "name": "KB leicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-f50c52e3e3", + "name": "KB", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-eb15afb22c", + "name": "KB", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-kb-floor-press-060586ebd6": { + "id": "movement-kb-floor-press-060586ebd6", + "name": "KB Floor Press", + "progression_id": "press", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-floor-press-de744d6e05", + "name": "leicht (Floor Press)", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-floor-press-5987ddc1d2", + "name": "leicht (Floor Press)", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-floor-press-mehr-3b454ea446", + "name": "Floor Press mehr", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-floor-press-mehr-b34710868b", + "name": "Floor Press mehr", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-leicht-7f9274fd29", + "name": "Overhead leicht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-leicht-3413a54a3a", + "name": "Overhead leicht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-mittel-4b236720a7", + "name": "Overhead mittel", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-mittel-ea724f9a42", + "name": "Overhead mittel", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-y-t-w-superman-1d331ef219": { + "id": "movement-y-t-w-superman-1d331ef219", + "name": "Y-T-W / Superman", + "progression_id": "sup", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-superman-hold-06efa2683a", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-34af38f543", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-b383f0e540", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-6b326b0a11", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-871916bcee", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-78c24f8835", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-b7c40c3579", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-a6d76cb15c", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-kniebeuge-leicht-4ae3fb9fc0": { + "id": "movement-kniebeuge-leicht-4ae3fb9fc0", + "name": "Kniebeuge (leicht)", + "progression_id": "name:kniebeuge-leicht", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-liegestuetze-1-stufe-leichter-6033b9cb92": { + "id": "movement-liegestuetze-1-stufe-leichter-6033b9cb92", + "name": "Liegestütze (1 Stufe leichter)", + "progression_id": "name:liegestuetze-1-stufe-leichter", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-rudern-leicht-566f8de8f2": { + "id": "movement-rudern-leicht-566f8de8f2", + "name": "Rudern (leicht)", + "progression_id": "name:rudern-leicht", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-mountain-climbers-locker-e4a7854ab9": { + "id": "movement-mountain-climbers-locker-e4a7854ab9", + "name": "Mountain Climbers (locker)", + "progression_id": "name:mountain-climbers-locker", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-inchworm-50016ff2a4": { + "id": "movement-inchworm-50016ff2a4", + "name": "Inchworm", + "progression_id": "name:inchworm", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-cat-cow-d8e060c65f": { + "id": "movement-cat-cow-d8e060c65f", + "name": "Cat-Cow", + "progression_id": "name:cat-cow", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-bird-dog-f2c9d49c80": { + "id": "movement-bird-dog-f2c9d49c80", + "name": "Bird Dog", + "progression_id": "name:bird-dog", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-kb-halo-leicht-e3c156d79e": { + "id": "movement-kb-halo-leicht-e3c156d79e", + "name": "KB-Halo (leicht)", + "progression_id": "name:kb-halo-leicht", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [] + }, + "movement-kb-swing-zweiarmig-327d7b5ad6": { + "id": "movement-kb-swing-zweiarmig-327d7b5ad6", + "name": "KB Swing (zweiarmig)", + "progression_id": "swing", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-dead-stop-leicht-technik-02548f1a05", + "name": "Dead-Stop, leicht (Technik)", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-dead-stop-leicht-technik-3b9715b104", + "name": "Dead-Stop, leicht (Technik)", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-dead-stop-mehr-reps-7be430a756", + "name": "Dead-Stop, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-dead-stop-mehr-reps-5c68075818", + "name": "Dead-Stop, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-mittel-183cc7dfa6", + "name": "durchgehend, mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-mittel-7762c8f240", + "name": "durchgehend, mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-schwerer-6da670bed8", + "name": "durchgehend, schwerer", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-schwerer-a1609b5bad", + "name": "durchgehend, schwerer", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-goblet-squat-kb-f065dfd5b6": { + "id": "movement-goblet-squat-kb-f065dfd5b6", + "name": "Goblet Squat (KB)", + "progression_id": "gobletkb", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-tiefe-lernen-77201f2b6b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-tiefe-lernen-70ea0f2e9b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-57415830b2", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-abf827246a", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-2d542b9c09", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-eda93ad7ea", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-9dd7fec573", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-3f76b2391b", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-kb-deadlift-beidbeinig-6f7c8bca76": { + "id": "movement-kb-deadlift-beidbeinig-6f7c8bca76", + "name": "KB-Deadlift (beidbeinig)", + "progression_id": "kbdl", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-technik-54ca22fe3d", + "name": "leicht, Technik", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-technik-fcd2dc3979", + "name": "leicht, Technik", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-db29d97bd7", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-fd7e87b98b", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-7ed7284a10", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-e9887f19f9", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwerer-kontrolliert-dd0f4f4538", + "name": "schwerer, kontrolliert", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwerer-kontrolliert-ab6af6caea", + "name": "schwerer, kontrolliert", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-halo-faca9a5cc9": { + "id": "movement-halo-faca9a5cc9", + "name": "Halo", + "progression_id": "halo", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-langsam-5d15ba8134", + "name": "leicht, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-langsam-c2a5c59e62", + "name": "leicht, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-81a736180a", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-fe775a4a23", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-c5c0bf8989", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-3898a7cea7", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-c9db464d00", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-e1a2e46fd3", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-einarmiges-rudern-kb-0ec1c11b35": { + "id": "movement-einarmiges-rudern-kb-0ec1c11b35", + "name": "Einarmiges Rudern (KB)", + "progression_id": "row1", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-db8c75b358", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-d1e4feaafa", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-19ded398d6", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-0ce270eb41", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-leicht-a6f388b349", + "name": "einarmig leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-leicht-a9539fbadb", + "name": "einarmig leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-mittel-74f0fe3e1e", + "name": "einarmig mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-mittel-3f961280f6", + "name": "einarmig mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-bottoms-up-kb-hold-griff-4a5f657f41": { + "id": "movement-bottoms-up-kb-hold-griff-4a5f657f41", + "name": "Bottoms-up KB-Hold (Griff)", + "progression_id": "carry", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-im-stand-10-15s-e90dd57ff1", + "name": "im Stand 10–15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-im-stand-10-15s-db8eec9df0", + "name": "im Stand 10–15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-15-20s-ruhig-321e582ab2", + "name": "15–20s, ruhig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-15-20s-ruhig-7db6eb1a01", + "name": "15–20s, ruhig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-bis-schulterhoehe-15s-f1763ea9e5", + "name": "bis Schulterhöhe 15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-bis-schulterhoehe-15s-21c8fd4d01", + "name": "bis Schulterhöhe 15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-laenger-ein-paar-schritte-fa5838da65", + "name": "länger / ein paar Schritte", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-laenger-ein-paar-schritte-ec93f04473", + "name": "länger / ein paar Schritte", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-around-the-world-uppercut-kb-7ea726bbde": { + "id": "movement-around-the-world-uppercut-kb-7ea726bbde", + "name": "Around-the-World + Uppercut (KB)", + "progression_id": "atw", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-nur-around-the-world-leicht-97c90b17c5", + "name": "nur Around-the-World, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-nur-around-the-world-leicht-fd2fab13cb", + "name": "nur Around-the-World, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-around-the-world-mehr-reps-7af159bd81", + "name": "Around-the-World, mehr Reps", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-around-the-world-mehr-reps-43f1762f06", + "name": "Around-the-World, mehr Reps", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-leicht-cb2ce4fe29", + "name": "+ Uppercut, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-leicht-db86827e21", + "name": "+ Uppercut, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-fluessig-4b3453d5f9", + "name": "+ Uppercut, flüssig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-fluessig-2463ac3c0e", + "name": "+ Uppercut, flüssig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + } + } + } +} diff --git a/android/app/src/test/resources/samples/proposals/phase-1.json b/android/app/src/test/resources/samples/proposals/phase-1.json new file mode 100644 index 0000000..3991c50 --- /dev/null +++ b/android/app/src/test/resources/samples/proposals/phase-1.json @@ -0,0 +1,43 @@ +{ + "version": 2, + "source_file": "phase-1.json", + "plan_id": "phase-1", + "published_revision": 1, + "updated_at": "2026-07-23T12:01:33+00:00", + "proposals": [ + { + "id": "overall:overall-1", + "status": "open", + "scope_key": "overall:overall", + "analysis_type": "overall", + "week": null, + "target": "Woche 2 / Pike", + "target_label": "Pike: nächste Stufe prüfen", + "target_exercise_id": "movement-pike-17f7716e00", + "target_progression_id": "pike", + "target_step_id": "step-hold-kleine-senkung-4f3bfac4b2", + "suggested_change": "Progressionsstufe von „Pike Hold“ auf „Hold + kleine Senkung“ setzen.", + "reason": "Zwei Einheiten mit 6x20 s Pike Hold sprechen für einen vorsichtigen Variantenwechsel.", + "condition": "Nur wenn beide Pike-Einheiten technisch sauber waren; sonst Pike Hold beibehalten.", + "manual_step": "Im Trainingsplan-Modul bei Pike die nächste Progressionsstufe auswählen.", + "analysis_created_at": "2026-07-23T12:01:33+00:00" + }, + { + "id": "overall:overall-2", + "status": "open", + "scope_key": "overall:overall", + "analysis_type": "overall", + "week": null, + "target": "Woche 2 / Einarm-Rudern ENG", + "target_label": "Einarm-Rudern ENG: Last oder Stufe prüfen", + "target_exercise_id": "movement-einarm-rudern-eng-kb-4907fbb5c7", + "target_progression_id": "rowe", + "target_step_id": "step-etwas-mehr-4f2775725c", + "suggested_change": "Von „leicht“ auf „etwas mehr“ wechseln.", + "reason": "Die Wiederholungen lagen in beiden Einheiten stabil bei etwa 8–9 pro Intervall.", + "condition": "Nur wenn Rückenposition und Zugweg sauber bleiben; sonst aktuelle Stufe beibehalten.", + "manual_step": "Im Trainingsplan-Modul die nächste Rudern-ENG-Progression oder passende Last eintragen.", + "analysis_created_at": "2026-07-23T12:01:33+00:00" + } + ] +} diff --git a/android/app/src/test/resources/samples/sessions/calihoss-newbie-8-wochen.json b/android/app/src/test/resources/samples/sessions/calihoss-newbie-8-wochen.json new file mode 100644 index 0000000..5c04350 --- /dev/null +++ b/android/app/src/test/resources/samples/sessions/calihoss-newbie-8-wochen.json @@ -0,0 +1,612 @@ +{ + "created_at": "2026-07-23T09:14:02+00:00", + "profile": { + "display_name": "", + "plan_notes": "", + "start_date": "" + }, + "progressions": { + "ls": { + "note": "", + "value": "" + }, + "name:1-2-reps-reserve-pruefen": { + "note": "", + "value": "" + }, + "name:beine-protokollieren": { + "note": "", + "value": "" + }, + "name:brustwirbelsaeule-schultern": { + "note": "", + "value": "" + }, + "name:bulletproof-shoulders": { + "note": "", + "value": "" + }, + "name:core-protokollieren": { + "note": "", + "value": "" + }, + "name:finger-plank": { + "note": "", + "value": "" + }, + "name:full-body-mobility": { + "note": "", + "value": "" + }, + "name:hueft-mobilitaet": { + "note": "", + "value": "" + }, + "name:lockerer-spaziergang": { + "note": "", + "value": "" + }, + "name:negative-pruefen": { + "note": "", + "value": "" + }, + "name:prone-y-t-w-technik": { + "note": "", + "value": "" + }, + "name:pull-protokollieren": { + "note": "", + "value": "" + }, + "name:push-protokollieren": { + "note": "", + "value": "" + }, + "name:regeneration-pruefen": { + "note": "", + "value": "" + }, + "name:schlaf-priorisieren": { + "note": "", + "value": "" + }, + "name:schmerzcheck": { + "note": "", + "value": "" + }, + "name:schwierigkeit-erhoehen": { + "note": "", + "value": "" + }, + "name:sonst-vollstaendig-pausieren": { + "note": "", + "value": "" + }, + "name:spaziergang": { + "note": "", + "value": "" + }, + "name:sprunggelenk-mobilitaet": { + "note": "", + "value": "" + }, + "name:startposition-pruefen": { + "note": "", + "value": "" + }, + "name:volle-reps-pruefen": { + "note": "", + "value": "" + }, + "name:wrist-conditioning": { + "note": "", + "value": "" + }, + "plank": { + "note": "", + "value": "" + }, + "rowe": { + "note": "", + "value": "" + }, + "squat": { + "note": "", + "value": "" + } + }, + "sessions": { + "w01-d01": { + "completed_at": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + }, + "warmup-4": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned" + }, + "w02-d01": { + "completed_at": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + }, + "warmup-4": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned" + }, + "w03-d01": { + "completed_at": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + }, + "warmup-4": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned" + }, + "w08-d01": { + "completed_at": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + }, + "warmup-4": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned" + }, + "w01-d02": { + "status": "planned", + "items": { + "warmup-0": { + "done": false, + "result": "", + "note": "" + }, + "warmup-1": { + "done": false, + "result": "", + "note": "" + }, + "d2-r0-e0": { + "done": false, + "result": "", + "note": "" + }, + "d2-r0-e1": { + "done": false, + "result": "", + "note": "" + }, + "d2-r0-e2": { + "done": false, + "result": "", + "note": "" + }, + "d2-r0-e3": { + "done": false, + "result": "", + "note": "" + }, + "d2-r1-e0": { + "done": false, + "result": "", + "note": "" + }, + "d2-r1-e1": { + "done": false, + "result": "", + "note": "" + }, + "d2-r1-e2": { + "done": false, + "result": "", + "note": "" + }, + "d2-r1-e3": { + "done": false, + "result": "", + "note": "" + }, + "cooldown-0": { + "done": false, + "result": "", + "note": "" + }, + "cooldown-1": { + "done": false, + "result": "", + "note": "" + }, + "stretch-0": { + "done": false, + "result": "", + "note": "" + } + }, + "note": "", + "started_at": "", + "completed_at": "" + }, + "w01-d03": { + "status": "planned", + "items": { + "warmup-0": { + "done": false, + "result": "", + "note": "" + }, + "warmup-1": { + "done": false, + "result": "", + "note": "" + }, + "warmup-2": { + "done": false, + "result": "", + "note": "" + }, + "warmup-3": { + "done": false, + "result": "", + "note": "" + }, + "warmup-4": { + "done": false, + "result": "", + "note": "" + }, + "d3-r0-e0": { + "done": false, + "result": "", + "note": "" + }, + "d3-r0-e1": { + "done": false, + "result": "", + "note": "" + }, + "d3-r0-e2": { + "done": false, + "result": "", + "note": "" + }, + "d3-r0-e3": { + "done": false, + "result": "", + "note": "" + }, + "d3-r1-e0": { + "done": false, + "result": "", + "note": "" + }, + "d3-r1-e1": { + "done": false, + "result": "", + "note": "" + }, + "d3-r1-e2": { + "done": false, + "result": "", + "note": "" + }, + "d3-r1-e3": { + "done": false, + "result": "", + "note": "" + }, + "cooldown-0": { + "done": false, + "result": "", + "note": "" + }, + "stretch-0": { + "done": false, + "result": "", + "note": "" + } + }, + "note": "", + "started_at": "", + "completed_at": "" + } + }, + "source_file": "calihoss-newbie-8-wochen.json", + "source_hash": "6d9ab7972384bb711693ced004c6a7053fbe858323e98dfea7e7a6b0c56c83b1", + "updated_at": "2026-07-23T09:42:21+00:00", + "version": 1 +} diff --git a/android/app/src/test/resources/samples/sessions/phase-1.json b/android/app/src/test/resources/samples/sessions/phase-1.json new file mode 100644 index 0000000..28eb549 --- /dev/null +++ b/android/app/src/test/resources/samples/sessions/phase-1.json @@ -0,0 +1,1393 @@ +{ + "created_at": "2026-07-23T09:43:25+00:00", + "profile": { + "display_name": "", + "plan_notes": "", + "start_date": "" + }, + "progressions": { + "atw": { + "note": "", + "value": "" + }, + "bic": { + "note": "", + "value": "" + }, + "bridge": { + "note": "", + "value": "" + }, + "calf": { + "note": "", + "value": "" + }, + "carry": { + "note": "", + "value": "" + }, + "db": { + "note": "", + "value": "" + }, + "dips": { + "note": "", + "value": "" + }, + "gobletkb": { + "note": "", + "value": "" + }, + "halo": { + "note": "", + "value": "" + }, + "holl": { + "note": "", + "value": "" + }, + "kbdl": { + "note": "", + "value": "" + }, + "load": { + "note": "", + "value": "" + }, + "lr": { + "note": "", + "value": "" + }, + "lraise": { + "note": "", + "value": "" + }, + "ls": { + "note": "", + "value": "" + }, + "lunge": { + "note": "", + "value": "" + }, + "mtn": { + "note": "", + "value": "" + }, + "name:bird-dog": { + "note": "", + "value": "" + }, + "name:cat-cow": { + "note": "", + "value": "" + }, + "name:inchworm": { + "note": "", + "value": "" + }, + "name:kb-halo-leicht": { + "note": "", + "value": "" + }, + "name:kniebeuge-leicht": { + "note": "", + "value": "" + }, + "name:liegestuetze-1-stufe-leichter": { + "note": "", + "value": "" + }, + "name:mountain-climbers-locker": { + "note": "", + "value": "" + }, + "name:rudern-leicht": { + "note": "", + "value": "" + }, + "pike": { + "note": "", + "value": "" + }, + "plank": { + "note": "", + "value": "" + }, + "press": { + "note": "", + "value": "" + }, + "row1": { + "note": "", + "value": "" + }, + "row2": { + "note": "", + "value": "" + }, + "rowe": { + "note": "", + "value": "" + }, + "sp": { + "note": "", + "value": "" + }, + "sup": { + "note": "", + "value": "" + }, + "swing": { + "note": "", + "value": "" + }, + "tw": { + "note": "", + "value": "" + }, + "wall": { + "note": "", + "value": "" + } + }, + "sessions": { + "w01-d01": { + "completed_at": "2026-07-23T10:01:58.783Z", + "items": { + "cooldown-0": { + "done": true, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": true, + "note": "", + "result": "" + }, + "d1-r0-e0": { + "done": true, + "exercise_name": "Liegestütze", + "inherited_from": "", + "note": "", + "progression": "Inkline (hoch)", + "progression_id": "ls", + "result": "8/7/7/7/6/6/5" + }, + "d1-r0-e1": { + "done": true, + "exercise_name": "Bank-Dips (Couch)", + "inherited_from": "", + "note": "", + "progression": "Knie stark gebeugt, klein", + "progression_id": "dips", + "result": "8/7/7/7/6/6" + }, + "d1-r0-e2": { + "done": true, + "exercise_name": "Pike", + "inherited_from": "", + "note": "", + "progression": "Pike Hold", + "progression_id": "pike", + "result": "6x20 s" + }, + "d1-r0-e3": { + "done": true, + "exercise_name": "Seitheben (KB/Rucksack)", + "inherited_from": "", + "note": "", + "progression": "leicht (Technik)", + "progression_id": "lraise", + "result": "4kg, 6/6/5 je Seite" + }, + "d1-r1-e0": { + "done": true, + "exercise_name": "Goblet Squat (KB/Rucksack)", + "inherited_from": "", + "note": "", + "progression": "leicht, Tiefe lernen", + "progression_id": "gobletkb", + "result": "0kg, 8/8/7/7/6/7" + }, + "d1-r1-e1": { + "done": true, + "exercise_name": "Ausfallschritt rückw.", + "inherited_from": "", + "note": "", + "progression": "Körpergewicht (ggf. festhalten)", + "progression_id": "lunge", + "result": "3x4 je Seite" + }, + "d1-r1-e2": { + "done": true, + "exercise_name": "Glute Bridge", + "inherited_from": "", + "note": "", + "progression": "beidbeinig", + "progression_id": "bridge", + "result": "6x 20s" + }, + "d1-r1-e3": { + "done": true, + "exercise_name": "Wadenheben", + "inherited_from": "", + "note": "", + "progression": "Körpergewicht", + "progression_id": "calf", + "result": "10kg, 10/10/10/10/10/10" + }, + "stretch-0": { + "done": true, + "note": "", + "result": "" + }, + "stretch-1": { + "done": true, + "note": "", + "result": "" + }, + "stretch-2": { + "done": true, + "note": "", + "result": "" + }, + "stretch-3": { + "done": true, + "note": "", + "result": "" + }, + "stretch-4": { + "done": true, + "note": "", + "result": "" + }, + "stretch-5": { + "done": true, + "note": "", + "result": "" + }, + "warmup-0": { + "done": true, + "note": "", + "result": "" + }, + "warmup-1": { + "done": true, + "note": "", + "result": "" + }, + "warmup-2": { + "done": true, + "note": "", + "result": "" + }, + "warmup-3": { + "done": true, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "2026-07-23T10:01:58.783Z", + "status": "completed" + }, + "w01-d02": { + "completed_at": "2026-07-23T10:03:32.468Z", + "items": { + "cooldown-0": { + "done": true, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": true, + "note": "", + "result": "" + }, + "d2-r0-e0": { + "done": true, + "exercise_name": "Einarm-Rudern ENG (KB)", + "inherited_from": "", + "note": "", + "progression": "leicht", + "progression_id": "rowe", + "result": "4kg, 9/9/8/8/8/9" + }, + "d2-r0-e1": { + "done": true, + "exercise_name": "Bizeps-Curls (KB/Rucksack)", + "inherited_from": "", + "note": "", + "progression": "leicht", + "progression_id": "load", + "result": "4kg, 8/8/8/8/8/7" + }, + "d2-r0-e2": { + "done": true, + "exercise_name": "Einarm-Rudern BREIT (KB)", + "inherited_from": "", + "note": "", + "progression": "leicht (Technik)", + "progression_id": "row2", + "result": "4kg, 7/7/6/7/6/8" + }, + "d2-r0-e3": { + "done": true, + "exercise_name": "Superman / Y-T-W", + "inherited_from": "", + "note": "", + "progression": "Superman Hold", + "progression_id": "sup", + "result": "6x20 s" + }, + "d2-r1-e0": { + "done": true, + "exercise_name": "Plank", + "inherited_from": "", + "note": "", + "progression": "auf Knien 15–20 s", + "progression_id": "plank", + "result": "6x20 s" + }, + "d2-r1-e1": { + "done": true, + "exercise_name": "Mountain Climbers", + "inherited_from": "", + "note": "", + "progression": "moderat", + "progression_id": "mtn", + "result": "6x20 s" + }, + "d2-r1-e2": { + "done": true, + "exercise_name": "Dead Bug", + "inherited_from": "", + "note": "", + "progression": "Arme ODER Beine einzeln", + "progression_id": "db", + "result": "6x20 s" + }, + "d2-r1-e3": { + "done": true, + "exercise_name": "Beinheben", + "inherited_from": "", + "note": "", + "progression": "Knie gebeugt", + "progression_id": "lr", + "result": "5/5/4/4/4/3" + }, + "stretch-0": { + "done": true, + "note": "", + "result": "" + }, + "stretch-1": { + "done": true, + "note": "", + "result": "" + }, + "stretch-2": { + "done": true, + "note": "", + "result": "" + }, + "stretch-3": { + "done": true, + "note": "", + "result": "" + }, + "stretch-4": { + "done": true, + "note": "", + "result": "" + }, + "stretch-5": { + "done": true, + "note": "", + "result": "" + }, + "warmup-0": { + "done": true, + "note": "", + "result": "" + }, + "warmup-1": { + "done": true, + "note": "", + "result": "" + }, + "warmup-2": { + "done": true, + "note": "", + "result": "" + }, + "warmup-3": { + "done": true, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "2026-07-23T10:03:32.468Z", + "status": "completed" + }, + "w01-d03": { + "completed_at": "2026-07-23T10:06:49.678Z", + "items": { + "cooldown-0": { + "done": true, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": true, + "note": "", + "result": "" + }, + "d3-r0-e0": { + "done": true, + "exercise_name": "Goblet Squat (KB/Rucksack)", + "inherited_from": "w01-d01", + "note": "", + "progression": "leicht, Tiefe lernen", + "progression_id": "gobletkb", + "result": "6kg, 8/8/7/7/6/7" + }, + "d3-r0-e1": { + "done": true, + "exercise_name": "RDL / Hip Hinge (KB/Rucksack)", + "inherited_from": "w01-d02", + "note": "", + "progression": "leicht", + "progression_id": "load", + "result": "8kg, 7/7/7/7/7/7" + }, + "d3-r0-e2": { + "done": true, + "exercise_name": "Glute Bridge 1-beinig", + "inherited_from": "w01-d01", + "note": "", + "progression": "beidbeinig", + "progression_id": "bridge", + "result": "6x20 s" + }, + "d3-r0-e3": { + "done": true, + "exercise_name": "Wall Sit", + "inherited_from": "", + "note": "", + "progression": "15–25 s", + "progression_id": "wall", + "result": "6x20 s" + }, + "d3-r1-e0": { + "done": true, + "exercise_name": "Hollow Hold", + "inherited_from": "", + "note": "", + "progression": "Tuck (Knie an)", + "progression_id": "holl", + "result": "6x20 s" + }, + "d3-r1-e1": { + "done": true, + "exercise_name": "Side Plank (Wechsel)", + "inherited_from": "", + "note": "", + "progression": "auf Knien", + "progression_id": "sp", + "result": "6x20s" + }, + "d3-r1-e2": { + "done": true, + "exercise_name": "Bicycle Crunch", + "inherited_from": "", + "note": "", + "progression": "langsam", + "progression_id": "bic", + "result": "6x20s" + }, + "d3-r1-e3": { + "done": true, + "exercise_name": "Russian Twist (KB)", + "inherited_from": "", + "note": "", + "progression": "ohne Gewicht", + "progression_id": "tw", + "result": "4kg, 4/4/4 je Seite" + }, + "stretch-0": { + "done": true, + "note": "", + "result": "" + }, + "stretch-1": { + "done": true, + "note": "", + "result": "" + }, + "stretch-2": { + "done": true, + "note": "", + "result": "" + }, + "stretch-3": { + "done": true, + "note": "", + "result": "" + }, + "stretch-4": { + "done": true, + "note": "", + "result": "" + }, + "stretch-5": { + "done": true, + "note": "", + "result": "" + }, + "warmup-0": { + "done": true, + "note": "", + "result": "" + }, + "warmup-1": { + "done": true, + "note": "", + "result": "" + }, + "warmup-2": { + "done": true, + "note": "", + "result": "" + }, + "warmup-3": { + "done": true, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "2026-07-23T10:06:49.678Z", + "status": "completed" + }, + "w01-d04": { + "completed_at": "2026-07-23T10:11:52.028Z", + "items": { + "cooldown-0": { + "done": true, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": true, + "note": "", + "result": "" + }, + "d4-r0-e0": { + "done": true, + "exercise_name": "Liegestütze", + "inherited_from": "w01-d01", + "note": "", + "progression": "Inkline (hoch)", + "progression_id": "ls", + "result": "8/7/7/6/6/6" + }, + "d4-r0-e1": { + "done": true, + "exercise_name": "Bank-Dips (Couch)", + "inherited_from": "w01-d01", + "note": "", + "progression": "Knie stark gebeugt, klein", + "progression_id": "dips", + "result": "8/7/7/7/6/6" + }, + "d4-r0-e2": { + "done": true, + "exercise_name": "Pike", + "inherited_from": "w01-d01", + "note": "", + "progression": "Pike Hold", + "progression_id": "pike", + "result": "6x20 s" + }, + "d4-r0-e3": { + "done": true, + "exercise_name": "KB Floor Press", + "inherited_from": "", + "note": "", + "progression": "leicht (Floor Press)", + "progression_id": "press", + "result": "10kg, 8/7/7/6/6/7" + }, + "d4-r1-e0": { + "done": true, + "exercise_name": "Einarm-Rudern ENG (KB)", + "inherited_from": "w01-d02", + "note": "", + "progression": "leicht", + "progression_id": "rowe", + "result": "4kg, 9/9/9/8/8/9" + }, + "d4-r1-e1": { + "done": true, + "exercise_name": "Bizeps-Curls (KB/Rucksack)", + "inherited_from": "w01-d03", + "note": "", + "progression": "leicht", + "progression_id": "load", + "result": "8kg, 7/7/7/7/7/8" + }, + "d4-r1-e2": { + "done": true, + "exercise_name": "Einarm-Rudern BREIT (KB)", + "inherited_from": "w01-d02", + "note": "", + "progression": "leicht (Technik)", + "progression_id": "row2", + "result": "4kg, 7/7/6/7/7/8" + }, + "d4-r1-e3": { + "done": true, + "exercise_name": "Y-T-W / Superman", + "inherited_from": "w01-d02", + "note": "", + "progression": "Superman Hold", + "progression_id": "sup", + "result": "6x20 s" + }, + "stretch-0": { + "done": true, + "note": "", + "result": "" + }, + "stretch-1": { + "done": true, + "note": "", + "result": "" + }, + "stretch-2": { + "done": true, + "note": "", + "result": "" + }, + "stretch-3": { + "done": true, + "note": "", + "result": "" + }, + "stretch-4": { + "done": true, + "note": "", + "result": "" + }, + "warmup-0": { + "done": true, + "note": "", + "result": "" + }, + "warmup-1": { + "done": true, + "note": "", + "result": "" + }, + "warmup-2": { + "done": true, + "note": "", + "result": "" + }, + "warmup-3": { + "done": true, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "2026-07-23T10:11:52.028Z", + "status": "completed" + }, + "w01-d05": { + "status": "planned", + "items": { + "warmup-0": { + "done": false, + "result": "", + "note": "" + }, + "warmup-1": { + "done": false, + "result": "", + "note": "" + }, + "warmup-2": { + "done": false, + "result": "", + "note": "" + }, + "warmup-3": { + "done": false, + "result": "", + "note": "" + }, + "d5-r0-e0": { + "done": false, + "result": "", + "note": "" + }, + "d5-r0-e1": { + "done": false, + "result": "", + "note": "" + }, + "d5-r0-e2": { + "done": false, + "result": "", + "note": "" + }, + "d5-r0-e3": { + "done": false, + "result": "", + "note": "" + }, + "d5-r1-e0": { + "done": false, + "result": "", + "note": "" + }, + "d5-r1-e1": { + "done": false, + "result": "", + "note": "" + }, + "d5-r1-e2": { + "done": false, + "result": "", + "note": "" + }, + "d5-r1-e3": { + "done": false, + "result": "", + "note": "" + }, + "cooldown-0": { + "done": false, + "result": "", + "note": "" + }, + "cooldown-1": { + "done": false, + "result": "", + "note": "" + }, + "stretch-0": { + "done": false, + "result": "", + "note": "" + }, + "stretch-1": { + "done": false, + "result": "", + "note": "" + }, + "stretch-2": { + "done": false, + "result": "", + "note": "" + }, + "stretch-3": { + "done": false, + "result": "", + "note": "" + }, + "stretch-4": { + "done": false, + "result": "", + "note": "" + } + }, + "note": "", + "warmup_note": "", + "cooldown_note": "", + "stretch_note": "", + "started_at": "", + "stopped_at": "", + "completed_at": "" + }, + "w01-d06": { + "completed_at": "2026-07-23T10:14:51.035Z", + "items": { + "cooldown-0": { + "done": true, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": true, + "note": "", + "result": "" + }, + "d6-r0-e0": { + "done": true, + "exercise_name": "KB Swing (zweiarmig)", + "inherited_from": "", + "note": "", + "progression": "Dead-Stop, leicht (Technik)", + "progression_id": "swing", + "result": "10kg, 15 reps" + }, + "d6-r0-e1": { + "done": true, + "exercise_name": "Goblet Squat (KB)", + "inherited_from": "w01-d03", + "note": "", + "progression": "leicht, Tiefe lernen", + "progression_id": "gobletkb", + "result": "8kg, 6/6/7/8/6/6" + }, + "d6-r0-e2": { + "done": true, + "exercise_name": "KB-Deadlift (beidbeinig)", + "inherited_from": "", + "note": "", + "progression": "leicht, Technik", + "progression_id": "kbdl", + "result": "10kg, 5/5/6/6/6/5" + }, + "d6-r0-e3": { + "done": true, + "exercise_name": "Halo", + "inherited_from": "", + "note": "", + "progression": "leicht, langsam", + "progression_id": "halo", + "result": "4kg, 5/4/4 je Seite" + }, + "d6-r1-e0": { + "done": true, + "exercise_name": "KB Floor Press", + "inherited_from": "w01-d04", + "note": "", + "progression": "leicht (Floor Press)", + "progression_id": "press", + "result": "10kg, 7/7/8/6/5/6" + }, + "d6-r1-e1": { + "done": true, + "exercise_name": "Einarmiges Rudern (KB)", + "inherited_from": "", + "note": "", + "progression": "leicht", + "progression_id": "row1", + "result": "6kg, 6/7/8 je Seite" + }, + "d6-r1-e2": { + "done": true, + "exercise_name": "Bottoms-up KB-Hold (Griff)", + "inherited_from": "", + "note": "", + "progression": "im Stand 10–15s", + "progression_id": "carry", + "result": "4kg, 3x20 s je Seite" + }, + "d6-r1-e3": { + "done": true, + "exercise_name": "Around-the-World + Uppercut (KB)", + "inherited_from": "", + "note": "", + "progression": "nur Around-the-World, leicht", + "progression_id": "atw", + "result": "4kg, 5/5/5 je Seite" + }, + "stretch-0": { + "done": true, + "note": "", + "result": "" + }, + "stretch-1": { + "done": true, + "note": "", + "result": "" + }, + "stretch-2": { + "done": true, + "note": "", + "result": "" + }, + "stretch-3": { + "done": true, + "note": "", + "result": "" + }, + "stretch-4": { + "done": true, + "note": "", + "result": "" + }, + "stretch-5": { + "done": true, + "note": "", + "result": "" + }, + "warmup-0": { + "done": true, + "note": "", + "result": "" + }, + "warmup-1": { + "done": true, + "note": "", + "result": "" + }, + "warmup-2": { + "done": true, + "note": "", + "result": "" + }, + "warmup-3": { + "done": true, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "2026-07-23T10:14:51.035Z", + "status": "completed" + }, + "w02-d01": { + "completed_at": "", + "cooldown_note": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "stretch-1": { + "done": false, + "note": "", + "result": "" + }, + "stretch-2": { + "done": false, + "note": "", + "result": "" + }, + "stretch-3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-4": { + "done": false, + "note": "", + "result": "" + }, + "stretch-5": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned", + "stopped_at": "", + "stretch_note": "", + "warmup_note": "" + }, + "w02-d02": { + "completed_at": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": false, + "note": "", + "result": "" + }, + "d2-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d2-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d2-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d2-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d2-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d2-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d2-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d2-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "stretch-1": { + "done": false, + "note": "", + "result": "" + }, + "stretch-2": { + "done": false, + "note": "", + "result": "" + }, + "stretch-3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-4": { + "done": false, + "note": "", + "result": "" + }, + "stretch-5": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned" + }, + "w02-d03": { + "completed_at": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": false, + "note": "", + "result": "" + }, + "d3-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d3-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d3-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d3-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d3-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d3-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d3-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d3-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "stretch-1": { + "done": false, + "note": "", + "result": "" + }, + "stretch-2": { + "done": false, + "note": "", + "result": "" + }, + "stretch-3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-4": { + "done": false, + "note": "", + "result": "" + }, + "stretch-5": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned" + }, + "w02-d04": { + "completed_at": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": false, + "note": "", + "result": "" + }, + "d4-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d4-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d4-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d4-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d4-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d4-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d4-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d4-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "stretch-1": { + "done": false, + "note": "", + "result": "" + }, + "stretch-2": { + "done": false, + "note": "", + "result": "" + }, + "stretch-3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-4": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned" + } + }, + "source_file": "phase-1.json", + "source_hash": "73ab9a0246f001da7a4b6cbd27c21078562e1dbf3ea4d638d298bd53af53264d", + "updated_at": "2026-07-23T21:28:19+00:00", + "version": 5 +} diff --git a/android/build-logs/backend-plugin-tests.log b/android/build-logs/backend-plugin-tests.log new file mode 100644 index 0000000..6b8956d --- /dev/null +++ b/android/build-logs/backend-plugin-tests.log @@ -0,0 +1,2 @@ +....ss.s.ssss.s.... [100%] +11 passed, 8 skipped in 0.05s diff --git a/android/build-logs/build-environment.log b/android/build-logs/build-environment.log new file mode 100644 index 0000000..8264ff6 --- /dev/null +++ b/android/build-logs/build-environment.log @@ -0,0 +1,16 @@ +Date: 2026-07-24T11:29:54+00:00 +OS: Linux 922d8f789c54 6.12.13 #1 SMP Wed Jul 22 00:33:13 UTC 2026 x86_64 GNU/Linux +Java: +openjdk version "21.0.10" 2026-01-20 +OpenJDK Runtime Environment (build 21.0.10+7-Debian-1deb13u1) +OpenJDK 64-Bit Server VM (build 21.0.10+7-Debian-1deb13u1, mixed mode, sharing) +Kotlinc: +info: kotlinc-jvm 1.9.0 (JRE 21.0.10+7-Debian-1deb13u1) +ANDROID_HOME= +ANDROID_SDK_ROOT= +gradle= +sdkmanager= +adb= +DNS services.gradle.org: + +Gradle cache: diff --git a/android/build-logs/gradle-assembleDebug.log b/android/build-logs/gradle-assembleDebug.log new file mode 100644 index 0000000..d92eb1e --- /dev/null +++ b/android/build-logs/gradle-assembleDebug.log @@ -0,0 +1,22 @@ +===== ./gradlew assembleDebug ===== +Exception in thread "main" java.net.UnknownHostException: services.gradle.org + at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:567) + at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327) + at java.base/java.net.Socket.connect(Socket.java:751) + at java.base/sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:304) + at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178) + at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:531) + at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:636) + at java.base/sun.net.www.protocol.https.HttpsClient.(HttpsClient.java:264) + at java.base/sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:377) + at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(AbstractDelegateHttpsURLConnection.java:193) + at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1257) + at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1143) + at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:179) + at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1705) + at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1629) + at java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:531) + at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:307) + at org.gradle.wrapper.GradleWrapperMain.download(GradleWrapperMain.java:108) + at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:55) +EXIT_CODE=1 diff --git a/android/build-logs/gradle-lintDebug.log b/android/build-logs/gradle-lintDebug.log new file mode 100644 index 0000000..1a9c3d4 --- /dev/null +++ b/android/build-logs/gradle-lintDebug.log @@ -0,0 +1,22 @@ +===== ./gradlew lintDebug ===== +Exception in thread "main" java.net.UnknownHostException: services.gradle.org + at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:567) + at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327) + at java.base/java.net.Socket.connect(Socket.java:751) + at java.base/sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:304) + at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178) + at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:531) + at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:636) + at java.base/sun.net.www.protocol.https.HttpsClient.(HttpsClient.java:264) + at java.base/sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:377) + at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(AbstractDelegateHttpsURLConnection.java:193) + at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1257) + at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1143) + at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:179) + at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1705) + at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1629) + at java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:531) + at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:307) + at org.gradle.wrapper.GradleWrapperMain.download(GradleWrapperMain.java:108) + at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:55) +EXIT_CODE=1 diff --git a/android/build-logs/gradle-test.log b/android/build-logs/gradle-test.log new file mode 100644 index 0000000..a318970 --- /dev/null +++ b/android/build-logs/gradle-test.log @@ -0,0 +1,22 @@ +===== ./gradlew test ===== +Exception in thread "main" java.net.UnknownHostException: services.gradle.org + at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:567) + at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327) + at java.base/java.net.Socket.connect(Socket.java:751) + at java.base/sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:304) + at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178) + at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:531) + at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:636) + at java.base/sun.net.www.protocol.https.HttpsClient.(HttpsClient.java:264) + at java.base/sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:377) + at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(AbstractDelegateHttpsURLConnection.java:193) + at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1257) + at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1143) + at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:179) + at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1705) + at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1629) + at java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:531) + at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:307) + at org.gradle.wrapper.GradleWrapperMain.download(GradleWrapperMain.java:108) + at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:55) +EXIT_CODE=1 diff --git a/android/build-logs/gradle-version.log b/android/build-logs/gradle-version.log new file mode 100644 index 0000000..fec4d89 --- /dev/null +++ b/android/build-logs/gradle-version.log @@ -0,0 +1,20 @@ +Exception in thread "main" java.net.UnknownHostException: services.gradle.org + at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:567) + at java.base/java.net.SocksSocketImpl.connect(SocksSocketImpl.java:327) + at java.base/java.net.Socket.connect(Socket.java:751) + at java.base/sun.security.ssl.SSLSocketImpl.connect(SSLSocketImpl.java:304) + at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:178) + at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:531) + at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:636) + at java.base/sun.net.www.protocol.https.HttpsClient.(HttpsClient.java:264) + at java.base/sun.net.www.protocol.https.HttpsClient.New(HttpsClient.java:377) + at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.getNewHttpClient(AbstractDelegateHttpsURLConnection.java:193) + at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1257) + at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1143) + at java.base/sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:179) + at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1705) + at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1629) + at java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:531) + at java.base/sun.net.www.protocol.https.HttpsURLConnectionImpl.getResponseCode(HttpsURLConnectionImpl.java:307) + at org.gradle.wrapper.GradleWrapperMain.download(GradleWrapperMain.java:77) + at org.gradle.wrapper.GradleWrapperMain.main(GradleWrapperMain.java:32) diff --git a/android/build-logs/kotlinc-static.log b/android/build-logs/kotlinc-static.log new file mode 100644 index 0000000..89d0395 --- /dev/null +++ b/android/build-logs/kotlinc-static.log @@ -0,0 +1,8017 @@ +app/src/androidTest/java/de/drazz/boehmitools/training/HiltTestRunner.kt:3:8: error: unresolved reference: android +import android.app.Application + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/HiltTestRunner.kt:4:8: error: unresolved reference: android +import android.content.Context + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/HiltTestRunner.kt:5:8: error: unresolved reference: androidx +import androidx.test.runner.AndroidJUnitRunner + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/HiltTestRunner.kt:6:8: error: unresolved reference: dagger +import dagger.hilt.android.testing.HiltTestApplication + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/HiltTestRunner.kt:8:24: error: unresolved reference: AndroidJUnitRunner +class HiltTestRunner : AndroidJUnitRunner() { + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/HiltTestRunner.kt:9:5: error: 'newApplication' overrides nothing + override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application { + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/HiltTestRunner.kt:9:80: error: unresolved reference: Context + override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application { + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/HiltTestRunner.kt:9:91: error: unresolved reference: Application + override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application { + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/HiltTestRunner.kt:10:22: error: unresolved reference: newApplication + return super.newApplication(cl, HiltTestApplication::class.java.name, context) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/HiltTestRunner.kt:10:41: error: unresolved reference: HiltTestApplication + return super.newApplication(cl, HiltTestApplication::class.java.name, context) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:3:8: error: unresolved reference: android +import android.content.Context + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:4:8: error: unresolved reference: androidx +import androidx.room.Room + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:5:8: error: unresolved reference: androidx +import androidx.test.core.app.ApplicationProvider + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:6:8: error: unresolved reference: androidx +import androidx.work.Configuration + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:7:8: error: unresolved reference: androidx +import androidx.work.WorkManager + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:8:8: error: unresolved reference: androidx +import androidx.work.testing.SynchronousExecutor + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:16:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.runBlocking + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:17:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.Json + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:18:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.buildJsonObject + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:19:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.put + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:20:8: error: unresolved reference: okhttp3 +import okhttp3.mockwebserver.MockResponse + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:21:8: error: unresolved reference: okhttp3 +import okhttp3.mockwebserver.MockWebServer + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:22:12: error: unresolved reference: junit +import org.junit.After + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:23:12: error: unresolved reference: junit +import org.junit.Assert.assertEquals + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:24:12: error: unresolved reference: junit +import org.junit.Assert.assertTrue + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:25:12: error: unresolved reference: junit +import org.junit.Before + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:26:12: error: unresolved reference: junit +import org.junit.Test + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:29:34: error: unresolved reference: MockWebServer + private lateinit var server: MockWebServer + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:33:6: error: unresolved reference: Before + @Before + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:34:19: error: unresolved reference: runBlocking + fun setUp() = runBlocking { + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:35:23: error: unresolved reference: ApplicationProvider + val context = ApplicationProvider.getApplicationContext() + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:35:65: error: unresolved reference: Context + val context = ApplicationProvider.getApplicationContext() + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:36:18: error: unresolved reference: MockWebServer + server = MockWebServer().apply { start() } + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:36:42: error: unresolved reference: start + server = MockWebServer().apply { start() } + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:37:20: error: unresolved reference: Room + database = Room.inMemoryDatabaseBuilder(context, TrainingDatabase::class.java) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:41:13: error: unresolved reference: WorkManager + WorkManager.initialize( + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:43:17: error: unresolved reference: Configuration + Configuration.Builder() + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:44:34: error: unresolved reference: SynchronousExecutor + .setExecutor(SynchronousExecutor()) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:45:38: error: unresolved reference: SynchronousExecutor + .setTaskExecutor(SynchronousExecutor()) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:50:18: error: suspend function 'saveConnection' should be called only from a coroutine or another suspend function + settings.saveConnection(server.url("/").toString(), "admin", "secret") + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:51:20: error: unresolved reference: Json + val json = Json { ignoreUnknownKeys = true; explicitNulls = false } + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:51:27: error: unresolved reference: ignoreUnknownKeys + val json = Json { ignoreUnknownKeys = true; explicitNulls = false } + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:51:53: error: unresolved reference: explicitNulls + val json = Json { ignoreUnknownKeys = true; explicitNulls = false } + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:52:85: error: unresolved reference: WorkManager + repository = TrainingRepository(ApiFactory(settings, json), database, json, WorkManager.getInstance(context)) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:55:6: error: unresolved reference: After + @After + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:57:18: error: unresolved reference: close + database.close() + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:61:6: error: unresolved reference: Test + @Test + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:62:48: error: unresolved reference: runBlocking + fun networkPlanListIsCachedAndReturned() = runBlocking { + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:64:31: error: suspend function 'trackerPlans' should be called only from a coroutine or another suspend function + val body = repository.trackerPlans(force = true) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:65:9: error: unresolved reference: assertEquals + assertEquals("phase-1.json", body.string("selected")) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:66:9: error: unresolved reference: assertEquals + assertEquals("/plugins/trainingstracker/api/plans", server.takeRequest().path) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:67:9: error: unresolved reference: assertTrue + assertTrue(database.cacheDao().get("tracker:plans") != null) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:67:40: error: suspend function 'get' should be called only from a coroutine or another suspend function + assertTrue(database.cacheDao().get("tracker:plans") != null) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:70:6: error: unresolved reference: Test + @Test + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:71:73: error: unresolved reference: runBlocking + fun revisionConflictIsSurfacedAndNeverConvertedToOfflineSuccess() = runBlocking { + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:74:24: error: suspend function 'patchSession' should be called only from a coroutine or another suspend function + repository.patchSession( + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:77:17: error: unresolved reference: buildJsonObject + buildJsonObject { + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:78:21: error: unresolved reference: put + put("expected_revision", 11) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:79:21: error: unresolved reference: put + put("session_key", "w01-d01") + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:80:21: error: unresolved reference: put + put("session", buildJsonObject { put("status", "in_progress") }) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:80:36: error: unresolved reference: buildJsonObject + put("session", buildJsonObject { put("status", "in_progress") }) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:80:54: error: unresolved reference: put + put("session", buildJsonObject { put("status", "in_progress") }) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:85:9: error: unresolved reference: assertTrue + assertTrue(error is RevisionConflictException) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:86:9: error: unresolved reference: assertEquals + assertEquals(12, (error as RevisionConflictException).currentRevision) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:87:9: error: unresolved reference: assertTrue + assertTrue(database.pendingMutationDao().allOrdered().isEmpty()) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:87:50: error: suspend function 'allOrdered' should be called only from a coroutine or another suspend function + assertTrue(database.pendingMutationDao().allOrdered().isEmpty()) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:90:6: error: unresolved reference: Test + @Test + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:91:56: error: unresolved reference: runBlocking + fun acceptedAnalysisKeeps202StatusForUiJobLock() = runBlocking { + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:93:33: error: suspend function 'analysisStart' should be called only from a coroutine or another suspend function + val result = repository.analysisStart("phase-1.json", "week", 1) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:94:9: error: unresolved reference: assertEquals + assertEquals(202, result.statusCode) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:95:9: error: unresolved reference: assertEquals + assertEquals("running", result.body.obj("analysis_state").string("status")) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/data/RepositoryIntegrationTest.kt:98:57: error: unresolved reference: MockResponse + private fun jsonResponse(code: Int, body: String) = MockResponse() + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:3:8: error: unresolved reference: androidx +import androidx.activity.ComponentActivity + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:4:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.Column + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:5:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.fillMaxWidth + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:6:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.width + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:7:8: error: unresolved reference: androidx +import androidx.compose.material3.MaterialTheme + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:8:8: error: unresolved reference: androidx +import androidx.compose.material3.OutlinedTextField + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:9:8: error: unresolved reference: androidx +import androidx.compose.material3.Text + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:10:8: error: unresolved reference: androidx +import androidx.compose.ui.Modifier + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:11:8: error: unresolved reference: androidx +import androidx.compose.ui.platform.testTag + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:12:8: error: unresolved reference: androidx +import androidx.compose.ui.test.assertIsDisplayed + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:13:8: error: unresolved reference: androidx +import androidx.compose.ui.test.assertWidthIsAtMost + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:14:8: error: unresolved reference: androidx +import androidx.compose.ui.test.junit4.createAndroidComposeRule + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:15:8: error: unresolved reference: androidx +import androidx.compose.ui.test.onNodeWithTag + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:16:8: error: unresolved reference: androidx +import androidx.compose.ui.unit.dp + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:17:12: error: unresolved reference: junit +import org.junit.Rule + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:18:12: error: unresolved reference: junit +import org.junit.Test + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:21:10: error: unresolved reference: Rule + @get:Rule val compose = createAndroidComposeRule() + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:21:29: error: unresolved reference: createAndroidComposeRule + @get:Rule val compose = createAndroidComposeRule() + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:21:54: error: unresolved reference: ComponentActivity + @get:Rule val compose = createAndroidComposeRule() + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:23:6: error: unresolved reference: Test + @Test + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:26:13: error: unresolved reference: MaterialTheme + MaterialTheme { + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:27:17: error: unresolved reference: Column + Column(Modifier.width(360.dp).testTag("root")) { + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:27:24: error: unresolved reference: Modifier + Column(Modifier.width(360.dp).testTag("root")) { + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:27:43: error: unresolved reference: dp + Column(Modifier.width(360.dp).testTag("root")) { + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:28:21: error: unresolved reference: Text + Text( + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:30:36: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth().testTag("long-name"), + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:32:21: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:35:36: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth().testTag("field"), + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:36:35: error: unresolved reference: Text + label = { Text("Strukturiertes Ergebnis") }, + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:41:88: error: unresolved reference: dp + compose.onNodeWithTag("long-name").assertIsDisplayed().assertWidthIsAtMost(360.dp) + ^ +app/src/androidTest/java/de/drazz/boehmitools/training/ui/CompactEditorUiTest.kt:42:84: error: unresolved reference: dp + compose.onNodeWithTag("field").assertIsDisplayed().assertWidthIsAtMost(360.dp) + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:3:8: error: unresolved reference: android +import android.os.Bundle + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:4:8: error: unresolved reference: androidx +import androidx.activity.ComponentActivity + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:5:8: error: unresolved reference: androidx +import androidx.activity.compose.setContent + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:6:8: error: unresolved reference: androidx +import androidx.activity.enableEdgeToEdge + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:7:8: error: unresolved reference: androidx +import androidx.compose.runtime.getValue + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:8:8: error: unresolved reference: androidx +import androidx.hilt.navigation.compose.hiltViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:9:8: error: unresolved reference: androidx +import androidx.lifecycle.compose.collectAsStateWithLifecycle + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:10:8: error: unresolved reference: dagger +import dagger.hilt.android.AndroidEntryPoint + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:15:2: error: unresolved reference: AndroidEntryPoint +@AndroidEntryPoint + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:16:22: error: unresolved reference: ComponentActivity +class MainActivity : ComponentActivity() { + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:17:5: error: 'onCreate' overrides nothing + override fun onCreate(savedInstanceState: Bundle?) { + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:17:47: error: unresolved reference: Bundle + override fun onCreate(savedInstanceState: Bundle?) { + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:18:15: error: unresolved reference: onCreate + super.onCreate(savedInstanceState) + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:19:9: error: unresolved reference: enableEdgeToEdge + enableEdgeToEdge() + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:20:9: error: unresolved reference: setContent + setContent { + ^ +app/src/main/java/de/drazz/boehmitools/training/MainActivity.kt:21:36: error: unresolved reference: hiltViewModel + val vm: AppViewModel = hiltViewModel() + ^ +app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt:3:8: error: unresolved reference: android +import android.app.Application + ^ +app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt:4:8: error: unresolved reference: androidx +import androidx.hilt.work.HiltWorkerFactory + ^ +app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt:5:8: error: unresolved reference: androidx +import androidx.work.Configuration + ^ +app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt:6:8: error: unresolved reference: dagger +import dagger.hilt.android.HiltAndroidApp + ^ +app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt:7:14: error: unresolved reference: inject +import javax.inject.Inject + ^ +app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt:9:2: error: unresolved reference: HiltAndroidApp +@HiltAndroidApp + ^ +app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt:10:29: error: unresolved reference: Application +class TrainingApplication : Application(), Configuration.Provider { + ^ +app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt:10:44: error: unresolved reference: Configuration +class TrainingApplication : Application(), Configuration.Provider { + ^ +app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt:11:6: error: unresolved reference: Inject + @Inject lateinit var workerFactory: HiltWorkerFactory + ^ +app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt:11:41: error: unresolved reference: HiltWorkerFactory + @Inject lateinit var workerFactory: HiltWorkerFactory + ^ +app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt:13:5: error: 'workManagerConfiguration' overrides nothing + override val workManagerConfiguration: Configuration + ^ +app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt:13:44: error: unresolved reference: Configuration + override val workManagerConfiguration: Configuration + ^ +app/src/main/java/de/drazz/boehmitools/training/TrainingApplication.kt:14:17: error: unresolved reference: Configuration + get() = Configuration.Builder() + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:3:8: error: unresolved reference: androidx +import androidx.room.Dao + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:4:8: error: unresolved reference: androidx +import androidx.room.Database + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:5:8: error: unresolved reference: androidx +import androidx.room.Entity + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:6:8: error: unresolved reference: androidx +import androidx.room.Insert + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:7:8: error: unresolved reference: androidx +import androidx.room.OnConflictStrategy + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:8:8: error: unresolved reference: androidx +import androidx.room.PrimaryKey + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:9:8: error: unresolved reference: androidx +import androidx.room.Query + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:10:8: error: unresolved reference: androidx +import androidx.room.RoomDatabase + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:11:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.Flow + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:13:2: error: unresolved reference: Entity +@Entity(tableName = "cache_entries") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:15:6: error: unresolved reference: PrimaryKey + @PrimaryKey val key: String, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:20:2: error: unresolved reference: Entity +@Entity(tableName = "pending_mutations") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:22:6: error: unresolved reference: PrimaryKey + @PrimaryKey(autoGenerate = true) val id: Long = 0, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:38:2: error: unresolved reference: Dao +@Dao + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:40:6: error: unresolved reference: Query + @Query("SELECT * FROM cache_entries WHERE `key` = :key") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:43:6: error: unresolved reference: Insert + @Insert(onConflict = OnConflictStrategy.REPLACE) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:43:26: error: unresolved reference: OnConflictStrategy + @Insert(onConflict = OnConflictStrategy.REPLACE) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:47:2: error: unresolved reference: Dao +@Dao + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:49:6: error: unresolved reference: Query + @Query("SELECT * FROM pending_mutations ORDER BY createdAt ASC, id ASC") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:52:6: error: unresolved reference: Query + @Query("SELECT * FROM pending_mutations ORDER BY createdAt ASC, id ASC") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:53:23: error: unresolved reference: Flow + fun observeAll(): Flow> + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:55:6: error: unresolved reference: Insert + @Insert + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:58:6: error: unresolved reference: Query + @Query("DELETE FROM pending_mutations WHERE id = :id") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:61:6: error: unresolved reference: Query + @Query("UPDATE pending_mutations SET state = :state, lastError = :message WHERE id = :id") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:64:6: error: unresolved reference: Query + @Query("DELETE FROM pending_mutations WHERE id = :id AND state = 'conflict'") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:68:2: error: unresolved reference: Database +@Database( + ^ +app/src/main/java/de/drazz/boehmitools/training/data/local/AppDatabase.kt:73:35: error: unresolved reference: RoomDatabase +abstract class TrainingDatabase : RoomDatabase() { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:3:38: error: unresolved reference: BuildConfig +import de.drazz.boehmitools.training.BuildConfig + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:6:14: error: unresolved reference: inject +import javax.inject.Inject + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:7:14: error: unresolved reference: inject +import javax.inject.Singleton + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:8:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.Json + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:9:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:10:8: error: unresolved reference: okhttp3 +import okhttp3.Credentials + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:11:8: error: unresolved reference: okhttp3 +import okhttp3.MediaType.Companion.toMediaType + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:12:8: error: unresolved reference: okhttp3 +import okhttp3.OkHttpClient + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:13:8: error: unresolved reference: okhttp3 +import okhttp3.logging.HttpLoggingInterceptor + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:14:8: error: unresolved reference: retrofit2 +import retrofit2.Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:15:8: error: unresolved reference: retrofit2 +import retrofit2.Retrofit + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:16:8: error: unresolved reference: retrofit2 +import retrofit2.converter.kotlinx.serialization.asConverterFactory + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:17:8: error: unresolved reference: retrofit2 +import retrofit2.http.Body + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:18:8: error: unresolved reference: retrofit2 +import retrofit2.http.DELETE + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:19:8: error: unresolved reference: retrofit2 +import retrofit2.http.GET + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:20:8: error: unresolved reference: retrofit2 +import retrofit2.http.PATCH + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:21:8: error: unresolved reference: retrofit2 +import retrofit2.http.POST + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:22:8: error: unresolved reference: retrofit2 +import retrofit2.http.PUT + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:23:8: error: unresolved reference: retrofit2 +import retrofit2.http.Path + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:26:6: error: unresolved reference: GET + @GET("api/health") suspend fun health(): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:26:46: error: unresolved reference: Response + @GET("api/health") suspend fun health(): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:26:55: error: unresolved reference: JsonObject + @GET("api/health") suspend fun health(): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:30:6: error: unresolved reference: GET + @GET("plugins/trainingstracker/api/health") suspend fun health(): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:30:71: error: unresolved reference: Response + @GET("plugins/trainingstracker/api/health") suspend fun health(): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:30:80: error: unresolved reference: JsonObject + @GET("plugins/trainingstracker/api/health") suspend fun health(): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:31:6: error: unresolved reference: GET + @GET("plugins/trainingstracker/api/plans") suspend fun plans(): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:31:69: error: unresolved reference: Response + @GET("plugins/trainingstracker/api/plans") suspend fun plans(): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:31:78: error: unresolved reference: JsonObject + @GET("plugins/trainingstracker/api/plans") suspend fun plans(): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:32:6: error: unresolved reference: POST + @POST("plugins/trainingstracker/api/plans/{planId}/select") suspend fun select(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:32:85: error: unresolved reference: Path + @POST("plugins/trainingstracker/api/plans/{planId}/select") suspend fun select(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:32:117: error: unresolved reference: Response + @POST("plugins/trainingstracker/api/plans/{planId}/select") suspend fun select(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:32:126: error: unresolved reference: JsonObject + @POST("plugins/trainingstracker/api/plans/{planId}/select") suspend fun select(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:33:6: error: unresolved reference: GET + @GET("plugins/trainingstracker/api/plans/{planId}") suspend fun plan(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:33:75: error: unresolved reference: Path + @GET("plugins/trainingstracker/api/plans/{planId}") suspend fun plan(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:33:107: error: unresolved reference: Response + @GET("plugins/trainingstracker/api/plans/{planId}") suspend fun plan(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:33:116: error: unresolved reference: JsonObject + @GET("plugins/trainingstracker/api/plans/{planId}") suspend fun plan(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:34:6: error: unresolved reference: PATCH + @PATCH("plugins/trainingstracker/api/plans/{planId}/tracker/session") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:35:31: error: unresolved reference: Path + suspend fun patchSession(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:35:63: error: unresolved reference: Body + suspend fun patchSession(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:35:77: error: unresolved reference: JsonObject + suspend fun patchSession(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:35:90: error: unresolved reference: Response + suspend fun patchSession(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:35:99: error: unresolved reference: JsonObject + suspend fun patchSession(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:36:6: error: unresolved reference: PUT + @PUT("plugins/trainingstracker/api/plans/{planId}/tracker") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:37:29: error: unresolved reference: Path + suspend fun putTracker(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:37:61: error: unresolved reference: Body + suspend fun putTracker(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:37:75: error: unresolved reference: JsonObject + suspend fun putTracker(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:37:88: error: unresolved reference: Response + suspend fun putTracker(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:37:97: error: unresolved reference: JsonObject + suspend fun putTracker(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:38:6: error: unresolved reference: POST + @POST("plugins/trainingstracker/api/plans/{planId}/analysis") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:39:32: error: unresolved reference: Path + suspend fun startAnalysis(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:39:64: error: unresolved reference: Body + suspend fun startAnalysis(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:39:78: error: unresolved reference: JsonObject + suspend fun startAnalysis(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:39:91: error: unresolved reference: Response + suspend fun startAnalysis(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:39:100: error: unresolved reference: JsonObject + suspend fun startAnalysis(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:40:6: error: unresolved reference: GET + @GET("plugins/trainingstracker/api/plans/{planId}/analysis/status") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:41:33: error: unresolved reference: Path + suspend fun analysisStatus(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:41:65: error: unresolved reference: Response + suspend fun analysisStatus(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:41:74: error: unresolved reference: JsonObject + suspend fun analysisStatus(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:45:6: error: unresolved reference: GET + @GET("plugins/trainingsplan/api/plans") suspend fun plans(): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:45:66: error: unresolved reference: Response + @GET("plugins/trainingsplan/api/plans") suspend fun plans(): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:45:75: error: unresolved reference: JsonObject + @GET("plugins/trainingsplan/api/plans") suspend fun plans(): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:46:6: error: unresolved reference: POST + @POST("plugins/trainingsplan/api/plans") suspend fun create(@Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:46:66: error: unresolved reference: Body + @POST("plugins/trainingsplan/api/plans") suspend fun create(@Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:46:80: error: unresolved reference: JsonObject + @POST("plugins/trainingsplan/api/plans") suspend fun create(@Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:46:93: error: unresolved reference: Response + @POST("plugins/trainingsplan/api/plans") suspend fun create(@Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:46:102: error: unresolved reference: JsonObject + @POST("plugins/trainingsplan/api/plans") suspend fun create(@Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:47:6: error: unresolved reference: GET + @GET("plugins/trainingsplan/api/plans/{planId}") suspend fun plan(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:47:72: error: unresolved reference: Path + @GET("plugins/trainingsplan/api/plans/{planId}") suspend fun plan(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:47:104: error: unresolved reference: Response + @GET("plugins/trainingsplan/api/plans/{planId}") suspend fun plan(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:47:113: error: unresolved reference: JsonObject + @GET("plugins/trainingsplan/api/plans/{planId}") suspend fun plan(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:48:6: error: unresolved reference: POST + @POST("plugins/trainingsplan/api/plans/{planId}/select") suspend fun select(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:48:82: error: unresolved reference: Path + @POST("plugins/trainingsplan/api/plans/{planId}/select") suspend fun select(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:48:114: error: unresolved reference: Response + @POST("plugins/trainingsplan/api/plans/{planId}/select") suspend fun select(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:48:123: error: unresolved reference: JsonObject + @POST("plugins/trainingsplan/api/plans/{planId}/select") suspend fun select(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:49:6: error: unresolved reference: POST + @POST("plugins/trainingsplan/api/plans/{planId}") suspend fun save(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:49:73: error: unresolved reference: Path + @POST("plugins/trainingsplan/api/plans/{planId}") suspend fun save(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:49:105: error: unresolved reference: Body + @POST("plugins/trainingsplan/api/plans/{planId}") suspend fun save(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:49:119: error: unresolved reference: JsonObject + @POST("plugins/trainingsplan/api/plans/{planId}") suspend fun save(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:49:132: error: unresolved reference: Response + @POST("plugins/trainingsplan/api/plans/{planId}") suspend fun save(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:49:141: error: unresolved reference: JsonObject + @POST("plugins/trainingsplan/api/plans/{planId}") suspend fun save(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:50:6: error: unresolved reference: POST + @POST("plugins/trainingsplan/api/plans/{planId}/validate") suspend fun validate(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:50:86: error: unresolved reference: Path + @POST("plugins/trainingsplan/api/plans/{planId}/validate") suspend fun validate(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:50:118: error: unresolved reference: Body + @POST("plugins/trainingsplan/api/plans/{planId}/validate") suspend fun validate(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:50:132: error: unresolved reference: JsonObject + @POST("plugins/trainingsplan/api/plans/{planId}/validate") suspend fun validate(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:50:145: error: unresolved reference: Response + @POST("plugins/trainingsplan/api/plans/{planId}/validate") suspend fun validate(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:50:154: error: unresolved reference: JsonObject + @POST("plugins/trainingsplan/api/plans/{planId}/validate") suspend fun validate(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:51:6: error: unresolved reference: POST + @POST("plugins/trainingsplan/api/plans/{planId}/publish") suspend fun publish(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:51:84: error: unresolved reference: Path + @POST("plugins/trainingsplan/api/plans/{planId}/publish") suspend fun publish(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:51:116: error: unresolved reference: Body + @POST("plugins/trainingsplan/api/plans/{planId}/publish") suspend fun publish(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:51:130: error: unresolved reference: JsonObject + @POST("plugins/trainingsplan/api/plans/{planId}/publish") suspend fun publish(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:51:143: error: unresolved reference: Response + @POST("plugins/trainingsplan/api/plans/{planId}/publish") suspend fun publish(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:51:152: error: unresolved reference: JsonObject + @POST("plugins/trainingsplan/api/plans/{planId}/publish") suspend fun publish(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:52:6: error: unresolved reference: POST + @POST("plugins/trainingsplan/api/plans/{planId}/rename") suspend fun rename(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:52:82: error: unresolved reference: Path + @POST("plugins/trainingsplan/api/plans/{planId}/rename") suspend fun rename(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:52:114: error: unresolved reference: Body + @POST("plugins/trainingsplan/api/plans/{planId}/rename") suspend fun rename(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:52:128: error: unresolved reference: JsonObject + @POST("plugins/trainingsplan/api/plans/{planId}/rename") suspend fun rename(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:52:141: error: unresolved reference: Response + @POST("plugins/trainingsplan/api/plans/{planId}/rename") suspend fun rename(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:52:150: error: unresolved reference: JsonObject + @POST("plugins/trainingsplan/api/plans/{planId}/rename") suspend fun rename(@Path("planId") planId: String, @Body payload: JsonObject): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:53:6: error: unresolved reference: DELETE + @DELETE("plugins/trainingsplan/api/plans/{planId}") suspend fun delete(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:53:77: error: unresolved reference: Path + @DELETE("plugins/trainingsplan/api/plans/{planId}") suspend fun delete(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:53:109: error: unresolved reference: Response + @DELETE("plugins/trainingsplan/api/plans/{planId}") suspend fun delete(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:53:118: error: unresolved reference: JsonObject + @DELETE("plugins/trainingsplan/api/plans/{planId}") suspend fun delete(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:54:6: error: unresolved reference: GET + @GET("plugins/trainingsplan/api/plans/{planId}/proposals") suspend fun proposals(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:54:87: error: unresolved reference: Path + @GET("plugins/trainingsplan/api/plans/{planId}/proposals") suspend fun proposals(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:54:119: error: unresolved reference: Response + @GET("plugins/trainingsplan/api/plans/{planId}/proposals") suspend fun proposals(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:54:128: error: unresolved reference: JsonObject + @GET("plugins/trainingsplan/api/plans/{planId}/proposals") suspend fun proposals(@Path("planId") planId: String): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:55:6: error: unresolved reference: POST + @POST("plugins/trainingsplan/api/plans/{planId}/proposals/{proposalId}") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:57:10: error: unresolved reference: Path + @Path("planId") planId: String, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:58:10: error: unresolved reference: Path + @Path("proposalId") proposalId: String, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:59:10: error: unresolved reference: Body + @Body payload: JsonObject, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:59:24: error: unresolved reference: JsonObject + @Body payload: JsonObject, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:60:8: error: unresolved reference: Response + ): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:60:17: error: unresolved reference: JsonObject + ): Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:69:2: error: unresolved reference: Singleton +@Singleton + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:70:19: error: unresolved reference: Inject +class ApiFactory @Inject constructor( + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:72:23: error: unresolved reference: Json + private val json: Json, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:77:17: error: unresolved reference: BuildConfig + require(BuildConfig.DEBUG || scheme == "https") { "Release-Builds erlauben ausschlie?lich HTTPS." } + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:80:23: error: unresolved reference: HttpLoggingInterceptor + val logging = HttpLoggingInterceptor().apply { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:81:13: error: unresolved reference: level + level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BASIC else HttpLoggingInterceptor.Level.NONE + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:81:25: error: unresolved reference: BuildConfig + level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BASIC else HttpLoggingInterceptor.Level.NONE + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:81:44: error: unresolved reference: HttpLoggingInterceptor + level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BASIC else HttpLoggingInterceptor.Level.NONE + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:81:84: error: unresolved reference: HttpLoggingInterceptor + level = if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BASIC else HttpLoggingInterceptor.Level.NONE + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:82:13: error: unresolved reference: redactHeader + redactHeader("Authorization") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:84:22: error: unresolved reference: OkHttpClient + val client = OkHttpClient.Builder() + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:88:31: error: cannot infer a type for this parameter. Please specify it explicitly. + .addInterceptor { chain -> + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:91:25: error: unresolved reference: header + header("Authorization", Credentials.basic(snapshot.username, snapshot.password)) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:91:49: error: unresolved reference: Credentials + header("Authorization", Credentials.basic(snapshot.username, snapshot.password)) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:93:21: error: unresolved reference: header + header("Accept", "application/json") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:99:24: error: unresolved reference: Retrofit + val retrofit = Retrofit.Builder() + ^ +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:102:77: error: unresolved reference: toMediaType + .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:3:8: error: unresolved reference: androidx +import androidx.work.Constraints + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:4:8: error: unresolved reference: androidx +import androidx.work.ExistingWorkPolicy + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:5:8: error: unresolved reference: androidx +import androidx.work.NetworkType + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:6:8: error: unresolved reference: androidx +import androidx.work.OneTimeWorkRequestBuilder + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:7:8: error: unresolved reference: androidx +import androidx.work.WorkManager + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:14:14: error: unresolved reference: inject +import javax.inject.Inject + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:15:14: error: unresolved reference: inject +import javax.inject.Singleton + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:16:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.Flow + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:17:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.Json + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:18:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:19:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.buildJsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:20:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.intOrNull + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:21:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.jsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:22:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.jsonPrimitive + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:23:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.put + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:24:8: error: unresolved reference: retrofit2 +import retrofit2.Response + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:26:77: error: unresolved reference: JsonObject +class ApiException(val status: Int, override val message: String, val body: JsonObject? = null) : Exception(message) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:30:32: error: unresolved reference: JsonObject + data class Saved(val body: JsonObject) : SaveOutcome + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:34:59: error: unresolved reference: JsonObject +data class AnalysisOutcome(val statusCode: Int, val body: JsonObject) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:36:2: error: unresolved reference: Singleton +@Singleton + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:37:27: error: unresolved reference: Inject +class TrainingRepository @Inject constructor( + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:40:23: error: unresolved reference: Json + private val json: Json, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:41:30: error: unresolved reference: WorkManager + private val workManager: WorkManager, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:46:29: error: unresolved reference: Flow + fun pendingMutations(): Flow> = pending.observeAll() + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:51:35: error: unresolved reference: JsonObject + suspend fun testConnection(): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:57:55: error: unresolved reference: JsonObject + suspend fun trackerPlans(force: Boolean = false): JsonObject = cached( + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:63:70: error: unresolved reference: JsonObject + suspend fun trackerPlan(planId: String, force: Boolean = false): JsonObject = cached( + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:69:52: error: unresolved reference: JsonObject + suspend fun selectTrackerPlan(planId: String): JsonObject = requireSuccess(apiFactory.create().tracker.select(planId)) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:71:75: error: unresolved reference: JsonObject + suspend fun patchSession(planId: String, sessionKey: String, payload: JsonObject): SaveOutcome { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:96:23: error: unresolved reference: buildJsonObject + val payload = buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:97:13: error: unresolved reference: put + put("scope", scope) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:98:50: error: unresolved reference: put + if (scope == "week" && week != null) put("week", week) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:107:49: error: unresolved reference: JsonObject + suspend fun analysisStatus(planId: String): JsonObject = requireSuccess(apiFactory.create().tracker.analysisStatus(planId)) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:109:54: error: unresolved reference: JsonObject + suspend fun editorPlans(force: Boolean = false): JsonObject = cached( + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:115:69: error: unresolved reference: JsonObject + suspend fun editorPlan(planId: String, force: Boolean = false): JsonObject = cached( + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:121:51: error: unresolved reference: JsonObject + suspend fun selectEditorPlan(planId: String): JsonObject = requireSuccess(apiFactory.create().editor.select(planId)) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:123:65: error: unresolved reference: JsonObject + suspend fun createPlan(name: String, from: String? = null): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:124:23: error: unresolved reference: buildJsonObject + val payload = buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:125:13: error: unresolved reference: put + put("name", name) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:126:13: error: unresolved reference: put + put("kind", "training") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:127:40: error: unresolved reference: put + if (!from.isNullOrBlank()) put("from", from) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:132:65: error: unresolved reference: JsonObject + suspend fun savePlan(planId: String, revision: Int, config: JsonObject): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:132:78: error: unresolved reference: JsonObject + suspend fun savePlan(planId: String, revision: Int, config: JsonObject): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:133:23: error: unresolved reference: buildJsonObject + val payload = buildJsonObject { put("expected_revision", revision); put("config", config) } + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:133:41: error: unresolved reference: put + val payload = buildJsonObject { put("expected_revision", revision); put("config", config) } + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:133:77: error: unresolved reference: put + val payload = buildJsonObject { put("expected_revision", revision); put("config", config) } + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:137:54: error: unresolved reference: JsonObject + suspend fun validatePlan(planId: String, config: JsonObject): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:137:67: error: unresolved reference: JsonObject + suspend fun validatePlan(planId: String, config: JsonObject): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:138:75: error: unresolved reference: buildJsonObject + return requireSuccess(apiFactory.create().editor.validate(planId, buildJsonObject { put("config", config) })) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:138:93: error: unresolved reference: put + return requireSuccess(apiFactory.create().editor.validate(planId, buildJsonObject { put("config", config) })) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:141:61: error: unresolved reference: JsonObject + suspend fun publishPlan(planId: String, revision: Int): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:142:74: error: unresolved reference: buildJsonObject + return requireSuccess(apiFactory.create().editor.publish(planId, buildJsonObject { put("expected_revision", revision) })) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:142:92: error: unresolved reference: put + return requireSuccess(apiFactory.create().editor.publish(planId, buildJsonObject { put("expected_revision", revision) })) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:145:59: error: unresolved reference: JsonObject + suspend fun renamePlan(planId: String, name: String): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:146:73: error: unresolved reference: buildJsonObject + return requireSuccess(apiFactory.create().editor.rename(planId, buildJsonObject { put("name", name) })) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:146:91: error: unresolved reference: put + return requireSuccess(apiFactory.create().editor.rename(planId, buildJsonObject { put("name", name) })) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:149:45: error: unresolved reference: JsonObject + suspend fun deletePlan(planId: String): JsonObject = requireSuccess(apiFactory.create().editor.delete(planId)) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:150:44: error: unresolved reference: JsonObject + suspend fun proposals(planId: String): JsonObject = requireSuccess(apiFactory.create().editor.proposals(planId)) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:152:88: error: unresolved reference: JsonObject + suspend fun setProposalStatus(planId: String, proposalId: String, status: String): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:154:75: error: unresolved reference: buildJsonObject + apiFactory.create().editor.proposalStatus(planId, proposalId, buildJsonObject { put("status", status) }) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:154:93: error: unresolved reference: put + apiFactory.create().editor.proposalStatus(planId, proposalId, buildJsonObject { put("status", status) }) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:189:32: error: unresolved reference: Response + request: suspend () -> Response, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:189:41: error: unresolved reference: JsonObject + request: suspend () -> Response, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:190:8: error: unresolved reference: JsonObject + ): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:204:27: error: unresolved reference: Constraints + val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build() + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:204:72: error: unresolved reference: NetworkType + val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build() + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:205:23: error: unresolved reference: OneTimeWorkRequestBuilder + val request = OneTimeWorkRequestBuilder().setConstraints(constraints).build() + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:206:63: error: unresolved reference: ExistingWorkPolicy + workManager.enqueueUniqueWork("pending-session-sync", ExistingWorkPolicy.KEEP, request) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:209:42: error: unresolved reference: Response + private fun requireSuccess(response: Response, fallback: String = "Serverfehler"): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:209:51: error: unresolved reference: JsonObject + private fun requireSuccess(response: Response, fallback: String = "Serverfehler"): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:209:100: error: unresolved reference: JsonObject + private fun requireSuccess(response: Response, fallback: String = "Serverfehler"): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:221:39: error: unresolved reference: Response + private fun bodyOrError(response: Response): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:221:52: error: unresolved reference: JsonObject + private fun bodyOrError(response: Response): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:221:66: error: unresolved reference: JsonObject + private fun bodyOrError(response: Response): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:222:32: error: 'return' is not allowed here + response.body()?.let { return it } + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:222:39: error: unresolved reference: it + response.body()?.let { return it } + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:224:85: error: unresolved reference: JsonObject + return runCatching { json.parseToJsonElement(raw).jsonObject }.getOrDefault(JsonObject(emptyMap())) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/repository/TrainingRepository.kt:227:35: error: unresolved reference: JsonObject + private fun messageFrom(body: JsonObject, fallback: String): String { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:3:8: error: unresolved reference: android +import android.content.Context + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:4:8: error: unresolved reference: android +import android.security.keystore.KeyGenParameterSpec + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:5:8: error: unresolved reference: android +import android.security.keystore.KeyProperties + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:6:8: error: unresolved reference: android +import android.util.Base64 + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:7:8: error: unresolved reference: androidx +import androidx.datastore.preferences.core.Preferences + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:8:8: error: unresolved reference: androidx +import androidx.datastore.preferences.core.edit + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:9:8: error: unresolved reference: androidx +import androidx.datastore.preferences.core.intPreferencesKey + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:10:8: error: unresolved reference: androidx +import androidx.datastore.preferences.core.stringPreferencesKey + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:11:8: error: unresolved reference: androidx +import androidx.datastore.preferences.preferencesDataStore + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:12:8: error: unresolved reference: dagger +import dagger.hilt.android.qualifiers.ApplicationContext + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:18:14: error: unresolved reference: inject +import javax.inject.Inject + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:19:14: error: unresolved reference: inject +import javax.inject.Singleton + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:20:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.Flow + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:21:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.first + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:22:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.map + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:24:13: error: unresolved reference: Context +private val Context.settingsDataStore by preferencesDataStore("settings") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:24:42: error: unresolved reference: preferencesDataStore +private val Context.settingsDataStore by preferencesDataStore("settings") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:26:2: error: unresolved reference: Singleton +@Singleton + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:27:27: error: unresolved reference: Inject +class SettingsRepository @Inject constructor( + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:28:6: error: unresolved reference: ApplicationContext + @ApplicationContext private val context: Context, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:28:46: error: unresolved reference: Context + @ApplicationContext private val context: Context, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:45:22: error: unresolved reference: stringPreferencesKey + val server = stringPreferencesKey("server_url") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:46:24: error: unresolved reference: stringPreferencesKey + val username = stringPreferencesKey("username") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:47:30: error: unresolved reference: stringPreferencesKey + val passwordCipher = stringPreferencesKey("password_cipher") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:48:26: error: unresolved reference: stringPreferencesKey + val passwordIv = stringPreferencesKey("password_iv") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:49:21: error: unresolved reference: stringPreferencesKey + val theme = stringPreferencesKey("theme") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:50:35: error: unresolved reference: stringPreferencesKey + val selectedTrackerPlan = stringPreferencesKey("tracker_selected_plan") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:51:34: error: unresolved reference: stringPreferencesKey + val selectedEditorPlan = stringPreferencesKey("editor_selected_plan") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:54:25: error: unresolved reference: Flow + val publicSettings: Flow = context.settingsDataStore.data.map { prefs -> + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:54:85: error: cannot infer a type for this parameter. Please specify it explicitly. + val publicSettings: Flow = context.settingsDataStore.data.map { prefs -> + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:75:42: error: cannot infer a type for this parameter. Please specify it explicitly. + context.settingsDataStore.edit { prefs -> + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:84:42: error: unresolved reference: it + context.settingsDataStore.edit { it[Keys.theme] = theme } + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:91:42: error: unresolved reference: it + context.settingsDataStore.edit { it[Keys.selectedTrackerPlan] = id } + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:95:42: error: unresolved reference: it + context.settingsDataStore.edit { it[Keys.selectedEditorPlan] = id } + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:101:25: error: unresolved reference: stringPreferencesKey + tab = prefs[stringPreferencesKey("tracker_tab_${safe(planId)}")] ?: "session", + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:102:26: error: unresolved reference: intPreferencesKey + week = prefs[intPreferencesKey("tracker_week_${safe(planId)}")] ?: 1, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:103:25: error: unresolved reference: intPreferencesKey + day = prefs[intPreferencesKey("tracker_day_${safe(planId)}")] ?: 1, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:108:42: error: cannot infer a type for this parameter. Please specify it explicitly. + context.settingsDataStore.edit { prefs -> + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:109:19: error: unresolved reference: stringPreferencesKey + prefs[stringPreferencesKey("tracker_tab_${safe(planId)}")] = tab + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:110:19: error: unresolved reference: intPreferencesKey + prefs[intPreferencesKey("tracker_week_${safe(planId)}")] = week + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:111:19: error: unresolved reference: intPreferencesKey + prefs[intPreferencesKey("tracker_day_${safe(planId)}")] = day + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:126:50: error: unresolved reference: KeyProperties + val generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:128:13: error: unresolved reference: KeyGenParameterSpec + KeyGenParameterSpec.Builder( + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:130:17: error: unresolved reference: KeyProperties + KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:130:50: error: unresolved reference: KeyProperties + KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:132:32: error: unresolved reference: KeyProperties + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:133:40: error: unresolved reference: KeyProperties + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:144:16: error: unresolved reference: Base64 + return Base64.encodeToString(cipher.doFinal(value.toByteArray()), Base64.NO_WRAP) to + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:144:75: error: unresolved reference: Base64 + return Base64.encodeToString(cipher.doFinal(value.toByteArray()), Base64.NO_WRAP) to + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:145:13: error: unresolved reference: Base64 + Base64.encodeToString(cipher.iv, Base64.NO_WRAP) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:145:46: error: unresolved reference: Base64 + Base64.encodeToString(cipher.iv, Base64.NO_WRAP) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:155:39: error: unresolved reference: Base64 + GCMParameterSpec(128, Base64.decode(ivText, Base64.NO_WRAP)), + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:155:61: error: unresolved reference: Base64 + GCMParameterSpec(128, Base64.decode(ivText, Base64.NO_WRAP)), + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:157:35: error: unresolved reference: Base64 + String(cipher.doFinal(Base64.decode(cipherText, Base64.NO_WRAP))) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/settings/SettingsRepository.kt:157:61: error: unresolved reference: Base64 + String(cipher.doFinal(Base64.decode(cipherText, Base64.NO_WRAP))) + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:3:8: error: unresolved reference: android +import android.content.Context + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:4:8: error: unresolved reference: androidx +import androidx.hilt.work.HiltWorker + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:5:8: error: unresolved reference: androidx +import androidx.work.CoroutineWorker + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:6:8: error: unresolved reference: androidx +import androidx.work.WorkerParameters + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:7:8: error: unresolved reference: dagger +import dagger.assisted.Assisted + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:8:8: error: unresolved reference: dagger +import dagger.assisted.AssistedInject + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:11:2: error: unresolved reference: HiltWorker +@HiltWorker + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:12:26: error: unresolved reference: AssistedInject +class PendingSyncWorker @AssistedInject constructor( + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:13:6: error: unresolved reference: Assisted + @Assisted context: Context, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:13:24: error: unresolved reference: Context + @Assisted context: Context, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:14:6: error: unresolved reference: Assisted + @Assisted params: WorkerParameters, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:14:23: error: unresolved reference: WorkerParameters + @Assisted params: WorkerParameters, + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:16:5: error: unresolved reference: CoroutineWorker +) : CoroutineWorker(context, params) { + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:17:5: error: 'doWork' overrides nothing + override suspend fun doWork(): Result = if (repository.syncPending()) Result.success() else Result.retry() + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:17:36: error: one type argument expected for class Result + override suspend fun doWork(): Result = if (repository.syncPending()) Result.success() else Result.retry() + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:17:82: error: not enough information to infer type variable T + override suspend fun doWork(): Result = if (repository.syncPending()) Result.success() else Result.retry() + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:17:89: error: no value passed for parameter 'value' + override suspend fun doWork(): Result = if (repository.syncPending()) Result.success() else Result.retry() + ^ +app/src/main/java/de/drazz/boehmitools/training/data/sync/PendingSyncWorker.kt:17:104: error: unresolved reference: retry + override suspend fun doWork(): Result = if (repository.syncPending()) Result.success() else Result.retry() + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:3:8: error: unresolved reference: android +import android.content.Context + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:4:8: error: unresolved reference: androidx +import androidx.room.Room + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:5:8: error: unresolved reference: androidx +import androidx.work.WorkManager + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:6:8: error: unresolved reference: dagger +import dagger.Module + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:7:8: error: unresolved reference: dagger +import dagger.Provides + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:8:8: error: unresolved reference: dagger +import dagger.hilt.InstallIn + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:9:8: error: unresolved reference: dagger +import dagger.hilt.android.qualifiers.ApplicationContext + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:10:8: error: unresolved reference: dagger +import dagger.hilt.components.SingletonComponent + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:12:14: error: unresolved reference: inject +import javax.inject.Singleton + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:13:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.Json + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:15:2: error: none of the following functions can be called with the arguments supplied: +public/*package*/ constructor Module(p0: ClassLoader!) defined in java.lang.Module +public/*package*/ constructor Module(p0: ClassLoader!, p1: ModuleDescriptor!) defined in java.lang.Module +public/*package*/ constructor Module(p0: ModuleLayer!, p1: ClassLoader!, p2: ModuleDescriptor!, p3: URI!) defined in java.lang.Module +@Module + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:16:2: error: unresolved reference: InstallIn +@InstallIn(SingletonComponent::class) + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:16:12: error: unresolved reference: SingletonComponent +@InstallIn(SingletonComponent::class) + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:18:6: error: unresolved reference: Provides + @Provides + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:19:6: error: unresolved reference: Singleton + @Singleton + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:20:17: error: unresolved reference: Json + fun json(): Json = Json { + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:20:24: error: unresolved reference: Json + fun json(): Json = Json { + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:21:9: error: unresolved reference: ignoreUnknownKeys + ignoreUnknownKeys = true + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:22:9: error: unresolved reference: explicitNulls + explicitNulls = false + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:23:9: error: unresolved reference: encodeDefaults + encodeDefaults = true + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:24:9: error: unresolved reference: isLenient + isLenient = true + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:27:6: error: unresolved reference: Provides + @Provides + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:28:6: error: unresolved reference: Singleton + @Singleton + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:29:19: error: unresolved reference: ApplicationContext + fun database(@ApplicationContext context: Context): TrainingDatabase = + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:29:47: error: unresolved reference: Context + fun database(@ApplicationContext context: Context): TrainingDatabase = + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:30:9: error: unresolved reference: Room + Room.databaseBuilder(context, TrainingDatabase::class.java, "training.db") + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:33:6: error: unresolved reference: Provides + @Provides + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:34:6: error: unresolved reference: Singleton + @Singleton + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:35:22: error: unresolved reference: ApplicationContext + fun workManager(@ApplicationContext context: Context): WorkManager = WorkManager.getInstance(context) + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:35:50: error: unresolved reference: Context + fun workManager(@ApplicationContext context: Context): WorkManager = WorkManager.getInstance(context) + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:35:60: error: unresolved reference: WorkManager + fun workManager(@ApplicationContext context: Context): WorkManager = WorkManager.getInstance(context) + ^ +app/src/main/java/de/drazz/boehmitools/training/di/AppModule.kt:35:74: error: unresolved reference: WorkManager + fun workManager(@ApplicationContext context: Context): WorkManager = WorkManager.getInstance(context) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:3:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonArray + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:4:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonElement + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:5:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonNull + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:6:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:7:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonPrimitive + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:8:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.booleanOrNull + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:9:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.buildJsonArray + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:10:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.buildJsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:11:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.contentOrNull + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:12:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.doubleOrNull + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:13:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.intOrNull + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:14:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.jsonArray + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:15:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.jsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:16:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.jsonPrimitive + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:17:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.put + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:19:5: error: unresolved reference: JsonObject +fun JsonObject.string(key: String, fallback: String = ""): String = this[key]?.jsonPrimitive?.contentOrNull ?: fallback + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:20:5: error: unresolved reference: JsonObject +fun JsonObject.int(key: String, fallback: Int = 0): Int = this[key]?.jsonPrimitive?.intOrNull ?: fallback + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:21:5: error: unresolved reference: JsonObject +fun JsonObject.bool(key: String, fallback: Boolean = false): Boolean = this[key]?.jsonPrimitive?.booleanOrNull ?: fallback + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:22:5: error: unresolved reference: JsonObject +fun JsonObject.obj(key: String): JsonObject = this[key] as? JsonObject ?: JsonObject(emptyMap()) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:22:34: error: unresolved reference: JsonObject +fun JsonObject.obj(key: String): JsonObject = this[key] as? JsonObject ?: JsonObject(emptyMap()) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:22:61: error: unresolved reference: JsonObject +fun JsonObject.obj(key: String): JsonObject = this[key] as? JsonObject ?: JsonObject(emptyMap()) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:23:5: error: unresolved reference: JsonObject +fun JsonObject.array(key: String): JsonArray = this[key] as? JsonArray ?: JsonArray(emptyList()) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:23:36: error: unresolved reference: JsonArray +fun JsonObject.array(key: String): JsonArray = this[key] as? JsonArray ?: JsonArray(emptyList()) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:23:62: error: unresolved reference: JsonArray +fun JsonObject.array(key: String): JsonArray = this[key] as? JsonArray ?: JsonArray(emptyList()) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:25:5: error: unresolved reference: JsonObject +fun JsonObject.updated(key: String, value: JsonElement?): JsonObject = JsonObject(toMutableMap().apply { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:25:44: error: unresolved reference: JsonElement +fun JsonObject.updated(key: String, value: JsonElement?): JsonObject = JsonObject(toMutableMap().apply { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:25:59: error: unresolved reference: JsonObject +fun JsonObject.updated(key: String, value: JsonElement?): JsonObject = JsonObject(toMutableMap().apply { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:29:5: error: unresolved reference: JsonObject +fun JsonObject.updatedString(key: String, value: String): JsonObject = updated(key, JsonPrimitive(value)) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:29:59: error: unresolved reference: JsonObject +fun JsonObject.updatedString(key: String, value: String): JsonObject = updated(key, JsonPrimitive(value)) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:30:5: error: unresolved reference: JsonObject +fun JsonObject.updatedInt(key: String, value: Int): JsonObject = updated(key, JsonPrimitive(value)) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:30:53: error: unresolved reference: JsonObject +fun JsonObject.updatedInt(key: String, value: Int): JsonObject = updated(key, JsonPrimitive(value)) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:31:5: error: unresolved reference: JsonObject +fun JsonObject.updatedBool(key: String, value: Boolean): JsonObject = updated(key, JsonPrimitive(value)) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:31:58: error: unresolved reference: JsonObject +fun JsonObject.updatedBool(key: String, value: Boolean): JsonObject = updated(key, JsonPrimitive(value)) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:47:14: error: unresolved reference: JsonObject + val raw: JsonObject, + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:55:14: error: unresolved reference: JsonObject + val raw: JsonObject, + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:66:14: error: unresolved reference: JsonObject + val raw: JsonObject, + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:73:14: error: unresolved reference: JsonObject + val raw: JsonObject, + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:85:14: error: unresolved reference: JsonObject + val raw: JsonObject, + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:97:25: error: unresolved reference: JsonObject + val trainingFormat: JsonObject, + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:98:26: error: unresolved reference: JsonObject + val exerciseCatalog: JsonObject, + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:99:16: error: unresolved reference: JsonObject + val front: JsonObject, + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:100:14: error: unresolved reference: JsonObject + val raw: JsonObject, + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:103:21: error: unresolved reference: JsonObject +fun parsePlan(plan: JsonObject): PlanUi { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:107:48: error: unresolved reference: it + val days = plan.array("days").mapNotNull { it as? JsonObject }.map { day -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:107:55: error: unresolved reference: JsonObject + val days = plan.array("days").mapNotNull { it as? JsonObject }.map { day -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:107:74: error: cannot infer a type for this parameter. Please specify it explicitly. + val days = plan.array("days").mapNotNull { it as? JsonObject }.map { day -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:109:61: error: unresolved reference: it + val rotations = day.array("rotations").mapNotNull { it as? JsonObject }.mapIndexed { ri, rotation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:109:68: error: unresolved reference: JsonObject + val rotations = day.array("rotations").mapNotNull { it as? JsonObject }.mapIndexed { ri, rotation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:109:94: error: cannot infer a type for this parameter. Please specify it explicitly. + val rotations = day.array("rotations").mapNotNull { it as? JsonObject }.mapIndexed { ri, rotation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:109:98: error: cannot infer a type for this parameter. Please specify it explicitly. + val rotations = day.array("rotations").mapNotNull { it as? JsonObject }.mapIndexed { ri, rotation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:110:70: error: unresolved reference: it + val exercises = rotation.array("exercises").mapNotNull { it as? JsonObject }.mapIndexed { ei, exercise -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:110:77: error: unresolved reference: JsonObject + val exercises = rotation.array("exercises").mapNotNull { it as? JsonObject }.mapIndexed { ei, exercise -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:110:103: error: cannot infer a type for this parameter. Please specify it explicitly. + val exercises = rotation.array("exercises").mapNotNull { it as? JsonObject }.mapIndexed { ei, exercise -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:110:107: error: cannot infer a type for this parameter. Please specify it explicitly. + val exercises = rotation.array("exercises").mapNotNull { it as? JsonObject }.mapIndexed { ei, exercise -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:118:84: error: unresolved reference: JsonObject + resultSchema = parseResultSchema(exercise["result_schema"] as? JsonObject, "exercise"), + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:131:72: error: unresolved reference: it + val normalized = day.obj(name).array("items").mapNotNull { it.jsonPrimitive.contentOrNull } + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:135:24: error: unresolved reference: it + .map { it.replace(Regex("<[^>]+>"), "").trim() } + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:136:27: error: unresolved reference: it + .filter { it.isNotBlank() } + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:149:18: error: unresolved reference: it + }.sortedBy { it.num } + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:166:40: error: unresolved reference: JsonElement +private fun parseProgressions(element: JsonElement?): Map { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:167:36: error: unresolved reference: JsonObject + val entries: List> = when (element) { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:168:12: error: unresolved reference: JsonArray + is JsonArray -> element.mapNotNull { value -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:168:46: error: cannot infer a type for this parameter. Please specify it explicitly. + is JsonArray -> element.mapNotNull { value -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:169:35: error: unresolved reference: JsonObject + val stage = value as? JsonObject ?: return@mapNotNull null + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:173:12: error: unresolved reference: JsonObject + is JsonObject -> element.mapNotNull { (mapKey, value) -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:173:47: error: cannot infer a type for this parameter. Please specify it explicitly. + is JsonObject -> element.mapNotNull { (mapKey, value) -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:174:35: error: unresolved reference: JsonObject + val stage = value as? JsonObject ?: return@mapNotNull null + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:179:32: error: cannot infer a type for this parameter. Please specify it explicitly. + return entries.associate { (mapKey, stage) -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:181:55: error: unresolved reference: it + val steps = stage.array("steps").mapNotNull { it as? JsonObject }.map { step -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:181:62: error: unresolved reference: JsonObject + val steps = stage.array("steps").mapNotNull { it as? JsonObject }.map { step -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:181:81: error: cannot infer a type for this parameter. Please specify it explicitly. + val steps = stage.array("steps").mapNotNull { it as? JsonObject }.map { step -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:185:76: error: unresolved reference: JsonObject + resultSchema = parseResultSchema(step["result_schema"] as? JsonObject, "progression"), + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:199:28: error: unresolved reference: JsonObject +fun parseResultSchema(raw: JsonObject?, source: String): ResultSchemaUi? { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:205:65: error: unresolved reference: it + weightMode = raw.string("weight_mode", "none").takeIf { it in setOf("none", "optional", "required") } ?: "none", + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:206:69: error: unresolved reference: it + laterality = raw.string("laterality", "bilateral").takeIf { it in setOf("bilateral", "unilateral") } ?: "bilateral", + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:207:63: error: unresolved reference: it + sidesMode = raw.string("sides_mode", "same").takeIf { it in setOf("same", "separate") } ?: "same", + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:215:21: error: unresolved reference: JsonObject + exerciseSchema: JsonObject?, + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:216:24: error: unresolved reference: JsonObject + progressionSchema: JsonObject?, + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:217:19: error: unresolved reference: JsonObject + manualSchema: JsonObject?, + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:219:42: error: unresolved reference: JsonObject + fun apply(base: ResultSchemaUi, raw: JsonObject?, source: String): ResultSchemaUi { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:222:24: error: unresolved reference: it + ?.takeIf { it in setOf("reps", "seconds", "minutes", "none", "auto") } + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:225:24: error: unresolved reference: it + ?.takeIf { it in setOf("none", "optional", "required") } + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:228:24: error: unresolved reference: it + ?.takeIf { it in setOf("bilateral", "unilateral") } + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:231:24: error: unresolved reference: it + ?.takeIf { it in setOf("same", "separate") } + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:265:11: error: unresolved reference: JsonObject + item: JsonObject, + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:269:47: error: unresolved reference: JsonObject + val manualRaw = item["result_format"] as? JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:274:55: error: unresolved reference: JsonObject + val stepRaw = step?.raw?.get("result_schema") as? JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:275:57: error: unresolved reference: JsonObject + val exerciseRaw = exercise.raw["result_schema"] as? JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:312:33: error: unresolved reference: JsonObject +fun blankSession(plan: PlanUi): JsonObject = buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:312:46: error: unresolved reference: buildJsonObject +fun blankSession(plan: PlanUi): JsonObject = buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:313:5: error: unresolved reference: put + put("status", "planned") + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:314:5: error: unresolved reference: put + put("plan_id", plan.planId) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:315:5: error: unresolved reference: put + put("plan_revision", plan.publishedRevision) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:316:5: error: unresolved reference: put + put("items", JsonObject(emptyMap())) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:316:18: error: unresolved reference: JsonObject + put("items", JsonObject(emptyMap())) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:317:5: error: unresolved reference: put + put("note", "") + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:320:29: error: unresolved reference: JsonObject +fun trackerSession(tracker: JsonObject, key: String, plan: PlanUi): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:320:69: error: unresolved reference: JsonObject +fun trackerSession(tracker: JsonObject, key: String, plan: PlanUi): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:321:46: error: unresolved reference: JsonObject + return (tracker.obj("sessions")[key] as? JsonObject) ?: blankSession(plan) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:324:33: error: unresolved reference: JsonObject +fun migrateLegacyItems(session: JsonObject, day: DayUi): Pair { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:324:63: error: unresolved reference: JsonObject +fun migrateLegacyItems(session: JsonObject, day: DayUi): Pair { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:334:37: error: unresolved reference: JsonObject + return session.updated("items", JsonObject(items)) to changed + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:337:22: error: unresolved reference: JsonObject +fun itemFor(session: JsonObject, exerciseId: String): JsonObject = session.obj("items")[exerciseId] as? JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:337:55: error: unresolved reference: JsonObject +fun itemFor(session: JsonObject, exerciseId: String): JsonObject = session.obj("items")[exerciseId] as? JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:337:105: error: unresolved reference: JsonObject +fun itemFor(session: JsonObject, exerciseId: String): JsonObject = session.obj("items")[exerciseId] as? JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:338:8: error: unresolved reference: buildJsonObject + ?: buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:339:9: error: unresolved reference: put + put("completion_status", "planned") + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:340:9: error: unresolved reference: put + put("done", false) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:341:9: error: unresolved reference: put + put("progression", "") + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:342:9: error: unresolved reference: put + put("result", "") + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:343:9: error: unresolved reference: put + put("note", "") + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:346:25: error: unresolved reference: JsonObject +fun updateItem(session: JsonObject, exerciseId: String, item: JsonObject): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:346:63: error: unresolved reference: JsonObject +fun updateItem(session: JsonObject, exerciseId: String, item: JsonObject): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:346:76: error: unresolved reference: JsonObject +fun updateItem(session: JsonObject, exerciseId: String, item: JsonObject): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:347:61: error: unresolved reference: put + val items = session.obj("items").toMutableMap().apply { put(exerciseId, item) } + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:348:37: error: unresolved reference: JsonObject + return session.updated("items", JsonObject(items)) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:351:35: error: unresolved reference: JsonObject +fun updateTrackerSession(tracker: JsonObject, key: String, session: JsonObject): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:351:69: error: unresolved reference: JsonObject +fun updateTrackerSession(tracker: JsonObject, key: String, session: JsonObject): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:351:82: error: unresolved reference: JsonObject +fun updateTrackerSession(tracker: JsonObject, key: String, session: JsonObject): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:352:67: error: unresolved reference: put + val sessions = tracker.obj("sessions").toMutableMap().apply { put(key, session) } + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:353:40: error: unresolved reference: JsonObject + return tracker.updated("sessions", JsonObject(sessions)) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:356:27: error: unresolved reference: JsonObject +fun sessionPatch(tracker: JsonObject, key: String, session: JsonObject): JsonObject = buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:356:61: error: unresolved reference: JsonObject +fun sessionPatch(tracker: JsonObject, key: String, session: JsonObject): JsonObject = buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:356:74: error: unresolved reference: JsonObject +fun sessionPatch(tracker: JsonObject, key: String, session: JsonObject): JsonObject = buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:356:87: error: unresolved reference: buildJsonObject +fun sessionPatch(tracker: JsonObject, key: String, session: JsonObject): JsonObject = buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:357:5: error: unresolved reference: put + put("expected_revision", tracker.int("revision", 1)) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:358:5: error: unresolved reference: put + put("profile", tracker.obj("profile")) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:359:5: error: unresolved reference: put + put("week_statuses", tracker.obj("week_statuses")) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:360:5: error: unresolved reference: put + put("session_key", key) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:361:5: error: unresolved reference: put + put("session", session) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:364:33: error: unresolved reference: JsonObject +fun overlaySessionPatches(base: JsonObject, patches: List): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:364:59: error: unresolved reference: JsonObject +fun overlaySessionPatches(base: JsonObject, patches: List): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:364:73: error: unresolved reference: JsonObject +fun overlaySessionPatches(base: JsonObject, patches: List): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:366:23: error: cannot infer a type for this parameter. Please specify it explicitly. + patches.forEach { patch -> + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:367:70: error: unresolved reference: it + patch["profile"]?.let { tracker = tracker.updated("profile", it) } + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:368:82: error: unresolved reference: it + patch["week_statuses"]?.let { tracker = tracker.updated("week_statuses", it) } + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:370:44: error: unresolved reference: JsonObject + val session = patch["session"] as? JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:378:38: error: unresolved reference: JsonObject +fun adaptResultDataToSchema(current: JsonObject, schema: ResultSchemaUi): JsonObject? { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:378:75: error: unresolved reference: JsonObject +fun adaptResultDataToSchema(current: JsonObject, schema: ResultSchemaUi): JsonObject? { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:391:32: error: unresolved reference: JsonArray + .updated("values", JsonArray(emptyList())) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:392:37: error: unresolved reference: JsonArray + .updated("left_values", JsonArray(emptyList())) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:393:38: error: unresolved reference: JsonArray + .updated("right_values", JsonArray(emptyList())) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:400:46: error: unresolved reference: JsonObject +fun emptyResultData(schema: ResultSchemaUi): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:402:12: error: unresolved reference: buildJsonObject + return buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:403:9: error: unresolved reference: put + put("version", 2) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:404:9: error: unresolved reference: put + put("mode", schema.mode) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:405:9: error: unresolved reference: put + put("laterality", schema.laterality) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:406:9: error: unresolved reference: put + put("sides_mode", if (schema.laterality == "unilateral") schema.sidesMode else "same") + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:407:9: error: unresolved reference: put + put("sets", sets) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:408:9: error: unresolved reference: put + put("weight_kg", JsonNull) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:408:26: error: unresolved reference: JsonNull + put("weight_kg", JsonNull) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:409:9: error: unresolved reference: put + put("values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:409:23: error: unresolved reference: buildJsonArray + put("values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:409:55: error: unresolved reference: add + put("values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:409:59: error: unresolved reference: JsonNull + put("values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:410:9: error: unresolved reference: put + put("left_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:410:28: error: unresolved reference: buildJsonArray + put("left_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:410:60: error: unresolved reference: add + put("left_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:410:64: error: unresolved reference: JsonNull + put("left_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:411:9: error: unresolved reference: put + put("right_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:411:29: error: unresolved reference: buildJsonArray + put("right_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:411:61: error: unresolved reference: add + put("right_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:411:65: error: unresolved reference: JsonNull + put("right_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:415:32: error: unresolved reference: JsonObject +fun normalizedResultData(item: JsonObject, schema: ResultSchemaUi): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:415:69: error: unresolved reference: JsonObject +fun normalizedResultData(item: JsonObject, schema: ResultSchemaUi): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:416:46: error: unresolved reference: JsonObject + val structured = item["result_data"] as? JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:421:38: error: unresolved reference: JsonObject +private fun normalizeResultData(raw: JsonObject, schema: ResultSchemaUi): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:421:75: error: unresolved reference: JsonObject +private fun normalizeResultData(raw: JsonObject, schema: ResultSchemaUi): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:423:39: error: unresolved reference: JsonArray + fun normalizedArray(key: String): JsonArray { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:425:47: error: unresolved reference: JsonNull + while (values.size < sets) values.add(JsonNull) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:426:16: error: unresolved reference: JsonArray + return JsonArray(values) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:428:12: error: unresolved reference: buildJsonObject + return buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:429:9: error: unresolved reference: put + put("version", 2) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:430:9: error: unresolved reference: put + put("mode", raw.string("mode", schema.mode)) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:431:9: error: unresolved reference: put + put("laterality", raw.string("laterality", schema.laterality)) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:432:9: error: unresolved reference: put + put("sides_mode", raw.string("sides_mode", schema.sidesMode)) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:433:9: error: unresolved reference: put + put("sets", sets) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:434:9: error: unresolved reference: put + put("weight_kg", raw["weight_kg"] ?: JsonNull) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:434:46: error: unresolved reference: JsonNull + put("weight_kg", raw["weight_kg"] ?: JsonNull) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:435:9: error: unresolved reference: put + put("values", normalizedArray("values")) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:436:9: error: unresolved reference: put + put("left_values", normalizedArray("left_values")) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:437:9: error: unresolved reference: put + put("right_values", normalizedArray("right_values")) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:441:62: error: unresolved reference: JsonObject +fun parseLegacyResult(text: String, schema: ResultSchemaUi): JsonObject? { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:465:12: error: unresolved reference: buildJsonObject + return buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:466:9: error: unresolved reference: put + put("version", 2) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:467:9: error: unresolved reference: put + put("mode", mode) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:468:9: error: unresolved reference: put + put("laterality", schema.laterality) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:469:9: error: unresolved reference: put + put("sides_mode", schema.sidesMode) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:470:9: error: unresolved reference: put + put("sets", sets) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:471:9: error: unresolved reference: put + put("weight_kg", weight?.let(::JsonPrimitive) ?: JsonNull) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:471:40: error: unresolved reference: JsonPrimitive + put("weight_kg", weight?.let(::JsonPrimitive) ?: JsonNull) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:471:58: error: unresolved reference: JsonNull + put("weight_kg", weight?.let(::JsonPrimitive) ?: JsonNull) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:472:9: error: unresolved reference: put + put("values", JsonArray(values.map(::JsonPrimitive))) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:472:23: error: unresolved reference: JsonArray + put("values", JsonArray(values.map(::JsonPrimitive))) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:472:46: error: unresolved reference: JsonPrimitive + put("values", JsonArray(values.map(::JsonPrimitive))) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:473:9: error: unresolved reference: put + put("left_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:473:28: error: unresolved reference: buildJsonArray + put("left_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:473:60: error: unresolved reference: add + put("left_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:473:64: error: unresolved reference: JsonNull + put("left_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:474:9: error: unresolved reference: put + put("right_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:474:29: error: unresolved reference: buildJsonArray + put("right_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:474:61: error: unresolved reference: add + put("right_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:474:65: error: unresolved reference: JsonNull + put("right_values", buildJsonArray { repeat(sets) { add(JsonNull) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:478:28: error: unresolved reference: JsonObject +fun formatResultData(data: JsonObject): String { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:479:22: error: unresolved reference: JsonElement + fun num(element: JsonElement?): String = element?.jsonPrimitive?.doubleOrNull?.let { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:480:13: error: unresolved reference: it + if (it % 1.0 == 0.0) it.toInt().toString() else it.toString().trimEnd('0').trimEnd('.') + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:480:30: error: unresolved reference: it + if (it % 1.0 == 0.0) it.toInt().toString() else it.toString().trimEnd('0').trimEnd('.') + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:480:57: error: unresolved reference: it + if (it % 1.0 == 0.0) it.toInt().toString() else it.toString().trimEnd('0').trimEnd('.') + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:484:79: error: unresolved reference: JsonPrimitive + val weight = data["weight_kg"]?.jsonPrimitive?.doubleOrNull?.let { "${num(JsonPrimitive(it))} kg ? " }.orEmpty() + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:484:93: error: unresolved reference: it + val weight = data["weight_kg"]?.jsonPrimitive?.doubleOrNull?.let { "${num(JsonPrimitive(it))} kg ? " }.orEmpty() + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:488:70: error: unresolved reference: it + val left = data.array("left_values").joinToString("/") { num(it) } + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:489:72: error: unresolved reference: it + val right = data.array("right_values").joinToString("/") { num(it) } + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:492:67: error: unresolved reference: it + val values = data.array("values").joinToString("/") { num(it) } + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:497:31: error: unresolved reference: JsonObject +fun resultDataWithValue(data: JsonObject, field: String, index: Int, value: Double?): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:497:87: error: unresolved reference: JsonObject +fun resultDataWithValue(data: JsonObject, field: String, index: Int, value: Double?): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:499:41: error: unresolved reference: JsonNull + while (list.size <= index) list.add(JsonNull) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:500:32: error: unresolved reference: JsonPrimitive + list[index] = value?.let(::JsonPrimitive) ?: JsonNull + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:500:50: error: unresolved reference: JsonNull + list[index] = value?.let(::JsonPrimitive) ?: JsonNull + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:501:32: error: unresolved reference: JsonArray + return data.updated(field, JsonArray(list)) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:504:30: error: unresolved reference: JsonObject +fun resultDataWithSets(data: JsonObject, sets: Int): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:504:54: error: unresolved reference: JsonObject +fun resultDataWithSets(data: JsonObject, sets: Int): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:509:44: error: unresolved reference: JsonNull + while (list.size < count) list.add(JsonNull) + ^ +app/src/main/java/de/drazz/boehmitools/training/domain/JsonDomain.kt:510:40: error: unresolved reference: JsonArray + result = result.updated(field, JsonArray(list)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:3:8: error: unresolved reference: androidx +import androidx.compose.material.icons.Icons + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:4:8: error: unresolved reference: androidx +import androidx.compose.material.icons.filled.Edit + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:5:8: error: unresolved reference: androidx +import androidx.compose.material.icons.filled.FitnessCenter + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:6:8: error: unresolved reference: androidx +import androidx.compose.material.icons.filled.Settings + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:7:8: error: unresolved reference: androidx +import androidx.compose.material3.Icon + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:8:8: error: unresolved reference: androidx +import androidx.compose.material3.NavigationBar + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:9:8: error: unresolved reference: androidx +import androidx.compose.material3.NavigationBarItem + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:10:8: error: unresolved reference: androidx +import androidx.compose.material3.Scaffold + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:11:8: error: unresolved reference: androidx +import androidx.compose.material3.Text + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:12:8: error: unresolved reference: androidx +import androidx.compose.runtime.Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:13:8: error: unresolved reference: androidx +import androidx.compose.runtime.getValue + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:14:8: error: unresolved reference: androidx +import androidx.compose.ui.Modifier + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:15:8: error: unresolved reference: androidx +import androidx.navigation.NavDestination.Companion.hierarchy + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:16:8: error: unresolved reference: androidx +import androidx.navigation.compose.NavHost + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:17:8: error: unresolved reference: androidx +import androidx.navigation.compose.composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:18:8: error: unresolved reference: androidx +import androidx.navigation.compose.currentBackStackEntryAsState + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:19:8: error: unresolved reference: androidx +import androidx.navigation.compose.rememberNavController + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:25:84: error: unresolved reference: Composable +private data class AppDestination(val route: String, val label: String, val icon: @Composable () -> Unit) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:27:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:32:25: error: unresolved reference: rememberNavController + val navController = rememberNavController() + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:34:48: error: unresolved reference: Icon + AppDestination("tracker", "Tracker") { Icon(Icons.Default.FitnessCenter, null) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:34:53: error: unresolved reference: Icons + AppDestination("tracker", "Tracker") { Icon(Icons.Default.FitnessCenter, null) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:35:50: error: unresolved reference: Icon + AppDestination("editor", "Planeditor") { Icon(Icons.Default.Edit, null) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:35:55: error: unresolved reference: Icons + AppDestination("editor", "Planeditor") { Icon(Icons.Default.Edit, null) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:36:55: error: unresolved reference: Icon + AppDestination("settings", "Einstellungen") { Icon(Icons.Default.Settings, null) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:36:60: error: unresolved reference: Icons + AppDestination("settings", "Einstellungen") { Icon(Icons.Default.Settings, null) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:39:5: error: unresolved reference: Scaffold + Scaffold( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:41:13: error: unresolved reference: NavigationBar + NavigationBar { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:43:77: error: unresolved reference: it + val selected = backStack?.destination?.hierarchy?.any { it.route == destination.route } == true + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:44:21: error: unresolved reference: NavigationBarItem + NavigationBarItem( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:48:33: error: unresolved reference: popUpTo + popUpTo("tracker") { saveState = true } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:48:54: error: unresolved reference: saveState + popUpTo("tracker") { saveState = true } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:49:33: error: unresolved reference: launchSingleTop + launchSingleTop = true + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:50:33: error: unresolved reference: restoreState + restoreState = true + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:54:35: error: unresolved reference: Text + label = { Text(destination.label) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:59:9: error: cannot infer a type for this parameter. Please specify it explicitly. + ) { padding -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:60:9: error: unresolved reference: NavHost + NavHost(navController = navController, startDestination = "tracker", modifier = Modifier) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:60:89: error: unresolved reference: Modifier + NavHost(navController = navController, startDestination = "tracker", modifier = Modifier) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:61:13: error: unresolved reference: composable + composable("tracker") { TrackerScreen(contentPadding = padding) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:62:13: error: unresolved reference: composable + composable("editor") { EditorScreen(contentPadding = padding) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/App.kt:63:13: error: unresolved reference: composable + composable("settings") { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt:3:8: error: unresolved reference: androidx +import androidx.lifecycle.ViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt:4:8: error: unresolved reference: androidx +import androidx.lifecycle.viewModelScope + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt:5:8: error: unresolved reference: dagger +import dagger.hilt.android.lifecycle.HiltViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt:7:14: error: unresolved reference: inject +import javax.inject.Inject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt:8:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.SharingStarted + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt:9:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.stateIn + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt:10:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.launch + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt:12:2: error: unresolved reference: HiltViewModel +@HiltViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt:13:21: error: unresolved reference: Inject +class AppViewModel @Inject constructor( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt:15:5: error: unresolved reference: ViewModel +) : ViewModel() { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt:17:9: error: unresolved reference: viewModelScope + viewModelScope, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt:18:9: error: unresolved reference: SharingStarted + SharingStarted.WhileSubscribed(5_000), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt:28:9: error: unresolved reference: viewModelScope + viewModelScope.launch { settingsRepository.setTheme(theme) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/AppViewModel.kt:28:52: error: suspend function 'setTheme' should be called only from a coroutine or another suspend function + viewModelScope.launch { settingsRepository.setTheme(theme) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:3:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.Arrangement + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:4:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.Box + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:5:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.Column + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:6:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.PaddingValues + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:7:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.Row + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:8:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.Spacer + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:9:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.fillMaxSize + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:10:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.fillMaxWidth + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:11:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.height + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:12:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.padding + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:13:8: error: unresolved reference: androidx +import androidx.compose.foundation.rememberScrollState + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:14:8: error: unresolved reference: androidx +import androidx.compose.foundation.verticalScroll + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:15:8: error: unresolved reference: androidx +import androidx.compose.material.icons.Icons + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:16:8: error: unresolved reference: androidx +import androidx.compose.material.icons.filled.Add + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:17:8: error: unresolved reference: androidx +import androidx.compose.material.icons.filled.Delete + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:18:8: error: unresolved reference: androidx +import androidx.compose.material.icons.filled.MoreVert + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:19:8: error: unresolved reference: androidx +import androidx.compose.material3.AlertDialog + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:20:8: error: unresolved reference: androidx +import androidx.compose.material3.AssistChip + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:21:8: error: unresolved reference: androidx +import androidx.compose.material3.Button + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:22:8: error: unresolved reference: androidx +import androidx.compose.material3.Card + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:23:8: error: unresolved reference: androidx +import androidx.compose.material3.Checkbox + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:24:8: error: unresolved reference: androidx +import androidx.compose.material3.CircularProgressIndicator + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:25:8: error: unresolved reference: androidx +import androidx.compose.material3.DropdownMenu + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:26:8: error: unresolved reference: androidx +import androidx.compose.material3.DropdownMenuItem + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:27:8: error: unresolved reference: androidx +import androidx.compose.material3.ExperimentalMaterial3Api + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:28:8: error: unresolved reference: androidx +import androidx.compose.material3.FilterChip + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:29:8: error: unresolved reference: androidx +import androidx.compose.material3.Icon + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:30:8: error: unresolved reference: androidx +import androidx.compose.material3.IconButton + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:31:8: error: unresolved reference: androidx +import androidx.compose.material3.MaterialTheme + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:32:8: error: unresolved reference: androidx +import androidx.compose.material3.OutlinedButton + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:33:8: error: unresolved reference: androidx +import androidx.compose.material3.OutlinedCard + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:34:8: error: unresolved reference: androidx +import androidx.compose.material3.OutlinedTextField + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:35:8: error: unresolved reference: androidx +import androidx.compose.material3.Scaffold + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:36:8: error: unresolved reference: androidx +import androidx.compose.material3.ScrollableTabRow + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:37:8: error: unresolved reference: androidx +import androidx.compose.material3.Tab + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:38:8: error: unresolved reference: androidx +import androidx.compose.material3.Text + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:39:8: error: unresolved reference: androidx +import androidx.compose.material3.TextButton + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:40:8: error: unresolved reference: androidx +import androidx.compose.material3.TopAppBar + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:41:8: error: unresolved reference: androidx +import androidx.compose.runtime.Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:42:8: error: unresolved reference: androidx +import androidx.compose.runtime.getValue + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:43:8: error: unresolved reference: androidx +import androidx.compose.runtime.mutableIntStateOf + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:44:8: error: unresolved reference: androidx +import androidx.compose.runtime.mutableStateOf + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:45:8: error: unresolved reference: androidx +import androidx.compose.runtime.remember + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:46:8: error: unresolved reference: androidx +import androidx.compose.runtime.setValue + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:47:8: error: unresolved reference: androidx +import androidx.compose.ui.Modifier + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:48:8: error: unresolved reference: androidx +import androidx.compose.ui.platform.testTag + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:49:8: error: unresolved reference: androidx +import androidx.compose.ui.text.font.FontWeight + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:50:8: error: unresolved reference: androidx +import androidx.compose.ui.unit.dp + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:51:8: error: unresolved reference: androidx +import androidx.hilt.navigation.compose.hiltViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:52:8: error: unresolved reference: androidx +import androidx.lifecycle.compose.collectAsStateWithLifecycle + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:61:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:62:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonPrimitive + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:66:8: error: unresolved reference: ExperimentalMaterial3Api +@OptIn(ExperimentalMaterial3Api::class) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:66:8: error: an annotation argument must be a compile-time constant +@OptIn(ExperimentalMaterial3Api::class) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:67:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:69:21: error: unresolved reference: PaddingValues + contentPadding: PaddingValues, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:70:34: error: unresolved reference: hiltViewModel + viewModel: EditorViewModel = hiltViewModel(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:73:24: error: unresolved reference: remember + var selectedTab by remember { mutableIntStateOf(0) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:73:35: error: unresolved reference: mutableIntStateOf + var selectedTab by remember { mutableIntStateOf(0) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:74:21: error: unresolved reference: remember + var planMenu by remember { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:74:32: error: unresolved reference: mutableStateOf + var planMenu by remember { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:75:25: error: unresolved reference: remember + var createDialog by remember { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:75:36: error: unresolved reference: mutableStateOf + var createDialog by remember { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:76:25: error: unresolved reference: remember + var renameDialog by remember { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:76:36: error: unresolved reference: mutableStateOf + var renameDialog by remember { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:77:25: error: unresolved reference: remember + var deleteDialog by remember { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:77:36: error: unresolved reference: mutableStateOf + var deleteDialog by remember { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:79:27: error: cannot infer a type for this parameter. Please specify it explicitly. + state.conflict?.let { conflict -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:80:9: error: unresolved reference: AlertDialog + AlertDialog( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:82:23: error: unresolved reference: Text + title = { Text("Revisionskonflikt") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:83:22: error: unresolved reference: Text + text = { Text("Der Serverplan liegt inzwischen bei Revision ${conflict.currentRevision}. Deine ?nderungen wurden nicht ?berschrieben.") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:84:31: error: unresolved reference: TextButton + confirmButton = { TextButton(onClick = viewModel::reloadAfterConflict) { Text("Serverstand neu laden") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:84:86: error: unresolved reference: Text + confirmButton = { TextButton(onClick = viewModel::reloadAfterConflict) { Text("Serverstand neu laden") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:85:31: error: unresolved reference: TextButton + dismissButton = { TextButton(onClick = viewModel::dismissConflict) { Text("Bearbeitung behalten") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:85:82: error: unresolved reference: Text + dismissButton = { TextButton(onClick = viewModel::dismissConflict) { Text("Bearbeitung behalten") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:103:23: error: unresolved reference: AlertDialog + if (deleteDialog) AlertDialog( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:105:19: error: unresolved reference: Text + title = { Text("Plan l?schen?") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:106:18: error: unresolved reference: Text + text = { Text("Der Server legt vor dem L?schen ein Backup an. Der letzte verbleibende Plan kann nicht gel?scht werden.") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:107:27: error: unresolved reference: TextButton + confirmButton = { TextButton(onClick = { deleteDialog = false; viewModel.deletePlan() }) { Text("L?schen") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:107:100: error: unresolved reference: Text + confirmButton = { TextButton(onClick = { deleteDialog = false; viewModel.deletePlan() }) { Text("L?schen") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:108:27: error: unresolved reference: TextButton + dismissButton = { TextButton(onClick = { deleteDialog = false }) { Text("Abbrechen") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:108:76: error: unresolved reference: Text + dismissButton = { TextButton(onClick = { deleteDialog = false }) { Text("Abbrechen") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:111:5: error: unresolved reference: Scaffold + Scaffold( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:112:20: error: unresolved reference: Modifier + modifier = Modifier.padding(contentPadding), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:114:13: error: unresolved reference: TopAppBar + TopAppBar( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:116:21: error: unresolved reference: Box + Box { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:117:25: error: unresolved reference: TextButton + TextButton(onClick = { planMenu = true }) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:118:29: error: unresolved reference: Text + Text(state.wrapper.string("name", "Planeditor"), maxLines = 2) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:120:25: error: unresolved reference: DropdownMenu + DropdownMenu(expanded = planMenu, onDismissRequest = { planMenu = false }) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:121:41: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + state.plans.forEach { plan -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:121:51: error: cannot infer a type for this parameter. Please specify it explicitly. + state.plans.forEach { plan -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:122:33: error: unresolved reference: DropdownMenuItem + DropdownMenuItem( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:123:46: error: unresolved reference: Text + text = { Text("${plan.name}${if (plan.hasUnpublishedChanges) " ?" else ""}") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:131:21: error: unresolved reference: IconButton + IconButton(onClick = { createDialog = true }) { Icon(Icons.Default.Add, "Plan anlegen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:131:69: error: unresolved reference: Icon + IconButton(onClick = { createDialog = true }) { Icon(Icons.Default.Add, "Plan anlegen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:131:74: error: unresolved reference: Icons + IconButton(onClick = { createDialog = true }) { Icon(Icons.Default.Add, "Plan anlegen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:132:21: error: unresolved reference: Box + Box { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:133:25: error: unresolved reference: IconButton + IconButton(onClick = { planMenu = true }) { Icon(Icons.Default.MoreVert, "Planaktionen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:133:69: error: unresolved reference: Icon + IconButton(onClick = { planMenu = true }) { Icon(Icons.Default.MoreVert, "Planaktionen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:133:74: error: unresolved reference: Icons + IconButton(onClick = { planMenu = true }) { Icon(Icons.Default.MoreVert, "Planaktionen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:138:9: error: cannot infer a type for this parameter. Please specify it explicitly. + ) { inner -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:139:9: error: unresolved reference: Column + Column(Modifier.fillMaxSize().padding(inner)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:139:16: error: unresolved reference: Modifier + Column(Modifier.fillMaxSize().padding(inner)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:140:13: error: unresolved reference: ScrollableTabRow + ScrollableTabRow(selectedTabIndex = selectedTab, edgePadding = 8.dp) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:140:78: error: unresolved reference: dp + ScrollableTabRow(selectedTabIndex = selectedTab, edgePadding = 8.dp) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:142:21: error: unresolved reference: Tab + Tab(selected = index == selectedTab, onClick = { selectedTab = index }, text = { Text(title) }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:142:102: error: unresolved reference: Text + Tab(selected = index == selectedTab, onClick = { selectedTab = index }, text = { Text(title) }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:146:17: error: unresolved reference: Box + Box(Modifier.fillMaxSize().padding(32.dp)) { CircularProgressIndicator() } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:146:21: error: unresolved reference: Modifier + Box(Modifier.fillMaxSize().padding(32.dp)) { CircularProgressIndicator() } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:146:55: error: unresolved reference: dp + Box(Modifier.fillMaxSize().padding(32.dp)) { CircularProgressIndicator() } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:146:62: error: unresolved reference: CircularProgressIndicator + Box(Modifier.fillMaxSize().padding(32.dp)) { CircularProgressIndicator() } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:150:17: error: unresolved reference: Column + Column(Modifier.fillMaxSize()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:150:24: error: unresolved reference: Modifier + Column(Modifier.fillMaxSize()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:166:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:168:5: error: unresolved reference: Column + Column(Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 6.dp), verticalArrangement = Arrangement.spacedBy(3.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:168:12: error: unresolved reference: Modifier + Column(Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 6.dp), verticalArrangement = Arrangement.spacedBy(3.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:168:60: error: unresolved reference: dp + Column(Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 6.dp), verticalArrangement = Arrangement.spacedBy(3.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:168:77: error: unresolved reference: dp + Column(Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 6.dp), verticalArrangement = Arrangement.spacedBy(3.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:168:104: error: unresolved reference: Arrangement + Column(Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 6.dp), verticalArrangement = Arrangement.spacedBy(3.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:168:127: error: unresolved reference: dp + Column(Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 6.dp), verticalArrangement = Arrangement.spacedBy(3.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:169:9: error: unresolved reference: Text + Text( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:171:21: error: unresolved reference: MaterialTheme + style = MaterialTheme.typography.bodySmall, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:173:9: error: 'when' expression must be exhaustive, add necessary 'else' branch + when { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:174:41: error: unresolved reference: Text + state.error.isNotBlank() -> Text(state.error, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:174:67: error: unresolved reference: MaterialTheme + state.error.isNotBlank() -> Text(state.error, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:174:108: error: unresolved reference: MaterialTheme + state.error.isNotBlank() -> Text(state.error, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:175:43: error: unresolved reference: Text + state.message.isNotBlank() -> Text(state.message, style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:175:71: error: unresolved reference: MaterialTheme + state.message.isNotBlank() -> Text(state.message, style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:180:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:182:5: error: unresolved reference: Column + Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:182:12: error: unresolved reference: Modifier + Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:182:46: error: unresolved reference: dp + Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:182:73: error: unresolved reference: Arrangement + Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:182:97: error: unresolved reference: dp + Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:183:9: error: unresolved reference: Text + Text("Noch kein Trainingsplan verf?gbar.", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:183:60: error: unresolved reference: MaterialTheme + Text("Noch kein Trainingsplan verf?gbar.", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:184:9: error: unresolved reference: Button + Button(onClick = onCreate) { Text("Plan anlegen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:184:38: error: unresolved reference: Text + Button(onClick = onCreate) { Text("Plan anlegen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:188:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:191:5: error: unresolved reference: Column + Column( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:192:9: error: unresolved reference: Modifier + Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:192:47: error: unresolved reference: rememberScrollState + Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:192:81: error: unresolved reference: dp + Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:193:31: error: unresolved reference: Arrangement + verticalArrangement = Arrangement.spacedBy(12.dp), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:193:55: error: unresolved reference: dp + verticalArrangement = Arrangement.spacedBy(12.dp), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:195:9: error: unresolved reference: Text + Text("Plan und Entwurf", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:195:42: error: unresolved reference: MaterialTheme + Text("Plan und Entwurf", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:196:9: error: unresolved reference: OutlinedTextField + OutlinedTextField(meta.string("title"), { vm.updateMeta("title", it) }, Modifier.fillMaxWidth(), label = { Text("Titel") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:196:74: error: unresolved reference: it + OutlinedTextField(meta.string("title"), { vm.updateMeta("title", it) }, Modifier.fillMaxWidth(), label = { Text("Titel") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:196:81: error: unresolved reference: Modifier + OutlinedTextField(meta.string("title"), { vm.updateMeta("title", it) }, Modifier.fillMaxWidth(), label = { Text("Titel") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:196:116: error: unresolved reference: Text + OutlinedTextField(meta.string("title"), { vm.updateMeta("title", it) }, Modifier.fillMaxWidth(), label = { Text("Titel") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:197:9: error: unresolved reference: OutlinedTextField + OutlinedTextField(meta.string("subtitle"), { vm.updateMeta("subtitle", it) }, Modifier.fillMaxWidth(), label = { Text("Untertitel") }, minLines = 2) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:197:80: error: unresolved reference: it + OutlinedTextField(meta.string("subtitle"), { vm.updateMeta("subtitle", it) }, Modifier.fillMaxWidth(), label = { Text("Untertitel") }, minLines = 2) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:197:87: error: unresolved reference: Modifier + OutlinedTextField(meta.string("subtitle"), { vm.updateMeta("subtitle", it) }, Modifier.fillMaxWidth(), label = { Text("Untertitel") }, minLines = 2) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:197:122: error: unresolved reference: Text + OutlinedTextField(meta.string("subtitle"), { vm.updateMeta("subtitle", it) }, Modifier.fillMaxWidth(), label = { Text("Untertitel") }, minLines = 2) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:198:9: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:200:31: error: unresolved reference: it + onValueChange = { it.toIntOrNull()?.let(vm::updateWeeks) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:201:24: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:202:23: error: unresolved reference: Text + label = { Text("Wochen") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:205:9: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:205:14: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:206:13: error: unresolved reference: Column + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:206:20: error: unresolved reference: Modifier + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:206:40: error: unresolved reference: dp + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:206:67: error: unresolved reference: Arrangement + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:206:90: error: unresolved reference: dp + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:207:17: error: unresolved reference: Text + Text("Stabile Identit?t", fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:207:56: error: unresolved reference: FontWeight + Text("Stabile Identit?t", fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:208:17: error: unresolved reference: Text + Text("Plan-ID: ${state.wrapper.string("plan_id", state.selectedPlanId)}") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:209:17: error: unresolved reference: Text + Text("Datei-ID: ${state.selectedPlanId}") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:210:17: error: unresolved reference: Text + Text("Vorhandene Sessions: ${state.wrapper.int("tracked_sessions")}") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:213:9: error: unresolved reference: Row + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:213:13: error: unresolved reference: Modifier + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:213:62: error: unresolved reference: Arrangement + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:213:85: error: unresolved reference: dp + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:214:13: error: unresolved reference: Button + Button(onClick = vm::save, enabled = !state.saving && state.dirty, modifier = Modifier.weight(1f)) { Text("Entwurf speichern") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:214:91: error: unresolved reference: Modifier + Button(onClick = vm::save, enabled = !state.saving && state.dirty, modifier = Modifier.weight(1f)) { Text("Entwurf speichern") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:214:114: error: unresolved reference: Text + Button(onClick = vm::save, enabled = !state.saving && state.dirty, modifier = Modifier.weight(1f)) { Text("Entwurf speichern") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:215:13: error: unresolved reference: OutlinedButton + OutlinedButton(onClick = vm::validate, enabled = !state.saving, modifier = Modifier.weight(1f)) { Text("Pr?fen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:215:88: error: unresolved reference: Modifier + OutlinedButton(onClick = vm::validate, enabled = !state.saving, modifier = Modifier.weight(1f)) { Text("Pr?fen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:215:111: error: unresolved reference: Text + OutlinedButton(onClick = vm::validate, enabled = !state.saving, modifier = Modifier.weight(1f)) { Text("Pr?fen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:217:9: error: unresolved reference: Button + Button(onClick = vm::publish, enabled = !state.saving && !state.dirty, modifier = Modifier.fillMaxWidth()) { Text("Entwurf ver?ffentlichen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:217:91: error: unresolved reference: Modifier + Button(onClick = vm::publish, enabled = !state.saving && !state.dirty, modifier = Modifier.fillMaxWidth()) { Text("Entwurf ver?ffentlichen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:217:118: error: unresolved reference: Text + Button(onClick = vm::publish, enabled = !state.saving && !state.dirty, modifier = Modifier.fillMaxWidth()) { Text("Entwurf ver?ffentlichen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:218:9: error: unresolved reference: OutlinedButton + OutlinedButton(onClick = onRename, modifier = Modifier.fillMaxWidth()) { Text("Plan umbenennen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:218:55: error: unresolved reference: Modifier + OutlinedButton(onClick = onRename, modifier = Modifier.fillMaxWidth()) { Text("Plan umbenennen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:218:82: error: unresolved reference: Text + OutlinedButton(onClick = onRename, modifier = Modifier.fillMaxWidth()) { Text("Plan umbenennen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:219:9: error: unresolved reference: TextButton + TextButton(onClick = onDelete, modifier = Modifier.fillMaxWidth()) { Text("Plan l?schen", color = MaterialTheme.colorScheme.error) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:219:51: error: unresolved reference: Modifier + TextButton(onClick = onDelete, modifier = Modifier.fillMaxWidth()) { Text("Plan l?schen", color = MaterialTheme.colorScheme.error) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:219:78: error: unresolved reference: Text + TextButton(onClick = onDelete, modifier = Modifier.fillMaxWidth()) { Text("Plan l?schen", color = MaterialTheme.colorScheme.error) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:219:107: error: unresolved reference: MaterialTheme + TextButton(onClick = onDelete, modifier = Modifier.fillMaxWidth()) { Text("Plan l?schen", color = MaterialTheme.colorScheme.error) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:220:9: error: unresolved reference: Spacer + Spacer(Modifier.height(24.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:220:16: error: unresolved reference: Modifier + Spacer(Modifier.height(24.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:220:35: error: unresolved reference: dp + Spacer(Modifier.height(24.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:224:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:228:5: error: unresolved reference: Column + Column( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:229:9: error: unresolved reference: Modifier + Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp).testTag("editor-days"), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:229:47: error: unresolved reference: rememberScrollState + Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp).testTag("editor-days"), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:229:81: error: unresolved reference: dp + Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp).testTag("editor-days"), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:230:31: error: unresolved reference: Arrangement + verticalArrangement = Arrangement.spacedBy(12.dp), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:230:55: error: unresolved reference: dp + verticalArrangement = Arrangement.spacedBy(12.dp), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:232:9: error: unresolved reference: Text + Text("Trainingstage", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:232:39: error: unresolved reference: MaterialTheme + Text("Trainingstage", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:233:49: error: unresolved reference: it + state.config.array("days").mapNotNull { it as? JsonObject }.forEach { day -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:233:56: error: unresolved reference: JsonObject + state.config.array("days").mapNotNull { it as? JsonObject }.forEach { day -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:233:69: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + state.config.array("days").mapNotNull { it as? JsonObject }.forEach { day -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:233:79: error: cannot infer a type for this parameter. Please specify it explicitly. + state.config.array("days").mapNotNull { it as? JsonObject }.forEach { day -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:236:9: error: unresolved reference: OutlinedButton + OutlinedButton(onClick = vm::addDay, modifier = Modifier.fillMaxWidth()) { Icon(Icons.Default.Add, null); Text(" Tag hinzuf?gen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:236:57: error: unresolved reference: Modifier + OutlinedButton(onClick = vm::addDay, modifier = Modifier.fillMaxWidth()) { Icon(Icons.Default.Add, null); Text(" Tag hinzuf?gen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:236:84: error: unresolved reference: Icon + OutlinedButton(onClick = vm::addDay, modifier = Modifier.fillMaxWidth()) { Icon(Icons.Default.Add, null); Text(" Tag hinzuf?gen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:236:89: error: unresolved reference: Icons + OutlinedButton(onClick = vm::addDay, modifier = Modifier.fillMaxWidth()) { Icon(Icons.Default.Add, null); Text(" Tag hinzuf?gen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:236:115: error: unresolved reference: Text + OutlinedButton(onClick = vm::addDay, modifier = Modifier.fillMaxWidth()) { Icon(Icons.Default.Add, null); Text(" Tag hinzuf?gen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:237:9: error: unresolved reference: Spacer + Spacer(Modifier.height(80.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:237:16: error: unresolved reference: Modifier + Spacer(Modifier.height(80.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:237:35: error: unresolved reference: dp + Spacer(Modifier.height(80.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:241:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:242:28: error: unresolved reference: JsonObject +private fun DayEditor(day: JsonObject, prepost: JsonObject, vm: EditorViewModel, highlightExerciseId: String) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:242:49: error: unresolved reference: JsonObject +private fun DayEditor(day: JsonObject, prepost: JsonObject, vm: EditorViewModel, highlightExerciseId: String) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:244:5: error: unresolved reference: OutlinedCard + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:244:18: error: unresolved reference: Modifier + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:245:9: error: unresolved reference: Column + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:245:16: error: unresolved reference: Modifier + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:245:36: error: unresolved reference: dp + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:245:63: error: unresolved reference: Arrangement + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:245:87: error: unresolved reference: dp + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:246:13: error: unresolved reference: Row + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:246:17: error: unresolved reference: Modifier + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:246:66: error: unresolved reference: Arrangement + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:247:17: error: unresolved reference: Text + Text("Tag ${day.int("num", 1)}", style = MaterialTheme.typography.titleLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:247:58: error: unresolved reference: MaterialTheme + Text("Tag ${day.int("num", 1)}", style = MaterialTheme.typography.titleLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:248:17: error: unresolved reference: IconButton + IconButton(onClick = { vm.removeDay(dayId) }) { Icon(Icons.Default.Delete, "Tag l?schen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:248:65: error: unresolved reference: Icon + IconButton(onClick = { vm.removeDay(dayId) }) { Icon(Icons.Default.Delete, "Tag l?schen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:248:70: error: unresolved reference: Icons + IconButton(onClick = { vm.removeDay(dayId) }) { Icon(Icons.Default.Delete, "Tag l?schen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:250:13: error: unresolved reference: OutlinedTextField + OutlinedTextField(day.string("focus"), { value -> vm.updateDay(dayId) { it.updatedString("focus", value) } }, Modifier.fillMaxWidth(), label = { Text("Fokus") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:250:54: error: cannot infer a type for this parameter. Please specify it explicitly. + OutlinedTextField(day.string("focus"), { value -> vm.updateDay(dayId) { it.updatedString("focus", value) } }, Modifier.fillMaxWidth(), label = { Text("Fokus") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:250:123: error: unresolved reference: Modifier + OutlinedTextField(day.string("focus"), { value -> vm.updateDay(dayId) { it.updatedString("focus", value) } }, Modifier.fillMaxWidth(), label = { Text("Fokus") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:250:158: error: unresolved reference: Text + OutlinedTextField(day.string("focus"), { value -> vm.updateDay(dayId) { it.updatedString("focus", value) } }, Modifier.fillMaxWidth(), label = { Text("Fokus") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:251:13: error: unresolved reference: Row + Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:251:37: error: unresolved reference: androidx + Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:252:17: error: unresolved reference: Checkbox + Checkbox(day.bool("light"), { value -> vm.updateDay(dayId) { it.updatedBool("light", value) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:252:47: error: cannot infer a type for this parameter. Please specify it explicitly. + Checkbox(day.bool("light"), { value -> vm.updateDay(dayId) { it.updatedBool("light", value) } }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:253:17: error: unresolved reference: Text + Text("Leichter Trainingstag") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:255:13: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:257:62: error: unresolved reference: it + { vm.updatePrePost(day.int("num"), "warmup", it) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:258:17: error: unresolved reference: Modifier + Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:259:27: error: unresolved reference: Text + label = { Text("Warm-up") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:262:13: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:264:64: error: unresolved reference: it + { vm.updatePrePost(day.int("num"), "cooldown", it) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:265:17: error: unresolved reference: Modifier + Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:266:27: error: unresolved reference: Text + label = { Text("Cool-down") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:269:13: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:271:63: error: unresolved reference: it + { vm.updatePrePost(day.int("num"), "stretch", it) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:272:17: error: unresolved reference: Modifier + Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:273:27: error: unresolved reference: Text + label = { Text("Stretching") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:276:49: error: unresolved reference: it + day.array("rotations").mapNotNull { it as? JsonObject }.forEach { rotation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:276:56: error: unresolved reference: JsonObject + day.array("rotations").mapNotNull { it as? JsonObject }.forEach { rotation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:276:69: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + day.array("rotations").mapNotNull { it as? JsonObject }.forEach { rotation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:276:79: error: cannot infer a type for this parameter. Please specify it explicitly. + day.array("rotations").mapNotNull { it as? JsonObject }.forEach { rotation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:279:13: error: unresolved reference: OutlinedButton + OutlinedButton(onClick = { vm.addRotation(dayId) }, modifier = Modifier.fillMaxWidth()) { Text("Block hinzuf?gen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:279:76: error: unresolved reference: Modifier + OutlinedButton(onClick = { vm.addRotation(dayId) }, modifier = Modifier.fillMaxWidth()) { Text("Block hinzuf?gen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:279:103: error: unresolved reference: Text + OutlinedButton(onClick = { vm.addRotation(dayId) }, modifier = Modifier.fillMaxWidth()) { Text("Block hinzuf?gen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:284:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:285:53: error: unresolved reference: JsonObject +private fun RotationEditor(dayId: String, rotation: JsonObject, vm: EditorViewModel, highlightExerciseId: String) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:287:5: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:287:10: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:288:9: error: unresolved reference: Column + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:288:16: error: unresolved reference: Modifier + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:288:36: error: unresolved reference: dp + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:288:63: error: unresolved reference: Arrangement + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:288:86: error: unresolved reference: dp + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:289:13: error: unresolved reference: Row + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:289:17: error: unresolved reference: Modifier + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:289:66: error: unresolved reference: Arrangement + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:289:89: error: unresolved reference: dp + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:290:17: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:292:23: error: cannot infer a type for this parameter. Please specify it explicitly. + { value -> vm.updateRotation(dayId, rotationId) { it.updatedString("label", value) } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:293:21: error: unresolved reference: Modifier + Modifier.weight(1f), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:294:31: error: unresolved reference: Text + label = { Text("Block") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:296:17: error: unresolved reference: IconButton + IconButton(onClick = { vm.removeRotation(dayId, rotationId) }) { Icon(Icons.Default.Delete, "Block l?schen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:296:82: error: unresolved reference: Icon + IconButton(onClick = { vm.removeRotation(dayId, rotationId) }) { Icon(Icons.Default.Delete, "Block l?schen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:296:87: error: unresolved reference: Icons + IconButton(onClick = { vm.removeRotation(dayId, rotationId) }) { Icon(Icons.Default.Delete, "Block l?schen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:298:54: error: unresolved reference: it + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:298:61: error: unresolved reference: JsonObject + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:298:74: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:298:84: error: cannot infer a type for this parameter. Please specify it explicitly. + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:301:13: error: unresolved reference: OutlinedButton + OutlinedButton(onClick = { vm.addExercise(dayId, rotationId) }, modifier = Modifier.fillMaxWidth()) { Text("?bung hinzuf?gen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:301:88: error: unresolved reference: Modifier + OutlinedButton(onClick = { vm.addExercise(dayId, rotationId) }, modifier = Modifier.fillMaxWidth()) { Text("?bung hinzuf?gen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:301:115: error: unresolved reference: Text + OutlinedButton(onClick = { vm.addExercise(dayId, rotationId) }, modifier = Modifier.fillMaxWidth()) { Text("?bung hinzuf?gen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:306:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:307:73: error: unresolved reference: JsonObject +private fun ExerciseEditor(dayId: String, rotationId: String, exercise: JsonObject, vm: EditorViewModel, highlighted: Boolean) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:309:5: error: unresolved reference: OutlinedCard + OutlinedCard(Modifier.fillMaxWidth().testTag("exercise-$placementId")) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:309:18: error: unresolved reference: Modifier + OutlinedCard(Modifier.fillMaxWidth().testTag("exercise-$placementId")) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:310:9: error: unresolved reference: Column + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:310:16: error: unresolved reference: Modifier + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:310:36: error: unresolved reference: dp + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:310:63: error: unresolved reference: Arrangement + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:310:86: error: unresolved reference: dp + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:311:30: error: unresolved reference: AssistChip + if (highlighted) AssistChip(onClick = {}, label = { Text("Ziel des ausgew?hlten Vorschlags") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:311:65: error: unresolved reference: Text + if (highlighted) AssistChip(onClick = {}, label = { Text("Ziel des ausgew?hlten Vorschlags") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:312:13: error: unresolved reference: Row + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:312:17: error: unresolved reference: Modifier + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:312:66: error: unresolved reference: Arrangement + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:313:17: error: unresolved reference: Text + Text(exercise.string("name", "?bung"), style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:313:64: error: unresolved reference: MaterialTheme + Text(exercise.string("name", "?bung"), style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:314:17: error: unresolved reference: IconButton + IconButton(onClick = { vm.removeExercise(dayId, rotationId, placementId) }) { Icon(Icons.Default.Delete, "?bung l?schen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:314:95: error: unresolved reference: Icon + IconButton(onClick = { vm.removeExercise(dayId, rotationId, placementId) }) { Icon(Icons.Default.Delete, "?bung l?schen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:314:100: error: unresolved reference: Icons + IconButton(onClick = { vm.removeExercise(dayId, rotationId, placementId) }) { Icon(Icons.Default.Delete, "?bung l?schen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:316:13: error: unresolved reference: OutlinedTextField + OutlinedTextField(exercise.string("name"), { value -> vm.updateExercise(dayId, rotationId, placementId) { it.updatedString("name", value) } }, Modifier.fillMaxWidth(), label = { Text("Name") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:316:58: error: cannot infer a type for this parameter. Please specify it explicitly. + OutlinedTextField(exercise.string("name"), { value -> vm.updateExercise(dayId, rotationId, placementId) { it.updatedString("name", value) } }, Modifier.fillMaxWidth(), label = { Text("Name") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:316:156: error: unresolved reference: Modifier + OutlinedTextField(exercise.string("name"), { value -> vm.updateExercise(dayId, rotationId, placementId) { it.updatedString("name", value) } }, Modifier.fillMaxWidth(), label = { Text("Name") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:316:191: error: unresolved reference: Text + OutlinedTextField(exercise.string("name"), { value -> vm.updateExercise(dayId, rotationId, placementId) { it.updatedString("name", value) } }, Modifier.fillMaxWidth(), label = { Text("Name") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:317:13: error: unresolved reference: OutlinedTextField + OutlinedTextField(exercise.string("cue"), { value -> vm.updateExercise(dayId, rotationId, placementId) { it.updatedString("cue", value) } }, Modifier.fillMaxWidth(), label = { Text("Hinweis") }, minLines = 2) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:317:57: error: cannot infer a type for this parameter. Please specify it explicitly. + OutlinedTextField(exercise.string("cue"), { value -> vm.updateExercise(dayId, rotationId, placementId) { it.updatedString("cue", value) } }, Modifier.fillMaxWidth(), label = { Text("Hinweis") }, minLines = 2) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:317:154: error: unresolved reference: Modifier + OutlinedTextField(exercise.string("cue"), { value -> vm.updateExercise(dayId, rotationId, placementId) { it.updatedString("cue", value) } }, Modifier.fillMaxWidth(), label = { Text("Hinweis") }, minLines = 2) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:317:189: error: unresolved reference: Text + OutlinedTextField(exercise.string("cue"), { value -> vm.updateExercise(dayId, rotationId, placementId) { it.updatedString("cue", value) } }, Modifier.fillMaxWidth(), label = { Text("Hinweis") }, minLines = 2) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:318:13: error: unresolved reference: OutlinedTextField + OutlinedTextField(exercise.string("exercise_id"), {}, Modifier.fillMaxWidth(), readOnly = true, label = { Text("Stabile Bewegungs-ID") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:318:67: error: unresolved reference: Modifier + OutlinedTextField(exercise.string("exercise_id"), {}, Modifier.fillMaxWidth(), readOnly = true, label = { Text("Stabile Bewegungs-ID") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:318:119: error: unresolved reference: Text + OutlinedTextField(exercise.string("exercise_id"), {}, Modifier.fillMaxWidth(), readOnly = true, label = { Text("Stabile Bewegungs-ID") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:319:13: error: unresolved reference: OutlinedTextField + OutlinedTextField(exercise.string("progression_id"), { value -> vm.updateExercise(dayId, rotationId, placementId) { it.updatedString("progression_id", value) } }, Modifier.fillMaxWidth(), label = { Text("Progressions-ID") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:319:68: error: cannot infer a type for this parameter. Please specify it explicitly. + OutlinedTextField(exercise.string("progression_id"), { value -> vm.updateExercise(dayId, rotationId, placementId) { it.updatedString("progression_id", value) } }, Modifier.fillMaxWidth(), label = { Text("Progressions-ID") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:319:176: error: unresolved reference: Modifier + OutlinedTextField(exercise.string("progression_id"), { value -> vm.updateExercise(dayId, rotationId, placementId) { it.updatedString("progression_id", value) } }, Modifier.fillMaxWidth(), label = { Text("Progressions-ID") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:319:211: error: unresolved reference: Text + OutlinedTextField(exercise.string("progression_id"), { value -> vm.updateExercise(dayId, rotationId, placementId) { it.updatedString("progression_id", value) } }, Modifier.fillMaxWidth(), label = { Text("Progressions-ID") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:322:36: error: cannot infer a type for this parameter. Please specify it explicitly. + onField = { field, value -> vm.updateExerciseSchema(dayId, rotationId, placementId, field, value) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:328:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:330:5: error: unresolved reference: Column + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:330:12: error: unresolved reference: Modifier + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:330:50: error: unresolved reference: rememberScrollState + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:330:84: error: unresolved reference: dp + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:330:111: error: unresolved reference: Arrangement + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:330:135: error: unresolved reference: dp + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:331:9: error: unresolved reference: Text + Text("Progressionsstufen", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:331:44: error: unresolved reference: MaterialTheme + Text("Progressionsstufen", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:332:36: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + state.config.obj("stages").forEach { (key, element) -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:332:46: error: cannot infer a type for this parameter. Please specify it explicitly. + state.config.obj("stages").forEach { (key, element) -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:333:37: error: unresolved reference: JsonObject + val stage = element as? JsonObject ?: return@forEach + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:334:13: error: unresolved reference: OutlinedCard + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:334:26: error: unresolved reference: Modifier + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:335:17: error: unresolved reference: Column + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:335:24: error: unresolved reference: Modifier + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:335:44: error: unresolved reference: dp + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:335:71: error: unresolved reference: Arrangement + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:335:95: error: unresolved reference: dp + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:336:21: error: unresolved reference: Text + Text(stage.string("name", key), style = MaterialTheme.typography.titleLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:336:61: error: unresolved reference: MaterialTheme + Text(stage.string("name", key), style = MaterialTheme.typography.titleLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:337:21: error: unresolved reference: Text + Text("ID: ${stage.string("id", key)} ? Schl?ssel: $key", style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:337:86: error: unresolved reference: MaterialTheme + Text("ID: ${stage.string("id", key)} ? Schl?ssel: $key", style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:338:55: error: unresolved reference: it + stage.array("steps").mapNotNull { it as? JsonObject }.forEach { step -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:338:62: error: unresolved reference: JsonObject + stage.array("steps").mapNotNull { it as? JsonObject }.forEach { step -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:338:75: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + stage.array("steps").mapNotNull { it as? JsonObject }.forEach { step -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:338:85: error: cannot infer a type for this parameter. Please specify it explicitly. + stage.array("steps").mapNotNull { it as? JsonObject }.forEach { step -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:340:25: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:340:30: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:341:29: error: unresolved reference: Column + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:341:36: error: unresolved reference: Modifier + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:341:56: error: unresolved reference: dp + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:341:83: error: unresolved reference: Arrangement + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:341:106: error: unresolved reference: dp + Column(Modifier.padding(10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:342:33: error: unresolved reference: OutlinedTextField + OutlinedTextField(step.string("name"), { value -> vm.updateProgressionStep(key, stepId) { it.updatedString("name", value) } }, Modifier.fillMaxWidth(), label = { Text("Stufe") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:342:74: error: cannot infer a type for this parameter. Please specify it explicitly. + OutlinedTextField(step.string("name"), { value -> vm.updateProgressionStep(key, stepId) { it.updatedString("name", value) } }, Modifier.fillMaxWidth(), label = { Text("Stufe") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:342:160: error: unresolved reference: Modifier + OutlinedTextField(step.string("name"), { value -> vm.updateProgressionStep(key, stepId) { it.updatedString("name", value) } }, Modifier.fillMaxWidth(), label = { Text("Stufe") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:342:195: error: unresolved reference: Text + OutlinedTextField(step.string("name"), { value -> vm.updateProgressionStep(key, stepId) { it.updatedString("name", value) } }, Modifier.fillMaxWidth(), label = { Text("Stufe") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:343:33: error: unresolved reference: OutlinedTextField + OutlinedTextField(step.string("phase_id"), { value -> vm.updateProgressionStep(key, stepId) { it.updatedString("phase_id", value) } }, Modifier.fillMaxWidth(), label = { Text("Phasen-ID") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:343:78: error: cannot infer a type for this parameter. Please specify it explicitly. + OutlinedTextField(step.string("phase_id"), { value -> vm.updateProgressionStep(key, stepId) { it.updatedString("phase_id", value) } }, Modifier.fillMaxWidth(), label = { Text("Phasen-ID") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:343:168: error: unresolved reference: Modifier + OutlinedTextField(step.string("phase_id"), { value -> vm.updateProgressionStep(key, stepId) { it.updatedString("phase_id", value) } }, Modifier.fillMaxWidth(), label = { Text("Phasen-ID") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:343:203: error: unresolved reference: Text + OutlinedTextField(step.string("phase_id"), { value -> vm.updateProgressionStep(key, stepId) { it.updatedString("phase_id", value) } }, Modifier.fillMaxWidth(), label = { Text("Phasen-ID") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:344:88: error: cannot infer a type for this parameter. Please specify it explicitly. + ResultSchemaEditor(step.obj("result_schema")) { field, value -> vm.updateStepSchema(key, stepId, field, value) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:348:21: error: unresolved reference: OutlinedButton + OutlinedButton(onClick = { vm.addProgressionStep(key) }, modifier = Modifier.fillMaxWidth()) { Text("Stufe hinzuf?gen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:348:89: error: unresolved reference: Modifier + OutlinedButton(onClick = { vm.addProgressionStep(key) }, modifier = Modifier.fillMaxWidth()) { Text("Stufe hinzuf?gen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:348:116: error: unresolved reference: Text + OutlinedButton(onClick = { vm.addProgressionStep(key) }, modifier = Modifier.fillMaxWidth()) { Text("Stufe hinzuf?gen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:352:9: error: unresolved reference: Spacer + Spacer(Modifier.height(80.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:352:16: error: unresolved reference: Modifier + Spacer(Modifier.height(80.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:352:35: error: unresolved reference: dp + Spacer(Modifier.height(80.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:356:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:357:40: error: unresolved reference: JsonObject +private fun ResultSchemaEditor(schema: JsonObject, onField: (String, JsonPrimitive) -> Unit) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:357:70: error: unresolved reference: JsonPrimitive +private fun ResultSchemaEditor(schema: JsonObject, onField: (String, JsonPrimitive) -> Unit) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:358:5: error: unresolved reference: Column + Column(verticalArrangement = Arrangement.spacedBy(7.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:358:34: error: unresolved reference: Arrangement + Column(verticalArrangement = Arrangement.spacedBy(7.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:358:57: error: unresolved reference: dp + Column(verticalArrangement = Arrangement.spacedBy(7.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:359:9: error: unresolved reference: Text + Text("Ergebnisschema", fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:359:45: error: unresolved reference: FontWeight + Text("Ergebnisschema", fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:360:123: error: unresolved reference: JsonPrimitive + ChoiceRow("Modus", listOf("reps", "seconds", "minutes", "none"), schema.string("mode", "reps")) { onField("mode", JsonPrimitive(it)) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:361:133: error: unresolved reference: JsonPrimitive + ChoiceRow("Gewicht", listOf("none", "optional", "required"), schema.string("weight_mode", "none")) { onField("weight_mode", JsonPrimitive(it)) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:362:130: error: unresolved reference: JsonPrimitive + ChoiceRow("Seiten", listOf("bilateral", "unilateral"), schema.string("laterality", "bilateral")) { onField("laterality", JsonPrimitive(it)) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:364:127: error: unresolved reference: JsonPrimitive + ChoiceRow("Seitenwerte", listOf("same", "separate"), schema.string("sides_mode", "same")) { onField("sides_mode", JsonPrimitive(it)) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:366:9: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:368:15: error: unresolved reference: it + { it.toIntOrNull()?.coerceIn(1, 20)?.let { count -> onField("sets", JsonPrimitive(count)) } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:368:56: error: cannot infer a type for this parameter. Please specify it explicitly. + { it.toIntOrNull()?.coerceIn(1, 20)?.let { count -> onField("sets", JsonPrimitive(count)) } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:368:81: error: unresolved reference: JsonPrimitive + { it.toIntOrNull()?.coerceIn(1, 20)?.let { count -> onField("sets", JsonPrimitive(count)) } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:369:13: error: unresolved reference: Modifier + Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:370:23: error: unresolved reference: Text + label = { Text("Standards?tze") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:373:9: error: unresolved reference: Row + Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:373:33: error: unresolved reference: androidx + Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:374:13: error: unresolved reference: Checkbox + Checkbox(schema.bool("locked_sets"), { onField("locked_sets", JsonPrimitive(it)) }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:374:75: error: unresolved reference: JsonPrimitive + Checkbox(schema.bool("locked_sets"), { onField("locked_sets", JsonPrimitive(it)) }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:374:89: error: unresolved reference: it + Checkbox(schema.bool("locked_sets"), { onField("locked_sets", JsonPrimitive(it)) }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:375:13: error: unresolved reference: Text + Text("Satzanzahl sperren") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:380:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:382:5: error: unresolved reference: Column + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:382:34: error: unresolved reference: Arrangement + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:382:57: error: unresolved reference: dp + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:383:9: error: unresolved reference: Text + Text(label, style = MaterialTheme.typography.labelMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:383:29: error: unresolved reference: MaterialTheme + Text(label, style = MaterialTheme.typography.labelMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:384:9: error: unresolved reference: Column + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:384:38: error: unresolved reference: Arrangement + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:384:61: error: unresolved reference: dp + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:386:17: error: unresolved reference: FilterChip + FilterChip( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:389:31: error: unresolved reference: Text + label = { Text(value) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:390:32: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:397:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:399:5: error: unresolved reference: Column + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:399:12: error: unresolved reference: Modifier + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:399:50: error: unresolved reference: rememberScrollState + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:399:84: error: unresolved reference: dp + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:399:111: error: unresolved reference: Arrangement + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:399:135: error: unresolved reference: dp + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:400:9: error: unresolved reference: Text + Text("?bungsbibliothek", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:400:42: error: unresolved reference: MaterialTheme + Text("?bungsbibliothek", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:401:9: error: unresolved reference: Text + Text("Die Bibliothek wird beim Speichern serverseitig aus stabilen Bewegungs- und Progressions-IDs neu aufgebaut.") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:403:32: error: unresolved reference: Text + if (catalog.isEmpty()) Text("Noch kein Katalog im Entwurf. Speichern oder validieren erzeugt ihn.") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:404:17: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + catalog.forEach { (id, element) -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:404:27: error: cannot infer a type for this parameter. Please specify it explicitly. + catalog.forEach { (id, element) -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:405:37: error: unresolved reference: JsonObject + val entry = element as? JsonObject ?: return@forEach + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:406:13: error: unresolved reference: OutlinedCard + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:406:26: error: unresolved reference: Modifier + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:407:17: error: unresolved reference: Column + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:407:24: error: unresolved reference: Modifier + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:407:44: error: unresolved reference: dp + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:407:71: error: unresolved reference: Arrangement + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:407:94: error: unresolved reference: dp + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:408:21: error: unresolved reference: Text + Text(entry.string("movement_label", entry.string("name", id)), fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:408:97: error: unresolved reference: FontWeight + Text(entry.string("movement_label", entry.string("name", id)), fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:409:21: error: unresolved reference: Text + Text(id, style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:409:38: error: unresolved reference: MaterialTheme + Text(id, style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:410:21: error: unresolved reference: Text + Text("Cluster: ${entry.string("movement_cluster", "general")}") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:411:21: error: unresolved reference: Text + Text("Varianten: ${entry.array("variants").size}") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:415:9: error: unresolved reference: Spacer + Spacer(Modifier.height(80.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:415:16: error: unresolved reference: Modifier + Spacer(Modifier.height(80.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:415:35: error: unresolved reference: dp + Spacer(Modifier.height(80.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:419:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:425:5: error: unresolved reference: Column + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:425:12: error: unresolved reference: Modifier + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:425:50: error: unresolved reference: rememberScrollState + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:425:84: error: unresolved reference: dp + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:425:111: error: unresolved reference: Arrangement + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:425:135: error: unresolved reference: dp + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:426:9: error: unresolved reference: Text + Text("Validierung und Vorschau", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:426:50: error: unresolved reference: MaterialTheme + Text("Validierung und Vorschau", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:427:9: error: unresolved reference: Row + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:427:13: error: unresolved reference: Modifier + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:427:62: error: unresolved reference: Arrangement + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:427:85: error: unresolved reference: dp + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:428:13: error: unresolved reference: Button + Button(onClick = vm::validate, enabled = !state.saving, modifier = Modifier.weight(1f)) { Text("Validieren") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:428:80: error: unresolved reference: Modifier + Button(onClick = vm::validate, enabled = !state.saving, modifier = Modifier.weight(1f)) { Text("Validieren") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:428:103: error: unresolved reference: Text + Button(onClick = vm::validate, enabled = !state.saving, modifier = Modifier.weight(1f)) { Text("Validieren") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:429:13: error: unresolved reference: OutlinedButton + OutlinedButton(onClick = vm::save, enabled = !state.saving && state.dirty, modifier = Modifier.weight(1f)) { Text("Speichern") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:429:99: error: unresolved reference: Modifier + OutlinedButton(onClick = vm::save, enabled = !state.saving && state.dirty, modifier = Modifier.weight(1f)) { Text("Speichern") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:429:122: error: unresolved reference: Text + OutlinedButton(onClick = vm::save, enabled = !state.saving && state.dirty, modifier = Modifier.weight(1f)) { Text("Speichern") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:431:35: error: unresolved reference: Text + if (validation.isEmpty()) Text("Noch keine Pr?fung f?r den aktuellen Entwurf ausgef?hrt.") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:432:16: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + errors.forEach { Text("Fehler: ${it.toString().trim('"')}", color = MaterialTheme.colorScheme.error) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:432:26: error: unresolved reference: Text + errors.forEach { Text("Fehler: ${it.toString().trim('"')}", color = MaterialTheme.colorScheme.error) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:432:42: error: unresolved reference: it + errors.forEach { Text("Fehler: ${it.toString().trim('"')}", color = MaterialTheme.colorScheme.error) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:432:77: error: unresolved reference: MaterialTheme + errors.forEach { Text("Fehler: ${it.toString().trim('"')}", color = MaterialTheme.colorScheme.error) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:433:18: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + warnings.forEach { Text("Warnung: ${it.toString().trim('"')}") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:433:28: error: unresolved reference: Text + warnings.forEach { Text("Warnung: ${it.toString().trim('"')}") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:433:45: error: unresolved reference: it + warnings.forEach { Text("Warnung: ${it.toString().trim('"')}") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:434:58: error: unresolved reference: AssistChip + if (validation.isNotEmpty() && errors.isEmpty()) AssistChip(onClick = {}, label = { Text("Keine Validierungsfehler") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:434:93: error: unresolved reference: Text + if (validation.isNotEmpty() && errors.isEmpty()) AssistChip(onClick = {}, label = { Text("Keine Validierungsfehler") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:436:13: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:436:18: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:437:17: error: unresolved reference: Column + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:437:24: error: unresolved reference: Modifier + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:437:44: error: unresolved reference: dp + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:437:71: error: unresolved reference: Arrangement + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:437:94: error: unresolved reference: dp + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:438:21: error: unresolved reference: Text + Text("?nderungs?bersicht", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:438:56: error: unresolved reference: MaterialTheme + Text("?nderungs?bersicht", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:439:21: error: unresolved reference: Text + Text("Bestehende Sessions: ${diff.int("tracked_sessions")}") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:442:25: error: unresolved reference: Text + Text("$label: +${section.array("added").size} / ?${section.array("removed").size} / ~${section.array("changed").size}") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:448:9: error: unresolved reference: Button + Button(onClick = vm::publish, enabled = !state.saving && !state.dirty && errors.isEmpty(), modifier = Modifier.fillMaxWidth()) { Text("Gepr?ften Entwurf ver?ffentlichen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:448:111: error: unresolved reference: Modifier + Button(onClick = vm::publish, enabled = !state.saving && !state.dirty && errors.isEmpty(), modifier = Modifier.fillMaxWidth()) { Text("Gepr?ften Entwurf ver?ffentlichen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:448:138: error: unresolved reference: Text + Button(onClick = vm::publish, enabled = !state.saving && !state.dirty && errors.isEmpty(), modifier = Modifier.fillMaxWidth()) { Text("Gepr?ften Entwurf ver?ffentlichen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:449:9: error: unresolved reference: Spacer + Spacer(Modifier.height(80.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:449:16: error: unresolved reference: Modifier + Spacer(Modifier.height(80.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:449:35: error: unresolved reference: dp + Spacer(Modifier.height(80.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:453:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:454:36: error: unresolved reference: JsonObject +private fun TrackerPreview(config: JsonObject) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:455:5: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:455:10: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:456:9: error: unresolved reference: Column + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:456:16: error: unresolved reference: Modifier + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:456:36: error: unresolved reference: dp + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:456:63: error: unresolved reference: Arrangement + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:456:86: error: unresolved reference: dp + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:457:13: error: unresolved reference: Text + Text("Tracker-Vorschau", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:457:46: error: unresolved reference: MaterialTheme + Text("Tracker-Vorschau", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:458:47: error: unresolved reference: it + config.array("days").mapNotNull { it as? JsonObject }.forEach { day -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:458:54: error: unresolved reference: JsonObject + config.array("days").mapNotNull { it as? JsonObject }.forEach { day -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:458:67: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + config.array("days").mapNotNull { it as? JsonObject }.forEach { day -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:458:77: error: cannot infer a type for this parameter. Please specify it explicitly. + config.array("days").mapNotNull { it as? JsonObject }.forEach { day -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:459:17: error: unresolved reference: Text + Text("Tag ${day.int("num")}: ${day.string("focus")}", fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:459:84: error: unresolved reference: FontWeight + Text("Tag ${day.int("num")}: ${day.string("focus")}", fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:460:53: error: unresolved reference: it + day.array("rotations").mapNotNull { it as? JsonObject }.forEach { rotation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:460:60: error: unresolved reference: JsonObject + day.array("rotations").mapNotNull { it as? JsonObject }.forEach { rotation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:460:73: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + day.array("rotations").mapNotNull { it as? JsonObject }.forEach { rotation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:460:83: error: cannot infer a type for this parameter. Please specify it explicitly. + day.array("rotations").mapNotNull { it as? JsonObject }.forEach { rotation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:461:21: error: unresolved reference: Text + Text(rotation.string("label")) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:462:62: error: unresolved reference: it + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> Text("? ${exercise.string("name")}") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:462:69: error: unresolved reference: JsonObject + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> Text("? ${exercise.string("name")}") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:462:82: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> Text("? ${exercise.string("name")}") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:462:92: error: cannot infer a type for this parameter. Please specify it explicitly. + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> Text("? ${exercise.string("name")}") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:462:104: error: unresolved reference: Text + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> Text("? ${exercise.string("name")}") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:469:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:471:69: error: unresolved reference: it + val proposals = state.proposals.array("proposals").mapNotNull { it as? JsonObject } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:471:76: error: unresolved reference: JsonObject + val proposals = state.proposals.array("proposals").mapNotNull { it as? JsonObject } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:472:5: error: unresolved reference: Column + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:472:12: error: unresolved reference: Modifier + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:472:50: error: unresolved reference: rememberScrollState + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:472:84: error: unresolved reference: dp + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:472:111: error: unresolved reference: Arrangement + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:472:135: error: unresolved reference: dp + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:473:9: error: unresolved reference: Text + Text("KI-Vorschl?ge", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:473:39: error: unresolved reference: MaterialTheme + Text("KI-Vorschl?ge", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:474:9: error: unresolved reference: Text + Text("Vorschl?ge ver?ndern den Plan niemals automatisch. Annahme und Ablehnung markieren ausschlie?lich den Pr?fstatus.") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:475:34: error: unresolved reference: Text + if (proposals.isEmpty()) Text("Keine Vorschl?ge vorhanden.") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:476:19: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + proposals.forEach { proposal -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:476:29: error: cannot infer a type for this parameter. Please specify it explicitly. + proposals.forEach { proposal -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:477:13: error: unresolved reference: OutlinedCard + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:477:26: error: unresolved reference: Modifier + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:478:17: error: unresolved reference: Column + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:478:24: error: unresolved reference: Modifier + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:478:44: error: unresolved reference: dp + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:478:71: error: unresolved reference: Arrangement + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:478:94: error: unresolved reference: dp + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:479:21: error: unresolved reference: Row + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:479:25: error: unresolved reference: Modifier + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:479:74: error: unresolved reference: Arrangement + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:480:25: error: unresolved reference: Text + Text(proposal.string("target_label", proposal.string("target")), style = MaterialTheme.typography.titleMedium, modifier = Modifier.weight(1f)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:480:98: error: unresolved reference: MaterialTheme + Text(proposal.string("target_label", proposal.string("target")), style = MaterialTheme.typography.titleMedium, modifier = Modifier.weight(1f)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:480:147: error: unresolved reference: Modifier + Text(proposal.string("target_label", proposal.string("target")), style = MaterialTheme.typography.titleMedium, modifier = Modifier.weight(1f)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:481:25: error: unresolved reference: AssistChip + AssistChip(onClick = {}, label = { Text(proposal.string("status", "open")) }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:481:60: error: unresolved reference: Text + AssistChip(onClick = {}, label = { Text(proposal.string("status", "open")) }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:483:21: error: unresolved reference: Text + Text(proposal.string("suggested_change"), fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:483:76: error: unresolved reference: FontWeight + Text(proposal.string("suggested_change"), fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:484:21: error: unresolved reference: Text + Text(proposal.string("reason")) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:485:68: error: unresolved reference: Text + if (proposal.string("condition").isNotBlank()) Text("Bedingung: ${proposal.string("condition")}") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:486:70: error: unresolved reference: Text + if (proposal.string("manual_step").isNotBlank()) Text("Manueller Schritt: ${proposal.string("manual_step")}") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:487:21: error: unresolved reference: OutlinedButton + OutlinedButton( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:492:36: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:493:25: error: unresolved reference: Text + ) { Text("Zum Ziel navigieren") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:494:21: error: unresolved reference: Row + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:494:25: error: unresolved reference: Modifier + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:494:74: error: unresolved reference: Arrangement + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:494:97: error: unresolved reference: dp + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:495:25: error: unresolved reference: Button + Button(onClick = { vm.setProposalStatus(proposal.string("id"), "accepted") }, modifier = Modifier.weight(1f)) { Text("Annehmen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:495:114: error: unresolved reference: Modifier + Button(onClick = { vm.setProposalStatus(proposal.string("id"), "accepted") }, modifier = Modifier.weight(1f)) { Text("Annehmen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:495:137: error: unresolved reference: Text + Button(onClick = { vm.setProposalStatus(proposal.string("id"), "accepted") }, modifier = Modifier.weight(1f)) { Text("Annehmen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:496:25: error: unresolved reference: OutlinedButton + OutlinedButton(onClick = { vm.setProposalStatus(proposal.string("id"), "rejected") }, modifier = Modifier.weight(1f)) { Text("Ablehnen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:496:122: error: unresolved reference: Modifier + OutlinedButton(onClick = { vm.setProposalStatus(proposal.string("id"), "rejected") }, modifier = Modifier.weight(1f)) { Text("Ablehnen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:496:145: error: unresolved reference: Text + OutlinedButton(onClick = { vm.setProposalStatus(proposal.string("id"), "rejected") }, modifier = Modifier.weight(1f)) { Text("Ablehnen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:501:9: error: unresolved reference: Spacer + Spacer(Modifier.height(80.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:501:16: error: unresolved reference: Modifier + Spacer(Modifier.height(80.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:501:35: error: unresolved reference: dp + Spacer(Modifier.height(80.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:505:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:513:17: error: unresolved reference: remember + var name by remember(initial) { mutableStateOf(initial) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:513:37: error: unresolved reference: mutableStateOf + var name by remember(initial) { mutableStateOf(initial) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:514:18: error: unresolved reference: remember + var clone by remember { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:514:29: error: unresolved reference: mutableStateOf + var clone by remember { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:515:5: error: unresolved reference: AlertDialog + AlertDialog( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:517:19: error: unresolved reference: Text + title = { Text(title) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:519:13: error: unresolved reference: Column + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:519:42: error: unresolved reference: Arrangement + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:519:65: error: unresolved reference: dp + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:520:17: error: unresolved reference: OutlinedTextField + OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text("Name") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:520:50: error: unresolved reference: it + OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text("Name") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:520:56: error: unresolved reference: Modifier + OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text("Name") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:520:91: error: unresolved reference: Text + OutlinedTextField(name, { name = it }, Modifier.fillMaxWidth(), label = { Text("Name") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:521:33: error: unresolved reference: Row + if (allowClone) Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:521:57: error: unresolved reference: androidx + if (allowClone) Row(verticalAlignment = androidx.compose.ui.Alignment.CenterVertically) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:522:21: error: unresolved reference: Checkbox + Checkbox(clone, { clone = it }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:522:47: error: unresolved reference: it + Checkbox(clone, { clone = it }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:523:21: error: unresolved reference: Text + Text("Aktuellen Plan als Vorlage verwenden") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:527:27: error: unresolved reference: TextButton + confirmButton = { TextButton(onClick = { onConfirm(name, clone) }, enabled = name.isNotBlank()) { Text("?bernehmen") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:527:107: error: unresolved reference: Text + confirmButton = { TextButton(onClick = { onConfirm(name, clone) }, enabled = name.isNotBlank()) { Text("?bernehmen") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:528:27: error: unresolved reference: TextButton + dismissButton = { TextButton(onClick = onDismiss) { Text("Abbrechen") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorScreen.kt:528:61: error: unresolved reference: Text + dismissButton = { TextButton(onClick = onDismiss) { Text("Abbrechen") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:3:8: error: unresolved reference: androidx +import androidx.lifecycle.ViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:4:8: error: unresolved reference: androidx +import androidx.lifecycle.viewModelScope + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:5:8: error: unresolved reference: dagger +import dagger.hilt.android.lifecycle.HiltViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:18:14: error: unresolved reference: inject +import javax.inject.Inject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:19:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.MutableStateFlow + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:20:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.StateFlow + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:21:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.asStateFlow + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:22:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.launch + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:23:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonArray + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:24:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonElement + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:25:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonNull + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:26:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:27:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonPrimitive + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:28:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.buildJsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:29:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.jsonArray + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:30:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.jsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:31:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.put + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:47:18: error: unresolved reference: JsonObject + val wrapper: JsonObject = JsonObject(emptyMap()), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:47:31: error: unresolved reference: JsonObject + val wrapper: JsonObject = JsonObject(emptyMap()), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:48:17: error: unresolved reference: JsonObject + val config: JsonObject = JsonObject(emptyMap()), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:48:30: error: unresolved reference: JsonObject + val config: JsonObject = JsonObject(emptyMap()), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:49:21: error: unresolved reference: JsonObject + val validation: JsonObject = JsonObject(emptyMap()), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:49:34: error: unresolved reference: JsonObject + val validation: JsonObject = JsonObject(emptyMap()), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:50:20: error: unresolved reference: JsonObject + val proposals: JsonObject = JsonObject(emptyMap()), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:50:33: error: unresolved reference: JsonObject + val proposals: JsonObject = JsonObject(emptyMap()), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:58:2: error: unresolved reference: HiltViewModel +@HiltViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:59:24: error: unresolved reference: Inject +class EditorViewModel @Inject constructor( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:62:5: error: unresolved reference: ViewModel +) : ViewModel() { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:63:26: error: unresolved reference: MutableStateFlow + private val _state = MutableStateFlow(EditorUiState()) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:64:16: error: unresolved reference: StateFlow + val state: StateFlow = _state.asStateFlow() + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:69:9: error: unresolved reference: viewModelScope + viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:70:20: error: variable expected + _state.value = _state.value.copy(loading = true, error = "", message = "") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:71:38: error: suspend function 'editorPlans' should be called only from a coroutine or another suspend function + runCatching { repository.editorPlans(force) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:72:30: error: cannot infer a type for this parameter. Please specify it explicitly. + .onSuccess { body -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:73:66: error: unresolved reference: it + val plans = body.array("plans").mapNotNull { it as? JsonObject }.map { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:73:73: error: unresolved reference: JsonObject + val plans = body.array("plans").mapNotNull { it as? JsonObject }.map { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:75:34: error: unresolved reference: it + id = it.string("id"), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:76:36: error: unresolved reference: it + name = it.string("name", it.string("id")), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:76:54: error: unresolved reference: it + name = it.string("name", it.string("id")), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:77:40: error: unresolved reference: it + revision = it.int("revision", 1), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:78:49: error: unresolved reference: it + publishedRevision = it.int("published_revision", 1), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:79:38: error: unresolved reference: it + active = it["active"]?.toString() == "true", + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:80:53: error: unresolved reference: it + hasUnpublishedChanges = it["has_unpublished_changes"]?.toString() == "true", + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:83:43: error: suspend function 'selectedEditorPlan' should be called only from a coroutine or another suspend function + val stored = settings.selectedEditorPlan() + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:84:73: error: cannot infer a type for this parameter. Please specify it explicitly. + val selected = _state.value.selectedPlanId.takeIf { id -> plans.any { it.id == id } } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:84:91: error: unresolved reference: it + val selected = _state.value.selectedPlanId.takeIf { id -> plans.any { it.id == id } } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:85:63: error: unresolved reference: it + ?: stored?.takeIf { id -> plans.any { it.id == id } } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:86:59: error: cannot infer a type for this parameter. Please specify it explicitly. + ?: body.string("active").takeIf { id -> plans.any { it.id == id } } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:86:77: error: unresolved reference: it + ?: body.string("active").takeIf { id -> plans.any { it.id == id } } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:88:28: error: variable expected + _state.value = _state.value.copy(loading = false, plans = plans) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:91:30: error: cannot infer a type for this parameter. Please specify it explicitly. + .onFailure { error -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:92:28: error: variable expected + _state.value = _state.value.copy(loading = false, error = error.message ?: "Planliste konnte nicht geladen werden.") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:98:9: error: unresolved reference: viewModelScope + viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:99:20: error: variable expected + _state.value = _state.value.copy(loading = true, selectedPlanId = planId, error = "", conflict = null) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:101:47: error: suspend function 'selectEditorPlan' should be called only from a coroutine or another suspend function + if (persistServer) repository.selectEditorPlan(planId) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:102:26: error: suspend function 'setSelectedEditorPlan' should be called only from a coroutine or another suspend function + settings.setSelectedEditorPlan(planId) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:103:42: error: suspend function 'editorPlan' should be called only from a coroutine or another suspend function + val wrapper = repository.editorPlan(planId, force = true) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:104:44: error: suspend function 'proposals' should be called only from a coroutine or another suspend function + val proposals = repository.proposals(planId) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:106:27: error: cannot infer a type for this parameter. Please specify it explicitly. + }.onSuccess { (wrapper, proposals) -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:107:24: error: variable expected + _state.value = _state.value.copy( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:112:34: error: unresolved reference: JsonObject + validation = JsonObject(emptyMap()), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:116:27: error: cannot infer a type for this parameter. Please specify it explicitly. + }.onFailure { error -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:117:24: error: variable expected + _state.value = _state.value.copy(loading = false, error = error.message ?: "Plan konnte nicht geladen werden.") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:124:9: error: unresolved reference: viewModelScope + viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:125:20: error: variable expected + _state.value = _state.value.copy(saving = true, error = "") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:126:38: error: suspend function 'createPlan' should be called only from a coroutine or another suspend function + runCatching { repository.createPlan(name.trim(), _state.value.selectedPlanId.takeIf { cloneCurrent }) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:127:30: error: cannot infer a type for this parameter. Please specify it explicitly. + .onSuccess { created -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:128:28: error: variable expected + _state.value = _state.value.copy( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:137:30: error: cannot infer a type for this parameter. Please specify it explicitly. + .onFailure { error -> _state.value = _state.value.copy(saving = false, error = error.message ?: "Plan konnte nicht angelegt werden.") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:137:46: error: variable expected + .onFailure { error -> _state.value = _state.value.copy(saving = false, error = error.message ?: "Plan konnte nicht angelegt werden.") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:144:9: error: unresolved reference: viewModelScope + viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:145:38: error: suspend function 'renamePlan' should be called only from a coroutine or another suspend function + runCatching { repository.renamePlan(id, name.trim()) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:147:28: error: variable expected + _state.value = _state.value.copy(wrapper = _state.value.wrapper.updatedString("name", name.trim()), message = "Name ge?ndert.") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:150:30: error: cannot infer a type for this parameter. Please specify it explicitly. + .onFailure { error -> _state.value = _state.value.copy(error = error.message ?: "Umbenennen fehlgeschlagen.") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:150:46: error: variable expected + .onFailure { error -> _state.value = _state.value.copy(error = error.message ?: "Umbenennen fehlgeschlagen.") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:157:9: error: unresolved reference: viewModelScope + viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:158:38: error: suspend function 'deletePlan' should be called only from a coroutine or another suspend function + runCatching { repository.deletePlan(id) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:160:28: error: variable expected + _state.value = EditorUiState(message = "Plan gel?scht.") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:163:30: error: cannot infer a type for this parameter. Please specify it explicitly. + .onFailure { error -> _state.value = _state.value.copy(error = error.message ?: "L?schen fehlgeschlagen.") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:163:46: error: variable expected + .onFailure { error -> _state.value = _state.value.copy(error = error.message ?: "L?schen fehlgeschlagen.") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:181:42: error: unresolved reference: JsonObject + val entry = (prepost[dayKey] as? JsonObject ?: JsonObject(emptyMap())).updatedString(field, value) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:181:56: error: unresolved reference: JsonObject + val entry = (prepost[dayKey] as? JsonObject ?: JsonObject(emptyMap())).updatedString(field, value) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:183:48: error: unresolved reference: JsonObject + mutateConfig(config.updated("prepost", JsonObject(prepost))) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:186:46: error: unresolved reference: JsonObject + fun updateDay(dayId: String, transform: (JsonObject) -> JsonObject) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:186:61: error: unresolved reference: JsonObject + fun updateDay(dayId: String, transform: (JsonObject) -> JsonObject) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:187:60: error: cannot infer a type for this parameter. Please specify it explicitly. + val days = _state.value.config.array("days").map { element -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:188:35: error: unresolved reference: JsonObject + val day = element as? JsonObject ?: return@map element + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:191:58: error: unresolved reference: JsonArray + mutateConfig(_state.value.config.updated("days", JsonArray(days))) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:197:42: error: unresolved reference: it + val number = (days.mapNotNull { (it as? JsonObject)?.int("num") }.maxOrNull() ?: 0) + 1 + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:197:49: error: unresolved reference: JsonObject + val number = (days.mapNotNull { (it as? JsonObject)?.int("num") }.maxOrNull() ?: 0) + 1 + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:198:14: error: function '' should return Unit to be used by corresponding operator '+=' + days += buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:198:17: error: unresolved reference: buildJsonObject + days += buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:199:13: error: unresolved reference: put + put("id", stableId("day")) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:200:13: error: unresolved reference: put + put("num", number) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:201:13: error: unresolved reference: put + put("focus", "Neuer Trainingstag") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:202:13: error: unresolved reference: put + put("light", false) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:203:13: error: unresolved reference: put + put("rotations", JsonArray(emptyList())) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:203:30: error: unresolved reference: JsonArray + put("rotations", JsonArray(emptyList())) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:206:38: error: unresolved reference: buildJsonObject + prepost[number.toString()] = buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:207:13: error: unresolved reference: put + put("warmup", "") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:208:13: error: unresolved reference: put + put("cooldown", "") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:209:13: error: unresolved reference: put + put("stretch", "") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:211:45: error: unresolved reference: JsonArray + mutateConfig(config.updated("days", JsonArray(days)).updated("prepost", JsonObject(prepost))) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:211:81: error: unresolved reference: JsonObject + mutateConfig(config.updated("days", JsonArray(days)).updated("prepost", JsonObject(prepost))) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:216:57: error: unresolved reference: it + val removed = config.array("days").mapNotNull { it as? JsonObject }.firstOrNull { it.string("id") == dayId } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:216:64: error: unresolved reference: JsonObject + val removed = config.array("days").mapNotNull { it as? JsonObject }.firstOrNull { it.string("id") == dayId } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:216:91: error: unresolved reference: it + val removed = config.array("days").mapNotNull { it as? JsonObject }.firstOrNull { it.string("id") == dayId } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:217:54: error: unresolved reference: it + val days = config.array("days").filterNot { (it as? JsonObject)?.string("id") == dayId } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:217:61: error: unresolved reference: JsonObject + val days = config.array("days").filterNot { (it as? JsonObject)?.string("id") == dayId } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:219:39: error: unresolved reference: it + removed?.let { prepost.remove(it.int("num").toString()) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:220:45: error: unresolved reference: JsonArray + mutateConfig(config.updated("days", JsonArray(days)).updated("prepost", JsonObject(prepost))) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:220:81: error: unresolved reference: JsonObject + mutateConfig(config.updated("days", JsonArray(days)).updated("prepost", JsonObject(prepost))) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:224:28: error: cannot infer a type for this parameter. Please specify it explicitly. + updateDay(dayId) { day -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:226:23: error: function '' should return Unit to be used by corresponding operator '+=' + rotations += buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:226:26: error: unresolved reference: buildJsonObject + rotations += buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:227:17: error: unresolved reference: put + put("id", stableId("rotation")) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:228:17: error: unresolved reference: put + put("label", "Neuer Block") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:229:17: error: unresolved reference: put + put("exercises", JsonArray(emptyList())) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:229:34: error: unresolved reference: JsonArray + put("exercises", JsonArray(emptyList())) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:231:38: error: unresolved reference: JsonArray + day.updated("rotations", JsonArray(rotations)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:235:71: error: unresolved reference: JsonObject + fun updateRotation(dayId: String, rotationId: String, transform: (JsonObject) -> JsonObject) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:235:86: error: unresolved reference: JsonObject + fun updateRotation(dayId: String, rotationId: String, transform: (JsonObject) -> JsonObject) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:236:28: error: cannot infer a type for this parameter. Please specify it explicitly. + updateDay(dayId) { day -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:237:58: error: cannot infer a type for this parameter. Please specify it explicitly. + val rotations = day.array("rotations").map { element -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:238:44: error: unresolved reference: JsonObject + val rotation = element as? JsonObject ?: return@map element + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:241:38: error: unresolved reference: JsonArray + day.updated("rotations", JsonArray(rotations)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:246:28: error: cannot infer a type for this parameter. Please specify it explicitly. + updateDay(dayId) { day -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:247:38: error: unresolved reference: JsonArray + day.updated("rotations", JsonArray(day.array("rotations").filterNot { (it as? JsonObject)?.string("id") == rotationId })) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:247:84: error: unresolved reference: it + day.updated("rotations", JsonArray(day.array("rotations").filterNot { (it as? JsonObject)?.string("id") == rotationId })) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:247:91: error: unresolved reference: JsonObject + day.updated("rotations", JsonArray(day.array("rotations").filterNot { (it as? JsonObject)?.string("id") == rotationId })) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:252:45: error: cannot infer a type for this parameter. Please specify it explicitly. + updateRotation(dayId, rotationId) { rotation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:255:23: error: function '' should return Unit to be used by corresponding operator '+=' + exercises += buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:255:26: error: unresolved reference: buildJsonObject + exercises += buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:256:17: error: unresolved reference: put + put("id", stableId("exercise-placement")) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:257:17: error: unresolved reference: put + put("legacy_id", "") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:258:17: error: unresolved reference: put + put("exercise_id", movementId) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:259:17: error: unresolved reference: put + put("progression_id", "") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:260:17: error: unresolved reference: put + put("name", "Neue ?bung") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:261:17: error: unresolved reference: put + put("cue", "") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:262:17: error: unresolved reference: put + put("movement_cluster", "general") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:263:17: error: unresolved reference: put + put("result_schema", defaultSchema()) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:265:43: error: unresolved reference: JsonArray + rotation.updated("exercises", JsonArray(exercises)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:269:91: error: unresolved reference: JsonObject + fun updateExercise(dayId: String, rotationId: String, exerciseId: String, transform: (JsonObject) -> JsonObject) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:269:106: error: unresolved reference: JsonObject + fun updateExercise(dayId: String, rotationId: String, exerciseId: String, transform: (JsonObject) -> JsonObject) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:270:45: error: cannot infer a type for this parameter. Please specify it explicitly. + updateRotation(dayId, rotationId) { rotation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:271:63: error: cannot infer a type for this parameter. Please specify it explicitly. + val exercises = rotation.array("exercises").map { element -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:272:44: error: unresolved reference: JsonObject + val exercise = element as? JsonObject ?: return@map element + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:275:43: error: unresolved reference: JsonArray + rotation.updated("exercises", JsonArray(exercises)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:280:45: error: cannot infer a type for this parameter. Please specify it explicitly. + updateRotation(dayId, rotationId) { rotation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:281:43: error: unresolved reference: JsonArray + rotation.updated("exercises", JsonArray(rotation.array("exercises").filterNot { (it as? JsonObject)?.string("id") == exerciseId })) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:281:94: error: unresolved reference: it + rotation.updated("exercises", JsonArray(rotation.array("exercises").filterNot { (it as? JsonObject)?.string("id") == exerciseId })) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:281:101: error: unresolved reference: JsonObject + rotation.updated("exercises", JsonArray(rotation.array("exercises").filterNot { (it as? JsonObject)?.string("id") == exerciseId })) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:285:107: error: unresolved reference: JsonElement + fun updateExerciseSchema(dayId: String, rotationId: String, exerciseId: String, field: String, value: JsonElement) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:286:57: error: cannot infer a type for this parameter. Please specify it explicitly. + updateExercise(dayId, rotationId, exerciseId) { exercise -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:287:57: error: unresolved reference: JsonObject + val schema = (exercise["result_schema"] as? JsonObject ?: defaultSchema()).updated(field, value) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:292:83: error: unresolved reference: JsonObject + fun updateProgressionStep(progressionKey: String, stepId: String, transform: (JsonObject) -> JsonObject) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:292:98: error: unresolved reference: JsonObject + fun updateProgressionStep(progressionKey: String, stepId: String, transform: (JsonObject) -> JsonObject) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:294:48: error: unresolved reference: JsonObject + val stage = stages[progressionKey] as? JsonObject ?: return + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:295:48: error: cannot infer a type for this parameter. Please specify it explicitly. + val steps = stage.array("steps").map { element -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:296:36: error: unresolved reference: JsonObject + val step = element as? JsonObject ?: return@map element + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:299:56: error: unresolved reference: put + val nextStages = stages.toMutableMap().apply { put(progressionKey, stage.updated("steps", JsonArray(steps))) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:299:99: error: unresolved reference: JsonArray + val nextStages = stages.toMutableMap().apply { put(progressionKey, stage.updated("steps", JsonArray(steps))) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:300:60: error: unresolved reference: JsonObject + mutateConfig(_state.value.config.updated("stages", JsonObject(nextStages))) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:305:48: error: unresolved reference: JsonObject + val stage = stages[progressionKey] as? JsonObject ?: return + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:307:15: error: function '' should return Unit to be used by corresponding operator '+=' + steps += buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:307:18: error: unresolved reference: buildJsonObject + steps += buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:308:13: error: unresolved reference: put + put("id", stableId("step")) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:309:13: error: unresolved reference: put + put("name", "Neue Stufe") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:310:13: error: unresolved reference: put + put("phase_id", "") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:311:13: error: unresolved reference: put + put("factor", 1.0) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:312:13: error: unresolved reference: put + put("movement_cluster", "general") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:313:13: error: unresolved reference: put + put("result_schema", defaultSchema()) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:315:56: error: unresolved reference: put + val nextStages = stages.toMutableMap().apply { put(progressionKey, stage.updated("steps", JsonArray(steps))) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:315:99: error: unresolved reference: JsonArray + val nextStages = stages.toMutableMap().apply { put(progressionKey, stage.updated("steps", JsonArray(steps))) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:316:60: error: unresolved reference: JsonObject + mutateConfig(_state.value.config.updated("stages", JsonObject(nextStages))) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:319:88: error: unresolved reference: JsonElement + fun updateStepSchema(progressionKey: String, stepId: String, field: String, value: JsonElement) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:320:57: error: cannot infer a type for this parameter. Please specify it explicitly. + updateProgressionStep(progressionKey, stepId) { step -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:321:53: error: unresolved reference: JsonObject + val schema = (step["result_schema"] as? JsonObject ?: defaultSchema()).updated(field, value) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:329:9: error: unresolved reference: viewModelScope + viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:330:20: error: variable expected + _state.value = s.copy(saving = true, error = "", conflict = null) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:331:38: error: suspend function 'savePlan' should be called only from a coroutine or another suspend function + runCatching { repository.savePlan(s.selectedPlanId, s.wrapper.int("revision", 1), s.config) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:332:30: error: cannot infer a type for this parameter. Please specify it explicitly. + .onSuccess { body -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:333:28: error: variable expected + _state.value = _state.value.copy( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:350:9: error: unresolved reference: viewModelScope + viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:351:20: error: variable expected + _state.value = s.copy(saving = true, error = "") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:352:38: error: suspend function 'validatePlan' should be called only from a coroutine or another suspend function + runCatching { repository.validatePlan(s.selectedPlanId, s.config) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:353:30: error: cannot infer a type for this parameter. Please specify it explicitly. + .onSuccess { validation -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:354:28: error: variable expected + _state.value = _state.value.copy( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:357:68: error: unresolved reference: it + config = validation.obj("config").takeIf { it.isNotEmpty() } ?: s.config, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:368:9: error: unresolved reference: viewModelScope + viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:369:20: error: variable expected + _state.value = s.copy(saving = true, error = "", conflict = null) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:370:38: error: suspend function 'publishPlan' should be called only from a coroutine or another suspend function + runCatching { repository.publishPlan(s.selectedPlanId, s.wrapper.int("revision", 1)) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:371:30: error: cannot infer a type for this parameter. Please specify it explicitly. + .onSuccess { body -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:372:28: error: variable expected + _state.value = _state.value.copy( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:389:9: error: unresolved reference: viewModelScope + viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:390:38: error: suspend function 'setProposalStatus' should be called only from a coroutine or another suspend function + runCatching { repository.setProposalStatus(planId, proposalId, status) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:391:30: error: cannot infer a type for this parameter. Please specify it explicitly. + .onSuccess { body -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:392:102: error: unresolved reference: JsonArray + val proposals = _state.value.proposals.updated("proposals", body["proposals"] ?: JsonArray(emptyList())) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:393:28: error: variable expected + _state.value = _state.value.copy(proposals = proposals, message = "Vorschlag als $status markiert.") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:395:30: error: cannot infer a type for this parameter. Please specify it explicitly. + .onFailure { error -> _state.value = _state.value.copy(error = error.message ?: "Vorschlag konnte nicht aktualisiert werden.") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:395:46: error: variable expected + .onFailure { error -> _state.value = _state.value.copy(error = error.message ?: "Vorschlag konnte nicht aktualisiert werden.") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:400:16: error: variable expected + _state.value = _state.value.copy(proposalTargetId = targetExerciseId) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:403:40: error: variable expected + fun clearProposalTarget() { _state.value = _state.value.copy(proposalTargetId = "") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:404:36: error: variable expected + fun dismissConflict() { _state.value = _state.value.copy(conflict = null) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:407:38: error: unresolved reference: JsonObject + private fun mutateConfig(config: JsonObject) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:408:16: error: variable expected + _state.value = _state.value.copy(config = config, dirty = true, message = "Ungespeicherte ?nderungen", error = "") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:412:16: error: variable expected + _state.value = if (error is RevisionConflictException) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:421:34: error: unresolved reference: JsonObject + private fun defaultSchema(): JsonObject = buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:421:47: error: unresolved reference: buildJsonObject + private fun defaultSchema(): JsonObject = buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:422:9: error: unresolved reference: put + put("mode", "reps") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:423:9: error: unresolved reference: put + put("weight_mode", "none") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:424:9: error: unresolved reference: put + put("laterality", "bilateral") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:425:9: error: unresolved reference: put + put("sides_mode", "same") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:426:9: error: unresolved reference: put + put("sets", 3) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/editor/EditorViewModel.kt:427:9: error: unresolved reference: put + put("locked_sets", false) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:3:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.Arrangement + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:4:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.Column + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:5:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.PaddingValues + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:6:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.Row + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:7:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.Spacer + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:8:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.fillMaxSize + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:9:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.fillMaxWidth + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:10:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.height + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:11:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.padding + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:12:8: error: unresolved reference: androidx +import androidx.compose.foundation.rememberScrollState + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:13:8: error: unresolved reference: androidx +import androidx.compose.foundation.verticalScroll + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:14:8: error: unresolved reference: androidx +import androidx.compose.material.icons.Icons + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:15:8: error: unresolved reference: androidx +import androidx.compose.material.icons.filled.CloudDone + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:16:8: error: unresolved reference: androidx +import androidx.compose.material3.Button + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:17:8: error: unresolved reference: androidx +import androidx.compose.material3.Card + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:18:8: error: unresolved reference: androidx +import androidx.compose.material3.CircularProgressIndicator + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:19:8: error: unresolved reference: androidx +import androidx.compose.material3.FilterChip + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:20:8: error: unresolved reference: androidx +import androidx.compose.material3.Icon + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:21:8: error: unresolved reference: androidx +import androidx.compose.material3.MaterialTheme + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:22:8: error: unresolved reference: androidx +import androidx.compose.material3.OutlinedTextField + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:23:8: error: unresolved reference: androidx +import androidx.compose.material3.Text + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:24:8: error: unresolved reference: androidx +import androidx.compose.runtime.Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:25:8: error: unresolved reference: androidx +import androidx.compose.runtime.getValue + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:26:8: error: unresolved reference: androidx +import androidx.compose.ui.Modifier + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:27:8: error: unresolved reference: androidx +import androidx.compose.ui.text.input.PasswordVisualTransformation + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:28:8: error: unresolved reference: androidx +import androidx.compose.ui.unit.dp + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:29:8: error: unresolved reference: androidx +import androidx.hilt.navigation.compose.hiltViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:30:8: error: unresolved reference: androidx +import androidx.lifecycle.compose.collectAsStateWithLifecycle + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:33:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:35:21: error: unresolved reference: PaddingValues + contentPadding: PaddingValues, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:38:36: error: unresolved reference: hiltViewModel + viewModel: SettingsViewModel = hiltViewModel(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:41:5: error: unresolved reference: Column + Column( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:42:20: error: unresolved reference: Modifier + modifier = Modifier + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:45:29: error: unresolved reference: rememberScrollState + .verticalScroll(rememberScrollState()) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:46:25: error: unresolved reference: dp + .padding(16.dp), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:47:31: error: unresolved reference: Arrangement + verticalArrangement = Arrangement.spacedBy(16.dp), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:47:55: error: unresolved reference: dp + verticalArrangement = Arrangement.spacedBy(16.dp), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:49:9: error: unresolved reference: Text + Text("Einstellungen", style = MaterialTheme.typography.headlineMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:49:39: error: unresolved reference: MaterialTheme + Text("Einstellungen", style = MaterialTheme.typography.headlineMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:50:9: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:50:14: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:51:13: error: unresolved reference: Column + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:51:20: error: unresolved reference: Modifier + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:51:40: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:51:67: error: unresolved reference: Arrangement + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:51:91: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:52:17: error: unresolved reference: Text + Text("Server & Anmeldung", style = MaterialTheme.typography.titleLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:52:52: error: unresolved reference: MaterialTheme + Text("Server & Anmeldung", style = MaterialTheme.typography.titleLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:53:17: error: unresolved reference: Text + Text( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:55:29: error: unresolved reference: MaterialTheme + style = MaterialTheme.typography.bodyMedium, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:57:17: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:60:32: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:61:31: error: unresolved reference: Text + label = { Text("Server-URL") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:64:17: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:67:32: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:68:31: error: unresolved reference: Text + label = { Text("Benutzername") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:71:17: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:74:32: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:75:31: error: unresolved reference: Text + label = { Text("Passwort") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:76:44: error: unresolved reference: PasswordVisualTransformation + visualTransformation = PasswordVisualTransformation(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:79:17: error: unresolved reference: Button + Button(onClick = viewModel::saveAndTest, enabled = !state.loading) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:80:40: error: unresolved reference: CircularProgressIndicator + if (state.loading) CircularProgressIndicator(modifier = Modifier.height(18.dp), strokeWidth = 2.dp) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:80:77: error: unresolved reference: Modifier + if (state.loading) CircularProgressIndicator(modifier = Modifier.height(18.dp), strokeWidth = 2.dp) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:80:96: error: unresolved reference: dp + if (state.loading) CircularProgressIndicator(modifier = Modifier.height(18.dp), strokeWidth = 2.dp) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:80:117: error: unresolved reference: dp + if (state.loading) CircularProgressIndicator(modifier = Modifier.height(18.dp), strokeWidth = 2.dp) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:81:26: error: unresolved reference: Icon + else Icon(Icons.Default.CloudDone, null) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:81:31: error: unresolved reference: Icons + else Icon(Icons.Default.CloudDone, null) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:82:21: error: unresolved reference: Spacer + Spacer(Modifier.padding(horizontal = 4.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:82:28: error: unresolved reference: Modifier + Spacer(Modifier.padding(horizontal = 4.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:82:60: error: unresolved reference: dp + Spacer(Modifier.padding(horizontal = 4.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:83:21: error: unresolved reference: Text + Text("Speichern & Verbindung testen") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:85:49: error: unresolved reference: Text + if (state.message.isNotBlank()) Text(state.message, color = MaterialTheme.colorScheme.primary) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:85:77: error: unresolved reference: MaterialTheme + if (state.message.isNotBlank()) Text(state.message, color = MaterialTheme.colorScheme.primary) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:86:47: error: unresolved reference: Text + if (state.error.isNotBlank()) Text(state.error, color = MaterialTheme.colorScheme.error) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:86:73: error: unresolved reference: MaterialTheme + if (state.error.isNotBlank()) Text(state.error, color = MaterialTheme.colorScheme.error) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:90:9: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:90:14: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:91:13: error: unresolved reference: Column + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:91:20: error: unresolved reference: Modifier + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:91:40: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:91:67: error: unresolved reference: Arrangement + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:91:91: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:92:17: error: unresolved reference: Text + Text("Darstellung", style = MaterialTheme.typography.titleLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:92:45: error: unresolved reference: MaterialTheme + Text("Darstellung", style = MaterialTheme.typography.titleLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:93:17: error: unresolved reference: Row + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:93:45: error: unresolved reference: Arrangement + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:93:68: error: unresolved reference: dp + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:95:25: error: unresolved reference: FilterChip + FilterChip( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsScreen.kt:98:39: error: unresolved reference: Text + label = { Text(label) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:3:8: error: unresolved reference: androidx +import androidx.lifecycle.ViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:4:8: error: unresolved reference: androidx +import androidx.lifecycle.viewModelScope + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:5:8: error: unresolved reference: dagger +import dagger.hilt.android.lifecycle.HiltViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:8:14: error: unresolved reference: inject +import javax.inject.Inject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:9:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.MutableStateFlow + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:10:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.StateFlow + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:11:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.asStateFlow + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:12:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.launch + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:23:2: error: unresolved reference: HiltViewModel +@HiltViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:24:26: error: unresolved reference: Inject +class SettingsViewModel @Inject constructor( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:27:5: error: unresolved reference: ViewModel +) : ViewModel() { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:28:26: error: unresolved reference: MutableStateFlow + private val _state = MutableStateFlow(SettingsUiState()) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:29:16: error: unresolved reference: StateFlow + val state: StateFlow = _state.asStateFlow() + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:32:9: error: unresolved reference: viewModelScope + viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:33:37: error: suspend function 'snapshot' should be called only from a coroutine or another suspend function + val snapshot = settings.snapshot() + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:34:20: error: variable expected + _state.value = _state.value.copy( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:42:46: error: variable expected + fun updateServer(value: String) { _state.value = _state.value.copy(serverUrl = value, message = "", error = "") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:43:48: error: variable expected + fun updateUsername(value: String) { _state.value = _state.value.copy(username = value, message = "", error = "") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:44:48: error: variable expected + fun updatePassword(value: String) { _state.value = _state.value.copy(password = value, message = "", error = "") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:47:9: error: unresolved reference: viewModelScope + viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:48:20: error: variable expected + _state.value = _state.value.copy(loading = true, message = "", error = "") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:50:26: error: suspend function 'saveConnection' should be called only from a coroutine or another suspend function + settings.saveConnection(_state.value.serverUrl, _state.value.username, _state.value.password) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:51:28: error: suspend function 'testConnection' should be called only from a coroutine or another suspend function + repository.testConnection() + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:52:27: error: cannot infer a type for this parameter. Please specify it explicitly. + }.onSuccess { health -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:54:24: error: variable expected + _state.value = _state.value.copy( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:58:27: error: cannot infer a type for this parameter. Please specify it explicitly. + }.onFailure { error -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/setup/SettingsViewModel.kt:59:24: error: variable expected + _state.value = _state.value.copy(loading = false, error = error.message ?: "Verbindung fehlgeschlagen.") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:3:8: error: unresolved reference: android +import android.app.Activity + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:4:8: error: unresolved reference: android +import android.os.Build + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:5:8: error: unresolved reference: androidx +import androidx.compose.foundation.isSystemInDarkTheme + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:6:8: error: unresolved reference: androidx +import androidx.compose.material3.MaterialTheme + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:7:8: error: unresolved reference: androidx +import androidx.compose.material3.darkColorScheme + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:8:8: error: unresolved reference: androidx +import androidx.compose.material3.dynamicDarkColorScheme + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:9:8: error: unresolved reference: androidx +import androidx.compose.material3.dynamicLightColorScheme + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:10:8: error: unresolved reference: androidx +import androidx.compose.material3.lightColorScheme + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:11:8: error: unresolved reference: androidx +import androidx.compose.runtime.Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:12:8: error: unresolved reference: androidx +import androidx.compose.ui.graphics.Color + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:13:8: error: unresolved reference: androidx +import androidx.compose.ui.platform.LocalContext + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:15:21: error: unresolved reference: lightColorScheme +private val Light = lightColorScheme( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:16:15: error: unresolved reference: Color + primary = Color(0xFF566600), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:17:17: error: unresolved reference: Color + onPrimary = Color.White, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:18:24: error: unresolved reference: Color + primaryContainer = Color(0xFFD9F26A), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:19:26: error: unresolved reference: Color + onPrimaryContainer = Color(0xFF172000), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:20:17: error: unresolved reference: Color + secondary = Color(0xFF53643C), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:21:16: error: unresolved reference: Color + tertiary = Color(0xFF00696C), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:22:18: error: unresolved reference: Color + background = Color(0xFFF9FAF2), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:23:15: error: unresolved reference: Color + surface = Color(0xFFF9FAF2), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:24:22: error: unresolved reference: Color + surfaceVariant = Color(0xFFE5E8D8), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:27:20: error: unresolved reference: darkColorScheme +private val Dark = darkColorScheme( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:28:15: error: unresolved reference: Color + primary = Color(0xFFC0D852), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:29:17: error: unresolved reference: Color + onPrimary = Color(0xFF2B3400), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:30:24: error: unresolved reference: Color + primaryContainer = Color(0xFF414D00), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:31:26: error: unresolved reference: Color + onPrimaryContainer = Color(0xFFD9F26A), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:32:17: error: unresolved reference: Color + secondary = Color(0xFFBACCA2), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:33:16: error: unresolved reference: Color + tertiary = Color(0xFF80D4D6), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:34:18: error: unresolved reference: Color + background = Color(0xFF11140E), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:35:15: error: unresolved reference: Color + surface = Color(0xFF11140E), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:36:22: error: unresolved reference: Color + surfaceVariant = Color(0xFF41443A), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:39:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:40:44: error: unresolved reference: Composable +fun TrainingTheme(theme: String, content: @Composable () -> Unit) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:44:17: error: unresolved reference: isSystemInDarkTheme + else -> isSystemInDarkTheme() + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/theme/Theme.kt:46:5: error: unresolved reference: MaterialTheme + MaterialTheme(colorScheme = if (dark) Dark else Light, content = content) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:3:8: error: unresolved reference: androidx +import androidx.compose.foundation.horizontalScroll + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:4:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.Arrangement + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:5:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.Box + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:6:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.Column + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:7:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.PaddingValues + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:8:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.Row + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:9:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.Spacer + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:10:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.fillMaxSize + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:11:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.fillMaxWidth + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:12:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.height + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:13:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.padding + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:14:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.width + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:15:8: error: unresolved reference: androidx +import androidx.compose.foundation.layout.weight + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:16:8: error: unresolved reference: androidx +import androidx.compose.foundation.rememberScrollState + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:17:8: error: unresolved reference: androidx +import androidx.compose.foundation.selection.selectable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:18:8: error: unresolved reference: androidx +import androidx.compose.foundation.verticalScroll + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:19:8: error: unresolved reference: androidx +import androidx.compose.material.icons.Icons + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:20:8: error: unresolved reference: androidx +import androidx.compose.material.icons.filled.CheckCircle + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:21:8: error: unresolved reference: androidx +import androidx.compose.material.icons.filled.MoreVert + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:22:8: error: unresolved reference: androidx +import androidx.compose.material.icons.filled.CloudOff + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:23:8: error: unresolved reference: androidx +import androidx.compose.material.icons.filled.PlayArrow + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:24:8: error: unresolved reference: androidx +import androidx.compose.material.icons.filled.Refresh + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:25:8: error: unresolved reference: androidx +import androidx.compose.material.icons.filled.Stop + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:26:8: error: unresolved reference: androidx +import androidx.compose.material3.AlertDialog + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:27:8: error: unresolved reference: androidx +import androidx.compose.material3.AssistChip + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:28:8: error: unresolved reference: androidx +import androidx.compose.material3.Button + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:29:8: error: unresolved reference: androidx +import androidx.compose.material3.Card + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:30:8: error: unresolved reference: androidx +import androidx.compose.material3.Checkbox + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:31:8: error: unresolved reference: androidx +import androidx.compose.material3.CircularProgressIndicator + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:32:8: error: unresolved reference: androidx +import androidx.compose.material3.Divider + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:33:8: error: unresolved reference: androidx +import androidx.compose.material3.DropdownMenu + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:34:8: error: unresolved reference: androidx +import androidx.compose.material3.DropdownMenuItem + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:35:8: error: unresolved reference: androidx +import androidx.compose.material3.ExperimentalMaterial3Api + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:36:8: error: unresolved reference: androidx +import androidx.compose.material3.FilterChip + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:37:8: error: unresolved reference: androidx +import androidx.compose.material3.Icon + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:38:8: error: unresolved reference: androidx +import androidx.compose.material3.IconButton + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:39:8: error: unresolved reference: androidx +import androidx.compose.material3.MaterialTheme + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:40:8: error: unresolved reference: androidx +import androidx.compose.material3.OutlinedButton + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:41:8: error: unresolved reference: androidx +import androidx.compose.material3.OutlinedCard + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:42:8: error: unresolved reference: androidx +import androidx.compose.material3.OutlinedTextField + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:43:8: error: unresolved reference: androidx +import androidx.compose.material3.Scaffold + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:44:8: error: unresolved reference: androidx +import androidx.compose.material3.ScrollableTabRow + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:45:8: error: unresolved reference: androidx +import androidx.compose.material3.Tab + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:46:8: error: unresolved reference: androidx +import androidx.compose.material3.Text + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:47:8: error: unresolved reference: androidx +import androidx.compose.material3.TextButton + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:48:8: error: unresolved reference: androidx +import androidx.compose.material3.TopAppBar + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:49:8: error: unresolved reference: androidx +import androidx.compose.runtime.Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:50:8: error: unresolved reference: androidx +import androidx.compose.runtime.getValue + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:51:8: error: unresolved reference: androidx +import androidx.compose.runtime.mutableStateOf + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:52:8: error: unresolved reference: androidx +import androidx.compose.runtime.remember + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:53:8: error: unresolved reference: androidx +import androidx.compose.runtime.setValue + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:54:8: error: unresolved reference: androidx +import androidx.compose.ui.Modifier + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:55:8: error: unresolved reference: androidx +import androidx.compose.ui.text.font.FontWeight + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:56:8: error: unresolved reference: androidx +import androidx.compose.foundation.text.KeyboardOptions + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:57:8: error: unresolved reference: androidx +import androidx.compose.ui.text.input.KeyboardType + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:58:8: error: unresolved reference: androidx +import androidx.compose.ui.unit.dp + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:59:8: error: unresolved reference: androidx +import androidx.hilt.navigation.compose.hiltViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:60:8: error: unresolved reference: androidx +import androidx.lifecycle.compose.collectAsStateWithLifecycle + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:81:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonArray + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:82:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonNull + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:83:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:84:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonPrimitive + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:85:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.doubleOrNull + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:86:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.jsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:87:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.jsonPrimitive + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:89:8: error: unresolved reference: ExperimentalMaterial3Api +@OptIn(ExperimentalMaterial3Api::class) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:89:8: error: an annotation argument must be a compile-time constant +@OptIn(ExperimentalMaterial3Api::class) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:90:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:92:21: error: unresolved reference: PaddingValues + contentPadding: PaddingValues, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:93:35: error: unresolved reference: hiltViewModel + viewModel: TrackerViewModel = hiltViewModel(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:97:43: error: unresolved reference: it + val currentPending = pending.filter { it.planId == state.selectedPlanId } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:98:21: error: unresolved reference: remember + var planMenu by remember { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:98:32: error: unresolved reference: mutableStateOf + var planMenu by remember { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:100:5: error: unresolved reference: Scaffold + Scaffold( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:101:20: error: unresolved reference: Modifier + modifier = Modifier.padding(contentPadding), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:103:13: error: unresolved reference: TopAppBar + TopAppBar( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:105:21: error: unresolved reference: Column + Column { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:106:25: error: unresolved reference: Text + Text(state.plan?.title ?: "boehmitools Training", maxLines = 2) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:107:59: error: unresolved reference: Text + if (state.saveState.isNotBlank()) Text(state.saveState, style = MaterialTheme.typography.labelSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:107:89: error: unresolved reference: MaterialTheme + if (state.saveState.isNotBlank()) Text(state.saveState, style = MaterialTheme.typography.labelSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:111:21: error: unresolved reference: Box + Box { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:112:25: error: unresolved reference: IconButton + IconButton(onClick = { planMenu = true }) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:113:29: error: unresolved reference: Icon + Icon(Icons.Default.MoreVert, contentDescription = "Plan wechseln") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:113:34: error: unresolved reference: Icons + Icon(Icons.Default.MoreVert, contentDescription = "Plan wechseln") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:115:25: error: unresolved reference: DropdownMenu + DropdownMenu(expanded = planMenu, onDismissRequest = { planMenu = false }) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:116:41: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + state.plans.forEach { plan -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:116:51: error: cannot infer a type for this parameter. Please specify it explicitly. + state.plans.forEach { plan -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:117:33: error: unresolved reference: DropdownMenuItem + DropdownMenuItem( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:118:46: error: unresolved reference: Text + text = { Text(plan.title) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:127:9: error: cannot infer a type for this parameter. Please specify it explicitly. + ) { inner -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:128:9: error: unresolved reference: Column + Column(Modifier.fillMaxSize().padding(inner)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:128:16: error: unresolved reference: Modifier + Column(Modifier.fillMaxSize().padding(inner)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:130:13: error: unresolved reference: ScrollableTabRow + ScrollableTabRow(selectedTabIndex = tabs.indexOfFirst { it.first == state.tab }.coerceAtLeast(0)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:132:21: error: unresolved reference: Tab + Tab(selected = state.tab == id, onClick = { viewModel.setTab(id) }, text = { Text(label) }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:132:98: error: unresolved reference: Text + Tab(selected = state.tab == id, onClick = { viewModel.setTab(id) }, text = { Text(label) }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:136:56: error: unresolved reference: Box + state.loading && state.plan == null -> Box(Modifier.fillMaxSize().padding(32.dp)) { CircularProgressIndicator() } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:136:60: error: unresolved reference: Modifier + state.loading && state.plan == null -> Box(Modifier.fillMaxSize().padding(32.dp)) { CircularProgressIndicator() } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:136:94: error: unresolved reference: dp + state.loading && state.plan == null -> Box(Modifier.fillMaxSize().padding(32.dp)) { CircularProgressIndicator() } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:136:101: error: unresolved reference: CircularProgressIndicator + state.loading && state.plan == null -> Box(Modifier.fillMaxSize().padding(32.dp)) { CircularProgressIndicator() } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:149:27: error: cannot infer a type for this parameter. Please specify it explicitly. + state.conflict?.let { conflict -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:150:9: error: unresolved reference: AlertDialog + AlertDialog( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:152:23: error: unresolved reference: Text + title = { Text("Revisionskonflikt") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:153:22: error: unresolved reference: Text + text = { Text("Der Server steht bereits auf Revision ${conflict.currentRevision}. Deine lokale ?nderung wird nicht automatisch ?berschrieben. Lade den Serverstand neu und wende die ?nderung bewusst erneut an.") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:154:31: error: unresolved reference: TextButton + confirmButton = { TextButton(onClick = viewModel::reloadAfterConflict) { Text("Serverstand neu laden") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:154:86: error: unresolved reference: Text + confirmButton = { TextButton(onClick = viewModel::reloadAfterConflict) { Text("Serverstand neu laden") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:155:31: error: unresolved reference: TextButton + dismissButton = { TextButton(onClick = viewModel::dismissConflict) { Text("Lokale Ansicht behalten") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:155:82: error: unresolved reference: Text + dismissButton = { TextButton(onClick = viewModel::dismissConflict) { Text("Lokale Ansicht behalten") } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:160:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:162:5: error: unresolved reference: Column + Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:162:12: error: unresolved reference: Modifier + Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:162:46: error: unresolved reference: dp + Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:162:73: error: unresolved reference: Arrangement + Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:162:97: error: unresolved reference: dp + Column(Modifier.fillMaxSize().padding(24.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:163:9: error: unresolved reference: Text + Text(message, color = MaterialTheme.colorScheme.error) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:163:31: error: unresolved reference: MaterialTheme + Text(message, color = MaterialTheme.colorScheme.error) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:164:9: error: unresolved reference: Button + Button(onClick = retry) { Icon(Icons.Default.Refresh, null); Spacer(Modifier.width(8.dp)); Text("Neu laden") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:164:35: error: unresolved reference: Icon + Button(onClick = retry) { Icon(Icons.Default.Refresh, null); Spacer(Modifier.width(8.dp)); Text("Neu laden") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:164:40: error: unresolved reference: Icons + Button(onClick = retry) { Icon(Icons.Default.Refresh, null); Spacer(Modifier.width(8.dp)); Text("Neu laden") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:164:70: error: unresolved reference: Spacer + Button(onClick = retry) { Icon(Icons.Default.Refresh, null); Spacer(Modifier.width(8.dp)); Text("Neu laden") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:164:77: error: unresolved reference: Modifier + Button(onClick = retry) { Icon(Icons.Default.Refresh, null); Spacer(Modifier.width(8.dp)); Text("Neu laden") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:164:94: error: unresolved reference: dp + Button(onClick = retry) { Icon(Icons.Default.Refresh, null); Spacer(Modifier.width(8.dp)); Text("Neu laden") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:164:100: error: unresolved reference: Text + Button(onClick = retry) { Icon(Icons.Default.Refresh, null); Spacer(Modifier.width(8.dp)); Text("Neu laden") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:168:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:173:5: error: unresolved reference: Column + Column( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:174:9: error: unresolved reference: Modifier + Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:174:47: error: unresolved reference: rememberScrollState + Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:174:81: error: unresolved reference: dp + Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:175:31: error: unresolved reference: Arrangement + verticalArrangement = Arrangement.spacedBy(12.dp), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:175:55: error: unresolved reference: dp + verticalArrangement = Arrangement.spacedBy(12.dp), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:177:39: error: unresolved reference: Text + if (state.error.isNotBlank()) Text(state.error, color = MaterialTheme.colorScheme.error) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:177:65: error: unresolved reference: MaterialTheme + if (state.error.isNotBlank()) Text(state.error, color = MaterialTheme.colorScheme.error) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:184:13: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:184:18: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:185:17: error: unresolved reference: Column + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:185:24: error: unresolved reference: Modifier + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:185:44: error: unresolved reference: dp + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:185:71: error: unresolved reference: Arrangement + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:185:95: error: unresolved reference: dp + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:186:21: error: unresolved reference: Text + Text(rotation.label, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:186:50: error: unresolved reference: MaterialTheme + Text(rotation.label, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:186:101: error: unresolved reference: FontWeight + Text(rotation.label, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:189:25: error: unresolved reference: Divider + Divider() + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:196:9: error: unresolved reference: Spacer + Spacer(Modifier.height(24.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:196:16: error: unresolved reference: Modifier + Spacer(Modifier.height(24.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:196:35: error: unresolved reference: dp + Spacer(Modifier.height(24.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:200:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:202:5: error: unresolved reference: OutlinedCard + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:202:18: error: unresolved reference: Modifier + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:203:9: error: unresolved reference: Column + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:203:16: error: unresolved reference: Modifier + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:203:36: error: unresolved reference: dp + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:203:63: error: unresolved reference: Arrangement + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:203:86: error: unresolved reference: dp + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:204:13: error: unresolved reference: Row + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:204:41: error: unresolved reference: Arrangement + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:204:64: error: unresolved reference: dp + Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:205:17: error: unresolved reference: Icon + Icon(Icons.Default.CloudOff, null) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:205:22: error: unresolved reference: Icons + Icon(Icons.Default.CloudOff, null) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:206:17: error: unresolved reference: Text + Text("${pending.size} ausstehende ?nderung(en)", fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:206:79: error: unresolved reference: FontWeight + Text("${pending.size} ausstehende ?nderung(en)", fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:209:17: error: unresolved reference: Text + Text("${item.sessionKey} ? Revision ${item.expectedRevision} ? ${item.state}", style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:209:104: error: unresolved reference: MaterialTheme + Text("${item.sessionKey} ? Revision ${item.expectedRevision} ? ${item.state}", style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:211:21: error: unresolved reference: Text + Text(item.lastError, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:211:50: error: unresolved reference: MaterialTheme + Text(item.lastError, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:211:91: error: unresolved reference: MaterialTheme + Text(item.lastError, color = MaterialTheme.colorScheme.error, style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:212:21: error: unresolved reference: TextButton + TextButton(onClick = { vm.discardQueuedConflict(item.id) }) { Text("Konflikt-Patch verwerfen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:212:83: error: unresolved reference: Text + TextButton(onClick = { vm.discardQueuedConflict(item.id) }) { Text("Konflikt-Patch verwerfen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:219:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:221:5: error: unresolved reference: Column + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:221:34: error: unresolved reference: Arrangement + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:221:57: error: unresolved reference: dp + Column(verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:222:9: error: unresolved reference: Row + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:222:13: error: unresolved reference: Modifier + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:222:54: error: unresolved reference: rememberScrollState + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:222:102: error: unresolved reference: Arrangement + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:222:125: error: unresolved reference: dp + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:223:48: error: unresolved reference: FilterChip + (1..plan.weeks).forEach { value -> FilterChip(selected = value == week, onClick = { vm.setWeek(value) }, label = { Text("W$value") }) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:223:128: error: unresolved reference: Text + (1..plan.weeks).forEach { value -> FilterChip(selected = value == week, onClick = { vm.setWeek(value) }, label = { Text("W$value") }) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:225:9: error: unresolved reference: Row + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:225:13: error: unresolved reference: Modifier + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:225:54: error: unresolved reference: rememberScrollState + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:225:102: error: unresolved reference: Arrangement + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:225:125: error: unresolved reference: dp + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:226:42: error: unresolved reference: FilterChip + plan.days.forEach { value -> FilterChip(selected = value.num == day, onClick = { vm.setDay(value.num) }, label = { Text("Tag ${value.num}") }) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:226:128: error: unresolved reference: Text + plan.days.forEach { value -> FilterChip(selected = value.num == day, onClick = { vm.setDay(value.num) }, label = { Text("Tag ${value.num}") }) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:231:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:232:62: error: unresolved reference: JsonObject +private fun SessionHeader(plan: PlanUi, day: DayUi, session: JsonObject, week: Int, vm: TrackerViewModel) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:234:5: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:234:10: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:235:9: error: unresolved reference: Column + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:235:16: error: unresolved reference: Modifier + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:235:36: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:235:63: error: unresolved reference: Arrangement + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:235:87: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:236:13: error: unresolved reference: Text + Text("Woche $week ? Tag ${day.num}", style = MaterialTheme.typography.labelLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:236:58: error: unresolved reference: MaterialTheme + Text("Woche $week ? Tag ${day.num}", style = MaterialTheme.typography.labelLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:237:13: error: unresolved reference: Text + Text(day.focus, style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:237:37: error: unresolved reference: MaterialTheme + Text(day.focus, style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:238:13: error: unresolved reference: Text + Text("Planrevision R${session.int("plan_revision", plan.publishedRevision)} ? Status: $status", style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:238:117: error: unresolved reference: MaterialTheme + Text("Planrevision R${session.int("plan_revision", plan.publishedRevision)} ? Status: $status", style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:240:34: error: unresolved reference: Row + "in_progress" -> Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:240:62: error: unresolved reference: Arrangement + "in_progress" -> Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:240:85: error: unresolved reference: dp + "in_progress" -> Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:241:21: error: unresolved reference: Button + Button(onClick = { vm.setSessionStatus("completed") }) { Icon(Icons.Default.CheckCircle, null); Spacer(Modifier.width(6.dp)); Text("Abschlie?en") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:241:78: error: unresolved reference: Icon + Button(onClick = { vm.setSessionStatus("completed") }) { Icon(Icons.Default.CheckCircle, null); Spacer(Modifier.width(6.dp)); Text("Abschlie?en") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:241:83: error: unresolved reference: Icons + Button(onClick = { vm.setSessionStatus("completed") }) { Icon(Icons.Default.CheckCircle, null); Spacer(Modifier.width(6.dp)); Text("Abschlie?en") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:241:117: error: unresolved reference: Spacer + Button(onClick = { vm.setSessionStatus("completed") }) { Icon(Icons.Default.CheckCircle, null); Spacer(Modifier.width(6.dp)); Text("Abschlie?en") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:241:124: error: unresolved reference: Modifier + Button(onClick = { vm.setSessionStatus("completed") }) { Icon(Icons.Default.CheckCircle, null); Spacer(Modifier.width(6.dp)); Text("Abschlie?en") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:241:141: error: unresolved reference: dp + Button(onClick = { vm.setSessionStatus("completed") }) { Icon(Icons.Default.CheckCircle, null); Spacer(Modifier.width(6.dp)); Text("Abschlie?en") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:241:147: error: unresolved reference: Text + Button(onClick = { vm.setSessionStatus("completed") }) { Icon(Icons.Default.CheckCircle, null); Spacer(Modifier.width(6.dp)); Text("Abschlie?en") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:242:21: error: unresolved reference: OutlinedButton + OutlinedButton(onClick = { vm.setSessionStatus("stopped") }) { Icon(Icons.Default.Stop, null); Spacer(Modifier.width(6.dp)); Text("Stoppen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:242:84: error: unresolved reference: Icon + OutlinedButton(onClick = { vm.setSessionStatus("stopped") }) { Icon(Icons.Default.Stop, null); Spacer(Modifier.width(6.dp)); Text("Stoppen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:242:89: error: unresolved reference: Icons + OutlinedButton(onClick = { vm.setSessionStatus("stopped") }) { Icon(Icons.Default.Stop, null); Spacer(Modifier.width(6.dp)); Text("Stoppen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:242:116: error: unresolved reference: Spacer + OutlinedButton(onClick = { vm.setSessionStatus("stopped") }) { Icon(Icons.Default.Stop, null); Spacer(Modifier.width(6.dp)); Text("Stoppen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:242:123: error: unresolved reference: Modifier + OutlinedButton(onClick = { vm.setSessionStatus("stopped") }) { Icon(Icons.Default.Stop, null); Spacer(Modifier.width(6.dp)); Text("Stoppen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:242:140: error: unresolved reference: dp + OutlinedButton(onClick = { vm.setSessionStatus("stopped") }) { Icon(Icons.Default.Stop, null); Spacer(Modifier.width(6.dp)); Text("Stoppen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:242:146: error: unresolved reference: Text + OutlinedButton(onClick = { vm.setSessionStatus("stopped") }) { Icon(Icons.Default.Stop, null); Spacer(Modifier.width(6.dp)); Text("Stoppen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:244:30: error: unresolved reference: Button + "stopped" -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Fortsetzen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:244:89: error: unresolved reference: Icon + "stopped" -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Fortsetzen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:244:94: error: unresolved reference: Icons + "stopped" -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Fortsetzen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:244:126: error: unresolved reference: Spacer + "stopped" -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Fortsetzen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:244:133: error: unresolved reference: Modifier + "stopped" -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Fortsetzen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:244:150: error: unresolved reference: dp + "stopped" -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Fortsetzen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:244:156: error: unresolved reference: Text + "stopped" -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Fortsetzen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:245:32: error: unresolved reference: OutlinedButton + "completed" -> OutlinedButton(onClick = { vm.setSessionStatus("in_progress") }) { Text("Wieder ?ffnen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:245:99: error: unresolved reference: Text + "completed" -> OutlinedButton(onClick = { vm.setSessionStatus("in_progress") }) { Text("Wieder ?ffnen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:246:25: error: unresolved reference: Button + else -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Session starten") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:246:84: error: unresolved reference: Icon + else -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Session starten") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:246:89: error: unresolved reference: Icons + else -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Session starten") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:246:121: error: unresolved reference: Spacer + else -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Session starten") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:246:128: error: unresolved reference: Modifier + else -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Session starten") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:246:145: error: unresolved reference: dp + else -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Session starten") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:246:151: error: unresolved reference: Text + else -> Button(onClick = { vm.setSessionStatus("in_progress") }) { Icon(Icons.Default.PlayArrow, null); Spacer(Modifier.width(6.dp)); Text("Session starten") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:248:13: error: unresolved reference: TextButton + TextButton(onClick = vm::resetSession) { Text("Session zur?cksetzen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:248:54: error: unresolved reference: Text + TextButton(onClick = vm::resetSession) { Text("Session zur?cksetzen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:253:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:254:87: error: unresolved reference: JsonObject +private fun SequenceCard(title: String, prefix: String, items: List, session: JsonObject, vm: TrackerViewModel) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:256:5: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:256:10: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:257:9: error: unresolved reference: Column + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:257:16: error: unresolved reference: Modifier + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:257:36: error: unresolved reference: dp + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:257:63: error: unresolved reference: Arrangement + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:257:86: error: unresolved reference: dp + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:258:13: error: unresolved reference: Text + Text(title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:258:33: error: unresolved reference: MaterialTheme + Text(title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:258:84: error: unresolved reference: FontWeight + Text(title, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:262:17: error: unresolved reference: Row + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:262:21: error: unresolved reference: Modifier + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:262:70: error: unresolved reference: Arrangement + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:262:93: error: unresolved reference: dp + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:263:21: error: unresolved reference: Checkbox + Checkbox(checked = item.string("completion_status") == "completed" || item.bool("done"), onCheckedChange = { vm.setSequenceStatus(id, it) }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:263:155: error: unresolved reference: it + Checkbox(checked = item.string("completion_status") == "completed" || item.bool("done"), onCheckedChange = { vm.setSequenceStatus(id, it) }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:264:21: error: unresolved reference: Text + Text(label, modifier = Modifier.padding(top = 12.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:264:44: error: unresolved reference: Modifier + Text(label, modifier = Modifier.padding(top = 12.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:264:70: error: unresolved reference: dp + Text(label, modifier = Modifier.padding(top = 12.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:271:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:272:57: error: unresolved reference: JsonObject +private fun ExerciseCard(exercise: ExerciseUi, session: JsonObject, plan: PlanUi, vm: TrackerViewModel) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:279:28: error: unresolved reference: remember + var progressionMenu by remember(exercise.id, progression) { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:279:65: error: unresolved reference: mutableStateOf + var progressionMenu by remember(exercise.id, progression) { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:281:5: error: unresolved reference: Column + Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:281:12: error: unresolved reference: Modifier + Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:281:59: error: unresolved reference: Arrangement + Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:281:82: error: unresolved reference: dp + Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:282:9: error: unresolved reference: Text + Text(exercise.name, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:282:37: error: unresolved reference: MaterialTheme + Text(exercise.name, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:282:88: error: unresolved reference: FontWeight + Text(exercise.name, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:283:40: error: unresolved reference: Text + if (exercise.cue.isNotBlank()) Text(exercise.cue, style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:283:67: error: unresolved reference: MaterialTheme + if (exercise.cue.isNotBlank()) Text(exercise.cue, style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:284:9: error: unresolved reference: Text + Text("ID ${exercise.id}", style = MaterialTheme.typography.labelSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:284:43: error: unresolved reference: MaterialTheme + Text("ID ${exercise.id}", style = MaterialTheme.typography.labelSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:285:9: error: unresolved reference: Column + Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:285:38: error: unresolved reference: Arrangement + Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:285:61: error: unresolved reference: dp + Column(verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:286:13: error: unresolved reference: Text + Text("Status", style = MaterialTheme.typography.labelMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:286:36: error: unresolved reference: MaterialTheme + Text("Status", style = MaterialTheme.typography.labelMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:287:13: error: unresolved reference: Row + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:287:17: error: unresolved reference: Modifier + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:287:58: error: unresolved reference: rememberScrollState + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:287:106: error: unresolved reference: Arrangement + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:287:129: error: unresolved reference: dp + Row(Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()), horizontalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:289:21: error: unresolved reference: FilterChip + FilterChip(selected = status == value, onClick = { vm.setItemStatus(exercise.id, value) }, label = { Text(label) }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:289:122: error: unresolved reference: Text + FilterChip(selected = status == value, onClick = { vm.setItemStatus(exercise.id, value) }, label = { Text(label) }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:294:13: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:296:78: error: unresolved reference: it + onValueChange = { vm.setItemText(exercise.id, "skip_reason", it) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:297:28: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:298:27: error: unresolved reference: Text + label = { Text("Grund") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:301:13: error: unresolved reference: Column + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:301:42: error: unresolved reference: Arrangement + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:301:65: error: unresolved reference: dp + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:302:17: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:304:74: error: unresolved reference: it + onValueChange = { vm.setItemProgression(exercise.id, it) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:305:32: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:306:31: error: unresolved reference: Text + label = { Text("Progressionsstufe") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:307:40: error: unresolved reference: Text + supportingText = { Text("Stufe ?berschreibt bei Bedarf das Ergebnisschema") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:310:21: error: unresolved reference: Box + Box { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:311:25: error: unresolved reference: OutlinedButton + OutlinedButton(onClick = { progressionMenu = true }, modifier = Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:311:89: error: unresolved reference: Modifier + OutlinedButton(onClick = { progressionMenu = true }, modifier = Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:312:29: error: unresolved reference: Text + Text("Stufe aus Plan ausw?hlen") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:314:25: error: unresolved reference: DropdownMenu + DropdownMenu(expanded = progressionMenu, onDismissRequest = { progressionMenu = false }) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:316:33: error: unresolved reference: DropdownMenuItem + DropdownMenuItem( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:317:46: error: unresolved reference: Text + text = { Text(step.name) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:327:60: error: unresolved reference: JsonObject + manualOverride = item["result_format"] as? JsonObject, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:331:13: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:333:71: error: unresolved reference: it + onValueChange = { vm.setItemText(exercise.id, "note", it) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:334:28: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:335:27: error: unresolved reference: Text + label = { Text("Sessionnotiz") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:343:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:346:21: error: unresolved reference: JsonObject + manualOverride: JsonObject?, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:347:16: error: unresolved reference: JsonObject + onChange: (JsonObject?) -> Unit, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:349:21: error: unresolved reference: remember + var expanded by remember(manualOverride) { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:349:48: error: unresolved reference: mutableStateOf + var expanded by remember(manualOverride) { mutableStateOf(false) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:359:48: error: unresolved reference: JsonObject + fun changed(field: String, value: String): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:360:22: error: not enough information to infer type variable V + val values = linkedMapOf( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:360:42: error: unresolved reference: kotlinx + val values = linkedMapOf( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:361:23: error: unresolved reference: JsonPrimitive + "mode" to JsonPrimitive(mode), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:362:30: error: unresolved reference: JsonPrimitive + "weight_mode" to JsonPrimitive(weightMode), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:363:29: error: unresolved reference: JsonPrimitive + "laterality" to JsonPrimitive(laterality), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:364:29: error: unresolved reference: JsonPrimitive + "sides_mode" to JsonPrimitive(sidesMode), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:366:25: error: unresolved reference: JsonPrimitive + values[field] = JsonPrimitive(value) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:367:38: error: unresolved reference: JsonPrimitive + if ((values["laterality"] as JsonPrimitive).content == "bilateral") { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:368:36: error: unresolved reference: JsonPrimitive + values["sides_mode"] = JsonPrimitive("same") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:370:16: error: unresolved reference: JsonObject + return JsonObject(values) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:373:5: error: unresolved reference: OutlinedCard + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:373:18: error: unresolved reference: Modifier + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:374:9: error: unresolved reference: Column + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:374:16: error: unresolved reference: Modifier + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:374:36: error: unresolved reference: dp + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:374:63: error: unresolved reference: Arrangement + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:374:86: error: unresolved reference: dp + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:375:13: error: unresolved reference: Row + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:375:17: error: unresolved reference: Modifier + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:375:66: error: unresolved reference: Arrangement + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:376:17: error: unresolved reference: Column + Column(Modifier.weight(1f)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:376:24: error: unresolved reference: Modifier + Column(Modifier.weight(1f)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:377:21: error: unresolved reference: Text + Text("Manuelle Session-Anpassung", style = MaterialTheme.typography.labelLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:377:64: error: unresolved reference: MaterialTheme + Text("Manuelle Session-Anpassung", style = MaterialTheme.typography.labelLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:378:21: error: unresolved reference: Text + Text( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:380:33: error: unresolved reference: MaterialTheme + style = MaterialTheme.typography.bodySmall, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:383:17: error: unresolved reference: TextButton + TextButton(onClick = { expanded = !expanded }) { Text(if (expanded) "Schlie?en" else "Anpassen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:383:66: error: unresolved reference: Text + TextButton(onClick = { expanded = !expanded }) { Text(if (expanded) "Schlie?en" else "Anpassen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:393:21: error: unresolved reference: OutlinedButton + OutlinedButton(onClick = { onChange(null) }, modifier = Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:393:77: error: unresolved reference: Modifier + OutlinedButton(onClick = { onChange(null) }, modifier = Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:394:25: error: unresolved reference: Text + Text("Manuelle Anpassung zur?cksetzen") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:402:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:404:5: error: unresolved reference: Column + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:404:34: error: unresolved reference: Arrangement + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:404:57: error: unresolved reference: dp + Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:405:9: error: unresolved reference: Text + Text(label, style = MaterialTheme.typography.labelMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:405:29: error: unresolved reference: MaterialTheme + Text(label, style = MaterialTheme.typography.labelMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:407:13: error: unresolved reference: FilterChip + FilterChip( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:410:27: error: unresolved reference: Text + label = { Text(value) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:411:28: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:417:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:418:56: error: unresolved reference: JsonObject +private fun ResultEditor(schema: ResultSchemaUi, data: JsonObject, onChange: (JsonObject) -> Unit) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:418:79: error: unresolved reference: JsonObject +private fun ResultEditor(schema: ResultSchemaUi, data: JsonObject, onChange: (JsonObject) -> Unit) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:419:5: error: unresolved reference: OutlinedCard + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:419:18: error: unresolved reference: Modifier + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:420:9: error: unresolved reference: Column + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:420:16: error: unresolved reference: Modifier + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:420:36: error: unresolved reference: dp + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:420:63: error: unresolved reference: Arrangement + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:420:86: error: unresolved reference: dp + Column(Modifier.padding(12.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:421:13: error: unresolved reference: Text + Text("Ergebnis ? ${schema.source}", style = MaterialTheme.typography.labelLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:421:57: error: unresolved reference: MaterialTheme + Text("Ergebnis ? ${schema.source}", style = MaterialTheme.typography.labelLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:422:13: error: unresolved reference: Text + Text("${schema.mode}, ${schema.laterality}, Gewicht ${schema.weightMode}", style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:422:96: error: unresolved reference: MaterialTheme + Text("${schema.mode}, ${schema.laterality}, Gewicht ${schema.weightMode}", style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:424:17: error: unresolved reference: Text + Text("F?r diese Stufe ist kein numerischer Messwert vorgesehen.") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:429:17: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:431:39: error: cannot infer a type for this parameter. Please specify it explicitly. + onValueChange = { raw -> onChange(data.updated("weight_kg", raw.replace(',', '.').toDoubleOrNull()?.let(::JsonPrimitive) ?: JsonNull)) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:431:145: error: unresolved reference: JsonNull + onValueChange = { raw -> onChange(data.updated("weight_kg", raw.replace(',', '.').toDoubleOrNull()?.let(::JsonPrimitive) ?: JsonNull)) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:432:32: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:433:31: error: unresolved reference: Text + label = { Text(if (schema.weightMode == "required") "Gewicht kg (Pflicht)" else "Gewicht kg (optional)") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:434:39: error: unresolved reference: KeyboardOptions + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:434:70: error: unresolved reference: KeyboardType + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:439:17: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:441:39: error: cannot infer a type for this parameter. Please specify it explicitly. + onValueChange = { raw -> raw.toIntOrNull()?.let { onChange(resultDataWithSets(data, it)) } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:441:105: error: unresolved reference: it + onValueChange = { raw -> raw.toIntOrNull()?.let { onChange(resultDataWithSets(data, it)) } }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:442:32: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:443:31: error: unresolved reference: Text + label = { Text("S?tze") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:444:39: error: unresolved reference: KeyboardOptions + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:444:70: error: unresolved reference: KeyboardType + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:451:21: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:453:43: error: cannot infer a type for this parameter. Please specify it explicitly. + onValueChange = { raw -> onChange(resultDataWithValue(data, "left_values", index, raw.replace(',', '.').toDoubleOrNull())) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:454:36: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:455:35: error: unresolved reference: Text + label = { Text("Satz ${index + 1} links") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:456:43: error: unresolved reference: KeyboardOptions + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:456:74: error: unresolved reference: KeyboardType + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:459:21: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:461:43: error: cannot infer a type for this parameter. Please specify it explicitly. + onValueChange = { raw -> onChange(resultDataWithValue(data, "right_values", index, raw.replace(',', '.').toDoubleOrNull())) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:462:36: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:463:35: error: unresolved reference: Text + label = { Text("Satz ${index + 1} rechts") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:464:43: error: unresolved reference: KeyboardOptions + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:464:74: error: unresolved reference: KeyboardType + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:468:21: error: unresolved reference: OutlinedTextField + OutlinedTextField( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:470:43: error: cannot infer a type for this parameter. Please specify it explicitly. + onValueChange = { raw -> onChange(resultDataWithValue(data, "values", index, raw.replace(',', '.').toDoubleOrNull())) }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:471:36: error: unresolved reference: Modifier + modifier = Modifier.fillMaxWidth(), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:472:35: error: unresolved reference: Text + label = { Text("Satz ${index + 1}${if (schema.laterality == "unilateral") " je Seite" else ""}") }, + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:473:43: error: unresolved reference: KeyboardOptions + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:473:74: error: unresolved reference: KeyboardType + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:478:13: error: unresolved reference: Text + Text("Gespeichert als: ${formatResultData(data)}", style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:478:72: error: unresolved reference: MaterialTheme + Text("Gespeichert als: ${formatResultData(data)}", style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:483:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:486:70: error: unresolved reference: it + val sessions = state.tracker.obj("sessions").values.mapNotNull { it as? JsonObject } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:486:77: error: unresolved reference: JsonObject + val sessions = state.tracker.obj("sessions").values.mapNotNull { it as? JsonObject } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:487:38: error: unresolved reference: it + val completed = sessions.count { it.string("status") == "completed" } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:488:36: error: unresolved reference: it + val stopped = sessions.count { it.string("status") == "stopped" } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:489:71: error: unresolved reference: it + val analysisState = state.response.obj("analysis_state").takeIf { it.isNotEmpty() } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:492:63: error: unresolved reference: it + val cache = state.response.obj("analysis_cache").takeIf { it.isNotEmpty() } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:494:68: error: unresolved reference: JsonObject + val weekRecord = cache.obj("weeks")[state.week.toString()] as? JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:495:40: error: unresolved reference: JsonObject + val overall = cache["overall"] as? JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:497:5: error: unresolved reference: Column + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:497:12: error: unresolved reference: Modifier + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:497:50: error: unresolved reference: rememberScrollState + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:497:84: error: unresolved reference: dp + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:497:111: error: unresolved reference: Arrangement + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:497:135: error: unresolved reference: dp + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:498:9: error: unresolved reference: Text + Text("Fortschritt", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:498:37: error: unresolved reference: MaterialTheme + Text("Fortschritt", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:499:9: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:499:14: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:500:13: error: unresolved reference: Column + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:500:20: error: unresolved reference: Modifier + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:500:40: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:500:67: error: unresolved reference: Arrangement + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:500:90: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:501:17: error: unresolved reference: Text + Text("Lokale ?bersicht", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:501:50: error: unresolved reference: MaterialTheme + Text("Lokale ?bersicht", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:502:17: error: unresolved reference: Text + Text("$completed abgeschlossene Sessions ? $stopped gestoppte Sessions ? ${sessions.size} angelegte Sessions") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:503:17: error: unresolved reference: Text + Text("Trackerrevision ${state.tracker.int("revision", 1)} ? Planrevision ${plan.publishedRevision}") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:507:9: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:507:14: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:508:13: error: unresolved reference: Column + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:508:20: error: unresolved reference: Modifier + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:508:40: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:508:67: error: unresolved reference: Arrangement + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:508:91: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:509:17: error: unresolved reference: Text + Text("Wochenstatus", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:509:46: error: unresolved reference: MaterialTheme + Text("Wochenstatus", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:511:17: error: unresolved reference: FilterChip + FilterChip(selected = closed, onClick = { vm.setWeekClosed(!closed) }, label = { Text(if (closed) "Woche ${state.week} abgeschlossen" else "Woche ${state.week} l?uft") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:511:98: error: unresolved reference: Text + FilterChip(selected = closed, onClick = { vm.setWeekClosed(!closed) }, label = { Text(if (closed) "Woche ${state.week} abgeschlossen" else "Woche ${state.week} l?uft") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:512:17: error: unresolved reference: Text + Text("Eine laufende Woche erzeugt eine Zwischenanalyse, eine geschlossene Woche eine Abschlussanalyse.", style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:512:130: error: unresolved reference: MaterialTheme + Text("Eine laufende Woche erzeugt eine Zwischenanalyse, eine geschlossene Woche eine Abschlussanalyse.", style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:515:9: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:515:14: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:516:13: error: unresolved reference: Column + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:516:20: error: unresolved reference: Modifier + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:516:40: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:516:67: error: unresolved reference: Arrangement + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:516:91: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:517:17: error: unresolved reference: Text + Text("Manuelle Analyse", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:517:50: error: unresolved reference: MaterialTheme + Text("Manuelle Analyse", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:518:17: error: unresolved reference: Button + Button(onClick = { vm.startAnalysis("week", state.week) }, enabled = !running) { Text("Woche ${state.week} analysieren") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:518:98: error: unresolved reference: Text + Button(onClick = { vm.startAnalysis("week", state.week) }, enabled = !running) { Text("Woche ${state.week} analysieren") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:519:17: error: unresolved reference: OutlinedButton + OutlinedButton(onClick = { vm.startAnalysis("overall") }, enabled = !running) { Text("Gesamtanalyse") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:519:97: error: unresolved reference: Text + OutlinedButton(onClick = { vm.startAnalysis("overall") }, enabled = !running) { Text("Gesamtanalyse") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:521:21: error: unresolved reference: CircularProgressIndicator + CircularProgressIndicator() + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:522:21: error: unresolved reference: Text + Text(analysisState.string("message", "Analysejob l?uft ?")) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:523:64: error: unresolved reference: Text + } else if (state.analysisMessage.isNotBlank()) Text(state.analysisMessage) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:524:47: error: unresolved reference: Text + if (state.error.isNotBlank()) Text(state.error, color = MaterialTheme.colorScheme.error) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:524:73: error: unresolved reference: MaterialTheme + if (state.error.isNotBlank()) Text(state.error, color = MaterialTheme.colorScheme.error) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:529:9: error: unresolved reference: Spacer + Spacer(Modifier.height(24.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:529:16: error: unresolved reference: Modifier + Spacer(Modifier.height(24.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:529:35: error: unresolved reference: dp + Spacer(Modifier.height(24.dp)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:533:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:534:49: error: unresolved reference: JsonObject +private fun AnalysisCard(title: String, record: JsonObject?) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:535:5: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:535:10: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:536:9: error: unresolved reference: Column + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:536:16: error: unresolved reference: Modifier + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:536:36: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:536:63: error: unresolved reference: Arrangement + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:536:86: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:537:13: error: unresolved reference: Text + Text(title, style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:537:33: error: unresolved reference: MaterialTheme + Text(title, style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:539:17: error: unresolved reference: Text + Text("Noch keine aktuelle Analyse vorhanden.") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:542:17: error: unresolved reference: Text + Text(response.string("headline", "Analyse"), style = MaterialTheme.typography.titleLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:542:70: error: unresolved reference: MaterialTheme + Text(response.string("headline", "Analyse"), style = MaterialTheme.typography.titleLarge) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:543:17: error: unresolved reference: Text + Text(response.string("summary")) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:544:56: error: unresolved reference: it + response.array("metrics").mapNotNull { it as? JsonObject }.forEach { metric -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:544:63: error: unresolved reference: JsonObject + response.array("metrics").mapNotNull { it as? JsonObject }.forEach { metric -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:544:76: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + response.array("metrics").mapNotNull { it as? JsonObject }.forEach { metric -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:544:86: error: cannot infer a type for this parameter. Please specify it explicitly. + response.array("metrics").mapNotNull { it as? JsonObject }.forEach { metric -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:545:21: error: unresolved reference: AssistChip + AssistChip(onClick = {}, label = { Text("${metric.string("label")}: ${metric.string("value")}") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:545:56: error: unresolved reference: Text + AssistChip(onClick = {}, label = { Text("${metric.string("label")}: ${metric.string("value")}") }) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:547:65: error: unresolved reference: it + response.array("exercise_updates").mapNotNull { it as? JsonObject }.take(6).forEach { update -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:547:72: error: unresolved reference: JsonObject + response.array("exercise_updates").mapNotNull { it as? JsonObject }.take(6).forEach { update -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:547:93: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + response.array("exercise_updates").mapNotNull { it as? JsonObject }.take(6).forEach { update -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:547:103: error: cannot infer a type for this parameter. Please specify it explicitly. + response.array("exercise_updates").mapNotNull { it as? JsonObject }.take(6).forEach { update -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:548:21: error: unresolved reference: OutlinedCard + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:548:34: error: unresolved reference: Modifier + OutlinedCard(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:549:25: error: unresolved reference: Column + Column(Modifier.padding(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:549:32: error: unresolved reference: Modifier + Column(Modifier.padding(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:549:52: error: unresolved reference: dp + Column(Modifier.padding(10.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:550:29: error: unresolved reference: Text + Text(update.string("name"), fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:550:70: error: unresolved reference: FontWeight + Text(update.string("name"), fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:551:29: error: unresolved reference: Text + Text(update.string("next_action")) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:552:74: error: unresolved reference: Text + if (update.string("criterion").isNotBlank()) Text("Kriterium: ${update.string("criterion")}", style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:552:131: error: unresolved reference: MaterialTheme + if (update.string("criterion").isNotBlank()) Text("Kriterium: ${update.string("criterion")}", style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:561:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:563:5: error: unresolved reference: Column + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:563:12: error: unresolved reference: Modifier + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:563:50: error: unresolved reference: rememberScrollState + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:563:84: error: unresolved reference: dp + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:563:111: error: unresolved reference: Arrangement + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:563:135: error: unresolved reference: dp + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:564:9: error: unresolved reference: Text + Text(plan.title, style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:564:34: error: unresolved reference: MaterialTheme + Text(plan.title, style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:565:9: error: unresolved reference: Text + Text(plan.subtitle) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:566:9: error: unresolved reference: Text + Text("${plan.weeks} Wochen ? ver?ffentlichte Revision ${plan.publishedRevision}") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:568:13: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:568:18: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:569:17: error: unresolved reference: Column + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:569:24: error: unresolved reference: Modifier + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:569:44: error: unresolved reference: dp + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:569:71: error: unresolved reference: Arrangement + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:569:94: error: unresolved reference: dp + Column(Modifier.padding(14.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:570:21: error: unresolved reference: Text + Text("Tag ${day.num}: ${day.focus}", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:570:66: error: unresolved reference: MaterialTheme + Text("Tag ${day.num}: ${day.focus}", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:572:25: error: unresolved reference: Text + Text(rotation.label, fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:572:59: error: unresolved reference: FontWeight + Text(rotation.label, fontWeight = FontWeight.Bold) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:573:66: error: unresolved reference: Text + rotation.exercises.forEach { exercise -> Text("? ${exercise.name}") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:581:2: error: unresolved reference: Composable +@Composable + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:585:5: error: unresolved reference: Column + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:585:12: error: unresolved reference: Modifier + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:585:50: error: unresolved reference: rememberScrollState + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:585:84: error: unresolved reference: dp + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:585:111: error: unresolved reference: Arrangement + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:585:135: error: unresolved reference: dp + Column(Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(12.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:586:9: error: unresolved reference: Text + Text("FAQ & Variantencluster", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:586:48: error: unresolved reference: MaterialTheme + Text("FAQ & Variantencluster", style = MaterialTheme.typography.headlineSmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:587:9: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:587:14: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:588:13: error: unresolved reference: Column + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:588:20: error: unresolved reference: Modifier + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:588:40: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:588:67: error: unresolved reference: Arrangement + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:588:90: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:589:17: error: unresolved reference: Text + Text("Trainingsformat", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:589:49: error: unresolved reference: MaterialTheme + Text("Trainingsformat", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:590:17: error: unresolved reference: Text + Text("Modus: ${plan.trainingFormat.string("mode", "sets_reps")}") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:592:21: error: unresolved reference: Text + Text("Festes Intervall: ${plan.trainingFormat.int("work_seconds")} s Arbeit / ${plan.trainingFormat.int("rest_seconds")} s Pause ? ${plan.trainingFormat.int("rounds")} Intervalle pro Block") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:593:21: error: unresolved reference: Text + Text("Die App implementiert bewusst keinen Timer und ver?ndert dieses Format nicht.") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:597:9: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:597:14: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:598:13: error: unresolved reference: Column + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:598:20: error: unresolved reference: Modifier + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:598:40: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:598:67: error: unresolved reference: Arrangement + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:598:90: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:599:17: error: unresolved reference: Text + Text("Planbezogene ?bungsbibliothek", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:599:63: error: unresolved reference: MaterialTheme + Text("Planbezogene ?bungsbibliothek", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:600:58: error: unresolved reference: it + plan.exerciseCatalog.values.mapNotNull { it as? JsonObject }.take(100).forEach { entry -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:600:65: error: unresolved reference: JsonObject + plan.exerciseCatalog.values.mapNotNull { it as? JsonObject }.take(100).forEach { entry -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:600:88: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + plan.exerciseCatalog.values.mapNotNull { it as? JsonObject }.take(100).forEach { entry -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:600:98: error: cannot infer a type for this parameter. Please specify it explicitly. + plan.exerciseCatalog.values.mapNotNull { it as? JsonObject }.take(100).forEach { entry -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:601:21: error: unresolved reference: Text + Text("${entry.string("movement_label", entry.string("name"))} ? ${entry.array("variants").size} Variante(n)") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:606:13: error: unresolved reference: Card + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:606:18: error: unresolved reference: Modifier + Card(Modifier.fillMaxWidth()) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:607:17: error: unresolved reference: Column + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:607:24: error: unresolved reference: Modifier + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:607:44: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:607:71: error: unresolved reference: Arrangement + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:607:94: error: unresolved reference: dp + Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(6.dp)) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:608:21: error: unresolved reference: Text + Text("Fallbackregeln des Servers", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:608:64: error: unresolved reference: MaterialTheme + Text("Fallbackregeln des Servers", style = MaterialTheme.typography.titleMedium) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:609:21: error: unresolved reference: Text + Text(guide.toString(), style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerScreen.kt:609:52: error: unresolved reference: MaterialTheme + Text(guide.toString(), style = MaterialTheme.typography.bodySmall) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:3:8: error: unresolved reference: androidx +import androidx.lifecycle.ViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:4:8: error: unresolved reference: androidx +import androidx.lifecycle.viewModelScope + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:5:8: error: unresolved reference: dagger +import dagger.hilt.android.lifecycle.HiltViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:35:14: error: unresolved reference: inject +import javax.inject.Inject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:36:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.Job + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:37:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.delay + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:38:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.MutableStateFlow + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:39:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.SharingStarted + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:40:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.StateFlow + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:41:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.asStateFlow + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:42:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.flow.stateIn + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:43:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.launch + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:44:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.sync.Mutex + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:45:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.sync.withLock + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:46:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.Json + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:47:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:48:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonPrimitive + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:49:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.buildJsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:50:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.jsonArray + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:51:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.jsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:52:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.jsonPrimitive + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:53:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.put + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:69:19: error: unresolved reference: JsonObject + val response: JsonObject = JsonObject(emptyMap()), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:69:32: error: unresolved reference: JsonObject + val response: JsonObject = JsonObject(emptyMap()), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:71:18: error: unresolved reference: JsonObject + val tracker: JsonObject = JsonObject(emptyMap()), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:71:31: error: unresolved reference: JsonObject + val tracker: JsonObject = JsonObject(emptyMap()), + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:80:2: error: unresolved reference: HiltViewModel +@HiltViewModel + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:81:25: error: unresolved reference: Inject +class TrackerViewModel @Inject constructor( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:84:5: error: unresolved reference: ViewModel +) : ViewModel() { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:85:26: error: unresolved reference: MutableStateFlow + private val _state = MutableStateFlow(TrackerUiState()) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:86:16: error: unresolved reference: StateFlow + val state: StateFlow = _state.asStateFlow() + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:87:57: error: unresolved reference: viewModelScope + val pending = repository.pendingMutations().stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:87:73: error: unresolved reference: SharingStarted + val pending = repository.pendingMutations().stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:89:26: error: unresolved reference: Job + private var saveJob: Job? = null + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:90:26: error: unresolved reference: Job + private var pollJob: Job? = null + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:91:29: error: unresolved reference: Mutex + private val saveMutex = Mutex() + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:92:28: error: unresolved reference: Json + private val wireJson = Json { ignoreUnknownKeys = true; explicitNulls = false } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:92:35: error: unresolved reference: ignoreUnknownKeys + private val wireJson = Json { ignoreUnknownKeys = true; explicitNulls = false } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:92:61: error: unresolved reference: explicitNulls + private val wireJson = Json { ignoreUnknownKeys = true; explicitNulls = false } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:97:9: error: unresolved reference: viewModelScope + viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:98:20: error: variable expected + _state.value = _state.value.copy(loading = true, error = "") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:99:38: error: suspend function 'trackerPlans' should be called only from a coroutine or another suspend function + runCatching { repository.trackerPlans(force) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:100:30: error: cannot infer a type for this parameter. Please specify it explicitly. + .onSuccess { payload -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:101:75: error: unresolved reference: it + val plans = payload["plans"]?.jsonArray?.mapNotNull { it as? JsonObject }?.map { item -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:101:82: error: unresolved reference: JsonObject + val plans = payload["plans"]?.jsonArray?.mapNotNull { it as? JsonObject }?.map { item -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:101:102: error: cannot infer a type for this parameter. Please specify it explicitly. + val plans = payload["plans"]?.jsonArray?.mapNotNull { it as? JsonObject }?.map { item -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:111:46: error: suspend function 'selectedTrackerPlan' should be called only from a coroutine or another suspend function + val preferred = settings.selectedTrackerPlan() + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:112:74: error: unresolved reference: it + val selected = preferred?.takeIf { id -> plans.any { it.id == id } } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:113:64: error: cannot infer a type for this parameter. Please specify it explicitly. + ?: payload.string("selected").takeIf { id -> plans.any { it.id == id } } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:113:82: error: unresolved reference: it + ?: payload.string("selected").takeIf { id -> plans.any { it.id == id } } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:115:28: error: variable expected + _state.value = _state.value.copy(plans = plans, loading = false) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:118:30: error: cannot infer a type for this parameter. Please specify it explicitly. + .onFailure { error -> _state.value = _state.value.copy(loading = false, error = error.message ?: "Planliste konnte nicht geladen werden.") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:118:46: error: variable expected + .onFailure { error -> _state.value = _state.value.copy(loading = false, error = error.message ?: "Planliste konnte nicht geladen werden.") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:123:9: error: unresolved reference: viewModelScope + viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:124:20: error: variable expected + _state.value = _state.value.copy(loading = true, error = "", conflict = null) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:125:13: error: not enough information to infer type variable R + runCatching { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:126:47: error: suspend function 'selectTrackerPlan' should be called only from a coroutine or another suspend function + if (persistServer) repository.selectTrackerPlan(planId) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:127:26: error: suspend function 'setSelectedTrackerPlan' should be called only from a coroutine or another suspend function + settings.setSelectedTrackerPlan(planId) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:128:35: error: suspend function 'trackerUi' should be called only from a coroutine or another suspend function + val ui = settings.trackerUi(planId) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:129:35: error: suspend function 'trackerPlan' should be called only from a coroutine or another suspend function + Triple(repository.trackerPlan(planId, force = true), ui, repository.pendingForPlan(planId)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:129:85: error: suspend function 'pendingForPlan' should be called only from a coroutine or another suspend function + Triple(repository.trackerPlan(planId, force = true), ui, repository.pendingForPlan(planId)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:130:27: error: cannot infer a type for this parameter. Please specify it explicitly. + }.onSuccess { (response, ui, queued) -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:134:43: error: cannot infer a type for this parameter. Please specify it explicitly. + val day = ui.day.takeIf { candidate -> plan.days.any { it.num == candidate } } ?: plan.days.firstOrNull()?.num ?: 1 + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:141:24: error: variable expected + _state.value = _state.value.copy( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:153:27: error: cannot infer a type for this parameter. Please specify it explicitly. + }.onFailure { error -> _state.value = _state.value.copy(loading = false, error = error.message ?: "Plan konnte nicht geladen werden.") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:153:43: error: variable expected + }.onFailure { error -> _state.value = _state.value.copy(loading = false, error = error.message ?: "Plan konnte nicht geladen werden.") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:158:16: error: variable expected + _state.value = _state.value.copy(tab = tab) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:164:16: error: variable expected + _state.value = _state.value.copy(week = week.coerceIn(1, plan.weeks)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:164:71: error: unresolved reference: weeks + _state.value = _state.value.copy(week = week.coerceIn(1, plan.weeks)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:171:18: error: unresolved reference: days + if (plan.days.none { it.num == day }) return + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:171:30: error: unresolved reference: it + if (plan.days.none { it.num == day }) return + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:172:16: error: variable expected + _state.value = _state.value.copy(day = day) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:180:9: error: unresolved reference: viewModelScope + viewModelScope.launch { settings.saveTrackerUi(s.selectedPlanId, s.tab, s.week, s.day) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:180:42: error: suspend function 'saveTrackerUi' should be called only from a coroutine or another suspend function + viewModelScope.launch { settings.saveTrackerUi(s.selectedPlanId, s.tab, s.week, s.day) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:183:71: error: unresolved reference: it + fun currentDay(): DayUi? = _state.value.plan?.days?.firstOrNull { it.num == _state.value.day } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:184:27: error: unresolved reference: JsonObject + fun currentSession(): JsonObject? { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:191:25: error: cannot infer a type for this parameter. Please specify it explicitly. + mutateSession { session -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:207:16: error: variable expected + _state.value = _state.value.copy(tracker = tracker) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:212:25: error: cannot infer a type for this parameter. Please specify it explicitly. + mutateSession { session -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:221:25: error: cannot infer a type for this parameter. Please specify it explicitly. + mutateSession { session -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:233:25: error: cannot infer a type for this parameter. Please specify it explicitly. + mutateSession { session -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:236:51: error: unresolved reference: JsonObject + val current = item["result_data"] as? JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:251:25: error: cannot infer a type for this parameter. Please specify it explicitly. + mutateSession { session -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:257:45: error: unresolved reference: JsonObject + fun setItemResultData(id: String, data: JsonObject) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:258:25: error: cannot infer a type for this parameter. Please specify it explicitly. + mutateSession { session -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:266:49: error: unresolved reference: JsonObject + fun setItemResultFormat(id: String, format: JsonObject?) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:267:25: error: cannot infer a type for this parameter. Please specify it explicitly. + mutateSession { session -> + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:270:51: error: unresolved reference: JsonObject + val current = item["result_data"] as? JsonObject + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:298:39: error: unresolved reference: buildJsonObject + statuses[s.week.toString()] = buildJsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:299:13: error: unresolved reference: put + put("status", if (closed) "closed" else "open") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:300:13: error: unresolved reference: put + put("updated_at", Instant.now().toString()) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:302:58: error: unresolved reference: JsonObject + val tracker = s.tracker.updated("week_statuses", JsonObject(statuses)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:303:16: error: variable expected + _state.value = s.copy(tracker = tracker) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:307:39: error: unresolved reference: JsonObject + private fun mutateSession(block: (JsonObject) -> JsonObject) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:307:54: error: unresolved reference: JsonObject + private fun mutateSession(block: (JsonObject) -> JsonObject) { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:312:16: error: variable expected + _state.value = s.copy(tracker = updateTrackerSession(s.tracker, key, nextSession), saveState = "Ungespeichert") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:319:24: error: unresolved reference: days + val day = plan.days.firstOrNull { it.num == s.day } ?: return + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:319:43: error: unresolved reference: it + val day = plan.days.firstOrNull { it.num == s.day } ?: return + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:323:20: error: variable expected + _state.value = s.copy(tracker = updateTrackerSession(s.tracker, key, session)) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:330:19: error: unresolved reference: viewModelScope + saveJob = viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:331:13: error: unresolved reference: delay + delay(650) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:337:9: error: unresolved reference: viewModelScope + viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:344:24: error: variable expected + _state.value = _state.value.copy(saveState = "Speichert ?") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:345:42: error: suspend function 'patchSession' should be called only from a coroutine or another suspend function + runCatching { repository.patchSession(snapshot.selectedPlanId, key, patch) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:350:40: error: variable expected + _state.value = _state.value.copy( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:359:40: error: variable expected + _state.value = _state.value.copy( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:368:36: error: variable expected + _state.value = _state.value.copy(conflict = error, saveState = "Konflikt") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:370:36: error: variable expected + _state.value = _state.value.copy(saveState = "Fehler", error = error.message ?: "Speichern fehlgeschlagen") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:377:45: error: unresolved reference: JsonObject + private fun overlayPendingPatches(base: JsonObject, queued: List): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:377:89: error: unresolved reference: JsonObject + private fun overlayPendingPatches(base: JsonObject, queued: List): JsonObject { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:384:36: error: variable expected + fun dismissConflict() { _state.value = _state.value.copy(conflict = null) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:386:43: error: unresolved reference: viewModelScope + fun discardQueuedConflict(id: Long) { viewModelScope.launch { repository.discardConflict(id) } } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:386:78: error: suspend function 'discardConflict' should be called only from a coroutine or another suspend function + fun discardQueuedConflict(id: Long) { viewModelScope.launch { repository.discardConflict(id) } } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:391:9: error: unresolved reference: viewModelScope + viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:392:20: error: variable expected + _state.value = _state.value.copy(analysisMessage = "Analyse wird gestartet ?", error = "") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:393:38: error: suspend function 'analysisStart' should be called only from a coroutine or another suspend function + runCatching { repository.analysisStart(planId, scope, week) } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:395:28: error: variable expected + _state.value = _state.value.copy( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:404:46: error: variable expected + .onFailure { error -> _state.value = _state.value.copy(analysisMessage = "", error = error.message ?: "Analyse fehlgeschlagen") } + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:411:19: error: unresolved reference: viewModelScope + pollJob = viewModelScope.launch { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:413:17: error: unresolved reference: delay + delay(2_000) + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:414:53: error: suspend function 'analysisStatus' should be called only from a coroutine or another suspend function + val body = runCatching { repository.analysisStatus(planId) }.getOrElse { + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:415:28: error: variable expected + _state.value = _state.value.copy(error = it.message ?: "Analysestatus konnte nicht gelesen werden") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:415:62: error: unresolved reference: it + _state.value = _state.value.copy(error = it.message ?: "Analysestatus konnte nicht gelesen werden") + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:416:21: error: 'return' is not allowed here + return@launch + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:418:24: error: variable expected + _state.value = _state.value.copy( + ^ +app/src/main/java/de/drazz/boehmitools/training/ui/tracker/TrackerViewModel.kt:425:24: error: variable expected + _state.value = _state.value.copy(analysisMessage = body.obj("analysis_state").string("message")) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:3:8: error: unresolved reference: retrofit2 +import retrofit2.converter.kotlinx.serialization.asConverterFactory + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:4:8: error: unresolved reference: kotlinx +import kotlinx.coroutines.test.runTest + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:5:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.Json + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:6:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonObject + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:7:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.buildJsonObject + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:8:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.put + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:9:8: error: unresolved reference: okhttp3 +import okhttp3.Credentials + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:10:8: error: unresolved reference: okhttp3 +import okhttp3.MediaType.Companion.toMediaType + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:11:8: error: unresolved reference: okhttp3 +import okhttp3.OkHttpClient + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:12:8: error: unresolved reference: okhttp3 +import okhttp3.mockwebserver.MockResponse + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:13:8: error: unresolved reference: okhttp3 +import okhttp3.mockwebserver.MockWebServer + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:14:12: error: unresolved reference: junit +import org.junit.After + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:15:12: error: unresolved reference: junit +import org.junit.Assert.assertEquals + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:16:12: error: unresolved reference: junit +import org.junit.Assert.assertTrue + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:17:12: error: unresolved reference: junit +import org.junit.Before + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:18:12: error: unresolved reference: junit +import org.junit.Test + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:19:8: error: unresolved reference: retrofit2 +import retrofit2.Retrofit + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:22:34: error: unresolved reference: MockWebServer + private lateinit var server: MockWebServer + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:26:6: error: unresolved reference: Before + @Before + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:28:18: error: unresolved reference: MockWebServer + server = MockWebServer().apply { start() } + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:28:42: error: unresolved reference: start + server = MockWebServer().apply { start() } + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:29:20: error: unresolved reference: Json + val json = Json { ignoreUnknownKeys = true } + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:29:27: error: unresolved reference: ignoreUnknownKeys + val json = Json { ignoreUnknownKeys = true } + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:30:22: error: unresolved reference: OkHttpClient + val client = OkHttpClient.Builder().addInterceptor { chain -> + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:30:62: error: cannot infer a type for this parameter. Please specify it explicitly. + val client = OkHttpClient.Builder().addInterceptor { chain -> + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:31:80: error: unresolved reference: Credentials + chain.proceed(chain.request().newBuilder().header("Authorization", Credentials.basic("admin", "secret")).build()) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:33:24: error: unresolved reference: Retrofit + val retrofit = Retrofit.Builder() + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:36:77: error: unresolved reference: toMediaType + .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:42:6: error: unresolved reference: After + @After fun tearDown() = server.shutdown() + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:44:6: error: unresolved reference: Test + @Test + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:45:67: error: unresolved reference: runTest + fun sessionPatchUsesExactPathBasicAuthAndExpectedRevision() = runTest { + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:46:24: error: unresolved reference: MockResponse + server.enqueue(MockResponse().setResponseCode(200).setHeader("Content-Type", "application/json") + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:48:23: error: unresolved reference: buildJsonObject + val payload = buildJsonObject { + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:49:13: error: unresolved reference: put + put("expected_revision", 7) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:50:13: error: unresolved reference: put + put("session_key", "w01-d01") + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:51:13: error: unresolved reference: put + put("session", buildJsonObject { put("status", "in_progress"); put("items", JsonObject(emptyMap())) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:51:28: error: unresolved reference: buildJsonObject + put("session", buildJsonObject { put("status", "in_progress"); put("items", JsonObject(emptyMap())) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:51:46: error: unresolved reference: put + put("session", buildJsonObject { put("status", "in_progress"); put("items", JsonObject(emptyMap())) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:51:76: error: unresolved reference: put + put("session", buildJsonObject { put("status", "in_progress"); put("items", JsonObject(emptyMap())) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:51:89: error: unresolved reference: JsonObject + put("session", buildJsonObject { put("status", "in_progress"); put("items", JsonObject(emptyMap())) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:53:32: error: suspend function 'patchSession' should be called only from a coroutine or another suspend function + val response = tracker.patchSession("phase-1.json", payload) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:54:9: error: unresolved reference: assertEquals + assertEquals(200, response.code()) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:55:9: error: unresolved reference: assertEquals + assertEquals(8, response.body()!!["revision"]!!.toString().toInt()) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:57:9: error: unresolved reference: assertEquals + assertEquals("PATCH", request.method) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:58:9: error: unresolved reference: assertEquals + assertEquals("/plugins/trainingstracker/api/plans/phase-1.json/tracker/session", request.path) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:59:9: error: unresolved reference: assertEquals + assertEquals(Credentials.basic("admin", "secret"), request.getHeader("Authorization")) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:59:22: error: unresolved reference: Credentials + assertEquals(Credentials.basic("admin", "secret"), request.getHeader("Authorization")) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:60:9: error: unresolved reference: assertTrue + assertTrue(request.body.readUtf8().contains("\"expected_revision\":7")) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:63:6: error: unresolved reference: Test + @Test + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:64:70: error: unresolved reference: runTest + fun analysisSupports202And200WithoutInventingASecondEndpoint() = runTest { + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:65:24: error: unresolved reference: MockResponse + server.enqueue(MockResponse().setResponseCode(202).setHeader("Content-Type", "application/json") + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:67:32: error: suspend function 'startAnalysis' should be called only from a coroutine or another suspend function + val accepted = tracker.startAnalysis("phase-1.json", buildJsonObject { put("scope", "week"); put("week", 1) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:67:62: error: unresolved reference: buildJsonObject + val accepted = tracker.startAnalysis("phase-1.json", buildJsonObject { put("scope", "week"); put("week", 1) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:67:80: error: unresolved reference: put + val accepted = tracker.startAnalysis("phase-1.json", buildJsonObject { put("scope", "week"); put("week", 1) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:67:102: error: unresolved reference: put + val accepted = tracker.startAnalysis("phase-1.json", buildJsonObject { put("scope", "week"); put("week", 1) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:68:9: error: unresolved reference: assertEquals + assertEquals(202, accepted.code()) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:69:9: error: unresolved reference: assertEquals + assertEquals("/plugins/trainingstracker/api/plans/phase-1.json/analysis", server.takeRequest().path) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:71:24: error: unresolved reference: MockResponse + server.enqueue(MockResponse().setResponseCode(200).setHeader("Content-Type", "application/json") + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:73:9: error: unresolved reference: assertEquals + assertEquals(200, tracker.startAnalysis("phase-1.json", buildJsonObject { put("scope", "overall") }).code()) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:73:35: error: suspend function 'startAnalysis' should be called only from a coroutine or another suspend function + assertEquals(200, tracker.startAnalysis("phase-1.json", buildJsonObject { put("scope", "overall") }).code()) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:73:65: error: unresolved reference: buildJsonObject + assertEquals(200, tracker.startAnalysis("phase-1.json", buildJsonObject { put("scope", "overall") }).code()) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:73:83: error: unresolved reference: put + assertEquals(200, tracker.startAnalysis("phase-1.json", buildJsonObject { put("scope", "overall") }).code()) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:76:6: error: unresolved reference: Test + @Test + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:77:75: error: unresolved reference: runTest + fun conflictAndUnauthorizedBodiesRemainAvailableToRepositoryLayer() = runTest { + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:78:24: error: unresolved reference: MockResponse + server.enqueue(MockResponse().setResponseCode(409).setHeader("Content-Type", "application/json") + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:80:32: error: suspend function 'patchSession' should be called only from a coroutine or another suspend function + val conflict = tracker.patchSession("phase-1.json", buildJsonObject { put("expected_revision", 8) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:80:61: error: unresolved reference: buildJsonObject + val conflict = tracker.patchSession("phase-1.json", buildJsonObject { put("expected_revision", 8) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:80:79: error: unresolved reference: put + val conflict = tracker.patchSession("phase-1.json", buildJsonObject { put("expected_revision", 8) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:81:9: error: unresolved reference: assertEquals + assertEquals(409, conflict.code()) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:82:9: error: unresolved reference: assertTrue + assertTrue(conflict.errorBody()!!.string().contains("current_revision")) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:84:24: error: unresolved reference: MockResponse + server.enqueue(MockResponse().setResponseCode(401).setHeader("Content-Type", "application/json") + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:87:35: error: suspend function 'plans' should be called only from a coroutine or another suspend function + val unauthorized = editor.plans() + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:88:9: error: unresolved reference: assertEquals + assertEquals(401, unauthorized.code()) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:89:9: error: unresolved reference: assertTrue + assertTrue(unauthorized.headers()["WWW-Authenticate"]!!.contains("Basic")) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:92:6: error: unresolved reference: Test + @Test + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:93:67: error: unresolved reference: runTest + fun editorSavePublishAndProposalStatusUseDocumentedRoutes() = runTest { + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:95:28: error: unresolved reference: MockResponse + server.enqueue(MockResponse().setResponseCode(200).setHeader("Content-Type", "application/json").setBody("{}")) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:97:16: error: suspend function 'save' should be called only from a coroutine or another suspend function + editor.save("phase-1", buildJsonObject { put("expected_revision", 3); put("config", JsonObject(emptyMap())) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:97:32: error: unresolved reference: buildJsonObject + editor.save("phase-1", buildJsonObject { put("expected_revision", 3); put("config", JsonObject(emptyMap())) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:97:50: error: unresolved reference: put + editor.save("phase-1", buildJsonObject { put("expected_revision", 3); put("config", JsonObject(emptyMap())) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:97:79: error: unresolved reference: put + editor.save("phase-1", buildJsonObject { put("expected_revision", 3); put("config", JsonObject(emptyMap())) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:97:93: error: unresolved reference: JsonObject + editor.save("phase-1", buildJsonObject { put("expected_revision", 3); put("config", JsonObject(emptyMap())) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:98:16: error: suspend function 'publish' should be called only from a coroutine or another suspend function + editor.publish("phase-1", buildJsonObject { put("expected_revision", 4) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:98:35: error: unresolved reference: buildJsonObject + editor.publish("phase-1", buildJsonObject { put("expected_revision", 4) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:98:53: error: unresolved reference: put + editor.publish("phase-1", buildJsonObject { put("expected_revision", 4) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:99:16: error: suspend function 'proposalStatus' should be called only from a coroutine or another suspend function + editor.proposalStatus("phase-1", "overall:overall-1", buildJsonObject { put("status", "accepted") }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:99:63: error: unresolved reference: buildJsonObject + editor.proposalStatus("phase-1", "overall:overall-1", buildJsonObject { put("status", "accepted") }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:99:81: error: unresolved reference: put + editor.proposalStatus("phase-1", "overall:overall-1", buildJsonObject { put("status", "accepted") }) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:100:9: error: unresolved reference: assertEquals + assertEquals("/plugins/trainingsplan/api/plans/phase-1", server.takeRequest().path) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:101:9: error: unresolved reference: assertEquals + assertEquals("/plugins/trainingsplan/api/plans/phase-1/publish", server.takeRequest().path) + ^ +app/src/test/java/de/drazz/boehmitools/training/data/network/ApiContractTest.kt:102:9: error: unresolved reference: assertEquals + assertEquals("/plugins/trainingsplan/api/plans/phase-1/proposals/overall:overall-1", server.takeRequest().path) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:4:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.Json + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:5:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.JsonObject + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:6:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.buildJsonObject + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:7:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.put + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:8:8: error: unresolved reference: kotlinx +import kotlinx.serialization.json.jsonObject + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:9:12: error: unresolved reference: junit +import org.junit.Assert.assertEquals + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:10:12: error: unresolved reference: junit +import org.junit.Assert.assertFalse + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:11:12: error: unresolved reference: junit +import org.junit.Assert.assertNotEquals + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:12:12: error: unresolved reference: junit +import org.junit.Assert.assertTrue + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:13:12: error: unresolved reference: junit +import org.junit.Test + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:16:24: error: unresolved reference: Json + private val json = Json { ignoreUnknownKeys = true } + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:16:31: error: unresolved reference: ignoreUnknownKeys + private val json = Json { ignoreUnknownKeys = true } + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:18:6: error: unresolved reference: Test + @Test + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:23:13: error: unresolved reference: assertEquals + assertEquals(3, config.int("schema_version")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:24:13: error: unresolved reference: assertEquals + assertEquals(2, config.int("contract_version")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:25:13: error: unresolved reference: assertEquals + assertEquals(name, config.string("plan_id")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:31:47: error: unresolved reference: it + config.array("days").mapNotNull { it as? JsonObject }.forEach { day -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:31:54: error: unresolved reference: JsonObject + config.array("days").mapNotNull { it as? JsonObject }.forEach { day -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:31:67: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + config.array("days").mapNotNull { it as? JsonObject }.forEach { day -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:31:77: error: cannot infer a type for this parameter. Please specify it explicitly. + config.array("days").mapNotNull { it as? JsonObject }.forEach { day -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:33:53: error: unresolved reference: it + day.array("rotations").mapNotNull { it as? JsonObject }.forEach { rotation -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:33:60: error: unresolved reference: JsonObject + day.array("rotations").mapNotNull { it as? JsonObject }.forEach { rotation -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:33:73: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + day.array("rotations").mapNotNull { it as? JsonObject }.forEach { rotation -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:33:83: error: cannot infer a type for this parameter. Please specify it explicitly. + day.array("rotations").mapNotNull { it as? JsonObject }.forEach { rotation -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:35:62: error: unresolved reference: it + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:35:69: error: unresolved reference: JsonObject + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:35:82: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:35:92: error: cannot infer a type for this parameter. Please specify it explicitly. + rotation.array("exercises").mapNotNull { it as? JsonObject }.forEach { exercise -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:37:25: error: unresolved reference: assertTrue + assertTrue(exercise.string("legacy_id").matches(Regex("d\\d+-r\\d+-e\\d+"))) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:38:25: error: unresolved reference: assertTrue + assertTrue(exercise.string("exercise_id").startsWith("movement-")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:39:25: error: unresolved reference: assertTrue + assertTrue(exercise.string("progression_id").isNotBlank()) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:40:25: error: unresolved reference: assertFalse + assertFalse(exercise.obj("result_schema").isEmpty()) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:44:54: error: unresolved reference: it + config.obj("stages").values.mapNotNull { it as? JsonObject }.forEach { stage -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:44:61: error: unresolved reference: JsonObject + config.obj("stages").values.mapNotNull { it as? JsonObject }.forEach { stage -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:44:74: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + config.obj("stages").values.mapNotNull { it as? JsonObject }.forEach { stage -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:44:84: error: cannot infer a type for this parameter. Please specify it explicitly. + config.obj("stages").values.mapNotNull { it as? JsonObject }.forEach { stage -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:45:51: error: unresolved reference: it + stage.array("steps").mapNotNull { it as? JsonObject }.forEach { step -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:45:58: error: unresolved reference: JsonObject + stage.array("steps").mapNotNull { it as? JsonObject }.forEach { step -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:45:71: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + stage.array("steps").mapNotNull { it as? JsonObject }.forEach { step -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:45:81: error: cannot infer a type for this parameter. Please specify it explicitly. + stage.array("steps").mapNotNull { it as? JsonObject }.forEach { step -> + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:47:21: error: unresolved reference: assertFalse + assertFalse(step.obj("result_schema").isEmpty()) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:51:17: error: unresolved reference: assertTrue + assertTrue(ids.all { it.isNotBlank() }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:52:17: error: unresolved reference: assertEquals + assertEquals(ids.size, ids.toSet().size) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:57:6: error: unresolved reference: Test + @Test + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:62:13: error: unresolved reference: assertEquals + assertEquals(8, plan.weeks) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:63:13: error: unresolved reference: assertEquals + assertEquals(6, plan.days.size) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:64:13: error: unresolved reference: assertEquals + assertEquals(31, plan.progressions.size) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:65:13: error: unresolved reference: assertTrue + assertTrue(plan.days.flatMap { it.rotations }.flatMap { it.exercises }.isNotEmpty()) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:66:13: error: unresolved reference: assertTrue + assertTrue(plan.title.isNotBlank()) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:70:6: error: unresolved reference: Test + @Test + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:75:13: error: unresolved reference: assertTrue + assertTrue(tracker.int("version") in 1..7) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:81:13: error: unresolved reference: assertTrue + assertTrue(changed) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:82:13: error: unresolved reference: assertEquals + assertEquals(originalCount, migrated.obj("items").size) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:85:21: error: unresolved reference: assertTrue + assertTrue(migrated.obj("items").containsKey(exercise.id)) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:86:21: error: unresolved reference: assertFalse + assertFalse(migrated.obj("items").containsKey(exercise.legacyId)) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:92:6: error: unresolved reference: Test + @Test + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:103:20: error: unresolved reference: buildJsonObject + item = buildJsonObject { + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:104:17: error: unresolved reference: put + put("progression_step_id", step.id) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:109:9: error: unresolved reference: assertEquals + assertEquals("progression", fromStep.source) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:110:9: error: unresolved reference: assertEquals + assertEquals(step.resultSchema?.mode, fromStep.mode) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:114:20: error: unresolved reference: buildJsonObject + item = buildJsonObject { + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:115:17: error: unresolved reference: put + put("result_format", buildJsonObject { + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:115:38: error: unresolved reference: buildJsonObject + put("result_format", buildJsonObject { + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:116:21: error: unresolved reference: put + put("mode", "minutes") + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:117:21: error: unresolved reference: put + put("weight_mode", "none") + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:118:21: error: unresolved reference: put + put("laterality", "bilateral") + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:119:21: error: unresolved reference: put + put("sides_mode", "same") + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:125:9: error: unresolved reference: assertEquals + assertEquals("session", manual.source) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:126:9: error: unresolved reference: assertEquals + assertEquals("minutes", manual.mode) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:127:9: error: unresolved reference: assertNotEquals + assertNotEquals(fromStep.mode, manual.mode) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:130:6: error: unresolved reference: Test + @Test + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:132:24: error: unresolved reference: buildJsonObject + val exercise = buildJsonObject { + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:133:13: error: unresolved reference: put + put("mode", "reps") + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:134:13: error: unresolved reference: put + put("weight_mode", "required") + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:135:13: error: unresolved reference: put + put("laterality", "unilateral") + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:136:13: error: unresolved reference: put + put("sides_mode", "separate") + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:137:13: error: unresolved reference: put + put("sets", 4) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:138:13: error: unresolved reference: put + put("locked_sets", true) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:140:27: error: unresolved reference: buildJsonObject + val progression = buildJsonObject { put("mode", "seconds") } + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:140:45: error: unresolved reference: put + val progression = buildJsonObject { put("mode", "seconds") } + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:142:9: error: unresolved reference: assertEquals + assertEquals("seconds", merged.mode) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:143:9: error: unresolved reference: assertEquals + assertEquals("required", merged.weightMode) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:144:9: error: unresolved reference: assertEquals + assertEquals("unilateral", merged.laterality) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:145:9: error: unresolved reference: assertEquals + assertEquals("separate", merged.sidesMode) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:146:9: error: unresolved reference: assertEquals + assertEquals(4, merged.defaultSets) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:147:9: error: unresolved reference: assertTrue + assertTrue(merged.lockedSets) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:148:9: error: unresolved reference: assertEquals + assertEquals("progression", merged.source) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:151:6: error: unresolved reference: Test + @Test + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:153:23: error: unresolved reference: buildJsonObject + val current = buildJsonObject { + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:154:13: error: unresolved reference: put + put("version", 2) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:155:13: error: unresolved reference: put + put("mode", "reps") + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:156:13: error: unresolved reference: put + put("laterality", "bilateral") + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:157:13: error: unresolved reference: put + put("sides_mode", "same") + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:158:13: error: unresolved reference: put + put("sets", 3) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:159:13: error: unresolved reference: put + put("values", kotlinx.serialization.json.buildJsonArray { add(8); add(7); add(6) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:159:27: error: unresolved reference: kotlinx + put("values", kotlinx.serialization.json.buildJsonArray { add(8); add(7); add(6) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:159:71: error: unresolved reference: add + put("values", kotlinx.serialization.json.buildJsonArray { add(8); add(7); add(6) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:159:79: error: unresolved reference: add + put("values", kotlinx.serialization.json.buildJsonArray { add(8); add(7); add(6) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:159:87: error: unresolved reference: add + put("values", kotlinx.serialization.json.buildJsonArray { add(8); add(7); add(6) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:160:13: error: unresolved reference: put + put("left_values", kotlinx.serialization.json.JsonArray(emptyList())) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:160:32: error: unresolved reference: kotlinx + put("left_values", kotlinx.serialization.json.JsonArray(emptyList())) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:161:13: error: unresolved reference: put + put("right_values", kotlinx.serialization.json.JsonArray(emptyList())) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:161:33: error: unresolved reference: kotlinx + put("right_values", kotlinx.serialization.json.JsonArray(emptyList())) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:162:13: error: unresolved reference: put + put("weight_kg", 8.0) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:168:9: error: unresolved reference: assertEquals + assertEquals("seconds", adapted.string("mode")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:169:9: error: unresolved reference: assertEquals + assertEquals(2, adapted.int("sets")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:170:9: error: unresolved reference: assertTrue + assertTrue(adapted.array("values").all { it == kotlinx.serialization.json.JsonNull }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:170:50: error: unresolved reference: it + assertTrue(adapted.array("values").all { it == kotlinx.serialization.json.JsonNull }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:170:56: error: unresolved reference: kotlinx + assertTrue(adapted.array("values").all { it == kotlinx.serialization.json.JsonNull }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:171:9: error: unresolved reference: assertTrue + assertTrue(adapted["weight_kg"] == kotlinx.serialization.json.JsonNull) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:171:44: error: unresolved reference: kotlinx + assertTrue(adapted["weight_kg"] == kotlinx.serialization.json.JsonNull) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:174:6: error: unresolved reference: Test + @Test + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:178:9: error: unresolved reference: assertEquals + assertEquals(2, parsed.int("version")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:179:9: error: unresolved reference: assertEquals + assertEquals("seconds", parsed.string("mode")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:180:9: error: unresolved reference: assertEquals + assertEquals(3, parsed.int("sets")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:181:9: error: unresolved reference: assertEquals + assertEquals(3, parsed.array("values").size) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:182:9: error: unresolved reference: assertTrue + assertTrue(formatResultData(parsed).contains("20")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:183:9: error: unresolved reference: assertTrue + assertTrue(formatResultData(parsed).contains("8 kg")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:186:9: error: unresolved reference: assertEquals + assertEquals(20, resized.int("sets")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:187:9: error: unresolved reference: assertEquals + assertEquals(20, resized.array("values").size) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:188:9: error: unresolved reference: assertEquals + assertEquals(20, resultDataWithSets(resized, 99).int("sets")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:191:6: error: unresolved reference: Test + @Test + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:193:20: error: unresolved reference: buildJsonObject + val base = buildJsonObject { + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:194:13: error: unresolved reference: put + put("revision", 5) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:195:13: error: unresolved reference: put + put("profile", buildJsonObject { put("display_name", "Server") }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:195:28: error: unresolved reference: buildJsonObject + put("profile", buildJsonObject { put("display_name", "Server") }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:195:46: error: unresolved reference: put + put("profile", buildJsonObject { put("display_name", "Server") }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:196:13: error: unresolved reference: put + put("week_statuses", JsonObject(emptyMap())) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:196:34: error: unresolved reference: JsonObject + put("week_statuses", JsonObject(emptyMap())) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:197:13: error: unresolved reference: put + put("sessions", JsonObject(emptyMap())) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:197:29: error: unresolved reference: JsonObject + put("sessions", JsonObject(emptyMap())) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:199:21: error: unresolved reference: buildJsonObject + val first = buildJsonObject { + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:200:13: error: unresolved reference: put + put("expected_revision", 5) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:201:13: error: unresolved reference: put + put("profile", buildJsonObject { put("display_name", "Lokal") }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:201:28: error: unresolved reference: buildJsonObject + put("profile", buildJsonObject { put("display_name", "Lokal") }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:201:46: error: unresolved reference: put + put("profile", buildJsonObject { put("display_name", "Lokal") }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:202:13: error: unresolved reference: put + put("session_key", "w01-d01") + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:203:13: error: unresolved reference: put + put("session", buildJsonObject { put("status", "in_progress") }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:203:28: error: unresolved reference: buildJsonObject + put("session", buildJsonObject { put("status", "in_progress") }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:203:46: error: unresolved reference: put + put("session", buildJsonObject { put("status", "in_progress") }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:205:22: error: unresolved reference: buildJsonObject + val second = buildJsonObject { + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:206:13: error: unresolved reference: put + put("expected_revision", 6) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:207:13: error: unresolved reference: put + put("week_statuses", buildJsonObject { put("1", buildJsonObject { put("status", "closed") }) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:207:34: error: unresolved reference: buildJsonObject + put("week_statuses", buildJsonObject { put("1", buildJsonObject { put("status", "closed") }) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:207:52: error: unresolved reference: put + put("week_statuses", buildJsonObject { put("1", buildJsonObject { put("status", "closed") }) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:207:61: error: unresolved reference: buildJsonObject + put("week_statuses", buildJsonObject { put("1", buildJsonObject { put("status", "closed") }) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:207:79: error: unresolved reference: put + put("week_statuses", buildJsonObject { put("1", buildJsonObject { put("status", "closed") }) }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:208:13: error: unresolved reference: put + put("session_key", "w01-d02") + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:209:13: error: unresolved reference: put + put("session", buildJsonObject { put("status", "completed") }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:209:28: error: unresolved reference: buildJsonObject + put("session", buildJsonObject { put("status", "completed") }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:209:46: error: unresolved reference: put + put("session", buildJsonObject { put("status", "completed") }) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:212:9: error: unresolved reference: assertEquals + assertEquals(7, overlaid.int("revision")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:213:9: error: unresolved reference: assertEquals + assertEquals("Lokal", overlaid.obj("profile").string("display_name")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:214:9: error: unresolved reference: assertEquals + assertEquals("closed", overlaid.obj("week_statuses").obj("1").string("status")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:215:9: error: unresolved reference: assertEquals + assertEquals("in_progress", overlaid.obj("sessions").obj("w01-d01").string("status")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:216:9: error: unresolved reference: assertEquals + assertEquals("completed", overlaid.obj("sessions").obj("w01-d02").string("status")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:219:6: error: unresolved reference: Test + @Test + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:224:9: error: unresolved reference: assertEquals + assertEquals("week", week.string("type")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:225:9: error: unresolved reference: assertEquals + assertEquals("overall", overall.string("type")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:226:9: error: unresolved reference: assertTrue + assertTrue(proposals.array("proposals").isNotEmpty()) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:227:51: error: unresolved reference: it + proposals.array("proposals").mapNotNull { it as? JsonObject }.forEach { + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:227:58: error: unresolved reference: JsonObject + proposals.array("proposals").mapNotNull { it as? JsonObject }.forEach { + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:227:71: error: overload resolution ambiguity: +public inline fun Iterable.forEach(action: (TypeVariable(T)) -> Unit): Unit defined in kotlin.collections +public inline fun Map.forEach(action: (Map.Entry) -> Unit): Unit defined in kotlin.collections + proposals.array("proposals").mapNotNull { it as? JsonObject }.forEach { + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:228:13: error: unresolved reference: assertTrue + assertTrue(it.string("id").isNotBlank()) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:228:24: error: unresolved reference: it + assertTrue(it.string("id").isNotBlank()) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:229:13: error: unresolved reference: assertTrue + assertTrue(it.string("target_exercise_id").startsWith("movement-")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:229:24: error: unresolved reference: it + assertTrue(it.string("target_exercise_id").startsWith("movement-")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:230:13: error: unresolved reference: assertEquals + assertEquals("open", it.string("status")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:230:34: error: unresolved reference: it + assertEquals("open", it.string("status")) + ^ +app/src/test/java/de/drazz/boehmitools/training/domain/FixtureContractTest.kt:234:40: error: unresolved reference: JsonObject + private fun fixture(path: String): JsonObject { + ^ diff --git a/android/build-logs/kotlinc-syntax-summary.txt b/android/build-logs/kotlinc-syntax-summary.txt new file mode 100644 index 0000000..1d17493 --- /dev/null +++ b/android/build-logs/kotlinc-syntax-summary.txt @@ -0,0 +1,6 @@ +Kotlinc was run without the Android/Gradle dependency classpath. Unresolved Android and library references are expected. +Potential parser/syntax diagnostics: +none + +Total diagnostics: +8017 build-logs/kotlinc-static.log diff --git a/android/build-logs/resource-validation.log b/android/build-logs/resource-validation.log new file mode 100644 index 0000000..50da2ab --- /dev/null +++ b/android/build-logs/resource-validation.log @@ -0,0 +1 @@ +PASS: 8 JSON-Dateien und 7 XML-Dateien parsebar diff --git a/android/build-logs/security-scan.log b/android/build-logs/security-scan.log new file mode 100644 index 0000000..c65e1d8 --- /dev/null +++ b/android/build-logs/security-scan.log @@ -0,0 +1,11 @@ +Production credential/key scan: +PASS: no production credential/key pattern found + +Forbidden technology scan: +PASS: no WebView or direct OpenAI client endpoint found + +Release cleartext controls: +app/src/main/res/xml/network_security_config.xml:3: +app/src/main/AndroidManifest.xml:15: android:usesCleartextTraffic="false"> +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:77: require(BuildConfig.DEBUG || scheme == "https") { "Release-Builds erlauben ausschließlich HTTPS." } +app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt:77: require(BuildConfig.DEBUG || scheme == "https") { "Release-Builds erlauben ausschließlich HTTPS." } diff --git a/android/build-logs/source-audit.json b/android/build-logs/source-audit.json new file mode 100644 index 0000000..d036373 --- /dev/null +++ b/android/build-logs/source-audit.json @@ -0,0 +1,148 @@ +{ + "current": { + "files": 103, + "extensions": { + ".py": 27, + ".json": 20, + ".example": 1, + "": 5, + ".md": 7, + ".txt": 1, + ".yml": 1, + ".css": 1, + ".js": 3, + ".tag": 1, + ".html": 6, + ".pyc": 29, + ".lock": 1 + } + }, + "bundle": { + "files": 29, + "extensions": { + ".json": 9, + ".md": 4, + ".py": 14, + ".html": 2 + } + }, + "samples": { + "files": 8, + "extensions": { + ".md": 1, + ".json": 7 + } + }, + "plans": [ + { + "file": "calihoss-newbie-8-wochen.json", + "plan_id": "calihoss-newbie-8-wochen", + "revision": 1, + "published_revision": 1, + "schema_version": 3, + "contract_version": 2, + "weeks": 8, + "days": 6, + "rotations": 12, + "exercise_placements": 48, + "stages": 31, + "steps": 248, + "exercise_catalog": 27, + "exercise_result_schemas": 48, + "step_result_schemas": 248, + "stable_ids_nonblank": true, + "stable_ids_unique": false, + "legacy_ids": 48, + "training_mode": "sets_reps" + }, + { + "file": "phase-1.json", + "plan_id": "phase-1", + "revision": 1, + "published_revision": 1, + "schema_version": 3, + "contract_version": 2, + "weeks": 8, + "days": 6, + "rotations": 12, + "exercise_placements": 48, + "stages": 31, + "steps": 248, + "exercise_catalog": 40, + "exercise_result_schemas": 48, + "step_result_schemas": 248, + "stable_ids_nonblank": true, + "stable_ids_unique": false, + "legacy_ids": 48, + "training_mode": "tabata" + } + ], + "sessions": [ + { + "file": "calihoss-newbie-8-wochen.json", + "version": 1, + "sessions": 6, + "progressions": 27, + "items": 88, + "legacy_position_items": 48, + "sequence_items": 40, + "structured_result_data": 0, + "free_text_results": 0 + }, + { + "file": "phase-1.json", + "version": 5, + "sessions": 10, + "progressions": 36, + "items": 197, + "legacy_position_items": 80, + "sequence_items": 117, + "structured_result_data": 0, + "free_text_results": 40 + } + ], + "analyses": [ + { + "file": "samples/analyses/phase-1/overall.json", + "type": "overall", + "week": null, + "status": "success", + "response_keys": [ + "headline", + "summary", + "data_quality", + "metrics", + "exercise_updates", + "plan_adjustments", + "warnings", + "conclusion" + ] + }, + { + "file": "samples/analyses/phase-1/week-01.json", + "type": "week", + "week": 1, + "status": "success", + "response_keys": [ + "headline", + "summary", + "data_quality", + "metrics", + "exercise_updates", + "plan_adjustments", + "warnings", + "conclusion" + ] + } + ], + "proposals": { + "version": 2, + "plan_id": "phase-1", + "published_revision": 1, + "count": 2, + "statuses": { + "open": 2 + } + }, + "screenshots": [] +} \ No newline at end of file diff --git a/android/build-logs/source-diff.log b/android/build-logs/source-diff.log new file mode 100644 index 0000000..55cacc3 --- /dev/null +++ b/android/build-logs/source-diff.log @@ -0,0 +1,11 @@ +trainingsplan diff: +Only in /mnt/data/boehmitools-training-work/boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan: __pycache__ +Only in /mnt/data/boehmitools-training-work/boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/tests: __pycache__ +trainingstracker diff: +Only in /mnt/data/boehmitools-training-work/boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker: __pycache__ +Only in /mnt/data/boehmitools-training-work/boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/tests: __pycache__ +plans diff: +Only in /mnt/data/boehmitools-training-work/boehmitools-current/01-boehmitools-current/boehmitools/data/trainingsplan/plans: couchsnacks.json +Only in /mnt/data/boehmitools-training-work/boehmitools-current/01-boehmitools-current/boehmitools/data/trainingsplan/plans: fruehstueck.json +Only in /mnt/data/boehmitools-training-work/boehmitools-current/01-boehmitools-current/boehmitools/data/trainingsplan/plans: granola.json +Only in /mnt/data/boehmitools-training-work/boehmitools-current/01-boehmitools-current/boehmitools/data/trainingsplan/plans: nachmittags.json diff --git a/android/build-logs/source-files.txt b/android/build-logs/source-files.txt new file mode 100644 index 0000000..1efc317 --- /dev/null +++ b/android/build-logs/source-files.txt @@ -0,0 +1,130 @@ +boehmitools-current/01-boehmitools-current/boehmitools/.dockerignore +boehmitools-current/01-boehmitools-current/boehmitools/.env.example +boehmitools-current/01-boehmitools-current/boehmitools/.gitignore +boehmitools-current/01-boehmitools-current/boehmitools/Dockerfile +boehmitools-current/01-boehmitools-current/boehmitools/MANIFEST.json +boehmitools-current/01-boehmitools-current/boehmitools/MIGRATION.md +boehmitools-current/01-boehmitools-current/boehmitools/README.md +boehmitools-current/01-boehmitools-current/boehmitools/RELEASE-NOTES.md +boehmitools-current/01-boehmitools-current/boehmitools/core/__init__.py +boehmitools-current/01-boehmitools-current/boehmitools/core/__pycache__/__init__.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/core/__pycache__/ai.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/core/__pycache__/app.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/core/__pycache__/backups.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/core/__pycache__/foodmatch.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/core/__pycache__/importer.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/core/__pycache__/jobs.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/core/__pycache__/loader.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/core/__pycache__/registry.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/core/__pycache__/security.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/core/__pycache__/settings.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/core/__pycache__/tandoor.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/core/ai.py +boehmitools-current/01-boehmitools-current/boehmitools/core/app.py +boehmitools-current/01-boehmitools-current/boehmitools/core/backups.py +boehmitools-current/01-boehmitools-current/boehmitools/core/foodmatch.py +boehmitools-current/01-boehmitools-current/boehmitools/core/importer.py +boehmitools-current/01-boehmitools-current/boehmitools/core/jobs.py +boehmitools-current/01-boehmitools-current/boehmitools/core/loader.py +boehmitools-current/01-boehmitools-current/boehmitools/core/registry.py +boehmitools-current/01-boehmitools-current/boehmitools/core/security.py +boehmitools-current/01-boehmitools-current/boehmitools/core/settings.py +boehmitools-current/01-boehmitools-current/boehmitools/core/static/404.html +boehmitools-current/01-boehmitools-current/boehmitools/core/static/dashboard.html +boehmitools-current/01-boehmitools-current/boehmitools/core/static/jobs.html +boehmitools-current/01-boehmitools-current/boehmitools/core/static/settings.html +boehmitools-current/01-boehmitools-current/boehmitools/core/tandoor.py +boehmitools-current/01-boehmitools-current/boehmitools/data/settings.json +boehmitools-current/01-boehmitools-current/boehmitools/data/trainingsplan/plans/calihoss-newbie-8-wochen.json +boehmitools-current/01-boehmitools-current/boehmitools/data/trainingsplan/plans/couchsnacks.json +boehmitools-current/01-boehmitools-current/boehmitools/data/trainingsplan/plans/fruehstueck.json +boehmitools-current/01-boehmitools-current/boehmitools/data/trainingsplan/plans/granola.json +boehmitools-current/01-boehmitools-current/boehmitools/data/trainingsplan/plans/nachmittags.json +boehmitools-current/01-boehmitools-current/boehmitools/data/trainingsplan/plans/phase-1.json +boehmitools-current/01-boehmitools-current/boehmitools/data/trainingstracker/analyses/phase-1.json/.analysis.lock +boehmitools-current/01-boehmitools-current/boehmitools/data/trainingstracker/analyses/phase-1.json/index.json +boehmitools-current/01-boehmitools-current/boehmitools/data/trainingstracker/analyses/phase-1.json/state.json +boehmitools-current/01-boehmitools-current/boehmitools/data/trainingstracker/analyses/phase-1.json/week-01.json +boehmitools-current/01-boehmitools-current/boehmitools/data/trainingstracker/sessions/calihoss-newbie-8-wochen.json +boehmitools-current/01-boehmitools-current/boehmitools/data/trainingstracker/sessions/phase-1.json +boehmitools-current/01-boehmitools-current/boehmitools/data/trainingstracker/settings.json +boehmitools-current/01-boehmitools-current/boehmitools/docker-compose.yml +boehmitools-current/01-boehmitools-current/boehmitools/plugins/README.md +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/TOOL-README.md +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/__pycache__/app.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/__pycache__/backend.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/__pycache__/plan_builder.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/__pycache__/recipe_builder.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/__pycache__/schema_contract.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/__pycache__/storage.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/app.py +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/backend.py +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/plan_builder.py +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/plugin.json +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/presets/couchsnacks.json +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/presets/fruehstueck.json +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/presets/granola.json +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/presets/nachmittags.json +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/recipe_builder.py +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/schema_contract.py +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/static/index.html +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/storage.py +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingsplan/tests/test_contract.py +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/README.md +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/__pycache__/analysis_store.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/__pycache__/app.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/__pycache__/backend.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/__pycache__/contract.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/__pycache__/session_store.cpython-312.pyc +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/analysis_store.py +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/app.py +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/backend.py +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/contract.py +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/plugin.json +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/session_store.py +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/static/index.html +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/tests/test_app.py +boehmitools-current/01-boehmitools-current/boehmitools/plugins/trainingstracker/tests/test_stability.py +boehmitools-current/01-boehmitools-current/boehmitools/requirements.txt +boehmitools-current/01-boehmitools-current/boehmitools/run.py +boehmitools-current/01-boehmitools-current/boehmitools/shared/boehmi-backups.js +boehmitools-current/01-boehmitools-current/boehmitools/shared/boehmi-runner.js +boehmitools-current/01-boehmitools-current/boehmitools/shared/boehmi.css +boehmitools-current/01-boehmitools-current/boehmitools/shared/boehmi.js +samples/training-app-samples/README.md +samples/training-app-samples/samples/analyses/phase-1/overall.json +samples/training-app-samples/samples/analyses/phase-1/week-01.json +samples/training-app-samples/samples/plans/calihoss-newbie-8-wochen.json +samples/training-app-samples/samples/plans/phase-1.json +samples/training-app-samples/samples/proposals/phase-1.json +samples/training-app-samples/samples/sessions/calihoss-newbie-8-wochen.json +samples/training-app-samples/samples/sessions/phase-1.json +training-tools-v2/MANIFEST.json +training-tools-v2/MIGRATION.md +training-tools-v2/RELEASE-NOTES.md +training-tools-v2/data/trainingsplan/plans/calihoss-newbie-8-wochen.json +training-tools-v2/data/trainingsplan/plans/phase-1.json +training-tools-v2/plugins/trainingsplan/TOOL-README.md +training-tools-v2/plugins/trainingsplan/app.py +training-tools-v2/plugins/trainingsplan/backend.py +training-tools-v2/plugins/trainingsplan/plan_builder.py +training-tools-v2/plugins/trainingsplan/plugin.json +training-tools-v2/plugins/trainingsplan/presets/couchsnacks.json +training-tools-v2/plugins/trainingsplan/presets/fruehstueck.json +training-tools-v2/plugins/trainingsplan/presets/granola.json +training-tools-v2/plugins/trainingsplan/presets/nachmittags.json +training-tools-v2/plugins/trainingsplan/recipe_builder.py +training-tools-v2/plugins/trainingsplan/schema_contract.py +training-tools-v2/plugins/trainingsplan/static/index.html +training-tools-v2/plugins/trainingsplan/storage.py +training-tools-v2/plugins/trainingsplan/tests/test_contract.py +training-tools-v2/plugins/trainingstracker/README.md +training-tools-v2/plugins/trainingstracker/analysis_store.py +training-tools-v2/plugins/trainingstracker/app.py +training-tools-v2/plugins/trainingstracker/backend.py +training-tools-v2/plugins/trainingstracker/contract.py +training-tools-v2/plugins/trainingstracker/plugin.json +training-tools-v2/plugins/trainingstracker/session_store.py +training-tools-v2/plugins/trainingstracker/static/index.html +training-tools-v2/plugins/trainingstracker/tests/test_app.py +training-tools-v2/plugins/trainingstracker/tests/test_stability.py diff --git a/android/build-logs/static-acceptance.log b/android/build-logs/static-acceptance.log new file mode 100644 index 0000000..b49d5f7 --- /dev/null +++ b/android/build-logs/static-acceptance.log @@ -0,0 +1,2 @@ +PASS: 728 Prüfungen +EXIT_CODE=0 diff --git a/android/build-logs/wrapper-bootstrap-sha256.log b/android/build-logs/wrapper-bootstrap-sha256.log new file mode 100644 index 0000000..93ae353 --- /dev/null +++ b/android/build-logs/wrapper-bootstrap-sha256.log @@ -0,0 +1,2 @@ +471264b9b8fdf46888558ce7f23bacf2e4c958edfac3106b80c065af6e727cf1 gradle/wrapper/gradle-wrapper.jar +d16696db59d33ccbed443f46c95e0f44b4636605db3fe515163e99a6d8c006f1 gradle/wrapper/bootstrap-src/org/gradle/wrapper/GradleWrapperMain.java diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..2ccc690 --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,8 @@ +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.compose) apply false + alias(libs.plugins.kotlin.serialization) apply false + alias(libs.plugins.kotlin.kapt) apply false + alias(libs.plugins.hilt) apply false +} diff --git a/android/docs/API_CONTRACT.md b/android/docs/API_CONTRACT.md new file mode 100644 index 0000000..59fcae6 --- /dev/null +++ b/android/docs/API_CONTRACT.md @@ -0,0 +1,280 @@ +# API-Vertrag boehmitools Training + +Stand: aus dem vollständigen boehmitools-Quellstand und den Plugins `trainingsplan`/`trainingstracker` 2.0 abgeleitet. Die Plugins werden durch die Host-App unter `/plugins/` gemountet. Alle Pfade unten sind relativ zur konfigurierbaren Serverbasis, standardmäßig `https://tools.d-razz.de/`. + +## Authentifizierung + +Die Host-App schützt alle Pfade außer exakt `/api/health` optional mit HTTP Basic Auth. Ist `APP_PASSWORD` gesetzt, muss der Client `Authorization: Basic ` senden. Der Standard-Benutzername des Servers ist `admin`, sofern `APP_USERNAME` nicht gesetzt wurde. + +Fehlerantwort bei fehlender oder falscher Anmeldung: + +```json +{"detail":"Anmeldung erforderlich."} +``` + +Status: `401 Unauthorized`, Header `WWW-Authenticate: Basic realm="boehmitools"`. + +## Host-Verbindungstest + +### `GET /api/health` + +Ohne Authentifizierung erreichbar. Antwortfelder laut Backend: + +```json +{ + "status": "ok", + "version": "1.0.0", + "plugins": {"trainingsplan":"ok","trainingstracker":"ok"}, + "tandoor_configured": false, + "openai_configured": true, + "authentication_enabled": true, + "jobs_running": 0 +} +``` + +Status: `200`. Die App prüft danach zusätzlich den authentifizierten Tracker-Health-Endpunkt. + +## Trainingstracker + +Basis: `/plugins/trainingstracker` + +### `GET /api/health` + +Liefert `ok`, Server-Datenpfade, Verfügbarkeit von OpenAI und die unterstützten Vertragsversionen: + +- `plan_schema` +- `training_contract` +- `tracker_schema` +- `result_data` +- `prompt` +- `analysis_schema` +- `normalization` + +Status: `200`, oder hostweit `401`. + +### `GET /api/plans` + +Antwort: + +```json +{ + "plans": [{ + "id": "phase-1.json", + "filename": "phase-1.json", + "name": "Phase 1", + "title": "…", + "subtitle": "…", + "weeks": 8, + "days": 6, + "tracked": true, + "start_date": "", + "completed_sessions": 5, + "source_changed": false, + "plan_id": "phase-1", + "published_revision": 1, + "tracker_revision": 7 + }], + "selected": "phase-1.json" +} +``` + +Status: `200`. + +### `POST /api/plans/{plan_id}/select` + +Kein Request-Body erforderlich. Antwort: `{"ok":true,"selected":"phase-1.json"}`. + +Status: `200`; `404` mit `{"error":"Trainingsplan nicht gefunden"}`. + +### `GET /api/plans/{plan_id}` + +Antwort: + +```json +{ + "plan": { /* normalisierte veröffentlichte Planfassung */ }, + "tracker": { /* Tracker-Schema 7 */ }, + "analysis_catalog": { /* Wochen- und Gesamtstatus */ }, + "equivalence_guide": { /* Variantencluster und Fallback-FAQ */ }, + "capabilities": {"openai": true} +} +``` + +`plan` enthält ausschließlich vom Backend erzeugte Felder wie `source_file`, `source_hash`, `plan_id`, `schema_version`, `contract_version`, `compatibility`, `published_revision`, `published_at`, `name`, `title`, `subtitle`, `weeks`, `days`, `phases`, `phase_by_week`, `stages`, `exercise_catalog`, `front`, `training_format`. + +`tracker` enthält mindestens `version`, `revision`, `source_file`, `source_hash`, `profile`, `progressions`, `sessions`, `week_statuses`, `created_at`, `updated_at`; für die GET-Antwort werden zusätzlich `source_changed`, `analysis_cache` und `analysis_state` zusammengesetzt. + +Status: `200`; `404`; `400` bei nicht lesbarem Plan. + +### `PUT /api/plans/{plan_id}/tracker` + +Vollständiges Speichern, primär für Kompatibilität. Request entweder direkt Trackerobjekt oder: + +```json +{"expected_revision":7,"tracker":{}} +``` + +Antwort: + +```json +{ + "ok": true, + "updated_at": "…", + "revision": 8, + "analysis_catalog": {}, + "analysis_state": {} +} +``` + +Status: `200`; `400` für ungültige Nutzdaten; `409` bei Revisionskonflikt; `413` über 5 MiB. + +### `PATCH /api/plans/{plan_id}/tracker/session` + +Der von der App verwendete Autosave-Vertrag: + +```json +{ + "expected_revision": 7, + "profile": {"start_date":"","display_name":"","plan_notes":""}, + "week_statuses": {"1":{"status":"open"}}, + "session_key": "w01-d01", + "session": { + "status": "in_progress", + "plan_id": "phase-1", + "plan_revision": 1, + "items": {} + } +} +``` + +`profile`, `week_statuses` und `session` sind jeweils optional. Ein Patch ersetzt die betreffende Session vollständig, nicht einzelne Felder innerhalb der Session. + +Erlaubte Sessionstatus: `planned`, `in_progress`, `stopped`, `completed`. + +Erlaubte Übungsstatus: `planned`, `completed`, `partial`, `skipped`. Bei `skipped` kann `skip_reason` gespeichert werden. Neue strukturierte Ergebnisse nutzen `result_data` Version 2: + +```json +{ + "version": 2, + "mode": "reps|seconds|minutes", + "laterality": "bilateral|unilateral", + "sides_mode": "same|separate", + "sets": 3, + "weight_kg": 8.0, + "values": [8,7,6], + "left_values": [8,7,6], + "right_values": [8,7,6] +} +``` + +Das Backend akzeptiert maximal 20 Sätze, Werte von 0 bis 100000 und genau ein gemeinsames Gewicht. Alte Freitextfelder `result`, `note`, `progression` bleiben erhalten. + +Erfolg wie beim PUT. Konfliktantwort: + +```json +{ + "error":"Die Session wurde in einem anderen Tab oder Gerät geändert.", + "code":"revision_conflict", + "current_revision":8 +} +``` + +Status: `409`. Die App darf dann nicht automatisch gegen Revision 8 überschreiben. + +### `POST /api/plans/{plan_id}/analysis` + +Request Woche: + +```json +{"scope":"week","week":1} +``` + +Request Gesamtanalyse: + +```json +{"scope":"overall"} +``` + +Unveränderte, bereits aktuelle Analyse: `200` mit `cached:true`, `selection`, `analysis_state`, `analysis_cache`, `analysis_catalog`. + +Neuer Hintergrundjob: `202 Accepted` mit `cached:false`, `job`, `analysis_state`, `analysis_catalog`. + +Fehler: `400` bei fehlender OpenAI-Konfiguration, ungültiger Woche oder fehlenden Daten. `409` wenn bereits eine Analyse läuft. `404` bei unbekanntem Plan. + +### `GET /api/plans/{plan_id}/analysis/status` + +Antwort enthält `analysis_state`, `analysis_cache`, `analysis_catalog`, `updated_at`. Die App pollt nur bei `analysis_state.status == "running"`. + +Status: `200`; `400`; `404`. + +### `GET /api/plans/{plan_id}/tracker/export` + +Liefert die Sessiondatei als `application/json`; erzeugt bei Bedarf einen leeren Tracker. Status `200` oder `404`. + +## Trainingsplan-Editor + +Basis: `/plugins/trainingsplan` + +### `GET /api/plans` + +Antwort: `plans` mit `id`, `name`, `active`, `revision`, `published_revision`, `has_unpublished_changes`, plus `active`. + +### `POST /api/plans` + +Request: + +```json +{"name":"Neue Phase","from":"optionale-plan-id","kind":"training"} +``` + +`kind` kann technisch auch `recipe` sein; die Android-App erstellt ausschließlich Trainingspläne. Antwort ist der öffentliche Wrapper mit `id`, `plan_id`, `name`, `revision`, `published_revision`, `updated_at`, `published_at`, `has_unpublished_changes`, `tracked_sessions`, `config`. + +### `GET /api/plans/{pid}` + +Öffentlicher Wrapper des Entwurfs. Status `200` oder `404`. + +### `POST /api/plans/{pid}/select` + +Setzt den aktiven Plan. Status `200` oder `404`. + +### `POST /api/plans/{pid}` + +Speichert den Entwurf: + +```json +{"config":{},"expected_revision":4} +``` + +Antwort: `ok`, öffentlicher Wrapper, `validation` mit `errors` und `warnings`. + +Konflikt: `409` mit `code:"revision_conflict"` und `current_revision`. + +### `POST /api/plans/{pid}/validate` + +Request `{"config":{}}`. Antwort: `errors`, `warnings`, normalisierte `config`, `diff`. `diff` enthält Zähler und stabile-ID-basierte Änderungen für `days`, `rotations`, `exercises`, `progression_steps`, außerdem `changes`, `total_changes`, `training_format_changed`, `plan_meta_changed`, `tracked_sessions`. + +### `POST /api/plans/{pid}/publish` + +Request `{"expected_revision":4}`. Erfolgsantwort: `ok`, Wrapper, `diff`, `validation`. + +Status `400` bei Validierungsfehlern; `409` bei Revisionskonflikt. + +### `POST /api/plans/{pid}/rename` + +Request `{"name":"…"}`. Antwort `id`, `name`, `revision`. Status `400` bei leerem Namen, `404` bei unbekanntem Plan. + +### `DELETE /api/plans/{pid}` + +Antwort `{"ok":true,"active":"…"}`. Status `400`, wenn der letzte Plan gelöscht werden soll; `404` bei unbekanntem Plan. + +### `GET /api/plans/{pid}/proposals` + +Liefert die Vorschlagsdatei unverändert oder `{}`. Vorschläge enthalten unter anderem `id`, `status`, `scope_key`, `analysis_type`, `week`, `target`, `target_label`, `target_exercise_id`, `target_progression_id`, `target_step_id`, `suggested_change`, `reason`, `condition`, `manual_step`, `analysis_created_at`. + +### `POST /api/plans/{pid}/proposals/{proposal_id}` + +Request `{"status":"open|accepted|rejected|reviewed"}`. Antwort `ok`, `proposals`. Ungültiger Status: `400`. + +### Weitere vorhandene Endpunkte + +`GET /api/config`, `POST /api/config`, `GET /api/default`, `POST /api/reset`, `GET /api/plans/{pid}/export`, `POST /api/plans/import`, `POST /api/pdf` existieren. Die native App verwendet für den Kernbetrieb die planbezogenen Endpunkte; sie bearbeitet niemals Serverdateien direkt. diff --git a/android/docs/DOMAIN_MODEL.md b/android/docs/DOMAIN_MODEL.md new file mode 100644 index 0000000..51ed7d5 --- /dev/null +++ b/android/docs/DOMAIN_MODEL.md @@ -0,0 +1,62 @@ +# Domänenmodell + +## Vertragsversionen + +- Plan: `schema_version = 3` +- Trainingsvertrag: `contract_version = 2` +- Ergebnisschema: Version 2 +- Tracker: `version = 7` +- Ergebnisdaten: `version = 2` + +## Identitäten + +Dauerhaft stabil sind `plan_id`, Phasen-ID, Tages-ID, Rotations-ID, Übungsplatzierungs-ID, Bewegungs-`exercise_id`, `progression_id`, Progressions-ID und Stufen-ID. `legacy_id` wie `d1-r0-e0` dient ausschließlich der Migration alter Sessions. Neue Sessionitems werden mit der stabilen Übungsplatzierungs-ID gespeichert. + +## Planlebenszyklus + +Eine Plandatei besitzt `config` als veröffentlichte Fassung und `draft` als bearbeitbaren Entwurf. `revision` schützt Bearbeitungen, `published_revision` kennzeichnet die vom Tracker gelesene Fassung. Entwurf speichern und veröffentlichen sind getrennte Operationen. + +## Planstruktur + +Plan → Phasen, Tage, Progressionen, Übungsbibliothek und Trainingsformat. + +Tag → Warm-up, Rotationen, Cooldown, Stretch. + +Rotation → stabile ID, Bezeichnung, Übungen. + +Übungsplatzierung → `id`, `legacy_id`, `exercise_id`, `progression_id`, Name, Hinweise, Standard-`result_schema`. + +Progression → stabile ID/Key, Name, geordnete Stufen. + +Progressionsstufe → ID, Name, optionale Phase, Bewegungscluster, Analysefaktor und optional überschreibendes `result_schema`. + +## Ergebnisschema + +Auflösungsreihenfolge: + +1. manuelle Sessionanpassung, +2. Schema der gewählten Progressionsstufe, +3. Standardschema der Übung, +4. automatische Fallback-Erkennung. + +Felder: `mode` (`reps`, `seconds`, `minutes`, `none`, im Plan zusätzlich `auto`), `weight_mode` (`none`, `optional`, `required`), `laterality` (`bilateral`, `unilateral`), `sides_mode` (`same`, `separate`) sowie optional eine feste Satzanzahl. + +## Tracker + +Tracker → Profil, Sessions, Wochenstatus, Revision und Planbezug. + +Session-Key: `wNN-dNN`. Sessionstatus: `planned`, `in_progress`, `stopped`, `completed`. + +Sessionitemstatus: `planned`, `completed`, `partial`, `skipped`. `done` bleibt als Kompatibilitätsfeld erhalten und ist bei `completed` oder `partial` wahr. + +`result_data` speichert eine Messart, gemeinsame oder getrennte Satzwerte und höchstens ein gemeinsames Gewicht. Unterschiedliche Gewichte pro Satz, RIR, RPE und Technikrating gehören bewusst nicht zum Vertrag. + +## Analyse + +`analysis_state` ist persistent und kann `idle`, `running`, `done` oder `error` sein. Der Cache enthält höchstens eine Analyse je Woche und genau eine Gesamtanalyse. Neuberechnung überschreibt dieselbe Datei. Es gibt keine Historie. + +`analysis_catalog` kennzeichnet Wochen als `missing`, `stale` oder `current`; die Gesamtanalyse zusätzlich als `needs_weeks`. + +## Vorschläge + +Analysen erzeugen höchstens drei strukturierte Vorschläge. Ziel-IDs verbinden Vorschläge mit Übungen, Progressionen und Stufen. Die App darf Status ändern und zum Ziel navigieren, aber niemals automatisch den Plan mutieren. diff --git a/android/docs/IMPLEMENTATION_PLAN.md b/android/docs/IMPLEMENTATION_PLAN.md new file mode 100644 index 0000000..2c7df07 --- /dev/null +++ b/android/docs/IMPLEMENTATION_PLAN.md @@ -0,0 +1,25 @@ +# Implementierungsplan + +## Meilenstein 0 + +Backendcode, Mounts, Authentifizierung, Datenmodelle, Migrationen, Beispielpläne, alte Sessions, Analysen, Vorschläge und Web-UIs analysieren. Vertrag und Konfliktmodell dokumentieren. + +## Meilenstein 1 + +Single-Activity-App, Compose/Material 3, Navigation, Hilt, Retrofit/OkHttp, Kotlin Serialization, Room, DataStore, Android-Keystore-Verschlüsselung, WorkManager und Verbindungstest. + +## Meilenstein 2 + +Trackerplan laden, UI-Zustand pro Plan persistieren, Legacy-IDs migrieren, Sessionstatus und Übungsstatus abbilden, Ergebnisfelder aus Stufe/Übung/Fallback bestimmen, debounced Autosave und Offline-Queue implementieren. + +## Meilenstein 3 + +Lokale Fortschrittskarten, Variantencluster/FAQ, Wochenabschluss, Analyseauswahl, `200`/`202`, persistente Jobanzeige und Polling. + +## Meilenstein 4 + +Entwurfs-CRUD, strukturierte Planansicht, Result-Schema-Editor, Bibliothek, Validierung, Diff, Publish und manuell bestätigte KI-Vorschläge. + +## Meilenstein 5 + +Unit-, Repository-, MockWebServer- und Compose-Tests mit allen gelieferten Fixtures; 360-dp-Prüfung; Light/Dark; Gradle-Test-, Lint- und Assemble-Läufe; ZIP/APK/Build- und Testbericht. diff --git a/android/docs/OFFLINE_AND_CONFLICTS.md b/android/docs/OFFLINE_AND_CONFLICTS.md new file mode 100644 index 0000000..e514db9 --- /dev/null +++ b/android/docs/OFFLINE_AND_CONFLICTS.md @@ -0,0 +1,27 @@ +# Offline- und Konfliktkonzept + +## Cache + +Room speichert die zuletzt erfolgreichen JSON-Antworten für Planlisten, normalisierte Trackerpläne, Editorentwürfe und Analysen. Der Cache ist lesbar, wenn das Netzwerk fehlt; er ist kein eigenständiger Serverdatenbestand. + +## Ausstehende Mutationen + +Sessionänderungen werden als vollständiger Session-Patch mit `expected_revision` gespeichert. Bei Transportfehlern wird exakt der gesendete JSON-Patch in einer FIFO-Queue abgelegt. WorkManager synchronisiert nur bei Netzwerkverbindung und in Erstellungsreihenfolge. + +## Revisionskonflikt + +HTTP `409` mit `code = revision_conflict` stoppt die Queue am konfliktbehafteten Eintrag. Der Eintrag bleibt erhalten und wird als Konflikt markiert. Die UI zeigt lokalen Patch und aktuelle Serverrevision. Es gibt kein automatisches Rebase und kein blindes Überschreiben. + +Sichere Aktionen: + +- Serverstand neu laden und lokale Änderung manuell erneut anwenden. +- Konflikt-Patch nach bewusster Benutzerentscheidung verwerfen. +- Keine automatische Änderung der `expected_revision`. + +## Analysejobs + +Ein laufender Job wird vom Server in `state.json` gespeichert. Die App verlässt sich nicht auf flüchtigen UI-Zustand, sondern liest nach Neustart `analysis_state` erneut. Solange `status == running`, bleibt der Startbutton deaktiviert und der Statusendpunkt wird gepollt. + +## Serveränderungen + +Die App schreibt nie direkt in Plan-, Session-, Analyse- oder Vorschlagsdateien. Sämtliche Mutationen laufen über die vorhandenen HTTP-Endpunkte. diff --git a/android/docs/SCREEN_MAP.md b/android/docs/SCREEN_MAP.md new file mode 100644 index 0000000..ed01975 --- /dev/null +++ b/android/docs/SCREEN_MAP.md @@ -0,0 +1,36 @@ +# Screen Map + +## App-Shell + +- Start/Verbindung: Server-URL, Benutzername, Passwort, Verbindungstest. +- Hauptnavigation: Tracker, Planeditor, Einstellungen. +- Material-3-Theme mit Light/Dark/System. + +## Tracker + +- Planauswahl. +- Session-Ansicht mit Wochen- und Tageschips. +- Sessionstatus starten, stoppen, fortsetzen, wieder öffnen, abschließen, zurücksetzen. +- Warm-up, Rotationen, Übungen, Cooldown und Stretch. +- Übungsstatus offen, erledigt, teilweise, übersprungen. +- Progressionsauswahl und progressionsabhängiger Ergebnis-Editor. +- Fortschritt: lokale Sessionkennzahlen, Wochenstatus, Analyseauswahl, Cache/Jobstatus. +- Plan: veröffentlichte Struktur und Planrevision. +- FAQ: Trainingsformat, Variantencluster und Backend-Fallbackregeln. + +## Planeditor + +- Planliste, Auswahl, neuer Plan, Umbenennen, Löschen. +- Entwurfsübersicht und Metadaten. +- Tage, Rotationen und Übungen mit stabilen IDs. +- Ergebnisschema pro Übung. +- Progressionsstufen und überschreibende Ergebnisschemata. +- Übungsbibliothek. +- Prüfung mit Fehlern/Warnungen. +- Tracker-Vorschau und Änderungsübersicht. +- Veröffentlichen. +- Vorschlags-Postfach: prüfen, Ziel öffnen, angenommen/abgelehnt/geprüft markieren. + +## Visuelle Referenz + +In den gelieferten Archiven sind keine Bilddateien oder Screenshots enthalten. Als vorhandene visuelle Referenz wurden daher die mobile-first HTML/CSS-Oberflächen der beiden Plugins ausgewertet. Die native Umsetzung übernimmt Informationshierarchie und Funktionsgruppen, nicht DOM-Struktur oder Web-Komponenten. diff --git a/android/docs/SOURCE_ANALYSIS.md b/android/docs/SOURCE_ANALYSIS.md new file mode 100644 index 0000000..924512e --- /dev/null +++ b/android/docs/SOURCE_ANALYSIS.md @@ -0,0 +1,85 @@ +# Vollständige Quellenanalyse + +Stand: 24. Juli 2026 + +## Untersuchte Lieferobjekte + +| Archiv | Originaldateien | SHA-256 | +|---|---:|---| +| `01-boehmitools-current(1).zip` | 93 | `167d0d1b4670cbe6bb7cab10d2b84c315fde6dadc824cf3f7b9d6ccce3d61d1e` | +| `02-training-tools-v2.0-bundle.zip` | 29 | `b2859ccb86a3915a86e3c64e157c9021f3b713b63b600360419e686f41c8dbee` | +| `03-training-app-samples.zip` | 8 | `1634f3b83e98d5e5df7d1c56719dede13ed4cf1ce1517a3745cfee0384f93991` | + +Alle enthaltenen Dateien wurden inventarisiert. Die technische Analyse umfasste insbesondere Host-Mounts und Basic Auth, beide Plugin-Backends, Storage- und Vertragsmodule, Plugin-Metadaten, Tests, HTML-Oberflächen, Migrationsdokumentation, beide Planwrapper, beide Legacy-Tracker, Wochen- und Gesamtanalyse sowie die Vorschlagsdatei. Die vollständige Dateiliste liegt unter `build-logs/source-files.txt`; die maschinenlesbare Datenauswertung unter `build-logs/source-audit.json`. + +## Abgleich aktueller Serverstand gegen v2.0-Bundle + +Die Quelltexte von `plugins/trainingsplan` und `plugins/trainingstracker` sind in beiden Lieferungen inhaltlich identisch. Im entpackten aktuellen Stand kamen durch lokale Python-Testläufe lediglich `__pycache__`-Dateien hinzu. Die beiden Trainingsplan-Dateien `phase-1.json` und `calihoss-newbie-8-wochen.json` sind ebenfalls identisch. Der vollständige Serverstand enthält zusätzlich vier Rezeptplan-Dateien, die nicht zum Umfang dieser Android-App gehören. + +Die Host-App mountet die Plugins unter: + +- `/plugins/trainingsplan` +- `/plugins/trainingstracker` + +Die App nutzt ausschließlich diese HTTP-Mounts. Es gibt keinen direkten Dateizugriff. + +## Pläne und Vertragsversionen + +Beide Planwrapper haben `revision = 1` und `published_revision = 1`. Die veröffentlichte Fassung liegt in `config`, der Entwurf in `draft`. + +| Plan | Modus | Wochen | Tage | Blöcke | Platzierungen | Progressionen | Stufen | Katalog | +|---|---|---:|---:|---:|---:|---:|---:|---:| +| `phase-1` | `tabata` | 8 | 6 | 12 | 48 | 31 | 248 | 40 | +| `calihoss-newbie-8-wochen` | `sets_reps` | 8 | 6 | 12 | 48 | 31 | 248 | 27 | + +Jede der 96 Übungsplatzierungen besitzt ein explizites `result_schema`; alle 496 Progressionsstufen ebenfalls. Tages-, Rotations-, Platzierungs-, Progressions- und Stufen-IDs sind innerhalb ihres jeweiligen Entitätstyps vollständig und eindeutig. Bewegungs-IDs werden absichtlich über mehrere Platzierungen hinweg wiederverwendet und dürfen daher nicht global eindeutig behandelt werden. + +Die Beschreibung verwendet die Begriffe `plan_schema` und `training_contract`. In den Plan-JSONs heißen die tatsächlichen Felder `schema_version = 3` und `contract_version = 2`. Der Tracker-Health-Endpunkt meldet dieselben Verträge als `versions.plan_schema = 3` und `versions.training_contract = 2`. Die Android-Domäne bildet diese Namensdifferenz explizit ab, statt ein zusätzliches JSON-Feld zu erfinden. + +## Legacy-Sessions und Migration + +| Sessiondatei | gelieferte Version | Sessions | Items | positionsbasierte Übungskeys | Freitextergebnisse | `result_data` v2 | +|---|---:|---:|---:|---:|---:|---:| +| `phase-1.json` | 5 | 10 | 197 | 80 | 40 | 0 | +| `calihoss-newbie-8-wochen.json` | 1 | 6 | 88 | 48 | 0 | 0 | + +Die Testdaten erzwingen damit beide Migrationspfade: + +1. `dN-rN-eN` wird ausschließlich anhand des im Plan mitgelieferten `legacy_id` in die stabile Platzierungs-ID überführt. +2. Vorhandene Freitextergebnisse werden lokal in `result_data` Version 2 geparst, ohne den Originaltext vor einer Benutzeränderung zu verwerfen. + +Warm-up-, Cooldown- und Stretch-Items besitzen eigene Sequenzschlüssel und werden nicht als Übungsplatzierungen migriert. + +## Analyse und Vorschläge + +Geliefert sind genau eine Wochenanalyse für Woche 1 und genau eine Gesamtanalyse. Beide besitzen `status = success` und die Antwortbereiche `headline`, `summary`, `data_quality`, `metrics`, `exercise_updates`, `plan_adjustments`, `warnings` und `conclusion`. + +Die Vorschlagsdatei hat Version 2, gehört zu `phase-1`, basiert auf `published_revision = 1` und enthält zwei offene Vorschläge. Zielreferenzen verwenden stabile Bewegungs-, Progressions- und Stufen-IDs. Der Editor aktualisiert nur den Vorschlagsstatus und navigiert zum Ziel. Er überträgt keinen Vorschlag automatisch in den Entwurf. + +## API- und Konfliktbefunde + +- Hostweite Basic Auth wird vor den Plugin-Routen ausgewertet; nur der exakte Hostpfad `/api/health` ist ausgenommen. +- Der Session-Patch ersetzt eine komplette Session und prüft die globale Tracker-`revision` über `expected_revision`. +- Ein `409 revision_conflict` liefert `current_revision`; es existiert kein serverseitiger Merge-Endpunkt. +- Analyse-POST liefert `200` für einen aktuellen Cachetreffer und `202` für einen neu gestarteten Hintergrundjob. +- Der laufende Analysezustand ist serverseitig persistent und wird über den Statusendpunkt gelesen. +- Planentwurf und Veröffentlichung haben getrennte Revisionen und getrennte Endpunkte. +- Der Server erstellt beim Planlöschen ein Backup. Die Android-App verlässt sich dennoch ausschließlich auf die API und kennt keinen Backup-Dateipfad. + +## Visuelle Referenz + +Keines der drei Archive enthält PNG-, JPEG-, WebP- oder andere Screenshotdateien. Das ist ein Widerspruch zur Aufgabenbeschreibung, die UI-Screenshots als mitgelieferte Referenz nennt. Für die Umsetzung wurden deshalb die beiden mobilen HTML/CSS-Oberflächen vollständig als visuelle und funktionale Referenz ausgewertet. Die native App übernimmt Informationshierarchie, Begriffe und Arbeitsabläufe, nicht DOM-Struktur oder Web-Komponenten. + +## Bewusst nicht übernommene Web-Funktionen + +Der `phase-1`-Plan deklariert ein Tabata-Format. Entsprechend der App-Vorgabe wird dieser Vertrag angezeigt und für Ergebnisschemata berücksichtigt, aber es wird kein Tabata- oder Trainingstimer implementiert. Ebenso fehlen bewusst RIR, RPE, Technikrating, unterschiedliche Gewichte pro Satz, WebView und direkte OpenAI-Kommunikation. + +## Verifikation des gelieferten Backends + +Die vorhandenen Plugin-Testsets wurden lokal ausgeführt: + +```text +11 passed, 8 skipped +``` + +Das vollständige Protokoll liegt unter `build-logs/backend-plugin-tests.log`. diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..f97184b --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8 +android.useAndroidX=true +kotlin.code.style=official +android.nonTransitiveRClass=true diff --git a/android/gradle/libs.versions.toml b/android/gradle/libs.versions.toml new file mode 100644 index 0000000..65ddd74 --- /dev/null +++ b/android/gradle/libs.versions.toml @@ -0,0 +1,74 @@ +[versions] +agp = "8.9.1" +kotlin = "2.1.20" +composeBom = "2025.05.01" +activityCompose = "1.10.1" +lifecycle = "2.9.0" +navigation = "2.9.0" +hilt = "2.56.2" +hiltNavigation = "1.2.0" +room = "2.7.1" +datastore = "1.1.7" +work = "2.10.1" +retrofit = "2.11.0" +okhttp = "4.12.0" +serialization = "1.8.1" +coreKtx = "1.16.0" +securityCrypto = "1.1.0-alpha06" +junit = "4.13.2" +androidxJunit = "1.2.1" +espresso = "3.6.1" +testRunner = "1.6.2" +testCore = "1.6.1" +mockwebserver = "4.12.0" +coroutines = "1.10.2" + +[libraries] +androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" } +androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" } +androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } +androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycle" } +androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" } +androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigation" } +compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" } +compose-ui = { module = "androidx.compose.ui:ui" } +compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } +compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } +compose-material3 = { module = "androidx.compose.material3:material3" } +compose-icons = { module = "androidx.compose.material:material-icons-extended" } +compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" } +compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" } +hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } +hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" } +hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigation" } +hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "hiltNavigation" } +hilt-work-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "hiltNavigation" } +room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } +room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } +room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } +room-testing = { module = "androidx.room:room-testing", version.ref = "room" } +datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore" } +work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "work" } +work-testing = { module = "androidx.work:work-testing", version.ref = "work" } +retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } +retrofit-kotlinx = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "retrofit" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } +okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" } +kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" } +junit = { module = "junit:junit", version.ref = "junit" } +androidx-junit = { module = "androidx.test.ext:junit", version.ref = "androidxJunit" } +espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" } +androidx-test-runner = { module = "androidx.test:runner", version.ref = "testRunner" } +androidx-test-core-ktx = { module = "androidx.test:core-ktx", version.ref = "testCore" } +hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" } +mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "mockwebserver" } +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } +hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } diff --git a/android/gradle/wrapper/bootstrap-src/org/gradle/wrapper/GradleWrapperMain.java b/android/gradle/wrapper/bootstrap-src/org/gradle/wrapper/GradleWrapperMain.java new file mode 100644 index 0000000..35145ff --- /dev/null +++ b/android/gradle/wrapper/bootstrap-src/org/gradle/wrapper/GradleWrapperMain.java @@ -0,0 +1,174 @@ +package org.gradle.wrapper; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.net.URLConnection; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.nio.file.StandardOpenOption; +import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.HexFormat; +import java.util.List; +import java.util.Properties; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +/** + * Small auditable Gradle wrapper bootstrap used because the official wrapper JAR + * could not be generated in the build container. It downloads the configured + * distribution, validates SHA-256, extracts it with zip-slip protection and + * starts Gradle in the project directory. + */ +public final class GradleWrapperMain { + private GradleWrapperMain() {} + + public static void main(String[] args) throws Exception { + Path jar = Paths.get(GradleWrapperMain.class.getProtectionDomain().getCodeSource().getLocation().toURI()); + Path projectRoot = jar.getParent().getParent().getParent(); + Path propertiesPath = jar.resolveSibling("gradle-wrapper.properties"); + Properties properties = new Properties(); + try (InputStream input = Files.newInputStream(propertiesPath)) { + properties.load(input); + } + + URI distributionUri = URI.create(required(properties, "distributionUrl")); + String archiveName = Paths.get(distributionUri.getPath()).getFileName().toString(); + String distributionName = archiveName.replaceFirst("-(bin|all)\\.zip$", ""); + Path distributionDirectory = gradleUserHome(args) + .resolve(properties.getProperty("distributionPath", "wrapper/dists")) + .resolve(distributionName) + .resolve(archiveName); + Path archive = distributionDirectory.resolve(archiveName); + Path marker = distributionDirectory.resolve(".installed"); + Files.createDirectories(distributionDirectory); + + if (!Files.exists(marker)) { + if (!Files.exists(archive)) { + download(distributionUri.toURL(), archive, intProperty(properties, "networkTimeout", 10_000)); + } + String expectedSha = properties.getProperty("distributionSha256Sum", "").trim(); + if (!expectedSha.isEmpty()) { + verifySha256(archive, expectedSha); + } + unzip(archive, distributionDirectory); + Files.writeString(marker, "ok\n", StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + } + + Path gradleHome; + try (Stream children = Files.list(distributionDirectory)) { + gradleHome = children + .filter(Files::isDirectory) + .filter(path -> path.getFileName().toString().startsWith("gradle-")) + .findFirst() + .orElseThrow(() -> new IOException("Extracted Gradle directory not found in " + distributionDirectory)); + } + + boolean windows = System.getProperty("os.name", "").toLowerCase().contains("win"); + Path executable = gradleHome.resolve("bin").resolve(windows ? "gradle.bat" : "gradle"); + if (!windows) executable.toFile().setExecutable(true); + List command = new ArrayList<>(); + command.add(executable.toString()); + for (String argument : args) command.add(argument); + Process process = new ProcessBuilder(command) + .directory(projectRoot.toFile()) + .inheritIO() + .start(); + System.exit(process.waitFor()); + } + + private static Path gradleUserHome(String[] args) { + for (int index = 0; index < args.length - 1; index++) { + if ("-g".equals(args[index]) || "--gradle-user-home".equals(args[index])) { + return Paths.get(args[index + 1]).toAbsolutePath(); + } + } + String configured = System.getenv("GRADLE_USER_HOME"); + return configured == null || configured.isBlank() + ? Paths.get(System.getProperty("user.home"), ".gradle") + : Paths.get(configured).toAbsolutePath(); + } + + private static void download(URL initialUrl, Path destination, int timeoutMillis) throws Exception { + URL url = initialUrl; + for (int redirect = 0; redirect < 8; redirect++) { + URLConnection connection = url.openConnection(); + connection.setConnectTimeout(timeoutMillis); + connection.setReadTimeout(Math.max(timeoutMillis, 30_000)); + connection.setRequestProperty("User-Agent", "boehmitools-training-gradle-wrapper"); + if (connection instanceof HttpURLConnection http) { + http.setInstanceFollowRedirects(false); + int status = http.getResponseCode(); + if (status >= 300 && status < 400) { + String location = http.getHeaderField("Location"); + if (location == null) throw new IOException("Redirect without Location from " + url); + url = url.toURI().resolve(location).toURL(); + http.disconnect(); + continue; + } + if (status >= 400) throw new IOException("HTTP " + status + " while downloading " + url); + } + Path partial = destination.resolveSibling(destination.getFileName() + ".part"); + try (InputStream input = connection.getInputStream(); OutputStream output = Files.newOutputStream(partial)) { + input.transferTo(output); + } + Files.move(partial, destination, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); + return; + } + throw new IOException("Too many redirects while downloading " + initialUrl); + } + + private static void verifySha256(Path file, String expected) throws Exception { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + try (InputStream input = Files.newInputStream(file)) { + byte[] buffer = new byte[64 * 1024]; + for (int read; (read = input.read(buffer)) >= 0;) { + if (read > 0) digest.update(buffer, 0, read); + } + } + String actual = HexFormat.of().formatHex(digest.digest()); + if (!actual.equalsIgnoreCase(expected)) { + Files.deleteIfExists(file); + throw new IOException("Gradle distribution SHA-256 mismatch: expected " + expected + ", got " + actual); + } + } + + private static void unzip(Path archive, Path destination) throws IOException { + try (ZipInputStream zip = new ZipInputStream(Files.newInputStream(archive))) { + for (ZipEntry entry; (entry = zip.getNextEntry()) != null;) { + Path target = destination.resolve(entry.getName()).normalize(); + if (!target.startsWith(destination.normalize())) { + throw new IOException("Unsafe ZIP entry: " + entry.getName()); + } + if (entry.isDirectory()) { + Files.createDirectories(target); + } else { + Files.createDirectories(target.getParent()); + Files.copy(zip, target, StandardCopyOption.REPLACE_EXISTING); + } + zip.closeEntry(); + } + } + } + + private static String required(Properties properties, String key) { + String value = properties.getProperty(key); + if (value == null || value.isBlank()) throw new IllegalArgumentException("Missing " + key); + return value; + } + + private static int intProperty(Properties properties, String key, int fallback) { + try { + return Integer.parseInt(properties.getProperty(key, Integer.toString(fallback))); + } catch (NumberFormatException ignored) { + return fallback; + } + } +} diff --git a/android/gradle/wrapper/gradle-8.11.1-bin.zip.sha256 b/android/gradle/wrapper/gradle-8.11.1-bin.zip.sha256 new file mode 100644 index 0000000..82619d4 --- /dev/null +++ b/android/gradle/wrapper/gradle-8.11.1-bin.zip.sha256 @@ -0,0 +1 @@ +f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6 \ No newline at end of file diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..9c92fdea5930c514a2922e88bc99e5178c39763e GIT binary patch literal 6084 zcmaKwcQl+^+r|;XAVjZGBHD=FO9)PsVT93J5JrzOCeewJkfV2o(TO@lONNN3qm4Rx z7bR-cXo>ie_x;u>>pSPWp7pGCKWksteeb>hc>eh5>s%uyCm-Ky6_xwh zSN7-oYw|yDlKAJUC(KFo-yIbH>u`eEIk-drA0yr0jlM8DFE1$UKY@QfHSnJUn*SMO z`rkO%&c#E--rdf}r@;bXWi>v2mD}CQ7C{lleMb-BC?!snKnQj<^5P-xVNN4+2-8|= zRkFp2d7}J@^)1m2<03Ql6B9*MBWX?btmtHI$bQ0p}gz(!0T? zE|=H2_u6gcoUL?w4vgFl?D#8(3&W+u4MMq!#$z-t6JIf+U1V(DS$Lv<*iI3Wk{gkQ zsnOFg;>0}2&h5q3HuD_aqqeS70JfIDUWkd_MlF{)i)-6kTxm%;$4ug0m-*x|^8!&s z#b#P^yXaoP%e$qj)Xv!#TJ<(ODMK{klKnU)yP7CNVkIjFLl8e!d&k#Cm}dNz?R zsY({1i)<*(9SvDj)N!i3$ZDd14Q~iFBA<` z@EBx!YUoF63p!Dt!pC0}lrrMg-`5Y?+6XXn-0@2a$Q<00Dp1O>Hf8YSxhuu55o4>> zB+&8q?dwY(p6eC)=aB$# zj(e25C-NZ({|aO!$#lS`I_7^m+o0YQ z2X{r#hq*LY<(DdN?NPW;Nv%kP#V+Xsk4cHLc@oL8?!7Js3|h4zcAZz0D2eTN5G^!~ zSpy>p*MKqrt2ipey>jb&v@sw)CO#zYP<_5RN0$?pN0DXhPBRtt1erg?ojW;XDJ6y6 zI_mC%%fRzBZ56AyN*z=B{H!;6ZyR;ZN-p)YrftHDal9(|BGJve{4ALgt=~w9BqAL? z_4~OVQM|g@AhO$V&nRUC zoPTS0Z7YQ>^9^CV)Eiv@Rx!R()4-$_X4zZ7!WR6 zW#Kk_Ddt|B-UL8{5m_s#of3`O3T9SX}xv-SH-+1dPX>GF&~vqpvw ztZEhvvA~IyRcs_01Xr{^scp@8nb#FJ&nL%H7yx%N^)kb2Qf`jNlg-7&*QSOqy9m*l zWW!+>z@VgoZery)MR~Pr7tT|Nl2n$i!eWtGz93r36WYE}`vRYyj~8dMP5z%Fg_5s%Ia^C$H1#k*Lc@ ztQ;FT<;r8HC#W`VSvkm_Z0Y}^VZ>MA@PH-Aq|2Tt-d?3GTYKK+tb;IAN-X?*XD1>K zdr_qkQJF?ZGpYUMA$gEqt;puq)Z9d?kgt@-(bH$=Gnr7bE)puPHoaSGIcjw$nj7~L zxmhE=kLX=@7REj4`(4P0k5lk8q17(v5o2lIi>%B_BB4<=AxFl-OoufXtm7=E^-fOe zkBK`J@A$KWZ11?AShFWtaP`C{V&9yT1Q~Rt6Su_hyy!aGcciGh9>K_KwdC&>=UL3T z%p$n&Ml`gtJvTu~+!1!?tw`VSSZ#_*ryG5cVOpdo%>-(g1HaV+VEKXM2Iy$kYrky@^Y(q^_oK1)=N{%tGANa?z;$xSfas)pxQLU>?F-e;u zJxHuKDG)t1#O+%hj2bw3SuOc;9&@h!1Q6+~BS~q-?rA!&SUEl}A$Fj5c ztt$INMotN+RUjQc1}pxh;l^~0SBifCFu>@$(D*f^OhvrL<%%$S8&z`@il{lF5Eut& zj$5J3mxK#DCVckt=_FW3x|#Hz5719#(Zv*9&Z(o>01-qMV_~vps;jg$XYdkjEFb#^ zRe6MaN`E4w)&wv1J?Py4!v zHyS_QrPyMxe;#EFq**P3TrZTTXE!MPkvCGL3C>uD9Pj|?>R&O|?KZZsjFQxnB}C`> zH6pi)ixGXr#Yb|=-}5uF^;X`T1|;)`(|9O0t2a$EC0Y(|*4h9ijxUrwu4A?`Z;%Lo z?s5K7YO^?svG;MZ?~c2%i;jhyAQ2{`<`P9x>J)pKf)M_l(10>EeQ51|MNgc9$29iPTQg1QL`YBwKW^%+UoP)};M6*4~T(b!&FSBc*@UZxZ z#F{I%K|qmZR*5e@XFagI-0+rl>F5K5$ZH)4p_|9+@tr3=5tsHmX7LZxJklVd&e`+^ zohSvVP_ACA-nKjgH2pK0b2U6GJCm&c8~lm0obyJ2z`Aq+YTEsl=XyT)aE_}-GreX} zHwX2(rhffXW%9P#yV6dlcwRi)qAhb`%z&aZPXoZU1gwii1l3-1xbHHm&K+E@UI*cG zgq|~M&vTa=Uf)8fmYLiwh_ugC@|ty#v^U-=6&8F)KErpd_?4jS8-sdhLwyPoqb+Tf zhYhlEU)_4K==7?aV>&^<>HD6m^H_Xqt=s_Rx6h#!64gz~4|T$#BQF)rOT;Q_rJX4c zlIQ4sJIBD6w09lD!4{{Z53be9F7U~U-4PI=4yF21c_ExEZ&#QN(bc+W^@W7(Is9%4 zir+%qhBrVHVtiH~4+W7oX|S78QT2Ga z-;aKFP}a8A+miQ5g4DXd#J)-7E4MAI0abDC`OAUyb$rS1-Ve>UFtYL9c3XDn~} zJp<+7>Z-0~67N#2WNCkxf98|Cc#5Xvll_Qk6+(dZ?4s*4G{@3@$CvirM>U0QfIW{+>3XsT%LQ)b}Q@~+&- zt@ywP_f!{Uz470b9KRqUYZk(!jnK5GJx;i@ekO94@E`iYa4&}pomIHB*KHe*r3YKS z!w)w~fS0K)Jna5!C+3qQfG;<8+bgH>Yj#Trul9v0sdoF_uNnFgm$5^TG044%=?;mFV-$K>7v-G!;~A8Y327v> z!m+lxG=aV4$pvbV3)I4{(u`^mC@8hCEg#(Az6w!}e&mFv8Sbe*3udok@yD2s+sZ$(()go8KN zy>Kddje$W~bD6ke3;CK9@jPkfXx^77P^mM!{YvI`Nf+i_omk6t z{kgu)a#)0ZnvjqLtpxKz@!puLg)V1Z#rJDJR`bhjbMOaGL>XgN}_ zt*Ck1{9^Q+@mZ{9{guZk?fHb8(E6avnR@z zZWhE5b9A*xY2Jcxa(Ut8`MXYHeBeasd8Cr?Yv95d|2$n;IZ(PxWokgDuh*7lg}qdt z>xyCj9+N-Uh2+QVJ~XK;XAfG5U}lK=x6oy`9C=L4$Yu30IlZ>)H}3c}B2cnnERRYY ziFZm@jZ_%kvYZN`c!AV$OT`D?lqR2{vIn0MqAcBx)|Fx#04STR?b=4z3T7%bkXUF9 z-vmIJWbxZD@!32;Z}s`q(B}$yjzWFj@(t(Q=NDZ&_i!A`YK;ruNFHUz`FZE~^!OXI z=UX&LgprAgquzgFR($2gfHB%mb6RlS6$#uDUxfO$-~-^89o%UIvB%sNY#$#;hk=Sd z!yK06GU+UH!q+5^s8dnKsMbh2Ot{U7R-3(dP{G?)obH%1iZ$q@8%K;U&y=RFMo- z3J;oFLQEhg&D1(p6n!9*Nd@GfOX8f78lB-s#TeNp>j$J_4Sf!*{JeVp-mspp-aycB z`{kQuTB&@xfsHn*<9y`InV*m3qGzGvh8~A0&P?frYfBCxf7;Oyd1X6Nj!DS>t9kQ6 z2fNAb7?4#sFzAKTUPL(a zk1oB=WGFddz$W`Ct0w1JULuim0hyar#Er^ng@BvsBZgC2iw`I+S<%N5GaJk^scTb3 ze%_5R5rveakus|nBE>XWo1Q5B(|o_ERGi`F9#E-lQ5wj4ADCI9PyF-?`%nh{q)`90 z7Zdq<0=#TPIy5=F)jQ>=RU9SLg%wmfAY!o5D%X!GNWPsr;TmP|8_MhVvT^#r-Oj#! zQSFHw!HAsXJEpkEg_XllmVE`P#AFMng-7;~#|eQqN=8-WTij!Ust>vEMw!vFe(V$z z z!&5UFDQT+KJqt~-qZwOib~o=8U*$-@Dk*$d`5<$V`^pN+jX5OX@gvl9cnGx|w6H{? zr&{7gzG64of3IBgR!Fbr<#FOKmC;LYg6Wjyr?9S!?eqo5$$V)m6*@Ja5g3u-*F%d8 ze(s+vhq?mLO`Pd{-@XLPEvA}+ti5M$d!GoI`DHY{{{WZ?d4&g%mU@o%^L9Yvld-ll zeEyVrTpl7VoY8llB3(yppd+Tn$ormDH*~yK%WqdaK(*I`GK^mq+y$pv`9ATPhMF&T zyE&NmrdLxWR~MMvu2Uy+_)(;j3U{sjKDD;pzFLvcZ*`tDsJz)%(HES#(6$7g)Y*T1 z3g_EMLx1zE9qG(UhAe{&-%(p^!PtX2RoZk={MAdwSfu0iLw2AerMd1}y*ul8yIJ2P zfo8__FhY%Yswf0E_8r&CgfWI$v4(@&y;JP1WCJ#*g^^|sQQ4u-T(_DnlIo|C#WWmUkK^WJ9D;7<)~ltXl+wB-Wm87c zfD3&$Xl=H$P_d%2=jW4lXf(dNX>vV;h@v01hrH+prVE$E6gup6j_rNWd^6|P^cE!; zpEs6u>{H8h(CD5FXmT<}am3(DW}8tJ@vt_hZsiu^08C%tD`ls1Ulqm547vkalV%!a zy^a)h(>k^=Uvbbv5B(nY@my!?DaY#%$TH8>u@5Adew^>WUhZJ2mjdQo71n}EJtdxx zc;Mkz$`ObCy3v45L4ap(nUv10ZV<=No3WAu{ZWY9v|gi<&!SzFZwU_moEF{uL4E#- z=FPHCDWTZAm!zA~bIQ|JZ-GafT(=C~$Rg`?eO|Ssfh+<-sH3KfdO2K(`mJT^#&u%f ze0YNQPs;Q3u=iff&S*;YRLQt+p7m7pjS@+&tV{1HOiXq~Mr6)c*RnRPkc54y>rBi_ z$?*4C|D8e@{6f{#^&hVAlV~KWaAF@17@;*V>Tg_y$jg2cpnW)cJCU6 zY9>xPWE#H|HU|jg)RqOLpfhS6Wi5|jY4!|Z;GSj9rDG6P6qALicv7`QV)Lrlhbil) zuR;Wbj|+Q^B}=UxM5eku{GKarkb+YWi}7~^m?TbT{Y4p1zv{ZK1bRb_{ShI!U=8LK#OY%H!2Cn7 zr07aVAk3UsQ9VvU$Z2K^auJMr6ab%S(s*=waomJY-aZr>P)7cc%0l^Sb%XKEs-BrA zclGsFY6M9@dWXG^_XNw6s>#-L6Ce(cq6aGQ%_36O(YS5F^{2o43!9d6eaud(1(lf$ zMr?-?{^GC~phB_wpMW=B@E3T?#1tRO~kX-+jz5eqF` None: + (checks if condition else errors).append(message) + +app_gradle = (ROOT / "app/build.gradle.kts").read_text() +manifest = (ROOT / "app/src/main/AndroidManifest.xml").read_text() +network = (ROOT / "app/src/main/res/xml/network_security_config.xml").read_text() +source = "\n".join(p.read_text(errors="replace") for p in (ROOT / "app/src/main").rglob("*.*") if p.is_file()) +api = (ROOT / "app/src/main/java/de/drazz/boehmitools/training/data/network/Api.kt").read_text() + +require('applicationId = "de.drazz.boehmitools.training"' in app_gradle, "Application ID stimmt") +require('minSdk = 26' in app_gradle, "Minimum SDK 26") +require('compileSdk = 35' in app_gradle, "Compile SDK 35") +require('android:usesCleartextTraffic="false"' in manifest, "Release-Manifest verbietet Klartext") +require('cleartextTrafficPermitted="false"' in network, "Release Network Security verbietet Klartext") +require('https://tools.d-razz.de/' in source, "Standardserver ist gesetzt") +require('WebView' not in source, "Keine WebView") +require('api.openai.com' not in source.lower(), "Keine direkte OpenAI-URL") +require('sk-' not in source, "Kein erkennbarer OpenAI-Key") +require('Credentials.basic' in api, "Basic Auth über OkHttp") +for route in [ + 'tracker/session', '/analysis', '/analysis/status', + 'trainingsplan/api/plans/{planId}/validate', + 'trainingsplan/api/plans/{planId}/publish', + 'proposals/{proposalId}', +]: + require(route in api, f"API-Route vorhanden: {route}") + +fixtures = ROOT / "app/src/test/resources/samples" +for name in ["phase-1", "calihoss-newbie-8-wochen"]: + wrapper = json.loads((fixtures / f"plans/{name}.json").read_text()) + config = wrapper["config"] + require(config.get("schema_version") == 3, f"{name}: schema_version 3") + require(config.get("contract_version") == 2, f"{name}: contract_version 2") + groups = {"days": [], "rotations": [], "placements": [], "steps": []} + for day in config.get("days", []): + groups["days"].append(day.get("id")) + for rotation in day.get("rotations", []): + groups["rotations"].append(rotation.get("id")) + for exercise in rotation.get("exercises", []): + groups["placements"].append(exercise.get("id")) + require(bool(re.fullmatch(r"d\d+-r\d+-e\d+", exercise.get("legacy_id", ""))), f"{name}: Legacy-ID gültig") + require(bool(exercise.get("result_schema")), f"{name}: Übungsschema vorhanden") + for stage in config.get("stages", {}).values(): + for step in stage.get("steps", []): + groups["steps"].append(step.get("id")) + require(bool(step.get("result_schema")), f"{name}: Stufenschema vorhanden") + for label, ids in groups.items(): + require(all(ids), f"{name}: {label} vollständig") + require(len(ids) == len(set(ids)), f"{name}: {label} eindeutig") + +for name in ["phase-1", "calihoss-newbie-8-wochen"]: + tracker = json.loads((fixtures / f"sessions/{name}.json").read_text()) + require(1 <= int(tracker.get("version", 0)) <= 7, f"{name}: Legacy-Tracker unterstützt") + require(bool(tracker.get("sessions")), f"{name}: Sessions vorhanden") + +print(f"PASS: {len(checks)} Prüfungen") +if errors: + print(f"FAIL: {len(errors)} Prüfungen") + for error in errors: + print(f"- {error}") + raise SystemExit(1) diff --git a/deploy/local/boehmitools-training.service b/deploy/local/boehmitools-training.service new file mode 100644 index 0000000..38eb0b4 --- /dev/null +++ b/deploy/local/boehmitools-training.service @@ -0,0 +1,21 @@ +[Unit] +Description=boehmitools Training +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=michael +Group=michael +WorkingDirectory=/home/michael/src/boehmitools-core +EnvironmentFile=/home/michael/runtime/training/.env +Environment=BOEHMITOOLS_PLUGINS=/home/michael/src/boehmitools-training/plugins +Environment=BOEHMITOOLS_DATA=/home/michael/runtime/training/data +Environment=HOST=0.0.0.0 +Environment=PORT=8101 +ExecStart=/home/michael/venvs/boehmitools-training/bin/python /home/michael/src/boehmitools-core/run.py +Restart=on-failure +RestartSec=3 + +[Install] +WantedBy=multi-user.target diff --git a/deploy/local/instance.env.example b/deploy/local/instance.env.example new file mode 100644 index 0000000..4cc9dfc --- /dev/null +++ b/deploy/local/instance.env.example @@ -0,0 +1,5 @@ +# Keine echten Secrets committen. +HOST=0.0.0.0 +PORT=8101 +BOEHMITOOLS_PLUGINS=/home/michael/src/boehmitools-training/plugins +BOEHMITOOLS_DATA=/home/michael/runtime/training/data diff --git a/deploy/local/start-training.sh.example b/deploy/local/start-training.sh.example new file mode 100755 index 0000000..4d8bd7d --- /dev/null +++ b/deploy/local/start-training.sh.example @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -Eeuo pipefail +set -a +source /home/michael/runtime/training/.env +set +a +export BOEHMITOOLS_PLUGINS=/home/michael/src/boehmitools-training/plugins +export BOEHMITOOLS_DATA=/home/michael/runtime/training/data +export HOST="0.0.0.0" +export PORT="8101" +cd /home/michael/src/boehmitools-core +exec /home/michael/venvs/boehmitools-training/bin/python run.py diff --git a/docs/MANIFEST.json b/docs/MANIFEST.json new file mode 100644 index 0000000..1a219f1 --- /dev/null +++ b/docs/MANIFEST.json @@ -0,0 +1,156 @@ +{ + "release": "training-tools-2.0.0", + "components": { + "trainingsplan": "2.0.0", + "trainingstracker": "2.0.0" + }, + "contracts": { + "plan_schema": 3, + "training_contract": 2, + "tracker_schema": 7, + "result_data": 2 + }, + "analysis_storage": "one-current-file-per-week-and-overall; overwrite on regeneration", + "files": [ + { + "path": "MIGRATION.md", + "size": 2397, + "sha256": "691c9ce3c9bbc2c535896b9b1cacc51e54b691099d538fe5372c02ff696b1107" + }, + { + "path": "RELEASE-NOTES.md", + "size": 1926, + "sha256": "e9cee71dfb96d619e8f013f0b174681c4304f07aaa6ae5c7f61dd0f3304ca3db" + }, + { + "path": "data/trainingsplan/plans/calihoss-newbie-8-wochen.json", + "size": 419707, + "sha256": "8365b612ed2b0d6d49db5844c1db8b9d1e001ce1b5d09fdda25c0612374cffbd" + }, + { + "path": "data/trainingsplan/plans/phase-1.json", + "size": 575838, + "sha256": "f61d90bedb1bff15bdb667ef7c49428ddd1d8e36ad5f39acf52085fbf641c618" + }, + { + "path": "plugins/trainingsplan/TOOL-README.md", + "size": 4055, + "sha256": "1c26eb25d57eca965cb71d7ac0baf0a93a53ba94c89521b76330d4011a6b5ba3" + }, + { + "path": "plugins/trainingsplan/app.py", + "size": 21571, + "sha256": "367b30e3cf62cb816c34099c67417495fe58fd243975beb46a22e6e000031e87" + }, + { + "path": "plugins/trainingsplan/backend.py", + "size": 1180, + "sha256": "9efa3cac72842c5c8548e3dcc4cd337579890eafd11d64be462c3581fad9f689" + }, + { + "path": "plugins/trainingsplan/plan_builder.py", + "size": 46127, + "sha256": "f8a9ade995272402bb32350e2686c0374945581a6bfbfbad9adb4a553159f49a" + }, + { + "path": "plugins/trainingsplan/plugin.json", + "size": 1292, + "sha256": "cac5da0eb04e320b07904ada1786f7b9c5cd43fd7aaac8bd584918e0cbc7db9e" + }, + { + "path": "plugins/trainingsplan/presets/couchsnacks.json", + "size": 74860, + "sha256": "73e70d563f32a540b3e140888adf30e56c39babb7aff6118ab8e0690a389f93b" + }, + { + "path": "plugins/trainingsplan/presets/fruehstueck.json", + "size": 60871, + "sha256": "1bace5aa1d97346600408020cefcc23481e40adb577c939bda0da781432935b0" + }, + { + "path": "plugins/trainingsplan/presets/granola.json", + "size": 39330, + "sha256": "73983b3ffd72a998f0136fea2df9e6278b1076366adf89bd60f2d5f5c7327d17" + }, + { + "path": "plugins/trainingsplan/presets/nachmittags.json", + "size": 66551, + "sha256": "65c7e11a9728597c725ae8af128f62ee25a70095b8f78f99972a879688c528ec" + }, + { + "path": "plugins/trainingsplan/recipe_builder.py", + "size": 18387, + "sha256": "c9738a7c5e0e178cd8f91be5cbdacd13ee100eea4413d4ed4e101c33f6881e48" + }, + { + "path": "plugins/trainingsplan/schema_contract.py", + "size": 20726, + "sha256": "4b02ed3b22f014c17b4f960521ee89bd0081309587c065619f67950923814a75" + }, + { + "path": "plugins/trainingsplan/static/index.html", + "size": 69985, + "sha256": "f86b38f18eaba2bcf10683d0cf4e8c32061c5a35f8dd53ab56b46d16df7d06e8" + }, + { + "path": "plugins/trainingsplan/storage.py", + "size": 1091, + "sha256": "a5ce0c9159d4a102b465e436e715e6c820cfbfeae0489773ea2d007f553e510c" + }, + { + "path": "plugins/trainingsplan/tests/test_contract.py", + "size": 3301, + "sha256": "7d6c4f807c3ce28dcb84d0ab918aae77c8ac9419a84ea72c132f755fa1cb1061" + }, + { + "path": "plugins/trainingstracker/README.md", + "size": 4240, + "sha256": "a237661fb657cabdc32592bcc65d948b3cacd837bd8dc9aef74af1e8619733b1" + }, + { + "path": "plugins/trainingstracker/analysis_store.py", + "size": 832, + "sha256": "b13402ddf8fd4b8d049ffa0200c02b7f32ef329aa9c93c9fd2156ef469e84293" + }, + { + "path": "plugins/trainingstracker/app.py", + "size": 143226, + "sha256": "f7f87f43ca7d13c09b98b7e5364d350ad6e11d6a9da3ecca4b78dbfcae0de761" + }, + { + "path": "plugins/trainingstracker/backend.py", + "size": 1453, + "sha256": "57490ca4d1283ad6084817e50e1331e2a2be80138f172b050905d1f2b8996c63" + }, + { + "path": "plugins/trainingstracker/contract.py", + "size": 269, + "sha256": "5b73a9698f41300b5a21390bab1393bdca431676aead6d986520da492c97bac3" + }, + { + "path": "plugins/trainingstracker/plugin.json", + "size": 1531, + "sha256": "8e3f10ac7a8325e925ad222561cd6d1d537c21059bebd517aa3afaea4937a604" + }, + { + "path": "plugins/trainingstracker/session_store.py", + "size": 1186, + "sha256": "4af7c49cdaaf3acea311e1f54a3f922b8503111550a2dc0c1c7a3089b6463b1f" + }, + { + "path": "plugins/trainingstracker/static/index.html", + "size": 116266, + "sha256": "3a37bf462667350f2104b362c90d4dde3eca92e2ecfe4a71f58f0fb39f213e40" + }, + { + "path": "plugins/trainingstracker/tests/test_app.py", + "size": 21391, + "sha256": "d101b1a64ba7f954148d2da82a15a85843bd77f9b2144381c1b74c2920683e4c" + }, + { + "path": "plugins/trainingstracker/tests/test_stability.py", + "size": 2012, + "sha256": "30f8af88f8f43b0294b860b8e35c4325aadfb477934a115b12eba1c84d0c58e4" + } + ] +} diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md new file mode 100644 index 0000000..8d43a1d --- /dev/null +++ b/docs/MIGRATION.md @@ -0,0 +1,63 @@ +# Migration auf Trainingsplan / Trainingstracker 2.0 + +## Enthaltene Komponenten + +```text +plugins/trainingsplan/ +plugins/trainingstracker/ +data/trainingsplan/plans/phase-1.json +data/trainingsplan/plans/calihoss-newbie-8-wochen.json +``` + +Es werden keine anderen boehmitools-Komponenten verändert. + +## Installation + +1. boehmitools beziehungsweise den betroffenen Container stoppen. +2. Die beiden bestehenden Plugin-Ordner sichern. +3. `plugins/trainingsplan/` und `plugins/trainingstracker/` durch die Ordner aus dem Paket ersetzen. +4. Die beiden mitgelieferten Plan-JSONs nach `data/trainingsplan/plans/` kopieren. +5. boehmitools wieder starten. + +## Bestehende Sessiondateien + +Die Dateien unter `data/trainingstracker/sessions/` müssen nicht manuell geändert werden. + +- Alte Positionsschlüssel wie `d1-r0-e0` bleiben lesbar. +- Die neuen Pläne speichern den ursprünglichen Positionsschlüssel dauerhaft als `legacy_id`. +- Beim Öffnen und nächsten Speichern einer Session wird der Eintrag auf die stabile Übungs-ID migriert. +- Bereits dokumentierte Sessiondaten erhalten beim nächsten Speichern die aktuelle `plan_id` und `plan_revision`, ohne Ergebnisse oder Notizen zu verwerfen. + +Vorhandene Sessiondateien sollten trotzdem einmal separat gesichert werden. + +## Bestehende Analysen + +Der Tracker führt künftig genau eine aktuelle Analyse je Woche und eine Gesamtanalyse: + +```text +analyses//week-01.json +analyses//week-02.json +analyses//overall.json +``` + +Beim ersten Lesen eines alten Analyseordners wird die im bisherigen `index.json` als aktuell markierte Analyse übernommen. Ältere Zeitstempeldateien und das alte History-Feld werden entfernt. Eine neue Analyse überschreibt anschließend immer dieselbe Datei. + +## Planrevisionen + +Jede Plandatei enthält jetzt: + +- `config`: die veröffentlichte, vom Tracker gelesene Fassung +- `draft`: den bearbeitbaren Entwurf +- `revision`: Bearbeitungsrevision für Konfliktschutz +- `published_revision`: veröffentlichte Planrevision + +Das Speichern eines Entwurfs verändert laufende Sessions nicht. Erst **Entwurf veröffentlichen** stellt die neue Fassung dem Tracker bereit. + +## Bewusste Nicht-Funktionen + +Nicht eingebaut wurden: + +- strukturierte Technik-, RIR- oder RPE-Werte +- unterschiedliches Gewicht pro Satz +- Trainings- oder Tabata-Timer +- mehrere Versionen oder Historien von KI-Analysen diff --git a/docs/RELEASE-NOTES.md b/docs/RELEASE-NOTES.md new file mode 100644 index 0000000..e7e718f --- /dev/null +++ b/docs/RELEASE-NOTES.md @@ -0,0 +1,41 @@ +# Trainingstools 2.0 + +## Datenmodell und Stabilität + +- stabile IDs für Plan, Phasen, Tage, Rotationen, Übungsinstanzen, Bewegungen, Progressionen und Progressionsstufen +- dauerhaft gespeicherte `legacy_id` für die sichere Migration alter Positionsschlüssel +- Progressionsstufen als Objekte mit Name, Schema, Cluster, Faktor und Phasenbezug +- gemeinsamer, versionierter Datenvertrag zwischen Planeditor und Tracker +- veröffentlichte Planfassung und bearbeitbarer Entwurf getrennt +- Planrevision pro Session gespeichert +- atomische Plan- und Tracker-Schreibvorgänge +- Konfliktschutz über Revisionsnummern und Session-Patches + +## Planeditor + +- Validierung vor Veröffentlichung +- stabil-ID-basierter Änderungsbericht +- Tracker-Vorschau für Ergebnisfelder und feste Intervalle +- automatische, anschließend editierbare Migration fehlender Ergebnisschemata +- planbezogene Übungsbibliothek mit Bewegungsclustern und Progressionsfaktoren +- KI-Vorschläge mit Ziel-IDs, direkter Navigation zum Ziel und manueller Bestätigung + +## Tracker + +- Übungen können erledigt, teilweise erledigt oder mit Grund übersprungen werden +- Woche kann explizit geöffnet oder abgeschlossen werden +- Sessiondaten werden nur als Patch gespeichert +- historische Sessions bleiben ihrer Planrevision zugeordnet +- robuste Analysejobs mit Dateisperre, Heartbeat und Ablauf-Lease +- Analysehash berücksichtigt Planrevision, Schema-, Prompt-, Modell- und Normalisierungsversion +- genau eine aktuelle Analyse je Woche und eine Gesamtanalyse +- Neuberechnung überschreibt die vorhandene Analyse +- alte Analysehistorien werden automatisch auf das Ein-Datei-Modell reduziert +- strukturierte Planvorschläge werden an den Planeditor übergeben + +## Nicht enthalten + +- keine strukturierten Technik-, RIR- oder RPE-Werte +- kein unterschiedliches Gewicht pro Satz +- kein Trainings- oder Intervalltimer +- keine Analysehistorie und keine Analyseversionierung diff --git a/plugins/trainingsplan/TOOL-README.md b/plugins/trainingsplan/TOOL-README.md new file mode 100644 index 0000000..94f504a --- /dev/null +++ b/plugins/trainingsplan/TOOL-README.md @@ -0,0 +1,107 @@ +# Trainingsplan- & Rezept-Editor 2.0 + +Der Editor verwaltet Trainingspläne und Rezeptsammlungen innerhalb von boehmitools. Trainingspläne sind zugleich die verbindliche Datenquelle für den `trainingstracker`. + +## Planlebenszyklus + +Jede Plandatei enthält eine veröffentlichte Fassung und einen bearbeitbaren Entwurf: + +```json +{ + "plan_id": "phase-1", + "revision": 4, + "published_revision": 2, + "config": {}, + "draft": {} +} +``` + +- **Entwurf speichern** erhöht die Bearbeitungsrevision, verändert aber nicht den vom Tracker gelesenen Plan. +- **Plan prüfen** validiert IDs, Progressionsbezüge, Ergebnisschemata und feste Intervalle. +- **Entwurf veröffentlichen** erzeugt eine neue veröffentlichte Planrevision. +- Bereits dokumentierte Sessions speichern die Planrevision, nach der sie ausgeführt wurden. +- Vor dem Ersetzen einer Plandatei wird atomisch geschrieben und ein Backup unter `data/trainingsplan/backups/` angelegt. +- Gleichzeitige Bearbeitungen werden über eine Revisionsprüfung erkannt und nicht still überschrieben. + +## Stabiler Datenvertrag + +Trainingspläne verwenden: + +```text +schema_version: 3 +contract_version: 2 +``` + +Phasen, Tage, Rotationen, Übungsplatzierungen, Bewegungen, Progressionen und Progressionsstufen besitzen dauerhafte IDs. Beim Verschieben im Editor bleiben diese IDs erhalten. + +Progressionsstufen sind Objekte und keine parallelen Text-/Schema-Arrays mehr: + +```json +{ + "id": "step-squat-hold-...", + "name": "Deep Squat Hold", + "phase_id": "phase-start-...", + "movement_cluster": "knee_dominant", + "factor": 1.0, + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } +} +``` + +Dadurch reisen Stufenname, Eingabeformat und Analysefaktor immer gemeinsam. + +## Ergebniserfassung + +Eine Übung besitzt ein Standardformat. Jede Progressionsstufe kann es überschreiben. + +Priorität im Tracker: + +```text +manuelle Anpassung der aktuellen Session +→ Schema der gewählten Progressionsstufe +→ Standard der Übung +→ automatische Erkennung als Fallback +``` + +Unterstützt werden Wiederholungen, Sekunden, Minuten, keine Zahl, optionales oder verpflichtendes Gewicht sowie gemeinsame oder getrennte Seitenwerte. + +## Tabata und feste Intervalle + +Das Trainingsformat wird ausdrücklich gespeichert: + +```json +{ + "mode": "tabata", + "fixed_interval": true, + "work_seconds": 20, + "rest_seconds": 10, + "rounds": 8, + "rounds_scope": "block" +} +``` + +`rounds` bezeichnet Intervalle pro Block. Bei acht Intervallen und vier Übungen entstehen zwei Durchgänge pro Übung. Eine zusätzlich im Rotationstitel angegebene Satzanzahl wird damit multipliziert. Ist die Intervallzahl nicht gleichmäßig durch die Übungszahl teilbar, meldet die Validierung einen Hinweis und verlangt eine feste Wertezahl im Ergebnisschema. + +## Übungsbibliothek und Analysefaktoren + +Aus den Übungen und Progressionen wird eine planbezogene `exercise_catalog` erzeugt. Sie enthält stabile Bewegungs-IDs, Cluster, Varianten, Ergebnisschemata und Faktoren. Diese Angaben haben im Tracker Vorrang vor Namens- und Regex-Erkennung. + +## Prüfung und Tracker-Vorschau + +Der Tab **Prüfung & Vorschläge** zeigt: + +- Validierungsfehler und Hinweise, +- das vom Tracker erwartete Ergebnisformat pro Übung, +- stufenabhängige Überschreibungen, +- die berechneten Durchgänge fester Intervallblöcke, +- strukturierte KI-Vorschläge aus dem Tracker mit stabilen Ziel-IDs. + +Vorschläge werden niemals automatisch in den Plan geschrieben. Über **Ziel im Entwurf öffnen** springt der Editor anhand der stabilen IDs direkt zur betroffenen Übung oder Progression. Nach der manuellen Änderung kann der Vorschlag als übernommen oder abgelehnt markiert werden. + +## Kompatibilität + +Ältere Pläne werden beim Laden in das neue Schema überführt. Fehlende Ergebnisschemata erhalten dabei einmalig explizite, im Editor korrigierbare Migrationswerte. Die veröffentlichten Beispielpläne liegen bereits in Version 3 vor. Rezeptsammlungen bleiben vom Trainingsvertrag unberührt. diff --git a/plugins/trainingsplan/app.py b/plugins/trainingsplan/app.py new file mode 100644 index 0000000..f5d7afc --- /dev/null +++ b/plugins/trainingsplan/app.py @@ -0,0 +1,450 @@ +# -*- coding: utf-8 -*- +"""Trainingsplan- und Rezepteditor mit Entwurf/Veröffentlichung und Konfliktschutz.""" +from __future__ import annotations + +import io +import json +import os +import re +from copy import deepcopy +from pathlib import Path +from typing import Any + +from flask import Flask, Response, jsonify, request, send_file + +import plan_builder +import recipe_builder +from schema_contract import migrate_training_config, validate_training_config, stable_id +from storage import atomic_json_write, utc_now + +BASE_DIR = Path(__file__).resolve().parent +DATA_DIR = Path(os.environ.get("DATA_DIR", BASE_DIR / "data")).resolve() +PLANS_DIR = DATA_DIR / "plans" +PRESETS_DIR = BASE_DIR / "presets" +ACTIVE_FILE = DATA_DIR / "active.txt" +LEGACY_CONFIG = DATA_DIR / "config.json" +STATIC_DIR = BASE_DIR / "static" +BACKUPS_DIR = DATA_DIR / "backups" +TRACKER_PROPOSALS_DIR = DATA_DIR.parent / "trainingstracker" / "proposals" +TRACKER_SESSIONS_DIR = DATA_DIR.parent / "trainingstracker" / "sessions" +PLANS_DIR.mkdir(parents=True, exist_ok=True) +BACKUPS_DIR.mkdir(parents=True, exist_ok=True) +app = Flask(__name__, static_folder=None) + + +def builder_for(cfg: dict[str, Any]): + return recipe_builder if (cfg or {}).get("type") == "recipe" else plan_builder + + +def default_for(kind: str): + cfg = recipe_builder.default_config() if kind == "recipe" else plan_builder.default_config() + return cfg if kind == "recipe" else migrate_training_config(cfg) + + +def slugify(name: Any) -> str: + s = str(name or "").strip().lower().replace("ä", "ae").replace("ö", "oe").replace("ü", "ue").replace("ß", "ss") + return re.sub(r"[^a-z0-9]+", "-", s).strip("-") or "phase" + + +def plan_path(pid: str) -> Path: + return PLANS_DIR / f"{pid}.json" + + +def unique_id(base: str) -> str: + stem, index = base, 2 + while plan_path(stem).exists(): + stem = f"{base}-{index}"; index += 1 + return stem + + +def _normalize_wrapper(pid: str, raw: Any) -> dict[str, Any]: + if not isinstance(raw, dict): + raw = {} + name = str(raw.get("name") or pid) + published = raw.get("config") if isinstance(raw.get("config"), dict) else {} + is_recipe = published.get("type") == "recipe" + if not is_recipe: + published = migrate_training_config(published, plan_id=str(raw.get("plan_id") or pid)) + draft = raw.get("draft") if isinstance(raw.get("draft"), dict) else deepcopy(published) + if not is_recipe: + draft = migrate_training_config(draft, plan_id=published.get("plan_id") or pid) + revision = max(1, int(raw.get("revision") or 1)) + published_revision = max(1, int(raw.get("published_revision") or 1)) + return { + "id": pid, + "plan_id": str(raw.get("plan_id") or published.get("plan_id") or stable_id("plan", pid, name)), + "name": name, + "revision": revision, + "published_revision": published_revision, + "created_at": str(raw.get("created_at") or utc_now()), + "updated_at": str(raw.get("updated_at") or utc_now()), + "published_at": str(raw.get("published_at") or raw.get("updated_at") or utc_now()), + "config": published, + "draft": draft, + } + + +def read_plan(pid: str) -> dict[str, Any]: + with plan_path(pid).open("r", encoding="utf-8") as handle: + return _normalize_wrapper(pid, json.load(handle)) + + +def write_plan(pid: str, wrapper: dict[str, Any], *, backup: bool = True) -> dict[str, Any]: + normalized = _normalize_wrapper(pid, wrapper) + normalized["updated_at"] = utc_now() + atomic_json_write(plan_path(pid), {k: v for k, v in normalized.items() if k != "id"}, BACKUPS_DIR if backup else None) + return normalized + + +def create_wrapper(pid: str, name: str, cfg: dict[str, Any]) -> dict[str, Any]: + if cfg.get("type") != "recipe": + cfg = migrate_training_config(cfg, plan_id=pid) + now = utc_now() + return { + "id": pid, "plan_id": cfg.get("plan_id") or pid, "name": name, + "revision": 1, "published_revision": 1, + "created_at": now, "updated_at": now, "published_at": now, + "config": deepcopy(cfg), "draft": deepcopy(cfg), + } + + +def list_plan_ids() -> list[str]: + return sorted(path.stem for path in PLANS_DIR.glob("*.json")) + + +def plan_name(pid: str) -> str: + try: return read_plan(pid)["name"] + except Exception: return pid + + +def get_active() -> str | None: + pid = ACTIVE_FILE.read_text(encoding="utf-8").strip() if ACTIVE_FILE.exists() else None + ids = list_plan_ids() + return pid if pid in ids else (ids[0] if ids else None) + + +def set_active(pid: str) -> None: + ACTIVE_FILE.parent.mkdir(parents=True, exist_ok=True) + ACTIVE_FILE.write_text(pid, encoding="utf-8") + + +def install_presets() -> None: + if not PRESETS_DIR.is_dir(): return + for source in sorted(PRESETS_DIR.glob("*.json")): + if plan_path(source.stem).exists(): continue + try: + raw = json.loads(source.read_text(encoding="utf-8")) + cfg = raw.get("config", {}) if isinstance(raw, dict) else {} + wrapper = create_wrapper(source.stem, str(raw.get("name") or source.stem), cfg) + write_plan(source.stem, wrapper, backup=False) + except Exception: pass + + +def ensure_seed() -> None: + if not list_plan_ids(): + try: cfg = json.loads(LEGACY_CONFIG.read_text(encoding="utf-8")) if LEGACY_CONFIG.exists() else default_for("training") + except Exception: cfg = default_for("training") + write_plan("phase-1", create_wrapper("phase-1", "Phase 1", cfg), backup=False) + install_presets() + if get_active() is None and list_plan_ids(): set_active(list_plan_ids()[0]) + + +def _tracked_session_count(pid: str) -> int: + path = TRACKER_SESSIONS_DIR / f"{pid}.json" + if not path.exists(): + return 0 + try: + raw = json.loads(path.read_text(encoding="utf-8")) + except Exception: + return 0 + sessions = raw.get("sessions") if isinstance(raw, dict) and isinstance(raw.get("sessions"), dict) else {} + count = 0 + for session in sessions.values(): + if not isinstance(session, dict): + continue + if str(session.get("status") or "planned") != "planned" or any( + isinstance(item, dict) and ( + item.get("done") or item.get("completion_status") in {"completed", "partial", "skipped"} + or any(str(item.get(field) or "").strip() for field in ("result", "note", "progression", "skip_reason")) + ) for item in (session.get("items") or {}).values() + ): + count += 1 + return count + + +def public_wrapper(wrapper: dict[str, Any], *, draft: bool = True) -> dict[str, Any]: + return { + "id": wrapper["id"], "plan_id": wrapper["plan_id"], "name": wrapper["name"], + "revision": wrapper["revision"], "published_revision": wrapper["published_revision"], + "updated_at": wrapper["updated_at"], "published_at": wrapper["published_at"], + "has_unpublished_changes": wrapper["draft"] != wrapper["config"], + "tracked_sessions": _tracked_session_count(wrapper["id"]), + "config": deepcopy(wrapper["draft"] if draft else wrapper["config"]), + } + + +def _validation(config: dict[str, Any]) -> dict[str, Any]: + if config.get("type") == "recipe": return {"errors": [], "warnings": [], "config": config} + return validate_training_config(config) + + +def _diff_summary(old: Any, new: Any) -> dict[str, Any]: + """Erzeugt einen stabil-ID-basierten Änderungsbericht für die Veröffentlichung.""" + old_cfg = old if isinstance(old, dict) else {} + new_cfg = new if isinstance(new, dict) else {} + + def collect(cfg: dict[str, Any]) -> dict[str, dict[str, dict[str, Any]]]: + result: dict[str, dict[str, dict[str, Any]]] = { + "days": {}, "rotations": {}, "exercises": {}, "progression_steps": {}, + } + for di, day in enumerate(cfg.get("days", []) if isinstance(cfg.get("days"), list) else []): + if not isinstance(day, dict): + continue + day_id = str(day.get("id") or f"day-index-{di}") + result["days"][day_id] = { + "label": str(day.get("focus") or f"Tag {di + 1}"), + "value": {"num": day.get("num"), "focus": day.get("focus"), "light": bool(day.get("light"))}, + } + for ri, rotation in enumerate(day.get("rotations", []) if isinstance(day.get("rotations"), list) else []): + if not isinstance(rotation, dict): + continue + rotation_id = str(rotation.get("id") or f"{day_id}:rotation:{ri}") + result["rotations"][rotation_id] = { + "label": f"{day.get('focus') or 'Tag'} · {rotation.get('label') or f'Block {ri + 1}'}", + "value": {"day_id": day_id, "label": rotation.get("label")}, + } + for ei, exercise in enumerate(rotation.get("exercises", []) if isinstance(rotation.get("exercises"), list) else []): + if not isinstance(exercise, dict): + continue + exercise_id = str(exercise.get("id") or f"{rotation_id}:exercise:{ei}") + result["exercises"][exercise_id] = { + "label": str(exercise.get("name") or f"Übung {ei + 1}"), + "value": { + "rotation_id": rotation_id, + "name": exercise.get("name"), "cue": exercise.get("cue"), + "progression_id": exercise.get("progression_id") or exercise.get("key"), + "exercise_id": exercise.get("exercise_id"), + "movement_cluster": exercise.get("movement_cluster"), + "result_schema": exercise.get("result_schema"), + }, + } + stages = cfg.get("stages") if isinstance(cfg.get("stages"), dict) else {} + for key, stage in stages.items(): + if not isinstance(stage, dict): + continue + for si, step in enumerate(stage.get("steps", []) if isinstance(stage.get("steps"), list) else []): + if not isinstance(step, dict): + continue + step_id = str(step.get("id") or f"{key}:step:{si}") + result["progression_steps"][step_id] = { + "label": f"{stage.get('name') or key} · {step.get('name') or f'Stufe {si + 1}'}", + "value": { + "progression_id": key, "name": step.get("name"), + "phase_id": step.get("phase_id"), "factor": step.get("factor"), + "movement_cluster": step.get("movement_cluster"), + "result_schema": step.get("result_schema"), + }, + } + return result + + before, after = collect(old_cfg), collect(new_cfg) + summary: dict[str, Any] = {} + changes: list[dict[str, Any]] = [] + for section in ("days", "rotations", "exercises", "progression_steps"): + left, right = before[section], after[section] + added = sorted(set(right) - set(left)) + removed = sorted(set(left) - set(right)) + changed = sorted(key for key in set(left) & set(right) if left[key]["value"] != right[key]["value"]) + summary[section] = { + "before": len(left), "after": len(right), "delta": len(right) - len(left), + "added": len(added), "removed": len(removed), "changed": len(changed), + } + for key in added: + changes.append({"section": section, "kind": "added", "id": key, "label": right[key]["label"]}) + for key in removed: + changes.append({"section": section, "kind": "removed", "id": key, "label": left[key]["label"]}) + for key in changed: + changes.append({"section": section, "kind": "changed", "id": key, "label": right[key]["label"]}) + summary["changes"] = changes[:50] + summary["total_changes"] = len(changes) + summary["training_format_changed"] = old_cfg.get("training_format") != new_cfg.get("training_format") + summary["plan_meta_changed"] = old_cfg.get("meta") != new_cfg.get("meta") + return summary + + +def _proposal_file(pid: str) -> Path: + return TRACKER_PROPOSALS_DIR / f"{pid}.json" + + +ensure_seed() + +@app.route("/") +def index(): + return Response((STATIC_DIR / "index.html").read_text(encoding="utf-8"), mimetype="text/html") + +@app.route("/api/plans", methods=["GET"]) +def api_plans(): + active = get_active(); plans = [] + for pid in list_plan_ids(): + try: + p = read_plan(pid) + plans.append({"id": pid, "name": p["name"], "active": pid == active, "revision": p["revision"], "published_revision": p["published_revision"], "has_unpublished_changes": p["draft"] != p["config"]}) + except Exception: continue + return jsonify({"plans": plans, "active": active}) + +@app.route("/api/plans", methods=["POST"]) +def api_plan_create(): + body = request.get_json(force=True) or {}; name = str(body.get("name") or "Neue Phase").strip() + src = body.get("from"); kind = body.get("kind") or body.get("type") or "training" + cfg = deepcopy(read_plan(src)["draft"]) if src and plan_path(str(src)).exists() else default_for(str(kind)) + pid = unique_id(slugify(name)); wrapper = create_wrapper(pid, name, cfg); write_plan(pid, wrapper, backup=False); set_active(pid) + return jsonify(public_wrapper(wrapper)) + +@app.route("/api/plans/", methods=["GET"]) +def api_plan_get(pid: str): + if not plan_path(pid).exists(): return jsonify({"error": "not found"}), 404 + return jsonify(public_wrapper(read_plan(pid))) + +@app.route("/api/plans//select", methods=["POST"]) +def api_plan_select(pid: str): + if not plan_path(pid).exists(): return jsonify({"error": "not found"}), 404 + set_active(pid); return jsonify(public_wrapper(read_plan(pid))) + +@app.route("/api/plans/", methods=["POST"]) +def api_plan_save(pid: str): + if not plan_path(pid).exists(): return jsonify({"error": "not found"}), 404 + body = request.get_json(force=True) or {} + config = body.get("config") if isinstance(body.get("config"), dict) else body + expected = body.get("expected_revision") if isinstance(body, dict) else None + wrapper = read_plan(pid) + if expected is not None and int(expected) != wrapper["revision"]: + return jsonify({"error": "Der Plan wurde zwischenzeitlich geändert.", "code": "revision_conflict", "current_revision": wrapper["revision"]}), 409 + validation = _validation(config) + wrapper["draft"] = validation["config"] + wrapper["revision"] += 1 + saved = write_plan(pid, wrapper) + return jsonify({"ok": True, **public_wrapper(saved), "validation": {"errors": validation["errors"], "warnings": validation["warnings"]}}) + +@app.route("/api/plans//validate", methods=["POST"]) +def api_plan_validate(pid: str): + if not plan_path(pid).exists(): return jsonify({"error": "not found"}), 404 + body = request.get_json(force=True) or {} + cfg = body.get("config") if isinstance(body.get("config"), dict) else body + result = _validation(cfg) + wrapper = read_plan(pid) + diff = _diff_summary(wrapper["config"], result["config"]) + diff["tracked_sessions"] = _tracked_session_count(pid) + return jsonify({"errors": result["errors"], "warnings": result["warnings"], "config": result["config"], "diff": diff}) + +@app.route("/api/plans//publish", methods=["POST"]) +def api_plan_publish(pid: str): + if not plan_path(pid).exists(): return jsonify({"error": "not found"}), 404 + body = request.get_json(silent=True) or {}; wrapper = read_plan(pid) + expected = body.get("expected_revision") + if expected is not None and int(expected) != wrapper["revision"]: + return jsonify({"error": "Der Entwurf wurde zwischenzeitlich geändert.", "code": "revision_conflict", "current_revision": wrapper["revision"]}), 409 + validation = _validation(wrapper["draft"]) + if validation["errors"]: + return jsonify({"error": "Der Plan enthält Validierungsfehler.", "validation": {"errors": validation["errors"], "warnings": validation["warnings"]}}), 400 + diff = _diff_summary(wrapper["config"], validation["config"]) + diff["tracked_sessions"] = _tracked_session_count(pid) + wrapper["config"] = deepcopy(validation["config"]) + wrapper["draft"] = deepcopy(validation["config"]) + wrapper["published_revision"] += 1 + wrapper["revision"] += 1 + wrapper["published_at"] = utc_now() + saved = write_plan(pid, wrapper) + return jsonify({"ok": True, **public_wrapper(saved), "diff": diff, "validation": {"errors": [], "warnings": validation["warnings"]}}) + +@app.route("/api/plans//rename", methods=["POST"]) +def api_plan_rename(pid: str): + if not plan_path(pid).exists(): return jsonify({"error": "not found"}), 404 + name = str((request.get_json(force=True) or {}).get("name") or "").strip() + if not name: return jsonify({"error": "name required"}), 400 + wrapper = read_plan(pid); wrapper["name"] = name; wrapper["revision"] += 1; saved = write_plan(pid, wrapper) + return jsonify({"id": pid, "name": name, "revision": saved["revision"]}) + +@app.route("/api/plans/", methods=["DELETE"]) +def api_plan_delete(pid: str): + ids = list_plan_ids() + if pid not in ids: return jsonify({"error": "not found"}), 404 + if len(ids) <= 1: return jsonify({"error": "letzte Phase kann nicht gelöscht werden"}), 400 + atomic_json_write(BACKUPS_DIR / f"deleted-{pid}-{utc_now().replace(':','')}.json", {k:v for k,v in read_plan(pid).items() if k != 'id'}) + plan_path(pid).unlink() + if get_active() is None: set_active(list_plan_ids()[0]) + return jsonify({"ok": True, "active": get_active()}) + +@app.route("/api/plans//export") +def api_plan_export(pid: str): + if not plan_path(pid).exists(): return jsonify({"error": "not found"}), 404 + return send_file(io.BytesIO(plan_path(pid).read_bytes()), mimetype="application/json", as_attachment=True, download_name=f"{pid}.json") + +@app.route("/api/plans/import", methods=["POST"]) +def api_plan_import(): + raw = request.files["file"].read() if "file" in request.files else request.data + if not raw: return jsonify({"error": "keine Datei"}), 400 + try: obj = json.loads(raw.decode("utf-8")) + except Exception as exc: return jsonify({"error": f"ungültiges JSON: {exc}"}), 400 + if isinstance(obj, dict) and "config" in obj: + cfg = obj.get("config", {}); name = str(obj.get("name") or "Importierte Phase") + else: + cfg = obj if isinstance(obj, dict) else {}; name = str((cfg.get("meta") or {}).get("title") or "Importierte Phase") + pid = unique_id(slugify(name)); wrapper = create_wrapper(pid, name, cfg); write_plan(pid, wrapper, backup=False); set_active(pid) + return jsonify(public_wrapper(wrapper)) + +@app.route("/api/plans//proposals") +def api_plan_proposals(pid: str): + raw = {} + path = _proposal_file(pid) + if path.exists(): + try: raw = json.loads(path.read_text(encoding="utf-8")) + except Exception: raw = {} + return jsonify(raw if isinstance(raw, dict) else {}) + +@app.route("/api/plans//proposals/", methods=["POST"]) +def api_plan_proposal_status(pid: str, proposal_id: str): + path = _proposal_file(pid); body = request.get_json(silent=True) or {}; status = str(body.get("status") or "reviewed") + if status not in {"open", "accepted", "rejected", "reviewed"}: + return jsonify({"error": "ungültiger Vorschlagsstatus"}), 400 + raw = {} + if path.exists(): + try: raw = json.loads(path.read_text(encoding="utf-8")) + except Exception: raw = {} + proposals = raw.get("proposals") if isinstance(raw.get("proposals"), list) else [] + for proposal in proposals: + if str(proposal.get("id")) == proposal_id: + proposal["status"] = status; proposal["reviewed_at"] = utc_now() + raw["proposals"] = proposals; raw["updated_at"] = utc_now(); atomic_json_write(path, raw) + return jsonify({"ok": True, "proposals": proposals}) + +# Backward-compatible active plan endpoints +@app.route("/api/config", methods=["GET"]) +def api_get_config(): + pid = get_active(); return jsonify(public_wrapper(read_plan(pid)) if pid else {"config": default_for("training")}) + +@app.route("/api/config", methods=["POST"]) +def api_save_config(): + pid = get_active() + if not pid: return jsonify({"error": "kein aktiver Plan"}), 400 + return api_plan_save(pid) + +@app.route("/api/default") +def api_default(): return jsonify(default_for(request.args.get("kind", "training"))) + +@app.route("/api/reset", methods=["POST"]) +def api_reset(): + pid = get_active(); wrapper = read_plan(pid); kind = "recipe" if wrapper["draft"].get("type") == "recipe" else "training" + wrapper["draft"] = default_for(kind); wrapper["revision"] += 1; saved = write_plan(pid, wrapper) + return jsonify(public_wrapper(saved)) + +@app.route("/api/pdf", methods=["POST"]) +def api_pdf(): + body = request.get_json(force=True) or {}; cfg = body.get("config") if isinstance(body.get("config"), dict) else body + if cfg.get("type") != "recipe": cfg = migrate_training_config(cfg) + try: pdf_bytes = builder_for(cfg).build_pdf(cfg) + except Exception as exc: return jsonify({"error": str(exc)}), 400 + name = cfg.get("meta", {}).get("output_name", "Trainingsplan.pdf") + return send_file(io.BytesIO(pdf_bytes), mimetype="application/pdf", as_attachment=True, download_name=name) + +if __name__ == "__main__": + app.run(host=os.environ.get("HOST", "0.0.0.0"), port=int(os.environ.get("PORT", "8080")), debug=False) diff --git a/plugins/trainingsplan/backend.py b/plugins/trainingsplan/backend.py new file mode 100644 index 0000000..6ad78c3 --- /dev/null +++ b/plugins/trainingsplan/backend.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +""" +Plugin-Adapter für den Trainingsplan- und Rezept-Editor. + +Das Original ist eine Flask-Anwendung (app.py) und bleibt vollständig +unverändert. Hier wird sie lediglich + + * auf ihren eigenen Datenordner data/trainingsplan/ gezeigt und + * als WSGI-App in die ASGI-Suite eingehängt. + +Das Tool läuft dadurch weiterhin auch allein: + cd plugins/trainingsplan && python app.py +""" +from __future__ import annotations + +import os + +from a2wsgi import WSGIMiddleware + +from core.loader import load_module + + +def create_app(ctx): + # app.py liest DATA_DIR beim Import und legt dort die Phasen an. + previous = os.environ.get("DATA_DIR") + os.environ["DATA_DIR"] = str(ctx.data_dir) + try: + module = load_module(ctx.path("app.py"), f"btp_{ctx.id}_app") + finally: + if previous is None: + os.environ.pop("DATA_DIR", None) + else: + os.environ["DATA_DIR"] = previous + + # Flask erzeugt seine URLs anhand von SCRIPT_NAME; a2wsgi übernimmt dafür + # den root_path des Mounts. Damit funktionieren alle Routen unter + # /plugins/trainingsplan genauso wie vorher unter /. + return WSGIMiddleware(module.app) diff --git a/plugins/trainingsplan/plan_builder.py b/plugins/trainingsplan/plan_builder.py new file mode 100644 index 0000000..fe90b44 --- /dev/null +++ b/plugins/trainingsplan/plan_builder.py @@ -0,0 +1,669 @@ +# -*- coding: utf-8 -*- +""" +plan_builder.py +Rendert den 8-Wochen-Calisthenics-Plan als A4-PDF. +Layout 1:1 wie im Original-Skript, aber alle Inhalte kommen aus einem Config-Dict. +build_pdf(config) -> bytes +default_config() -> dict (Standard-Inhalte) +""" +import copy +from schema_contract import migrate_training_config +from io import BytesIO + +from reportlab.lib.pagesizes import A4 +from reportlab.lib.units import cm +from reportlab.lib import colors +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, + PageBreak, HRFlowable, KeepTogether) +from reportlab.lib.enums import TA_RIGHT + +# ========================================================================= +# STANDARD-KONFIGURATION (entspricht exakt dem Original-Skript) +# ========================================================================= +DEFAULTS = { + "meta": { + "title": "Calisthenics-Basis – 8-Wochen Komplettplan", + "subtitle": "Blutiger Anfänger · 1,90 m · 95 kg · Rucksack + Couch + Kettlebell · tägliche ~30-Min Tabata-Session", + "output_name": "Calisthenics-8-Wochen-Komplett.pdf", + "footer_left": "Calisthenics 8-Wochen Komplettplan \u00b7 mit Kettlebell", + "doc_title": "Calisthenics 8-Wochen Komplettplan (mit Kettlebell)", + "weeks": 8, + }, + "colors": { + "INK": "#1A1A1A", "ACCENT": "#C2410C", "ACC2": "#0F766E", "KBCOL": "#6D28D9", + "LINK": "#1565C0", "LIGHT": "#F4F1EC", "GRID": "#CFC9BF", "MUTE": "#6B6256", + "DARK": "#23201C", "WARMBG": "#EAF1F0", "GOALBG": "#FBF1E9", "KBBG": "#F1ECFB", + }, + "front": { + "session_how_head": "So funktioniert eine Session", + "session_how_body": ( + "Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). " + "Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. " + "Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen." + ), + "flow_rows": [ + ["Teil", "Inhalt", "Zeit"], + ["Warm-up", "Gelenk-Prep (dynamisch)", "~3 Min"], + ["Rotation A", "4 Übungen, 3 Sätze (kurze Pausen)", "~14 Min"], + ["Rotation B", "4 Übungen, 3 Sätze (kurze Pausen)", "~14 Min"], + ["Cool-down", "Ausklingen + Dehnen", "~6 Min"], + ["", "Gesamt", "~35 Min"], + ], + "timer_note": "Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.", + "kb_head": "Warum Kettlebell – und warum es zu deinem Ziel passt", + "kb_box": ( + "Du trainierst für Calisthenics, nicht für Maximalkraft. Genau da liegen die belegten Stärken der Kettlebell – " + "ohne dass dich ihr dokumentierter Schwachpunkt (Maximalkraft/Hypertrophie wegen fixer Gewichtssprünge) trifft:
" + "•  Griffkraft (Studie BELL: +6–7 kg) – dein größter Mehrwert auf dem Weg zu Klimmzug & Dip.
" + "•  Power / ballistische Hüftstreckung (Swing) – explosive Basis für spätere Skills.
" + "•  Kondition & Körperfett – wirksam belegt; du startest mit wenig Ausdauer.
" + "•  Ganzkörper, ein Gerät, zeiteffizient – passt ideal ins 30-Min-Tabata-Format; niedriges Einstiegs-Verletzungsrisiko bei sauberer Technik.
" + "Ehrlich: Reines KB ersetzt kein Maximalkraft-/Muskelaufbau-Programm. Für dein Ziel (Basis-Fundament) liegt das ohnehin unter der relevanten Schwelle." + ), + "goals_head": "Realistische Ziele nach 8 Wochen", + "goals_box": ( + "Du startest bei 2–3 Liegestützen und 0 Dips. Realistisch und ehrlich nach 8 Wochen sauberer Arbeit:
" + "•  Liegestütze: von 2–3 auf erste saubere volle Reps (Rest in Knie-/Negativ-Form).
" + "•  Bank-Dips: saubere Reps mit Füßen etwas weiter weg.   Pike Push-up: am Boden mit kleinem ROM.
" + "•  Plank ~45–60 s, Wall Sit ~60 s, viele saubere Goblet-Kniebeugen, sauberer Swing + spürbar bessere Griffkraft.
" + "•  Noch NICHT dran: Diamond Push-ups, L-Sit, Handstand, schwerer Turkish Get-Up, Snatch – Stoff für Woche 12+." + ), + "goals_note": ( + "Wichtig: Die Wochen-Stufen sind ein Rahmen, kein Muss. Bleib auf einer Stufe, solange sie fordert. " + "Aufstiegs-Regel: erst hoch (oder schwerere Glocke), wenn du eine Übung 2 Sessions in Folge in allen Sätzen sauber über die vollen 20s schaffst. " + "KB = Kettlebell bevorzugt; Rucksack bleibt als Alternative." + ), + "lib_head": "Übungs-Bibliothek (mit Anleitungen)", + "lib_intro": ( + "Für Übungen, die du noch nicht kennst, ist ein Link zu einer bebilderten Anleitung dabei. " + "Lila Gruppe = Kettlebell-Übungen." + ), + "baseline_head": "Standortbestimmung & Tracking", + "baseline_intro": "Mach die Tests vor Tag 1 und in Woche 8 identisch nochmal – dein Vorher/Nachher.", + "baseline_rows": [ + ["Test", "Tag 0", "Woche 8"], + ["Liegestütze (max, saubere Reps)", "", ""], + ["Bank-Dips an der Couch (max)", "", ""], + ["Kniebeugen in 60 Sekunden", "", ""], + ["Ausfallschritte (max gesamt)", "", ""], + ["Plank (max Sekunden)", "", ""], + ["Wall Sit (max Sekunden)", "", ""], + ["KB Farmer-Hold (max Sekunden, Griff)", "", ""], + ["Körpergewicht (kg)", "95", ""], + ], + "reminder_head": "Kurz-Reminder", + "reminders": [ + "Form vor Reps. Wähle die Stufe / Glocke, bei der du die 20s kontrolliert durcharbeitest – mit 1–2 Reps Reserve.", + "Swing-Technik first. Der Swing ist ein Hüft-Schnapp, KEIN Kniebeuge und KEIN Armheben. Erst Dead-Stop-Swings, bis die Hüftstreckung sitzt.", + "Einseitige Übungen (Tag 2): Durchgang 1 = links, Durchgang 2 = rechts – so ist jede Seite automatisch ausbalanciert.", + "Täglich ist viel. Tag 5 leicht, Tag 7 frei. Bei starkem Muskelkater einen Extra-Ruhetag – gehört zum Aufbau.", + "Schlaf & Eiweiß. 7–9 h Schlaf, ~1,6 g Eiweiß pro kg. Stechender Schmerz = stoppen.", + ], + "sheets_note": ( + "Ab der nächsten Seite: 1 Druckblatt pro Session (Woche 1–8). Tag 6 = reine Kettlebell-Session. " + "Warm-up steht über, Cool-down + Dehnen unter den Übungen. Bei Vorerkrankungen/Verletzungen vorher ärztlich abklären." + ), + }, + "library": [ + {"group": "DRÜCKEN (Brust / Schultern / Trizeps)", "color": "ACC2", "items": [ + {"name": "Liegestütze", "desc": "Stütz, Körper gerade, Brust kontrolliert senken & drücken. Stufen: Inkline (Couch), Knie, Negativ, Voll.", "url": "https://blog.calimove.com/2023/01/25/push-up-variations-beginner-expert/"}, + {"name": "Bank-Dips (Couch)", "desc": "Rücken zur Couch, Hände auf der Kante, Po davor absenken (Ellbogen nach hinten), hochdrücken. Knie beugen = leichter.", "url": "https://www.healthline.com/health/fitness-exercise/bench-dips"}, + {"name": "Pike Push-up / Hold", "desc": "Umgekehrtes V (Hintern hoch), Kopf Richtung Boden senken, hochdrücken. Schulter-Fokus. Hände erhöht = leichter.", "url": "https://www.hingehealth.com/resources/articles/pike-push-up/"}, + {"name": "Seitheben (KB/Rucksack)", "desc": "Arme seitlich bis Schulterhöhe heben, Ellbogen leicht gebeugt, kontrolliert senken. Seitlicher Deltamuskel.", "url": ""}, + ]}, + {"group": "ZIEHEN (Rücken / Bizeps – KB oder Rucksack + Boden)", "color": "ACC2", "items": [ + {"name": "Einarm-Rudern ENG", "desc": "Hüfte beugen, Rücken GERADE, abgestützt. Ellbogen an den Rippen, Zug zur Hüfte – Lat.", "url": "https://www.puregym.com/exercises/back/rows/dumbbell-bent-over-row/"}, + {"name": "Einarm-Rudern BREIT", "desc": "Gleiche Position, aber Ellbogen ~45° geöffnet, Zug zum Brustkorb – oberer Rücken / hinterer Delta.", "url": "https://www.puregym.com/exercises/back/rows/dumbbell-bent-over-row/"}, + {"name": "Bizeps-Curls (KB/Rucksack)", "desc": "Last unten greifen, Oberarme fix am Körper, kontrolliert zu den Schultern curlen.", "url": ""}, + {"name": "Superman / Reverse Snow Angels / Y-T-W", "desc": "Bäuchlings Arme+Beine heben (Superman). Snow Angels = Arme am Boden auf/ab. Rücken ohne Stange.", "url": "https://www.calixpert.com/exercises/hollow-body-to-superman-hold"}, + ]}, + {"group": "BEINE", "color": "ACC2", "items": [ + {"name": "Kniebeuge / Goblet Squat (KB/Rucksack)", "desc": "Hüfte zurück, Oberschenkel parallel, Fersen am Boden. Goblet = Glocke/Rucksack vor der Brust umarmen.", "url": ""}, + {"name": "Ausfallschritte", "desc": "Großer Schritt (rückwärts = knieschonend), hinteres Knie Richtung Boden. Anfangs ggf. festhalten.", "url": ""}, + {"name": "RDL / Hip Hinge (KB/Rucksack)", "desc": "Knie leicht gebeugt, Hüfte nach hinten, Rücken gerade, Last entlang der Beine senken bis Dehnung hinten (Hamstrings).", "url": ""}, + {"name": "Glute Bridge / Wadenheben / Wall Sit", "desc": "Bridge: Hüfte hochdrücken (1-beinig steigern). Waden: Zehenspitzen hoch/runter. Wall Sit: Rücken an die Wand, halten.", "url": ""}, + ]}, + {"group": "KETTLEBELL (Power / Griff / Ganzkörper)", "color": "KBCOL", "items": [ + {"name": "Swing (zweiarmig, Hardstyle)", "desc": "Wichtigste KB-Übung. Hüfthinge, Glocke aus der Hüfte explosiv auf Brusthöhe schnappen (kein Armheben), oben Po+Bauch fest. Dead-Stop zum Lernen.", "url": "https://www.onepeloton.com/blog/kettlebell-swing/"}, + {"name": "Goblet Squat (KB)", "desc": "Glocke an den Hörnern vor der Brust umarmen, tief und aufrecht hocken. Beste Einsteiger-KB-Kniebeuge.", "url": "https://www.onepeloton.com/blog/goblet-squat/"}, + {"name": "KB-Deadlift (beidbeinig)", "desc": "Glocke zwischen den Füßen, Hüfte hinge, Rücken gerade, vom Boden heben und Hüfte durchstrecken. Stabile Swing-Vorbereitung.", "url": "https://www.onepeloton.com/blog/kettlebell-deadlift/"}, + {"name": "KB Floor Press", "desc": "Rücklings am Boden, Glocke vom Boden weg nach oben drücken. Schulterschonend, gut für Anfänger.", "url": "https://www.masterclass.com/articles/kettlebell-floor-press-guide"}, + {"name": "Halo", "desc": "Glocke (oder Rucksack) vor der Brust, langsam eng um den Kopf kreisen. Schulter-Mobility + Core.", "url": "https://www.tomsguide.com/how-to/kettlebell-halo-exercise-how-to-do-it-and-the-benefits-for-your-core-and-shoulder-muscles"}, + {"name": "Bottoms-up KB-Hold / Carry", "desc": "Glocke am Griff, Boden nach oben, Unterarm senkrecht, still halten. Schwierigkeit kommt aus der Balance – fordert Griff + Handgelenk auch mit leichter Glocke.", "url": "https://www.onepeloton.com/blog/farmers-carry/"}, + {"name": "Around-the-World + Uppercut", "desc": "Glocke kreist um den Körper (Around-the-World); danach diagonal nach oben zur gegenüberliegenden Schulter/Brust führen, die andere Hand fängt ab (wie ein Uppercut von unten). Rotatorischer Core + Koordination. Erst nur das Umkreisen lernen, Uppercut später.", "url": ""}, + ]}, + {"group": "CORE", "color": "ACC2", "items": [ + {"name": "Plank / Side Plank", "desc": "Unterarmstütz, Körper gerade. Anfangs auf den Knien. Side Plank seitlich auf einem Unterarm.", "url": ""}, + {"name": "Hollow Hold", "desc": "Auf dem Rücken, unterer Rücken am Boden, Arme+Beine leicht angehoben. Anfänger: angewinkelt (Tuck).", "url": "https://www.calixpert.com/exercises/hollow-body-to-superman-hold"}, + {"name": "Dead Bug", "desc": "Auf dem Rücken, Arme+Knie 90°, gegengleich Arm+Bein langsam ausstrecken, unterer Rücken bleibt am Boden.", "url": "https://dieringe.com/exercises/dead-bug"}, + {"name": "Bird Dog", "desc": "Vierfüßler, gegengleich Arm+Bein ausstrecken, Rumpf stabil, kurz halten.", "url": "https://www.hingehealth.com/resources/articles/bird-dog/"}, + {"name": "Mountain Climbers / Bicycle / Russian Twist / Beinheben", "desc": "Climbers: Knie zügig zur Brust. Bicycle: Ellbogen zum Gegenknie. Twist: sitzend drehen (KB). Beinheben: Knie gebeugt = leichter.", "url": ""}, + ]}, + {"group": "WARM-UP & MOBILITY", "color": "ACC2", "items": [ + {"name": "Inchworm", "desc": "Aus dem Stand vorbeugen, Hände nach vorne in den Stütz wandern, dann zurück.", "url": "https://www.onepeloton.com/blog/inchworm-exercise"}, + {"name": "Jacks / High Knees / Kreisen / Cat-Cow", "desc": "Klassische dynamische Aufwärm-Bewegungen – locker, zum Warmwerden.", "url": ""}, + ]}, + ], + "phases": { + "names": ["Phase 1 · Fundament", "Phase 2 · Aufbau", "Phase 3 · Steigerung", "Phase 4 · Festigen"], + "params": [ + "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.", + "Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.", + "Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.", + ], + "param_light": "Bewusst locker – aktive Erholung. Saubere, leichte Wiederholungen, keine Belastung bis zum Limit.", + }, + "stages": { + 'ls': ['Inkline (hoch)', 'Inkline tiefer / Knie', 'Knie / Negativ', 'Negativ + erste Volle'], + 'dips': ['Knie stark gebeugt, klein', 'Knie gebeugt, voll', 'Füße etwas weiter', 'Füße weiter weg'], + 'pike': ['Pike Hold', 'Hold + kleine Senkung', 'Push-up Hände erhöht', 'Push-up Boden, klein'], + 'lseng': ['Inkline (hoch)', 'Inkline mittel', 'Inkline tief', 'Knie-Liegestütze'], + 'load': ['leicht', 'etwas mehr', 'mehr', 'fordernd'], + 'squat': ['Körpergewicht', 'Körpergewicht, mehr Reps', 'Goblet leicht', 'Goblet mittel'], + 'tsquat': ['3s runter', '3s + Pause unten', 'Goblet 3s', 'Goblet 3s + Pause'], + 'lunge': ['Körpergewicht (ggf. festhalten)', 'Körpergewicht', 'mehr Reps', '+ Last leicht'], + 'bridge': ['beidbeinig', 'beidbeinig, 2s halten', 'einbeinig', 'einbeinig, 2s halten'], + 'wall': ['15–25 s', '25–40 s', '40–55 s', '55–70 s'], + 'calf': ['Körpergewicht', 'langsam', 'langsam, mehr Reps', '1-beinig'], + 'sup': ['Superman Hold', 'Superman Hold länger', 'Reverse Snow Angels', 'Y-T-W Raises'], + 'plank': ['auf Knien 15–20 s', 'voll 20–30 s', 'voll + Schulter-Taps', 'voll, länger'], + 'mtn': ['moderat', 'moderat', 'zügig', 'zügig'], + 'db': ['Arme ODER Beine einzeln', 'gegengleich', 'gegengleich + Pause', 'Beine weiter gestreckt'], + 'lr': ['Knie gebeugt', 'Knie gebeugt, langsam', 'halb gestreckt', 'gestreckt'], + 'holl': ['Tuck (Knie an)', 'Tuck, länger', 'halb gestreckt', 'halb/voll'], + 'sp': ['auf Knien', 'auf Knien, länger', 'voll', 'voll, länger'], + 'bic': ['langsam', 'moderat', 'moderat', 'zügig'], + 'tw': ['ohne Gewicht', 'ohne Gewicht', 'KB leicht', 'KB'], + 'swing': ['Dead-Stop, leicht (Technik)', 'Dead-Stop, mehr Reps', 'durchgehend, mittel', 'durchgehend, schwerer'], + 'gobletkb': ['leicht, Tiefe lernen', 'leicht, mehr Reps', 'mittel', 'mittel, 3s runter'], + 'press': ['leicht (Floor Press)', 'Floor Press mehr', 'Overhead leicht', 'Overhead mittel'], + 'halo': ['leicht, langsam', 'leicht', 'mittel', 'mittel, langsam'], + 'carry': ['im Stand 10–15s', '15–20s, ruhig', 'bis Schulterhöhe 15s', 'länger / ein paar Schritte'], + 'kbdl': ['leicht, Technik', 'leicht, mehr Reps', 'mittel', 'schwerer, kontrolliert'], + 'rowe': ['leicht', 'etwas mehr', 'mehr', 'fordernd'], + 'row1': ['leicht', 'etwas mehr', 'einarmig leicht', 'einarmig mittel'], + 'row2': ['leicht (Technik)', 'leicht, mehr Reps', 'mittel', 'mittel, Pause oben'], + 'lraise': ['leicht (Technik)', 'leicht, mehr Reps', 'mittel', 'mittel, langsam'], + 'atw': ['nur Around-the-World, leicht', 'Around-the-World, mehr Reps', '+ Uppercut, leicht', '+ Uppercut, flüssig'], + }, + "prepost": { + "1": {"warmup": "Handflächen-Wippen · Armkreisen/Wand-Slides · Hüftkreisen + Beinschwünge · Knie-über-Zeh an der Wand", + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Türrahmen (Brust/Schulter, je 30–40s) · Trizeps (je 30s) · Seitheben/Lat-Seitdehnung (je 30s) · Quad im Stand (je 30s) · Knieender Hüftbeuger (je 30–40s) · Wade an der Wand (je 30s)"}, + "2": {"warmup": "Handflächen-Wippen · Handtuch-Dislocates (8–10×) · Cat-Cow · Hüftkreisen + Beinschwünge", + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Lat-/Seitdehnung (je 30s) · Türrahmen (je 30–40s) · BWS über die Couch (30–40s) · Cobra/Sphinx (Bauch, 30s) · Unterarm (je 20s) · Knieender Hüftbeuger (je 30s)"}, + "3": {"warmup": "Hüftkreisen + Beinschwünge · Knie-über-Zeh an der Wand · Cat-Cow · Handflächen-Wippen", + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Hamstring-Dehnung (Couch, je 30–40s) · Quad im Stand (je 30s) · Knieender Hüftbeuger (je 30–40s) · Figure-4/Taube (je 30–40s) · Cobra + liegende Wirbelsäulen-Rotation (je 30s) · Wade an der Wand (je 30s)"}, + "4": {"warmup": "Handflächen-Wippen · Handtuch-Dislocates (8–10×) · Armkreisen/Wand-Slides · Cat-Cow", + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Türrahmen (je 30–40s) · Trizeps (je 30s) · Lat-/Seitdehnung (je 30s) · BWS über die Couch (30–40s) · Unterarm (je 20s)"}, + "5": {"warmup": "Cat-Cow · Hüftkreisen + Beinschwünge · Armkreisen/Wand-Slides · Knie-über-Zeh an der Wand", + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Türrahmen (je 30s) · Knieender Hüftbeuger (je 30s) · BWS über die Couch (30–40s) · Figure-4/Taube (je 30s) · Wade an der Wand (je 30s)"}, + "6": {"warmup": "Halo (leicht) · Hüftkreisen + Beinschwünge · Cat-Cow · Handflächen-Wippen", + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Hamstring-Dehnung (Couch, je 30–40s) · Knieender Hüftbeuger (je 30–40s) · Figure-4/Taube (je 30–40s) · Unterarm (je 20s) · BWS über die Couch (30–40s) · Lat-/Seitdehnung (je 30s)"}, + }, + "days": [ + {"num": 1, "focus": "Drücken + Beine", + "rotA": [{"name": "Liegestütze", "key": "ls", "cue": "Brust kontrolliert senken & drücken"}, + {"name": "Bank-Dips (Couch)", "key": "dips", "cue": "Po vor der Couch senken, hochdrücken"}, + {"name": "Pike", "key": "pike", "cue": "umgek. V, Kopf senken (Schultern)"}, + {"name": "Seitheben (KB/Rucksack)", "key": "lraise", "cue": "Arme seitlich bis Schulterhöhe, kontrolliert"}], + "rotB": [{"name": "Goblet Squat (KB/Rucksack)", "key": "gobletkb", "cue": "Last umarmen, tief"}, + {"name": "Ausfallschritt rückw.", "key": "lunge", "cue": "großer Schritt, Knie Richtung Boden"}, + {"name": "Glute Bridge", "key": "bridge", "cue": "Hüfte hochdrücken, Po fest"}, + {"name": "Wadenheben", "key": "calf", "cue": "auf Zehenspitzen hoch/runter"}]}, + {"num": 2, "focus": "Ziehen + Core", + "rotA": [{"name": "Einarm-Rudern ENG (KB)", "key": "rowe", "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts"}, + {"name": "Bizeps-Curls (KB/Rucksack)", "key": "load", "cue": "Oberarme fix, zu den Schultern"}, + {"name": "Einarm-Rudern BREIT (KB)", "key": "row2", "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts"}, + {"name": "Superman / Y-T-W", "key": "sup", "cue": "bäuchlings Arme+Beine heben"}], + "rotB": [{"name": "Plank", "key": "plank", "cue": "gerade Linie halten"}, + {"name": "Mountain Climbers", "key": "mtn", "cue": "Knie zügig zur Brust"}, + {"name": "Dead Bug", "key": "db", "cue": "gegengleich, Rücken am Boden"}, + {"name": "Beinheben", "key": "lr", "cue": "Beine heben/senken"}]}, + {"num": 3, "focus": "Beine + Core", + "rotA": [{"name": "Goblet Squat (KB/Rucksack)", "key": "gobletkb", "cue": "Last umarmen, tief"}, + {"name": "RDL / Hip Hinge (KB/Rucksack)", "key": "load", "cue": "Hüfte zurück, Rücken gerade, Hamstrings"}, + {"name": "Glute Bridge 1-beinig", "key": "bridge", "cue": "ein Bein gestreckt"}, + {"name": "Wall Sit", "key": "wall", "cue": "Rücken an die Wand, halten"}], + "rotB": [{"name": "Hollow Hold", "key": "holl", "cue": "unterer Rücken am Boden"}, + {"name": "Side Plank (Wechsel)", "key": "sp", "cue": "seitlich auf einem Unterarm"}, + {"name": "Bicycle Crunch", "key": "bic", "cue": "Ellbogen zum Gegenknie"}, + {"name": "Russian Twist (KB)", "key": "tw", "cue": "sitzend Rumpf drehen"}]}, + {"num": 4, "focus": "Oberkörper: Drücken + Ziehen", + "rotA": [{"name": "Liegestütze", "key": "ls", "cue": "Brust kontrolliert senken & drücken"}, + {"name": "Bank-Dips (Couch)", "key": "dips", "cue": "Po vor der Couch senken"}, + {"name": "Pike", "key": "pike", "cue": "umgek. V, Kopf senken"}, + {"name": "KB Floor Press", "key": "press", "cue": "Glocke vom Boden weg drücken"}], + "rotB": [{"name": "Einarm-Rudern ENG (KB)", "key": "rowe", "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts"}, + {"name": "Bizeps-Curls (KB/Rucksack)", "key": "load", "cue": "Oberarme fix, zu den Schultern"}, + {"name": "Einarm-Rudern BREIT (KB)", "key": "row2", "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts"}, + {"name": "Y-T-W / Superman", "key": "sup", "cue": "bäuchlings, Rücken"}]}, + {"num": 5, "focus": "Ganzkörper LEICHT + Mobility", + "rotA": [{"name": "Kniebeuge (leicht)", "key": "", "cue": "locker, sauber"}, + {"name": "Liegestütze (1 Stufe leichter)", "key": "", "cue": "leichtere Variante"}, + {"name": "Rudern (leicht)", "key": "", "cue": "wenig Gewicht"}, + {"name": "Mountain Climbers (locker)", "key": "", "cue": "ruhiges Tempo"}], + "rotB": [{"name": "Inchworm", "key": "", "cue": "in den Stütz wandern, zurück"}, + {"name": "Cat-Cow", "key": "", "cue": "Rücken ründen/strecken"}, + {"name": "Bird Dog", "key": "", "cue": "gegengleich Arm+Bein, kurz halten"}, + {"name": "KB-Halo (leicht)", "key": "", "cue": "langsam um den Kopf kreisen"}]}, + {"num": 6, "focus": "KETTLEBELL Ganzkörper", + "rotA": [{"name": "KB Swing (zweiarmig)", "key": "swing", "cue": "Hüft-Schnapp, kein Armheben"}, + {"name": "Goblet Squat (KB)", "key": "gobletkb", "cue": "Glocke vor der Brust, tief"}, + {"name": "KB-Deadlift (beidbeinig)", "key": "kbdl", "cue": "Glocke zwischen den Füßen, Hüfte hinge"}, + {"name": "Halo", "key": "halo", "cue": "eng um den Kopf kreisen"}], + "rotB": [{"name": "KB Floor Press", "key": "press", "cue": "Glocke vom Boden weg drücken"}, + {"name": "Einarmiges Rudern (KB)", "key": "row1", "cue": "Rücken gerade, zum Bauch"}, + {"name": "Bottoms-up KB-Hold (Griff)", "key": "carry", "cue": "Glocke Boden nach oben, Unterarm senkrecht, still halten"}, + {"name": "Around-the-World + Uppercut (KB)", "key": "atw", "cue": "Glocke um den Körper, dann diagonal zur Gegen-Schulter, andere Hand fängt"}]}, + ], +} + + +KB_NOTE = ("Reine Kettlebell-Session. Swing = Hüft-Schnapp, kein Armheben. " + "Glocke so wählen, dass alle Reps sauber sind.") + + +def _lighten(hexstr, w=0.90): + try: + c = colors.HexColor(hexstr) + except Exception: + c = colors.HexColor("#0F766E") + return colors.Color(c.red * (1 - w) + w, c.green * (1 - w) + w, c.blue * (1 - w) + w) + + +def migrate_config(cfg): + """Führt alte Trainings-Configs in das neue, flexible Format über (idempotent). + Neu: phases.items[{name,params,weeks}], stages{key:{name,steps[]}}, + days[{...,rotations:[{label,exercises[]}]}] statt rotA/rotB, + Tag-Farbe/Notiz/Badge statt fest verdrahtetem KB-Tag.""" + cfg = copy.deepcopy(cfg or {}) + meta = cfg.setdefault("meta", {}) + days = cfg.get("days", []) or [] + + # phases: {names,params} -> {items:[{name,params,weeks}], param_light} + ph = cfg.get("phases", {}) or {} + if "items" not in ph: + names = ph.get("names", []) or [] + params = ph.get("params", []) or [] + n = max(len(names), len(params)) + items = [{"name": names[i] if i < len(names) else ("Phase %d" % (i + 1)), + "params": params[i] if i < len(params) else "", + "weeks": 2} for i in range(n)] + cfg["phases"] = {"items": items, "param_light": ph.get("param_light", "")} + + # Übungsnamen je Key sammeln (für Stufen-Namen) + name_by_key = {} + for d in days: + groups = [d.get("rotA", []) or [], d.get("rotB", []) or []] + for rot in d.get("rotations", []) or []: + groups.append(rot.get("exercises", []) or []) + for grp in groups: + for it in grp: + k = it.get("key") + if k and it.get("name") and k not in name_by_key: + name_by_key[k] = it["name"] + + # stages: [..] -> {name, steps:[..], result_schemas:[..]} + # ``result_schemas`` liegt parallel zu ``steps``. Ein Eintrag kann ``None`` + # sein und bedeutet dann: Übungsstandard bzw. Tracker-Fallback verwenden. + newstages = {} + for key, val in (cfg.get("stages", {}) or {}).items(): + if isinstance(val, dict) and "steps" in val: + stage = copy.deepcopy(val) + steps = stage.get("steps") if isinstance(stage.get("steps"), list) else [] + schemas = stage.get("result_schemas") if isinstance(stage.get("result_schemas"), list) else [] + schemas = list(schemas[:len(steps)]) + while len(schemas) < len(steps): + schemas.append(None) + stage["steps"] = steps + stage["result_schemas"] = schemas + newstages[key] = stage + else: + steps = val if isinstance(val, list) else [] + nm = name_by_key.get(key) or next((s for s in steps if s), "") or key + newstages[key] = {"name": nm, "steps": steps, "result_schemas": [None] * len(steps)} + cfg["stages"] = newstages + + # Explizites Trainingsformat. Alte Pläne bleiben auf ``auto`` und werden + # vom Tracker weiterhin aus den Plantexten erkannt. Neue Pläne können + # Tabata/Intervalle damit ohne Text-Heuristik eindeutig beschreiben. + fmt = cfg.get("training_format") + if not isinstance(fmt, dict): + fmt = {} + mode = str(fmt.get("mode") or "auto") + if mode not in {"auto", "sets_reps", "tabata", "fixed_interval"}: + mode = "auto" + normalized_fmt = {"mode": mode} + for field in ("work_seconds", "rest_seconds", "rounds"): + try: + value = int(fmt.get(field)) + except (TypeError, ValueError): + value = 0 + normalized_fmt[field] = value if value > 0 else None + normalized_fmt["fixed_interval"] = bool( + fmt.get("fixed_interval") or mode in {"tabata", "fixed_interval"} + ) + normalized_fmt["rounds_scope"] = "block" + cfg["training_format"] = normalized_fmt + + # days: rotA/rotB -> rotations (+ KB-Styling & light-Tag nur bei Alt-Konvertierung) + for d in days: + if "rotations" not in d: + rots = [] + if d.get("rotA"): + rots.append({"label": "ROTATION A — 3 Sätze", "exercises": d.get("rotA")}) + if d.get("rotB"): + rots.append({"label": "ROTATION B — 3 Sätze", "exercises": d.get("rotB")}) + d["rotations"] = rots + d.pop("rotA", None) + d.pop("rotB", None) + if d.get("num") == 6: + d.setdefault("color", "#6D28D9") + d.setdefault("note", KB_NOTE) + d.setdefault("note_bg", "#F1ECFB") + if d.get("num") == 5: + d.setdefault("light", True) + cfg["days"] = days + meta.setdefault("days", len(days)) + return migrate_training_config(cfg) + + +def default_config(): + return migrate_config(copy.deepcopy(DEFAULTS)) + + +# ========================================================================= +# RENDERER +# ========================================================================= +def build_pdf(cfg): + """cfg: Trainings-Config (altes oder neues Format). Gibt PDF-Bytes zurück.""" + _D = DEFAULTS + cfg = dict(cfg or {}) + meta = dict(_D["meta"]); meta.update(cfg.get("meta") or {}) + cols = dict(_D["colors"]); cols.update(cfg.get("colors") or {}) + cfg["meta"], cfg["colors"] = meta, cols + for k in ("front", "library", "phases", "stages", "prepost", "days"): + if not cfg.get(k): + cfg[k] = copy.deepcopy(_D[k]) + cfg = migrate_config(cfg) # auf neues Modell normalisieren + + C = {k: colors.HexColor(v) for k, v in cfg["colors"].items()} + INK, ACCENT, ACC2, KBCOL = C["INK"], C["ACCENT"], C["ACC2"], C["KBCOL"] + LINK, LIGHT, GRID, MUTE = C["LINK"], C["LIGHT"], C["GRID"], C["MUTE"] + DARK, WARMBG, GOALBG, KBBG = C["DARK"], C["WARMBG"], C["GOALBG"], C["KBBG"] + link_hex = cfg["colors"]["LINK"] + + styles = getSampleStyleSheet() + + def Sty(name, **kw): + base = kw.pop("parent", styles["Normal"]) + return ParagraphStyle(name, parent=base, **kw) + + title_s = Sty("title", fontName="Helvetica-Bold", fontSize=23, textColor=INK, leading=26, spaceAfter=2) + sub_s = Sty("sub", fontName="Helvetica", fontSize=10.5, textColor=MUTE, leading=14, spaceAfter=2) + h2_s = Sty("h2", fontName="Helvetica-Bold", fontSize=14, textColor=ACCENT, leading=17, spaceBefore=13, spaceAfter=5) + body_s = Sty("body", fontName="Helvetica", fontSize=9.6, textColor=INK, leading=13.5, spaceAfter=4) + smallm_s = Sty("smallm", fontName="Helvetica", fontSize=8.2, textColor=MUTE, leading=10.5) + cell_s = Sty("cell", fontName="Helvetica", fontSize=8.3, textColor=INK, leading=10.6) + cellb_s = Sty("cellb", fontName="Helvetica-Bold", fontSize=8.3, textColor=INK, leading=10.6) + link_s = Sty("link", fontName="Helvetica", fontSize=8.2, textColor=LINK, leading=10.6) + th_s = Sty("th", fontName="Helvetica-Bold", fontSize=8.6, textColor=colors.white, leading=11) + tag_s = Sty("tag", fontName="Helvetica-Bold", fontSize=8.4, textColor=colors.white, leading=11) + note_s = Sty("note", fontName="Helvetica", fontSize=9, textColor=INK, leading=13) + goal_s = Sty("goal", fontName="Helvetica", fontSize=8.9, textColor=INK, leading=12.5) + kb_s = Sty("kb", fontName="Helvetica", fontSize=8.9, textColor=INK, leading=12.5) + bandL = Sty("bandL", fontName="Helvetica-Bold", fontSize=17, textColor=colors.white, leading=19) + bandR = Sty("bandR", fontName="Helvetica-Bold", fontSize=11.5, textColor=colors.white, leading=14, alignment=TA_RIGHT) + howto = Sty("howto", fontName="Helvetica", fontSize=8.6, textColor=MUTE, leading=11.5, spaceBefore=4, spaceAfter=2) + warm = Sty("warm", fontName="Helvetica", fontSize=9, textColor=INK, leading=12.5) + roth = Sty("roth", fontName="Helvetica-Bold", fontSize=11, textColor=colors.white, leading=13) + exn = Sty("exn", fontName="Helvetica-Bold", fontSize=9.6, textColor=INK, leading=11.5) + stage_s = Sty("stage", fontName="Helvetica-Bold", fontSize=9, textColor=ACCENT, leading=11) + param_s = Sty("param", fontName="Helvetica", fontSize=8.8, textColor=INK, leading=12) + logst = Sty("logst", fontName="Helvetica", fontSize=9.5, textColor=INK, leading=18) + + USABLE = A4[0] - 2 * 1.3 * cm + + def link(url, label="Anleitung (Bilder/Video)"): + return Paragraph('%s' % (url, link_hex, label), link_s) + + front = cfg["front"] + story = [] + + # ===================== FRONT MATTER ===================== + story.append(Paragraph(cfg["meta"]["title"], title_s)) + story.append(Paragraph(cfg["meta"]["subtitle"], sub_s)) + story.append(HRFlowable(width="100%", thickness=2, color=ACCENT, spaceBefore=6, spaceAfter=6)) + + story.append(Paragraph(front["session_how_head"], h2_s)) + story.append(Paragraph(front["session_how_body"], body_s)) + + flow = front["flow_rows"] + fr = [[Paragraph(c, th_s if i == 0 else (cellb_s if (j == 1 and r[0] == '') else cell_s)) for j, c in enumerate(r)] for i, r in enumerate(flow)] + t = Table(fr, colWidths=[3.2 * cm, 9.6 * cm, 2.7 * cm]) + t.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, 0), DARK), ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, LIGHT]), + ("GRID", (0, 0), (-1, -1), 0.5, GRID), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("TOPPADDING", (0, 0), (-1, -1), 4), ("BOTTOMPADDING", (0, 0), (-1, -1), 4), ("LEFTPADDING", (0, 0), (-1, -1), 6)])) + story.append(t) + story.append(Spacer(1, 4)) + story.append(Paragraph(front["timer_note"], smallm_s)) + + # KB evidence box + story.append(Paragraph(front["kb_head"], h2_s)) + kb_rows = [[Paragraph(front["kb_box"], kb_s)]] + gt = Table(kb_rows, colWidths=[USABLE]) + gt.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), KBBG), ("BOX", (0, 0), (-1, -1), 0.8, KBCOL), + ("TOPPADDING", (0, 0), (-1, -1), 8), ("BOTTOMPADDING", (0, 0), (-1, -1), 8), + ("LEFTPADDING", (0, 0), (-1, -1), 10), ("RIGHTPADDING", (0, 0), (-1, -1), 10)])) + story.append(gt) + + story.append(Paragraph(front["goals_head"], h2_s)) + goal_rows = [[Paragraph(front["goals_box"], goal_s)]] + gt2 = Table(goal_rows, colWidths=[USABLE]) + gt2.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), GOALBG), ("BOX", (0, 0), (-1, -1), 0.8, ACCENT), + ("TOPPADDING", (0, 0), (-1, -1), 8), ("BOTTOMPADDING", (0, 0), (-1, -1), 8), + ("LEFTPADDING", (0, 0), (-1, -1), 10), ("RIGHTPADDING", (0, 0), (-1, -1), 10)])) + story.append(gt2) + story.append(Spacer(1, 5)) + story.append(Paragraph(front["goals_note"], note_s)) + story.append(PageBreak()) + + # ---- exercise library ---- + story.append(Paragraph(front["lib_head"], h2_s)) + story.append(Paragraph(front["lib_intro"], smallm_s)) + + for grp in cfg["library"]: + gcol = C.get(grp["color"], ACC2) + rows = [[Paragraph(grp["group"], tag_s), "", ""], + [Paragraph("Übung", th_s), Paragraph("Ausführung (Kurz)", th_s), Paragraph("Anleitung", th_s)]] + for it in grp["items"]: + rows.append([Paragraph(it["name"], cellb_s), Paragraph(it["desc"], cell_s), + link(it["url"]) if it.get("url") else Paragraph("Standard-Bewegung", smallm_s)]) + tstyle = [("SPAN", (0, 0), (-1, 0)), ("BACKGROUND", (0, 0), (-1, 0), gcol), ("BACKGROUND", (0, 1), (-1, 1), DARK), + ("ROWBACKGROUNDS", (0, 2), (-1, -1), [colors.white, LIGHT]), ("GRID", (0, 1), (-1, -1), 0.5, GRID), + ("VALIGN", (0, 0), (-1, -1), "TOP"), ("TOPPADDING", (0, 0), (-1, -1), 4), ("BOTTOMPADDING", (0, 0), (-1, -1), 4), + ("LEFTPADDING", (0, 0), (-1, -1), 6), ("RIGHTPADDING", (0, 0), (-1, -1), 6)] + story.append(KeepTogether(Table(rows, colWidths=[4.0 * cm, 8.9 * cm, 3.5 * cm], style=TableStyle(tstyle)))) + story.append(Spacer(1, 6)) + story.append(PageBreak()) + + # ---- baseline + reminders ---- + story.append(Paragraph(front["baseline_head"], h2_s)) + story.append(Paragraph(front["baseline_intro"], body_s)) + track = front["baseline_rows"] + tr = [[Paragraph(c, th_s if i == 0 else cell_s) for c in r] for i, r in enumerate(track)] + tt = Table(tr, colWidths=[9.5 * cm, 3.0 * cm, 3.0 * cm]) + tt.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, 0), DARK), ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, LIGHT]), + ("GRID", (0, 0), (-1, -1), 0.5, GRID), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("TOPPADDING", (0, 0), (-1, -1), 5), ("BOTTOMPADDING", (0, 0), (-1, -1), 5), ("LEFTPADDING", (0, 0), (-1, -1), 6)])) + story.append(tt) + story.append(Spacer(1, 8)) + story.append(Paragraph(front["reminder_head"], h2_s)) + for r in front["reminders"]: + story.append(Paragraph("•  " + r, body_s)) + story.append(Spacer(1, 6)) + story.append(HRFlowable(width="100%", thickness=1, color=GRID)) + story.append(Paragraph(front["sheets_note"], smallm_s)) + story.append(PageBreak()) + + # ===================== SESSION SHEETS ===================== + stages = cfg["stages"] + phase_items = cfg["phases"]["items"] + PARAM_LIGHT = cfg["phases"].get("param_light", "") + prepost = cfg["prepost"] + + def phase_index(week): + acc = 0 + for i, ph in enumerate(phase_items): + try: + acc += int(ph.get("weeks", 2) or 0) + except Exception: + acc += 2 + if week <= acc: + return i + return max(0, len(phase_items) - 1) + + def phase_name(pi): + return phase_items[pi]["name"] if 0 <= pi < len(phase_items) else "" + + def phase_params(pi): + return phase_items[pi].get("params", "") if 0 <= pi < len(phase_items) else "" + + def stage_text(key, pi): + st = stages.get(key) if key else None + if st: + steps = st.get("steps", []) if isinstance(st, dict) else st + if 0 <= pi < len(steps) and steps[pi]: + step = steps[pi] + return step.get("name", "") if isinstance(step, dict) else step + return "leicht" + + def rotation_table(rot, pi): + rows = [[Paragraph("Übung", th_s), Paragraph("Stufe heute", th_s), + Paragraph("Satz 1", th_s), Paragraph("Satz 2", th_s), Paragraph("Satz 3", th_s)]] + for item in rot: + name, key, cue = item.get("name", ""), item.get("key") or None, item.get("cue", "") + ex = Paragraph("%s
%s" % (name, cue), exn) + sc = Paragraph(stage_text(key, pi), stage_s) + rows.append([ex, sc, "", "", ""]) + tb = Table(rows, colWidths=[7.2 * cm, 3.5 * cm, 1.95 * cm, 1.95 * cm, 1.95 * cm]) + tb.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, 0), DARK), ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, LIGHT]), + ("GRID", (0, 0), (-1, -1), 0.6, GRID), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("TOPPADDING", (0, 1), (-1, -1), 8), ("BOTTOMPADDING", (0, 1), (-1, -1), 8), + ("TOPPADDING", (0, 0), (-1, 0), 4), ("BOTTOMPADDING", (0, 0), (-1, 0), 4), + ("LEFTPADDING", (0, 0), (-1, -1), 6), ("RIGHTPADDING", (0, 0), (-1, -1), 6), ("ALIGN", (2, 0), (-1, -1), "CENTER")])) + return tb + + def day_has_exercises(day): + return any((rot.get("exercises") or []) for rot in (day.get("rotations") or [])) + + def session_page(week, day): + num, focus = day.get("num"), day.get("focus", "") + pi = phase_index(week) + color = day.get("color") + band_col = colors.HexColor(color) if color else ACCENT + rot_col = colors.HexColor(color) if color else ACC2 + pp = prepost.get(str(num), {"warmup": "", "cooldown": "", "stretch": ""}) + pre_txt = pp.get("warmup", ""); cd_txt = pp.get("cooldown", ""); stretch_txt = pp.get("stretch", "") + flow = [] + badge = day.get("badge") or {} + btext = badge.get("text", "").strip() + left_html = "WOCHE %d · TAG %d" % (week, num) + if btext: + left_html += " %s " % (badge.get("color", "#6D28D9"), btext) + band = Table([[Paragraph(left_html, bandL), + Paragraph("%s
%s" % (focus, phase_name(pi)), bandR)]], + colWidths=[USABLE * 0.5, USABLE * 0.5]) + band.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), band_col), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("TOPPADDING", (0, 0), (-1, -1), 9), ("BOTTOMPADDING", (0, 0), (-1, -1), 9), + ("LEFTPADDING", (0, 0), (0, 0), 12), ("RIGHTPADDING", (-1, 0), (-1, 0), 12)])) + flow.append(band) + flow.append(Paragraph("Tabata: 20s an / 10s aus · pro Block 4 Übungen × 2 Durchgänge = 1 Satz · 10s Vorlauf", howto)) + wb = Table([[Paragraph("WARM-UP · Gelenk-Prep (dynamisch, ~3 Min, kein Halten): " + pre_txt, warm)]], colWidths=[USABLE]) + wb.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), WARMBG), ("BOX", (0, 0), (-1, -1), 0.6, ACC2), + ("TOPPADDING", (0, 0), (-1, -1), 6), ("BOTTOMPADDING", (0, 0), (-1, -1), 6), + ("LEFTPADDING", (0, 0), (-1, -1), 9), ("RIGHTPADDING", (0, 0), (-1, -1), 9)])) + flow.append(Spacer(1, 5)) + flow.append(wb) + note = (day.get("note") or "").strip() + if note: + nb = colors.HexColor(day["note_bg"]) if day.get("note_bg") else _lighten(color or "#0F766E") + nborder = colors.HexColor(color) if color else ACC2 + flow.append(Spacer(1, 4)) + nt = Table([[Paragraph(note, warm)]], colWidths=[USABLE]) + nt.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), nb), ("BOX", (0, 0), (-1, -1), 0.6, nborder), + ("TOPPADDING", (0, 0), (-1, -1), 6), ("BOTTOMPADDING", (0, 0), (-1, -1), 6), + ("LEFTPADDING", (0, 0), (-1, -1), 9), ("RIGHTPADDING", (0, 0), (-1, -1), 9)])) + flow.append(nt) + flow.append(Spacer(1, 8)) + for rot in (day.get("rotations") or []): + exs = rot.get("exercises") or [] + if not exs: + continue + rh = Table([[Paragraph(rot.get("label", ""), roth)]], colWidths=[USABLE]) + rh.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), rot_col), ("TOPPADDING", (0, 0), (-1, -1), 4), + ("BOTTOMPADDING", (0, 0), (-1, -1), 4), ("LEFTPADDING", (0, 0), (-1, -1), 8)])) + flow.append(rh) + flow.append(Spacer(1, 2)) + flow.append(rotation_table(exs, pi)) + flow.append(Spacer(1, 8)) + pbp = day.get("params_by_phase") or [] + if 0 <= pi < len(pbp) and (pbp[pi] or "").strip(): + ptxt = pbp[pi] + else: + ptxt = PARAM_LIGHT if day.get("light") else phase_params(pi) + pb = Table([[Paragraph(ptxt, param_s)]], colWidths=[USABLE]) + pb.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), LIGHT), ("BOX", (0, 0), (-1, -1), 0.6, GRID), + ("TOPPADDING", (0, 0), (-1, -1), 6), ("BOTTOMPADDING", (0, 0), (-1, -1), 6), ("LEFTPADDING", (0, 0), (-1, -1), 9)])) + flow.append(pb) + flow.append(Spacer(1, 7)) + cd = Table([[Paragraph("COOL-DOWN (~1–2 Min): " + cd_txt + + "
DEHNEN (warm & statisch, bis mildes Ziehen, ~4–6 Min): " + stretch_txt, warm)]], colWidths=[USABLE]) + cd.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), WARMBG), ("BOX", (0, 0), (-1, -1), 0.6, ACC2), + ("TOPPADDING", (0, 0), (-1, -1), 6), ("BOTTOMPADDING", (0, 0), (-1, -1), 6), + ("LEFTPADDING", (0, 0), (-1, -1), 9), ("RIGHTPADDING", (0, 0), (-1, -1), 9)])) + flow.append(cd) + flow.append(Spacer(1, 7)) + flow.append(Paragraph("Gefühl heute (1–10): ______ Glocke (kg): ______ Notizen: ________________________________", logst)) + return flow + + weeks = int(cfg["meta"].get("weeks", 8) or 0) + for week in range(1, weeks + 1): + for day in cfg["days"]: + if not day_has_exercises(day): + continue + story += session_page(week, day) + story.append(PageBreak()) + if story and isinstance(story[-1], PageBreak): + story.pop() + + footer_left = cfg["meta"]["footer_left"] + + def footer(canvas, doc): + canvas.saveState() + canvas.setFont("Helvetica", 7.5) + canvas.setFillColor(MUTE) + canvas.drawString(1.3 * cm, 1.0 * cm, footer_left) + canvas.drawRightString(A4[0] - 1.3 * cm, 1.0 * cm, "Seite %d" % doc.page) + canvas.restoreState() + + buf = BytesIO() + doc = SimpleDocTemplate(buf, pagesize=A4, leftMargin=1.3 * cm, rightMargin=1.3 * cm, + topMargin=1.2 * cm, bottomMargin=1.4 * cm, title=cfg["meta"]["doc_title"]) + doc.build(story, onFirstPage=footer, onLaterPages=footer) + return buf.getvalue() + + +if __name__ == "__main__": + data = build_pdf(default_config()) + with open("/tmp/test_plan.pdf", "wb") as f: + f.write(data) + print("OK", len(data), "bytes") diff --git a/plugins/trainingsplan/plugin.json b/plugins/trainingsplan/plugin.json new file mode 100644 index 0000000..df7252b --- /dev/null +++ b/plugins/trainingsplan/plugin.json @@ -0,0 +1,30 @@ +{ + "id": "trainingsplan", + "name": "Trainingsplan- & Rezept-Editor", + "summary": "Versionierte Trainingspläne mit stabilen IDs, Validierung und Tracker-Vorschau bearbeiten", + "description": "Editor mit Entwurf und Veröffentlichung, stabilem Planvertrag, progressionsabhängigen Ergebnisschemata, planbezogener Übungsbibliothek, Konfliktschutz und strukturierten Vorschlägen aus dem Session-Tracker.", + "icon": "🏋", + "category": "Planung", + "version": "2.0.0", + "entrypoint": "backend:create_app", + "order": 10, + "requires": [], + "contract": { + "plan_schema": 3, + "training_contract": 2, + "result_schema": 2 + }, + "features": [ + "Entwurf und veröffentlichte Planrevision sind getrennt", + "Stabile IDs für Phasen, Tage, Rotationen, Übungen und Progressionsstufen", + "Progressionsstufen als Objekte mit Ergebnisschema, Cluster und Faktor", + "Planbezogene Übungsbibliothek für den Tracker", + "Validierung von IDs, Schemata und festen Intervallen", + "Tracker-Vorschau inklusive Tabata-Durchgängen", + "Atomisches Speichern mit Backups und Revisionskonfliktschutz", + "Strukturiertes Prüfpostfach für KI-Planvorschläge", + "Import, Export und PDF-Erzeugung", + "Rezeptsammlungen bleiben unterstützt" + ], + "docs": "TOOL-README.md" +} diff --git a/plugins/trainingsplan/presets/couchsnacks.json b/plugins/trainingsplan/presets/couchsnacks.json new file mode 100644 index 0000000..e6eeb48 --- /dev/null +++ b/plugins/trainingsplan/presets/couchsnacks.json @@ -0,0 +1,1607 @@ +{ + "name": "Couch-Snacks (Abend)", + "config": { + "type": "recipe", + "meta": { + "title": "Couch-Snacks für den Abend", + "header_left": "COUCH-SNACKS · Abend-Edition", + "header_right": "Begleitend zum 8-Wochen-Plan", + "footer_left": "Teller = Portionsgrenze · Tüten in den Schrank · Trend über die Woche zählt", + "output_name": "Couch-Snacks_Abend-Edition.pdf", + "doc_title": "Couch-Snacks – Abend-Edition (100 Rezepte)", + "brand_color": "#6D28D9", + "tip_label": "Abend-Tipp", + "steps_label": "SO GEHT'S" + }, + "intro": { + "headline": "Couch-Snacks für den Abend", + "subtitle": "100 Rezepte für vor dem Fernseher & vorm Schlafengehen – inkl. Airfryer-Rezepten und Orientierungs-Nährwerten, viele mit Würz- und Geschmacks-Variationen. Nicht verzichten, sondern clever ersetzen.", + "paragraphs": [ + "Diese Snacks lösen dein Couch-Problem: Sie befriedigen die Lust auf Süßes oder Knabbern, machen aber satt statt nur den Mund zu beschäftigen. Der Hebel ist nicht „gesund oder ungesund“, sondern die Portion und der Teller. Eiweiß (Quark, Skyr, Hüttenkäse, Eier, Edamame) hält bis zum Schlaf satt. Wo ein Rezept einen VARIATIONEN-Block hat, ist das dieselbe Basis in mehreren Geschmacksrichtungen. Rezepte mit AIRFRYER-Badge funktionieren im Heißluftfritteuse. Der NÄHRWERTE-Streifen liefert Orientierungswerte (ca.-Werte für die angegebene Menge) – keine Zähl-Pflicht." + ], + "rule_head": "Die eine Grundregel", + "rule_text": "Teller fertig machen → Tüten und Beutel zu → ab in den Schrank. Der Teller ist deine Portionsgrenze – nicht dein Sättigungsgefühl.", + "categories_head": "13 Kategorien" + }, + "categories": [ + { + "name": "Cremig & süß · Löffelsnacks", + "color": "#7c3aed" + }, + { + "name": "Aus dem Tiefkühler · Eis- & Süßigkeiten-Ersatz", + "color": "#0ea5e9" + }, + { + "name": "Knusprig & herzhaft · Chip-Ersatz", + "color": "#ea580c" + }, + { + "name": "Frisch & zum Knabbern", + "color": "#16a34a" + }, + { + "name": "Herzhaft & sättigend · Mini-Mahlzeiten", + "color": "#dc2626" + }, + { + "name": "Süße Häppchen & Energy Bites", + "color": "#db2777" + }, + { + "name": "Warm & gemütlich · vorm Schlafen", + "color": "#b45309" + }, + { + "name": "Airfryer · Knusprig & herzhaft", + "color": "#4338ca" + }, + { + "name": "Airfryer · Süß & Gebäck", + "color": "#2563eb" + }, + { + "name": "Mehr herzhafte Mini-Mahlzeiten", + "color": "#475569" + }, + { + "name": "Mehr süße Löffel- & Obst-Snacks", + "color": "#a21caf" + }, + { + "name": "Mehr Bowls & Frisches", + "color": "#059669" + }, + { + "name": "Mehr warme Snacks & Getränke", + "color": "#92400e" + } + ], + "recipes": [ + { + "num": "01", + "category": "Cremig & süß · Löffelsnacks", + "title": "Schoko-Quark-Mousse", + "tagline": "Schoko-Pudding-Gefühl, das wirklich satt macht.", + "nutrition": "ca. ≈ 190 kcal · Eiweiß 25 g · Fett 4 g · KH 15 g · Zucker 13 g · Ballaststoffe 2 g", + "ingredients": "200 g Magerquark · 1 TL Backkakao · etwas Süße · optional Schuss Milch", + "steps": "Quark mit Kakao und Süße glattrühren, bei Bedarf Milch unter, bis es cremig-dunkel ist.", + "methods": "", + "variations": [ + "Erdnuss-Schoko: + 1 TL Erdnussmus", + "Vanille-Beere: Vanille statt Kakao + TK-Beeren", + "Mokka: + Schuss kalter Espresso", + "Zimt: Zimt statt Kakao" + ], + "tip": "Abend-Tipp Eiweiß-reich, Schoko-Kick ohne ganze Tafel.", + "badge": "", + "yield": "" + }, + { + "num": "02", + "category": "Cremig & süß · Löffelsnacks", + "title": "Skyr-Beeren-Bowl mit Crunch", + "tagline": "Dessert-Optik, Knusper inklusive.", + "nutrition": "ca. ≈ 160 kcal · Eiweiß 20 g · Fett 1 g · KH 17 g · Zucker 11 g · Ballaststoffe 2 g", + "ingredients": "150–200 g Skyr · Handvoll (TK-)Beeren · 1 zerbröselte Mais-/Linsenwaffel · Prise Zimt", + "steps": "Skyr in die Schale, Beeren druntermischen, Waffelbrösel und Zimt obendrauf.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Die Waffel liefert den Crunch ohne Müsli-Zuckerladung.", + "badge": "", + "yield": "" + }, + { + "num": "03", + "category": "Cremig & süß · Löffelsnacks", + "title": "Protein-Tiramisu im Glas", + "tagline": "Schmeckt nach Dessert, ist Eiweiß-Quark.", + "nutrition": "ca. ≈ 205 kcal · Eiweiß 26 g · Fett 3 g · KH 19 g · Zucker 14 g · Ballaststoffe 1 g", + "ingredients": "200 g Magerquark/Skyr · Schuss Milch · etwas Süße · kalter Espresso · 1 Vollkornkeks · Kakao", + "steps": "Quark cremig rühren. Keks in Espresso tunken, ins Glas, Quark drauf, mit Kakao bestäuben.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Tiramisu-Gefühl ohne Mascarpone-Sahne-Bombe.", + "badge": "", + "yield": "" + }, + { + "num": "04", + "category": "Cremig & süß · Löffelsnacks", + "title": "Chia-Pudding", + "tagline": "Abends vorbereiten, am Couch-Abend fertig.", + "nutrition": "ca. ≈ 255 kcal · Eiweiß 14 g · Fett 13 g · KH 16 g · Zucker 10 g · Ballaststoffe 10 g", + "ingredients": "3 EL Chiasamen · 200 ml Milch/Pflanzendrink · etwas Süße", + "steps": "Verrühren, 10 Min stehen, nochmal rühren, min. 1 Std (oder über Nacht) kühlen.", + "methods": "", + "variations": [ + "Schoko: + 1 TL Kakao", + "Beere: TK-Beeren untermischen", + "Vanille-Kokos: Vanille + Schuss Kokosmilch", + "Erdnuss: + 1 TL Erdnussmus" + ], + "tip": "Abend-Tipp Quillt im Magen und macht erstaunlich satt.", + "badge": "", + "yield": "" + }, + { + "num": "05", + "category": "Cremig & süß · Löffelsnacks", + "title": "Apfel-Zimt-Quark", + "tagline": "„Apfelkuchen-Gefühl“ ohne Teig.", + "nutrition": "ca. ≈ 240 kcal · Eiweiß 26 g · Fett 7 g · KH 31 g · Zucker 26 g · Ballaststoffe 5 g", + "ingredients": "200 g Magerquark · 1 geraspelter Apfel · Zimt · optional 1 TL Honig, paar Walnüsse", + "steps": "Apfel raspeln, mit Quark, Zimt und Süße verrühren, Nüsse drauf.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Süß und sättigend – der Apfel bringt Volumen statt Zucker.", + "badge": "", + "yield": "" + }, + { + "num": "06", + "category": "Cremig & süß · Löffelsnacks", + "title": "Griech. Joghurt mit Honig & Walnuss", + "tagline": "Cremig + Crunch + süß in 30 Sekunden.", + "nutrition": "ca. ≈ 285 kcal · Eiweiß 10 g · Fett 22 g · KH 12 g · Zucker 11 g · Ballaststoffe 1 g", + "ingredients": "150 g griech. Joghurt oder Skyr · 1 TL Honig · paar Walnüsse · Zimt", + "steps": "Joghurt in die Schale, Honig, Walnüsse und Zimt darüber.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Honig sparsam – ein Teelöffel reicht für den süßen Ton.", + "badge": "", + "yield": "" + }, + { + "num": "07", + "category": "Cremig & süß · Löffelsnacks", + "title": "Mango-Quark-Bowl", + "tagline": "Tropisch-cremig, ohne Zuckerzusatz.", + "nutrition": "ca. ≈ 170 kcal · Eiweiß 24 g · Fett 1 g · KH 16 g · Zucker 15 g · Ballaststoffe 1 g", + "ingredients": "200 g Magerquark/Skyr · paar (TK-)Mangostücke · Spritzer Limette", + "steps": "Mango antauen, pürieren oder klein, unter den Quark, Limette drüber.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Die Limette bremst die Süße-Gier und frischt auf.", + "badge": "", + "yield": "" + }, + { + "num": "08", + "category": "Cremig & süß · Löffelsnacks", + "title": "Beeren-„Eton-Mess“ light", + "tagline": "Sieht aufwändig aus, ist in 2 Min fertig.", + "nutrition": "ca. ≈ 145 kcal · Eiweiß 18 g · Fett 1 g · KH 16 g · Zucker 10 g · Ballaststoffe 1 g", + "ingredients": "150 g Skyr · Handvoll Beeren · 1 zerbröselte Reiswaffel (oder Baiser-Splitter)", + "steps": "Beeren leicht zerdrücken, locker (nicht ganz) unter den Skyr, Knusper drüber.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Knusper + Beere + Eiweiß als schnelles Dessert.", + "badge": "", + "yield": "" + }, + { + "num": "09", + "category": "Aus dem Tiefkühler · Eis- & Süßigkeiten-Ersatz", + "title": "Frozen Yogurt Bark", + "tagline": "Selbstgemachte Joghurt-Schokolade zum Brechen.", + "nutrition": "ca. ≈ 235 kcal · Eiweiß 28 g · Fett 4 g · KH 19 g · Zucker 15 g · Ballaststoffe 3 g · ganzer Ansatz", + "ingredients": "250 g Skyr/Joghurt · etwas Süße · Beeren · optional Kakao-Nibs/Nüsse", + "steps": "Joghurt auf Backpapier dünn streichen, Toppings reindrücken, 2–3 Std einfrieren, in Stücke brechen.", + "methods": "", + "variations": [ + "Beere pur", + "Schoko-Nuss: Kakao-Nibs + gehackte Nüsse", + "PB&J: Klecks Erdnussmus + Beeren marmoriert", + "Mango-Kokos: Mangostücke + Kokosraspel" + ], + "tip": "Abend-Tipp Einmal machen, ganze Woche naschen – der Süßigkeiten-Ersatz schlechthin.", + "badge": "", + "yield": "" + }, + { + "num": "10", + "category": "Aus dem Tiefkühler · Eis- & Süßigkeiten-Ersatz", + "title": "Gefrorene Trauben & Beeren", + "tagline": "Dein Gummibärchen-Ersatz.", + "nutrition": "ca. ≈ 70 kcal · Eiweiß 1 g · Fett 0 g · KH 17 g · Zucker 16 g · Ballaststoffe 1 g", + "ingredients": "Weintrauben und/oder Beeren", + "steps": "Gewaschen ins Gefrierfach, ein paar Stunden. Direkt aus dem Tiefkühler naschen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Kalt, süß, leicht zäh – beschäftigt den Mund wie Gummibären, ist aber nur Obst.", + "badge": "", + "yield": "" + }, + { + "num": "11", + "category": "Aus dem Tiefkühler · Eis- & Süßigkeiten-Ersatz", + "title": "Joghurt-Beeren-Eis am Stiel", + "tagline": "Selbstgemachtes Eis ohne Zuckerbombe.", + "nutrition": "ca. ≈ 200 kcal · Eiweiß 28 g · Fett 1 g · KH 22 g · Zucker 20 g · Ballaststoffe 2 g · ganzer Ansatz", + "ingredients": "250 g Skyr/Joghurt · Beeren · etwas Süße · Eisförmchen", + "steps": "Joghurt mit Beeren und Süße mischen, in Förmchen füllen, einfrieren.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Immer im TK parat, wenn der Eis-Reflex kommt.", + "badge": "", + "yield": "" + }, + { + "num": "12", + "category": "Aus dem Tiefkühler · Eis- & Süßigkeiten-Ersatz", + "title": "Zartbitter-Bananen-Bites", + "tagline": "Knack-Schokoschicht + kaltes Obst.", + "nutrition": "ca. ≈ 285 kcal · Eiweiß 4 g · Fett 14 g · KH 36 g · Zucker 18 g · Ballaststoffe 6 g", + "ingredients": "1 Banane in Scheiben · 30 g Zartbitter (geschmolzen) · optional gehackte Nüsse", + "steps": "Bananenscheiben kurz in geschmolzene Schoko tunken, auf Backpapier, Nüsse drauf, einfrieren.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Ein, zwei Stück reichen – wenig Schoko, viel Effekt.", + "badge": "", + "yield": "" + }, + { + "num": "13", + "category": "Aus dem Tiefkühler · Eis- & Süßigkeiten-Ersatz", + "title": "Frozen Joghurt-Drops", + "tagline": "Mini-Häppchen statt großer Portion.", + "nutrition": "ca. ≈ 110 kcal · Eiweiß 17 g · Fett 1 g · KH 10 g · Zucker 9 g · Ballaststoffe 0 g · ganzer Ansatz", + "ingredients": "Skyr/Joghurt · etwas Süße", + "steps": "Kleine Joghurt-Kleckse aufs Backpapier setzen, einfrieren, aus dem TK naschen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Befriedigt das „immer-wieder-greifen“, ohne reinzuhauen.", + "badge": "", + "yield": "" + }, + { + "num": "14", + "category": "Aus dem Tiefkühler · Eis- & Süßigkeiten-Ersatz", + "title": "Gefrorene Mango-/Melonen-Sticks", + "tagline": "Fast wie Sorbet am Stiel.", + "nutrition": "ca. ≈ 60 kcal · Eiweiß 1 g · Fett 0 g · KH 15 g · Zucker 14 g · Ballaststoffe 2 g", + "ingredients": "Mango oder Melone in Sticks", + "steps": "In Sticks schneiden, einfrieren, direkt naschen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Erfrischend süß, null Zusatz.", + "badge": "", + "yield": "" + }, + { + "num": "15", + "category": "Knusprig & herzhaft · Chip-Ersatz", + "title": "Würzige geröstete Kichererbsen", + "tagline": "Knuspern wie Chips, ohne Tüte.", + "nutrition": "ca. ≈ 330 kcal · Eiweiß 15 g · Fett 9 g · KH 41 g · Zucker 1 g · Ballaststoffe 12 g · ganzer Ansatz", + "ingredients": "1 Dose Kichererbsen · 1 TL Öl · Gewürz nach Wahl (siehe Variationen)", + "steps": "Abtropfen, gut trocken tupfen, mit Öl und Gewürz mischen.", + "methods": "Airfryer: 190 °C, 12–15 Min, 2× schütteln · Ofen: 200 °C, 25–30 Min", + "variations": [ + "Paprika-rauchig: geräuch. Paprika + Salz", + "Knoblauch-Kräuter: Knoblauchgranulat + Oregano/Thymian", + "Curry: Currypulver + Prise Salz", + "Chili-Limette: Chili + Limettenabrieb", + "„Käsig“: Hefeflocken + Salz", + "Süß: Zimt + Prise Süße (nach dem Rösten)" + ], + "tip": "Abend-Tipp Eiweiß + Ballaststoffe statt leerem Fett. Vorrat hält ein paar Tage.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "16", + "category": "Knusprig & herzhaft · Chip-Ersatz", + "title": "Edamame mit Meersalz & Chili", + "tagline": "Pulen beschäftigt – das bremst von allein.", + "nutrition": "ca. ≈ 95 kcal · Eiweiß 9 g · Fett 4 g · KH 8 g · Zucker 2 g · Ballaststoffe 5 g", + "ingredients": "TK-Edamame in der Schote · Meersalz · Chiliflocken", + "steps": "Nach Packung dämpfen/Mikrowelle, würzen, aus der Schote pulen.", + "methods": "", + "variations": [ + "Knoblauch-Soja: Sojasauce + Knoblauch", + "Sesam: geröstete Sesamkörner + Salz", + "Limette-Chili: Limettensaft + Chiliflocken" + ], + "tip": "Abend-Tipp Proteinreich, und das Pulen verlangsamt das Essen.", + "badge": "", + "yield": "" + }, + { + "num": "17", + "category": "Knusprig & herzhaft · Chip-Ersatz", + "title": "Herzhaftes Popcorn (luftgepoppt)", + "tagline": "Kino-Gefühl ohne Butter-Bombe.", + "nutrition": "ca. ≈ 150 kcal · Eiweiß 4 g · Fett 5 g · KH 22 g · Zucker 0 g · Ballaststoffe 4 g", + "ingredients": "30 g Mais-Kerner · optional 1 TL Öl · Gewürz statt Zucker/viel Salz", + "steps": "Im Topf mit Deckel oder Heißluft-Popper poppen, Gewürz oder Hefeflocken drüber.", + "methods": "", + "variations": [ + "Paprika", + "„Käse“: Hefeflocken", + "Knoblauch-Kräuter", + "Curry", + "Zimt-süß (sparsam)", + "Chili-Schoko: Chili + Kakao-Nibs" + ], + "tip": "Abend-Tipp Riesen-Volumen für wenig – füllt die Schüssel optisch.", + "badge": "", + "yield": "" + }, + { + "num": "18", + "category": "Knusprig & herzhaft · Chip-Ersatz", + "title": "Knäcke-Brett", + "tagline": "Salzig-frisch zum Draufbeißen.", + "nutrition": "ca. ≈ 155 kcal · Eiweiß 10 g · Fett 3 g · KH 23 g · Zucker 2 g · Ballaststoffe 5 g", + "ingredients": "2–3 Knäckebrote/Maiswaffeln · Belag nach Wahl", + "steps": "Knäcke bestreichen, belegen, würzen.", + "methods": "", + "variations": [ + "Hüttenkäse + Gurke", + "Tomate-Mozzarella + Basilikum", + "Avocado + hartes Ei + Pfeffer", + "Frischkäse + Räucherlachs", + "Hummus + Paprikastreifen" + ], + "tip": "Abend-Tipp Knusper + Eiweiß + Frische, sättigt mehr als die Chipstüte.", + "badge": "", + "yield": "" + }, + { + "num": "19", + "category": "Knusprig & herzhaft · Chip-Ersatz", + "title": "Geröstete Nüsse, würzig", + "tagline": "Sättigend – bewusst eine Handvoll.", + "nutrition": "ca. ≈ 150 kcal · Eiweiß 4 g · Fett 14 g · KH 4 g · Zucker 1 g · Ballaststoffe 2 g", + "ingredients": "1 Handvoll gemischte Nüsse · Prise Öl · Gewürz", + "steps": "Nüsse (optional mit einem Hauch Öl) mit Gewürz mischen. Alternativ in der Pfanne ohne Öl rösten.", + "methods": "Airfryer: 160 °C, 5–7 Min, schütteln (verbrennen schnell – im Blick behalten) · Ofen: 160 °C, 8–10 Min", + "variations": [ + "Rosmarin-Salz", + "Chili-Honig (Spur Honig)", + "Curry", + "Zimt-süß" + ], + "tip": "Abend-Tipp Kaloriendicht – eine Handvoll als klare Portion.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "20", + "category": "Knusprig & herzhaft · Chip-Ersatz", + "title": "Gemüsechips", + "tagline": "Echter Chips-Ersatz aus Gemüse.", + "nutrition": "ca. ≈ 90 kcal · Eiweiß 2 g · Fett 5 g · KH 9 g · Zucker 5 g · Ballaststoffe 3 g", + "ingredients": "Zucchini, Karotte oder Rote Bete dünn gehobelt · wenig Öl · Salz", + "steps": "Hauchdünn hobeln, trocken tupfen, mit wenig Öl und Salz mischen.", + "methods": "Airfryer: 160 °C, 12–18 Min, zwischendurch schütteln · Ofen: 150 °C, 30–40 Min", + "variations": [ + "Paprika", + "Rosmarin", + "Knoblauch", + "Meersalz pur" + ], + "tip": "Abend-Tipp Dünn = knusprig. Geht auch als „Mix-Teller“.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "21", + "category": "Knusprig & herzhaft · Chip-Ersatz", + "title": "Parmesan-Crisps", + "tagline": "Knusprig, herzhaft, sehr sättigend.", + "nutrition": "ca. ≈ 120 kcal · Eiweiß 11 g · Fett 8 g · KH 1 g · Zucker 0 g · Ballaststoffe 0 g", + "ingredients": "geriebener Parmesan/Hartkäse", + "steps": "Kleine Häufchen auf Backpapier setzen (im Korb luftdurchlässig auslegen).", + "methods": "Airfryer: 180 °C, 4–6 Min bis golden · Ofen: 200 °C, 5–7 Min (oder Mikrowelle ~1 Min)", + "variations": [], + "tip": "Abend-Tipp Wenige Stück genügen – intensiv und proteinreich.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "22", + "category": "Knusprig & herzhaft · Chip-Ersatz", + "title": "Reiswaffel-„Nachos“", + "tagline": "Nacho-Gefühl ohne Chip-Berg.", + "nutrition": "ca. ≈ 140 kcal · Eiweiß 6 g · Fett 6 g · KH 14 g · Zucker 2 g · Ballaststoffe 1 g", + "ingredients": "2 Reiswaffeln · 1 EL Salsa/Tomate · etwas Käse · optional Jalapeño", + "steps": "Belegen, 1 Min in der Mikrowelle bis der Käse schmilzt.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Schnell, herzhaft, klar portioniert.", + "badge": "", + "yield": "" + }, + { + "num": "23", + "category": "Knusprig & herzhaft · Chip-Ersatz", + "title": "Herzhafter Knabber-Mix", + "tagline": "Der salzige Studentenfutter-Ersatz.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 11 g · Fett 14 g · KH 21 g · Zucker 2 g · Ballaststoffe 6 g", + "ingredients": "geröstete Kichererbsen + paar Nüsse + geröstete Sojabohnen/Edamame · optional Salzbrezel", + "steps": "Alles mischen, eine kleine Schale füllen (nicht die ganze Schüssel).", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Portion abfüllen, Rest in den Schrank – sonst läuft's wie die Chipstüte.", + "badge": "", + "yield": "" + }, + { + "num": "24", + "category": "Frisch & zum Knabbern", + "title": "Gemüsesticks mit Dip", + "tagline": "Knabbern, das du nicht bereuen kannst.", + "nutrition": "ca. ≈ 115 kcal · Eiweiß 13 g · Fett 1 g · KH 12 g · Zucker 6 g · Ballaststoffe 4 g", + "ingredients": "Paprika, Gurke, Karotte, Stangensellerie · Dip nach Wahl", + "steps": "Gemüse in Sticks schneiden, Dip anrühren, dippen.", + "methods": "", + "variations": [ + "Kräuterquark", + "Hummus", + "Avocado-Limette (Guacamole-light)", + "Curry-Joghurt", + "Tomate-Feta", + "Erdnuss-Soja" + ], + "tip": "Abend-Tipp Die freieste Option – beim Gemüse-Volumen darfst du großzügig sein.", + "badge": "", + "yield": "" + }, + { + "num": "25", + "category": "Frisch & zum Knabbern", + "title": "Spieße zum Knabbern", + "tagline": "Süß-herzhafter Mini-Apéro.", + "nutrition": "ca. ≈ 145 kcal · Eiweiß 8 g · Fett 8 g · KH 10 g · Zucker 8 g · Ballaststoffe 1 g", + "ingredients": "Zutaten nach Variation · Zahnstocher/Spieße", + "steps": "Abwechselnd aufspießen, auf einem Teller anrichten.", + "methods": "", + "variations": [ + "Käse-Trauben", + "Caprese: Tomate-Mozzarella-Basilikum", + "Melone-Feta", + "Gurke-Oliven-Feta", + "Apfel-Käse" + ], + "tip": "Abend-Tipp Die Spieße zwingen zur kleinen Menge – genau der Trick.", + "badge": "", + "yield": "" + }, + { + "num": "26", + "category": "Frisch & zum Knabbern", + "title": "Apfelscheiben mit Erdnussmus", + "tagline": "Süß-sättigend, schnell gemacht.", + "nutrition": "ca. ≈ 135 kcal · Eiweiß 3 g · Fett 4 g · KH 24 g · Zucker 18 g · Ballaststoffe 5 g", + "ingredients": "1 Apfel in Scheiben · 1 TL Erdnussmus", + "steps": "Apfel in Scheiben, dünn Erdnussmus drauf.", + "methods": "", + "variations": [ + "+ Zartbitter-Raspel", + "+ Zimt", + "+ gehackte Walnuss", + "+ paar Rosinen" + ], + "tip": "Abend-Tipp Erdnussmus sparsam – ein Teelöffel reicht.", + "badge": "", + "yield": "" + }, + { + "num": "27", + "category": "Frisch & zum Knabbern", + "title": "„Ants on a log“ – gefüllter Sellerie", + "tagline": "Knackig, kindlich-gut, kaum Kalorien.", + "nutrition": "ca. ≈ 90 kcal · Eiweiß 2 g · Fett 5 g · KH 9 g · Zucker 6 g · Ballaststoffe 2 g", + "ingredients": "Stangensellerie · Frischkäse oder Erdnussmus · ein paar Rosinen", + "steps": "Sellerie mit Frischkäse/Erdnussmus füllen, Rosinen drauf.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Mit Frischkäse-Variante besonders leicht.", + "badge": "", + "yield": "" + }, + { + "num": "28", + "category": "Frisch & zum Knabbern", + "title": "Gurken-Boote", + "tagline": "Proteinreich, frisch, sättigend für wenig.", + "nutrition": "ca. ≈ 145 kcal · Eiweiß 25 g · Fett 2 g · KH 8 g · Zucker 4 g · Ballaststoffe 2 g", + "ingredients": "1 Gurke längs halbiert & ausgehöhlt · Thunfisch + Quark (oder Frischkäse) · Pfeffer", + "steps": "Gurke aushöhlen, mit der Masse füllen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Viel Volumen, viel Eiweiß, wenig drumherum.", + "badge": "", + "yield": "" + }, + { + "num": "29", + "category": "Frisch & zum Knabbern", + "title": "Caprese-Häppchen", + "tagline": "Fühlt sich nach Apéro an, ist leicht.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 11 g · Fett 12 g · KH 8 g · Zucker 5 g · Ballaststoffe 1 g", + "ingredients": "Cherrytomaten · Mini-Mozzarella · Basilikum · Spritzer Balsamico", + "steps": "Aufspießen oder mischen, Balsamico drüber.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Frisch und herzhaft statt salzig-fettig.", + "badge": "", + "yield": "" + }, + { + "num": "30", + "category": "Frisch & zum Knabbern", + "title": "Paprika-Schiffchen mit Hüttenkäse", + "tagline": "Knackiges Löffel-zum-Mund-Snacking.", + "nutrition": "ca. ≈ 135 kcal · Eiweiß 14 g · Fett 5 g · KH 11 g · Zucker 8 g · Ballaststoffe 3 g", + "ingredients": "Paprika in Schiffchen · Hüttenkäse · Schnittlauch, Pfeffer", + "steps": "Paprika füllen oder mit gewürztem Hüttenkäse dippen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Süße Paprika + Eiweiß, sehr sättigend.", + "badge": "", + "yield": "" + }, + { + "num": "31", + "category": "Herzhaft & sättigend · Mini-Mahlzeiten", + "title": "Gekochte Eier, gewürzt", + "tagline": "Der unterschätzte Sattmacher.", + "nutrition": "ca. ≈ 155 kcal · Eiweiß 13 g · Fett 11 g · KH 1 g · Zucker 0 g · Ballaststoffe 0 g", + "ingredients": "1–2 hartgekochte Eier · Salz, Pfeffer, Paprika", + "steps": "Eier pellen, würzen.", + "methods": "", + "variations": [ + "+ Senf", + "+ Sriracha", + "+ Everything-Bagel-Gewürz", + "halbiert mit Avocado" + ], + "tip": "Abend-Tipp Eiweiß pur, kaum Aufwand – ideal bei echtem Hunger.", + "badge": "", + "yield": "" + }, + { + "num": "32", + "category": "Herzhaft & sättigend · Mini-Mahlzeiten", + "title": "Omelett-Muffins (vorbereitet)", + "tagline": "Sonntag machen, ganze Woche snacken.", + "nutrition": "ca. ≈ 325 kcal · Eiweiß 26 g · Fett 23 g · KH 5 g · Zucker 3 g · Ballaststoffe 1 g · ganzer Ansatz", + "ingredients": "3 Eier · Gemüse-Würfel · etwas Käse · Salz/Pfeffer · Muffinform", + "steps": "Eier verquirlen, Füllung in die Muffinform geben. Kalt oder warm genießen.", + "methods": "Airfryer: 160 °C, 10–12 Min · Ofen: 180 °C, 18 Min", + "variations": [ + "Spinat-Feta", + "Paprika-Zwiebel", + "Schinken-Käse", + "Pilz-Kräuter" + ], + "tip": "Abend-Tipp Eiweiß-Snack zum Vorbereiten – immer griffbereit.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "33", + "category": "Herzhaft & sättigend · Mini-Mahlzeiten", + "title": "Vollkorn-Toast belegt", + "tagline": "Eine Scheibe sättigt mehr als halbe Chipstüte.", + "nutrition": "ca. ≈ 150 kcal · Eiweiß 12 g · Fett 4 g · KH 18 g · Zucker 3 g · Ballaststoffe 4 g", + "ingredients": "1 Scheibe Vollkorntoast · Belag nach Wahl", + "steps": "Toasten, belegen.", + "methods": "", + "variations": [ + "Hüttenkäse + Tomate", + "Avocado + Ei", + "Frischkäse + Gurke", + "Magerquark + Schnittlauch", + "Erdnussmus + Banane (süß)" + ], + "tip": "Abend-Tipp Bewusst eine Scheibe – das ist die Portion.", + "badge": "", + "yield": "" + }, + { + "num": "34", + "category": "Herzhaft & sättigend · Mini-Mahlzeiten", + "title": "Herzhafter Quark-Dip mit Gemüse", + "tagline": "Großes Volumen, viel Eiweiß.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 25 g · Fett 1 g · KH 16 g · Zucker 9 g · Ballaststoffe 4 g", + "ingredients": "200 g Magerquark · Schnittlauch, Salz, Pfeffer, Spritzer Wasser · Gemüsesticks", + "steps": "Quark cremig würzen, mit Gemüse dippen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Sättigt stark bei wenig drumherum.", + "badge": "", + "yield": "" + }, + { + "num": "35", + "category": "Herzhaft & sättigend · Mini-Mahlzeiten", + "title": "Thunfisch-Quark-Aufstrich auf Knäcke", + "tagline": "Sehr sättigend, eiweißstark.", + "nutrition": "ca. ≈ 210 kcal · Eiweiß 33 g · Fett 2 g · KH 15 g · Zucker 2 g · Ballaststoffe 3 g", + "ingredients": "1 Dose Thunfisch (im Wasser) · 2 EL Magerquark · Zitrone, Pfeffer · Knäckebrot", + "steps": "Thunfisch mit Quark und Zitrone verrühren, auf Knäcke streichen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Gute Wahl, wenn der Hunger echt ist.", + "badge": "", + "yield": "" + }, + { + "num": "36", + "category": "Herzhaft & sättigend · Mini-Mahlzeiten", + "title": "Mini-Wrap-Röllchen", + "tagline": "Fingerfood-Optik, ordentlich Eiweiß.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 15 g · Fett 9 g · KH 25 g · Zucker 3 g · Ballaststoffe 3 g", + "ingredients": "1 Vollkorn-Tortilla · Frischkäse · Putenbrust/Schinken · Gurke/Paprika", + "steps": "Bestreichen, belegen, fest rollen, in Scheiben schneiden.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Leicht zu portionieren – Röllchen abzählen.", + "badge": "", + "yield": "" + }, + { + "num": "37", + "category": "Herzhaft & sättigend · Mini-Mahlzeiten", + "title": "Hüttenkäse-Bowl", + "tagline": "Frisch und proteinreich, fast Mini-Mahlzeit.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 20 g · Fett 8 g · KH 8 g · Zucker 5 g · Ballaststoffe 2 g", + "ingredients": "150 g Hüttenkäse · Cherrytomaten · Gurke · Pfeffer · optional Leinöl", + "steps": "Alles in eine Schale, würzen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Sättigt lange und ist schnell zusammengeworfen.", + "badge": "", + "yield": "" + }, + { + "num": "38", + "category": "Herzhaft & sättigend · Mini-Mahlzeiten", + "title": "Edamame-„Hummus“ mit Gemüse", + "tagline": "Eiweißreicher Dip-Twist.", + "nutrition": "ca. ≈ 270 kcal · Eiweiß 19 g · Fett 14 g · KH 20 g · Zucker 4 g · Ballaststoffe 9 g", + "ingredients": "150 g Edamame · 1 TL Tahin/Öl · Zitrone, Knoblauch, Salz · Gemüsesticks", + "steps": "Edamame mit den Zutaten pürieren, mit Gemüse dippen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Sättigt besser als Chips & Salsa.", + "badge": "", + "yield": "" + }, + { + "num": "39", + "category": "Süße Häppchen & Energy Bites", + "title": "Energy Balls", + "tagline": "1–2 Stück als idealer Süß-Stopper.", + "nutrition": "ca. ≈ 645 kcal · Eiweiß 28 g · Fett 22 g · KH 90 g · Zucker 20 g · Ballaststoffe 13 g · ganzer Ansatz", + "ingredients": "100 g Haferflocken · 2 EL Erdnussmus · 4 entkernte Datteln (o. 1 EL Honig) · Schuss Milch · optional 1 EL Proteinpulver", + "steps": "Alles zu einer Masse mixen/kneten, Kugeln formen, kalt stellen.", + "methods": "", + "variations": [ + "Schoko: + Kakao/Schoko-Drops", + "Kokos: in Kokosraspel wälzen", + "Cranberry-Nuss", + "Zimt-Apfel: + getrocknete Apfelstücke" + ], + "tip": "Abend-Tipp Vorrat im Kühlschrank – greifbar, wenn die Lust kommt.", + "badge": "", + "yield": "" + }, + { + "num": "40", + "category": "Süße Häppchen & Energy Bites", + "title": "„Snickers“-Datteln", + "tagline": "Schmeckt nach Riegel, ist Dattel + Nuss.", + "nutrition": "ca. ≈ 290 kcal · Eiweiß 6 g · Fett 17 g · KH 28 g · Zucker 20 g · Ballaststoffe 4 g · ganzer Ansatz", + "ingredients": "entkernte Datteln · Erdnussmus · 20 g Zartbitter (geschmolzen) · optional Meersalz", + "steps": "Datteln aufklappen, mit Erdnussmus füllen, in Schoko tunken, kühlen/gefrieren, Salz drauf.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Riegel-Gefühl mit nur ein bisschen Zartbitter.", + "badge": "", + "yield": "" + }, + { + "num": "41", + "category": "Süße Häppchen & Energy Bites", + "title": "Schoko-Obst (Erdbeeren/Banane)", + "tagline": "Edler Nachtisch-Effekt mit wenig Schoko.", + "nutrition": "ca. ≈ 210 kcal · Eiweiß 4 g · Fett 14 g · KH 20 g · Zucker 12 g · Ballaststoffe 4 g", + "ingredients": "Erdbeeren oder Bananenstücke · 30 g Zartbitterschokolade", + "steps": "Schoko schmelzen, Obst tunken, auf Backpapier fest werden lassen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Obst trägt die Süße, die Schoko ist nur Hülle.", + "badge": "", + "yield": "" + }, + { + "num": "42", + "category": "Süße Häppchen & Energy Bites", + "title": "Mini-Bananen-Hafer-Kekse", + "tagline": "2 Zutaten, kein Zuckerzusatz nötig.", + "nutrition": "ca. ≈ 290 kcal · Eiweiß 8 g · Fett 4 g · KH 60 g · Zucker 15 g · Ballaststoffe 8 g · ganzer Ansatz", + "ingredients": "1 reife Banane · 5 EL Haferflocken · optional Extras", + "steps": "Banane zerdrücken, mit Haferflocken (+ Extras) mischen, kleine Häufchen formen.", + "methods": "Airfryer: 160 °C, 8–10 Min · Ofen: 180 °C, 12 Min", + "variations": [ + "+ Zartbitter-Drops", + "+ Rosinen", + "+ Zimt", + "+ gehackte Nuss" + ], + "tip": "Abend-Tipp Frisch gebacken am besten – warme Mini-Kekse.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "43", + "category": "Süße Häppchen & Energy Bites", + "title": "Kokoskugeln (Quark-Kokos)", + "tagline": "„Raffaello-Gefühl“ mit Eiweiß-Basis.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 19 g · Fett 13 g · KH 12 g · Zucker 8 g · Ballaststoffe 3 g · ganzer Ansatz", + "ingredients": "150 g Magerquark · Kokosraspel · etwas Süße · Vanille", + "steps": "Quark mit Kokos und Süße zu fester Masse, Kugeln formen, in Kokos wälzen, kühlen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Cremig-kokosig, ohne Sahne-Füllung.", + "badge": "", + "yield": "" + }, + { + "num": "44", + "category": "Süße Häppchen & Energy Bites", + "title": "Dattel-Walnuss-Bissen", + "tagline": "Eine Zutat-Kombi, natürlich süß.", + "nutrition": "ca. ≈ 120 kcal · Eiweiß 2 g · Fett 6 g · KH 17 g · Zucker 14 g · Ballaststoffe 2 g", + "ingredients": "entkernte Datteln · je 1 Walnusshälfte", + "steps": "Datteln aufklappen, Walnuss reinsetzen, zuklappen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Knackig + süß, schnell satt – Datteln sind zuckerintensiv, also wenige.", + "badge": "", + "yield": "" + }, + { + "num": "45", + "category": "Warm & gemütlich · vorm Schlafen", + "title": "Warmer Protein-Kakao", + "tagline": "Beruhigt und sättigt zugleich.", + "nutrition": "ca. ≈ 110 kcal · Eiweiß 9 g · Fett 2 g · KH 15 g · Zucker 13 g · Ballaststoffe 1 g", + "ingredients": "250 ml Magermilch (o. ungesüßte Pflanzenmilch) · 1 TL Backkakao · Prise Zimt · etwas Süße", + "steps": "Milch erhitzen, Kakao und Zimt einrühren, leicht süßen, langsam trinken.", + "methods": "", + "variations": [ + "Goldene Milch: Kurkuma + Prise Pfeffer statt Kakao", + "Chai: Zimt-Kardamom-Ingwer", + "Mandel-Zimt", + "„White“: ohne Kakao, mit Vanille" + ], + "tip": "Abend-Tipp Warmes Getränk + Eiweiß statt Cola vor dem Schlaf.", + "badge": "", + "yield": "" + }, + { + "num": "46", + "category": "Warm & gemütlich · vorm Schlafen", + "title": "Haferflocken-Mug-Cake", + "tagline": "„Kuchen“ in der Tasse, in 2 Minuten.", + "nutrition": "ca. ≈ 230 kcal · Eiweiß 12 g · Fett 8 g · KH 26 g · Zucker 12 g · Ballaststoffe 4 g", + "ingredients": "3 EL Haferflocken · 1 Ei (o. 30 g Quark) · Prise Backpulver · 1 TL Kakao · etwas Süße · Schuss Milch", + "steps": "Alles in einer Tasse verrühren, 1,5–2 Min in der Mikrowelle, kurz abkühlen.", + "methods": "", + "variations": [ + "Schoko", + "Apfel-Zimt", + "Beere", + "Erdnuss-Banane", + "Carrot-Cake: geraspelte Karotte + Zimt" + ], + "tip": "Abend-Tipp Hafer + Eiweiß statt Mehl + Zucker. Warm am besten.", + "badge": "", + "yield": "" + }, + { + "num": "47", + "category": "Warm & gemütlich · vorm Schlafen", + "title": "Warmes Beeren-Kompott mit Quark", + "tagline": "Warm-kalt-Kontrast wie Dessert.", + "nutrition": "ca. ≈ 155 kcal · Eiweiß 19 g · Fett 1 g · KH 16 g · Zucker 12 g · Ballaststoffe 3 g", + "ingredients": "Handvoll TK-Beeren · Spur Süße · 150 g Magerquark", + "steps": "Beeren in Mikrowelle/Topf warm machen, über kalten Quark geben.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Beeren = wenig Zucker, viel Geschmack.", + "badge": "", + "yield": "" + }, + { + "num": "48", + "category": "Warm & gemütlich · vorm Schlafen", + "title": "Porridge / Overnight Oats klein", + "tagline": "Hält ruhig satt bis zum Schlaf.", + "nutrition": "ca. ≈ 230 kcal · Eiweiß 10 g · Fett 6 g · KH 32 g · Zucker 12 g · Ballaststoffe 4 g", + "ingredients": "4 EL Haferflocken · 150 ml Milch · etwas Süße", + "steps": "Warm: 2 Min köcheln/Mikrowelle. Kalt: über Nacht quellen lassen.", + "methods": "", + "variations": [ + "Schoko", + "Apfel-Zimt", + "Beere", + "Banane-Erdnuss" + ], + "tip": "Abend-Tipp Kleine Portion abends ist okay – Hafer + Milch beruhigen.", + "badge": "", + "yield": "" + }, + { + "num": "49", + "category": "Warm & gemütlich · vorm Schlafen", + "title": "Warme Milch mit Honig & Zimt", + "tagline": "Ruhiger Absacker statt Cola.", + "nutrition": "ca. ≈ 140 kcal · Eiweiß 9 g · Fett 4 g · KH 18 g · Zucker 17 g · Ballaststoffe 0 g", + "ingredients": "250 ml Milch · 1 TL Honig · Zimt", + "steps": "Milch erwärmen, Honig und Zimt einrühren.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Honig sparsam – ein Teelöffel reicht.", + "badge": "", + "yield": "" + }, + { + "num": "50", + "category": "Warm & gemütlich · vorm Schlafen", + "title": "Warmer Milchreis / Grießbrei (klein)", + "tagline": "Cremiger Kindheits-Comfort.", + "nutrition": "ca. ≈ 215 kcal · Eiweiß 11 g · Fett 4 g · KH 34 g · Zucker 15 g · Ballaststoffe 1 g", + "ingredients": "30 g Milchreis oder Grieß · 200 ml Milch · etwas Süße · Zimt", + "steps": "In Milch nach Packung weich köcheln, mit Zimt/Süße abschmecken.", + "methods": "", + "variations": [ + "Apfelmus", + "Beeren", + "Kakao", + "Zimt-Zucker (sparsam)" + ], + "tip": "Abend-Tipp Kleine Portion, warm, sättigend – ohne Riesenschüssel.", + "badge": "", + "yield": "" + }, + { + "num": "51", + "category": "Airfryer · Knusprig & herzhaft", + "title": "Airfryer-Tofu-Würfel", + "tagline": "Knuspriger Eiweiß-Snack zum Knabbern.", + "nutrition": "ca. ≈ 355 kcal · Eiweiß 32 g · Fett 20 g · KH 15 g · Zucker 1 g · Ballaststoffe 2 g", + "ingredients": "200 g fester Tofu (gewürfelt) · 1 TL Öl · 1 TL Speisestärke · Sojasauce · Gewürz", + "steps": "Tofu trocken pressen, mit Sojasauce, Stärke, Öl und Gewürz mischen.", + "methods": "Airfryer: 200 °C, 12–15 Min, 1× schütteln · Ofen: 220 °C, 25 Min, einmal wenden", + "variations": [ + "BBQ", + "Sweet-Chili", + "Curry", + "Sesam-Soja" + ], + "tip": "Abend-Tipp Außen knusprig, innen weich – sättigt wie ein Mini-Gericht.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "52", + "category": "Airfryer · Knusprig & herzhaft", + "title": "Airfryer-Edamame, knusprig", + "tagline": "Aus weich wird knusprig.", + "nutrition": "ca. ≈ 115 kcal · Eiweiß 9 g · Fett 6 g · KH 8 g · Zucker 2 g · Ballaststoffe 4 g", + "ingredients": "TK-Edamame (geschält) · Spritzer Öl · Salz, Chili", + "steps": "Edamame auftauen, gut trocken tupfen, mit Öl und Gewürz mischen.", + "methods": "Airfryer: 190 °C, 10–12 Min, schütteln · Ofen: 200 °C, 18–20 Min", + "variations": [], + "tip": "Abend-Tipp Proteinreicher Chip-Ersatz – knuspert wie geröstete Nüsse.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "53", + "category": "Airfryer · Knusprig & herzhaft", + "title": "Airfryer-Mini-Pizza-Toasts", + "tagline": "Pizza-Gefühl als Mini-Happen.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 9 g · Fett 7 g · KH 18 g · Zucker 3 g · Ballaststoffe 2 g", + "ingredients": "Vollkorn-Toast/Brot · 1 EL Tomatensauce · etwas Mozzarella/Käse · Oregano · optional Gemüse", + "steps": "Brot mit Sauce, Käse und Oregano belegen.", + "methods": "Airfryer: 180 °C, 5–7 Min · Ofen: 200 °C, 8–10 Min", + "variations": [], + "tip": "Abend-Tipp Herzhaft-warm; ein, zwei Stück statt halber Tiefkühlpizza.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "54", + "category": "Airfryer · Knusprig & herzhaft", + "title": "Airfryer-Brokkoli / Rosenkohl", + "tagline": "Geröstetes Gemüse mit Röstaromen.", + "nutrition": "ca. ≈ 120 kcal · Eiweiß 7 g · Fett 8 g · KH 8 g · Zucker 3 g · Ballaststoffe 4 g", + "ingredients": "Brokkoli-Röschen oder halbierter Rosenkohl · 1 TL Öl · Salz, Knoblauch, optional Parmesan", + "steps": "Gemüse mit Öl und Gewürz mischen.", + "methods": "Airfryer: 190 °C, 10–12 Min, schütteln · Ofen: 210 °C, 20–25 Min", + "variations": [], + "tip": "Abend-Tipp Knusprige Röschen – herzhaft genug, um Chips zu ersetzen.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "55", + "category": "Airfryer · Knusprig & herzhaft", + "title": "Airfryer-Blumenkohl-„Wings“", + "tagline": "„Wings“-Gefühl aus Gemüse.", + "nutrition": "ca. ≈ 90 kcal · Eiweiß 4 g · Fett 5 g · KH 9 g · Zucker 4 g · Ballaststoffe 4 g", + "ingredients": "Blumenkohl-Röschen · 1 TL Öl · Paprika, Knoblauch, Salz · optional scharfe Sauce", + "steps": "Röschen mit Öl und Gewürz mischen; nach dem Garen optional Sauce.", + "methods": "Airfryer: 190 °C, 12–15 Min, schütteln · Ofen: 220 °C, 25 Min", + "variations": [], + "tip": "Abend-Tipp Herzhaft-scharf, sättigend, leicht.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "56", + "category": "Airfryer · Knusprig & herzhaft", + "title": "Airfryer-Süßkartoffel-Pommes", + "tagline": "Knusprige Pommes ohne Fritteuse.", + "nutrition": "ca. ≈ 190 kcal · Eiweiß 3 g · Fett 5 g · KH 35 g · Zucker 6 g · Ballaststoffe 5 g", + "ingredients": "Süßkartoffel (Stifte) · 1 TL Öl · 1 TL Stärke · Salz, Paprika", + "steps": "Stifte mit Öl, Stärke und Gewürz mischen (Korb nicht überladen).", + "methods": "Airfryer: 190 °C, 15–18 Min, schütteln · Ofen: 220 °C, 25–30 Min", + "variations": [], + "tip": "Abend-Tipp Süß-salzig und knusprig; Portion bewusst abmessen.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "57", + "category": "Airfryer · Knusprig & herzhaft", + "title": "Airfryer-Apfelchips", + "tagline": "Süß-knusprig ganz ohne Zucker.", + "nutrition": "ca. ≈ 85 kcal · Eiweiß 1 g · Fett 0 g · KH 22 g · Zucker 18 g · Ballaststoffe 4 g", + "ingredients": "1 Apfel (hauchdünn) · Zimt", + "steps": "Apfel sehr dünn hobeln, trocken tupfen, mit Zimt bestreuen.", + "methods": "Airfryer: 150 °C, 12–16 Min, zwischendurch wenden · Ofen: 120 °C, 1,5–2 Std", + "variations": [], + "tip": "Abend-Tipp Dein Chips-Ersatz für die süße Schiene.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "58", + "category": "Airfryer · Knusprig & herzhaft", + "title": "Airfryer-Tortilla-Chips", + "tagline": "Selbstgemachte Nacho-Basis.", + "nutrition": "ca. ≈ 170 kcal · Eiweiß 5 g · Fett 6 g · KH 24 g · Zucker 2 g · Ballaststoffe 3 g", + "ingredients": "Vollkorn-Tortilla (in Dreiecke) · Spritzer Öl · Salz/Paprika", + "steps": "Dreiecke leicht ölen und würzen.", + "methods": "Airfryer: 180 °C, 5–7 Min · Ofen: 200 °C, 8–10 Min", + "variations": [], + "tip": "Abend-Tipp Mit Salsa oder Skyr-Dip ein herzhafter Teller.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "59", + "category": "Airfryer · Knusprig & herzhaft", + "title": "Airfryer-Knusper-Erbsen", + "tagline": "Grüne Erbsen als Knabber-Kugeln.", + "nutrition": "ca. ≈ 100 kcal · Eiweiß 5 g · Fett 3 g · KH 14 g · Zucker 5 g · Ballaststoffe 5 g", + "ingredients": "TK-Erbsen · Spritzer Öl · Salz, Paprika", + "steps": "Erbsen auftauen, gut trocken tupfen, mit Öl und Gewürz mischen.", + "methods": "Airfryer: 190 °C, 10–14 Min, schütteln · Ofen: 200 °C, 20 Min", + "variations": [], + "tip": "Abend-Tipp Klein, knusprig, proteinreich – überraschend gut.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "60", + "category": "Airfryer · Knusprig & herzhaft", + "title": "Airfryer-Croutons", + "tagline": "Herzhaft-knusprig zum Knabbern oder Dippen.", + "nutrition": "ca. ≈ 130 kcal · Eiweiß 4 g · Fett 5 g · KH 16 g · Zucker 1 g · Ballaststoffe 3 g", + "ingredients": "Altbackenes Vollkornbrot (Würfel) · 1 TL Öl · Knoblauch, Kräuter, Salz", + "steps": "Würfel mit Öl und Gewürz mischen.", + "methods": "Airfryer: 180 °C, 5–7 Min, schütteln · Ofen: 200 °C, 8–10 Min", + "variations": [], + "tip": "Abend-Tipp Pur knabbern oder über den Quark-Dip streuen.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "61", + "category": "Airfryer · Knusprig & herzhaft", + "title": "Airfryer-Hähnchen-„Popcorn“", + "tagline": "Der Eiweiß-Snack bei echtem Hunger.", + "nutrition": "ca. ≈ 200 kcal · Eiweiß 28 g · Fett 6 g · KH 8 g · Zucker 0 g · Ballaststoffe 1 g", + "ingredients": "Hähnchenbrust (Würfel) · 1 TL Öl · Paprika, Salz · optional Vollkorn-Paniermehl", + "steps": "Würfel würzen, optional in Paniermehl wälzen.", + "methods": "Airfryer: 200 °C, 10–12 Min, schütteln · Ofen: 220 °C, 18–20 Min", + "variations": [], + "tip": "Abend-Tipp Sehr sättigend; mit Joghurt-Dip ein kleines Gericht.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "62", + "category": "Airfryer · Knusprig & herzhaft", + "title": "Airfryer-Falafel", + "tagline": "Herzhaft-sättigender Kichererbsen-Happen.", + "nutrition": "ca. ≈ 260 kcal · Eiweiß 8 g · Fett 14 g · KH 25 g · Zucker 2 g · Ballaststoffe 5 g", + "ingredients": "TK-Falafel (oder selbst: Kichererbsen + Kräuter + Knoblauch zu Bällchen)", + "steps": "Fertige Falafel in den Korb; selbstgemacht zu kleinen Bällchen formen.", + "methods": "Airfryer: 190 °C, 10–12 Min, wenden · Ofen: 200 °C, 18–20 Min", + "variations": [], + "tip": "Abend-Tipp Mit Joghurt-Dip und Gemüse ein vollwertiger Teller.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "63", + "category": "Airfryer · Knusprig & herzhaft", + "title": "Airfryer-gefüllte Champignons", + "tagline": "Warmer, feiner Couch-Happen.", + "nutrition": "ca. ≈ 145 kcal · Eiweiß 6 g · Fett 11 g · KH 5 g · Zucker 3 g · Ballaststoffe 2 g", + "ingredients": "Champignons · Frischkäse/Hüttenkäse · Kräuter · optional etwas Parmesan", + "steps": "Stiele entfernen, Köpfe mit der Käse-Kräuter-Masse füllen.", + "methods": "Airfryer: 180 °C, 8–10 Min · Ofen: 200 °C, 15 Min", + "variations": [], + "tip": "Abend-Tipp Herzhaft und leicht – kaum Kalorien, viel Geschmack.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "64", + "category": "Airfryer · Knusprig & herzhaft", + "title": "Airfryer-paniertes Gemüse", + "tagline": "Knuspriges Gemüse statt Chips.", + "nutrition": "ca. ≈ 225 kcal · Eiweiß 15 g · Fett 11 g · KH 16 g · Zucker 4 g · Ballaststoffe 3 g", + "ingredients": "Zucchini-/Paprika-Sticks · 1 Ei · Vollkorn-Paniermehl + Parmesan", + "steps": "Sticks durch Ei ziehen, in der Panade wälzen.", + "methods": "Airfryer: 200 °C, 8–10 Min · Ofen: 220 °C, 15–18 Min", + "variations": [], + "tip": "Abend-Tipp Knusprig-paniert, herzhaft, mit Eiweiß vom Ei.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "65", + "category": "Airfryer · Süß & Gebäck", + "title": "Airfryer-Bratapfel", + "tagline": "Gemütlich, aber schneller als im Ofen.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 2 g · Fett 7 g · KH 30 g · Zucker 25 g · Ballaststoffe 5 g", + "ingredients": "1 Apfel (entkernt) · Zimt · gehackte Nüsse · 1 TL Rosinen", + "steps": "Apfel entkernen, mit Nuss-Rosinen-Zimt-Füllung füllen.", + "methods": "Airfryer: 170 °C, 12–15 Min · Ofen: 180 °C, 20–25 Min", + "variations": [], + "tip": "Abend-Tipp Süß ohne Kuchenteig; mit Quark dazu noch sättigender.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "66", + "category": "Airfryer · Süß & Gebäck", + "title": "Airfryer-Bananenbrot-Muffin", + "tagline": "Saftiger Mini-Kuchen auf Hafer-Banane-Basis.", + "nutrition": "ca. ≈ 330 kcal · Eiweiß 12 g · Fett 9 g · KH 51 g · Zucker 15 g · Ballaststoffe 6 g · ganzer Ansatz", + "ingredients": "1 Banane · 1 Ei · 4 EL Haferflocken · Prise Backpulver · optional Extras · kleine Förmchen", + "steps": "Banane zerdrücken, alles verrühren, in Förmchen füllen.", + "methods": "Airfryer: 160 °C, 12–15 Min · Ofen: 180 °C, 18–20 Min", + "variations": [ + "Schoko", + "Walnuss", + "Heidelbeere", + "Erdnuss" + ], + "tip": "Abend-Tipp Mini-Kuchengefühl ohne Mehl und Zucker.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "67", + "category": "Airfryer · Süß & Gebäck", + "title": "Airfryer-Beeren-Crumble (Single)", + "tagline": "Warmer Crumble in der Single-Portion.", + "nutrition": "ca. ≈ 210 kcal · Eiweiß 5 g · Fett 7 g · KH 32 g · Zucker 14 g · Ballaststoffe 5 g", + "ingredients": "TK-Beeren · 3 EL Haferflocken + 1 TL Öl/Butter + Prise Süße (Streusel)", + "steps": "Beeren in eine Förmchen, Streusel-Mischung darüber.", + "methods": "Airfryer: 170 °C, 10–12 Min · Ofen: 190 °C, 18–20 Min", + "variations": [], + "tip": "Abend-Tipp Mit einem Klecks Quark ein vollwertiges Dessert.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "68", + "category": "Airfryer · Süß & Gebäck", + "title": "Airfryer-Quark-Bällchen", + "tagline": "„Quarkbällchen“ ohne Frittierfett.", + "nutrition": "ca. ≈ 305 kcal · Eiweiß 29 g · Fett 8 g · KH 27 g · Zucker 10 g · Ballaststoffe 3 g · ganzer Ansatz", + "ingredients": "Magerquark · 1 Ei · Haferflocken/Mehl · Prise Backpulver · etwas Süße", + "steps": "Zu festem Teig verrühren, kleine Bällchen formen.", + "methods": "Airfryer: 180 °C, 8–10 Min · Ofen: 190 °C, 15 Min", + "variations": [], + "tip": "Abend-Tipp Eiweißreicher Süß-Happen, warm am besten.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "69", + "category": "Airfryer · Süß & Gebäck", + "title": "Airfryer-French-Toast-Sticks", + "tagline": "Süßes Frühstücks-Feeling als Abend-Happen.", + "nutrition": "ca. ≈ 185 kcal · Eiweiß 12 g · Fett 7 g · KH 18 g · Zucker 3 g · Ballaststoffe 3 g", + "ingredients": "Vollkornbrot (in Sticks) · 1 Ei + Schuss Milch + Zimt", + "steps": "Sticks in der Eimasse wenden.", + "methods": "Airfryer: 180 °C, 6–8 Min, wenden · Ofen: 200 °C, 12 Min", + "variations": [], + "tip": "Abend-Tipp Eiweiß vom Ei macht's sättigend; ohne Sirup-Flut.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "70", + "category": "Airfryer · Süß & Gebäck", + "title": "Airfryer-Zimt-Apfel-Spalten", + "tagline": "Warme Zimtäpfel – wie Bratapfel, schneller.", + "nutrition": "ca. ≈ 85 kcal · Eiweiß 1 g · Fett 0 g · KH 22 g · Zucker 18 g · Ballaststoffe 4 g", + "ingredients": "Apfel (Spalten) · Zimt · Spritzer Zitrone", + "steps": "Spalten mit Zimt und Zitrone mischen.", + "methods": "Airfryer: 180 °C, 8–10 Min · Ofen: 200 °C, 15 Min", + "variations": [], + "tip": "Abend-Tipp Warm und süß ganz ohne Zuckerzusatz.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "71", + "category": "Airfryer · Süß & Gebäck", + "title": "Airfryer-Schoko-Bananen-Päckchen", + "tagline": "Warm-schokoladig, fast wie Dessert.", + "nutrition": "ca. ≈ 165 kcal · Eiweiß 2 g · Fett 5 g · KH 30 g · Zucker 15 g · Ballaststoffe 4 g", + "ingredients": "Banane (längs) · 10 g Zartbitter-Raspel · Backpapier", + "steps": "Banane einschneiden, Schoko-Raspel reinstecken, locker in Backpapier wickeln.", + "methods": "Airfryer: 180 °C, 6–8 Min · Ofen: 200 °C, 10–12 Min", + "variations": [], + "tip": "Abend-Tipp Geschmolzene Schoko in warmer Banane – wenig reicht.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "72", + "category": "Airfryer · Süß & Gebäck", + "title": "Airfryer-gebackene Birne", + "tagline": "Saftig-süß mit Röstnote.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 2 g · Fett 7 g · KH 30 g · Zucker 20 g · Ballaststoffe 4 g", + "ingredients": "1 Birne (halbiert, entkernt) · Zimt · paar Walnüsse · optional Spur Honig", + "steps": "Birne halbieren, mit Zimt und Nüssen belegen.", + "methods": "Airfryer: 170 °C, 10–12 Min · Ofen: 190 °C, 18–20 Min", + "variations": [], + "tip": "Abend-Tipp Warme Birne als feiner, leichter Nachtisch.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "73", + "category": "Mehr herzhafte Mini-Mahlzeiten", + "title": "Reiswaffel mit Avocado & Ei", + "tagline": "Cremig + Eiweiß, sättigt für wenig.", + "nutrition": "ca. ≈ 230 kcal · Eiweiß 9 g · Fett 16 g · KH 14 g · Zucker 1 g · Ballaststoffe 5 g", + "ingredients": "Reiswaffel · ½ Avocado · 1 hartes Ei · Chili, Salz", + "steps": "Avocado zerdrücken, auf die Waffel, Ei in Scheiben darauf.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Herzhafter Mini-Snack mit gutem Sattmacher-Mix.", + "badge": "", + "yield": "" + }, + { + "num": "74", + "category": "Mehr herzhafte Mini-Mahlzeiten", + "title": "Skyr-Tzatziki mit Gemüse", + "tagline": "Frisch, proteinreich, großes Volumen.", + "nutrition": "ca. ≈ 145 kcal · Eiweiß 18 g · Fett 1 g · KH 13 g · Zucker 6 g · Ballaststoffe 3 g", + "ingredients": "150 g Skyr · ½ Gurke (geraspelt, ausgedrückt) · Knoblauch, Salz, Dill · Gemüsesticks", + "steps": "Skyr mit Gurke, Knoblauch und Dill verrühren, mit Gemüse dippen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Viel Dip-Volumen bei wenig drumherum.", + "badge": "", + "yield": "" + }, + { + "num": "75", + "category": "Mehr herzhafte Mini-Mahlzeiten", + "title": "Linsen-Bohnen-Salat (Mini)", + "tagline": "Proteinreich, hält lange satt.", + "nutrition": "ca. ≈ 240 kcal · Eiweiß 13 g · Fett 6 g · KH 30 g · Zucker 5 g · Ballaststoffe 9 g", + "ingredients": "Gekochte Linsen/Bohnen (Dose) · Tomate, Gurke, Zwiebel · Zitrone, Öl, Salz", + "steps": "Alles klein schneiden und mischen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Sättigt deutlich – gut bei echtem Abendhunger.", + "badge": "", + "yield": "" + }, + { + "num": "76", + "category": "Mehr herzhafte Mini-Mahlzeiten", + "title": "Hüttenkäse pikant auf Gurke", + "tagline": "Mini-Häppchen, viel Eiweiß.", + "nutrition": "ca. ≈ 115 kcal · Eiweiß 14 g · Fett 5 g · KH 6 g · Zucker 4 g · Ballaststoffe 1 g", + "ingredients": "Hüttenkäse · Gurkenscheiben · Paprikapulver, Schnittlauch, Pfeffer", + "steps": "Hüttenkäse würzen, auf Gurkenscheiben setzen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Knackig-frisch und sehr leicht.", + "badge": "", + "yield": "" + }, + { + "num": "77", + "category": "Mehr herzhafte Mini-Mahlzeiten", + "title": "Airfryer-Mozzarella-Sticks (light)", + "tagline": "„Frittiertes“ Gefühl mit wenig Fett.", + "nutrition": "ca. ≈ 255 kcal · Eiweiß 20 g · Fett 12 g · KH 16 g · Zucker 2 g · Ballaststoffe 1 g", + "ingredients": "Light-Mozzarella/Käse-Sticks · 1 Ei · Vollkorn-Paniermehl", + "steps": "Sticks panieren, kurz anfrieren (hält die Form).", + "methods": "Airfryer: 200 °C, 6–7 Min · Ofen: 220 °C, 8–10 Min", + "variations": [], + "tip": "Abend-Tipp Klein halten – als Highlight, nicht als Hauptmenge.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "78", + "category": "Mehr herzhafte Mini-Mahlzeiten", + "title": "Pute-Frischkäse-Röllchen", + "tagline": "Low-Carb Eiweiß-Röllchen.", + "nutrition": "ca. ≈ 145 kcal · Eiweiß 15 g · Fett 8 g · KH 3 g · Zucker 2 g · Ballaststoffe 1 g", + "ingredients": "Putenbrust-/Schinkenscheiben · Frischkäse · Gurkenstreifen", + "steps": "Scheiben bestreichen, Gurke einrollen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Schnell, herzhaft, klar portionierbar.", + "badge": "", + "yield": "" + }, + { + "num": "79", + "category": "Mehr herzhafte Mini-Mahlzeiten", + "title": "Airfryer-Frittata-Cup", + "tagline": "Warmer Eiweiß-Snack aus dem Korb.", + "nutrition": "ca. ≈ 245 kcal · Eiweiß 18 g · Fett 17 g · KH 4 g · Zucker 3 g · Ballaststoffe 1 g", + "ingredients": "2 Eier · Gemüse-Würfel · etwas Käse · Salz/Pfeffer · ofenfeste Tasse", + "steps": "Eier verquirlen, mit Füllung in die Tasse geben.", + "methods": "Airfryer: 160 °C, 10–12 Min · Ofen: 180 °C, 18 Min", + "variations": [], + "tip": "Abend-Tipp Frisch und warm – ideal nach dem Training.", + "badge": "AIRFRYER", + "yield": "" + }, + { + "num": "80", + "category": "Mehr herzhafte Mini-Mahlzeiten", + "title": "Avocado-Ei-Boot", + "tagline": "Cremig, herzhaft, sättigend.", + "nutrition": "ca. ≈ 190 kcal · Eiweiß 8 g · Fett 16 g · KH 6 g · Zucker 1 g · Ballaststoffe 5 g", + "ingredients": "½ Avocado · 1 hartes Ei · Salz, Pfeffer, Chili", + "steps": "Avocado-Mulde nutzen, Ei klein schneiden, hineingeben, würzen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Gute Fette + Eiweiß; bewusst eine halbe Avocado.", + "badge": "", + "yield": "" + }, + { + "num": "81", + "category": "Mehr süße Löffel- & Obst-Snacks", + "title": "Skyr-Schoko-Crunch", + "tagline": "Schoko-Stückchen im cremigen Skyr.", + "nutrition": "ca. ≈ 145 kcal · Eiweiß 18 g · Fett 3 g · KH 11 g · Zucker 9 g · Ballaststoffe 1 g", + "ingredients": "150 g Skyr · 1 TL Kakao oder Süße · 5 g Zartbitter (gehackt)", + "steps": "Skyr (optional mit Kakao) cremig rühren, Schoko-Stücke unterheben.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Cremig + knackig – Schoko als Akzent, nicht als Tafel.", + "badge": "", + "yield": "" + }, + { + "num": "82", + "category": "Mehr süße Löffel- & Obst-Snacks", + "title": "Erdnuss-Schoko-Joghurt", + "tagline": "Wie ein Dessert, mit Eiweiß-Basis.", + "nutrition": "ca. ≈ 170 kcal · Eiweiß 19 g · Fett 5 g · KH 12 g · Zucker 9 g · Ballaststoffe 2 g", + "ingredients": "150 g Joghurt/Skyr · 1 TL Erdnussmus · 1 TL Kakao · etwas Süße", + "steps": "Alles verrühren, bis cremig.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp „Reese’s“-Gefühl ohne Riegel.", + "badge": "", + "yield": "" + }, + { + "num": "83", + "category": "Mehr süße Löffel- & Obst-Snacks", + "title": "Beeren-Chia-„Marmelade“ auf Quark", + "tagline": "Schnelle Marmelade ohne Zucker.", + "nutrition": "ca. ≈ 150 kcal · Eiweiß 19 g · Fett 2 g · KH 12 g · Zucker 9 g · Ballaststoffe 3 g", + "ingredients": "Handvoll TK-Beeren · 1 TL Chiasamen · 150 g Magerquark", + "steps": "Beeren warm zerdrücken, Chia unter, 5–10 Min andicken, über den Quark.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Fruchtig-süß auf Eiweiß-Basis, wenig Zucker.", + "badge": "", + "yield": "" + }, + { + "num": "84", + "category": "Mehr süße Löffel- & Obst-Snacks", + "title": "Magerquark mit Zimt & Röst-Haferflocken", + "tagline": "Cremig + warmer Crunch.", + "nutrition": "ca. ≈ 225 kcal · Eiweiß 27 g · Fett 2 g · KH 24 g · Zucker 12 g · Ballaststoffe 3 g", + "ingredients": "200 g Magerquark · Zimt · 2 EL kurz angeröstete Haferflocken · etwas Süße", + "steps": "Haferflocken trocken anrösten, über den gewürzten Quark geben.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Sättigt cremig, der Röst-Crunch macht's besonders.", + "badge": "", + "yield": "" + }, + { + "num": "85", + "category": "Mehr süße Löffel- & Obst-Snacks", + "title": "Bananensplit light", + "tagline": "Eisdielen-Optik, leicht gemacht.", + "nutrition": "ca. ≈ 245 kcal · Eiweiß 19 g · Fett 3 g · KH 40 g · Zucker 22 g · Ballaststoffe 4 g", + "ingredients": "1 Banane (längs) · 150 g Skyr · Beeren · 5 g Zartbitter-Raspel", + "steps": "Banane längs in die Schale, Skyr drauf, Beeren und Raspel verteilen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Bananensplit-Gefühl ohne Eis und Sahne.", + "badge": "", + "yield": "" + }, + { + "num": "86", + "category": "Mehr süße Löffel- & Obst-Snacks", + "title": "Quark-„Käsekuchen“ im Glas", + "tagline": "Käsekuchen-Geschmack, kein Backen.", + "nutrition": "ca. ≈ 185 kcal · Eiweiß 25 g · Fett 2 g · KH 18 g · Zucker 13 g · Ballaststoffe 1 g", + "ingredients": "200 g Magerquark · Vanille · etwas Süße · 1 zerbröselter Vollkornkeks", + "steps": "Quark mit Vanille/Süße cremig rühren, ins Glas, Keksbrösel als Boden/Topping.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Dessert-Feeling mit Eiweiß statt Sahne-Boden.", + "badge": "", + "yield": "" + }, + { + "num": "87", + "category": "Mehr süße Löffel- & Obst-Snacks", + "title": "Dattel-Kakao-Creme mit Obst", + "tagline": "Natürlich süße Schoko-Creme zum Dippen.", + "nutrition": "ca. ≈ 160 kcal · Eiweiß 3 g · Fett 3 g · KH 34 g · Zucker 27 g · Ballaststoffe 5 g", + "ingredients": "3–4 entkernte Datteln · 1 TL Kakao · Schuss Milch · Obst zum Dippen", + "steps": "Datteln mit Kakao und Milch pürieren, Obst hineintunken.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Schoko-Dip ohne Zuckerzusatz; Datteln sind süß – wenig reicht.", + "badge": "", + "yield": "" + }, + { + "num": "88", + "category": "Mehr süße Löffel- & Obst-Snacks", + "title": "Skyr-„Eiskaffee“", + "tagline": "Löffelbarer Eiskaffee als Süß-Stopper.", + "nutrition": "ca. ≈ 115 kcal · Eiweiß 18 g · Fett 1 g · KH 10 g · Zucker 8 g · Ballaststoffe 1 g", + "ingredients": "150 g Skyr · 1 Schuss kalter Espresso · etwas Süße · Prise Kakao", + "steps": "Skyr mit Espresso und Süße verrühren, Kakao darüber.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Koffein-Kick + Eiweiß; abends ggf. entkoffeiniert.", + "badge": "", + "yield": "" + }, + { + "num": "89", + "category": "Mehr Bowls & Frisches", + "title": "Hüttenkäse-Obst-Bowl", + "tagline": "Herzhaft trifft süß.", + "nutrition": "ca. ≈ 190 kcal · Eiweiß 21 g · Fett 7 g · KH 15 g · Zucker 11 g · Ballaststoffe 2 g", + "ingredients": "150 g Hüttenkäse · Beeren oder Ananasstücke · Zimt", + "steps": "Hüttenkäse in die Schale, Obst und Zimt darüber.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Cremig-körnig + fruchtig, viel Eiweiß.", + "badge": "", + "yield": "" + }, + { + "num": "90", + "category": "Mehr Bowls & Frisches", + "title": "Skyr-Müsli-Bowl", + "tagline": "Knusper-Bowl ohne Zuckermüsli-Falle.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 25 g · Fett 2 g · KH 27 g · Zucker 15 g · Ballaststoffe 3 g", + "ingredients": "200 g Skyr · 2 EL Haferflocken/Müsli · Obst der Wahl", + "steps": "Skyr in die Schale, Haferflocken und Obst darauf.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Bewusst 2 EL Müsli – als Topping, nicht als Berg.", + "badge": "", + "yield": "" + }, + { + "num": "91", + "category": "Mehr Bowls & Frisches", + "title": "Quark-Bowl „PB&J“", + "tagline": "Erdnuss-Beere – die Wohlfühl-Kombi.", + "nutrition": "ca. ≈ 205 kcal · Eiweiß 26 g · Fett 5 g · KH 15 g · Zucker 9 g · Ballaststoffe 3 g", + "ingredients": "200 g Magerquark · 1 TL Erdnussmus · Handvoll Beeren", + "steps": "Quark in die Schale, Erdnussmus und Beeren einswirbeln.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Sättigend und süß; Erdnussmus sparsam.", + "badge": "", + "yield": "" + }, + { + "num": "92", + "category": "Mehr Bowls & Frisches", + "title": "Beeren-Smoothie-Bowl (dick)", + "tagline": "Dick und löffelbar statt zum Trinken.", + "nutrition": "ca. ≈ 175 kcal · Eiweiß 19 g · Fett 3 g · KH 20 g · Zucker 13 g · Ballaststoffe 3 g", + "ingredients": "150 g Skyr · Handvoll TK-Beeren · Schuss Milch · Topping", + "steps": "Skyr mit gefrorenen Beeren dick pürieren, in die Schale, Topping drauf.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Eis-Bowl-Gefühl, proteinreich, ohne Zuckerzusatz.", + "badge": "", + "yield": "" + }, + { + "num": "93", + "category": "Mehr Bowls & Frisches", + "title": "Melonen-Feta-Minz-Schale", + "tagline": "Süß-salzig-frisch.", + "nutrition": "ca. ≈ 160 kcal · Eiweiß 6 g · Fett 8 g · KH 13 g · Zucker 12 g · Ballaststoffe 1 g", + "ingredients": "Melonenwürfel · etwas Feta · frische Minze", + "steps": "Melone in die Schale, Feta zerbröseln, Minze darüber.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Erfrischend, leicht, überraschend befriedigend.", + "badge": "", + "yield": "" + }, + { + "num": "94", + "category": "Mehr Bowls & Frisches", + "title": "Caprese-Bowl", + "tagline": "Herzhaft-frische Schale.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 14 g · Fett 18 g · KH 8 g · Zucker 5 g · Ballaststoffe 1 g", + "ingredients": "Cherrytomaten · Mini-Mozzarella · Basilikum · Spritzer Balsamico/Olivenöl", + "steps": "Alles in eine Schale, Basilikum und Balsamico darüber.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Apéro-Gefühl als leichte Schale.", + "badge": "", + "yield": "" + }, + { + "num": "95", + "category": "Mehr warme Snacks & Getränke", + "title": "Heißer Apfel-Zimt-Drink", + "tagline": "Warmer, gemütlicher Absacker.", + "nutrition": "ca. ≈ 115 kcal · Eiweiß 1 g · Fett 0 g · KH 28 g · Zucker 26 g · Ballaststoffe 0 g", + "ingredients": "250 ml naturtrüber Apfelsaft (oder Apfelstücke + Wasser) · Zimt · 1 Nelke", + "steps": "Saft mit Zimt und Nelke kurz erwärmen (nicht kochen), Nelke entfernen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Warm und süß-würzig statt Cola am Abend.", + "badge": "", + "yield": "" + }, + { + "num": "96", + "category": "Mehr warme Snacks & Getränke", + "title": "Matcha-Latte (warm)", + "tagline": "Sanfter, grüner Wachmacher.", + "nutrition": "ca. ≈ 140 kcal · Eiweiß 9 g · Fett 4 g · KH 16 g · Zucker 15 g · Ballaststoffe 1 g", + "ingredients": "1 TL Matcha · 250 ml heiße Milch/Pflanzendrink · etwas Süße", + "steps": "Matcha mit wenig heißem Wasser anrühren, mit warmer Milch aufgießen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Cremig-warm; abends mild dosieren (enthält Koffein).", + "badge": "", + "yield": "" + }, + { + "num": "97", + "category": "Mehr warme Snacks & Getränke", + "title": "Warmer Beeren-Punsch", + "tagline": "Fruchtig-warm, alkoholfrei.", + "nutrition": "ca. ≈ 50 kcal · Eiweiß 1 g · Fett 0 g · KH 12 g · Zucker 11 g · Ballaststoffe 2 g", + "ingredients": "Handvoll TK-Beeren · 250 ml Wasser · Zimt, Sternanis · etwas Süße", + "steps": "Beeren mit Wasser und Gewürzen kurz erwärmen, leicht süßen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Wärmt und schmeckt nach Winter, fast ohne Zucker.", + "badge": "", + "yield": "" + }, + { + "num": "98", + "category": "Mehr warme Snacks & Getränke", + "title": "Heiße Zitrone-Ingwer-Honig", + "tagline": "Klassiker zum Runterkommen.", + "nutrition": "ca. ≈ 30 kcal · Eiweiß 0 g · Fett 0 g · KH 8 g · Zucker 6 g · Ballaststoffe 0 g", + "ingredients": "Saft ½ Zitrone · paar Scheiben Ingwer · heißes Wasser · 1 TL Honig", + "steps": "Zitrone und Ingwer mit heißem Wasser aufgießen, Honig einrühren.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Beruhigend und wärmend; Honig sparsam.", + "badge": "", + "yield": "" + }, + { + "num": "99", + "category": "Mehr warme Snacks & Getränke", + "title": "Warmer Schoko-Bananen-„Pudding“", + "tagline": "Warm, schokoladig, sättigend.", + "nutrition": "ca. ≈ 210 kcal · Eiweiß 6 g · Fett 3 g · KH 40 g · Zucker 16 g · Ballaststoffe 6 g", + "ingredients": "1 reife Banane · 1 TL Kakao · 2 EL Haferflocken · Schuss Milch", + "steps": "Banane zerdrücken, mit Kakao, Haferflocken und Milch verrühren, 1–2 Min in der Mikrowelle.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Warmer Schoko-Pudding ganz ohne Zuckerzusatz.", + "badge": "", + "yield": "" + }, + { + "num": "100", + "category": "Mehr warme Snacks & Getränke", + "title": "Kräutertee + 1 Stück Zartbitter", + "tagline": "Das bewusste Absacker-Ritual.", + "nutrition": "ca. ≈ 45 kcal · Eiweiß 1 g · Fett 3 g · KH 3 g · Zucker 1 g · Ballaststoffe 1 g", + "ingredients": "1 Kräutertee · 1 Stück Zartbitterschokolade (85 %+)", + "steps": "Tee aufgießen, EIN Stück Schokolade ganz bewusst dazu genießen.", + "methods": "", + "variations": [], + "tip": "Abend-Tipp Das Ritual ersetzt das Tüten-Grasen: ein Stück, achtsam – Schluss.", + "badge": "", + "yield": "" + } + ] + } +} \ No newline at end of file diff --git a/plugins/trainingsplan/presets/fruehstueck.json b/plugins/trainingsplan/presets/fruehstueck.json new file mode 100644 index 0000000..a58d72e --- /dev/null +++ b/plugins/trainingsplan/presets/fruehstueck.json @@ -0,0 +1,1240 @@ +{ + "name": "Frühstück (Morgen)", + "config": { + "type": "recipe", + "meta": { + "title": "Frühstück für den Morgen", + "header_left": "FRÜHSTÜCK · Morgen-Edition · Begleitend zum 8-Wochen-Plan", + "footer_left": "Eiweiß zuerst · satt bis Mittag · Trend über die Woche zählt", + "output_name": "Fruehstueck_Morgen.pdf", + "doc_title": "Frühstück für den Morgen", + "brand_color": "#6D28D9", + "steps_label": "SO GEHT'S" + }, + "intro": { + "headline": "Frühstück für den Morgen", + "subtitle": "83 Rezepte plus zahlreiche Variationen – schnell, abends vorbereitbar oder warm.", + "paragraphs": [ + "Nährwerte pro Portion als Richtwert. Nicht auslassen, sondern satt starten.", + "Diese Rezepte lösen dein Morgen-Problem: Ein Frühstück mit Eiweiß macht satt bis zum Mittag und dämpft den", + "Vormittags-Zucker im Büro – die heimlichen Snacks fallen leichter weg, wenn du nicht schon um zehn unterzuckert", + "vor der Schublade stehst. Der Hebel ist nicht „gesund oder ungesund“, sondern genug Eiweiß und eine echte", + "Portion. Quark, Skyr, Hüttenkäse, Eier, Milch und Haferflocken liefern Baustoff für die Regeneration nach dem", + "Training und halten lange satt. Wo ein Rezept einen VARIATIONEN-Block hat, ist das dieselbe Basis in mehreren", + "Geschmacksrichtungen – einmal lernen, nie langweilig. Viele Rezepte lassen sich abends vorbereiten, damit", + "morgens keine Ausrede bleibt. Rezepte mit AIRFRYER-Badge nennen im Block ZUBEREITUNG Zeit und", + "Temperatur für Airfryer und Ofen (Werte je Gerät leicht unterschiedlich – beim ersten Mal kurz kontrollieren). Jeder", + "Eintrag nennt unter NÄHRWERTE ca. grobe Richtwerte pro Portion – Kalorien plus alle Makros (Eiweiß, Fett, KH,", + "Zucker, Ballaststoffe). Das sind Schätzwerte zur Orientierung, kein Zwang zum Zählen – Eiweiß zuerst und eine" + ], + "rule_head": "echte Portion bleiben die Regel.", + "rule_text": "Die eine Grundregel: Eiweiß zuerst Bau das Frühstück um eine Eiweißquelle (Quark, Skyr, Ei, Hüttenkäse, Milch) und füll mit Hafer, Obst oder Gemüse auf. Ein satter Vormittag ist der beste Schutz gegen den Griff in die Süßigkeiten-Schublade um zehn – nicht Disziplin, sondern schlicht kein Hunger.", + "categories_head": "12 Kategorien" + }, + "categories": [ + { + "name": "Quark & Skyr · Löffel-Frühstück", + "color": "#7C3AED" + }, + { + "name": "Overnight Oats · abends vorbereiten", + "color": "#0EA5E9" + }, + { + "name": "Warmer Porridge & Getreidebrei", + "color": "#EA580C" + }, + { + "name": "Eier · herzhaft & proteinreich", + "color": "#16A34A" + }, + { + "name": "Herzhaftes Brot & Belag", + "color": "#DC2626" + }, + { + "name": "Pfannkuchen, Waffeln & Protein-Backwerk", + "color": "#DB2777" + }, + { + "name": "Smoothies & Trink-Frühstück", + "color": "#B45309" + }, + { + "name": "Frisch & schnell · unter 5 Minuten", + "color": "#4338CA" + }, + { + "name": "Airfryer · warm & knusprig", + "color": "#2563EB" + }, + { + "name": "Mehr Löffel- & Joghurt-Frühstück", + "color": "#475569" + }, + { + "name": "Mehr herzhafte Frühstücke", + "color": "#A21CAF" + }, + { + "name": "Mehr warme & schnelle Frühstücke", + "color": "#059669" + } + ], + "recipes": [ + { + "num": "01", + "category": "Quark & Skyr · Löffel-Frühstück", + "title": "Quark- & Skyr-Bowl · Basis", + "tagline": "Kalt gerührt, in 3 Minuten fertig.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 30 g · Fett 6 g · KH 18 g · Zucker 10 g · Ballaststoffe 3 g", + "ingredients": "200 g Magerquark oder Skyr · Schuss Milch · 1 EL Haferflocken oder Nüsse · Topping nach Wahl", + "steps": "Quark/Skyr mit Milch cremig rühren, Topping unterheben oder drüberstreuen.", + "variations": [ + "Beeren-Crunch Beeren + 1 EL Hafer + 1 EL Nüsse", + "Schoko-Banane 1 TL Backkakao + 1/2 Banane", + "Erdnuss-Banane 1 EL Erdnussmus + 1/2 Banane", + "Kirsch-Mandel TK-Kirschen + 1 EL Mandeln", + "Kaffee-Proffee Schuss kalter Espresso + 1 TL Kakao", + "Als Parfait im Glas mit Beeren und Hafer schichten" + ], + "tip": "Morgen-Tipp Skyr oder Magerquark bringen 20–25 g Eiweiß pro Becher – der beste Sattmacher am Morgen.", + "badge": "", + "yield": "" + }, + { + "num": "02", + "category": "Quark & Skyr · Löffel-Frühstück", + "title": "Hüttenkäse-Becher süß", + "tagline": "Der unterschätzte Eiweiß-Löffel.", + "nutrition": "ca. ≈ 320 kcal · Eiweiß 28 g · Fett 13 g · KH 18 g · Zucker 14 g · Ballaststoffe 3 g", + "ingredients": "200 g Hüttenkäse · Obst nach Wahl · 1 EL Nüsse · optional 1 TL Honig", + "steps": "Hüttenkäse in die Schale, Obst und Nüsse drüber, dünn süßen.", + "variations": [ + "Beeren-Honig Beeren + 1 TL Honig + Walnüsse", + "Zimt-Apfel gewürfelter Apfel + Zimt + Rosinen", + "Ananas Ananasstücke – klassisch herzhaft-süß" + ], + "tip": "Morgen-Tipp Ohne Kochen in unter 3 Minuten fertig – ideal, wenn morgens keine Zeit ist.", + "badge": "", + "yield": "" + }, + { + "num": "03", + "category": "Quark & Skyr · Löffel-Frühstück", + "title": "Vanille-Skyr mit warmem Apfel", + "tagline": "Warmes Obst trifft kühlen Skyr.", + "nutrition": "ca. ≈ 280 kcal · Eiweiß 20 g · Fett 5 g · KH 38 g · Zucker 22 g · Ballaststoffe 4 g", + "ingredients": "150–200 g Skyr · 1 Apfel gewürfelt · Zimt · 1 EL Haferflocken · optional Nüsse", + "steps": "Apfel mit Zimt in der Pfanne weich dünsten, auf den kalten Skyr geben, Hafer drüber.", + "variations": [ + "Birne Birne statt Apfel", + "Warme Beeren TK-Beeren kurz erwärmen statt Apfel", + "Bratbanane Banane in der Pfanne karamellisieren" + ], + "tip": "Morgen-Tipp Gedünstetes Obst süßt von allein – der warm-kalt-Kontrast macht satt und zufrieden.", + "badge": "", + "yield": "" + }, + { + "num": "04", + "category": "Overnight Oats · abends vorbereiten", + "title": "Overnight Oats · Basis", + "tagline": "Abends 2 Minuten, morgens null Ausrede.", + "nutrition": "ca. ≈ 340 kcal · Eiweiß 24 g · Fett 8 g · KH 43 g · Zucker 12 g · Ballaststoffe 7 g", + "ingredients": "50 g Haferflocken · 150 ml Milch/Pflanzendrink · 100 g Skyr oder Quark · 1 TL Chiasamen", + "steps": "Alles im Glas verrühren, über Nacht kühlen; morgens Topping drauf.", + "variations": [ + "Schoko-Banane 1 TL Kakao + 1/2 zerdrückte Banane", + "Apfel-Zimt 1 geriebener Apfel + Zimt", + "Beeren-Vanille Handvoll TK-Beeren + Vanille", + "Erdnuss 1 EL Erdnussmus", + "Kaffee-Proffee Schuss kalter Espresso + Kakao", + "Kokos-Mango Mangostücke + 1 EL Kokosraspel", + "Karottenkuchen 1 geriebene Karotte + Zimt + Walnüsse + Rosinen" + ], + "tip": "Morgen-Tipp Skyr oder Quark mit rein hebt das Eiweiß deutlich – so hält das Glas bis Mittag.", + "badge": "", + "yield": "" + }, + { + "num": "05", + "category": "Overnight Oats · abends vorbereiten", + "title": "Bircher-Müsli", + "tagline": "Der Schweizer Ur-Klassiker mit frischem Apfel.", + "nutrition": "ca. ≈ 360 kcal · Eiweiß 12 g · Fett 10 g · KH 50 g · Zucker 22 g · Ballaststoffe 7 g", + "ingredients": "50 g Haferflocken · 150 ml Milch · 1 geriebener Apfel · 1 EL Zitronensaft · 1 EL Nüsse · optional Quark", + "steps": "Apfel reiben, sofort mit Zitrone mischen, mit Hafer und Milch über Nacht ziehen lassen.", + "variations": [ + "Beeren-Bircher zusätzlich Handvoll Beeren", + "Protein-Bircher 100 g Quark/Skyr unterrühren" + ], + "tip": "Morgen-Tipp Zitrone hält den Apfel hell und bringt Frische. Für mehr Eiweiß Quark dazu.", + "badge": "", + "yield": "" + }, + { + "num": "06", + "category": "Warmer Porridge & Getreidebrei", + "title": "Haferporridge · Basis", + "tagline": "Warm, günstig, endlos variierbar.", + "nutrition": "ca. ≈ 300 kcal · Eiweiß 15 g · Fett 8 g · KH 42 g · Zucker 12 g · Ballaststoffe 5 g", + "ingredients": "50 g Haferflocken · 250 ml Milch/Wasser · Prise Salz", + "steps": "Hafer mit Flüssigkeit 3–5 Min cremig köcheln; nach Wunsch toppen.", + "variations": [ + "Schoko-Protein 1 TL Kakao mitkochen, nach dem Kochen 1 Portion Skyr/Quark einrühren", + "Apfel-Zimt geriebenen Apfel + Zimt mitkochen", + "Beere TK-Beeren die letzten 2 Min mitkochen", + "Erdnuss-Banane 1 EL Erdnussmus + Bananenscheiben" + ], + "tip": "Morgen-Tipp Eine Prise Salz macht Haferbrei erst rund. Quark erst nach dem Kochen einrühren, sonst gerinnt er.", + "badge": "", + "yield": "" + }, + { + "num": "07", + "category": "Warmer Porridge & Getreidebrei", + "title": "Herzhafter Porridge mit Ei", + "tagline": "Wer's morgens nicht süß mag.", + "nutrition": "ca. ≈ 375 kcal · Eiweiß 21 g · Fett 18 g · KH 31 g · Zucker 2 g · Ballaststoffe 5 g", + "ingredients": "50 g Haferflocken · 250 ml Wasser/Brühe · 1 Ei · geriebener Käse · Pfeffer", + "steps": "Hafer in Brühe köcheln, ein Ei unterrühren, Käse drauf schmelzen lassen.", + "variations": [ + "Mit Lachs geräucherten Lachs + Schnittlauch", + "Mit Spinat eine Handvoll Blattspinat unterrühren" + ], + "tip": "Morgen-Tipp Herzhafte Oats sind unterschätzt und mega sättigend.", + "badge": "", + "yield": "" + }, + { + "num": "08", + "category": "Warmer Porridge & Getreidebrei", + "title": "Protein-Grießbrei", + "tagline": "Kindheit trifft Eiweiß.", + "nutrition": "ca. ≈ 330 kcal · Eiweiß 18 g · Fett 5 g · KH 41 g · Zucker 16 g · Ballaststoffe 3 g", + "ingredients": "3 EL Weichweizengrieß · 250 ml Milch · etwas Süße · optional 1 Portion Quark", + "steps": "Grieß in heiße Milch einrühren, kurz quellen lassen; Obst dazu.", + "variations": [ + "Beeren-Grieß TK-Beeren unterrühren", + "Schoko-Grieß 1 TL Kakao einrühren" + ], + "tip": "Morgen-Tipp Für mehr Eiweiß nach dem Quellen etwas Quark unterheben.", + "badge": "", + "yield": "" + }, + { + "num": "09", + "category": "Warmer Porridge & Getreidebrei", + "title": "Quinoa-Frühstücksbrei", + "tagline": "Glutenfreie Abwechslung.", + "nutrition": "ca. ≈ 350 kcal · Eiweiß 14 g · Fett 11 g · KH 46 g · Zucker 16 g · Ballaststoffe 6 g", + "ingredients": "50 g Quinoa · 200 ml Milch · Zimt · Beeren · 1 EL Nüsse", + "steps": "Quinoa gründlich spülen, in Milch 15 Min weich köcheln; toppen.", + "variations": [ + "Schoko-Quinoa 1 TL Kakao einrühren", + "Apfel-Zimt geriebenen Apfel + Zimt" + ], + "tip": "Morgen-Tipp Gleich die doppelte Menge kochen – hält 3 Tage im Kühlschrank.", + "badge": "", + "yield": "" + }, + { + "num": "10", + "category": "Warmer Porridge & Getreidebrei", + "title": "Hirse-Brei mit Beeren", + "tagline": "Mineralstoff-Booster.", + "nutrition": "ca. ≈ 330 kcal · Eiweiß 13 g · Fett 6 g · KH 52 g · Zucker 18 g · Ballaststoffe 4 g", + "ingredients": "50 g Hirse · 250 ml Milch · Zimt · Beeren · etwas Süße", + "steps": "Hirse spülen, in Milch ca. 20 Min weich köcheln, Beeren unterrühren.", + "variations": [ + "Mit Apfel gewürfelten Apfel mitkochen", + "Nuss-Honig 1 EL Nüsse + 1 TL Honig" + ], + "tip": "Morgen-Tipp Hirse liefert Eisen und Magnesium – gute Ergänzung zum Hafer.", + "badge": "", + "yield": "" + }, + { + "num": "11", + "category": "Warmer Porridge & Getreidebrei", + "title": "Couscous-Frühstück süß", + "tagline": "In 5 Minuten fertig, ohne Kochen.", + "nutrition": "ca. ≈ 360 kcal · Eiweiß 12 g · Fett 8 g · KH 58 g · Zucker 19 g · Ballaststoffe 5 g", + "ingredients": "50 g Couscous · 150 ml heiße Milch · Zimt · Apfel/Beeren · 1 EL Nüsse", + "steps": "Couscous mit heißer Milch übergießen, 5 Min ziehen lassen, auflockern, Obst drauf.", + "variations": [ + "Herzhaft mit Hüttenkäse + Tomate statt Obst", + "Schoko 1 TL Kakao + etwas Süße" + ], + "tip": "Morgen-Tipp Schnellste warme Getreide-Basis überhaupt – nur Quellen statt Kochen.", + "badge": "", + "yield": "" + }, + { + "num": "12", + "category": "Eier · herzhaft & proteinreich", + "title": "Rührei · Basis", + "tagline": "Der Eiweiß-Anker am Morgen.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 19 g · Fett 19 g · KH 2 g · Zucker 2 g · Ballaststoffe 0 g", + "ingredients": "2–3 Eier · Schuss Milch · Salz, Pfeffer · 1 TL Öl/Butter", + "steps": "Eier verquirlen, in der Pfanne bei mittlerer Hitze langsam stocken lassen, cremig ziehen.", + "variations": [ + "Käse gegen Ende geriebenen Käse einrühren", + "Kräuter Schnittlauch/Petersilie unter", + "Gemüse Paprika/Spinat mitbraten", + "Lachs Räucherlachs am Ende dazu" + ], + "tip": "Morgen-Tipp Niedrige Hitze und Geduld machen Rührei cremig statt trocken.", + "badge": "", + "yield": "" + }, + { + "num": "13", + "category": "Eier · herzhaft & proteinreich", + "title": "Spiegelei auf Vollkorntoast", + "tagline": "Klassiker mit Biss.", + "nutrition": "ca. ≈ 260 kcal · Eiweiß 16 g · Fett 13 g · KH 18 g · Zucker 2 g · Ballaststoffe 3 g", + "ingredients": "1–2 Eier · 1 Scheibe Vollkornbrot · Salz, Pfeffer · optional Tomate", + "steps": "Ei brutzeln, bis das Weiße fest und das Gelbe noch flüssig ist, auf Toast legen.", + "variations": [], + "tip": "Morgen-Tipp Vollkorn statt Weißbrot hält den Vormittag über satt.", + "badge": "", + "yield": "" + }, + { + "num": "14", + "category": "Eier · herzhaft & proteinreich", + "title": "Gemüse-Omelett", + "tagline": "Sattes Frühstück, das nach Mittag schmeckt.", + "nutrition": "ca. ≈ 360 kcal · Eiweiß 26 g · Fett 26 g · KH 6 g · Zucker 4 g · Ballaststoffe 2 g", + "ingredients": "3 Eier · Handvoll Gemüse (Paprika, Zwiebel, Spinat) · Käse · Öl", + "steps": "Gemüse anbraten, verquirlte Eier drüber, stocken lassen, zusammenklappen.", + "variations": [], + "tip": "Morgen-Tipp Reste-Gemüse vom Vortag landet perfekt im Omelett.", + "badge": "", + "yield": "" + }, + { + "num": "15", + "category": "Eier · herzhaft & proteinreich", + "title": "Shakshuka", + "tagline": "Eier in würziger Tomatensauce.", + "nutrition": "ca. ≈ 280 kcal · Eiweiß 18 g · Fett 15 g · KH 18 g · Zucker 10 g · Ballaststoffe 4 g", + "ingredients": "2 Eier · 1 Dose Tomaten · 1/2 Zwiebel · Paprika · Kreuzkümmel · Knoblauch", + "steps": "Zwiebel, Paprika, Tomaten einköcheln, würzen, Eier reinschlagen, zugedeckt stocken.", + "variations": [], + "tip": "Morgen-Tipp Am Wochenende, wenn Zeit ist – herzhaft und ohne viel Fett.", + "badge": "", + "yield": "" + }, + { + "num": "16", + "category": "Eier · herzhaft & proteinreich", + "title": "Ei auf Avocado-Toast", + "tagline": "Modern-Klassiker, richtig sättigend.", + "nutrition": "ca. ≈ 340 kcal · Eiweiß 13 g · Fett 23 g · KH 20 g · Zucker 3 g · Ballaststoffe 7 g", + "ingredients": "1 Scheibe Vollkornbrot · 1/2 Avocado · 1 Ei (pochiert/gekocht) · Chili, Salz", + "steps": "Avocado auf Toast zerdrücken, Ei drauf, würzen.", + "variations": [], + "tip": "Morgen-Tipp Gute Fette plus Eiweiß – hält lange, aber Avocado in Maßen.", + "badge": "", + "yield": "" + }, + { + "num": "17", + "category": "Eier · herzhaft & proteinreich", + "title": "Frühstücks-Wrap mit Ei", + "tagline": "Grab-and-go für unterwegs.", + "nutrition": "ca. ≈ 400 kcal · Eiweiß 24 g · Fett 22 g · KH 30 g · Zucker 3 g · Ballaststoffe 3 g", + "ingredients": "1 Vollkorn-Wrap · 2 Eier (Rührei) · Käse · Handvoll Gemüse/Spinat", + "steps": "Rührei zubereiten, mit Käse und Gemüse in den Wrap, einrollen.", + "variations": [], + "tip": "Morgen-Tipp Kalt genauso gut – abends machen, morgens mitnehmen.", + "badge": "", + "yield": "" + }, + { + "num": "18", + "category": "Eier · herzhaft & proteinreich", + "title": "Eiersalat-Brot", + "tagline": "Meal-Prep-Held der Woche.", + "nutrition": "ca. ≈ 300 kcal · Eiweiß 20 g · Fett 15 g · KH 20 g · Zucker 3 g · Ballaststoffe 3 g", + "ingredients": "2 gekochte Eier · 1 EL Skyr/Joghurt · Senf · Schnittlauch · Vollkornbrot", + "steps": "Eier zerdrücken, mit Skyr und Senf mischen, auf Brot streichen.", + "variations": [], + "tip": "Morgen-Tipp Skyr statt Mayo spart Fett und bringt Eiweiß.", + "badge": "", + "yield": "" + }, + { + "num": "19", + "category": "Eier · herzhaft & proteinreich", + "title": "Gekochte Eier + Brot", + "tagline": "Simpler geht Eiweiß nicht.", + "nutrition": "ca. ≈ 330 kcal · Eiweiß 18 g · Fett 14 g · KH 34 g · Zucker 3 g · Ballaststoffe 5 g", + "ingredients": "2 gekochte Eier · 1–2 Scheiben Vollkornbrot · Butter dünn · Salz", + "steps": "Eier am Wochenanfang kochen, morgens pellen, mit Brot essen.", + "variations": [], + "tip": "Morgen-Tipp 6 Eier auf Vorrat kochen – 3 Frühstücke in 10 Minuten erledigt.", + "badge": "", + "yield": "" + }, + { + "num": "20", + "category": "Eier · herzhaft & proteinreich", + "title": "Frittata-Muffins", + "tagline": "Vorbereiten, einfrieren, aufwärmen.", + "nutrition": "ca. ≈ 200 kcal · Eiweiß 15 g · Fett 14 g · KH 4 g · Zucker 3 g · Ballaststoffe 1 g", + "ingredients": "6 Eier · Handvoll Gemüse gewürfelt · Käse · Salz, Pfeffer · Muffinform", + "steps": "Eier verquirlen, mit Gemüse in die Form, 18–20 Min bei 180 °C backen.", + "variations": [], + "tip": "Morgen-Tipp Batch kochen: 6 Muffins = 3 Frühstücke, halten 4 Tage.", + "badge": "", + "yield": "" + }, + { + "num": "21", + "category": "Eier · herzhaft & proteinreich", + "title": "Herzhafte Ei-Haferflocken-Pfannkuchen", + "tagline": "Oats mal salzig.", + "nutrition": "ca. ≈ 370 kcal · Eiweiß 24 g · Fett 20 g · KH 26 g · Zucker 2 g · Ballaststoffe 4 g", + "ingredients": "40 g Haferflocken · 2 Eier · Käse · Kräuter · Salz", + "steps": "Alles verrühren, wie kleine Pfannkuchen in wenig Öl ausbacken.", + "variations": [], + "tip": "Morgen-Tipp Sättigt wie ein Mini-Gericht – ideal nach früher Session.", + "badge": "", + "yield": "" + }, + { + "num": "22", + "category": "Herzhaftes Brot & Belag", + "title": "Hüttenkäse-Brot", + "tagline": "Cremiger Eiweiß-Belag statt Wurst.", + "nutrition": "ca. ≈ 260 kcal · Eiweiß 18 g · Fett 6 g · KH 34 g · Zucker 4 g · Ballaststoffe 5 g", + "ingredients": "1–2 Scheiben Vollkornbrot · 100 g Hüttenkäse · Radieschen/Gurke · Pfeffer, Schnittlauch", + "steps": "Hüttenkäse aufs Brot, Gemüse drauf, würzen.", + "variations": [ + "Tomate-Basilikum Tomatenscheiben + Basilikum", + "Kresse Kresse + etwas Salz", + "Paprika Paprikastreifen + Paprikapulver" + ], + "tip": "Morgen-Tipp Hüttenkäse schlägt jeden Frischkäse beim Eiweiß.", + "badge": "", + "yield": "" + }, + { + "num": "23", + "category": "Herzhaftes Brot & Belag", + "title": "Avocado-Ei-Brot", + "tagline": "Sattmacher-Kombi.", + "nutrition": "ca. ≈ 340 kcal · Eiweiß 13 g · Fett 23 g · KH 20 g · Zucker 2 g · Ballaststoffe 7 g", + "ingredients": "1 Scheibe Vollkornbrot · 1/2 Avocado · 1 gekochtes Ei · Chili, Salz", + "steps": "Avocado zerdrücken, aufs Brot, Ei in Scheiben drauf, würzen.", + "variations": [], + "tip": "Morgen-Tipp Halbe Avocado reicht – ganz wird's schnell fettig.", + "badge": "", + "yield": "" + }, + { + "num": "24", + "category": "Herzhaftes Brot & Belag", + "title": "Lachs-Frischkäse-Brot", + "tagline": "Sonntags-Feeling, werktags machbar.", + "nutrition": "ca. ≈ 270 kcal · Eiweiß 16 g · Fett 14 g · KH 19 g · Zucker 2 g · Ballaststoffe 3 g", + "ingredients": "1 Scheibe Vollkornbrot · 1 EL Frischkäse · Räucherlachs · Zitrone, Dill", + "steps": "Frischkäse streichen, Lachs auflegen, Zitrone drüber.", + "variations": [], + "tip": "Morgen-Tipp Lachs liefert Eiweiß und Omega-3 – gute Abwechslung zum Ei.", + "badge": "", + "yield": "" + }, + { + "num": "25", + "category": "Herzhaftes Brot & Belag", + "title": "Kräuter-Frischkäse mit Tomate", + "tagline": "Frisch und schnell.", + "nutrition": "ca. ≈ 230 kcal · Eiweiß 12 g · Fett 5 g · KH 34 g · Zucker 5 g · Ballaststoffe 4 g", + "ingredients": "1–2 Scheiben Vollkornbrot · Kräuterquark/Frischkäse · Tomate · Pfeffer", + "steps": "Aufstrich streichen, Tomatenscheiben drauf, salzen und pfeffern.", + "variations": [], + "tip": "Morgen-Tipp Kräuterquark statt Frischkäse = mehr Eiweiß, weniger Fett.", + "badge": "", + "yield": "" + }, + { + "num": "26", + "category": "Herzhaftes Brot & Belag", + "title": "Harzer-Käse-Brot", + "tagline": "Der Eiweiß-König unter den Käsen.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 34 g · Fett 3 g · KH 20 g · Zucker 3 g · Ballaststoffe 3 g", + "ingredients": "1 Scheibe Vollkornbrot · 1 Harzer Käse · Zwiebel · Kümmel · Senf", + "steps": "Käse aufs Brot, Zwiebelringe und Kümmel drauf.", + "variations": [], + "tip": "Morgen-Tipp Harzer hat fast kein Fett und extrem viel Eiweiß – Geschmackssache, aber top.", + "badge": "", + "yield": "" + }, + { + "num": "27", + "category": "Herzhaftes Brot & Belag", + "title": "Thunfisch-Brot", + "tagline": "Herzhaft, günstig, proteinreich.", + "nutrition": "ca. ≈ 280 kcal · Eiweiß 34 g · Fett 4 g · KH 20 g · Zucker 3 g · Ballaststoffe 3 g", + "ingredients": "1 Dose Thunfisch (Wasser) · 1 EL Skyr/Joghurt · Zwiebel · Vollkornbrot", + "steps": "Thunfisch abtropfen, mit Skyr und Zwiebel mischen, aufs Brot.", + "variations": [], + "tip": "Morgen-Tipp Thunfisch im eigenen Saft, nicht in Öl – spart unnötiges Fett.", + "badge": "", + "yield": "" + }, + { + "num": "28", + "category": "Herzhaftes Brot & Belag", + "title": "Skyr-Dip mit Gemüsesticks", + "tagline": "Frühstück zum Dippen.", + "nutrition": "ca. ≈ 230 kcal · Eiweiß 18 g · Fett 3 g · KH 26 g · Zucker 10 g · Ballaststoffe 5 g", + "ingredients": "150 g Skyr · Kräuter · Knoblauch · Salz · Gurke, Paprika, Karotte", + "steps": "Skyr würzen, Gemüse in Sticks schneiden und dippen; dazu 1 Scheibe Brot.", + "variations": [], + "tip": "Morgen-Tipp Wer morgens nicht auf Brot steht: rohes Gemüse plus Eiweiß-Dip.", + "badge": "", + "yield": "" + }, + { + "num": "29", + "category": "Herzhaftes Brot & Belag", + "title": "Erdnussbutter-Bananen-Brot", + "tagline": "Süß-herzhafte Brücke.", + "nutrition": "ca. ≈ 290 kcal · Eiweiß 9 g · Fett 12 g · KH 34 g · Zucker 10 g · Ballaststoffe 5 g", + "ingredients": "1 Scheibe Vollkornbrot · 1 EL Erdnussmus · 1/2 Banane · Zimt", + "steps": "Erdnussmus streichen, Bananenscheiben drauf, Zimt darüber.", + "variations": [], + "tip": "Morgen-Tipp Nimm 100 % Erdnussmus ohne Zucker und Palmöl.", + "badge": "", + "yield": "" + }, + { + "num": "30", + "category": "Herzhaftes Brot & Belag", + "title": "Bohnen- oder Hummus-Brot", + "tagline": "Pflanzliches Eiweiß am Morgen.", + "nutrition": "ca. ≈ 230 kcal · Eiweiß 8 g · Fett 9 g · KH 30 g · Zucker 3 g · Ballaststoffe 7 g", + "ingredients": "1 Scheibe Vollkornbrot · 2 EL Hummus/Bohnenpüree · Tomate · Paprikapulver", + "steps": "Hummus streichen, Tomate drauf, würzen.", + "variations": [], + "tip": "Morgen-Tipp Kichererbsen und Bohnen liefern Eiweiß plus jede Menge Ballaststoffe.", + "badge": "", + "yield": "" + }, + { + "num": "31", + "category": "Pfannkuchen, Waffeln & Protein-Backwerk", + "title": "Protein-Pancakes · Basis", + "tagline": "Fluffig, ohne Backmischung.", + "nutrition": "ca. ≈ 360 kcal · Eiweiß 20 g · Fett 14 g · KH 40 g · Zucker 14 g · Ballaststoffe 5 g", + "ingredients": "1 Banane · 2 Eier · 40 g Haferflocken · Prise Backpulver · etwas Zimt", + "steps": "Alles pürieren, kleine Pfannkuchen bei mittlerer Hitze ausbacken.", + "variations": [ + "Schoko + 1 TL Kakao in den Teig", + "Beere TK-Beeren in den Teig", + "Protein 1 Löffel Proteinpulver, dafür weniger Hafer", + "Apfel-Zimt geriebener Apfel + mehr Zimt" + ], + "tip": "Morgen-Tipp Reife Banane süßt genug – kein extra Zucker nötig.", + "badge": "", + "yield": "" + }, + { + "num": "32", + "category": "Pfannkuchen, Waffeln & Protein-Backwerk", + "title": "Quark-Pfannkuchen", + "tagline": "Saftig und eiweißreich.", + "nutrition": "ca. ≈ 330 kcal · Eiweiß 26 g · Fett 14 g · KH 24 g · Zucker 6 g · Ballaststoffe 3 g", + "ingredients": "100 g Quark · 2 Eier · 30 g Haferflocken/Mehl · etwas Süße · Backpulver", + "steps": "Teig verrühren, in wenig Öl goldbraun ausbacken.", + "variations": [], + "tip": "Morgen-Tipp Quark macht die Pfannkuchen saftig, nicht trocken.", + "badge": "", + "yield": "" + }, + { + "num": "33", + "category": "Pfannkuchen, Waffeln & Protein-Backwerk", + "title": "Haferflocken-Waffeln", + "tagline": "Wenn ein Waffeleisen da ist.", + "nutrition": "ca. ≈ 400 kcal · Eiweiß 28 g · Fett 16 g · KH 34 g · Zucker 6 g · Ballaststoffe 5 g", + "ingredients": "50 g Haferflocken (fein) · 2 Eier · 100 g Quark · Backpulver · Vanille", + "steps": "Teig verrühren, portionsweise im Waffeleisen backen.", + "variations": [], + "tip": "Morgen-Tipp Doppelte Menge backen und einfrieren – toasten reicht dann morgens.", + "badge": "", + "yield": "" + }, + { + "num": "34", + "category": "Pfannkuchen, Waffeln & Protein-Backwerk", + "title": "Bananenbrot-Muffins", + "tagline": "Vorrat für die ganze Woche.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 9 g · Fett 8 g · KH 38 g · Zucker 12 g · Ballaststoffe 4 g", + "ingredients": "2 reife Bananen · 2 Eier · 150 g Haferflocken · Backpulver · Zimt · Nüsse", + "steps": "Bananen zerdrücken, alles mischen, in Muffinform 20 Min bei 180 °C backen.", + "variations": [], + "tip": "Morgen-Tipp Ohne Zucker – die Bananen tragen die Süße.", + "badge": "", + "yield": "" + }, + { + "num": "35", + "category": "Pfannkuchen, Waffeln & Protein-Backwerk", + "title": "Skyr-Pancakes", + "tagline": "Extra Eiweiß, extra fluffig.", + "nutrition": "ca. ≈ 370 kcal · Eiweiß 26 g · Fett 13 g · KH 34 g · Zucker 7 g · Ballaststoffe 3 g", + "ingredients": "150 g Skyr · 2 Eier · 40 g Mehl/Hafer · Backpulver · Süße", + "steps": "Verrühren, kleine Pfannkuchen backen; mit Beeren servieren.", + "variations": [], + "tip": "Morgen-Tipp Mit Skyr statt Milch wird der Teig cremig und proteinreich.", + "badge": "", + "yield": "" + }, + { + "num": "36", + "category": "Pfannkuchen, Waffeln & Protein-Backwerk", + "title": "Beeren-Waffeln", + "tagline": "Sonntagsfrühstück, das reinpasst.", + "nutrition": "ca. ≈ 410 kcal · Eiweiß 28 g · Fett 16 g · KH 38 g · Zucker 9 g · Ballaststoffe 5 g", + "ingredients": "50 g Hafer · 2 Eier · 100 g Quark · TK-Beeren · Backpulver", + "steps": "Beeren vorsichtig unterheben, im Waffeleisen backen.", + "variations": [], + "tip": "Morgen-Tipp Beeren erst zum Schluss unter den Teig, sonst zerlaufen sie.", + "badge": "", + "yield": "" + }, + { + "num": "37", + "category": "Pfannkuchen, Waffeln & Protein-Backwerk", + "title": "Protein-Kaiserschmarrn", + "tagline": "Österreich, aber alltagstauglich.", + "nutrition": "ca. ≈ 350 kcal · Eiweiß 26 g · Fett 14 g · KH 30 g · Zucker 8 g · Ballaststoffe 4 g", + "ingredients": "100 g Quark · 2 Eier · 30 g Hafer · Süße · Backpulver · TK-Beeren", + "steps": "Teig in die Pfanne, stocken lassen, zerreißen und kurz weiterbraten.", + "variations": [], + "tip": "Morgen-Tipp Ein Löffel Puderzucker als Deko reicht – nicht die ganze Dose.", + "badge": "", + "yield": "" + }, + { + "num": "38", + "category": "Pfannkuchen, Waffeln & Protein-Backwerk", + "title": "3-Zutaten-Bananen-Ei-Pfannkuchen", + "tagline": "Minimalismus, der funktioniert.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 14 g · Fett 11 g · KH 26 g · Zucker 14 g · Ballaststoffe 3 g", + "ingredients": "1 Banane · 2 Eier · Prise Zimt", + "steps": "Banane zerdrücken, mit Ei verrühren, kleine Pfannkuchen vorsichtig backen.", + "variations": [], + "tip": "Morgen-Tipp Wenden erst, wenn die Unterseite fest ist – sonst reißt es.", + "badge": "", + "yield": "" + }, + { + "num": "39", + "category": "Smoothies & Trink-Frühstück", + "title": "Protein-Beeren-Smoothie", + "tagline": "Frühstück zum Trinken, wenn's schnell muss.", + "nutrition": "ca. ≈ 330 kcal · Eiweiß 24 g · Fett 6 g · KH 45 g · Zucker 24 g · Ballaststoffe 5 g", + "ingredients": "150 g Skyr/Quark · Handvoll TK-Beeren · 1/2 Banane · 150 ml Milch · Haferflocken", + "steps": "Alles in den Mixer, cremig mixen.", + "variations": [ + "Grün Handvoll Spinat dazu", + "Schoko + 1 TL Kakao", + "Mango Mango statt Beeren", + "Erdnuss + 1 EL Erdnussmus" + ], + "tip": "Morgen-Tipp Trinken ersetzt kein Kauen dauerhaft – aber besser als Frühstück auslassen.", + "badge": "", + "yield": "" + }, + { + "num": "40", + "category": "Smoothies & Trink-Frühstück", + "title": "Grüner Smoothie", + "tagline": "Gemüse, das man nicht schmeckt.", + "nutrition": "ca. ≈ 230 kcal · Eiweiß 18 g · Fett 2 g · KH 34 g · Zucker 22 g · Ballaststoffe 4 g", + "ingredients": "Handvoll Spinat · 1/2 Banane · 150 g Skyr · 150 ml Wasser/Milch · 1/2 Apfel", + "steps": "Alles mixen, bis glatt.", + "variations": [], + "tip": "Morgen-Tipp Banane und Apfel übertönen den Spinat komplett.", + "badge": "", + "yield": "" + }, + { + "num": "41", + "category": "Smoothies & Trink-Frühstück", + "title": "Schoko-Erdnuss-Shake", + "tagline": "Schmeckt wie Milchshake.", + "nutrition": "ca. ≈ 400 kcal · Eiweiß 16 g · Fett 18 g · KH 44 g · Zucker 28 g · Ballaststoffe 4 g", + "ingredients": "250 ml Milch · 1 Banane · 1 EL Erdnussmus · 1 TL Kakao · optional Proteinpulver", + "steps": "Alles mixen.", + "variations": [], + "tip": "Morgen-Tipp Nach früher Session ideal: flüssig, aber sättigend.", + "badge": "", + "yield": "" + }, + { + "num": "42", + "category": "Smoothies & Trink-Frühstück", + "title": "Proffee (Kaffee-Protein-Shake)", + "tagline": "Kaffee und Frühstück in einem Glas.", + "nutrition": "ca. ≈ 220 kcal · Eiweiß 24 g · Fett 4 g · KH 16 g · Zucker 12 g · Ballaststoffe 0 g", + "ingredients": "1 Espresso/starker Kaffee (kalt) · 200 ml Milch · 1 Portion Quark/Proteinpulver · Eis", + "steps": "Alles mit Eis mixen.", + "variations": [], + "tip": "Morgen-Tipp Der wache Zwilling zum warmen Kaffee – plus rund 20 g Eiweiß.", + "badge": "", + "yield": "" + }, + { + "num": "43", + "category": "Smoothies & Trink-Frühstück", + "title": "Mango-Kokos-Smoothie", + "tagline": "Urlaub im Glas.", + "nutrition": "ca. ≈ 270 kcal · Eiweiß 18 g · Fett 4 g · KH 40 g · Zucker 26 g · Ballaststoffe 3 g", + "ingredients": "Mangostücke · 150 g Skyr · 100 ml Kokoswasser/Milch · Limette", + "steps": "Alles mixen; Limette bringt Frische.", + "variations": [], + "tip": "Morgen-Tipp TK-Mango macht den Smoothie schön cremig-kalt.", + "badge": "", + "yield": "" + }, + { + "num": "44", + "category": "Smoothies & Trink-Frühstück", + "title": "Bananen-Hafer-Smoothie", + "tagline": "Der sättigende unter den Smoothies.", + "nutrition": "ca. ≈ 380 kcal · Eiweiß 14 g · Fett 8 g · KH 60 g · Zucker 28 g · Ballaststoffe 6 g", + "ingredients": "1 Banane · 3 EL Haferflocken · 250 ml Milch · Zimt · optional Erdnussmus", + "steps": "Alles mixen; kurz stehen lassen, damit der Hafer quillt.", + "variations": [], + "tip": "Morgen-Tipp Hafer macht aus dem Smoothie ein echtes Frühstück.", + "badge": "", + "yield": "" + }, + { + "num": "45", + "category": "Smoothies & Trink-Frühstück", + "title": "Beeren-Kefir-Drink", + "tagline": "Leicht, spritzig, für den Darm.", + "nutrition": "ca. ≈ 240 kcal · Eiweiß 12 g · Fett 8 g · KH 30 g · Zucker 20 g · Ballaststoffe 5 g", + "ingredients": "250 ml Kefir · Handvoll Beeren · etwas Süße · 1 TL Chiasamen", + "steps": "Alles mixen, 5 Min quellen lassen.", + "variations": [], + "tip": "Morgen-Tipp Kefir bringt Eiweiß und probiotische Kulturen.", + "badge": "", + "yield": "" + }, + { + "num": "46", + "category": "Smoothies & Trink-Frühstück", + "title": "Smoothie-Prep aus dem Tiefkühler", + "tagline": "Morgens nur noch mixen.", + "nutrition": "ca. ≈ 330 kcal · Eiweiß 22 g · Fett 6 g · KH 48 g · Zucker 26 g · Ballaststoffe 6 g", + "ingredients": "Beutel mit TK-Obst + Haferflocken vorportioniert · morgens: Milch + Skyr", + "steps": "Portionsbeutel vorbereiten, morgens Inhalt mit Milch und Skyr mixen.", + "variations": [], + "tip": "Morgen-Tipp 5 Beutel am Sonntag füllen – die ganze Woche ohne Denken.", + "badge": "", + "yield": "" + }, + { + "num": "47", + "category": "Frisch & schnell · unter 5 Minuten", + "title": "Joghurt-Obst-Nuss-Schale", + "tagline": "Das schnellste vollwertige Frühstück.", + "nutrition": "ca. ≈ 290 kcal · Eiweiß 20 g · Fett 9 g · KH 28 g · Zucker 20 g · Ballaststoffe 4 g", + "ingredients": "200 g Naturjoghurt/Skyr · Obst der Saison · 1 EL Nüsse", + "steps": "Joghurt in die Schale, Obst schneiden, Nüsse drüber.", + "variations": [], + "tip": "Morgen-Tipp Naturjoghurt statt Fruchtjoghurt spart einen Haufen versteckten Zucker.", + "badge": "", + "yield": "" + }, + { + "num": "48", + "category": "Frisch & schnell · unter 5 Minuten", + "title": "Banane + Erdnussmus", + "tagline": "Zwei Zutaten, sofort satt.", + "nutrition": "ca. ≈ 190 kcal · Eiweiß 5 g · Fett 9 g · KH 26 g · Zucker 14 g · Ballaststoffe 4 g", + "ingredients": "1 Banane · 1 EL Erdnussmus", + "steps": "Banane längs aufschneiden, Erdnussmus drauf.", + "variations": [], + "tip": "Morgen-Tipp Notfrühstück, das schlägt jeden Riegel aus dem Automaten.", + "badge": "", + "yield": "" + }, + { + "num": "49", + "category": "Frisch & schnell · unter 5 Minuten", + "title": "Käse-Apfel-Brot to go", + "tagline": "In der Hand aus dem Haus.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 12 g · Fett 11 g · KH 30 g · Zucker 12 g · Ballaststoffe 4 g", + "ingredients": "1 Scheibe Vollkornbrot · 1 Scheibe Käse · 1/2 Apfel", + "steps": "Brot mit Käse belegen, Apfel dazu essen oder drauflegen.", + "variations": [], + "tip": "Morgen-Tipp Käse plus Apfel: Eiweiß, Fett und Frische in Sekunden.", + "badge": "", + "yield": "" + }, + { + "num": "50", + "category": "Frisch & schnell · unter 5 Minuten", + "title": "Skyr + Müsli schnell", + "tagline": "Fertig, bevor der Kaffee durch ist.", + "nutrition": "ca. ≈ 280 kcal · Eiweiß 18 g · Fett 6 g · KH 38 g · Zucker 14 g · Ballaststoffe 5 g", + "ingredients": "150 g Skyr · 3 EL zuckerarmes Müsli/Haferflocken · Beeren", + "steps": "Skyr in die Schale, Müsli und Beeren drüber.", + "variations": [], + "tip": "Morgen-Tipp Blick aufs Etikett: viele Müslis sind halb Zucker – lieber pure Flocken.", + "badge": "", + "yield": "" + }, + { + "num": "51", + "category": "Frisch & schnell · unter 5 Minuten", + "title": "Obstsalat mit Quark-Klecks", + "tagline": "Frisch und leicht.", + "nutrition": "ca. ≈ 200 kcal · Eiweiß 14 g · Fett 1 g · KH 30 g · Zucker 22 g · Ballaststoffe 4 g", + "ingredients": "Obst der Saison gewürfelt · 100 g Quark · Zimt · optional Nüsse", + "steps": "Obst würfeln, Quark-Klecks drauf, Zimt darüber.", + "variations": [], + "tip": "Morgen-Tipp Der Quark-Klecks macht aus Obst ein sättigendes Frühstück.", + "badge": "", + "yield": "" + }, + { + "num": "52", + "category": "Frisch & schnell · unter 5 Minuten", + "title": "Hüttenkäse pur mit Obst", + "tagline": "Ohne Schnickschnack, viel Eiweiß.", + "nutrition": "ca. ≈ 240 kcal · Eiweiß 26 g · Fett 8 g · KH 16 g · Zucker 12 g · Ballaststoffe 2 g", + "ingredients": "200 g Hüttenkäse · Handvoll Beeren oder 1/2 Banane", + "steps": "Hüttenkäse in die Schale, Obst dazu.", + "variations": [], + "tip": "Morgen-Tipp Direkt aus dem Becher – ehrlich schneller geht's kaum.", + "badge": "", + "yield": "" + }, + { + "num": "53", + "category": "Frisch & schnell · unter 5 Minuten", + "title": "Vollkorn-Cracker mit Käse & Tomate", + "tagline": "Knabber-Frühstück.", + "nutrition": "ca. ≈ 230 kcal · Eiweiß 10 g · Fett 10 g · KH 26 g · Zucker 4 g · Ballaststoffe 4 g", + "ingredients": "3–4 Vollkorn-Cracker · Frischkäse/Käse · Cherrytomaten", + "steps": "Cracker mit Käse bestreichen, Tomate drauf.", + "variations": [], + "tip": "Morgen-Tipp Für die, die morgens nicht viel runterbekommen – klein, aber mit Eiweiß.", + "badge": "", + "yield": "" + }, + { + "num": "54", + "category": "Frisch & schnell · unter 5 Minuten", + "title": "Edamame-Schale herzhaft", + "tagline": "Grünes Protein für Salzige.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 16 g · Fett 8 g · KH 12 g · Zucker 3 g · Ballaststoffe 5 g", + "ingredients": "150 g Edamame (TK, geschält) · Salz · Chili · Sesam", + "steps": "Edamame kurz in kochendem Wasser ziehen lassen, würzen.", + "variations": [], + "tip": "Morgen-Tipp Ungewöhnlich zum Frühstück, aber sattmachend und proteinreich.", + "badge": "", + "yield": "" + }, + { + "num": "55", + "category": "Airfryer · warm & knusprig", + "title": "Airfryer-Egg-Muffins AIRFRYER", + "tagline": "Vorkochen, aufwärmen, fertig.", + "nutrition": "ca. ≈ 220 kcal · Eiweiß 16 g · Fett 15 g · KH 4 g · Zucker 3 g · Ballaststoffe 1 g", + "ingredients": "4 Eier · Gemüse gewürfelt · Käse · Salz, Pfeffer · kleine Silikonförmchen", + "steps": "Eier verquirlen, mit Gemüse in die Förmchen, in den Airfryer. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 175 °C, 10–12 Min Ofen: 180 °C, 15–18 Min", + "variations": [ + "Schinken gewürfelten Schinken/Pute dazu", + "Spinat-Feta Spinat + Feta", + "Paprika Paprikawürfel + Paprikapulver" + ], + "tip": "Morgen-Tipp Batch machen: halten 4 Tage, morgens 2 Min aufwärmen.", + "badge": "", + "yield": "" + }, + { + "num": "56", + "category": "Airfryer · warm & knusprig", + "title": "Airfryer-Baked-Oats AIRFRYER", + "tagline": "Warmer Haferkuchen zum Löffeln.", + "nutrition": "ca. ≈ 360 kcal · Eiweiß 15 g · Fett 10 g · KH 48 g · Zucker 18 g · Ballaststoffe 6 g", + "ingredients": "50 g Haferflocken · 1 Ei · 100 ml Milch · 1/2 Banane · Backpulver · Beeren", + "steps": "Alles verrühren, in eine kleine Form, in den Airfryer, bis fest. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 175 °C, 12–15 Min Ofen: 180 °C, 20–25 Min", + "variations": [], + "tip": "Morgen-Tipp Schmeckt wie Kuchen, ist aber im Kern nur Haferbrei.", + "badge": "", + "yield": "" + }, + { + "num": "57", + "category": "Airfryer · warm & knusprig", + "title": "Airfryer-French-Toast-Sticks AIRFRYER", + "tagline": "Arme Ritter, entfettet.", + "nutrition": "ca. ≈ 290 kcal · Eiweiß 14 g · Fett 9 g · KH 38 g · Zucker 6 g · Ballaststoffe 4 g", + "ingredients": "2 Scheiben Vollkornbrot in Streifen · 1 Ei · Schuss Milch · Zimt", + "steps": "Brotstreifen in Ei-Milch wenden, in den Airfryer, bis goldbraun. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 190 °C, 6–8 Min, 1× wenden Ofen: 200 °C, 10–12 Min", + "variations": [], + "tip": "Morgen-Tipp Ohne Butterpfanne – knusprig mit einem Bruchteil des Fetts.", + "badge": "", + "yield": "" + }, + { + "num": "58", + "category": "Airfryer · warm & knusprig", + "title": "Airfryer-Frühstücks-Hash AIRFRYER", + "tagline": "Herzhaft, knusprig, mit Ei.", + "nutrition": "ca. ≈ 320 kcal · Eiweiß 10 g · Fett 13 g · KH 40 g · Zucker 6 g · Ballaststoffe 6 g", + "ingredients": "1 kleine Süßkartoffel/Kartoffel gewürfelt · Paprika · 1 TL Öl · Gewürz · 1 Ei", + "steps": "Gemüse würfeln, ölen, würzen, im Airfryer knusprig garen; dazu ein Ei braten. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 200 °C, 15–18 Min, 1× schütteln Ofen: 210 °C, 25–30 Min", + "variations": [], + "tip": "Morgen-Tipp Kartoffel vorher klein würfeln, dann wird sie gleichmäßig knusprig.", + "badge": "", + "yield": "" + }, + { + "num": "59", + "category": "Airfryer · warm & knusprig", + "title": "Airfryer-Hafer-Bananen-Riegel AIRFRYER", + "tagline": "Selbstgemacht statt gekauft.", + "nutrition": "ca. ≈ 230 kcal · Eiweiß 6 g · Fett 6 g · KH 40 g · Zucker 12 g · Ballaststoffe 5 g", + "ingredients": "150 g Haferflocken · 2 zerdrückte Bananen · 1 EL Nüsse · Zimt", + "steps": "Alles mischen, flach in eine Form drücken, im Airfryer backen, in Riegel schneiden. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 170 °C, 12–15 Min Ofen: 180 °C, 18–22 Min", + "variations": [], + "tip": "Morgen-Tipp Ohne Sirup und Schokoglasur – nur Hafer, Banane, Nuss.", + "badge": "", + "yield": "" + }, + { + "num": "60", + "category": "Airfryer · warm & knusprig", + "title": "Airfryer-Käse-Toast AIRFRYER", + "tagline": "Croque-Gefühl in Minuten.", + "nutrition": "ca. ≈ 330 kcal · Eiweiß 20 g · Fett 13 g · KH 34 g · Zucker 4 g · Ballaststoffe 4 g", + "ingredients": "2 Scheiben Vollkornbrot · Käse · optional Pute/Schinken · Senf", + "steps": "Brot belegen, zusammenklappen, im Airfryer knusprig überbacken. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 180 °C, 6–8 Min Ofen: 200 °C, 8–10 Min", + "variations": [], + "tip": "Morgen-Tipp Vollkorn und magerer Belag machen aus dem Toast ein sattes Frühstück.", + "badge": "", + "yield": "" + }, + { + "num": "61", + "category": "Airfryer · warm & knusprig", + "title": "Airfryer-Frühstücks-Burrito AIRFRYER", + "tagline": "Warm, knusprig, zum Mitnehmen.", + "nutrition": "ca. ≈ 450 kcal · Eiweiß 24 g · Fett 20 g · KH 42 g · Zucker 5 g · Ballaststoffe 6 g", + "ingredients": "1 Vollkorn-Wrap · Rührei · Bohnen · Käse · Salsa", + "steps": "Wrap füllen, fest einrollen, im Airfryer knusprig anrösten. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 190 °C, 6–8 Min Ofen: 200 °C, 10–12 Min", + "variations": [], + "tip": "Morgen-Tipp Vorab rollen, einwickeln – morgens nur in den Airfryer.", + "badge": "", + "yield": "" + }, + { + "num": "62", + "category": "Airfryer · warm & knusprig", + "title": "Airfryer-Apfel-Zimt-Chips AIRFRYER", + "tagline": "Süßer Crunch ohne Zucker.", + "nutrition": "ca. ≈ 200 kcal · Eiweiß 14 g · Fett 1 g · KH 32 g · Zucker 20 g · Ballaststoffe 4 g", + "ingredients": "1 Apfel dünn gehobelt · Zimt · dazu 150 g Quark", + "steps": "Apfelscheiben dünn hobeln, mit Zimt bestreuen, im Airfryer knusprig trocknen; Quark dazu. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 150 °C, 15–18 Min Ofen: 140 °C, 30–40 Min", + "variations": [], + "tip": "Morgen-Tipp Süß und knusprig aus einer einzigen Frucht – Quark liefert das Eiweiß.", + "badge": "", + "yield": "" + }, + { + "num": "63", + "category": "Mehr Löffel- & Joghurt-Frühstück", + "title": "Griechischer Joghurt, Honig & Walnuss", + "tagline": "Simpel, aber gut.", + "nutrition": "ca. ≈ 300 kcal · Eiweiß 18 g · Fett 16 g · KH 22 g · Zucker 18 g · Ballaststoffe 1 g", + "ingredients": "200 g griechischer Joghurt · 1 TL Honig · 1 EL Walnüsse", + "steps": "Joghurt in die Schale, Walnüsse drüber, dünn Honig darüber.", + "variations": [], + "tip": "Morgen-Tipp Griechischer Joghurt bringt mehr Eiweiß als der normale.", + "badge": "", + "yield": "" + }, + { + "num": "64", + "category": "Mehr Löffel- & Joghurt-Frühstück", + "title": "Chia-Pudding · Basis", + "tagline": "Abends rühren, morgens fertig.", + "nutrition": "ca. ≈ 170 kcal · Eiweiß 10 g · Fett 9 g · KH 20 g · Zucker 10 g · Ballaststoffe 8 g", + "ingredients": "3 EL Chiasamen · 200 ml Milch · etwas Süße · Beeren zum Toppen", + "steps": "Verrühren, 10 Min stehen, nochmal rühren, über Nacht kühlen.", + "variations": [ + "Schoko + 1 TL Kakao", + "Vanille-Kokos Vanille + Schuss Kokosmilch", + "Beere TK-Beeren untermischen", + "Erdnuss + 1 EL Erdnussmus" + ], + "tip": "Morgen-Tipp Nochmal rühren nach 10 Min verhindert Klümpchen.", + "badge": "", + "yield": "" + }, + { + "num": "65", + "category": "Mehr Löffel- & Joghurt-Frühstück", + "title": "Skyr-Schoko-Mousse", + "tagline": "Nachtisch, den man morgens darf.", + "nutrition": "ca. ≈ 150 kcal · Eiweiß 22 g · Fett 2 g · KH 12 g · Zucker 9 g · Ballaststoffe 2 g", + "ingredients": "200 g Skyr · 1 TL Kakao · etwas Süße · Schuss Milch", + "steps": "Alles glattrühren, bis cremig-dunkel.", + "variations": [], + "tip": "Morgen-Tipp Schoko-Kick am Morgen, ohne dass eine Tafel wandert.", + "badge": "", + "yield": "" + }, + { + "num": "66", + "category": "Mehr Löffel- & Joghurt-Frühstück", + "title": "Kefir-Beeren-Bowl", + "tagline": "Frisch und leicht verdaulich.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 12 g · Fett 8 g · KH 32 g · Zucker 16 g · Ballaststoffe 5 g", + "ingredients": "200 g Kefir · Beeren · 2 EL Haferflocken · 1 TL Leinsamen", + "steps": "Kefir in die Schale, Beeren, Hafer und Leinsamen drüber.", + "variations": [], + "tip": "Morgen-Tipp Leinsamen frisch schroten, sonst gehen sie unverdaut durch.", + "badge": "", + "yield": "" + }, + { + "num": "67", + "category": "Mehr Löffel- & Joghurt-Frühstück", + "title": "Quark-Müsli-Schichtglas", + "tagline": "Fürs Büro vorbereiten.", + "nutrition": "ca. ≈ 300 kcal · Eiweiß 22 g · Fett 9 g · KH 34 g · Zucker 12 g · Ballaststoffe 4 g", + "ingredients": "150 g Quark · 3 EL Müsli/Hafer · Beeren · Nüsse", + "steps": "Abwechselnd Quark, Müsli und Beeren ins Glas schichten.", + "variations": [], + "tip": "Morgen-Tipp Getrennt schichten hält das Müsli bis mittags knusprig.", + "badge": "", + "yield": "" + }, + { + "num": "68", + "category": "Mehr Löffel- & Joghurt-Frühstück", + "title": "Joghurt mit gebratener Banane", + "tagline": "Warm trifft kühl.", + "nutrition": "ca. ≈ 280 kcal · Eiweiß 18 g · Fett 8 g · KH 32 g · Zucker 22 g · Ballaststoffe 3 g", + "ingredients": "200 g Joghurt/Skyr · 1 Banane · Zimt · 1 TL Öl", + "steps": "Banane in Scheiben kurz anbraten, mit Zimt auf den Joghurt geben.", + "variations": [], + "tip": "Morgen-Tipp Gebratene Banane karamellisiert von selbst – kein Zucker nötig.", + "badge": "", + "yield": "" + }, + { + "num": "69", + "category": "Mehr Löffel- & Joghurt-Frühstück", + "title": "Protein-Milchreis (kalt)", + "tagline": "Reispudding, aber eiweißreich.", + "nutrition": "ca. ≈ 290 kcal · Eiweiß 15 g · Fett 4 g · KH 45 g · Zucker 16 g · Ballaststoffe 2 g", + "ingredients": "150 g gekochter Milchreis · 100 g Skyr · Zimt · Beeren", + "steps": "Kalten Milchreis mit Skyr verrühren, Beeren und Zimt drauf.", + "variations": [], + "tip": "Morgen-Tipp Reste-Milchreis vom Vortag plus Skyr = fertiges Frühstück.", + "badge": "", + "yield": "" + }, + { + "num": "70", + "category": "Mehr herzhafte Frühstücke", + "title": "Herzhafte Frühstücks-Bowl", + "tagline": "Ein Teller, alles drin.", + "nutrition": "ca. ≈ 340 kcal · Eiweiß 15 g · Fett 22 g · KH 22 g · Zucker 5 g · Ballaststoffe 8 g", + "ingredients": "1 gekochtes/pochiertes Ei · 1/2 Avocado · 3 EL Bohnen · Cherrytomaten · Gewürz", + "steps": "Alles in eine Schale, würzen, warm oder kalt essen.", + "variations": [], + "tip": "Morgen-Tipp Reste vom Abendessen lassen sich prima zur Frühstücks-Bowl umbauen.", + "badge": "", + "yield": "" + }, + { + "num": "71", + "category": "Mehr herzhafte Frühstücke", + "title": "Tofu-Rührei (Scramble)", + "tagline": "Pflanzliche Rührei-Alternative.", + "nutrition": "ca. ≈ 230 kcal · Eiweiß 18 g · Fett 15 g · KH 6 g · Zucker 3 g · Ballaststoffe 3 g", + "ingredients": "150 g fester Tofu · Kurkuma · Kala Namak/Salz · Gemüse · Öl", + "steps": "Tofu zerbröseln, mit Gewürzen und Gemüse anbraten.", + "variations": [], + "tip": "Morgen-Tipp Kala Namak (Schwarzsalz) gibt den typischen Ei-Geschmack.", + "badge": "", + "yield": "" + }, + { + "num": "72", + "category": "Mehr herzhafte Frühstücke", + "title": "Käse-Omelett-Wrap", + "tagline": "Herzhaft und tragbar.", + "nutrition": "ca. ≈ 400 kcal · Eiweiß 24 g · Fett 22 g · KH 28 g · Zucker 3 g · Ballaststoffe 3 g", + "ingredients": "2 Eier · Käse · 1 Vollkorn-Wrap · Spinat", + "steps": "Dünnes Omelett braten, mit Käse und Spinat in den Wrap rollen.", + "variations": [], + "tip": "Morgen-Tipp Dünn ausgebacken lässt sich das Omelett besser einrollen.", + "badge": "", + "yield": "" + }, + { + "num": "73", + "category": "Mehr herzhafte Frühstücke", + "title": "Pute-Ei-Sandwich", + "tagline": "Deftig und proteinstark.", + "nutrition": "ca. ≈ 360 kcal · Eiweiß 30 g · Fett 12 g · KH 34 g · Zucker 4 g · Ballaststoffe 5 g", + "ingredients": "2 Scheiben Vollkornbrot · Putenbrust · 1 gekochtes Ei · Senf · Salat", + "steps": "Brot belegen, Senf drauf, zusammenklappen.", + "variations": [], + "tip": "Morgen-Tipp Magere Pute plus Ei bringt richtig viel Eiweiß pro Bissen.", + "badge": "", + "yield": "" + }, + { + "num": "74", + "category": "Mehr herzhafte Frühstücke", + "title": "Linsen-Frühstückssalat", + "tagline": "Ungewöhnlich, aber stark.", + "nutrition": "ca. ≈ 330 kcal · Eiweiß 18 g · Fett 14 g · KH 34 g · Zucker 6 g · Ballaststoffe 9 g", + "ingredients": "150 g gekochte Linsen · Tomate · Gurke · Feta · Zitrone, Öl", + "steps": "Alles mischen, mit Zitrone und wenig Öl abschmecken.", + "variations": [], + "tip": "Morgen-Tipp Vorkochen und 3 Tage im Kühlschrank – herzhaftes Meal-Prep.", + "badge": "", + "yield": "" + }, + { + "num": "75", + "category": "Mehr herzhafte Frühstücke", + "title": "Räuchertofu-/Schinken-Brot", + "tagline": "Schneller herzhafter Belag.", + "nutrition": "ca. ≈ 240 kcal · Eiweiß 16 g · Fett 10 g · KH 20 g · Zucker 3 g · Ballaststoffe 3 g", + "ingredients": "1 Scheibe Vollkornbrot · Räuchertofu oder magerer Schinken · Gurke · Senf", + "steps": "Brot belegen, Gurke drauf.", + "variations": [], + "tip": "Morgen-Tipp Räuchertofu ist die pflanzliche, proteinreiche Schinken-Alternative.", + "badge": "", + "yield": "" + }, + { + "num": "76", + "category": "Mehr herzhafte Frühstücke", + "title": "Champignon-Rührei", + "tagline": "Herzhaft mit Umami.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 18 g · Fett 18 g · KH 4 g · Zucker 2 g · Ballaststoffe 2 g", + "ingredients": "2–3 Eier · Handvoll Champignons · Zwiebel · Petersilie · Öl", + "steps": "Pilze und Zwiebel anbraten, verquirlte Eier drüber, stocken lassen.", + "variations": [], + "tip": "Morgen-Tipp Pilze erst gut anbraten, bis das Wasser weg ist – dann werden sie aromatisch.", + "badge": "", + "yield": "" + }, + { + "num": "77", + "category": "Mehr warme & schnelle Frühstücke", + "title": "Blitz-Porridge aus der Mikrowelle", + "tagline": "Warm in 2 Minuten.", + "nutrition": "ca. ≈ 260 kcal · Eiweiß 11 g · Fett 6 g · KH 38 g · Zucker 12 g · Ballaststoffe 5 g", + "ingredients": "40 g Haferflocken · 150 ml Milch · Prise Salz · Beeren", + "steps": "Hafer mit Milch 2 Min in der Mikrowelle, umrühren, Beeren drauf.", + "variations": [], + "tip": "Morgen-Tipp Tiefer Teller nehmen – Haferbrei kocht in der Mikrowelle gern über.", + "badge": "", + "yield": "" + }, + { + "num": "78", + "category": "Mehr warme & schnelle Frühstücke", + "title": "Schoko-Bananen-Mug-Cake", + "tagline": "Kuchen in der Tasse, protein-style.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 12 g · Fett 9 g · KH 32 g · Zucker 12 g · Ballaststoffe 4 g", + "ingredients": "1 Ei · 1/2 Banane · 3 EL Haferflocken · 1 TL Kakao · Backpulver", + "steps": "Alles in einer Tasse verrühren, 1,5–2 Min in der Mikrowelle.", + "variations": [], + "tip": "Morgen-Tipp Fertig, bevor der Ofen überhaupt vorgeheizt wäre.", + "badge": "", + "yield": "" + }, + { + "num": "79", + "category": "Mehr warme & schnelle Frühstücke", + "title": "Grießbrei aus der Mikrowelle", + "tagline": "Kindheit in 3 Minuten.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 12 g · Fett 5 g · KH 40 g · Zucker 16 g · Ballaststoffe 2 g", + "ingredients": "3 EL Grieß · 200 ml Milch · Süße · Zimt", + "steps": "Grieß und Milch verrühren, 2–3 Min in der Mikrowelle, zwischendurch rühren.", + "variations": [], + "tip": "Morgen-Tipp Zwischendurch rühren, sonst klumpt der Grieß.", + "badge": "", + "yield": "" + }, + { + "num": "80", + "category": "Mehr warme & schnelle Frühstücke", + "title": "Warmer Zimt-Apfel mit Quark", + "tagline": "Bratapfel-Feeling ohne Ofen.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 18 g · Fett 1 g · KH 26 g · Zucker 20 g · Ballaststoffe 4 g", + "ingredients": "1 Apfel gewürfelt · Zimt · Schuss Wasser · 150 g Quark", + "steps": "Apfel mit Zimt und etwas Wasser 2 Min in der Mikrowelle weich garen, Quark dazu.", + "variations": [], + "tip": "Morgen-Tipp Der warme Apfel auf kaltem Quark ist ein kleines Highlight.", + "badge": "", + "yield": "" + }, + { + "num": "81", + "category": "Mehr warme & schnelle Frühstücke", + "title": "Protein-Kakao mit Vollkornbrot", + "tagline": "Trinken und kauen kombiniert.", + "nutrition": "ca. ≈ 330 kcal · Eiweiß 18 g · Fett 14 g · KH 32 g · Zucker 14 g · Ballaststoffe 3 g", + "ingredients": "250 ml warme Milch · 1 TL Kakao · optional Proteinpulver · 1 Scheibe Vollkornbrot mit Käse", + "steps": "Kakao anrühren, dazu ein belegtes Brot.", + "variations": [], + "tip": "Morgen-Tipp Warme Milch plus Käsebrot: einfach, warm und sättigend.", + "badge": "", + "yield": "" + }, + { + "num": "82", + "category": "Mehr warme & schnelle Frühstücke", + "title": "Mikrowellen-Ei-Muffin", + "tagline": "Rührei ohne Pfanne.", + "nutrition": "ca. ≈ 240 kcal · Eiweiß 18 g · Fett 18 g · KH 3 g · Zucker 2 g · Ballaststoffe 0 g", + "ingredients": "2 Eier · Schuss Milch · Käse · Salz · Tasse/Becher", + "steps": "Eier in der Tasse verquirlen, 1–1,5 Min in der Mikrowelle, zwischendurch rühren.", + "variations": [], + "tip": "Morgen-Tipp In 30-Sekunden-Schritten garen und prüfen, sonst wird's gummiartig.", + "badge": "", + "yield": "" + }, + { + "num": "83", + "category": "Mehr warme & schnelle Frühstücke", + "title": "Zimt-Porridge-Ritual", + "tagline": "Der bewusste warme Start.", + "nutrition": "ca. ≈ 300 kcal · Eiweiß 12 g · Fett 10 g · KH 40 g · Zucker 12 g · Ballaststoffe 5 g", + "ingredients": "40 g Haferflocken · 200 ml Milch · Zimt · 1 TL Nüsse", + "steps": "Hafer mit Milch warm köcheln, Zimt und Nüsse drüber, in Ruhe löffeln.", + "variations": [], + "tip": "Morgen-Tipp Fünf Minuten am Tisch statt im Stehen – der Tag beginnt anders, wenn man wirklich frühstückt.", + "badge": "", + "yield": "" + } + ] + } +} \ No newline at end of file diff --git a/plugins/trainingsplan/presets/granola.json b/plugins/trainingsplan/presets/granola.json new file mode 100644 index 0000000..3952713 --- /dev/null +++ b/plugins/trainingsplan/presets/granola.json @@ -0,0 +1,530 @@ +{ + "name": "Granola (Vorrat)", + "config": { + "type": "recipe", + "meta": { + "title": "Die Granola-Bibel", + "header_left": "GRANOLA · Vorrats-Edition · Begleitend zum 8-Wochen-Plan", + "footer_left": "Topping statt Schüssel · Portion abwiegen · Zartbitter statt Zucker", + "output_name": "Granola_Vorrat.pdf", + "doc_title": "Die Granola-Bibel", + "brand_color": "#6D28D9", + "steps_label": "SO GEHT'S" + }, + "intro": { + "headline": "Die Granola-Bibel", + "subtitle": "28 Rezepte mit exakten Arbeitsschritten – deutlich weniger Zucker als jedes Fertig-Granola, Nährwerte", + "paragraphs": [ + "pro Portion als Richtwert.", + "Gekauftes Granola ist meist eine als Müsli getarnte Süßigkeit – 20–30 % Zucker sind normal. Diese Rezepte", + "drehen das um: Hafer, Nüsse, Kerne und Buchweizen tragen, gesüßt wird sparsam und bewusst. Das ist genau", + "dein Substitutions-Hebel: Granola ist hier ein Topping für Skyr oder Quark, kein Schüssel-Füller. Eine", + "Eiweiß-Basis plus Crunch schlägt abends auf der Couch die Schoko-Chips-Cola-Kombi – nicht durch Verzicht,", + "sondern durch einen besseren Reiz. Ein Wort zur Ehrlichkeit: Granola bleibt kaloriendicht. Der Hebel ist die", + "Portion, nicht die Zutat. Wieg dir 40–50 g ab und stell das Glas weg – aus dem Glas gelöffelt wird jedes Granola", + "zur Falle. Die Nährwerte unter NÄHRWERTE ca. sind Richtwerte pro Portion (≈50 g), keine Zielvorgabe.", + "Die Grund-Technik – gilt für fast jedes Rezept", + "1 Trocken mischen: Hafer, grob gehackte Nüsse, Kerne, Gewürze, ggf. Kakao/Proteinpulver in einer großen", + "Schüssel verrühren.", + "2 Bindung erwärmen: Öl bzw. Nussmus mit Süße und Aroma kurz schmelzen, bis es dünnflüssig ist – so", + "verteilt es sich gleichmäßig.", + "3 Benetzen: über das Trockene gießen und gründlich mischen, bis alles glänzt. Trockene Nester = weiche", + "Stellen später.", + "4 Cluster-Trick: für feste Brocken 1 Eiweiß schaumig schlagen und unterheben, Masse fest aufs Blech", + "drücken – und beim Backen nicht rühren.", + "5 Backen: meist 150–160 °C Umluft. Niedrig und länger röstet gleichmäßig; ohne Cluster einmal wenden, mit", + "Cluster in Ruhe lassen.", + "6 Auskühlen = knusprig: Granola ist frisch aus dem Ofen noch weich. Erst vollständig auf dem Blech", + "auskühlen lassen, dann brechen.", + "7 Empfindliches später: Schokolade, Trockenfrüchte und Kokoschips erst nach dem Abkühlen zugeben, sonst", + "verbrennen sie.", + "8 Lagern: luftdicht, kühl und dunkel 4–6 Wochen. Nicht warm einfüllen, sonst schwitzt es und wird zäh." + ], + "rule_head": "", + "rule_text": "", + "categories_head": "7 Kategorien" + }, + "categories": [ + { + "name": "Schoko & Kakao", + "color": "#7C3AED" + }, + { + "name": "Nuss, Kern & Klassik", + "color": "#0EA5E9" + }, + { + "name": "Frucht & Beere", + "color": "#EA580C" + }, + { + "name": "Gewürz & Saison", + "color": "#16A34A" + }, + { + "name": "Protein-Boost & Post-Workout", + "color": "#DC2626" + }, + { + "name": "Grain-free & Low-Carb", + "color": "#DB2777" + }, + { + "name": "Ohne Ofen & Overnight", + "color": "#B45309" + } + ], + "recipes": [ + { + "num": "01", + "category": "Schoko & Kakao", + "title": "Malz-Schoko-Buchweizen-Granola", + "tagline": "Die Referenz nach Anna Jones – zuckerreduziert adaptiert.", + "nutrition": "ca. ≈ 238 kcal · Eiweiß 7 g · Fett 13 g · KH 24 g · Zucker 6 g · Ballaststoffe 5 g", + "ingredients": "250 g Haferflocken (kernig) · 100 g roher Buchweizen · 80 g Haselnüsse grob · 40 g Kakaonibs · 30 g Backkakao · 1 EL Gerstenmalz-Extrakt · 2 EL Ahornsirup · 3 EL Kokosöl · 1 Prise Meersalz · 60 g Zartbitter 85 % gehackt (nach dem Backen)", + "steps": "1 Ofen auf 150 °C Umluft vorheizen, Blech mit Backpapier.\n2 Hafer, Buchweizen, Haselnüsse, Kakaonibs, Kakao und Salz in einer großen Schüssel gut vermischen.\n3 Kokosöl, Malz-Extrakt und Ahornsirup in einem Töpfchen bei kleiner Hitze schmelzen und glatt rühren.\n4 Flüssige Mischung über das Trockene gießen und gründlich wenden, bis alles dunkel glänzt.\n5 Gleichmäßig aufs Blech verteilen und leicht andrücken.\n6 25–30 Min backen, nach der Hälfte einmal wenden – Kakao verbrennt schnell, lieber kürzer.\n7 Vollständig auf dem Blech auskühlen lassen, dann die gehackte Zartbitter untermischen.", + "variations": [ + "Nuss-Tausch Mandeln oder Pekannüsse statt Haselnüssen", + "Ohne Malz Malz weglassen, dafür 1 TL Instant-Kaffee für Tiefe" + ], + "tip": "Coach-Tipp Kakaonibs bringen Schoko-Bitterkeit fast ohne Zucker – der Trick, um Vollmilch-Schoko-Gelüste auf der Couch abzufangen.", + "badge": "", + "yield": "ergibt ca. 800 g · ~16 Portionen à 50 g" + }, + { + "num": "02", + "category": "Schoko & Kakao", + "title": "Tiramisu-Granola", + "tagline": "Nach The Doctor's Kitchen – Kaffee, Kakao, feste Cluster.", + "nutrition": "ca. ≈ 245 kcal · Eiweiß 8 g · Fett 15 g · KH 20 g · Zucker 6 g · Ballaststoffe 5 g", + "ingredients": "250 g Haferflocken · 100 g gemischte Nüsse grob · 40 g Kürbiskerne · 40 g Sonnenblumenkerne · 2 EL Leinsamen · 20 g Backkakao · 1 TL Zimt · 2 EL Mandelmus · 2 EL Honig · 3 EL Kokosöl · 60 ml starker Espresso (abgekühlt) · 1 TL Vanille · 1 Eiweiß · Kakao zum Bestäuben", + "steps": "1 Ofen auf 155 °C Umluft, Blech mit Backpapier.\n2 Hafer, Nüsse, Kürbis- und Sonnenblumenkerne, Leinsamen, Kakao und Zimt vermischen.\n3 Mandelmus, Honig, Kokosöl und Espresso leicht erwärmen und glatt rühren, Vanille einrühren.\n4 Über das Trockene gießen und gründlich mischen.\n5 Eiweiß schaumig schlagen und unterheben – das gibt die Tiramisu-typischen Cluster.\n6 Masse fest aufs Blech drücken (ca. 1,5 cm dick), nicht verteilen.\n7 30–35 Min backen, dabei NICHT rühren, damit die Brocken halten.\n8 Auskühlen lassen, in Brocken brechen und mit etwas Kakao bestäuben.", + "variations": [ + "Entkoffeiniert Espresso durch Lupinen- oder Malzkaffee ersetzen", + "Nüsse pur Kakao weglassen für reine Cappuccino-Note" + ], + "tip": "Coach-Tipp Kaffee + Kakao schmecken „Dessert“ ohne Süße-Bombe. Über Skyr wird das dein Abend-Dessert-Ersatz.", + "badge": "", + "yield": "ergibt ca. 700 g · ~14 Portionen à 50 g" + }, + { + "num": "03", + "category": "Schoko & Kakao", + "title": "Doppel-Kakao-Haselnuss", + "tagline": "Die „Nutella“-Idee als knuspriges Topping.", + "nutrition": "ca. ≈ 252 kcal · Eiweiß 8 g · Fett 16 g · KH 20 g · Zucker 6 g · Ballaststoffe 5 g", + "ingredients": "250 g Haferflocken · 120 g Haselnüsse grob · 30 g Backkakao · 2 EL Haselnussmus · 2 EL Kokosöl · 3 EL Ahornsirup · 1 Prise Salz · 40 g Kakaonibs (nach dem Backen)", + "steps": "1 Ofen auf 150 °C Umluft, Blech mit Backpapier.\n2 Hafer, Haselnüsse, Kakao und Salz mischen.\n3 Haselnussmus, Kokosöl und Ahornsirup schmelzen und glatt rühren.\n4 Über das Trockene gießen und mischen, bis alles dunkel und gleichmäßig ist.\n5 Aufs Blech, leicht andrücken.\n6 25 Min backen, nach der Hälfte wenden – wegen Kakao auf die Farbe achten.\n7 Auskühlen lassen, Kakaonibs untermischen.", + "variations": [ + "Extra Crunch 1 EL Buchweizen mitrösten", + "Milder Kakao halbieren, 1 TL Vanille zugeben" + ], + "tip": "Coach-Tipp Haselnuss + Kakao trifft exakt den Nutella-Reiz – aber mit Ballaststoffen statt Palmöl und Zucker.", + "badge": "", + "yield": "ergibt ca. 750 g · ~15 Portionen à 50 g" + }, + { + "num": "04", + "category": "Schoko & Kakao", + "title": "Schoko-Meersalz-Cluster", + "tagline": "Feste Brocken, salzig-bitter, sehr gering gesüßt.", + "nutrition": "ca. ≈ 248 kcal · Eiweiß 8 g · Fett 15 g · KH 21 g · Zucker 5 g · Ballaststoffe 6 g", + "ingredients": "250 g Haferflocken · 80 g Mandeln grob · 40 g Sonnenblumenkerne · 25 g Backkakao · 2 EL Mandelmus · 2 EL Kokosöl · 2 EL Ahornsirup · 1 Eiweiß · grobes Meersalz · 60 g Zartbitter 85 % (nach dem Backen)", + "steps": "1 Ofen auf 150 °C Umluft, Blech mit Backpapier.\n2 Hafer, Mandeln, Kerne und Kakao mischen.\n3 Mandelmus, Kokosöl und Ahornsirup schmelzen, unterrühren.\n4 Eiweiß schaumig schlagen, mit unterheben – gibt die Cluster-Bindung.\n5 Fest aufs Blech drücken, mit etwas grobem Meersalz bestreuen.\n6 30 Min backen, NICHT rühren.\n7 Vollständig auskühlen lassen, in Brocken brechen, gehackte Zartbitter untermischen.", + "variations": [ + "Orange Abrieb 1/2 Bio-Orange in die Bindung", + "Chili 1 Prise Chili für Schoko-Chili-Kick" + ], + "tip": "Coach-Tipp Salz + 85 %-Schoko: der intensivste Schoko-Hit pro Gramm. Kleine Menge sättigt den Gusto komplett.", + "badge": "", + "yield": "ergibt ca. 700 g · ~14 Portionen à 50 g" + }, + { + "num": "05", + "category": "Schoko & Kakao", + "title": "Mokka-Walnuss", + "tagline": "Espresso trifft Röstaroma, kaum Süße.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 8 g · Fett 16 g · KH 19 g · Zucker 5 g · Ballaststoffe 5 g", + "ingredients": "250 g Haferflocken · 100 g Walnüsse grob · 40 g Leinsamen · 15 g Backkakao · 1 TL Instant-Espresso · 3 EL Kokosöl · 3 EL Ahornsirup · 1 Prise Salz", + "steps": "1 Ofen auf 150 °C Umluft, Blech mit Backpapier.\n2 Hafer, Walnüsse, Leinsamen, Kakao, Espressopulver und Salz mischen.\n3 Kokosöl und Ahornsirup schmelzen, unterrühren.\n4 Über das Trockene gießen, gut mischen.\n5 Aufs Blech, leicht andrücken.\n6 25–30 Min backen, einmal wenden.\n7 Auskühlen lassen – wird erst kalt knusprig.", + "variations": [ + "Pekan Pekannüsse statt Walnüssen", + "Stärker 2 TL Espressopulver für echten Mokka-Punch" + ], + "tip": "Coach-Tipp Walnüsse liefern Omega-3 – passt gut in die Regenerationswochen mit viel Volumen.", + "badge": "", + "yield": "ergibt ca. 720 g · ~14 Portionen à 50 g" + }, + { + "num": "06", + "category": "Nuss, Kern & Klassik", + "title": "Klassik pur · Nuss-Kern-Granola", + "tagline": "Die Basis. Minimaler Zucker, maximal variierbar.", + "nutrition": "ca. ≈ 236 kcal · Eiweiß 7 g · Fett 13 g · KH 22 g · Zucker 4 g · Ballaststoffe 5 g", + "ingredients": "300 g Haferflocken · 100 g gemischte Nüsse grob · 40 g Kürbiskerne · 40 g Sonnenblumenkerne · 2 EL Leinsamen · 3 EL Kokosöl · 2 EL Ahornsirup · 1 TL Zimt · 1 Prise Salz", + "steps": "1 Ofen auf 155 °C Umluft, Blech mit Backpapier.\n2 Alle trockenen Zutaten mit Zimt und Salz mischen.\n3 Kokosöl und Ahornsirup schmelzen, unterrühren, bis alles glänzt.\n4 Aufs Blech, gleichmäßig dünn verteilen.\n5 25–30 Min backen, nach der Hälfte wenden, bis goldbraun.\n6 Auf dem Blech auskühlen lassen.", + "variations": [ + "Frucht nach dem Backen 40 g Trockenfrüchte untermischen", + "Nüsse pur Kerne durch mehr Nüsse ersetzen für weniger KH", + "Vanille 1 TL Vanille in die Bindung" + ], + "tip": "Coach-Tipp Lern dieses Rezept auswendig – alle anderen sind nur Varianten davon. Sparsame Süße ist Absicht.", + "badge": "", + "yield": "ergibt ca. 750 g · ~15 Portionen à 50 g" + }, + { + "num": "07", + "category": "Nuss, Kern & Klassik", + "title": "Erdnuss-Protein-Cluster", + "tagline": "Post-Workout-tauglich, feste Brocken, extra Eiweiß.", + "nutrition": "ca. ≈ 258 kcal · Eiweiß 12 g · Fett 14 g · KH 21 g · Zucker 5 g · Ballaststoffe 5 g", + "ingredients": "250 g Haferflocken · 60 g Erdnüsse grob · 40 g Sonnenblumenkerne · 30 g Whey oder Erbsenprotein (neutral/vanille) · 3 EL Erdnussmus · 2 EL Kokosöl · 2 EL Ahornsirup · 1 Eiweiß · 1 Prise Salz", + "steps": "1 Ofen auf 150 °C Umluft, Blech mit Backpapier.\n2 Hafer, Erdnüsse, Kerne und Proteinpulver mischen (Pulver gut unterheben, keine Klumpen).\n3 Erdnussmus, Kokosöl und Ahornsirup schmelzen, unterrühren.\n4 Eiweiß schaumig schlagen und unterheben.\n5 Fest aufs Blech drücken.\n6 28–32 Min bei niedriger Hitze backen, NICHT rühren – Protein bräunt schneller, Farbe im Auge behalten.\n7 Vollständig auskühlen lassen, in Brocken brechen.", + "variations": [ + "Schoko 20 g Kakao ins Trockene", + "Casein-Abend Casein statt Whey für langsame Abend-Variante" + ], + "tip": "Coach-Tipp Proteinpulver bräunt früher – lieber 150 °C und Sichtkontrolle. Über Skyr kommst du locker auf 25–30 g Eiweiß.", + "badge": "", + "yield": "ergibt ca. 700 g · ~14 Portionen à 50 g" + }, + { + "num": "08", + "category": "Nuss, Kern & Klassik", + "title": "Mandel-Vanille", + "tagline": "Schlicht, hell, alltagstauglich.", + "nutrition": "ca. ≈ 240 kcal · Eiweiß 7 g · Fett 14 g · KH 21 g · Zucker 5 g · Ballaststoffe 5 g", + "ingredients": "300 g Haferflocken · 120 g Mandeln grob · 40 g Leinsamen · 3 EL Kokosöl · 2 EL Ahornsirup · 2 TL Vanille · 1 Prise Salz", + "steps": "1 Ofen auf 155 °C Umluft, Blech mit Backpapier.\n2 Hafer, Mandeln, Leinsamen und Salz mischen.\n3 Kokosöl und Ahornsirup schmelzen, Vanille einrühren, über das Trockene gießen.\n4 Gut mischen, aufs Blech, andrücken.\n5 25–30 Min backen, einmal wenden.\n6 Auskühlen lassen.", + "variations": [ + "Mandel-Zimt 1 TL Zimt zugeben", + "Kirsch nach dem Backen getrocknete Sauerkirschen unter)" + ], + "tip": "Coach-Tipp Die neutralste Basis – perfekt, wenn der Rest der Schüssel (Beeren, Quark) den Geschmack trägt.", + "badge": "", + "yield": "ergibt ca. 750 g · ~15 Portionen à 50 g" + }, + { + "num": "09", + "category": "Nuss, Kern & Klassik", + "title": "Kokos-Cashew", + "tagline": "Cremig-tropisch, leicht süß von allein.", + "nutrition": "ca. ≈ 255 kcal · Eiweiß 7 g · Fett 16 g · KH 21 g · Zucker 5 g · Ballaststoffe 5 g", + "ingredients": "260 g Haferflocken · 100 g Cashews grob · 50 g Kokoschips · 40 g Sonnenblumenkerne · 3 EL Kokosöl · 2 EL Ahornsirup · 1 Prise Salz", + "steps": "1 Ofen auf 150 °C Umluft, Blech mit Backpapier.\n2 Hafer, Cashews, Kerne und Salz mischen – Kokoschips noch NICHT zugeben.\n3 Kokosöl und Ahornsirup schmelzen, unterrühren.\n4 Aufs Blech, andrücken.\n5 20 Min backen, dann wenden und die Kokoschips untermischen.\n6 Weitere 5–8 Min backen, bis die Kokoschips goldgelb sind – sie verbrennen schnell.\n7 Auskühlen lassen.", + "variations": [ + "Schoko-Kokos 40 g Zartbitter nach dem Backen", + "Limette Abrieb 1 Limette in die Bindung" + ], + "tip": "Coach-Tipp Kokoschips zum Schluss zugeben ist Pflicht – sonst sind sie schwarz, bevor der Rest fertig ist.", + "badge": "", + "yield": "ergibt ca. 730 g · ~14 Portionen à 50 g" + }, + { + "num": "10", + "category": "Nuss, Kern & Klassik", + "title": "Tahin-Sesam-Halva", + "tagline": "Nährig, orientalisch, unerwartet gut.", + "nutrition": "ca. ≈ 252 kcal · Eiweiß 9 g · Fett 16 g · KH 19 g · Zucker 5 g · Ballaststoffe 5 g", + "ingredients": "250 g Haferflocken · 80 g Mandeln grob · 40 g Sesam · 40 g Sonnenblumenkerne · 3 EL Tahin · 2 EL Kokosöl · 2 EL Honig · 1 Prise Salz", + "steps": "1 Ofen auf 150 °C Umluft, Blech mit Backpapier.\n2 Hafer, Mandeln, Sesam, Kerne und Salz mischen.\n3 Tahin, Kokosöl und Honig leicht erwärmen und glatt rühren.\n4 Über das Trockene gießen, gut mischen.\n5 Aufs Blech, andrücken.\n6 22–26 Min backen, einmal wenden – Sesam bräunt schnell.\n7 Auskühlen lassen.", + "variations": [ + "Schoko 30 g Kakaonibs nach dem Backen", + "Kardamom 1/2 TL gemahlener Kardamom in die Bindung" + ], + "tip": "Coach-Tipp Tahin bringt Kalzium und einen leicht bitteren Halva-Ton – sättigt richtig, kleine Portion reicht.", + "badge": "", + "yield": "ergibt ca. 720 g · ~14 Portionen à 50 g" + }, + { + "num": "11", + "category": "Nuss, Kern & Klassik", + "title": "Ahorn-Pekan", + "tagline": "Der Klassiker – hier bewusst sparsam gesüßt.", + "nutrition": "ca. ≈ 246 kcal · Eiweiß 6 g · Fett 15 g · KH 22 g · Zucker 7 g · Ballaststoffe 4 g", + "ingredients": "290 g Haferflocken · 110 g Pekannüsse grob · 40 g Sonnenblumenkerne · 3 EL Kokosöl · 3 EL Ahornsirup · 1 TL Zimt · 1 Prise Salz", + "steps": "1 Ofen auf 155 °C Umluft, Blech mit Backpapier.\n2 Hafer, Pekannüsse, Kerne, Zimt und Salz mischen.\n3 Kokosöl und Ahornsirup schmelzen, unterrühren.\n4 Aufs Blech, andrücken.\n5 25–30 Min backen, einmal wenden.\n6 Auskühlen lassen – die Karamell-Note kommt erst kalt.", + "variations": [ + "Ahorn-Apfel nach dem Backen getrocknete Apfelstücke unter", + "Weniger süß nur 2 EL Ahornsirup" + ], + "tip": "Coach-Tipp Das klassische Frühstücks-Granola – aber mit 7 g Zucker statt der üblichen 15–20 g im Handel.", + "badge": "", + "yield": "ergibt ca. 750 g · ~15 Portionen à 50 g" + }, + { + "num": "12", + "category": "Frucht & Beere", + "title": "Beeren-Chia", + "tagline": "Säuerlich-fruchtig, Beeren als frisches Topping.", + "nutrition": "ca. ≈ 232 kcal · Eiweiß 7 g · Fett 12 g · KH 24 g · Zucker 7 g · Ballaststoffe 6 g", + "ingredients": "280 g Haferflocken · 80 g Mandeln grob · 30 g Chiasamen · 30 g Sonnenblumenkerne · 3 EL Kokosöl · 2 EL Ahornsirup · 1 Prise Salz · 40 g gefriergetrocknete Beeren (nach dem Backen)", + "steps": "1 Ofen auf 155 °C Umluft, Blech mit Backpapier.\n2 Hafer, Mandeln, Chia, Kerne und Salz mischen.\n3 Kokosöl und Ahornsirup schmelzen, unterrühren.\n4 Aufs Blech, andrücken.\n5 25 Min backen, einmal wenden.\n6 Auskühlen lassen, dann die gefriergetrockneten Beeren untermischen.\n7 Frische oder TK-Beeren erst in der Schüssel dazugeben, nicht ins Vorratsglas.", + "variations": [ + "Himbeer-Kakao 20 g Kakao + gefriergetrocknete Himbeeren", + "Frisch in der Schüssel mit TK-Heidelbeeren toppen" + ], + "tip": "Coach-Tipp Gefrorene Beeren statt Gummibärchen – hier direkt eingebaut. Säure bremst die Süß-Gier.", + "badge": "", + "yield": "ergibt ca. 740 g · ~14 Portionen à 50 g" + }, + { + "num": "13", + "category": "Frucht & Beere", + "title": "Apfel-Zimt-Bratapfel", + "tagline": "Apfelkuchen-Gefühl ohne Teig.", + "nutrition": "ca. ≈ 234 kcal · Eiweiß 6 g · Fett 12 g · KH 25 g · Zucker 8 g · Ballaststoffe 5 g", + "ingredients": "290 g Haferflocken · 90 g Walnüsse grob · 40 g Sonnenblumenkerne · 2 TL Zimt · 3 EL Kokosöl · 2 EL Ahornsirup · 1 Prise Salz · 60 g getrocknete Apfelstücke (nach dem Backen)", + "steps": "1 Ofen auf 155 °C Umluft, Blech mit Backpapier.\n2 Hafer, Walnüsse, Kerne, Zimt und Salz mischen.\n3 Kokosöl und Ahornsirup schmelzen, unterrühren.\n4 Aufs Blech, andrücken.\n5 25–30 Min backen, einmal wenden.\n6 Auskühlen lassen, dann getrocknete Apfelstücke untermischen (sonst werden sie zäh/hart).", + "variations": [ + "Rosine zusätzlich eine kleine Handvoll Rosinen", + "Warm in der Schüssel mit warmem, gewürfeltem Apfel toppen" + ], + "tip": "Coach-Tipp Der Kuchen-Ersatz. Über Skyr mit warmem Apfel schmeckt es wie Apfelstrudel – ohne Zuckerschock.", + "badge": "", + "yield": "ergibt ca. 760 g · ~15 Portionen à 50 g" + }, + { + "num": "14", + "category": "Frucht & Beere", + "title": "Bananenbrot-Granola", + "tagline": "Reife Banane bringt Süße fast ohne Sirup.", + "nutrition": "ca. ≈ 228 kcal · Eiweiß 7 g · Fett 12 g · KH 24 g · Zucker 8 g · Ballaststoffe 5 g", + "ingredients": "270 g Haferflocken · 90 g Walnüsse grob · 40 g Leinsamen · 1 sehr reife Banane · 2 EL Kokosöl · 1 EL Ahornsirup · 1 TL Zimt · 1 Prise Salz", + "steps": "1 Ofen auf 150 °C Umluft, Blech mit Backpapier.\n2 Banane mit einer Gabel fein zerdrücken.\n3 Hafer, Walnüsse, Leinsamen, Zimt und Salz mischen.\n4 Bananenpüree, geschmolzenes Kokosöl und Ahornsirup verrühren, über das Trockene geben.\n5 Gut mischen, aufs Blech, andrücken.\n6 30–35 Min backen – Banane braucht länger zum Trocknen; wenden nach 20 Min.\n7 Vollständig auskühlen lassen (bleibt sonst weich).", + "variations": [ + "Schoko-Chunk 40 g Zartbitter nach dem Backen", + "Erdnuss 2 EL Erdnussmus in die Bindung" + ], + "tip": "Coach-Tipp Braune Bananen sinnvoll verwerten statt wegwerfen – sie ersetzen einen Gutteil des Sirups.", + "badge": "", + "yield": "ergibt ca. 700 g · ~14 Portionen à 50 g" + }, + { + "num": "15", + "category": "Frucht & Beere", + "title": "Orange-Cranberry", + "tagline": "Weihnachtlich-fruchtig, säuerlicher Kontrast.", + "nutrition": "ca. ≈ 238 kcal · Eiweiß 6 g · Fett 12 g · KH 26 g · Zucker 9 g · Ballaststoffe 5 g", + "ingredients": "280 g Haferflocken · 90 g Mandeln grob · 40 g Sonnenblumenkerne · Abrieb 1 Bio-Orange · 3 EL Kokosöl · 2 EL Ahornsirup · 1 Prise Salz · 50 g getrocknete Cranberries (nach dem Backen)", + "steps": "1 Ofen auf 155 °C Umluft, Blech mit Backpapier.\n2 Hafer, Mandeln, Kerne, Orangenabrieb und Salz mischen.\n3 Kokosöl und Ahornsirup schmelzen, unterrühren.\n4 Aufs Blech, andrücken.\n5 25 Min backen, einmal wenden.\n6 Auskühlen lassen, dann Cranberries untermischen.", + "variations": [ + "Pistazie statt Mandeln für Farbe und Weihnachtslook", + "Zitrone Zitronenabrieb statt Orange" + ], + "tip": "Coach-Tipp Cranberries sind von Natur aus sauer und oft gezuckert – lieber sparsam, der Orangenabrieb trägt den Geschmack.", + "badge": "", + "yield": "ergibt ca. 740 g · ~14 Portionen à 50 g" + }, + { + "num": "16", + "category": "Frucht & Beere", + "title": "Trail-Mix-Granola", + "tagline": "Studentenfutter-Idee, ideal als Wander-/Büro-Snack.", + "nutrition": "ca. ≈ 256 kcal · Eiweiß 8 g · Fett 15 g · KH 23 g · Zucker 9 g · Ballaststoffe 5 g", + "ingredients": "250 g Haferflocken · 120 g gemischte Nüsse grob · 40 g Kürbiskerne · 3 EL Kokosöl · 2 EL Ahornsirup · 1 Prise Salz · 50 g Rosinen + 30 g Zartbitter-Tropfen (nach dem Backen)", + "steps": "1 Ofen auf 155 °C Umluft, Blech mit Backpapier.\n2 Hafer, Nüsse, Kürbiskerne und Salz mischen.\n3 Kokosöl und Ahornsirup schmelzen, unterrühren.\n4 Aufs Blech, andrücken.\n5 25–30 Min backen, einmal wenden.\n6 Vollständig auskühlen lassen, dann Rosinen und Zartbitter untermischen (Schoko schmilzt sonst).", + "variations": [ + "Salzig 1 Prise mehr Salz + geröstete Erdnüsse", + "Ohne Schoko stattdessen mehr Nüsse" + ], + "tip": "Coach-Tipp Portionsbeutel abwiegen und in die Tasche – das ist dein Büro-Swap gegen Chips und Gummibärchen.", + "badge": "", + "yield": "ergibt ca. 780 g · ~15 Portionen à 50 g" + }, + { + "num": "17", + "category": "Gewürz & Saison", + "title": "Kürbisgewürz (Pumpkin Spice)", + "tagline": "Herbst im Glas, wärmende Gewürze.", + "nutrition": "ca. ≈ 240 kcal · Eiweiß 8 g · Fett 14 g · KH 21 g · Zucker 6 g · Ballaststoffe 5 g", + "ingredients": "270 g Haferflocken · 90 g Pekannüsse grob · 60 g Kürbiskerne · 2 TL Zimt · 1/2 TL Ingwer · 1/2 TL Muskat · 1 Prise Nelke · 3 EL Kokosöl · 2 EL Ahornsirup · 1 Prise Salz", + "steps": "1 Ofen auf 155 °C Umluft, Blech mit Backpapier.\n2 Hafer, Pekannüsse, Kürbiskerne, alle Gewürze und Salz gründlich mischen.\n3 Kokosöl und Ahornsirup schmelzen, unterrühren.\n4 Aufs Blech, andrücken.\n5 25–30 Min backen, einmal wenden.\n6 Auskühlen lassen.", + "variations": [ + "Mit Kürbis 2 EL Kürbispüree in die Bindung, 5 Min länger backen", + "Ahorn-Pekan-Plus zusätzlich Vanille" + ], + "tip": "Coach-Tipp Das Gewürz macht die Süße subjektiv größer – typischer Trick, um mit weniger Zucker auszukommen.", + "badge": "", + "yield": "ergibt ca. 740 g · ~14 Portionen à 50 g" + }, + { + "num": "18", + "category": "Gewürz & Saison", + "title": "Ingwer-Kurkuma „Golden“", + "tagline": "Wärmend, leicht scharf, entzündungsfreundlich.", + "nutrition": "ca. ≈ 242 kcal · Eiweiß 7 g · Fett 14 g · KH 22 g · Zucker 6 g · Ballaststoffe 5 g", + "ingredients": "270 g Haferflocken · 90 g Cashews grob · 40 g Sonnenblumenkerne · 1 TL Kurkuma · 1 TL Ingwer · 1 Prise Pfeffer · 3 EL Kokosöl · 2 EL Honig · 1 Prise Salz", + "steps": "1 Ofen auf 150 °C Umluft, Blech mit Backpapier.\n2 Hafer, Cashews, Kerne, Kurkuma, Ingwer, Pfeffer und Salz mischen (Pfeffer macht Kurkuma verfügbar).\n3 Kokosöl und Honig schmelzen, unterrühren.\n4 Aufs Blech, andrücken.\n5 25 Min backen, einmal wenden.\n6 Auskühlen lassen.", + "variations": [ + "Kokos 40 g Kokoschips in den letzten 6 Min", + "Schärfer frischen geriebenen Ingwer in die Bindung" + ], + "tip": "Coach-Tipp Kurkuma färbt alles gelb (auch das Blech-Papier) – harmlos. Eine Prise Pfeffer gehört dazu.", + "badge": "", + "yield": "ergibt ca. 730 g · ~14 Portionen à 50 g" + }, + { + "num": "19", + "category": "Gewürz & Saison", + "title": "Spekulatius-Lebkuchen", + "tagline": "Winter-Edition, ohne die übliche Zuckerlast.", + "nutrition": "ca. ≈ 238 kcal · Eiweiß 7 g · Fett 13 g · KH 23 g · Zucker 7 g · Ballaststoffe 5 g", + "ingredients": "280 g Haferflocken · 90 g Mandeln grob · 40 g Haselnüsse grob · 2 TL Lebkuchengewürz · 1 TL Zimt · 3 EL Kokosöl · 3 EL Ahornsirup · 1 Prise Salz", + "steps": "1 Ofen auf 155 °C Umluft, Blech mit Backpapier.\n2 Hafer, Mandeln, Haselnüsse, Lebkuchengewürz, Zimt und Salz mischen.\n3 Kokosöl und Ahornsirup schmelzen, unterrühren.\n4 Aufs Blech, andrücken.\n5 25–30 Min backen, einmal wenden.\n6 Auskühlen lassen.", + "variations": [ + "Orange Orangenabrieb dazu", + "Schoko 40 g Zartbitter nach dem Backen" + ], + "tip": "Coach-Tipp Weihnachtsgebäck-Ersatz für den Advent – stillt den Spekulatius-Reflex, ohne die Kekstüte.", + "badge": "", + "yield": "ergibt ca. 750 g · ~15 Portionen à 50 g" + }, + { + "num": "20", + "category": "Gewürz & Saison", + "title": "Chai-Gewürz", + "tagline": "Schwarzer Tee trifft wärmende Gewürze.", + "nutrition": "ca. ≈ 239 kcal · Eiweiß 7 g · Fett 13 g · KH 23 g · Zucker 6 g · Ballaststoffe 5 g", + "ingredients": "280 g Haferflocken · 90 g Mandeln grob · 40 g Sonnenblumenkerne · 1 TL Zimt · 1/2 TL Kardamom · 1/2 TL Ingwer · 1 Prise Nelke · 1 Prise Pfeffer · 3 EL Kokosöl · 2 EL Honig · 1 Prise Salz", + "steps": "1 Ofen auf 155 °C Umluft, Blech mit Backpapier.\n2 Hafer, Mandeln, Kerne und alle Gewürze mischen.\n3 Kokosöl und Honig schmelzen, unterrühren.\n4 Aufs Blech, andrücken.\n5 25 Min backen, einmal wenden.\n6 Auskühlen lassen.", + "variations": [ + "Vanille-Chai 1 TL Vanille zugeben", + "Milchkaffee über warme Milch als Chai-Porridge" + ], + "tip": "Coach-Tipp Kardamom und Nelke sind intensiv – sparsam dosieren, sonst überdeckt es alles.", + "badge": "", + "yield": "ergibt ca. 730 g · ~14 Portionen à 50 g" + }, + { + "num": "21", + "category": "Gewürz & Saison", + "title": "Karottenkuchen-Granola", + "tagline": "Carrot Cake als Frühstücks-Topping.", + "nutrition": "ca. ≈ 236 kcal · Eiweiß 7 g · Fett 13 g · KH 23 g · Zucker 8 g · Ballaststoffe 5 g", + "ingredients": "260 g Haferflocken · 80 g Walnüsse grob · 40 g Sonnenblumenkerne · 80 g fein geraspelte Möhre · 2 TL Zimt · 1/2 TL Muskat · 3 EL Kokosöl · 2 EL Ahornsirup · 1 Prise Salz · 30 g Rosinen (nach dem Backen)", + "steps": "1 Ofen auf 150 °C Umluft, Blech mit Backpapier.\n2 Möhre fein raspeln und mit Küchenpapier gut ausdrücken – sonst wird das Granola nicht knusprig.\n3 Hafer, Walnüsse, Kerne, Gewürze, Salz und die geraspelte Möhre mischen.\n4 Kokosöl und Ahornsirup schmelzen, unterrühren.\n5 Aufs Blech, dünn verteilen, andrücken.\n6 30–35 Min backen – Möhre bringt Feuchtigkeit, länger und niedriger trocknen; nach 20 Min wenden.\n7 Vollständig auskühlen lassen, dann Rosinen untermischen.", + "variations": [ + "Frischkäse-Note über Skyr = Carrot-Cake-Frosting-Effekt", + "Kokos 2 EL Kokosraspel dazu" + ], + "tip": "Coach-Tipp Möhre gut ausdrücken ist der ganze Trick – sonst bleibt es zäh statt knusprig.", + "badge": "", + "yield": "ergibt ca. 740 g · ~14 Portionen à 50 g" + }, + { + "num": "22", + "category": "Protein-Boost & Post-Workout", + "title": "Vanille-Whey-Cluster", + "tagline": "Post-Workout: viel Eiweiß, feste Brocken.", + "nutrition": "ca. ≈ 260 kcal · Eiweiß 13 g · Fett 13 g · KH 22 g · Zucker 5 g · Ballaststoffe 5 g", + "ingredients": "240 g Haferflocken · 70 g Mandeln grob · 40 g Sonnenblumenkerne · 40 g Whey Vanille · 3 EL Mandelmus · 2 EL Kokosöl · 2 EL Ahornsirup · 1 Eiweiß · 1 Prise Salz", + "steps": "1 Ofen auf 150 °C Umluft, Blech mit Backpapier.\n2 Hafer, Mandeln, Kerne und Whey mischen – Pulver klumpenfrei unterheben.\n3 Mandelmus, Kokosöl und Ahornsirup schmelzen, unterrühren.\n4 Eiweiß schaumig schlagen, unterheben.\n5 Fest aufs Blech drücken.\n6 25–30 Min bei 150 °C backen, NICHT rühren – Whey bräunt früh, Farbe kontrollieren.\n7 Vollständig auskühlen lassen, in Brocken brechen.", + "variations": [ + "Schoko-Whey Schoko-Whey + 15 g Kakao", + "Erdnuss Erdnussmus statt Mandelmus" + ], + "tip": "Coach-Tipp Über 150 g Skyr sind das ~30 g Eiweiß – idealer Post-Session-Snack rund ums Training.", + "badge": "", + "yield": "ergibt ca. 700 g · ~14 Portionen à 50 g" + }, + { + "num": "23", + "category": "Protein-Boost & Post-Workout", + "title": "Schoko-Casein-Abend", + "tagline": "Langsames Eiweiß für den Couch-Abend.", + "nutrition": "ca. ≈ 256 kcal · Eiweiß 13 g · Fett 13 g · KH 21 g · Zucker 5 g · Ballaststoffe 6 g", + "ingredients": "240 g Haferflocken · 70 g Haselnüsse grob · 40 g Leinsamen · 40 g Casein Schoko · 20 g Kakao · 3 EL Mandelmus · 2 EL Kokosöl · 2 EL Ahornsirup · 1 Eiweiß · 1 Prise Salz", + "steps": "1 Ofen auf 150 °C Umluft, Blech mit Backpapier.\n2 Hafer, Haselnüsse, Leinsamen, Casein und Kakao mischen.\n3 Mandelmus, Kokosöl und Ahornsirup schmelzen, unterrühren.\n4 Eiweiß schaumig schlagen, unterheben.\n5 Fest aufs Blech drücken.\n6 25–28 Min backen, NICHT rühren – Kakao + Casein bräunen schnell.\n7 Vollständig auskühlen lassen, in Brocken brechen.", + "variations": [ + "Meersalz grobes Salz vor dem Backen aufstreuen", + "Nibs 30 g Kakaonibs nach dem Backen" + ], + "tip": "Coach-Tipp Casein macht langsam satt – als Abend-Dessert über Magerquark dein bester Schoko-Couch-Ersatz.", + "badge": "", + "yield": "ergibt ca. 700 g · ~14 Portionen à 50 g" + }, + { + "num": "24", + "category": "Protein-Boost & Post-Workout", + "title": "Skyr-Topper · grob & pur", + "tagline": "Extra grobe Brocken, kaum Süße – nur zum Toppen.", + "nutrition": "ca. ≈ 244 kcal · Eiweiß 9 g · Fett 15 g · KH 19 g · Zucker 3 g · Ballaststoffe 6 g", + "ingredients": "240 g Haferflocken kernig · 100 g gemischte Nüsse grob · 40 g Kürbiskerne · 30 g Leinsamen · 3 EL Mandelmus · 2 EL Kokosöl · 1 EL Ahornsirup · 1 Eiweiß · 1 Prise Salz", + "steps": "1 Ofen auf 150 °C Umluft, Blech mit Backpapier.\n2 Hafer, Nüsse, Kerne und Leinsamen mischen.\n3 Mandelmus, Kokosöl und Ahornsirup schmelzen, unterrühren.\n4 Eiweiß schaumig schlagen, unterheben – für maximal grobe Cluster sehr fest andrücken.\n5 28–32 Min backen, NICHT rühren.\n6 Vollständig auskühlen lassen, in große Brocken brechen.", + "variations": [ + "Herzhaft Salz erhöhen, Süße weglassen", + "Zimt 1 TL Zimt für milde Süße ohne Zucke" + ], + "tip": "Coach-Tipp Bewusst wenig Zucker (3 g) – die Süße kommt vom Obst in der Schüssel. Das gröbste Rezept, perfekt als Crunch auf Quark.", + "badge": "", + "yield": "ergibt ca. 720 g · ~14 Portionen à 50 g" + }, + { + "num": "25", + "category": "Grain-free & Low-Carb", + "title": "Grain-free Nuss-Granola", + "tagline": "Ohne Getreide, sehr wenig KH, viel Crunch.", + "nutrition": "ca. ≈ 272 kcal · Eiweiß 9 g · Fett 23 g · KH 8 g · Zucker 4 g · Ballaststoffe 6 g", + "ingredients": "120 g Mandeln · 120 g Pekannüsse · 80 g Kürbiskerne · 60 g Sonnenblumenkerne · 80 g Kokoschips · 30 g Leinsamen · 3 EL Kokosöl · 2 EL Ahornsirup · 1 Eiweiß · 1 Prise Salz", + "steps": "1 Ofen auf 150 °C Umluft, Blech mit Backpapier.\n2 Nüsse grob hacken; mit Kernen und Leinsamen mischen (Kokoschips zurückhalten).\n3 Kokosöl und Ahornsirup schmelzen, unterrühren.\n4 Eiweiß schaumig schlagen, unterheben – bindet die Cluster ohne Hafer.\n5 Fest aufs Blech drücken.\n6 18–22 Min backen; in den letzten 6 Min Kokoschips untermischen – Nüsse und Kokos verbrennen schnell.\n7 Vollständig auskühlen lassen, in Brocken brechen.", + "variations": [ + "Schoko 20 g Kakao ins Trockene", + "Zimt-Kokos 1 TL Zimt zugeben" + ], + "tip": "Coach-Tipp Für Tage mit wenig Bewegung: kaum KH, sättigt lange über Fett und Ballaststoffe. Portion trotzdem abwiegen – sehr kaloriendicht.", + "badge": "", + "yield": "ergibt ca. 650 g · ~13 Portionen à 50 g" + }, + { + "num": "26", + "category": "Grain-free & Low-Carb", + "title": "Buchweizen-Buckini-Crunch", + "tagline": "Aktivierter Buchweizen – extrem knusprig, glutenfrei.", + "nutrition": "ca. ≈ 234 kcal · Eiweiß 8 g · Fett 11 g · KH 27 g · Zucker 5 g · Ballaststoffe 5 g", + "ingredients": "200 g roher Buchweizen · 100 g Haferflocken (oder mehr Buchweizen für glutenfrei) · 80 g Mandeln grob · 40 g Sonnenblumenkerne · 3 EL Kokosöl · 2 EL Ahornsirup · 1 TL Zimt · 1 Prise Salz", + "steps": "1 Buchweizen 4–6 h in Wasser einweichen, dann gründlich abspülen und gut abtropfen lassen (macht ihn\nbekömmlicher und knuspriger).\n2 Ofen auf 150 °C Umluft, Blech mit Backpapier.\n3 Buchweizen, Hafer, Mandeln, Kerne, Zimt und Salz mischen.\n4 Kokosöl und Ahornsirup schmelzen, unterrühren.\n5 Dünn aufs Blech, andrücken.\n6 30–35 Min backen, alle 10 Min wenden – eingeweichter Buchweizen braucht länger zum Trocknen.\n7 Vollständig auskühlen lassen – wird dann glasig-knusprig.", + "variations": [ + "Kakao 20 g Kakao für Schoko-Buckini", + "Schnell Buchweizen ohne Einweichen – etwas härter, geht abe" + ], + "tip": "Coach-Tipp Der knusprigste Crunch überhaupt. Wenn Hafer dir schwer im Magen liegt, ist das die leichtere Basis.", + "badge": "", + "yield": "ergibt ca. 700 g · ~14 Portionen à 50 g" + }, + { + "num": "27", + "category": "Ohne Ofen & Overnight", + "title": "No-Bake Erdnuss-Cluster", + "tagline": "Ohne Ofen, aus dem Kühlschrank fest.", + "nutrition": "ca. ≈ 248 kcal · Eiweiß 10 g · Fett 14 g · KH 22 g · Zucker 9 g · Ballaststoffe 5 g", + "ingredients": "200 g Haferflocken · 60 g Erdnüsse grob · 30 g Leinsamen · 6 entsteinte Datteln · 4 EL Erdnussmus · 2 EL Kokosöl · 1 Prise Salz · optional 20 g Zartbitter", + "steps": "1 Datteln 10 Min in warmem Wasser einweichen, abgießen und zu einer Paste zerdrücken.\n2 Erdnussmus und Kokosöl leicht erwärmen, mit der Dattelpaste glatt rühren.\n3 Hafer, Erdnüsse, Leinsamen und Salz untermischen, bis alles klebt.\n4 Masse fest in eine mit Papier ausgelegte Form drücken (ca. 1,5 cm).\n5 Mindestens 1 h kalt stellen, bis sie fest ist.\n6 In Riegel oder Brocken schneiden; optional mit geschmolzener Zartbitter bestreichen.\n7 Kühl gelagert 1 Woche haltbar (kein Backen = kürzer haltbar als gebäcken).", + "variations": [ + "Schoko 20 g Kakao in die Paste", + "Riegel als Snack-Riegel schneiden für unterwegs" + ], + "tip": "Coach-Tipp Kein Ofen nötig – aber Datteln bringen Zucker (9 g). Portion klein halten, dann ein solider Riegel-Ersatz.", + "badge": "", + "yield": "ergibt ca. 600 g · ~12 Portionen à 50 g" + }, + { + "num": "28", + "category": "Ohne Ofen & Overnight", + "title": "Overnight-Bircher-Mix", + "tagline": "Trockenmischung zum Einweichen – quasi Granola rückwärts.", + "nutrition": "ca. ≈ 230 kcal · Eiweiß 8 g · Fett 10 g · KH 27 g · Zucker 6 g · Ballaststoffe 6 g", + "ingredients": "350 g Haferflocken · 60 g Mandeln grob · 40 g Leinsamen · 30 g Chiasamen · 1 TL Zimt · 40 g Rosinen – pro Portion: 150 g Skyr/Milch + geraspelter Apfel Trockenmix für ~14 Portionen à 50 g", + "steps": "1 Alle trockenen Zutaten mischen und in ein großes Vorratsglas füllen (kein Backen).\n2 Am Vorabend 50 g Mix mit 150 g Skyr oder Milch übergießen und verrühren.\n3 Optional geraspelten Apfel oder Beeren unterheben.\n4 Abgedeckt über Nacht in den Kühlschrank.\n5 Am Morgen kurz umrühren; bei Bedarf einen Schuss Milch für die Konsistenz.\n6 Nach Wunsch mit frischem Obst oder einem Teelöffel gebackenem Granola toppen.", + "variations": [ + "Schoko 1 TL Kakao einrühren", + "Protein 1/2 Portion Whey unterrühren", + "Beere TK-Beeren mit einweichen" + ], + "tip": "Coach-Tipp Das Löst die Ausrede „keine Zeit“ – abends 30 Sekunden Aufwand, morgens Eiweiß-Basis ohne Nachdenken.", + "badge": "", + "yield": "" + } + ] + } +} \ No newline at end of file diff --git a/plugins/trainingsplan/presets/nachmittags.json b/plugins/trainingsplan/presets/nachmittags.json new file mode 100644 index 0000000..109faf7 --- /dev/null +++ b/plugins/trainingsplan/presets/nachmittags.json @@ -0,0 +1,1450 @@ +{ + "name": "Kaffee-Snacks (Nachmittag)", + "config": { + "type": "recipe", + "meta": { + "title": "Kaffee-Snacks für den Nachmittag", + "header_left": "KAFFEE-SNACKS · Nachmittags-Edition · Begleitend zum 8-Wochen-Plan", + "footer_left": "Portion abfüllen · Rest aus dem Sichtfeld · bewusst zum Kaffee", + "output_name": "KaffeeSnacks_Nachmittag.pdf", + "doc_title": "Kaffee-Snacks für den Nachmittag", + "brand_color": "#6D28D9", + "steps_label": "SO GEHT'S" + }, + "intro": { + "headline": "Kaffee-Snacks für den Nachmittag", + "subtitle": "100 einfache Snacks für die Lücke zwischen Mittag und Abendessen – Grab & Eat und Vorrat, inkl.", + "paragraphs": [ + "Airfryer-Rezepten und Orientierungs-Nährwerten.", + "Nicht verzichten, sondern clever ersetzen: ein Eiweiß- oder Ballaststoff-Snack macht satt, statt nur den Mund zu", + "beschäftigen – und nimmt dem späteren Süßigkeiten-Schub die Wucht. Der Schwerpunkt liegt auf Grab & Eat (null bis", + "zwei Minuten) und auf Vorrat, den du einmal machst und die Woche über greifbar hast. Als Orientierung reichen", + "nachmittags grob 150–250 kcal – kein Ziel, sondern eine Richtung: der echte Hebel ist der Tausch, nicht das Zählen.", + "Jede Karte nennt unter NÄHRWERTE ca. Richtwerte pro Portion (kcal + Eiweiß, Fett, KH, Zucker, Ballaststoffe); bei", + "Vorrat gilt der Wert für den ganzen Ansatz. Rezepte mit AIRFRYER-Badge nennen Zeit und Temperatur für Airfryer und", + "Ofen. Für exakte Zahlen dein Produkt in YAZIO scannen.", + "Die eine Grundregel: vorher portionieren", + "Teller oder Tütchen fertig machen → Rest aus dem Sichtfeld → bewusst zum Kaffee genießen. Aus der offenen Packung", + "am Schreibtisch zu grasen ist dasselbe Loch wie früher die Chipstüte. Die abgefüllte Portion ist deine Grenze – nicht dein", + "Sättigungsgefühl." + ], + "rule_head": "", + "rule_text": "", + "categories_head": "13 Kategorien" + }, + "categories": [ + { + "name": "Cremig & süß · Löffel zum Kaffee", + "color": "#7C3AED" + }, + { + "name": "Schnell & süß · Kuchengefühl in Minuten", + "color": "#0EA5E9" + }, + { + "name": "Frisch & fruchtig · schnell gegriffen", + "color": "#EA580C" + }, + { + "name": "Knabbern & herzhaft · Chip-Ersatz", + "color": "#16A34A" + }, + { + "name": "Herzhaft & sättigend · kleine Happen", + "color": "#DC2626" + }, + { + "name": "Grab & Eat · null Aufwand", + "color": "#DB2777" + }, + { + "name": "Büro-Schublade · haltbar & vorportioniert", + "color": "#B45309" + }, + { + "name": "Energy Bites & Kugeln · Vorrat", + "color": "#4338CA" + }, + { + "name": "Riegel & Gebäck · Vorrat", + "color": "#2563EB" + }, + { + "name": "Airfryer · herzhaft & knusprig", + "color": "#475569" + }, + { + "name": "Airfryer · süß & Gebäck", + "color": "#A21CAF" + }, + { + "name": "Warm & gemütlich zum Kaffee", + "color": "#059669" + }, + { + "name": "Kaffee-Drinks & Protein-Shakes", + "color": "#92400E" + } + ], + "recipes": [ + { + "num": "1", + "category": "Cremig & süß · Löffel zum Kaffee", + "title": "Skyr mit Beeren & Zimt", + "tagline": "Der schnelle Kuchen-Ersatz mit Eiweiß.", + "nutrition": "ca. ≈ 130 kcal · Eiweiß 18 g · Fett 0 g · KH 12 g · Zucker 9 g · Ballaststoffe 3 g", + "ingredients": "150 g Skyr · 1 Handvoll TK-Beeren · Zimt", + "steps": "Beeren untermischen, Zimt drüber – tauen im Skyr auf.", + "variations": [ + "+ 1 TL Honig", + "+ Kakaonibs", + "+ 1 EL Haferflocken", + "+ Vanille" + ], + "tip": "Nachmittags-Tipp Beeren färben den Skyr lila und bringen Süße ohne Zucker.", + "badge": "", + "yield": "" + }, + { + "num": "2", + "category": "Cremig & süß · Löffel zum Kaffee", + "title": "Cappuccino-Quark", + "tagline": "Schmeckt wie Kaffeecreme, hält lange satt.", + "nutrition": "ca. ≈ 160 kcal · Eiweiß 28 g · Fett 1 g · KH 8 g · Zucker 5 g · Ballaststoffe 1 g", + "ingredients": "200 g Magerquark · 1 TL lösl. Espresso · Prise Kakao · etwas Süße", + "steps": "Alles glatt rühren, evtl. Schuss Milch.", + "variations": [ + "+ Zimt", + "+ Kakaonibs", + "Vanille-Version", + "+ 1 TL Erdnussmus" + ], + "tip": "Nachmittags-Tipp Der Kaffee gibt Bitternote – wirkt süßer als er ist.", + "badge": "", + "yield": "" + }, + { + "num": "3", + "category": "Cremig & süß · Löffel zum Kaffee", + "title": "Skyr-„Tiramisu“ im Glas", + "tagline": "Löffelbiskuit-Gefühl ohne Zuckerbombe.", + "nutrition": "ca. ≈ 200 kcal · Eiweiß 19 g · Fett 3 g · KH 22 g · Zucker 8 g · Ballaststoffe 3 g", + "ingredients": "150 g Skyr · 2 EL Haferflocken · Schuss Espresso · Kakao", + "steps": "Haferflocken in Espresso tränken, mit Skyr schichten, Kakao drüber.", + "variations": [], + "tip": "Nachmittags-Tipp Espresso-Hafer statt Keksboden – selbe Idee, mehr Eiweiß.", + "badge": "", + "yield": "" + }, + { + "num": "4", + "category": "Cremig & süß · Löffel zum Kaffee", + "title": "Hüttenkäse mit Honig & Walnuss", + "tagline": "Süß-salzig, überraschend gut zum Kaffee.", + "nutrition": "ca. ≈ 230 kcal · Eiweiß 24 g · Fett 12 g · KH 8 g · Zucker 7 g · Ballaststoffe 1 g", + "ingredients": "200 g Hüttenkäse · 1 TL Honig · 15 g Walnüsse", + "steps": "Hüttenkäse in die Schale, Honig und Nuss drüber.", + "variations": [], + "tip": "Nachmittags-Tipp Sehr eiweißreich – trägt bis zum Abendessen.", + "badge": "", + "yield": "" + }, + { + "num": "5", + "category": "Cremig & süß · Löffel zum Kaffee", + "title": "Schoko-Protein-Pudding", + "tagline": "Dunkle Schokonote ohne Riegel.", + "nutrition": "ca. ≈ 150 kcal · Eiweiß 24 g · Fett 2 g · KH 9 g · Zucker 5 g · Ballaststoffe 2 g", + "ingredients": "150 g Magerquark · 1 EL Kakao · etwas Süße · Schuss Milch", + "steps": "Kakao und Süße einrühren, mit Milch cremig ziehen.", + "variations": [ + "+ Erdnussmus", + "+ 1 TL Espresso", + "+ 1/2 Banane", + "+ Kakaonibs" + ], + "tip": "Nachmittags-Tipp Deckt die Schokolust mit fast nur Eiweiß.", + "badge": "", + "yield": "" + }, + { + "num": "6", + "category": "Cremig & süß · Löffel zum Kaffee", + "title": "Vanille-Quark mit Nuss-Krokant", + "tagline": "Krokant gibt Biss zum weichen Quark.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 20 g · Fett 8 g · KH 8 g · Zucker 6 g · Ballaststoffe 1 g", + "ingredients": "150 g Quark · Vanille · 1 TL geh. Nüsse (in der Pfanne geröstet)", + "steps": "Nüsse ohne Öl rösten, über den Vanille-Quark streuen.", + "variations": [], + "tip": "Nachmittags-Tipp Warme Nuss auf kaltem Quark – kleiner Kontrast, große Wirkung.", + "badge": "", + "yield": "" + }, + { + "num": "7", + "category": "Cremig & süß · Löffel zum Kaffee", + "title": "Skyr mit Apfel & Zimt", + "tagline": "Apfel-Crunch im cremigen Skyr.", + "nutrition": "ca. ≈ 150 kcal · Eiweiß 17 g · Fett 0 g · KH 18 g · Zucker 14 g · Ballaststoffe 2 g", + "ingredients": "150 g Skyr · 1/2 Apfel gewürfelt · Zimt", + "steps": "Apfel würfeln, unterheben, Zimt drüber.", + "variations": [], + "tip": "Nachmittags-Tipp Frischer Biss statt Kuchengabel.", + "badge": "", + "yield": "" + }, + { + "num": "8", + "category": "Cremig & süß · Löffel zum Kaffee", + "title": "Griechischer Joghurt mit Beeren-Swirl", + "tagline": "Vollfett macht satt – kleine Portion reicht.", + "nutrition": "ca. ≈ 170 kcal · Eiweiß 7 g · Fett 14 g · KH 9 g · Zucker 8 g · Ballaststoffe 2 g", + "ingredients": "150 g griech. Joghurt 10% · 2 EL zerdrückte Beeren", + "steps": "Beeren zerdrücken, in den Joghurt strudeln.", + "variations": [], + "tip": "Nachmittags-Tipp Das Fett sättigt – bewusst eine kleine Schale.", + "badge": "", + "yield": "" + }, + { + "num": "9", + "category": "Cremig & süß · Löffel zum Kaffee", + "title": "Kaffee-Bananen-Creme", + "tagline": "Süße kommt aus der Banane, kein Zucker nötig.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 20 g · Fett 1 g · KH 24 g · Zucker 16 g · Ballaststoffe 2 g", + "ingredients": "1/2 Banane · 150 g Quark · 1 TL lösl. Espresso", + "steps": "Banane zerdrücken, mit Quark und Kaffee glatt rühren.", + "variations": [], + "tip": "Nachmittags-Tipp Reife Banane süßt von allein.", + "badge": "", + "yield": "" + }, + { + "num": "10", + "category": "Cremig & süß · Löffel zum Kaffee", + "title": "Zimt-Quark mit Kaffeekrokant", + "tagline": "Bratapfel-Aroma zum Espresso.", + "nutrition": "ca. ≈ 170 kcal · Eiweiß 20 g · Fett 5 g · KH 12 g · Zucker 8 g · Ballaststoffe 2 g", + "ingredients": "150 g Quark · Zimt · 1 TL geröstete Haferflocken + Nuss", + "steps": "Hafer und Nuss trocken rösten, über den Zimt-Quark.", + "variations": [], + "tip": "Nachmittags-Tipp Knusper-Topping macht aus Quark ein Dessert.", + "badge": "", + "yield": "" + }, + { + "num": "11", + "category": "Schnell & süß · Kuchengefühl in Minuten", + "title": "Protein-Mugcake (Schoko)", + "tagline": "Warmer Kuchen in der Tasse, frisch am besten.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 15 g · Fett 8 g · KH 30 g · Zucker 6 g · Ballaststoffe 5 g", + "ingredients": "3 EL Haferflocken · 1 Ei · 1 EL Kakao · Prise Backpulver · Süße", + "steps": "Alles in der Tasse verrühren, 80–90 s in die Mikrowelle.", + "variations": [ + "+ Zartbitter-Drops", + "+ Beeren", + "+ 1 TL Erdnussmus", + "Vanille-Version" + ], + "tip": "Nachmittags-Tipp Der Klassiker gegen den Kaffee-und-Kuchen-Reflex.", + "badge": "", + "yield": "" + }, + { + "num": "12", + "category": "Schnell & süß · Kuchengefühl in Minuten", + "title": "Blitz-Käsekuchen im Glas", + "tagline": "No-Bake, in 2 Minuten geschichtet.", + "nutrition": "ca. ≈ 200 kcal · Eiweiß 20 g · Fett 5 g · KH 20 g · Zucker 10 g · Ballaststoffe 1 g", + "ingredients": "150 g Magerquark · 1 Vollkornkeks zerbröselt · Süße · Vanille", + "steps": "Keks als Boden, Quark mit Süße drüber schichten.", + "variations": [], + "tip": "Nachmittags-Tipp Käsekuchen-Gefühl ohne Ofen und ohne halbe Torte.", + "badge": "", + "yield": "" + }, + { + "num": "13", + "category": "Schnell & süß · Kuchengefühl in Minuten", + "title": "Bananen-Ei-Pfännchen", + "tagline": "2 Zutaten, warmes Kuchengefühl.", + "nutrition": "ca. ≈ 200 kcal · Eiweiß 9 g · Fett 6 g · KH 28 g · Zucker 15 g · Ballaststoffe 2 g", + "ingredients": "1 Banane · 1 Ei", + "steps": "Zerdrücken, verquirlen, als kleine Pancakes in die Pfanne.", + "variations": [ + "+ Zimt", + "+ Beeren", + "+ 1 EL Haferflocken" + ], + "tip": "Nachmittags-Tipp Nichts süßen – die Banane reicht.", + "badge": "", + "yield": "" + }, + { + "num": "14", + "category": "Schnell & süß · Kuchengefühl in Minuten", + "title": "Apfel-Zimt-Tasse", + "tagline": "Wie warmer Bratapfel, ohne Ofen.", + "nutrition": "ca. ≈ 130 kcal · Eiweiß 5 g · Fett 0 g · KH 25 g · Zucker 20 g · Ballaststoffe 4 g", + "ingredients": "1 Apfel gewürfelt · Zimt · danach Klecks Quark", + "steps": "Apfel mit Zimt 2 min in die Mikrowelle, Quark drauf.", + "variations": [], + "tip": "Nachmittags-Tipp Schnellster Bratapfel der Welt.", + "badge": "", + "yield": "" + }, + { + "num": "15", + "category": "Schnell & süß · Kuchengefühl in Minuten", + "title": "Haferflocken-Mikro-Kuchen", + "tagline": "Löffelbarer Mini-Auflauf.", + "nutrition": "ca. ≈ 230 kcal · Eiweiß 8 g · Fett 4 g · KH 42 g · Zucker 14 g · Ballaststoffe 6 g", + "ingredients": "4 EL Haferflocken · Schuss Milch · 1/2 zerdrückte Banane", + "steps": "Verrühren, 2 min in die Mikrowelle.", + "variations": [], + "tip": "Nachmittags-Tipp Wird fest wie ein warmer Kuchen – mit dem Löffel essen.", + "badge": "", + "yield": "" + }, + { + "num": "16", + "category": "Schnell & süß · Kuchengefühl in Minuten", + "title": "Protein-Waffel", + "tagline": "Wenn das Eisen da ist: knusprig, herzhaft-süß.", + "nutrition": "ca. ≈ 220 kcal · Eiweiß 18 g · Fett 7 g · KH 22 g · Zucker 3 g · Ballaststoffe 3 g", + "ingredients": "3 EL gem. Haferflocken · 1 Ei · 50 g Quark", + "steps": "Zu Teig rühren, im Waffeleisen backen.", + "variations": [], + "tip": "Nachmittags-Tipp Warm mit Beeren oder Quark ein kleines Dessert.", + "badge": "", + "yield": "" + }, + { + "num": "17", + "category": "Schnell & süß · Kuchengefühl in Minuten", + "title": "Schoko-Tassenkuchen ohne Ei", + "tagline": "Vegan-tauglich, saftig.", + "nutrition": "ca. ≈ 200 kcal · Eiweiß 7 g · Fett 4 g · KH 35 g · Zucker 8 g · Ballaststoffe 5 g", + "ingredients": "3 EL gem. Haferflocken · 1 EL Kakao · Schuss Milch · Backpulver", + "steps": "Verrühren, 80 s in die Mikrowelle.", + "variations": [], + "tip": "Nachmittags-Tipp Ohne Ei, trotzdem fluffig.", + "badge": "", + "yield": "" + }, + { + "num": "18", + "category": "Schnell & süß · Kuchengefühl in Minuten", + "title": "Beeren-Quark-Auflauf (Mikro)", + "tagline": "Fluffig-warm, sehr proteinreich.", + "nutrition": "ca. ≈ 200 kcal · Eiweiß 24 g · Fett 6 g · KH 10 g · Zucker 7 g · Ballaststoffe 2 g", + "ingredients": "150 g Quark · 1 Ei · Handvoll Beeren", + "steps": "Quark und Ei verrühren, Beeren rein, 2–3 min Mikrowelle.", + "variations": [], + "tip": "Nachmittags-Tipp Warmer Eiweiß-Snack, der wie Nachtisch schmeckt.", + "badge": "", + "yield": "" + }, + { + "num": "19", + "category": "Frisch & fruchtig · schnell gegriffen", + "title": "Apfelspalten mit Erdnussmus", + "tagline": "Süß-nussig, deckt die Naschlust.", + "nutrition": "ca. ≈ 200 kcal · Eiweiß 6 g · Fett 10 g · KH 24 g · Zucker 18 g · Ballaststoffe 4 g", + "ingredients": "1 Apfel · 1 EL Erdnussmus", + "steps": "Apfel in Spalten, Mus als Dip.", + "variations": [], + "tip": "Nachmittags-Tipp Frucht + gutes Fett – hält länger als Gummibärchen.", + "badge": "", + "yield": "" + }, + { + "num": "20", + "category": "Frisch & fruchtig · schnell gegriffen", + "title": "Beeren-Becher pur", + "tagline": "Wenig kcal, viel Volumen.", + "nutrition": "ca. ≈ 70 kcal · Eiweiß 1 g · Fett 0 g · KH 14 g · Zucker 10 g · Ballaststoffe 4 g", + "ingredients": "150 g gemischte Beeren", + "steps": "In die Schale, fertig.", + "variations": [], + "tip": "Nachmittags-Tipp Füllt das Auge und die Hand für fast nichts.", + "badge": "", + "yield": "" + }, + { + "num": "21", + "category": "Frisch & fruchtig · schnell gegriffen", + "title": "Weintrauben gefroren", + "tagline": "Dein Gummibärchen-Ersatz, kalt und langsam.", + "nutrition": "ca. ≈ 60 kcal · Eiweiß 0 g · Fett 0 g · KH 15 g · Zucker 15 g · Ballaststoffe 1 g", + "ingredients": "1 Handvoll Trauben (TK)", + "steps": "Trauben ins Gefrierfach, gefroren snacken.", + "variations": [], + "tip": "Nachmittags-Tipp Gefroren isst man sie langsam – genau der Trick.", + "badge": "", + "yield": "" + }, + { + "num": "22", + "category": "Frisch & fruchtig · schnell gegriffen", + "title": "Banane mit Zartbitter-Dip", + "tagline": "Schoko als Hülle, Frucht trägt die Süße.", + "nutrition": "ca. ≈ 200 kcal · Eiweiß 3 g · Fett 8 g · KH 28 g · Zucker 18 g · Ballaststoffe 3 g", + "ingredients": "1 Banane · 15 g 85%-Schokolade", + "steps": "Schoko schmelzen, Bananenstücke tunken, kurz fest werden lassen.", + "variations": [], + "tip": "Nachmittags-Tipp Wenig Schoko, viel Effekt.", + "badge": "", + "yield": "" + }, + { + "num": "23", + "category": "Frisch & fruchtig · schnell gegriffen", + "title": "Orangen-Filets mit Zimt", + "tagline": "Erfrischend, passt zum Espresso.", + "nutrition": "ca. ≈ 80 kcal · Eiweiß 1 g · Fett 0 g · KH 18 g · Zucker 14 g · Ballaststoffe 3 g", + "ingredients": "1 Orange · Zimt", + "steps": "Filetieren, Zimt drüber.", + "variations": [], + "tip": "Nachmittags-Tipp Säure weckt – gute Kaffee-Begleitung.", + "badge": "", + "yield": "" + }, + { + "num": "24", + "category": "Frisch & fruchtig · schnell gegriffen", + "title": "Melone-Feta-Spieße", + "tagline": "Süß-salzig, sommerlich.", + "nutrition": "ca. ≈ 140 kcal · Eiweiß 6 g · Fett 8 g · KH 12 g · Zucker 10 g · Ballaststoffe 1 g", + "ingredients": "150 g Melone · 30 g Feta", + "steps": "Würfeln, abwechselnd aufspießen.", + "variations": [], + "tip": "Nachmittags-Tipp Der Salz-Kontrast macht die Frucht spannend.", + "badge": "", + "yield": "" + }, + { + "num": "25", + "category": "Frisch & fruchtig · schnell gegriffen", + "title": "Birne mit Walnuss", + "tagline": "Klassische Kaffee-Kombi.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 3 g · Fett 10 g · KH 24 g · Zucker 16 g · Ballaststoffe 4 g", + "ingredients": "1 Birne · 15 g Walnüsse", + "steps": "Birne in Spalten, Nuss dazu.", + "variations": [], + "tip": "Nachmittags-Tipp Zwei Zutaten, fertig ist der Nachmittagsteller.", + "badge": "", + "yield": "" + }, + { + "num": "26", + "category": "Frisch & fruchtig · schnell gegriffen", + "title": "Physalis-Handvoll", + "tagline": "Klein, süß-säuerlich, beschäftigt die Hände.", + "nutrition": "ca. ≈ 55 kcal · Eiweiß 1 g · Fett 0 g · KH 11 g · Zucker 8 g · Ballaststoffe 3 g", + "ingredients": "100 g Physalis", + "steps": "Aus den Blättern pulen, essen.", + "variations": [], + "tip": "Nachmittags-Tipp Das Pulen bremst das Tempo.", + "badge": "", + "yield": "" + }, + { + "num": "27", + "category": "Knabbern & herzhaft · Chip-Ersatz", + "title": "Edamame mit Meersalz", + "tagline": "Pulen bremst das Tempo von allein.", + "nutrition": "ca. ≈ 120 kcal · Eiweiß 12 g · Fett 5 g · KH 9 g · Zucker 2 g · Ballaststoffe 6 g", + "ingredients": "150 g TK-Edamame (Schote) · Meersalz", + "steps": "Nach Packung erwärmen, salzen, aus der Schote pulen.", + "variations": [ + "Chili-Flocken", + "Sesam + Salz", + "Knoblauch-Soja", + "Limette-Chili" + ], + "tip": "Nachmittags-Tipp Proteinreich – und das Pulen verlangsamt das Essen.", + "badge": "", + "yield": "" + }, + { + "num": "28", + "category": "Knabbern & herzhaft · Chip-Ersatz", + "title": "Salzige Mandeln", + "tagline": "Eine Handvoll = eine Portion, dann Tüte zu.", + "nutrition": "ca. ≈ 150 kcal · Eiweiß 6 g · Fett 13 g · KH 3 g · Zucker 1 g · Ballaststoffe 3 g", + "ingredients": "25 g Mandeln · Prise Salz", + "steps": "Abzählen oder abwiegen, salzen.", + "variations": [], + "tip": "Nachmittags-Tipp Kaloriendicht – die abgewogene Menge ist die Grenze.", + "badge": "", + "yield": "" + }, + { + "num": "29", + "category": "Knabbern & herzhaft · Chip-Ersatz", + "title": "Käsewürfel", + "tagline": "Herzhaft, sättigt, null Aufwand.", + "nutrition": "ca. ≈ 150 kcal · Eiweiß 10 g · Fett 12 g · KH 0 g · Zucker 0 g · Ballaststoffe 0 g", + "ingredients": "40 g Gouda/Emmentaler", + "steps": "In Würfel schneiden.", + "variations": [], + "tip": "Nachmittags-Tipp Reines Eiweiß-Fett-Paket gegen den Chips-Reflex.", + "badge": "", + "yield": "" + }, + { + "num": "30", + "category": "Knabbern & herzhaft · Chip-Ersatz", + "title": "Oliven & Cornichons", + "tagline": "Salzig ohne Chips-Fett-Bombe.", + "nutrition": "ca. ≈ 90 kcal · Eiweiß 1 g · Fett 8 g · KH 2 g · Zucker 1 g · Ballaststoffe 1 g", + "ingredients": "8 Oliven · 3 Gewürzgurken", + "steps": "In ein Schälchen, fertig.", + "variations": [], + "tip": "Nachmittags-Tipp Deckt die Salz-Gier mit fast keinen Kalorien.", + "badge": "", + "yield": "" + }, + { + "num": "31", + "category": "Knabbern & herzhaft · Chip-Ersatz", + "title": "Knäckebrot mit Frischkäse", + "tagline": "Knusper + herzhaft.", + "nutrition": "ca. ≈ 150 kcal · Eiweiß 6 g · Fett 7 g · KH 16 g · Zucker 2 g · Ballaststoffe 4 g", + "ingredients": "2 Knäckebrote · 30 g Frischkäse · Schnittlauch", + "steps": "Bestreichen, Schnittlauch drüber.", + "variations": [], + "tip": "Nachmittags-Tipp Der Crunch ersetzt die Chips-Textur.", + "badge": "", + "yield": "" + }, + { + "num": "32", + "category": "Knabbern & herzhaft · Chip-Ersatz", + "title": "Reiswaffeln mit Hüttenkäse", + "tagline": "Leicht, proteinreich.", + "nutrition": "ca. ≈ 140 kcal · Eiweiß 9 g · Fett 4 g · KH 16 g · Zucker 2 g · Ballaststoffe 1 g", + "ingredients": "2 Reiswaffeln · 60 g Hüttenkäse · Pfeffer", + "steps": "Belegen, pfeffern.", + "variations": [], + "tip": "Nachmittags-Tipp Viel Volumen, wenig Kalorien.", + "badge": "", + "yield": "" + }, + { + "num": "33", + "category": "Knabbern & herzhaft · Chip-Ersatz", + "title": "Wasabi-Erbsen", + "tagline": "Scharf-knusprig, kleine Menge reicht.", + "nutrition": "ca. ≈ 120 kcal · Eiweiß 5 g · Fett 3 g · KH 18 g · Zucker 2 g · Ballaststoffe 3 g", + "ingredients": "30 g Wasabi-Erbsen (gekauft)", + "steps": "Portion abfüllen.", + "variations": [], + "tip": "Nachmittags-Tipp Die Schärfe bremst – man isst automatisch langsamer.", + "badge": "", + "yield": "" + }, + { + "num": "34", + "category": "Knabbern & herzhaft · Chip-Ersatz", + "title": "Salzstangen mit Senf-Dip", + "tagline": "Portion abzählen, nicht die Tüte.", + "nutrition": "ca. ≈ 150 kcal · Eiweiß 5 g · Fett 2 g · KH 28 g · Zucker 2 g · Ballaststoffe 1 g", + "ingredients": "30 g Salzstangen · 1 EL Quark-Senf-Dip", + "steps": "Dip anrühren (Quark + Senf), Stangen dazu.", + "variations": [], + "tip": "Nachmittags-Tipp Klar portioniert schlägt die offene Tüte.", + "badge": "", + "yield": "" + }, + { + "num": "35", + "category": "Knabbern & herzhaft · Chip-Ersatz", + "title": "Trockenfleisch (Jerky)", + "tagline": "Herzhaft-zäh, hoher Eiweißanteil, lange haltbar.", + "nutrition": "ca. ≈ 110 kcal · Eiweiß 18 g · Fett 3 g · KH 4 g · Zucker 3 g · Ballaststoffe 0 g", + "ingredients": "30 g Beef Jerky", + "steps": "Aus der Packung, fertig.", + "variations": [], + "tip": "Nachmittags-Tipp Perfekt für die Büro-Schublade.", + "badge": "", + "yield": "" + }, + { + "num": "36", + "category": "Herzhaft & sättigend · kleine Happen", + "title": "Gekochtes Ei, gewürzt", + "tagline": "Der unterschätzte Sattmacher.", + "nutrition": "ca. ≈ 155 kcal · Eiweiß 13 g · Fett 11 g · KH 1 g · Zucker 0 g · Ballaststoffe 0 g", + "ingredients": "2 Eier · Salz, Pfeffer, Paprika", + "steps": "Pellen, würzen.", + "variations": [ + "+ Senf", + "+ Sriracha", + "+ Everything-Gewürz", + "halbiert mit Avocado" + ], + "tip": "Nachmittags-Tipp Sonntag vorkochen – immer griffbereit.", + "badge": "", + "yield": "" + }, + { + "num": "37", + "category": "Herzhaft & sättigend · kleine Happen", + "title": "Vollkorn-Toast mit Hüttenkäse", + "tagline": "Eine Scheibe = die Portion.", + "nutrition": "ca. ≈ 150 kcal · Eiweiß 12 g · Fett 4 g · KH 18 g · Zucker 3 g · Ballaststoffe 4 g", + "ingredients": "1 Scheibe Vollkorntoast · 60 g Hüttenkäse · Tomate", + "steps": "Toasten, belegen.", + "variations": [], + "tip": "Nachmittags-Tipp Bewusst eine Scheibe statt halber Brottüte.", + "badge": "", + "yield": "" + }, + { + "num": "38", + "category": "Herzhaft & sättigend · kleine Happen", + "title": "Mini-Wrap mit Putenbrust", + "tagline": "Rollen, halbieren, essen.", + "nutrition": "ca. ≈ 160 kcal · Eiweiß 14 g · Fett 5 g · KH 15 g · Zucker 2 g · Ballaststoffe 2 g", + "ingredients": "1/2 Vollkorn-Tortilla · 40 g Putenbrust · Frischkäse", + "steps": "Bestreichen, belegen, rollen.", + "variations": [], + "tip": "Nachmittags-Tipp Klein und transportabel – auch fürs Büro.", + "badge": "", + "yield": "" + }, + { + "num": "39", + "category": "Herzhaft & sättigend · kleine Happen", + "title": "Gurken-Boote mit Thunfisch", + "tagline": "Frisch, sehr proteinreich.", + "nutrition": "ca. ≈ 110 kcal · Eiweiß 15 g · Fett 2 g · KH 3 g · Zucker 2 g · Ballaststoffe 1 g", + "ingredients": "1/2 Gurke · 50 g Thunfisch (Wasser) · 1 EL Joghurt", + "steps": "Gurke halbieren, aushöhlen, Thunfisch-Joghurt reinfüllen.", + "variations": [], + "tip": "Nachmittags-Tipp Kaum Kalorien, viel Sättigung.", + "badge": "", + "yield": "" + }, + { + "num": "40", + "category": "Herzhaft & sättigend · kleine Happen", + "title": "Käse-Schinken-Röllchen", + "tagline": "Low Carb, herzhaft.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 16 g · Fett 13 g · KH 1 g · Zucker 1 g · Ballaststoffe 0 g", + "ingredients": "30 g Käse · 30 g Schinken", + "steps": "Aufeinanderlegen, rollen.", + "variations": [], + "tip": "Nachmittags-Tipp Kein Brot nötig – der Belag ist der Snack.", + "badge": "", + "yield": "" + }, + { + "num": "41", + "category": "Herzhaft & sättigend · kleine Happen", + "title": "Tomate-Mozzarella (Mini)", + "tagline": "Frisch und mediterran.", + "nutrition": "ca. ≈ 120 kcal · Eiweiß 9 g · Fett 8 g · KH 4 g · Zucker 4 g · Ballaststoffe 1 g", + "ingredients": "5 Kirschtomaten · 40 g Mini-Mozzarella · Basilikum", + "steps": "Halbieren, mischen, Basilikum drüber.", + "variations": [], + "tip": "Nachmittags-Tipp Leicht, sättigend, sieht nach mehr aus.", + "badge": "", + "yield": "" + }, + { + "num": "42", + "category": "Herzhaft & sättigend · kleine Happen", + "title": "Hummus mit Gemüsesticks", + "tagline": "Cremig-herzhaft, Ballaststoffe satt.", + "nutrition": "ca. ≈ 140 kcal · Eiweiß 5 g · Fett 8 g · KH 12 g · Zucker 4 g · Ballaststoffe 4 g", + "ingredients": "40 g Hummus · Paprika- und Möhrensticks", + "steps": "Gemüse schneiden, dippen.", + "variations": [], + "tip": "Nachmittags-Tipp Der Dip macht rohes Gemüse zum Genuss.", + "badge": "", + "yield": "" + }, + { + "num": "43", + "category": "Herzhaft & sättigend · kleine Happen", + "title": "Vollkorn-Cracker mit Käse", + "tagline": "Klar portioniert, herzhaft.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 9 g · Fett 11 g · KH 12 g · Zucker 1 g · Ballaststoffe 2 g", + "ingredients": "4 Vollkorn-Cracker · 30 g Käse", + "steps": "Belegen.", + "variations": [], + "tip": "Nachmittags-Tipp Abgezählte Stück statt Griff in die Packung.", + "badge": "", + "yield": "" + }, + { + "num": "44", + "category": "Grab & Eat · null Aufwand", + "title": "Fertiger Protein-Pudding", + "tagline": "Für den Impuls-Griff im Kühlschrank.", + "nutrition": "ca. ≈ 150 kcal · Eiweiß 20 g · Fett 3 g · KH 12 g · Zucker 8 g · Ballaststoffe 0 g", + "ingredients": "1 Becher (200 g)", + "steps": "Deckel auf, löffeln.", + "variations": [], + "tip": "Nachmittags-Tipp Wenn es schnell gehen muss – besser als der Riegel-Automat.", + "badge": "", + "yield": "" + }, + { + "num": "45", + "category": "Grab & Eat · null Aufwand", + "title": "Skyr-Becher pur", + "tagline": "Nichts drin, was reinmuss – einfach löffeln.", + "nutrition": "ca. ≈ 90 kcal · Eiweiß 17 g · Fett 0 g · KH 6 g · Zucker 4 g · Ballaststoffe 0 g", + "ingredients": "1 Becher Skyr (150 g)", + "steps": "Öffnen, essen.", + "variations": [], + "tip": "Nachmittags-Tipp Purer Eiweiß-Sattmacher, null Vorbereitung.", + "badge": "", + "yield": "" + }, + { + "num": "46", + "category": "Grab & Eat · null Aufwand", + "title": "Harzer Käse", + "tagline": "Eiweiß-Bombe, fast fettfrei.", + "nutrition": "ca. ≈ 65 kcal · Eiweiß 15 g · Fett 0 g · KH 0 g · Zucker 0 g · Ballaststoffe 0 g", + "ingredients": "1 Rolle (~50 g)", + "steps": "Aus der Packung, evtl. pfeffern.", + "variations": [], + "tip": "Nachmittags-Tipp Kaum Kalorien, extrem viel Eiweiß.", + "badge": "", + "yield": "" + }, + { + "num": "47", + "category": "Grab & Eat · null Aufwand", + "title": "Babybel + Apfel", + "tagline": "Klassisch, transportabel.", + "nutrition": "ca. ≈ 160 kcal · Eiweiß 7 g · Fett 6 g · KH 18 g · Zucker 16 g · Ballaststoffe 3 g", + "ingredients": "1 Babybel · 1 Apfel", + "steps": "Auspacken, essen.", + "variations": [], + "tip": "Nachmittags-Tipp Passt in jede Tasche.", + "badge": "", + "yield": "" + }, + { + "num": "48", + "category": "Grab & Eat · null Aufwand", + "title": "Handvoll Studentenfutter", + "tagline": "Portion abfüllen, Rest weg.", + "nutrition": "ca. ≈ 160 kcal · Eiweiß 4 g · Fett 10 g · KH 14 g · Zucker 10 g · Ballaststoffe 2 g", + "ingredients": "30 g (gekauft)", + "steps": "Abwiegen, Rest wegräumen.", + "variations": [], + "tip": "Nachmittags-Tipp Kaloriendicht – die Handvoll ist die Grenze.", + "badge": "", + "yield": "" + }, + { + "num": "49", + "category": "Grab & Eat · null Aufwand", + "title": "Proteinriegel (gekauft)", + "tagline": "Notfall-Griff – Zutatenliste checken.", + "nutrition": "ca. ≈ 200 kcal · Eiweiß 20 g · Fett 7 g · KH 18 g · Zucker 3 g · Ballaststoffe 2 g", + "ingredients": "1 Riegel (~50 g)", + "steps": "Auspacken.", + "variations": [], + "tip": "Nachmittags-Tipp Für unterwegs okay – aber kein Dauer-Ersatz für echtes Essen.", + "badge": "", + "yield": "" + }, + { + "num": "50", + "category": "Grab & Eat · null Aufwand", + "title": "Trinkjoghurt / Kefir", + "tagline": "Flüssig, schnell – Zucker im Blick.", + "nutrition": "ca. ≈ 130 kcal · Eiweiß 9 g · Fett 5 g · KH 14 g · Zucker 14 g · Ballaststoffe 0 g", + "ingredients": "250 ml", + "steps": "Trinken.", + "variations": [], + "tip": "Nachmittags-Tipp Praktisch, aber gezuckerte Sorten meiden.", + "badge": "", + "yield": "" + }, + { + "num": "51", + "category": "Grab & Eat · null Aufwand", + "title": "Zwei Stück Zartbitter 85%", + "tagline": "", + "nutrition": "ca. ≈ 90 kcal · Eiweiß 1 g · Fett 7 g · KH 5 g · Zucker 3 g · Ballaststoffe 2 g", + "ingredients": "Intensiv – 2 Stück genügen wirklich. 2 Stück (~15 g)", + "steps": "Langsam auf der Zunge schmelzen lassen.", + "variations": [], + "tip": "Nachmittags-Tipp Dunkle Schoko sättigt den Impuls schneller als Vollmilch.", + "badge": "", + "yield": "" + }, + { + "num": "52", + "category": "Büro-Schublade · haltbar & vorportioniert", + "title": "Nuss-Portionstütchen", + "tagline": "Sonntag abfüllen, Woche mitnehmen.", + "nutrition": "ca. ≈ 160 kcal · Eiweiß 5 g · Fett 14 g · KH 4 g · Zucker 1 g · Ballaststoffe 2 g", + "ingredients": "25 g gemischte Nüsse pro Tütchen", + "steps": "In kleine Beutel/Dosen portionieren.", + "variations": [], + "tip": "Nachmittags-Tipp Ein Tütchen = eine Portion, nicht die Tüte.", + "badge": "", + "yield": "" + }, + { + "num": "53", + "category": "Büro-Schublade · haltbar & vorportioniert", + "title": "Zartbitter + Mandeln", + "tagline": "Deckt Schoko- UND Knabber-Lust.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 4 g · Fett 14 g · KH 8 g · Zucker 3 g · Ballaststoffe 3 g", + "ingredients": "2 Stück 85% · 10 Mandeln", + "steps": "Zusammen ins Tütchen.", + "variations": [], + "tip": "Nachmittags-Tipp Zwei Impulse mit einer vorportionierten Ration bedient.", + "badge": "", + "yield": "" + }, + { + "num": "54", + "category": "Büro-Schublade · haltbar & vorportioniert", + "title": "Geröstete Kichererbsen (Vorrat)", + "tagline": "Knusprig, unbekühlt haltbar.", + "nutrition": "ca. ≈ 130 kcal · Eiweiß 6 g · Fett 4 g · KH 16 g · Zucker 1 g · Ballaststoffe 5 g", + "ingredients": "30 g (selbst geröstet o. gekauft)", + "steps": "In verschließbare Dose.", + "variations": [], + "tip": "Nachmittags-Tipp Hält ein paar Tage – Chip-Ersatz für den Schreibtisch.", + "badge": "", + "yield": "" + }, + { + "num": "55", + "category": "Büro-Schublade · haltbar & vorportioniert", + "title": "Reiswaffeln (einzeln)", + "tagline": "Leer knabbern oder mit Portionsmus.", + "nutrition": "ca. ≈ 60 kcal · Eiweiß 1 g · Fett 1 g · KH 13 g · Zucker 0 g · Ballaststoffe 0 g", + "ingredients": "2 Stück", + "steps": "Einzeln in der Schublade lagern.", + "variations": [], + "tip": "Nachmittags-Tipp Trocken-Vorrat, kombinierbar mit allem aus dem Bürokühlschrank.", + "badge": "", + "yield": "" + }, + { + "num": "56", + "category": "Büro-Schublade · haltbar & vorportioniert", + "title": "Erdnussmus-Portionsbeutel", + "tagline": "Kein Glas am Platz – einzeln dosiert.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 7 g · Fett 13 g · KH 8 g · Zucker 2 g · Ballaststoffe 2 g", + "ingredients": "1 Portionsbeutel (20 g) · dazu Reiswaffel", + "steps": "Beutel aufreißen, auf die Waffel.", + "variations": [], + "tip": "Nachmittags-Tipp Ein Beutel begrenzt – das Glas nicht.", + "badge": "", + "yield": "" + }, + { + "num": "57", + "category": "Büro-Schublade · haltbar & vorportioniert", + "title": "Trockenobst-Nuss-Mix", + "tagline": "Trockenobst bewusst wenig – zuckerintensiv.", + "nutrition": "ca. ≈ 150 kcal · Eiweiß 4 g · Fett 9 g · KH 14 g · Zucker 11 g · Ballaststoffe 2 g", + "ingredients": "15 g Trockenobst · 15 g Nüsse", + "steps": "Mischen, portionieren.", + "variations": [], + "tip": "Nachmittags-Tipp Nüsse tragen, Trockenobst ist nur Deko.", + "badge": "", + "yield": "" + }, + { + "num": "58", + "category": "Büro-Schublade · haltbar & vorportioniert", + "title": "Knäckebrot einzeln", + "tagline": "Trocken-Vorrat, dazu was aus dem Kühlschrank.", + "nutrition": "ca. ≈ 80 kcal · Eiweiß 2 g · Fett 1 g · KH 14 g · Zucker 1 g · Ballaststoffe 3 g", + "ingredients": "2 Scheiben", + "steps": "In der Schublade lagern.", + "variations": [], + "tip": "Nachmittags-Tipp Basis für einen schnellen herzhaften Happen.", + "badge": "", + "yield": "" + }, + { + "num": "59", + "category": "Büro-Schublade · haltbar & vorportioniert", + "title": "Geröstete Sojabohnen", + "tagline": "Knusprig, sehr proteinreich, lange haltbar.", + "nutrition": "ca. ≈ 120 kcal · Eiweiß 11 g · Fett 6 g · KH 7 g · Zucker 2 g · Ballaststoffe 3 g", + "ingredients": "25 g", + "steps": "Portion abfüllen.", + "variations": [], + "tip": "Nachmittags-Tipp Der proteinreichste Knabber-Vorrat fürs Büro.", + "badge": "", + "yield": "" + }, + { + "num": "60", + "category": "Energy Bites & Kugeln · Vorrat", + "title": "Kaffee-Energy-Balls", + "tagline": "1–2 Kugeln reichen – Kaffee macht sie besonders.", + "nutrition": "ca. ≈ 700 kcal · Eiweiß 22 g · Fett 26 g · KH 92 g · Zucker 35 g · Ballaststoffe 12 g · ganzer Ansatz", + "ingredients": "100 g Haferflocken · 2 EL Erdnussmus · 4 Datteln · 1 TL lösl. Espresso", + "steps": "Alles zu einer Masse mixen, Kugeln formen, kalt stellen.", + "variations": [ + "+ Kakao", + "in Kokos wälzen", + "+ Cranberrys" + ], + "tip": "Nachmittags-Tipp Kühlschrank-Vorrat, mitnehmbar – der Kaffee-Kick zum Nachmittag.", + "badge": "", + "yield": "" + }, + { + "num": "61", + "category": "Energy Bites & Kugeln · Vorrat", + "title": "Kakao-Kokos-Kugeln", + "tagline": "Raffaello-Gefühl mit Hafer-Basis.", + "nutrition": "ca. ≈ 640 kcal · Eiweiß 14 g · Fett 20 g · KH 100 g · Zucker 34 g · Ballaststoffe 14 g · ganzer Ansatz", + "ingredients": "100 g Haferflocken · 3 Datteln · 1 EL Kakao · Kokosraspel", + "steps": "Mixen, Kugeln formen, in Kokos wälzen, kalt stellen.", + "variations": [], + "tip": "Nachmittags-Tipp Cremig-kokosig, ohne Sahnefüllung.", + "badge": "", + "yield": "" + }, + { + "num": "62", + "category": "Energy Bites & Kugeln · Vorrat", + "title": "Protein-Bliss-Balls", + "tagline": "Mehr Eiweiß pro Kugel.", + "nutrition": "ca. ≈ 640 kcal · Eiweiß 45 g · Fett 26 g · KH 55 g · Zucker 10 g · Ballaststoffe 8 g · ganzer Ansatz", + "ingredients": "60 g Haferflocken · 30 g Proteinpulver · 2 EL Erdnussmus · Schuss Milch", + "steps": "Zu fester Masse kneten, Kugeln formen.", + "variations": [], + "tip": "Nachmittags-Tipp Für die Tage, an denen der Eiweiß-Bedarf hoch ist.", + "badge": "", + "yield": "" + }, + { + "num": "63", + "category": "Energy Bites & Kugeln · Vorrat", + "title": "Cranberry-Nuss-Kugeln", + "tagline": "Fruchtig-herb.", + "nutrition": "ca. ≈ 620 kcal · Eiweiß 12 g · Fett 24 g · KH 88 g · Zucker 38 g · Ballaststoffe 10 g · ganzer Ansatz", + "ingredients": "80 g Haferflocken · 30 g Cranberrys · 30 g Nüsse · 2 Datteln", + "steps": "Mixen, Kugeln formen, kalt stellen.", + "variations": [], + "tip": "Nachmittags-Tipp Die Säure der Cranberrys bricht die Süße.", + "badge": "", + "yield": "" + }, + { + "num": "64", + "category": "Energy Bites & Kugeln · Vorrat", + "title": "Dattel-Mandel-Kugeln", + "tagline": "Nur 2 Zutaten, sehr süß – klein portionieren.", + "nutrition": "ca. ≈ 700 kcal · Eiweiß 16 g · Fett 38 g · KH 72 g · Zucker 58 g · Ballaststoffe 14 g · ganzer Ansatz", + "ingredients": "8 Datteln · 60 g Mandeln", + "steps": "Zusammen mixen, Kugeln formen.", + "variations": [], + "tip": "Nachmittags-Tipp Datteln sind zuckerintensiv – kleine Kugeln, wenige davon.", + "badge": "", + "yield": "" + }, + { + "num": "65", + "category": "Energy Bites & Kugeln · Vorrat", + "title": "Zimt-Apfel-Kugeln", + "tagline": "Schmeckt nach Apfelkuchen.", + "nutrition": "ca. ≈ 560 kcal · Eiweiß 12 g · Fett 8 g · KH 110 g · Zucker 40 g · Ballaststoffe 14 g · ganzer Ansatz", + "ingredients": "100 g Haferflocken · 3 Datteln · getr. Apfelstücke · Zimt", + "steps": "Mixen, Kugeln formen, kalt stellen.", + "variations": [], + "tip": "Nachmittags-Tipp Apfelkuchen-Aroma in einer Kugel.", + "badge": "", + "yield": "" + }, + { + "num": "66", + "category": "Energy Bites & Kugeln · Vorrat", + "title": "Schoko-Erdnuss-Kugeln", + "tagline": "Wie ein weicher Schoko-Riegel.", + "nutrition": "ca. ≈ 720 kcal · Eiweiß 22 g · Fett 38 g · KH 72 g · Zucker 26 g · Ballaststoffe 12 g · ganzer Ansatz", + "ingredients": "80 g Haferflocken · 3 EL Erdnussmus · 1 EL Kakao · 2 Datteln", + "steps": "Kneten, Kugeln formen, kalt stellen.", + "variations": [], + "tip": "Nachmittags-Tipp Riegel-Gefühl ohne die Zuckerfüllung.", + "badge": "", + "yield": "" + }, + { + "num": "67", + "category": "Energy Bites & Kugeln · Vorrat", + "title": "Marzipan-Kugeln (Mandel)", + "tagline": "Weihnachtsgefühl das ganze Jahr.", + "nutrition": "ca. ≈ 640 kcal · Eiweiß 18 g · Fett 44 g · KH 40 g · Zucker 34 g · Ballaststoffe 12 g · ganzer Ansatz", + "ingredients": "80 g gemahlene Mandeln · 3 Datteln · Tropfen Mandelaroma", + "steps": "Mixen, kleine Kugeln formen.", + "variations": [], + "tip": "Nachmittags-Tipp Marzipan-Nähe ohne den vielen Zucker.", + "badge": "", + "yield": "" + }, + { + "num": "68", + "category": "Riegel & Gebäck · Vorrat", + "title": "Protein-Haferriegel AIRFRYER", + "tagline": "", + "nutrition": "ca. ≈ 1400 kcal · Eiweiß 55 g · Fett 45 g · KH 170 g · Zucker 40 g · Ballaststoffe 20 g · ganzer Ansatz", + "ingredients": "8 Riegel, hält 5 Tage, transportabel. 150 g Haferflocken · 3 EL Erdnussmus · 2 Bananen · 30 g Proteinpulver", + "steps": "Verkneten, aufs Blech drücken, backen, in Riegel schneiden. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 180 °C, 12–15 Min Ofen: 180 °C, 20 Min", + "variations": [], + "tip": "Nachmittags-Tipp Der Büro-Klassiker: einzeln einpacken, mitnehmen.", + "badge": "", + "yield": "" + }, + { + "num": "69", + "category": "Riegel & Gebäck · Vorrat", + "title": "Baked Oats (Blech) AIRFRYER", + "tagline": "In Stücke schneiden, Kuchen-Textur.", + "nutrition": "ca. ≈ 1000 kcal · Eiweiß 38 g · Fett 22 g · KH 150 g · Zucker 40 g · Ballaststoffe 18 g · ganzer Ansatz", + "ingredients": "150 g Haferflocken · 1 Ei · 250 ml Milch · 1 Banane · Backpulver", + "steps": "Verrühren, in die Form, backen, abkühlen, schneiden. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 170 °C, 18–20 Min Ofen: 180 °C, 25–30 Min", + "variations": [], + "tip": "Nachmittags-Tipp Schmeckt wie Kuchen, trägt aber echte Portion Hafer.", + "badge": "", + "yield": "" + }, + { + "num": "70", + "category": "Riegel & Gebäck · Vorrat", + "title": "Bananenbrot-Muffins AIRFRYER", + "tagline": "6 Muffins, einzeln einpacken.", + "nutrition": "ca. ≈ 1100 kcal · Eiweiß 55 g · Fett 25 g · KH 150 g · Zucker 50 g · Ballaststoffe 16 g · ganzer Ansatz", + "ingredients": "2 Bananen · 120 g Haferflocken · 2 Eier · 30 g Proteinpulver", + "steps": "Verrühren, in die Muffinform, backen. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 160 °C, 12–15 Min Ofen: 180 °C, 20 Min", + "variations": [], + "tip": "Nachmittags-Tipp Kuchen-Gefühl portioniert – perfekt für die Woche.", + "badge": "", + "yield": "" + }, + { + "num": "71", + "category": "Riegel & Gebäck · Vorrat", + "title": "Haferflocken-Cookies AIRFRYER", + "tagline": "3 Zutaten, Blech, 15 Min Ofen.", + "nutrition": "ca. ≈ 700 kcal · Eiweiß 14 g · Fett 18 g · KH 120 g · Zucker 40 g · Ballaststoffe 12 g · ganzer Ansatz", + "ingredients": "100 g Haferflocken · 1 Banane · 30 g Zartbitter-Drops", + "steps": "Mischen, kleine Häufchen, backen. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 160 °C, 8–10 Min Ofen: 180 °C, 12–15 Min", + "variations": [], + "tip": "Nachmittags-Tipp Kein Zuckerzusatz nötig – die Banane bindet und süßt.", + "badge": "", + "yield": "" + }, + { + "num": "72", + "category": "Riegel & Gebäck · Vorrat", + "title": "Käsekuchen-Gläser (No-Bake)", + "tagline": "Hält 3 Tage im Kühlschrank.", + "nutrition": "ca. ≈ 800 kcal · Eiweiß 60 g · Fett 35 g · KH 55 g · Zucker 30 g · Ballaststoffe 4 g · ganzer Ansatz", + "ingredients": "300 g Magerquark · 100 g Frischkäse · Haferboden · Süße · Vanille", + "steps": "Boden in 4 Gläser, Creme drüber, kalt stellen.", + "variations": [], + "tip": "Nachmittags-Tipp Vier fertige Kaffee-Desserts auf einen Schlag.", + "badge": "", + "yield": "" + }, + { + "num": "73", + "category": "Riegel & Gebäck · Vorrat", + "title": "Datteln-Hafer-Riegel (ohne Backen)", + "tagline": "Kalt pressen, schneiden, sehr süß.", + "nutrition": "ca. ≈ 1000 kcal · Eiweiß 20 g · Fett 35 g · KH 150 g · Zucker 70 g · Ballaststoffe 18 g · ganzer Ansatz", + "ingredients": "10 Datteln · 100 g Haferflocken · 50 g Nüsse", + "steps": "Mixen, in Form pressen, kalt stellen, schneiden.", + "variations": [], + "tip": "Nachmittags-Tipp Ohne Ofen – aber zuckerintensiv, kleine Riegel schneiden.", + "badge": "", + "yield": "" + }, + { + "num": "74", + "category": "Riegel & Gebäck · Vorrat", + "title": "Protein-Brownies AIRFRYER", + "tagline": "Saftig, in Würfel schneiden.", + "nutrition": "ca. ≈ 900 kcal · Eiweiß 60 g · Fett 25 g · KH 100 g · Zucker 25 g · Ballaststoffe 18 g · ganzer Ansatz", + "ingredients": "100 g Haferflocken · 40 g Kakao · 2 Eier · 200 g Quark · Süße", + "steps": "Verrühren, backen, abkühlen, würfeln. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 170 °C, 15 Min Ofen: 180 °C, 22–25 Min", + "variations": [], + "tip": "Nachmittags-Tipp Schokoladig und trotzdem proteinreich.", + "badge": "", + "yield": "" + }, + { + "num": "75", + "category": "Riegel & Gebäck · Vorrat", + "title": "Apfel-Zimt-Schnitten AIRFRYER", + "tagline": "Bratapfel als Blechkuchen.", + "nutrition": "ca. ≈ 800 kcal · Eiweiß 28 g · Fett 15 g · KH 130 g · Zucker 35 g · Ballaststoffe 16 g · ganzer Ansatz", + "ingredients": "120 g Haferflocken · 1 Apfel geraspelt · 2 Eier · Zimt", + "steps": "Verrühren, aufs Blech, backen, schneiden. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 170 °C, 15 Min Ofen: 180 °C, 22 Min", + "variations": [], + "tip": "Nachmittags-Tipp Warmer Apfelkuchen-Effekt, portioniert für die Woche.", + "badge": "", + "yield": "" + }, + { + "num": "76", + "category": "Airfryer · herzhaft & knusprig", + "title": "Airfryer-Kichererbsen AIRFRYER", + "tagline": "Knuspern wie Chips, ohne Tüte.", + "nutrition": "ca. ≈ 330 kcal · Eiweiß 15 g · Fett 9 g · KH 41 g · Zucker 1 g · Ballaststoffe 12 g · ganzer Ansatz", + "ingredients": "1 Dose Kichererbsen · 1 TL Öl · Gewürz nach Wahl", + "steps": "Abtropfen, trocken tupfen, mit Öl und Gewürz mischen. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 190 °C, 12–15 Min, 2× schütteln Ofen: 200 °C, 25–30 Min", + "variations": [ + "Paprika-rauchig", + "Curry", + "Chili-Limette", + "Knoblauch-Kräuter" + ], + "tip": "Nachmittags-Tipp Eiweiß + Ballaststoffe statt leerem Fett. Vorrat hält Tage.", + "badge": "", + "yield": "" + }, + { + "num": "77", + "category": "Airfryer · herzhaft & knusprig", + "title": "Parmesan-Crisps AIRFRYER", + "tagline": "Wenige Stück genügen, sehr intensiv.", + "nutrition": "ca. ≈ 120 kcal · Eiweiß 11 g · Fett 8 g · KH 1 g · Zucker 0 g · Ballaststoffe 0 g", + "ingredients": "geriebener Parmesan/Hartkäse", + "steps": "Kleine Häufchen auf luftdurchlässiges Papier setzen. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 180 °C, 4–6 Min Ofen: 200 °C, 5–7 Min", + "variations": [], + "tip": "Nachmittags-Tipp Herzhaft, proteinreich, fast keine Kohlenhydrate.", + "badge": "", + "yield": "" + }, + { + "num": "78", + "category": "Airfryer · herzhaft & knusprig", + "title": "Airfryer-Gemüsechips AIRFRYER", + "tagline": "Hauchdünn = knusprig.", + "nutrition": "ca. ≈ 90 kcal · Eiweiß 2 g · Fett 5 g · KH 9 g · Zucker 5 g · Ballaststoffe 3 g", + "ingredients": "Zucchini/Möhre dünn gehobelt · wenig Öl · Salz", + "steps": "Hauchdünn hobeln, trocken tupfen, mit Öl und Salz mischen. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 160 °C, 12–18 Min Ofen: 150 °C, 30–40 Min", + "variations": [ + "Paprika", + "Rosmarin", + "Knoblauch", + "Meersalz pur" + ], + "tip": "Nachmittags-Tipp Echter Chip-Ersatz aus Gemüse.", + "badge": "", + "yield": "" + }, + { + "num": "79", + "category": "Airfryer · herzhaft & knusprig", + "title": "Käse-Cracker (Airfryer) AIRFRYER", + "tagline": "Herzhaft, Low Carb.", + "nutrition": "ca. ≈ 160 kcal · Eiweiß 12 g · Fett 12 g · KH 2 g · Zucker 0 g · Ballaststoffe 1 g", + "ingredients": "40 g Käse-Häufchen · Kürbiskerne", + "steps": "Käse in Häufchen, Kerne drüber. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 180 °C, 5–6 Min Ofen: 200 °C, 6–8 Min", + "variations": [], + "tip": "Nachmittags-Tipp Knusprig ohne Mehl.", + "badge": "", + "yield": "" + }, + { + "num": "80", + "category": "Airfryer · herzhaft & knusprig", + "title": "Airfryer-Nüsse würzig AIRFRYER", + "tagline": "Verbrennen schnell – im Blick behalten.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 5 g · Fett 16 g · KH 5 g · Zucker 1 g · Ballaststoffe 2 g", + "ingredients": "30 g Nüsse · Prise Öl · Gewürz", + "steps": "Mit Gewürz mischen. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 160 °C, 5–7 Min, schütteln Ofen: 160 °C, 8–10 Min", + "variations": [], + "tip": "Nachmittags-Tipp Warm geröstet schmeckt intensiver – kleine Menge.", + "badge": "", + "yield": "" + }, + { + "num": "81", + "category": "Airfryer · herzhaft & knusprig", + "title": "Airfryer-Edamame knusprig AIRFRYER", + "tagline": "Aus der Schote gepult, dann geröstet.", + "nutrition": "ca. ≈ 130 kcal · Eiweiß 11 g · Fett 6 g · KH 9 g · Zucker 2 g · Ballaststoffe 5 g", + "ingredients": "100 g Edamame (gepult) · Öl · Salz", + "steps": "Trocken tupfen, mit Öl und Salz mischen. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 190 °C, 10–12 Min Ofen: 200 °C, 18–20 Min", + "variations": [], + "tip": "Nachmittags-Tipp Überraschend knusprig, sehr proteinreich.", + "badge": "", + "yield": "" + }, + { + "num": "82", + "category": "Airfryer · herzhaft & knusprig", + "title": "Vollkorn-Pita-Chips AIRFRYER", + "tagline": "Dazu Hummus oder Quark-Dip.", + "nutrition": "ca. ≈ 160 kcal · Eiweiß 5 g · Fett 5 g · KH 24 g · Zucker 1 g · Ballaststoffe 3 g", + "ingredients": "1 Vollkorn-Pita in Dreiecke · Öl · Salz", + "steps": "Dreiecke leicht ölen und würzen. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 180 °C, 5–7 Min Ofen: 200 °C, 8–10 Min", + "variations": [], + "tip": "Nachmittags-Tipp Nacho-Gefühl aus einem Fladen.", + "badge": "", + "yield": "" + }, + { + "num": "83", + "category": "Airfryer · süß & Gebäck", + "title": "Airfryer-Apfelchips AIRFRYER", + "tagline": "Natürlich süß, knusprig, Vorrat.", + "nutrition": "ca. ≈ 90 kcal · Eiweiß 0 g · Fett 0 g · KH 24 g · Zucker 18 g · Ballaststoffe 4 g", + "ingredients": "1 Apfel hauchdünn · Zimt", + "steps": "Dünn hobeln, mit Zimt bestreuen. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 150 °C, 15–20 Min Ofen: 120 °C, 40–60 Min", + "variations": [], + "tip": "Nachmittags-Tipp Ohne Zuckerzusatz – Gummibärchen-Ersatz zum Knabbern.", + "badge": "", + "yield": "" + }, + { + "num": "84", + "category": "Airfryer · süß & Gebäck", + "title": "Zimt-Bananen-Scheiben AIRFRYER", + "tagline": "Karamellig ohne Zuckerzusatz.", + "nutrition": "ca. ≈ 110 kcal · Eiweiß 1 g · Fett 0 g · KH 28 g · Zucker 16 g · Ballaststoffe 3 g", + "ingredients": "1 Banane in Scheiben · Zimt", + "steps": "Scheiben mit Zimt bestreuen. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 180 °C, 8–10 Min Ofen: 180 °C, 12–15 Min", + "variations": [], + "tip": "Nachmittags-Tipp Die Banane karamellisiert von allein.", + "badge": "", + "yield": "" + }, + { + "num": "85", + "category": "Airfryer · süß & Gebäck", + "title": "Mini-Bananen-Hafer-Kekse AIRFRYER", + "tagline": "Frisch am besten, warm.", + "nutrition": "ca. ≈ 290 kcal · Eiweiß 8 g · Fett 4 g · KH 60 g · Zucker 15 g · Ballaststoffe 8 g · ganzer Ansatz", + "ingredients": "1 reife Banane · 5 EL Haferflocken", + "steps": "Banane zerdrücken, mit Haferflocken mischen, Häufchen formen. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 160 °C, 8–10 Min Ofen: 180 °C, 12 Min", + "variations": [ + "+ Zartbitter-Drops", + "+ Rosinen", + "+ Zimt", + "+ Nuss" + ], + "tip": "Nachmittags-Tipp 2 Zutaten, kein Zuckerzusatz nötig.", + "badge": "", + "yield": "" + }, + { + "num": "86", + "category": "Airfryer · süß & Gebäck", + "title": "Airfryer-Pfirsich mit Quark AIRFRYER", + "tagline": "Warme Frucht, kühler Quark.", + "nutrition": "ca. ≈ 120 kcal · Eiweiß 10 g · Fett 1 g · KH 15 g · Zucker 13 g · Ballaststoffe 2 g", + "ingredients": "1/2 Pfirsich · danach Klecks Quark", + "steps": "Pfirsich kurz garen, mit Quark servieren. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 180 °C, 6–8 Min Ofen: 190 °C, 12 Min", + "variations": [], + "tip": "Nachmittags-Tipp Warm-kalt-Kontrast wie ein Mini-Dessert.", + "badge": "", + "yield": "" + }, + { + "num": "87", + "category": "Airfryer · süß & Gebäck", + "title": "Haferflocken-Cookie-Bites AIRFRYER", + "tagline": "Kleine Häppchen zum Espresso.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 7 g · Fett 4 g · KH 48 g · Zucker 12 g · Ballaststoffe 7 g · ganzer Ansatz", + "ingredients": "60 g Haferflocken · 1/2 Banane · Zimt", + "steps": "Mischen, kleine Bites formen. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 160 °C, 8–10 Min Ofen: 180 °C, 12 Min", + "variations": [], + "tip": "Nachmittags-Tipp Espresso-Begleiter statt Keksschale.", + "badge": "", + "yield": "" + }, + { + "num": "88", + "category": "Airfryer · süß & Gebäck", + "title": "Airfryer-Birne mit Walnuss AIRFRYER", + "tagline": "Warmer Nachtisch-Effekt.", + "nutrition": "ca. ≈ 140 kcal · Eiweiß 2 g · Fett 8 g · KH 18 g · Zucker 12 g · Ballaststoffe 3 g", + "ingredients": "1/2 Birne · 10 g Walnüsse", + "steps": "Birne halbieren, Nuss in die Mulde. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 180 °C, 8 Min Ofen: 190 °C, 14 Min", + "variations": [], + "tip": "Nachmittags-Tipp Fühlt sich edler an als es Aufwand ist.", + "badge": "", + "yield": "" + }, + { + "num": "89", + "category": "Airfryer · süß & Gebäck", + "title": "Zimt-Kichererbsen (süß) AIRFRYER", + "tagline": "Süße Variante des Chip-Ersatzes.", + "nutrition": "ca. ≈ 340 kcal · Eiweiß 15 g · Fett 9 g · KH 42 g · Zucker 4 g · Ballaststoffe 12 g · ganzer Ansatz", + "ingredients": "1 Dose Kichererbsen · Zimt · Prise Süße nach dem Rösten", + "steps": "Abtropfen, trocken tupfen, mit Zimt mischen, rösten. ZUBEREITUNG · AIRFRYER / OFEN Airfryer: 190 °C, 12–15 Min Ofen: 200 °C, 25–30 Min", + "variations": [], + "tip": "Nachmittags-Tipp Süß und knusprig – Ballaststoffe inklusive.", + "badge": "", + "yield": "" + }, + { + "num": "90", + "category": "Warm & gemütlich zum Kaffee", + "title": "Warme Milch mit Zimt", + "tagline": "Beruhigend, dazu ein Stück Zartbitter.", + "nutrition": "ca. ≈ 130 kcal · Eiweiß 9 g · Fett 5 g · KH 13 g · Zucker 13 g · Ballaststoffe 0 g", + "ingredients": "250 ml Milch · Zimt · Prise Süße", + "steps": "Milch erwärmen, Zimt einrühren.", + "variations": [], + "tip": "Nachmittags-Tipp Warmes Getränk beschäftigt – oft reicht das schon.", + "badge": "", + "yield": "" + }, + { + "num": "91", + "category": "Warm & gemütlich zum Kaffee", + "title": "Porridge-Tasse", + "tagline": "Warm sättigt lange.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 12 g · Fett 7 g · KH 38 g · Zucker 12 g · Ballaststoffe 5 g", + "ingredients": "40 g Haferflocken · 200 ml Milch", + "steps": "3 Min köcheln, Toppings nach Wahl.", + "variations": [ + "+ Beeren", + "+ Apfel-Zimt", + "+ Kakao", + "+ 1 TL Erdnussmus" + ], + "tip": "Nachmittags-Tipp Kleiner Nachmittags-Porridge trägt bis zum Abendessen.", + "badge": "", + "yield": "" + }, + { + "num": "92", + "category": "Warm & gemütlich zum Kaffee", + "title": "Ofen-Apfel (Mikro)", + "tagline": "Bratapfel-Gefühl in Minuten.", + "nutrition": "ca. ≈ 140 kcal · Eiweiß 2 g · Fett 4 g · KH 25 g · Zucker 20 g · Ballaststoffe 4 g", + "ingredients": "1 Apfel entkernt · Zimt · 5 g Walnuss", + "steps": "Füllen, 3 Min in die Mikrowelle.", + "variations": [], + "tip": "Nachmittags-Tipp Warmer Apfel schlägt kalten Kuchen.", + "badge": "", + "yield": "" + }, + { + "num": "93", + "category": "Warm & gemütlich zum Kaffee", + "title": "Heiße Schokolade (mager)", + "tagline": "Echter Kakao, kein Fertigpulver-Zucker.", + "nutrition": "ca. ≈ 140 kcal · Eiweiß 9 g · Fett 5 g · KH 16 g · Zucker 13 g · Ballaststoffe 2 g", + "ingredients": "200 ml Milch · 1 EL Kakao · Süße", + "steps": "Kakao mit etwas Milch anrühren, Rest heiß dazu.", + "variations": [], + "tip": "Nachmittags-Tipp Selbst gemacht steckt weniger Zucker drin als im Beutel.", + "badge": "", + "yield": "" + }, + { + "num": "94", + "category": "Warm & gemütlich zum Kaffee", + "title": "Grießbrei klein", + "tagline": "Kindheits-Gefühl, kleine Portion.", + "nutrition": "ca. ≈ 200 kcal · Eiweiß 10 g · Fett 6 g · KH 28 g · Zucker 14 g · Ballaststoffe 1 g", + "ingredients": "20 g Grieß · 200 ml Milch · Zimt", + "steps": "Grieß in die Milch einrühren, köcheln bis cremig.", + "variations": [], + "tip": "Nachmittags-Tipp Warm und tröstlich – bewusst eine kleine Schüssel.", + "badge": "", + "yield": "" + }, + { + "num": "95", + "category": "Warm & gemütlich zum Kaffee", + "title": "Warmer Beeren-Quark", + "tagline": "Warm-kalt-Kontrast, sehr proteinreich.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 20 g · Fett 1 g · KH 20 g · Zucker 14 g · Ballaststoffe 4 g", + "ingredients": "100 g TK-Beeren · 150 g Quark", + "steps": "Beeren erwärmen, über den kalten Quark geben.", + "variations": [], + "tip": "Nachmittags-Tipp Wie warme Grütze auf Quark – Dessert-Gefühl mit Eiweiß.", + "badge": "", + "yield": "" + }, + { + "num": "96", + "category": "Kaffee-Drinks & Protein-Shakes", + "title": "Protein-Kaffee (Iced)", + "tagline": "Wach + satt, ersetzt den süßen Coffee-to-go.", + "nutrition": "ca. ≈ 180 kcal · Eiweiß 24 g · Fett 5 g · KH 8 g · Zucker 6 g · Ballaststoffe 1 g", + "ingredients": "200 ml kalter Kaffee · 150 ml Milch · 20 g Proteinpulver · Eis", + "steps": "Alles shaken, über Eis gießen.", + "variations": [], + "tip": "Nachmittags-Tipp Der Griff zum Zucker-Coffee-Shop fällt weg.", + "badge": "", + "yield": "" + }, + { + "num": "97", + "category": "Kaffee-Drinks & Protein-Shakes", + "title": "Cappuccino mit Magermilch", + "tagline": "Cremig ohne Sirup-Zucker.", + "nutrition": "ca. ≈ 80 kcal · Eiweiß 9 g · Fett 1 g · KH 10 g · Zucker 10 g · Ballaststoffe 0 g", + "ingredients": "200 ml Magermilch · Espresso", + "steps": "Milch aufschäumen, Espresso dazu.", + "variations": [], + "tip": "Nachmittags-Tipp Der Schaum gibt Genuss – ohne Sirup.", + "badge": "", + "yield": "" + }, + { + "num": "98", + "category": "Kaffee-Drinks & Protein-Shakes", + "title": "Bananen-Kaffee-Shake", + "tagline": "Süße aus der Banane, kein Zuckerzusatz.", + "nutrition": "ca. ≈ 220 kcal · Eiweiß 9 g · Fett 5 g · KH 36 g · Zucker 24 g · Ballaststoffe 2 g", + "ingredients": "1 Banane · 200 ml Milch · 1 TL lösl. Espresso", + "steps": "Alles mixen.", + "variations": [], + "tip": "Nachmittags-Tipp Milchshake-Gefühl, aber natürlich gesüßt.", + "badge": "", + "yield": "" + }, + { + "num": "99", + "category": "Kaffee-Drinks & Protein-Shakes", + "title": "Schoko-Protein-Shake", + "tagline": "Wie ein Milchshake, sättigt echt.", + "nutrition": "ca. ≈ 250 kcal · Eiweiß 32 g · Fett 8 g · KH 15 g · Zucker 13 g · Ballaststoffe 1 g", + "ingredients": "300 ml Milch · 30 g Schoko-Proteinpulver · Eis", + "steps": "Shaken oder mixen.", + "variations": [], + "tip": "Nachmittags-Tipp Deckt Schokolust und Eiweiß in einem Glas.", + "badge": "", + "yield": "" + }, + { + "num": "100", + "category": "Kaffee-Drinks & Protein-Shakes", + "title": "Iced Latte mit Zimt", + "tagline": "", + "nutrition": "ca. ≈ 145 kcal · Eiweiß 25 g · Fett 1 g · KH 10 g · Zucker 7 g · Ballaststoffe 0 g · Basis, pro Portion", + "ingredients": "Erfrischend, dazu 2 Stück Zartbitter. 200 ml Milch · Espresso · Zimt · Eiswürfel 200 g Magerquark oder Skyr · Schuss Milch/Wasser · Süße nach Bedarf (Süßstoff/Erythrit)", + "steps": "Über Eis gießen, Zimt drüber.\nQuark mit dem Schuss Milch glatt rühren, Zutat\nder Variante untermischen. ~30 Sekunden.\n20 VARIATIONEN\nTrocken gesüßt · bleibt bei ~25 g Eiweiß · ~7 g Zucker (nur Milchzucker)\nSchoko: 1 TL Kakao + Süßstoff\nVanille: Vanillemark/-extrakt + Süßstoff\nCappuccino: 1 TL lösl. Espresso + Süßstoff\nZimt: Zimt + Süßstoff\nSchoko-Minze: Kakao + Tropfen Minzaroma\nKokos: 1 TL Kokosraspel + Süßstoff\nKaramell: Karamell-Aroma + Süßstoff\nMandel-Marzipan: Tropfen Mandelaroma + Süßstoff\nMit Frucht · + natürlicher Zucker (~10–14 g), + Ballaststoffe\nBeere: 60 g TK-Beeren untermischen\nApfel-Zimt: ½ Apfel gewürfelt + Zimt\nBanane: ½ zerdrückte Banane\nMango: 60 g Mangostücke\nKirsche: 60 g TK-Kirschen\nHimbeer-Kakao: Himbeeren + 1 TL Kakao\nPfirsich: ½ Pfirsich gewürfelt\nTopping & Booster · mehr Biss oder mehr Eiweiß\nErdnuss-Schoko: 1 TL Erdnussmus + Kakao\nNuss-Krokant: 1 TL geröstete geh. Nüsse\nGranola: 2 EL Granola drüber (Crunch)\nKakaonibs: 1 EL Kakaonibs\nProtein-Booster: 15 g Schoko-Proteinpulver → ~37 g EW", + "variations": [], + "tip": "Nachmittags-Tipp Morgens zuhause anrühren, in eine Dose. Obst erst am Platz reinschneiden – sonst wird's wässrig.", + "badge": "", + "yield": "" + } + ] + } +} \ No newline at end of file diff --git a/plugins/trainingsplan/recipe_builder.py b/plugins/trainingsplan/recipe_builder.py new file mode 100644 index 0000000..49fe6d7 --- /dev/null +++ b/plugins/trainingsplan/recipe_builder.py @@ -0,0 +1,399 @@ +# -*- coding: utf-8 -*- +""" +recipe_builder.py +Rendert eine Rezeptsammlung als A4-PDF im Design der Original-Editionen: +farbiges Kategorie-Band, farbiges Rezept-Titelband (weiße Schrift), getönter +Untertitel, NÄHRWERTE-Strip, zweispaltig ZUTATEN | SO GEHT'S, getönte +VARIATIONEN- und Tipp-Box, dünn umrandete Karte. + +Regeln (wie im Original-Generator): + * jedes Rezept ist KeepTogether -> wird nie durch einen Seitenumbruch geteilt + * vor jeder Kategorie ein PageBreak -> jede Kategorie beginnt auf neuer Seite + +build_pdf(cfg) -> bytes ; default_config() -> dict +""" +import re +import copy +from io import BytesIO + +from reportlab.lib.pagesizes import A4 +from reportlab.lib.units import cm +from reportlab.lib import colors +from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle +from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, + KeepTogether, PageBreak) + +DEFAULTS = { + "type": "recipe", + "meta": { + "title": "Neue Rezeptsammlung", + "header_left": "REZEPTE · Edition", + "header_right": "Begleitend zum 8-Wochen-Plan", + "footer_left": "Teller = Portionsgrenze · Trend über die Woche zählt", + "output_name": "Rezepte.pdf", + "doc_title": "Rezeptsammlung", + "brand_color": "#6D28D9", + "tip_label": "Tipp", + "steps_label": "SO GEHT'S", + }, + "intro": { + "headline": "Neue Rezeptsammlung", + "subtitle": "Kurzbeschreibung der Edition.", + "paragraphs": ["Einleitender Absatz."], + "rule_head": "Die eine Grundregel", + "rule_text": "Teller fertig machen, Rest aus dem Sichtfeld.", + "categories_head": "1 Kategorie", + }, + "categories": [{"name": "Cremig & süß", "color": "#7C3AED"}], + "recipes": [ + {"num": "01", "category": "Cremig & süß", "title": "Beispiel-Rezept", + "tagline": "Kurzer Untertitel.", "nutrition": "", "badge": "", "yield": "", + "ingredients": "200 g Magerquark · etwas Süße", + "steps": "Alles verrühren, fertig.", + "variations": ["Schoko: + 1 TL Kakao"], "tip": "Tipp Eiweiß-reich."}, + ], +} + + +def default_config(): + return copy.deepcopy(DEFAULTS) + + +# ---------------------------------------------------------------- helpers +_ENT = re.compile(r"&(?!(?:amp|lt|gt|quot|apos|nbsp|#\d+|#x[0-9a-fA-F]+);)") + + +def esc(s): + if s is None: + return "" + s = str(s).replace("<", "<").replace(">", ">") + return _ENT.sub("&", s) + + +def _hx(c): + return "#" + c.hexval()[2:] + + +def lighten(hexstr, w=0.92): + """Mischt eine Farbe mit Weiß (w = Weiß-Anteil) -> heller Ton.""" + try: + c = colors.HexColor(hexstr) + except Exception: + c = colors.HexColor("#334155") + r = c.red * 255 * (1 - w) + 255 * w + g = c.green * 255 * (1 - w) + 255 * w + b = c.blue * 255 * (1 - w) + 255 * w + return colors.Color(r / 255, g / 255, b / 255) + + +TIP_LABEL_RE = re.compile(r"^\s*([A-Za-zÄÖÜäöü]+-Tipp|Tipp)\b[:\s]*(.*)$", re.S) + + +def split_tip(text, default_label): + """Zerlegt 'Morgen-Tipp Text' -> ('Morgen-Tipp', 'Text').""" + if not text: + return default_label, "" + m = TIP_LABEL_RE.match(text) + if m: + return m.group(1), m.group(2).strip() + return default_label, text.strip() + + +def bold_lead(v): + """Erste Bezeichnung einer Variation fett (bis Doppelpunkt bzw. erstes Wort).""" + v = v.strip() + m = re.match(r"^([^:]{2,40}):\s*(.*)$", v, re.S) + if m: + return "%s: %s" % (esc(m.group(1)), esc(m.group(2))) + m = re.match(r"^(\S+)\s+(.*)$", v, re.S) + if m: + return "%s %s" % (esc(m.group(1)), esc(m.group(2))) + return esc(v) + + +NOSPLIT = {"bis", "damit", "sodass", "bevor", "nachdem", "während", "wenn", "falls", "sobald", + "weil", "sowie", "oder", "aber", "bei", "je", "ohne", "mit", "in", "im", "am", "zum", + "zur", "auf", "für", "über", "unter", "dabei", "dazu", "nach", "vor", "als"} + + +def split_steps(text): + t = (text or "").strip().replace("z. B.", "z.B.") + raw = re.split(r"(?<=[.!?])\s+|,\s+", t) + steps = [] + for frag in raw: + frag = frag.strip() + if not frag: + continue + first = frag.split()[0].lower().strip("(„\"'.:;»«") + if steps and first in NOSPLIT: + steps[-1] = steps[-1].rstrip(".") + ", " + frag + else: + steps.append(frag) + steps = [s if s.endswith((".", "!", "?")) else s + "." for s in steps] + return [s[:1].upper() + s[1:] for s in steps] + + +def numbered_steps(steps_txt): + """Bereits nummerierte (mehrzeilige) Schritte -> Zeilen; sonst Prosa splitten.""" + if steps_txt and "\n" in steps_txt: + out = [] + for ln in steps_txt.split("\n"): + ln = re.sub(r"^\s*\d+[\.\)]?\s+", "", ln.strip()) + if ln: + out.append(ln) + return out + return split_steps(steps_txt) + + +# ---------------------------------------------------------------- renderer +def build_pdf(cfg): + _D = DEFAULTS + meta = dict(_D["meta"]); meta.update(cfg.get("meta") or {}) + intro = dict(_D["intro"]); intro.update(cfg.get("intro") or {}) + cats = cfg.get("categories") or _D["categories"] + recipes = cfg.get("recipes") or [] + + INK = colors.HexColor("#23272F") + SUB = colors.HexColor("#5B6270") + HAIR = colors.HexColor("#E3E1EA") + NUTBG = colors.HexColor("#F7F7F9") + BRAND = colors.HexColor(meta.get("brand_color", "#6D28D9")) + TIPBG = lighten(_hx(BRAND), 0.93) + + def catcolor(name): + for c in cats: + if c.get("name") == name: + try: + return colors.HexColor(c.get("color", "#334155")) + except Exception: + return colors.HexColor("#334155") + return colors.HexColor("#334155") + + styles = getSampleStyleSheet() + + def S(name, **kw): + base = kw.pop("parent", styles["Normal"]) + return ParagraphStyle(name, parent=base, **kw) + + st_title = S("t", fontName="Helvetica-Bold", fontSize=22, leading=25, textColor=INK) + st_sub = S("s", fontName="Helvetica", fontSize=10.5, leading=14, textColor=SUB) + st_intro = S("i", fontName="Helvetica", fontSize=10, leading=15, textColor=INK) + st_callh = S("ch", fontName="Helvetica-Bold", fontSize=10.5, leading=13, textColor=INK) + st_legend= S("lg", fontName="Helvetica", fontSize=8.6, leading=12, textColor=INK) + st_cat = S("c", fontName="Helvetica-Bold", fontSize=12.5, leading=15, textColor=colors.white) + st_rname = S("rn", fontName="Helvetica-Bold", fontSize=11.5, leading=14, textColor=colors.white) + st_badge = S("bd", fontName="Helvetica-Bold", fontSize=7.5, leading=9) + st_tag = S("tg", fontName="Helvetica-Oblique", fontSize=9, leading=12, textColor=SUB) + st_lbl = S("lb", fontName="Helvetica-Bold", fontSize=7.5, leading=10, spaceAfter=1) + st_body = S("bd2", fontName="Helvetica", fontSize=8.8, leading=12, textColor=INK) + st_var = S("vr", fontName="Helvetica", fontSize=8.4, leading=11.6, textColor=INK) + st_tip = S("tp", fontName="Helvetica", fontSize=8.5, leading=12, textColor=INK) + st_yield = S("yl", fontName="Helvetica-Oblique", fontSize=8, leading=11, textColor=SUB) + + PAGE_W, PAGE_H = A4 + LM = RM = 1.5 * cm + CW = PAGE_W - LM - RM + + def lbl(text, acc): + return Paragraph(text, S("lx", parent=st_lbl, textColor=acc)) + + # ---- category band ---- + def category_band(name): + acc = catcolor(name) + t = Table([[Paragraph(esc(name), st_cat)]], colWidths=[CW]) + t.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), acc), + ("LEFTPADDING", (0, 0), (-1, -1), 10), ("RIGHTPADDING", (0, 0), (-1, -1), 10), + ("TOPPADDING", (0, 0), (-1, -1), 6), ("BOTTOMPADDING", (0, 0), (-1, -1), 6)])) + return t + + # ---- one recipe card ---- + def recipe_card(r): + acc = catcolor(r.get("category", "")) + accx = _hx(acc) + tint = lighten(accx, 0.92) + + # Titelband (+ optional AIRFRYER-Badge) + name_p = Paragraph("%s  %s" % (esc(r.get("num", "")), esc(r.get("title", ""))), st_rname) + if r.get("badge"): + badge = Table([[Paragraph(esc(r["badge"]), S("bx", parent=st_badge, textColor=acc))]]) + badge.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), colors.white), + ("LEFTPADDING", (0, 0), (-1, -1), 5), ("RIGHTPADDING", (0, 0), (-1, -1), 5), + ("TOPPADDING", (0, 0), (-1, -1), 2), ("BOTTOMPADDING", (0, 0), (-1, -1), 2)])) + badge.hAlign = "RIGHT" + head = Table([[name_p, badge]], colWidths=[CW - 82, 82]) + head.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), acc), ("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("LEFTPADDING", (0, 0), (0, 0), 9), ("RIGHTPADDING", (0, 0), (0, 0), 4), + ("LEFTPADDING", (1, 0), (1, 0), 0), ("RIGHTPADDING", (1, 0), (1, 0), 9), + ("TOPPADDING", (0, 0), (-1, -1), 5), ("BOTTOMPADDING", (0, 0), (-1, -1), 5)])) + else: + head = Table([[name_p]], colWidths=[CW]) + head.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), acc), + ("LEFTPADDING", (0, 0), (-1, -1), 9), ("RIGHTPADDING", (0, 0), (-1, -1), 9), + ("TOPPADDING", (0, 0), (-1, -1), 5), ("BOTTOMPADDING", (0, 0), (-1, -1), 5)])) + + rows = [[head]] + + # Untertitel (getönt) + if r.get("tagline"): + tg = Table([[Paragraph(esc(r["tagline"]), st_tag)]], colWidths=[CW]) + tg.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), tint), + ("LEFTPADDING", (0, 0), (-1, -1), 9), ("RIGHTPADDING", (0, 0), (-1, -1), 9), + ("TOPPADDING", (0, 0), (-1, -1), 4), ("BOTTOMPADDING", (0, 0), (-1, -1), 4)])) + rows.append([tg]) + + # NÄHRWERTE-Strip + if r.get("nutrition"): + ntext = 'NÄHRWERTE  %s' % (accx, esc(r["nutrition"])) + n = Table([[Paragraph(ntext, st_var)]], colWidths=[CW]) + n.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), NUTBG), + ("LEFTPADDING", (0, 0), (-1, -1), 9), ("RIGHTPADDING", (0, 0), (-1, -1), 9), + ("TOPPADDING", (0, 0), (-1, -1), 4), ("BOTTOMPADDING", (0, 0), (-1, -1), 4), + ("LINEBELOW", (0, 0), (-1, 0), 0.6, HAIR)])) + rows.append([n]) + + # ZUTATEN – volle Breite + zut_cell = [lbl("ZUTATEN", acc), Paragraph(esc(r.get("ingredients", "")), st_body)] + if r.get("yield"): + zut_cell.append(Spacer(1, 2)) + zut_cell.append(Paragraph("%s" % esc(r["yield"]), st_yield)) + zut = Table([[zut_cell]], colWidths=[CW]) + zut.setStyle(TableStyle([("VALIGN", (0, 0), (-1, -1), "TOP"), + ("LEFTPADDING", (0, 0), (-1, -1), 9), ("RIGHTPADDING", (0, 0), (-1, -1), 9), + ("TOPPADDING", (0, 0), (-1, -1), 6), ("BOTTOMPADDING", (0, 0), (-1, -1), 5), + ("BACKGROUND", (0, 0), (-1, -1), colors.white)])) + rows.append([zut]) + + # Schritte – nummeriert, volle Breite + steps = numbered_steps(r.get("steps", "")) + srows = [[Paragraph('%d' % (accx, i), st_body), + Paragraph(esc(s), st_body)] for i, s in enumerate(steps, 1)] + if srows: + steps_inner = Table(srows, colWidths=[15, CW - 18 - 15]) + steps_inner.setStyle(TableStyle([("VALIGN", (0, 0), (-1, -1), "TOP"), + ("LEFTPADDING", (0, 0), (-1, -1), 0), ("RIGHTPADDING", (0, 0), (0, 0), 4), ("RIGHTPADDING", (1, 0), (1, 0), 0), + ("TOPPADDING", (0, 0), (-1, 0), 0), ("TOPPADDING", (0, 1), (-1, -1), 3), ("BOTTOMPADDING", (0, 0), (-1, -1), 0)])) + arb_cell = [lbl(meta.get("steps_label", "SO GEHT'S"), acc), Spacer(1, 2), steps_inner] + else: + arb_cell = [lbl(meta.get("steps_label", "SO GEHT'S"), acc)] + arb = Table([[arb_cell]], colWidths=[CW]) + arb.setStyle(TableStyle([("VALIGN", (0, 0), (-1, -1), "TOP"), + ("LEFTPADDING", (0, 0), (-1, -1), 9), ("RIGHTPADDING", (0, 0), (-1, -1), 9), + ("TOPPADDING", (0, 0), (-1, -1), 6), ("BOTTOMPADDING", (0, 0), (-1, -1), 7), + ("LINEABOVE", (0, 0), (-1, 0), 0.6, HAIR), ("BACKGROUND", (0, 0), (-1, -1), colors.white)])) + rows.append([arb]) + + # ZUBEREITUNG (Airfryer/Ofen), optional + if r.get("methods"): + m_cell = [lbl("ZUBEREITUNG · AIRFRYER / OFEN", acc), Paragraph(esc(r["methods"]), st_var)] + mt = Table([[m_cell]], colWidths=[CW]) + mt.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#F3F4F6")), + ("LEFTPADDING", (0, 0), (-1, -1), 9), ("RIGHTPADDING", (0, 0), (-1, -1), 9), + ("TOPPADDING", (0, 0), (-1, -1), 6), ("BOTTOMPADDING", (0, 0), (-1, -1), 6), + ("LINEABOVE", (0, 0), (-1, 0), 0.6, HAIR)])) + rows.append([mt]) + + # VARIATIONEN (getönt) + if r.get("variations"): + vhtml = "
".join(bold_lead(v) for v in r["variations"] if v) + v = Table([[[lbl("VARIATIONEN", acc), Paragraph(vhtml, st_var)]]], colWidths=[CW]) + v.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), tint), + ("LEFTPADDING", (0, 0), (-1, -1), 9), ("RIGHTPADDING", (0, 0), (-1, -1), 9), + ("TOPPADDING", (0, 0), (-1, -1), 6), ("BOTTOMPADDING", (0, 0), (-1, -1), 6), + ("LINEABOVE", (0, 0), (-1, 0), 0.6, HAIR)])) + rows.append([v]) + + # Tipp (Marken-Tint) + if r.get("tip"): + lab, txt = split_tip(r["tip"], meta.get("tip_label", "Tipp")) + tip_html = '%s  %s' % (accx, esc(lab), esc(txt)) + tp = Table([[Paragraph(tip_html, st_tip)]], colWidths=[CW]) + tp.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), TIPBG), + ("LEFTPADDING", (0, 0), (-1, -1), 9), ("RIGHTPADDING", (0, 0), (-1, -1), 9), + ("TOPPADDING", (0, 0), (-1, -1), 5), ("BOTTOMPADDING", (0, 0), (-1, -1), 5)])) + rows.append([tp]) + + card = Table(rows, colWidths=[CW]) + card.setStyle(TableStyle([("LEFTPADDING", (0, 0), (-1, -1), 0), ("RIGHTPADDING", (0, 0), (-1, -1), 0), + ("TOPPADDING", (0, 0), (-1, -1), 0), ("BOTTOMPADDING", (0, 0), (-1, -1), 0), + ("BOX", (0, 0), (-1, -1), 0.8, HAIR)])) + return KeepTogether([card, Spacer(1, 7)]) + + # ---- story ---- + story = [] + story.append(Spacer(1, 0.3 * cm)) + story.append(Paragraph(esc(intro.get("headline", meta["title"])), st_title)) + story.append(Spacer(1, 3)) + if intro.get("subtitle"): + story.append(Paragraph(esc(intro["subtitle"]), st_sub)) + story.append(Spacer(1, 10)) + for p in intro.get("paragraphs", []): + story.append(Paragraph(esc(p), st_intro)) + story.append(Spacer(1, 6)) + if intro.get("rule_head") or intro.get("rule_text"): + gr = Table([[[Paragraph(esc(intro.get("rule_head", "")), st_callh), Spacer(1, 3), + Paragraph(esc(intro.get("rule_text", "")), st_intro)]]], colWidths=[CW]) + gr.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), TIPBG), + ("LEFTPADDING", (0, 0), (-1, -1), 12), ("RIGHTPADDING", (0, 0), (-1, -1), 12), + ("TOPPADDING", (0, 0), (-1, -1), 9), ("BOTTOMPADDING", (0, 0), (-1, -1), 9), + ("LINEBEFORE", (0, 0), (0, 0), 3, BRAND)])) + story.append(Spacer(1, 4)) + story.append(gr) + story.append(Spacer(1, 12)) + story.append(Paragraph(esc(intro.get("categories_head", "%d Kategorien" % len(cats))), + S("lh", parent=st_callh, fontSize=10))) + story.append(Spacer(1, 4)) + leg_rows = [] + for c in cats: + sw = Table([[""]], colWidths=[10], rowHeights=[10]) + sw.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), catcolor(c["name"])), + ("LEFTPADDING", (0, 0), (-1, -1), 0), ("RIGHTPADDING", (0, 0), (-1, -1), 0), + ("TOPPADDING", (0, 0), (-1, -1), 0), ("BOTTOMPADDING", (0, 0), (-1, -1), 0)])) + leg_rows.append([sw, Paragraph(esc(c["name"]), st_legend)]) + if leg_rows: + leg = Table(leg_rows, colWidths=[16, CW - 16]) + leg.setStyle(TableStyle([("VALIGN", (0, 0), (-1, -1), "MIDDLE"), + ("LEFTPADDING", (0, 0), (-1, -1), 0), ("RIGHTPADDING", (0, 0), (0, 0), 6), + ("TOPPADDING", (0, 0), (-1, -1), 2), ("BOTTOMPADDING", (0, 0), (-1, -1), 2)])) + story.append(leg) + + # Rezepte, gruppiert je Kategorie -> neue Seite pro Kategorie, Rezept nie geteilt + cur = None + for r in recipes: + c = r.get("category", "") + if c != cur: + story.append(PageBreak()) + story.append(category_band(c)) + story.append(Spacer(1, 7)) + cur = c + story.append(recipe_card(r)) + + # ---- Kopf-/Fußzeile ---- + header_left = meta.get("header_left", "") + header_right = meta.get("header_right", "Begleitend zum 8-Wochen-Plan") + footer_left = meta.get("footer_left", "") + + def deco(canvas, doc): + canvas.saveState() + canvas.setFillColor(BRAND); canvas.setFont("Helvetica-Bold", 8) + canvas.drawString(LM, PAGE_H - 1.05 * cm, header_left) + canvas.setFont("Helvetica", 8); canvas.setFillColor(SUB) + canvas.drawRightString(PAGE_W - RM, PAGE_H - 1.05 * cm, header_right) + canvas.setStrokeColor(HAIR); canvas.setLineWidth(0.7) + canvas.line(LM, PAGE_H - 1.2 * cm, PAGE_W - RM, PAGE_H - 1.2 * cm) + canvas.line(LM, 1.15 * cm, PAGE_W - RM, 1.15 * cm) + canvas.setFont("Helvetica", 7.5); canvas.setFillColor(SUB) + canvas.drawString(LM, 0.8 * cm, footer_left) + canvas.drawRightString(PAGE_W - RM, 0.8 * cm, "Seite %d" % doc.page) + canvas.restoreState() + + buf = BytesIO() + doc = SimpleDocTemplate(buf, pagesize=A4, leftMargin=LM, rightMargin=RM, + topMargin=1.6 * cm, bottomMargin=1.5 * cm, + title=meta.get("doc_title", meta.get("title", "Rezepte"))) + doc.build(story, onFirstPage=deco, onLaterPages=deco) + return buf.getvalue() + + +if __name__ == "__main__": + open("/tmp/recipe_test.pdf", "wb").write(build_pdf(default_config())) + print("OK") diff --git a/plugins/trainingsplan/schema_contract.py b/plugins/trainingsplan/schema_contract.py new file mode 100644 index 0000000..0a2462d --- /dev/null +++ b/plugins/trainingsplan/schema_contract.py @@ -0,0 +1,391 @@ +# -*- coding: utf-8 -*- +"""Versionierter Datenvertrag zwischen Trainingsplan-Editor und Session-Tracker.""" +from __future__ import annotations + +import copy +import hashlib +import re +from typing import Any + +PLAN_SCHEMA_VERSION = 3 +CONTRACT_VERSION = 2 +RESULT_SCHEMA_VERSION = 2 + +VALID_RESULT_MODES = {"auto", "reps", "seconds", "minutes", "none"} +VALID_WEIGHT_MODES = {"none", "optional", "required"} +VALID_LATERALITY = {"bilateral", "unilateral"} +VALID_SIDES_MODES = {"same", "separate"} +VALID_TRAINING_MODES = {"auto", "sets_reps", "tabata", "fixed_interval"} + + +def slug(value: Any) -> str: + text = str(value or "").strip().lower() + text = text.replace("ä", "ae").replace("ö", "oe").replace("ü", "ue").replace("ß", "ss") + return re.sub(r"[^a-z0-9]+", "-", text).strip("-") or "item" + + +def stable_id(prefix: str, *parts: Any) -> str: + seed = "|".join(str(part or "") for part in parts) + digest = hashlib.sha1(seed.encode("utf-8")).hexdigest()[:10] + return f"{prefix}-{slug(parts[-1] if parts else prefix)[:30]}-{digest}" + + +def normalize_result_schema(value: Any, partial: bool = False) -> dict[str, Any] | None: + if value is None: + return None + if not isinstance(value, dict): + value = {} + mode = str(value.get("mode") or ("auto" if not partial else "")) + weight_mode = str(value.get("weight_mode") or ("none" if not partial else "")) + laterality = str(value.get("laterality") or ("bilateral" if not partial else "")) + sides_mode = str(value.get("sides_mode") or ("same" if not partial else "")) + result: dict[str, Any] = {} + if mode in VALID_RESULT_MODES: + result["mode"] = mode + elif not partial: + result["mode"] = "auto" + if weight_mode in VALID_WEIGHT_MODES: + result["weight_mode"] = weight_mode + elif not partial: + result["weight_mode"] = "none" + if laterality in VALID_LATERALITY: + result["laterality"] = laterality + elif not partial: + result["laterality"] = "bilateral" + if sides_mode in VALID_SIDES_MODES: + result["sides_mode"] = sides_mode + elif not partial: + result["sides_mode"] = "same" + try: + sets = int(value.get("sets")) + except (TypeError, ValueError): + sets = 0 + if 1 <= sets <= 40: + result["sets"] = sets + if "locked_sets" in value: + result["locked_sets"] = bool(value.get("locked_sets")) + if result.get("laterality") == "bilateral": + result["sides_mode"] = "same" + return result or None + + + +def infer_result_schema(name: Any, cue: Any = "", *, stage: bool = False) -> dict[str, Any]: + """Leitet für Legacy- und neue Standardpläne ein explizites Basisschema ab. + + Die Ableitung ist nur eine Migrationshilfe. Nach dem Speichern liegt das + Ergebnis fest im Plan und kann im Editor jederzeit korrigiert werden. + """ + text = f"{name or ''} {cue or ''}".casefold() + no_measure = re.search( + r"(?:regeneration|schlaf|schmerzcheck|pausieren|protokollieren|prüfen|check|" + r"mobility|mobilität|dehnen|stretch|cool.?down|warm.?up|spaziergang locker)$", + text, + ) + if no_measure: + mode = "none" + elif re.search(r"(?:spaziergang|gehen|walk).*(?:min|minute)|(?:min|minute).*(?:spaziergang|gehen|walk)", text): + mode = "minutes" + elif re.search(r"(?:hold|plank|hang|wall\s*sit|wandsitz|isometr|halte|stützposition|deep\s+squat)", text): + mode = "seconds" + elif re.search(r"(?:sek(?:unde)?|\d+\s*s)", text) and not re.search(r"(?:reps?|wiederhol|negativ|push|pull|squat|row|rudern|dip)", text): + mode = "seconds" + else: + mode = "reps" + + has_load = bool(re.search(r"(?:kb|kettlebell|kurzhantel|dumbbell|rucksack|gewicht|last)", text)) + bodyweight_mix = bool(re.search(r"(?:optional|wahlweise|ggf\.?|bodyweight|bw|ungewichtet|körpergewicht)", text)) + weight_mode = "optional" if has_load and bodyweight_mix else ("required" if has_load else "none") + # Bei generischen Stufen mit „gewichtet“ bleibt Gewicht optional, wenn eine + # ungewichtete Variante im selben Text genannt wird. + if "gewichtet" in text and ("ungewichtet" in text or "bw" in text): + weight_mode = "optional" + + unilateral = bool(re.search(r"(?:einarm|einbein|je\s+seite|pro\s+seite|links.?/?rechts|seitenwechsel|side\s+plank|wechsel)", text)) + schema = { + "mode": mode, + "weight_mode": weight_mode, + "laterality": "unilateral" if unilateral else "bilateral", + "sides_mode": "separate" if unilateral else "same", + } + # Nicht messbare Checklistenpunkte benötigen nie Gewicht oder Seitenfelder. + if mode == "none": + schema.update({"weight_mode": "none", "laterality": "bilateral", "sides_mode": "same"}) + return normalize_result_schema(schema, partial=False) or { + "mode": "auto", "weight_mode": "none", "laterality": "bilateral", "sides_mode": "same" + } + +def infer_cluster(name: str, key: str = "") -> tuple[str, str]: + text = f"{key} {name}".lower() + rules = [ + (r"liegest|push.?up|\bls\b", "horizontal_push", "Push-up"), + (r"dip", "vertical_push", "Dip"), + (r"pike|overhead|floor press", "vertical_push", "Schulterdrücken"), + (r"pull.?up|klimm|hang", "vertical_pull", "Pull-up / Hang"), + (r"rudern|row", "horizontal_pull", "Rudern"), + (r"squat|kniebeuge|goblet|lunge|ausfallschritt|wall sit", "knee_dominant", "Kniebeuge / Ausfallschritt"), + (r"deadlift|rdl|hinge|swing|glute bridge", "hip_hinge", "Hip Hinge / Hüftstreckung"), + (r"plank|hollow|dead bug|side plank|core", "core", "Core"), + (r"curl", "elbow_flexion", "Bizeps"), + (r"waden|calf", "calf", "Waden"), + (r"halo|around|carry|bottoms", "loaded_mobility", "Kettlebell-Kontrolle"), + ] + for pattern, cid, label in rules: + if re.search(pattern, text): + return cid, label + return "general", "Allgemein" + + +def infer_factor(name: str, cluster_id: str) -> float: + text = str(name or "").lower() + if cluster_id == "horizontal_push": + if "wand" in text: return 0.05 + if "hoch" in text and ("incline" in text or "inkline" in text): return 0.10 + if "mittel" in text and ("incline" in text or "inkline" in text): return 0.20 + if "tief" in text and ("incline" in text or "inkline" in text): return 0.35 + if "negativ" in text and ("voll" in text or "erste" in text): return 0.85 + if "negativ" in text: return 0.70 + if "assist" in text: return 0.55 + if "knie" in text: return 0.45 + if "teil" in text or "partial" in text: return 0.35 + if "voll" in text or "standard" in text or "normal" in text: return 1.00 + if cluster_id == "vertical_pull": + if "active hang" in text or "hang" in text: return 0.20 + if "scapular" in text: return 0.25 + if "negativ" in text: return 0.70 + if "leicht assist" in text: return 0.80 + if "assist" in text: return 0.55 + if "voll" in text or "klimm" in text or "pull-up" in text: return 1.00 + if cluster_id == "horizontal_pull": + if "aufrecht" in text or "stark assist" in text: return 0.25 + if "bent" in text or "gebeugt" in text: return 0.50 + if "straight" in text or "gestreckt" in text: return 1.00 + if "füße erhöht" in text: return 1.15 + if cluster_id == "knee_dominant": + if "hold" in text: return 1.00 + if "chair" in text or "box" in text or "stuhl" in text: return 0.35 + if "negativ" in text: return 0.75 + if "assist" in text or "festhalten" in text: return 0.55 + if "voll" in text or "goblet" in text or "körpergewicht" in text: return 1.00 + if cluster_id == "vertical_push": + if "hold" in text: return 0.30 + if "assist" in text: return 0.60 + if "negativ" in text: return 0.80 + if "voll" in text: return 1.00 + if cluster_id == "core": + if "knie" in text or "tuck" in text: return 0.65 + if "halb" in text: return 0.82 + if "voll" in text: return 1.00 + return 1.00 + + +def migrate_training_config(config: Any, plan_id: str | None = None) -> dict[str, Any]: + cfg = copy.deepcopy(config or {}) + if cfg.get("type") == "recipe": + return cfg + cfg["schema_version"] = PLAN_SCHEMA_VERSION + cfg["contract_version"] = CONTRACT_VERSION + meta = cfg.setdefault("meta", {}) + resolved_plan_id = str(cfg.get("plan_id") or plan_id or stable_id("plan", meta.get("title") or "training")) + cfg["plan_id"] = resolved_plan_id + + phases = cfg.setdefault("phases", {}).setdefault("items", []) + for index, phase in enumerate(phases): + if not isinstance(phase, dict): + phases[index] = phase = {"name": str(phase or "")} + phase.setdefault("id", stable_id("phase", resolved_plan_id, index, phase.get("name"))) + phase_ids = [phase.get("id") for phase in phases] + + fmt = cfg.get("training_format") if isinstance(cfg.get("training_format"), dict) else {} + mode = str(fmt.get("mode") or "auto") + if mode not in VALID_TRAINING_MODES: + mode = "auto" + fmt["mode"] = mode + fmt["fixed_interval"] = bool(fmt.get("fixed_interval") or mode in {"tabata", "fixed_interval"}) + fmt["rounds_scope"] = str(fmt.get("rounds_scope") or "block") + fmt["schema_version"] = 1 + cfg["training_format"] = fmt + + days = cfg.get("days") if isinstance(cfg.get("days"), list) else [] + for di, day in enumerate(days): + if not isinstance(day, dict): + continue + day.setdefault("id", stable_id("day", resolved_plan_id, day.get("num", di + 1), day.get("focus"))) + rotations = day.get("rotations") if isinstance(day.get("rotations"), list) else [] + for ri, rotation in enumerate(rotations): + if not isinstance(rotation, dict): + continue + rotation.setdefault("id", stable_id("rotation", day["id"], ri, rotation.get("label"))) + exercises = rotation.get("exercises") if isinstance(rotation.get("exercises"), list) else [] + day_num = day.get("num", di + 1) + for ei, exercise in enumerate(exercises): + if not isinstance(exercise, dict): + continue + # Persist the original positional key once so legacy session data can + # still be migrated safely even after the published plan is reordered. + exercise.setdefault("legacy_id", f"d{day_num}-r{ri}-e{ei}") + explicit_progression = str(exercise.get("progression_id") or exercise.get("key") or "").strip() + progression_id = explicit_progression or f"name:{slug(exercise.get('name'))}" + exercise["progression_id"] = progression_id + exercise["key"] = str(exercise.get("key") or progression_id) + exercise.setdefault("id", stable_id("exercise", rotation["id"], progression_id, exercise.get("name"), ei)) + exercise.setdefault("exercise_id", stable_id("movement", progression_id, exercise.get("name"))) + cid, label = infer_cluster(str(exercise.get("name") or ""), progression_id) + exercise.setdefault("movement_cluster", cid) + exercise.setdefault("movement_label", label) + if exercise.get("result_schema") is not None: + exercise["result_schema"] = normalize_result_schema(exercise.get("result_schema"), partial=False) + else: + exercise["result_schema"] = infer_result_schema(exercise.get("name"), exercise.get("cue")) + cfg["days"] = days + + old_stages = cfg.get("stages") if isinstance(cfg.get("stages"), dict) else {} + new_stages: dict[str, Any] = {} + for key, raw_stage in old_stages.items(): + stage = copy.deepcopy(raw_stage) if isinstance(raw_stage, dict) else {"steps": raw_stage if isinstance(raw_stage, list) else []} + stage_id = str(stage.get("id") or stable_id("progression", resolved_plan_id, key, stage.get("name"))) + stage["id"] = stage_id + stage["key"] = str(stage.get("key") or key) + raw_steps = stage.get("steps") if isinstance(stage.get("steps"), list) else [] + schemas = stage.get("result_schemas") if isinstance(stage.get("result_schemas"), list) else [] + steps: list[dict[str, Any]] = [] + for index, raw_step in enumerate(raw_steps): + if isinstance(raw_step, dict): + step = copy.deepcopy(raw_step) + step_name = str(step.get("name") or step.get("label") or "") + else: + step_name = str(raw_step or "") + step = {"name": step_name} + step.setdefault("id", stable_id("step", stage_id, index, step_name)) + step.setdefault("phase_id", phase_ids[index] if index < len(phase_ids) else "") + schema = step.get("result_schema") + if schema is None and index < len(schemas): + schema = schemas[index] + if schema is not None: + step["result_schema"] = normalize_result_schema(schema, partial=False) + else: + step["result_schema"] = infer_result_schema(step_name, stage.get("name"), stage=True) + cid, _ = infer_cluster(str(stage.get("name") or step_name), key) + step.setdefault("movement_cluster", cid) + step.setdefault("factor", infer_factor(step_name, cid)) + steps.append(step) + stage["steps"] = steps + stage.pop("result_schemas", None) + new_stages[str(key)] = stage + cfg["stages"] = new_stages + + # Zentrale, planbezogene Übungsbibliothek. Der Tracker nutzt diese vor Regex-Fallbacks. + catalog: dict[str, Any] = {} + for day in days: + for rotation in day.get("rotations", []): + for exercise in rotation.get("exercises", []): + eid = str(exercise.get("exercise_id")) + pid = str(exercise.get("progression_id") or "") + entry = catalog.setdefault(eid, { + "id": eid, + "name": str(exercise.get("name") or ""), + "progression_id": pid, + "movement_cluster": exercise.get("movement_cluster") or "general", + "movement_label": exercise.get("movement_label") or "Allgemein", + "result_schema": copy.deepcopy(exercise.get("result_schema")), + "variants": [], + }) + stage = new_stages.get(pid) + if stage: + entry["variants"] = [ + { + "id": step.get("id"), "name": step.get("name", ""), + "factor": step.get("factor", 1.0), + "movement_cluster": step.get("movement_cluster") or entry["movement_cluster"], + "result_schema": copy.deepcopy(step.get("result_schema")), + } + for step in stage.get("steps", []) + ] + cfg["exercise_catalog"] = catalog + return cfg + + +def validate_training_config(config: Any) -> dict[str, list[dict[str, Any]]]: + cfg = migrate_training_config(config) + errors: list[dict[str, Any]] = [] + warnings: list[dict[str, Any]] = [] + ids: dict[str, str] = {} + + def register(value: Any, location: str) -> None: + sid = str(value or "") + if not sid: + errors.append({"code": "missing_id", "location": location, "message": "Stabile ID fehlt."}) + elif sid in ids: + errors.append({"code": "duplicate_id", "location": location, "message": f"ID wird bereits bei {ids[sid]} verwendet."}) + else: + ids[sid] = location + + def validate_result_schema(schema: Any, location: str, *, required: bool) -> None: + if not isinstance(schema, dict): + target = errors if required else warnings + target.append({"code": "missing_result_schema", "location": location, "message": "Ergebniserfassung ist nicht eindeutig festgelegt."}) + return + mode = str(schema.get("mode") or "auto") + weight = str(schema.get("weight_mode") or "none") + laterality = str(schema.get("laterality") or "bilateral") + sides = str(schema.get("sides_mode") or "same") + if mode == "none" and weight != "none": + errors.append({"code": "invalid_result_schema", "location": location, "message": "Ohne Messwert darf kein Gewicht verlangt werden."}) + if laterality == "bilateral" and sides == "separate": + errors.append({"code": "invalid_sides", "location": location, "message": "Getrennte Seitenwerte sind nur bei einseitigen Übungen möglich."}) + if laterality == "unilateral" and sides not in {"same", "separate"}: + errors.append({"code": "invalid_sides", "location": location, "message": "Für einseitige Übungen muss die Seitenlogik festgelegt sein."}) + try: + sets = int(schema.get("sets") or 0) + except (TypeError, ValueError): + sets = 0 + if "sets" in schema and not 1 <= sets <= 40: + errors.append({"code": "invalid_sets", "location": location, "message": "Die feste Anzahl muss zwischen 1 und 40 liegen."}) + + for pi, phase in enumerate(cfg.get("phases", {}).get("items", [])): + register(phase.get("id"), f"Phase {pi + 1}") + if not str(phase.get("name") or "").strip(): + errors.append({"code": "empty_phase", "location": f"Phase {pi + 1}", "message": "Phasenname fehlt."}) + for di, day in enumerate(cfg.get("days", [])): + register(day.get("id"), f"Tag {di + 1}") + for ri, rotation in enumerate(day.get("rotations", [])): + register(rotation.get("id"), f"Tag {di + 1}, Rotation {ri + 1}") + exercises = rotation.get("exercises", []) + if not exercises: + warnings.append({"code": "empty_rotation", "location": f"Tag {di + 1}, Rotation {ri + 1}", "message": "Rotation enthält keine Übungen."}) + for ei, exercise in enumerate(exercises): + location = f"Tag {di + 1}, Rotation {ri + 1}, Übung {ei + 1}" + register(exercise.get("id"), location) + if not str(exercise.get("name") or "").strip(): + errors.append({"code": "empty_exercise", "location": location, "message": "Übungsname fehlt."}) + pid = str(exercise.get("progression_id") or "") + if pid and not pid.startswith("name:") and pid not in cfg.get("stages", {}): + warnings.append({"code": "unknown_progression", "location": location, "message": f"Progression „{pid}“ ist nicht definiert."}) + validate_result_schema(exercise.get("result_schema"), f"{location}, Ergebniserfassung", required=True) + for key, stage in cfg.get("stages", {}).items(): + register(stage.get("id"), f"Progression {key}") + for si, step in enumerate(stage.get("steps", [])): + register(step.get("id"), f"Progression {key}, Stufe {si + 1}") + step_location = f"Progression {key}, Stufe {si + 1}" + if not str(step.get("name") or "").strip(): + warnings.append({"code": "empty_step", "location": step_location, "message": "Stufenname ist leer."}) + validate_result_schema(step.get("result_schema"), f"{step_location}, Ergebniserfassung", required=False) + try: + factor = float(step.get("factor") or 0) + except (TypeError, ValueError): + factor = 0 + if factor <= 0: + errors.append({"code": "invalid_factor", "location": step_location, "message": "Der Analysefaktor muss größer als 0 sein."}) + fmt = cfg.get("training_format", {}) + if fmt.get("mode") in {"tabata", "fixed_interval"}: + if not fmt.get("work_seconds"): + errors.append({"code": "missing_work_seconds", "location": "Trainingsformat", "message": "Arbeitszeit fehlt."}) + if not fmt.get("rounds"): + errors.append({"code": "missing_rounds", "location": "Trainingsformat", "message": "Intervallzahl fehlt."}) + rounds = int(fmt.get("rounds") or 0) + for di, day in enumerate(cfg.get("days", [])): + for ri, rotation in enumerate(day.get("rotations", [])): + count = len(rotation.get("exercises", [])) + if rounds and count and rounds % count: + warnings.append({"code": "uneven_intervals", "location": f"Tag {di + 1}, Rotation {ri + 1}", "message": f"{rounds} Intervalle sind nicht durch {count} Übungen teilbar. Feste Satzanzahl im Ergebnisschema setzen."}) + return {"errors": errors, "warnings": warnings, "config": cfg} diff --git a/plugins/trainingsplan/static/index.html b/plugins/trainingsplan/static/index.html new file mode 100644 index 0000000..a46a907 --- /dev/null +++ b/plugins/trainingsplan/static/index.html @@ -0,0 +1,1118 @@ + + + + + +Trainingsplan-Editor + + + + + +
+

Trainingsplan-Editor

+ + + Revision + + + + + +
+ +
+ Phase: + + + + + + + + + +
+ +
+ +
+
+ Umlaute und Sonderzeichen (ü ä ö ß – · • × °) einfach direkt eintippen — sie landen so im PDF. + Für Formatierung: <b>…</b> = fett, <br/> = Zeilenumbruch, + <font color='#6D28D9'>…</font> = Farbe. Ein kaufmännisches Und tippst du normal als &. +
+ +
+
+ +
+ + + + diff --git a/plugins/trainingsplan/storage.py b/plugins/trainingsplan/storage.py new file mode 100644 index 0000000..581c09e --- /dev/null +++ b/plugins/trainingsplan/storage.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +from __future__ import annotations +import json, os, shutil, tempfile +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + + +def utc_now() -> str: + return datetime.now(timezone.utc).isoformat(timespec="seconds") + + +def atomic_json_write(path: str | Path, payload: Any, backup_dir: str | Path | None = None) -> None: + target = Path(path) + target.parent.mkdir(parents=True, exist_ok=True) + if backup_dir and target.exists(): + bdir = Path(backup_dir); bdir.mkdir(parents=True, exist_ok=True) + stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S%fZ") + shutil.copy2(target, bdir / f"{target.stem}_{stamp}{target.suffix}") + fd, tmp = tempfile.mkstemp(prefix=f".{target.name}.", suffix=".tmp", dir=target.parent) + try: + with os.fdopen(fd, "w", encoding="utf-8") as handle: + json.dump(payload, handle, ensure_ascii=False, indent=2) + handle.flush(); os.fsync(handle.fileno()) + os.replace(tmp, target) + finally: + if os.path.exists(tmp): os.unlink(tmp) diff --git a/plugins/trainingsplan/tests/test_contract.py b/plugins/trainingsplan/tests/test_contract.py new file mode 100644 index 0000000..e05478b --- /dev/null +++ b/plugins/trainingsplan/tests/test_contract.py @@ -0,0 +1,70 @@ +from __future__ import annotations +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parents[1])) +from schema_contract import migrate_training_config, validate_training_config + + +def sample_config(): + return { + "meta": {"title": "Test", "weeks": 2}, + "training_format": {"mode": "tabata", "fixed_interval": True, "work_seconds": 20, "rest_seconds": 10, "rounds": 8}, + "phases": {"items": [{"name": "Start", "weeks": 1}, {"name": "Aufbau", "weeks": 1}]}, + "days": [{"num": 1, "focus": "A", "rotations": [{"label": "Block", "exercises": [{ + "name": "Squat-Progression", "key": "squat", "result_schema": {"mode": "reps", "weight_mode": "none", "laterality": "bilateral"} + }]}]}], + "stages": {"squat": {"name": "Squat-Progression", "steps": [ + "Deep Squat Hold", "Volle Squats" + ], "result_schemas": [ + {"mode": "seconds", "weight_mode": "optional", "laterality": "bilateral"}, + {"mode": "reps", "weight_mode": "optional", "laterality": "bilateral"}, + ]}}, + } + + +def test_migration_creates_stable_ids_object_steps_and_catalog(): + first = migrate_training_config(sample_config(), plan_id="test") + second = migrate_training_config(first, plan_id="test") + assert first["schema_version"] == 3 + assert first["contract_version"] == 2 + assert first["days"][0]["id"] == second["days"][0]["id"] + exercise = first["days"][0]["rotations"][0]["exercises"][0] + assert exercise["id"] == second["days"][0]["rotations"][0]["exercises"][0]["id"] + steps = first["stages"]["squat"]["steps"] + assert all(isinstance(step, dict) and step.get("id") for step in steps) + assert steps[0]["result_schema"]["mode"] == "seconds" + assert steps[1]["result_schema"]["mode"] == "reps" + assert first["exercise_catalog"] + + +def test_validation_accepts_complete_plan(): + result = validate_training_config(sample_config()) + assert result["errors"] == [] + + +def test_missing_result_schemas_are_migrated_to_explicit_defaults(): + cfg = sample_config() + exercise = cfg["days"][0]["rotations"][0]["exercises"][0] + exercise.pop("result_schema", None) + cfg["stages"]["squat"].pop("result_schemas", None) + migrated = migrate_training_config(cfg, plan_id="test") + ex_schema = migrated["days"][0]["rotations"][0]["exercises"][0]["result_schema"] + assert ex_schema["mode"] == "reps" + steps = migrated["stages"]["squat"]["steps"] + assert steps[0]["result_schema"]["mode"] == "seconds" + assert steps[1]["result_schema"]["mode"] == "reps" + assert validate_training_config(migrated)["errors"] == [] + + +def test_legacy_position_id_is_persisted_across_reordering(): + migrated = migrate_training_config(sample_config(), plan_id="test") + exercise = migrated["days"][0]["rotations"][0]["exercises"][0] + legacy_id = exercise["legacy_id"] + exercise_id = exercise["id"] + migrated["days"][0]["rotations"][0]["exercises"].insert(0, { + "name": "Neue Übung", "key": "new", "result_schema": {"mode": "reps"} + }) + remigrated = migrate_training_config(migrated, plan_id="test") + original = next(item for item in remigrated["days"][0]["rotations"][0]["exercises"] if item["id"] == exercise_id) + assert original["legacy_id"] == legacy_id diff --git a/plugins/trainingstracker/README.md b/plugins/trainingstracker/README.md new file mode 100644 index 0000000..0454ccd --- /dev/null +++ b/plugins/trainingstracker/README.md @@ -0,0 +1,85 @@ +# Trainings-Session-Tracker 2.0 + +Mobiler Session-Tracker für veröffentlichte Pläne des boehmitools-Plugins `trainingsplan`. + +## Datenaufteilung + +```text +data/trainingstracker/ +├── sessions/.json +├── analyses// +│ ├── index.json +│ ├── state.json +│ ├── week-01.json +│ ├── week-02.json +│ └── overall.json +└── proposals/.json +``` + +Die Sessiondatei enthält ausschließlich Profil-, Wochenstatus- und Sessiondaten. Prompts, KI-Antworten und Jobstatus liegen vollständig getrennt. + +## Genau eine Analyse pro Bereich + +Für jede Woche gibt es höchstens eine Datei `week-NN.json`, für den Gesamtplan genau eine `overall.json`. Eine neue Auswertung überschreibt die vorherige Datei. Es gibt keine Analysehistorie und keine Versionierung von KI-Auswertungen. Ältere Zeitstempeldateien werden beim ersten Lesen auf dieses Modell reduziert; erhalten bleibt nur die zuvor als aktuell markierte Auswertung. + +Unveränderte Daten werden über einen Hash erkannt und lösen keinen neuen OpenAI-Aufruf aus. Der Hash berücksichtigt unter anderem: + +- veröffentlichte Planrevision, +- Plan- und Vertragsversion, +- Prompt- und Antwortschemaversion, +- Modell, +- Normalisierungsversion, +- Sessiondaten und Wochenstatus. + +## Sessiondaten und Konfliktschutz + +Der Browser speichert nur die aktuell bearbeitete Session als Patch. Eine Revisionsnummer verhindert, dass ein älterer Browser-Tab neuere Daten überschreibt. + +Jede Session merkt `plan_id` und `plan_revision`. Alte positionsbasierte Einträge wie `d1-r0-e0` werden beim Öffnen automatisch den neuen stabilen Übungs-IDs zugeordnet und beim nächsten Speichern migriert. + +Sessionstatus: + +```text +planned → in_progress → stopped/completed +stopped → in_progress +completed → in_progress +``` + +Übungsstatus: + +- offen, +- erledigt, +- teilweise, +- übersprungen mit optionalem Grund. + +Nicht enthalten sind RIR/RPE, Technikbewertung oder unterschiedliche Gewichte pro Satz. + +## Plan- und progressionsabhängige Ergebnisfelder + +Der Tracker liest das Ergebnisformat zunächst aus der gewählten Progressionsstufe, danach aus der Übung. So kann eine Squat-Progression zunächst Sekunden mit optionalem Gewicht und später Wiederholungen oder Gewicht plus Wiederholungen verlangen. + +Alte Freitextergebnisse bleiben lesbar. Eindeutige Werte werden in strukturierte Felder übernommen, unklare Angaben nicht erfunden. + +## Wochenabschluss + +Eine Woche kann ausdrücklich als laufend oder abgeschlossen markiert werden. Eine laufende Woche erzeugt eine Zwischenanalyse, eine geschlossene Woche eine Abschlussanalyse. Wird der Wochenstatus oder eine Session geändert, gilt die bestehende Analyse als veraltet. Die nächste manuelle Auswertung überschreibt sie. + +## Progressionsanalyse + +Wochen werden einzeln analysiert. Die Gesamtanalyse verwendet nur aktuelle Wochenzusammenfassungen und lokale Aggregate, nicht erneut sämtliche Rohsessions. + +Der veröffentlichte Plan ist bindend. Bei Tabata bleiben Arbeitszeit, Pause, Rundenzahl und Satzlogik unverändert. Die KI erhält lokal berechnete Variantencluster und soll deren Faktoren nicht selbst neu erfinden. + +Planvorschläge werden strukturiert mit `exercise_id`, `progression_id` und `step_id` nach `data/trainingstracker/proposals/` geschrieben. Der Planeditor zeigt sie als Prüfpostfach an und verändert den Plan niemals automatisch. + +## Robuste Analysejobs + +Der Jobstatus wird vor dem API-Aufruf gespeichert. Eine Prozess- und Dateisperre verhindert parallele Jobs für denselben Plan. Ein Heartbeat verlängert die Job-Lease. Nach einem Prozessabbruch läuft die Sperre zeitnah ab, auch nach einem Browser-Reload oder Containerneustart. + +## Übungsbibliothek und FAQ + +Die planbezogene `exercise_catalog` ist die primäre Quelle für Bewegungscluster, Varianten und Faktoren. Die FAQ zeigt diese Planbibliothek sowie die transparenten Fallback-Tabellen. Dynamische Übungen werden als Referenz-Reps, Holds als Referenzsekunden und externe Lasten als kg·Reps beziehungsweise kg·s ausgewertet. + +## Oberflächenzustand + +Der letzte Tab, die Woche, der Tag und die ausgewählte Analyse bleiben pro Trainingsplan im Browser erhalten. diff --git a/plugins/trainingstracker/analysis_store.py b/plugins/trainingstracker/analysis_store.py new file mode 100644 index 0000000..7574dad --- /dev/null +++ b/plugins/trainingstracker/analysis_store.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import annotations +import re +from pathlib import Path +from typing import Any + + +def current_filename(record: dict[str, Any]) -> str: + if record.get("type") == "week": + return f"week-{int(record.get('week') or 0):02d}.json" + return "overall.json" + + +def cleanup_legacy_files(directory: Path, record: dict[str, Any], keep: str) -> None: + if record.get("type") == "week": + week = int(record.get("week") or 0) + pattern = re.compile(rf"^week[-_]?0?{week}(?:[-_].*)?\.json$", re.I) + else: + pattern = re.compile(r"^overall(?:[-_].*)?\.json$", re.I) + for path in directory.glob("*.json"): + if path.name in {"index.json", "state.json", keep}: continue + if pattern.match(path.name): + try: path.unlink() + except OSError: pass diff --git a/plugins/trainingstracker/app.py b/plugins/trainingstracker/app.py new file mode 100644 index 0000000..22f6fd0 --- /dev/null +++ b/plugins/trainingstracker/app.py @@ -0,0 +1,2964 @@ +# -*- coding: utf-8 -*- +"""Mobile Tracking-App für boehmitools-Trainingspläne. + +Quelle, strikt read-only: + /data/trainingsplan/plans/*.json + +Tracker-Daten: + /data/trainingstracker/sessions/.json + +Analyse-Daten, vollständig getrennt von den Sessions. Pro Woche und +Gesamtplan existiert jeweils nur eine überschreibbare Datei: + /data/trainingstracker/analyses// +""" +from __future__ import annotations + +import hashlib +import html +import json +import os +import re +import tempfile +import threading +import time +from contextlib import contextmanager +from copy import deepcopy +from datetime import date, datetime, timedelta, timezone +from pathlib import Path +from typing import Any + +from flask import Flask, Response, jsonify, request, send_file + +from contract import (PLAN_SCHEMA_VERSION, CONTRACT_VERSION, TRACKER_SCHEMA_VERSION, RESULT_DATA_VERSION, PROMPT_VERSION, ANALYSIS_SCHEMA_VERSION, PROGRESSION_NORMALIZATION_VERSION) +from session_store import merge_session_patch, VALID_ITEM_STATUSES +from analysis_store import current_filename, cleanup_legacy_files + +PLUGIN_DIR = Path(__file__).resolve().parent +STATIC_DIR = PLUGIN_DIR / "static" +_WRITE_LOCK = threading.RLock() +_ANALYSIS_GUARD = threading.RLock() +_ANALYSIS_THREADS: dict[str, threading.Thread] = {} +_ANALYSIS_TIMEOUT_SECONDS = 3 * 60 +_ANALYSIS_HEARTBEAT_SECONDS = 30 + + +# Version der deterministischen, planunabhängigen Normalisierung. Eine Änderung +# macht bestehende Analysen über den Dataset-Hash automatisch veraltet. + +# Die Faktoren sind keine medizinischen oder biomechanischen Naturkonstanten. +# Sie dienen ausschließlich dazu, Varianten desselben Bewegungsmusters in einer +# nachvollziehbaren Trendkurve abzubilden. Push-up-Anker orientieren sich an veröffentlichten Kraftplattenmessungen; die +# eigentliche Referenzskala ist bewusst nichtlinear, damit ein Stufenwechsel mit +# weniger Wiederholungen nicht fälschlich als Rückschritt erscheint. Alle übrigen +# Werte sind konservative Heuristiken aus Hebel, Unterstützung, ROM und Stufenfolge. +_EQUIVALENCE_TABLES: list[dict[str, Any]] = [ + { + "id": "push_up", "label": "Push-up", "mode": "reps", "priority": 100, + "reference": "voller Push-up = 1,00 Referenz-Rep", + "variants": [ + {"label": "Wand-Push-up", "factor": 0.05, "patterns": [r"wand", r"wall"]}, + {"label": "Incline hoch, ca. 60 cm", "factor": 0.10, "patterns": [r"inkline.?\(?hoch", r"incline.?high", r"hände hoch"]}, + {"label": "Incline mittel", "factor": 0.20, "patterns": [r"inkline.?mittel", r"incline.?medium", r"halbe höhe"]}, + {"label": "Incline tief, ca. 30 cm", "factor": 0.35, "patterns": [r"inkline.?tief", r"incline.?low", r"tiefe erhöh"]}, + {"label": "Incline tiefer / Knie", "factor": 0.40, "patterns": [r"tiefer.?/? ?knie"]}, + {"label": "Knie-Push-up", "factor": 0.45, "patterns": [r"knie.?liegest", r"knee.?push", r"\bknie\b"]}, + {"label": "Teilwiederholung", "factor": 0.35, "patterns": [r"teil.?rep", r"partial", r"kleiner rom"]}, + {"label": "Negativ", "factor": 0.70, "patterns": [r"negativ", r"eccentric", r"absenk"]}, + {"label": "Negativ + erste volle", "factor": 0.85, "patterns": [r"negativ.*erste volle"]}, + {"label": "Voller Push-up", "factor": 1.00, "patterns": [r"volle? push", r"standard", r"normal", r"boden"]}, + {"label": "Decline / Zusatzlast", "factor": 1.25, "patterns": [r"decline", r"füße erhöht", r"zusatzlast", r"gewichtete"]}, + ], + }, + { + "id": "dip", "label": "Dip", "mode": "reps", "priority": 82, + "reference": "voller Dip am Barren = 1,00 Referenz-Rep", + "variants": [ + {"label": "Bank-Dip, Knie stark gebeugt, kleiner ROM", "factor": 0.25, "patterns": [r"knie stark gebeugt.*klein"]}, + {"label": "Bank-Dip, Knie gebeugt, voller ROM", "factor": 0.40, "patterns": [r"knie gebeugt.*voll"]}, + {"label": "Bank-Dip, Füße etwas weiter", "factor": 0.55, "patterns": [r"füße etwas weiter"]}, + {"label": "Bank-Dip, Füße weit / Beine gerade", "factor": 0.70, "patterns": [r"füße weiter", r"beine gerade"]}, + {"label": "Assistierter Dip", "factor": 0.65, "patterns": [r"assistiert"]}, + {"label": "Negativer Dip", "factor": 0.80, "patterns": [r"negativ"]}, + {"label": "Voller Dip", "factor": 1.00, "patterns": [r"volle? dip", r"standard dip"]}, + ], + }, + { + "id": "pike_press", "label": "Pike Push-up", "mode": "reps", "priority": 78, + "reference": "voller Pike Push-up = 1,00 Referenz-Rep", + "variants": [ + {"label": "Hold + kleine Senkung", "factor": 0.30, "patterns": [r"kleine senkung", r"partial"]}, + {"label": "Pike Push-up, Hände erhöht", "factor": 0.50, "patterns": [r"hände erhöht", r"erhöht"]}, + {"label": "Pike Push-up am Boden, kleiner ROM", "factor": 0.75, "patterns": [r"boden.*klein", r"kleiner rom"]}, + {"label": "Voller Pike Push-up", "factor": 1.00, "patterns": [r"voll", r"pike push"]}, + ], + }, + { + "id": "inverted_row", "label": "Inverted Row", "mode": "reps", "priority": 92, + "reference": "Straight-Leg Row = 1,00 Referenz-Rep", + "variants": [ + {"label": "Aufrechte assistierte Row", "factor": 0.25, "patterns": [r"aufrecht", r"stark assist"]}, + {"label": "Bent-Leg Row", "factor": 0.50, "patterns": [r"bent.?leg", r"beine gebeugt", r"knie gebeugt"]}, + {"label": "Straight-Leg Row", "factor": 1.00, "patterns": [r"straight.?leg", r"beine gestreckt", r"volle row"]}, + {"label": "Füße erhöht", "factor": 1.15, "patterns": [r"füße erhöht", r"feet elevated"]}, + ], + }, + { + "id": "pull_up", "label": "Pull-up", "mode": "reps", "priority": 100, + "reference": "voller Pull-up = 1,00 Referenz-Rep", + "variants": [ + {"label": "Scapular Pull-up", "factor": 0.20, "patterns": [r"scapular"]}, + {"label": "Stark assistiert / Fußhilfe", "factor": 0.35, "patterns": [r"fußhilfe", r"stark assist"]}, + {"label": "Assistierter Pull-up", "factor": 0.55, "patterns": [r"assistiert", r"band"]}, + {"label": "Negativer Pull-up", "factor": 0.70, "patterns": [r"negativ"]}, + {"label": "Leicht assistiert", "factor": 0.80, "patterns": [r"leicht assist"]}, + {"label": "Voller Pull-up", "factor": 1.00, "patterns": [r"volle? pull", r"klimmzug", r"standard"]}, + {"label": "Pull-up mit Zusatzlast", "factor": 1.15, "patterns": [r"zusatzlast", r"weighted"]}, + ], + }, + { + "id": "squat", "label": "Squat", "mode": "reps", "priority": 100, + "reference": "voller Bodyweight Squat = 1,00 Referenz-Rep", + "variants": [ + {"label": "Chair / Box Squat", "factor": 0.35, "patterns": [r"chair", r"box", r"stuhl"]}, + {"label": "Assistierter Squat", "factor": 0.55, "patterns": [r"assistiert", r"festhalten", r"türrahmen"]}, + {"label": "Teilwiederholung", "factor": 0.45, "patterns": [r"teil", r"partial"]}, + {"label": "Negativer Squat", "factor": 0.75, "patterns": [r"negativ", r"3s runter"]}, + {"label": "Bodyweight Squat", "factor": 1.00, "patterns": [r"körpergewicht", r"bodyweight", r"volle squat", r"kniebeuge"]}, + {"label": "Goblet Squat", "factor": 1.00, "patterns": [r"goblet"]}, + {"label": "Einbeinige Variante", "factor": 1.45, "patterns": [r"pistol", r"einbeinig", r"shrimp"]}, + ], + }, + { + "id": "plank", "label": "Plank", "mode": "seconds", "priority": 100, + "reference": "volle Plank = 1,00 Referenzsekunde", + "variants": [ + {"label": "Erhöhte Plank", "factor": 0.55, "patterns": [r"erhöht", r"incline"]}, + {"label": "Plank auf Knien", "factor": 0.65, "patterns": [r"knien", r"knie"]}, + {"label": "Volle Plank", "factor": 1.00, "patterns": [r"voll", r"standard", r"high plank"]}, + {"label": "Plank mit Schulter-Taps", "factor": 1.15, "patterns": [r"schulter.?tap", r"shoulder.?tap"]}, + {"label": "Long-Lever / erschwerter Hebel", "factor": 1.25, "patterns": [r"long.?lever", r"hebel", r"schwieriger"]}, + ], + }, + { + "id": "side_plank", "label": "Side Plank", "mode": "seconds", "priority": 82, + "reference": "volle Side Plank = 1,00 Referenzsekunde", + "variants": [ + {"label": "Side Plank auf Knien", "factor": 0.65, "patterns": [r"knien", r"knie"]}, + {"label": "Volle Side Plank", "factor": 1.00, "patterns": [r"voll", r"standard"]}, + {"label": "Star / langer Hebel", "factor": 1.25, "patterns": [r"star", r"langer hebel", r"bein oben"]}, + ], + }, + { + "id": "hollow_hold", "label": "Hollow Hold", "mode": "seconds", "priority": 82, + "reference": "voller Hollow Hold = 1,00 Referenzsekunde", + "variants": [ + {"label": "Tuck", "factor": 0.65, "patterns": [r"tuck", r"knie an"]}, + {"label": "Halb gestreckt", "factor": 0.82, "patterns": [r"halb"]}, + {"label": "Voll", "factor": 1.00, "patterns": [r"voll", r"gestreckt"]}, + ], + }, + { + "id": "active_hang", "label": "Active Hang", "mode": "seconds", "priority": 92, + "reference": "freier Active Hang = 1,00 Referenzsekunde", + "variants": [ + {"label": "Mit Fußhilfe", "factor": 0.60, "patterns": [r"fußhilfe", r"assist"]}, + {"label": "Freier Active Hang", "factor": 1.00, "patterns": [r"active hang", r"frei"]}, + {"label": "Einarmig", "factor": 1.60, "patterns": [r"einarm"]}, + ], + }, + { + "id": "deep_squat_hold", "label": "Deep Squat Hold", "mode": "seconds", "priority": 90, + "reference": "ungewichteter Deep Squat Hold = 1,00 Referenzsekunde", + "variants": [ + {"label": "Assistiert", "factor": 0.75, "patterns": [r"assist", r"festhalten"]}, + {"label": "Ungewichtet", "factor": 1.00, "patterns": [r"ungewichtet", r"bodyweight", r"deep squat"]}, + {"label": "Gewichtet", "factor": 1.00, "patterns": [r"gewichtet", r"goblet"]}, + ], + }, + { + "id": "wall_sit", "label": "Wall Sit", "mode": "seconds", "priority": 75, + "reference": "sauberer Wall Sit = 1,00 Referenzsekunde", + "variants": [{"label": "Wall Sit", "factor": 1.00, "patterns": [r"wall sit", r"wandsitz"]}], + }, +] + +_EQUIVALENCE_SOURCES = [ + { + "title": "CaliHoss Calisthenics Course", + "url": "https://calihoss.me/learn/calisthenics/#playlist-2-progression", + "note": "Progression über Technik, ROM, Unterstützung, Hebel, Widerstand und mechanische Schwierigkeit; 4–8 Reps bzw. 10–30-s-Holds.", + }, + { + "title": "Suprak et al. (2011): The effect of position on the percentage of body mass supported during traditional and modified push-up variants", + "url": "https://pubmed.ncbi.nlm.nih.gov/21273908/", + "note": "Biomechanischer Anker für die Reihenfolge traditioneller und Knie-Push-ups; die Analysefaktoren sind bewusst nichtlinear.", + }, + { + "title": "Ebben et al. (2011): Kinetic analysis of several variations of push-ups", + "url": "https://pubmed.ncbi.nlm.nih.gov/21873902/", + "note": "Biomechanischer Anker für die Reihenfolge verschiedener Handhöhen; die Analysefaktoren sind bewusst nichtlinear.", + }, +] + + +def _discover_boehmitools_root() -> Path: + explicit = os.environ.get("BOEHMITOOLS_ROOT") + if explicit: + return Path(explicit).expanduser().resolve() + for parent in [PLUGIN_DIR, *PLUGIN_DIR.parents]: + if (parent / "data" / "trainingsplan" / "plans").is_dir(): + return parent + # Standardfall bei .../boehmitools/plugins/trainingstracker + if PLUGIN_DIR.parent.name == "plugins": + return PLUGIN_DIR.parent.parent + return PLUGIN_DIR + + +ROOT_DIR = _discover_boehmitools_root() +DATA_DIR = Path(os.environ.get( + "TRAININGSTRACKER_DATA_DIR", ROOT_DIR / "data" / "trainingstracker" +)).expanduser().resolve() +PLANS_DIR = Path(os.environ.get( + "TRAININGSTRACKER_PLANS_DIR", ROOT_DIR / "data" / "trainingsplan" / "plans" +)).expanduser().resolve() +SESSIONS_DIR = DATA_DIR / "sessions" +ANALYSES_DIR = DATA_DIR / "analyses" +PROPOSALS_DIR = DATA_DIR / "proposals" +SETTINGS_FILE = DATA_DIR / "settings.json" +SESSIONS_DIR.mkdir(parents=True, exist_ok=True) +ANALYSES_DIR.mkdir(parents=True, exist_ok=True) +PROPOSALS_DIR.mkdir(parents=True, exist_ok=True) + +app = Flask(__name__, static_folder=None) + + +# --------------------------------------------------------------------------- +# Dateisystem und JSON +# --------------------------------------------------------------------------- +def _utc_now() -> str: + return datetime.now(timezone.utc).isoformat(timespec="seconds") + + +def _atomic_json_write(path: Path, payload: Any) -> None: + """Schreibt JSON atomar, damit auch ein abgebrochener Request nichts zerlegt.""" + path.parent.mkdir(parents=True, exist_ok=True) + with _WRITE_LOCK: + fd, temp_name = tempfile.mkstemp(prefix=f".{path.name}.", suffix=".tmp", dir=path.parent) + try: + with os.fdopen(fd, "w", encoding="utf-8") as handle: + json.dump(payload, handle, ensure_ascii=False, indent=2) + handle.write("\n") + handle.flush() + os.fsync(handle.fileno()) + os.replace(temp_name, path) + finally: + if os.path.exists(temp_name): + os.unlink(temp_name) + + +def _read_json(path: Path, default: Any = None) -> Any: + try: + with path.open("r", encoding="utf-8") as handle: + return json.load(handle) + except (FileNotFoundError, json.JSONDecodeError, OSError): + return deepcopy(default) + + +def _source_hash(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 128), b""): + digest.update(chunk) + return digest.hexdigest() + + +def _known_plan_files() -> dict[str, Path]: + """Ermittelt valide Trainingspläne. Rezeptsammlungen werden ausgeschlossen.""" + result: dict[str, Path] = {} + if not PLANS_DIR.is_dir(): + return result + for path in sorted(PLANS_DIR.glob("*.json"), key=lambda p: p.name.casefold()): + try: + raw = _read_json(path) + _, cfg = _unwrap_plan(raw, path.stem) + if _is_training_config(cfg): + result[path.name] = path + except Exception: + continue + return result + + +def _validated_plan_path(plan_id: str) -> Path | None: + # Exakte Auswahl aus dem Scan verhindert Traversal und fremde Dateien. + return _known_plan_files().get(plan_id) + + +def _tracker_path(plan_filename: str) -> Path: + # Original-Dateiname im separaten sessions-Ordner, wie vorgegeben. + return SESSIONS_DIR / Path(plan_filename).name + + +def _analysis_plan_dir(plan_filename: str) -> Path: + # Exakter Plan-Dateiname als eigener Archivordner, ohne Annahmen zum Linux-Root. + return ANALYSES_DIR / Path(plan_filename).name + + +def _analysis_index_path(plan_filename: str) -> Path: + return _analysis_plan_dir(plan_filename) / "index.json" + + +def _analysis_state_path(plan_filename: str) -> Path: + return _analysis_plan_dir(plan_filename) / "state.json" + + +def _analysis_lock_path(plan_filename: str) -> Path: + return _analysis_plan_dir(plan_filename) / ".analysis.lock" + + +@contextmanager +def _analysis_file_lock(plan_filename: str): + """Prozessübergreifende Sperre für Job-Claim und Statuswechsel auf Linux.""" + import fcntl + path = _analysis_lock_path(plan_filename) + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("a+", encoding="utf-8") as handle: + fcntl.flock(handle.fileno(), fcntl.LOCK_EX) + try: + yield + finally: + fcntl.flock(handle.fileno(), fcntl.LOCK_UN) + + +def _empty_analysis_index(plan_filename: str) -> dict[str, Any]: + return { + "version": 2, + "source_file": Path(plan_filename).name, + "latest": {"weeks": {}, "overall": None}, + "updated_at": _utc_now(), + } + + +def _read_analysis_index(plan_filename: str) -> dict[str, Any]: + raw = _read_json(_analysis_index_path(plan_filename), None) + index = raw if isinstance(raw, dict) else _empty_analysis_index(plan_filename) + latest = index.get("latest") if isinstance(index.get("latest"), dict) else {} + index = { + "version": 2, + "source_file": Path(plan_filename).name, + "latest": { + "weeks": latest.get("weeks") if isinstance(latest.get("weeks"), dict) else {}, + "overall": latest.get("overall") if isinstance(latest.get("overall"), str) else None, + }, + "updated_at": str(index.get("updated_at") or _utc_now()), + } + return index + + +def _write_analysis_index(plan_filename: str, index: dict[str, Any]) -> None: + index = _sanitize(index) + if not isinstance(index, dict): + raise ValueError("Ungültiger Analyseindex") + index["version"] = 2 + index["source_file"] = Path(plan_filename).name + index["updated_at"] = _utc_now() + index.pop("history", None) + _atomic_json_write(_analysis_index_path(plan_filename), index) + + +def _public_analysis_record(record: Any) -> dict[str, Any] | None: + if not isinstance(record, dict) or record.get("status") not in (None, "success"): + return None + response = record.get("response") if isinstance(record.get("response"), dict) else record.get("result") + if not isinstance(response, dict): + return None + keys = ( + "id", "type", "week", "created_at", "model", "source_hash", + "sessions_considered", "weeks_considered", "archive_file", "analysis_kind", + ) + public = {key: deepcopy(record.get(key)) for key in keys if key in record} + public["result"] = deepcopy(response) + public["visuals"] = deepcopy(record.get("visuals") if isinstance(record.get("visuals"), dict) else {}) + return public + + +def _read_analysis_record(plan_filename: str, relative_name: Any) -> dict[str, Any] | None: + if not isinstance(relative_name, str) or not relative_name: + return None + base = _analysis_plan_dir(plan_filename).resolve() + candidate = (base / relative_name).resolve() + try: + candidate.relative_to(base) + except ValueError: + return None + raw = _read_json(candidate, None) + return raw if isinstance(raw, dict) else None + + +def _migrate_single_analysis_store(plan_filename: str) -> dict[str, Any]: + """Reduziert ältere Analysearchive auf genau eine aktuelle Datei je Scope. + + Alte Versionen führten Zeitstempeldateien und optional eine Historie im Index. + Beim ersten Lesen werden nur die im Index als aktuell markierten Datensätze + übernommen, auf deterministische Dateinamen umgeschrieben und alle älteren + Dateien desselben Scopes entfernt. + """ + with _WRITE_LOCK: + index = _read_analysis_index(plan_filename) + directory = _analysis_plan_dir(plan_filename) + changed = False + weeks = index["latest"]["weeks"] + for week_key, relative_name in list(weeks.items()): + record = _read_analysis_record(plan_filename, relative_name) + if not isinstance(record, dict): + weeks.pop(week_key, None) + changed = True + continue + try: + week = int(record.get("week") or week_key) + except (TypeError, ValueError): + weeks.pop(week_key, None) + changed = True + continue + record["type"] = "week" + record["week"] = week + target = current_filename(record) + if relative_name != target or not (directory / target).exists(): + record["archive_file"] = target + _atomic_json_write(directory / target, record) + changed = True + cleanup_legacy_files(directory, record, target) + if week_key != str(week): + weeks.pop(week_key, None) + if weeks.get(str(week)) != target: + weeks[str(week)] = target + changed = True + + overall_name = index["latest"].get("overall") + if overall_name: + record = _read_analysis_record(plan_filename, overall_name) + if isinstance(record, dict): + record["type"] = "overall" + target = current_filename(record) + if overall_name != target or not (directory / target).exists(): + record["archive_file"] = target + _atomic_json_write(directory / target, record) + changed = True + cleanup_legacy_files(directory, record, target) + if index["latest"].get("overall") != target: + index["latest"]["overall"] = target + changed = True + else: + index["latest"]["overall"] = None + changed = True + if changed: + _write_analysis_index(plan_filename, index) + return index + + +def _read_analysis_cache(plan: dict[str, Any]) -> dict[str, Any]: + index = _migrate_single_analysis_store(plan["source_file"]) + latest = index["latest"] + weeks: dict[str, Any] = {} + for week, relative_name in latest["weeks"].items(): + public = _public_analysis_record(_read_analysis_record(plan["source_file"], relative_name)) + if public is not None: + weeks[str(week)] = public + overall = _public_analysis_record(_read_analysis_record(plan["source_file"], latest.get("overall"))) + return {"weeks": weeks, "overall": overall} + + +def _write_proposals(plan: dict[str, Any], archive: dict[str, Any]) -> None: + response = archive.get("response") if isinstance(archive.get("response"), dict) else {} + adjustments = response.get("plan_adjustments") if isinstance(response.get("plan_adjustments"), list) else [] + scope_key = f"{archive.get('type', 'analysis')}:{archive.get('week') or 'overall'}" + proposals = [] + for index, item in enumerate(adjustments[:3]): + if not isinstance(item, dict): + continue + proposal = deepcopy(item) + proposal.setdefault("id", f"{scope_key}-{index+1}") + proposal.setdefault("status", "open") + proposal["scope_key"] = scope_key + proposal["analysis_created_at"] = archive.get("created_at") + proposal["analysis_type"] = archive.get("type") + proposal["week"] = archive.get("week") + proposals.append(proposal) + path = PROPOSALS_DIR / Path(plan["source_file"]).name + previous = _read_json(path, {}) + previous_items = previous.get("proposals") if isinstance(previous, dict) and isinstance(previous.get("proposals"), list) else [] + retained = [ + deepcopy(item) for item in previous_items + if isinstance(item, dict) and str(item.get("scope_key") or "") != scope_key + ] + payload = { + "version": 2, "source_file": plan["source_file"], "plan_id": plan.get("plan_id"), + "published_revision": plan.get("published_revision"), "updated_at": _utc_now(), + "proposals": retained + proposals, + } + _atomic_json_write(path, payload) + + +def _archive_analysis_record(plan: dict[str, Any], record: dict[str, Any]) -> dict[str, Any]: + """Speichert genau eine aktuelle Analyse je Woche sowie eine Gesamtanalyse. + + Neue Auswertungen überschreiben die bisherige Datei. Es wird bewusst keine + Analysehistorie oder Versionierung geführt. + """ + archive = _sanitize(record) + if not isinstance(archive, dict): + raise ValueError("Ungültiger Analysedatensatz") + filename = current_filename(archive) + archive["version"] = 2 + archive["status"] = "success" + archive["source_file"] = plan["source_file"] + archive["archive_file"] = filename + directory = _analysis_plan_dir(plan["source_file"]) + path = directory / filename + _atomic_json_write(path, archive) + cleanup_legacy_files(directory, archive, filename) + + with _WRITE_LOCK: + index = _read_analysis_index(plan["source_file"]) + if archive.get("type") == "week": + index["latest"]["weeks"][str(int(archive.get("week") or 0))] = filename + else: + index["latest"]["overall"] = filename + _write_analysis_index(plan["source_file"], index) + _write_proposals(plan, archive) + public = _public_analysis_record(archive) + if public is None: + raise ValueError("Analyse konnte nicht gelesen werden") + return public + + +def _read_analysis_state(plan: dict[str, Any]) -> dict[str, Any]: + return _normalize_analysis_state(_read_json(_analysis_state_path(plan["source_file"]), {})) + + +def _write_analysis_state(plan: dict[str, Any], state: dict[str, Any]) -> dict[str, Any]: + clean = _normalize_analysis_state(_sanitize(state)) + clean["source_file"] = plan["source_file"] + clean["updated_at"] = _utc_now() + _atomic_json_write(_analysis_state_path(plan["source_file"]), clean) + return clean + + +def _read_settings() -> dict[str, Any]: + settings = _read_json(SETTINGS_FILE, {}) + return settings if isinstance(settings, dict) else {} + + +def _save_selected(plan_id: str) -> None: + settings = _read_settings() + settings["selected_plan"] = plan_id + settings["updated_at"] = _utc_now() + _atomic_json_write(SETTINGS_FILE, settings) + + +# --------------------------------------------------------------------------- +# Plan-Normalisierung +# --------------------------------------------------------------------------- +def _unwrap_plan(raw: Any, fallback_name: str) -> tuple[str, dict[str, Any]]: + if not isinstance(raw, dict): + raise ValueError("Plan ist kein JSON-Objekt") + if isinstance(raw.get("config"), dict): + return _plain_text(raw.get("name") or fallback_name), raw["config"] + meta = raw.get("meta") if isinstance(raw.get("meta"), dict) else {} + return _plain_text(meta.get("title") or fallback_name), raw + + +def _is_training_config(cfg: dict[str, Any]) -> bool: + if not isinstance(cfg, dict) or cfg.get("type") == "recipe": + return False + days = cfg.get("days") + return isinstance(days, list) and any(isinstance(day, dict) for day in days) + + +def _plain_text(value: Any) -> str: + """Wandelt HTML-formatierte Planfelder in sichtbaren Klartext um. + + Der PDF-Editor speichert Sonderzeichen teils als HTML-Entities, zum Beispiel + ``&``. In reinen UI-Labels dürfen diese nicht erneut escaped werden. + """ + text = str(value or "") + text = re.sub(r"", "\n", text, flags=re.I) + text = re.sub(r"<[^>]+>", "", text) + return html.unescape(text).strip() + + +def _sequence_items(value: Any) -> list[str]: + text = str(value or "") + text = re.sub(r"", " · ", text, flags=re.I) + text = _plain_text(text) + if not text: + return [] + parts = re.split(r"\s*(?:·|•|\n)\s*", text) + return [part.strip() for part in parts if part.strip()] + + +def _slug(value: Any) -> str: + text = _plain_text(value).lower() + text = text.replace("ä", "ae").replace("ö", "oe").replace("ü", "ue").replace("ß", "ss") + text = re.sub(r"[^a-z0-9]+", "-", text).strip("-") + return text or "item" + + +def _normalize_phases(cfg: dict[str, Any]) -> list[dict[str, Any]]: + phases = cfg.get("phases") if isinstance(cfg.get("phases"), dict) else {} + items = phases.get("items") + normalized: list[dict[str, Any]] = [] + if isinstance(items, list): + for item in items: + if not isinstance(item, dict): + continue + normalized.append({ + "id": str(item.get("id") or f"phase-{len(normalized)+1}"), + "name": _plain_text(item.get("name") or f"Phase {len(normalized) + 1}"), + "params": str(item.get("params") or ""), + "weeks": max(1, _as_int(item.get("weeks"), 1)), + }) + else: + names = phases.get("names") if isinstance(phases.get("names"), list) else [] + params = phases.get("params") if isinstance(phases.get("params"), list) else [] + for index, name in enumerate(names): + normalized.append({ + "id": f"phase-{index+1}", + "name": _plain_text(name or f"Phase {index + 1}"), + "params": str(params[index] if index < len(params) else ""), + "weeks": 2, + }) + if not normalized: + normalized.append({"id": "phase-1", "name": "Gesamter Plan", "params": "", "weeks": max(1, _plan_weeks(cfg))}) + + cursor = 1 + for index, item in enumerate(normalized): + item["index"] = index + item["start_week"] = cursor + item["end_week"] = cursor + item["weeks"] - 1 + cursor = item["end_week"] + 1 + return normalized + + +def _sanitize_plan_result_schema(value: Any, *, partial: bool = True) -> dict[str, Any] | None: + """Normalisiert ein im Trainingsplan gespeichertes Ergebnisschema. + + Stufen-Schemata dürfen partiell sein und nur einzelne Eigenschaften des + Übungsstandards überschreiben. Unbekannte Felder werden ignoriert. + """ + if not isinstance(value, dict): + return None + result: dict[str, Any] = {} + mode = str(value.get("mode") or "") + if mode in {"auto", "reps", "seconds", "minutes", "none"}: + result["mode"] = mode + weight_mode = str(value.get("weight_mode") or "") + if weight_mode in {"none", "optional", "required"}: + result["weight_mode"] = weight_mode + laterality = str(value.get("laterality") or "") + if laterality in {"bilateral", "unilateral"}: + result["laterality"] = laterality + sides_mode = str(value.get("sides_mode") or "") + if sides_mode in {"same", "separate"}: + result["sides_mode"] = sides_mode + try: + sets = int(value.get("sets") or value.get("default_sets") or 0) + except (TypeError, ValueError): + sets = 0 + if 1 <= sets <= 20: + result["sets"] = sets + result["locked_sets"] = bool(value.get("locked_sets", True)) + elif "locked_sets" in value: + result["locked_sets"] = bool(value.get("locked_sets")) + if not partial: + result.setdefault("mode", "auto") + result.setdefault("weight_mode", "none") + result.setdefault("laterality", "bilateral") + result.setdefault("sides_mode", "same") + return result or None + + +def _normalize_stages(cfg: dict[str, Any]) -> dict[str, dict[str, Any]]: + stages = cfg.get("stages") if isinstance(cfg.get("stages"), dict) else {} + result: dict[str, dict[str, Any]] = {} + for key, value in stages.items(): + if isinstance(value, dict): + raw_steps = value.get("steps") if isinstance(value.get("steps"), list) else [] + raw_schemas = value.get("result_schemas") if isinstance(value.get("result_schemas"), list) else [] + steps: list[dict[str, Any]] = [] + for index, raw_step in enumerate(raw_steps): + if isinstance(raw_step, dict): + name = html.unescape(str(raw_step.get("name") or raw_step.get("label") or "")) + schema = _sanitize_plan_result_schema(raw_step.get("result_schema"), partial=True) + step_id = str(raw_step.get("id") or f"{key}-step-{index+1}") + factor = _result_number(raw_step.get("factor")) or 1.0 + cluster = str(raw_step.get("movement_cluster") or "") + phase_id = str(raw_step.get("phase_id") or "") + else: + name = html.unescape(str(raw_step or "")) + schema = _sanitize_plan_result_schema(raw_schemas[index], partial=True) if index < len(raw_schemas) else None + step_id = f"{key}-step-{index+1}" + factor = 1.0 + cluster = "" + phase_id = "" + steps.append({ + "id": step_id, "name": name, "result_schema": schema, + "factor": factor, "movement_cluster": cluster, "phase_id": phase_id, + }) + result[str(key)] = { + "id": str(value.get("id") or f"progression-{key}"), + "key": str(value.get("key") or key), + "name": _plain_text(value.get("name") or key), + "steps": steps, + } + elif isinstance(value, list): + result[str(key)] = { + "id": f"progression-{key}", "key": str(key), "name": _plain_text(key), + "steps": [{"id": f"{key}-step-{i+1}", "name": html.unescape(str(step or "")), "result_schema": None, "factor": 1.0, "movement_cluster": "", "phase_id": ""} for i, step in enumerate(value)], + } + return result + + +def _library_lookup(cfg: dict[str, Any]) -> dict[str, dict[str, str]]: + lookup: dict[str, dict[str, str]] = {} + groups = cfg.get("library") if isinstance(cfg.get("library"), list) else [] + for group in groups: + if not isinstance(group, dict): + continue + for item in group.get("items", []) if isinstance(group.get("items"), list) else []: + if not isinstance(item, dict): + continue + name = _plain_text(item.get("name") or "") + if not name: + continue + entry = { + "name": name, + "desc": str(item.get("desc") or ""), + "url": str(item.get("url") or ""), + "group": _plain_text(group.get("group") or ""), + } + lookup[_slug(name)] = entry + return lookup + + +def _closest_library_entry(name: str, lookup: dict[str, dict[str, str]]) -> dict[str, str] | None: + exact = lookup.get(_slug(name)) + if exact: + return exact + target = _slug(name) + for key, entry in lookup.items(): + if target in key or key in target: + return entry + return None + + +_RESULT_STATIC_RE = re.compile( + r"(?:\bhold\b|\bplank\b|\bhang\b|wall\s*sit|wandsitz|isometr|stützposition)", + flags=re.I, +) +_RESULT_DYNAMIC_RE = re.compile( + r"(?:push.?up|pull.?up|klimmzug|scapular|negative?|liegestütz|wiederhol|\breps?\b|rudern|\brow\b|" + r"squat|kniebeuge|dip|curl|deadlift|rdl|hinge|swing|raise|heben|climber|crunch|twist|lunge|ausfallschritt)", + flags=re.I, +) +_RESULT_NO_MEASURE_RE = re.compile( + r"(?:regeneration\s+prüfen|schlaf\s+priorisieren|schmerzcheck|sonst\s+vollständig\s+pausieren|" + r"(?:push|pull|beine|core)\s+protokollieren|schwierigkeit\s+erhöhen|reps?\s+reserve\s+prüfen|" + r"mobilität|mobility)", + flags=re.I, +) +_RESULT_MINUTES_RE = re.compile(r"(?:spaziergang|walk|walking)", flags=re.I) + +_RESULT_REQUIRED_WEIGHT_RE = re.compile( + r"(?:\bkb\b|kettlebell|rucksack|goblet|floor\s*press|deadlift|\brdl\b|hip\s*hinge|" + r"\bswing\b|\bhalo\b|\bcurl|rudern.*\bkb\b|row.*\bkb\b|around.the.world)", + flags=re.I, +) +_RESULT_OPTIONAL_WEIGHT_RE = re.compile( + r"(?:gewicht|gewichtet|zusatzlast|\blast\b|\bkg\b|kettlebell|\bkb\b|rucksack|" + r"schwerer|wadenheben|calf)", + flags=re.I, +) +_RESULT_UNWEIGHTED_RE = re.compile( + r"(?:ohne\s+gewicht|ungewichtet|körpergewicht|koerpergewicht|\bbw\b)", + flags=re.I, +) +_RESULT_UNILATERAL_RE = re.compile( + r"(?:einarm|einbein|je\s+seite|pro\s+seite|links|rechts|seitenwechsel|\bwechsel\b|" + r"ausfallschritt|\blunge\b|side\s*plank|seitstütz|seitheben|lateral\s*raise|" + r"russian\s*twist|dead\s*bug|\bhalo\b|around.the.world|bottoms.?up)", + flags=re.I, +) + + +def _result_default_sets( + rotation_label: Any, cue: Any, training_format: dict[str, Any], exercise_count: int, +) -> tuple[int, bool]: + label = _plain_text(rotation_label or "") + text = _plain_text(f"{label} {cue or ''}") + if re.search(r"\barbeitssatz\s*\d+", label, flags=re.I): + return 1, True + + set_count = None + match = re.search(r"(?= 1 + and exercise_count >= 1 and rounds % exercise_count == 0 + ): + per_block = max(1, rounds // exercise_count) + return max(1, min(20, per_block * (set_count or 1))), True + + if set_count and 1 <= set_count <= 20: + return set_count, True + for pattern in (r"(? dict[str, Any]: + """Leitet die feste Ergebniseingabe aus Übung, Stufen und Planformat ab.""" + name = _plain_text(exercise.get("name") or "") + cue = _plain_text(exercise.get("cue") or "") + progression_id = str(exercise.get("progression_id") or "") + stage = stages.get(progression_id) if isinstance(stages, dict) else None + steps = stage.get("steps") if isinstance(stage, dict) and isinstance(stage.get("steps"), list) else [] + stage_text = " ".join(_plain_text(step) for step in steps) + full_text = f"{name} {cue} {stage_text}" + + static_name = bool(_RESULT_STATIC_RE.search(name)) + static_stage = bool(_RESULT_STATIC_RE.search(stage_text)) + dynamic_stage = bool(_RESULT_DYNAMIC_RE.search(stage_text)) + explicit_time = bool(re.search(r"(?= 1 + and rounds % exercise_count == 0 and rounds // exercise_count == 2 + and default_sets % 2 == 0 + ): + default_sets = max(1, default_sets // 2) + + return { + "version": RESULT_DATA_VERSION, + "mode": mode, + "weight_mode": weight_mode, + "laterality": laterality, + "sides_mode": sides_mode if laterality == "unilateral" else "same", + "default_sets": default_sets, + "locked_sets": locked_sets, + "work_seconds": training_format.get("work_seconds") if training_format.get("fixed_interval") else None, + "fixed_interval": bool(training_format.get("fixed_interval")), + } + + +def _normalize_rotations(day: dict[str, Any], day_num: int, library: dict[str, dict[str, str]]) -> list[dict[str, Any]]: + rotations = day.get("rotations") + if not isinstance(rotations, list): + rotations = [] + if isinstance(day.get("rotA"), list): rotations.append({"label": "Rotation A", "exercises": day["rotA"]}) + if isinstance(day.get("rotB"), list): rotations.append({"label": "Rotation B", "exercises": day["rotB"]}) + result: list[dict[str, Any]] = [] + day_id = str(day.get("id") or f"day-{day_num}") + for rotation_index, rotation in enumerate(rotations): + if not isinstance(rotation, dict): continue + rotation_id = str(rotation.get("id") or f"{day_id}-rotation-{rotation_index+1}") + exercises = rotation.get("exercises") if isinstance(rotation.get("exercises"), list) else [] + normalized_exercises = [] + for exercise_index, exercise in enumerate(exercises): + if not isinstance(exercise, dict): continue + name = _plain_text(exercise.get("name") or f"Übung {exercise_index + 1}") + key = str(exercise.get("progression_id") or exercise.get("key") or "").strip() + progression_id = key or f"name:{_slug(name)}" + normalized_exercises.append({ + "id": str(exercise.get("id") or f"{rotation_id}-exercise-{exercise_index+1}"), + "legacy_id": str(exercise.get("legacy_id") or f"d{day_num}-r{rotation_index}-e{exercise_index}"), + "exercise_id": str(exercise.get("exercise_id") or progression_id), + "name": name, "key": key, "progression_id": progression_id, + "cue": str(exercise.get("cue") or ""), + "movement_cluster": str(exercise.get("movement_cluster") or ""), + "result_schema": _sanitize_plan_result_schema(exercise.get("result_schema"), partial=False), + "library": _closest_library_entry(name, library), + }) + result.append({"id": rotation_id, "label": str(rotation.get("label") or f"Block {rotation_index + 1}"), "exercises": normalized_exercises}) + return result + + +def _as_int(value: Any, default: int) -> int: + try: + return int(value) + except (TypeError, ValueError): + return default + + +def _plan_weeks(cfg: dict[str, Any]) -> int: + meta = cfg.get("meta") if isinstance(cfg.get("meta"), dict) else {} + return max(1, _as_int(meta.get("weeks"), 8)) + + +def _first_int(patterns: list[str], text: str) -> int | None: + for pattern in patterns: + match = re.search(pattern, text, flags=re.I) + if match: + try: + return int(match.group(1)) + except (TypeError, ValueError): + continue + return None + + +def _detect_training_format(cfg: dict[str, Any], days: list[dict[str, Any]]) -> dict[str, Any]: + """Leitet bindende Session-Regeln aus dem Plan ab. + + Die KI bekommt damit nicht nur Übungen, sondern auch das tatsächliche + Ausführungsformat. Bei Tabata bleiben Arbeits- und Pausenintervalle fest. + """ + explicit_format = cfg.get("training_format") if isinstance(cfg.get("training_format"), dict) else {} + explicit_mode = str(explicit_format.get("mode") or "auto") + if explicit_mode in {"sets_reps", "tabata", "fixed_interval"}: + def _positive_int(field: str) -> int | None: + try: + number = int(explicit_format.get(field)) + except (TypeError, ValueError): + return None + return number if number > 0 else None + + fixed = explicit_mode in {"tabata", "fixed_interval"} or bool(explicit_format.get("fixed_interval")) + work = _positive_int("work_seconds") + rest = _positive_int("rest_seconds") + rounds = _positive_int("rounds") + constraints: list[str] = [] + if fixed: + interval = f"{work} Sekunden" if work else "die vorgegebene Dauer" + constraints.extend([ + f"Arbeitsintervalle bleiben fest bei {interval}; keine längeren Holds oder verlängerten Arbeitsintervalle empfehlen.", + "Progression nur innerhalb des Planformats: planmäßige schwierigere Variante, mehr Gewicht, saubererer ROM/Tempo oder bessere Technik; Pausen und Rundenzahl nicht eigenmächtig ändern.", + ]) + else: + constraints.append( + "Vorgegebene Wiederholungs-, Haltezeit-, Satz- und Pausenbereiche bleiben bindend; nach Erreichen der Obergrenze die planmäßige Schwierigkeit anpassen statt unbegrenzt Volumen zu addieren." + ) + front = cfg.get("front") if isinstance(cfg.get("front"), dict) else {} + return { + "mode": explicit_mode, + "is_tabata": explicit_mode == "tabata", + "fixed_interval": fixed, + "work_seconds": work, + "rest_seconds": rest, + "rounds": rounds, + "rounds_scope": "block", + "source": "plan", + "session_method": _plain_text(front.get("session_how_body") or ""), + "timer_note": _plain_text(front.get("timer_note") or ""), + "constraints": constraints, + } + + front = cfg.get("front") if isinstance(cfg.get("front"), dict) else {} + phases = cfg.get("phases") if isinstance(cfg.get("phases"), dict) else {} + phase_items = phases.get("items") if isinstance(phases.get("items"), list) else [] + fragments: list[str] = [ + str(front.get("session_how_body") or ""), + str(front.get("timer_note") or ""), + str(front.get("goals_note") or ""), + *[str(value or "") for value in front.get("reminders", []) if isinstance(front.get("reminders"), list)], + *[str(item.get("params") or "") for item in phase_items if isinstance(item, dict)], + ] + for day in days: + fragments.append(str(day.get("note") or "")) + for rotation in day.get("rotations", []) if isinstance(day.get("rotations"), list) else []: + fragments.append(str(rotation.get("label") or "")) + for exercise in rotation.get("exercises", []) if isinstance(rotation.get("exercises"), list) else []: + fragments.append(str(exercise.get("cue") or "")) + raw_text = " ".join(fragments) + plain = _plain_text(raw_text) + low = plain.casefold() + negated_tabata = bool(re.search(r"\b(?:kein|keine|ohne)\s+tabata\b|\bkein\s+20\s*/\s*10", low)) + is_tabata = not negated_tabata and ("tabata" in low or bool(re.search(r"\b20\s*/\s*10\b", low))) + work_seconds = _first_int([ + r"(\d+)\s*(?:s|sek(?:unden)?)\s*(?:arbeit|work)", + r"(?:arbeit|work)\s*(?:von|:)??\s*(\d+)\s*(?:s|sek(?:unden)?)", + r"(\d+)\s*/\s*\d+", + ], low) + rest_seconds = _first_int([ + r"(\d+)\s*(?:s|sek(?:unden)?)\s*(?:pause|rest)", + r"(?:pause|rest)\s*(?:von|:)??\s*(\d+)\s*(?:s|sek(?:unden)?)", + r"\d+\s*/\s*(\d+)", + ], low) + rounds = _first_int([ + r"(\d+)\s*[×x]\s*\(", + r"(\d+)\s*(?:runden|intervalle)", + r"(?:runden|intervalle)\s*(?:von|:)??\s*(\d+)", + ], low) + fixed_interval = bool(is_tabata or ((work_seconds and rest_seconds) and not negated_tabata)) + mode = "tabata" if is_tabata else ("fixed_interval" if fixed_interval else "sets_reps") + constraints: list[str] = [] + if fixed_interval: + interval = f"{work_seconds} Sekunden" if work_seconds else "die vorgegebene Dauer" + constraints.append( + f"Arbeitsintervalle bleiben fest bei {interval}; keine längeren Holds oder verlängerten Arbeitsintervalle empfehlen." + ) + constraints.append( + "Progression nur innerhalb des Planformats: planmäßige schwierigere Variante, mehr Gewicht, saubererer ROM/Tempo oder bessere Technik; Pausen und Rundenzahl nicht eigenmächtig ändern." + ) + else: + constraints.append( + "Vorgegebene Wiederholungs-, Haltezeit-, Satz- und Pausenbereiche bleiben bindend; nach Erreichen der Obergrenze die planmäßige Schwierigkeit anpassen statt unbegrenzt Volumen zu addieren." + ) + return { + "mode": mode, + "is_tabata": is_tabata, + "fixed_interval": fixed_interval, + "work_seconds": work_seconds, + "rest_seconds": rest_seconds, + "rounds": rounds, + "rounds_scope": "block", + "source": "text_fallback", + "session_method": _plain_text(front.get("session_how_body") or ""), + "timer_note": _plain_text(front.get("timer_note") or ""), + "constraints": constraints, + } + + +def _published_config_hash(config: dict[str, Any]) -> str: + raw = json.dumps(config, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8") + return hashlib.sha256(raw).hexdigest() + + +def _normalize_plan(path: Path) -> dict[str, Any]: + raw = _read_json(path) + plan_name, cfg = _unwrap_plan(raw, path.stem) + wrapper = raw if isinstance(raw, dict) and isinstance(raw.get("config"), dict) else {} + if not _is_training_config(cfg): + raise ValueError("Keine Trainingsplan-Datei") + + raw_schema_version = int(cfg.get("schema_version") or 1) + raw_contract_version = int(cfg.get("contract_version") or 1) + if raw_schema_version > PLAN_SCHEMA_VERSION or raw_contract_version > CONTRACT_VERSION: + raise ValueError( + f"Der Trainingsplan verwendet Schema {raw_schema_version}/Vertrag {raw_contract_version}; " + f"dieser Tracker unterstützt höchstens {PLAN_SCHEMA_VERSION}/{CONTRACT_VERSION}." + ) + compatibility_warnings = [] + if raw_schema_version < PLAN_SCHEMA_VERSION: + compatibility_warnings.append( + f"Legacy-Plan-Schema {raw_schema_version}; Fallback-Erkennung bleibt aktiv. Im Trainingsplan-Editor neu veröffentlichen." + ) + if raw_contract_version < CONTRACT_VERSION: + compatibility_warnings.append( + f"Legacy-Trainingsvertrag {raw_contract_version}; einige stabile IDs oder Ergebnisschemata können fehlen." + ) + + meta = cfg.get("meta") if isinstance(cfg.get("meta"), dict) else {} + front = cfg.get("front") if isinstance(cfg.get("front"), dict) else {} + prepost = cfg.get("prepost") if isinstance(cfg.get("prepost"), dict) else {} + library = _library_lookup(cfg) + phases = _normalize_phases(cfg) + stages = _normalize_stages(cfg) + + days: list[dict[str, Any]] = [] + for fallback_index, day in enumerate(cfg.get("days", []), start=1): + if not isinstance(day, dict): + continue + day_num = max(1, _as_int(day.get("num"), fallback_index)) + pp = prepost.get(str(day_num)) if isinstance(prepost.get(str(day_num)), dict) else {} + days.append({ + "id": str(day.get("id") or f"day-{day_num}"), + "num": day_num, + "focus": _plain_text(day.get("focus") or f"Tag {day_num}"), + "light": bool(day.get("light")), + "color": str(day.get("color") or ""), + "badge": deepcopy(day.get("badge") if isinstance(day.get("badge"), dict) else {}), + "note": str(day.get("note") or ""), + "note_bg": str(day.get("note_bg") or ""), + "rotations": _normalize_rotations(day, day_num, library), + "warmup": { + "text": str(pp.get("warmup") or ""), + "items": _sequence_items(pp.get("warmup")), + }, + "cooldown": { + "text": str(pp.get("cooldown") or ""), + "items": _sequence_items(pp.get("cooldown")), + }, + "stretch": { + "text": str(pp.get("stretch") or ""), + "items": _sequence_items(pp.get("stretch")), + }, + }) + days.sort(key=lambda item: item["num"]) + + weeks = _plan_weeks(cfg) + phase_by_week: list[int] = [] + for week in range(1, weeks + 1): + phase_index = len(phases) - 1 + for phase in phases: + if phase["start_week"] <= week <= phase["end_week"]: + phase_index = phase["index"] + break + phase_by_week.append(phase_index) + + training_format = _detect_training_format(cfg, days) + for day in days: + for rotation in day.get("rotations", []): + for exercise in rotation.get("exercises", []): + exercise["result_schema"] = _infer_result_schema( + exercise, stages, training_format, rotation.get("label") or "", + len(rotation.get("exercises", [])), + ) + + return { + "source_file": path.name, + # Entwurfsänderungen im Planeditor dürfen laufende Sessions und Analysen + # nicht veralten lassen. Relevant ist ausschließlich die veröffentlichte Config. + "source_hash": _published_config_hash(cfg), + "plan_id": str(cfg.get("plan_id") or wrapper.get("plan_id") or path.stem), + "schema_version": raw_schema_version, + "contract_version": raw_contract_version, + "compatibility": { + "supported": True, + "legacy": bool(compatibility_warnings), + "warnings": compatibility_warnings, + "supported_plan_schema": PLAN_SCHEMA_VERSION, + "supported_contract": CONTRACT_VERSION, + }, + "published_revision": int(wrapper.get("published_revision") or 1), + "published_at": str(wrapper.get("published_at") or ""), + "name": plan_name, + "title": _plain_text(meta.get("title") or plan_name), + "subtitle": _plain_text(meta.get("subtitle") or ""), + "weeks": weeks, + "days": days, + "phases": phases, + "phase_by_week": phase_by_week, + "stages": stages, + "exercise_catalog": deepcopy(cfg.get("exercise_catalog") if isinstance(cfg.get("exercise_catalog"), dict) else {}), + "front": { + "goals_head": _plain_text(front.get("goals_head") or ""), + "goals_box": str(front.get("goals_box") or ""), + "goals_note": str(front.get("goals_note") or ""), + "reminder_head": _plain_text(front.get("reminder_head") or ""), + "reminders": [str(value or "") for value in front.get("reminders", [])] + if isinstance(front.get("reminders"), list) else [], + "session_how_head": _plain_text(front.get("session_how_head") or ""), + "session_how_body": str(front.get("session_how_body") or ""), + "timer_note": str(front.get("timer_note") or ""), + }, + "training_format": training_format, + } + + +# --------------------------------------------------------------------------- +# Tracker-Daten +# --------------------------------------------------------------------------- +def _default_tracker(plan: dict[str, Any]) -> dict[str, Any]: + return { + "version": TRACKER_SCHEMA_VERSION, + "revision": 1, + "source_file": plan["source_file"], + "source_hash": plan["source_hash"], + "profile": { + "start_date": "", + "display_name": "", + "plan_notes": "", + }, + "progressions": {}, # Legacy-Fallback aus Version 1; nicht mehr aktiv bearbeitet. + "sessions": {}, + "week_statuses": {}, + "created_at": _utc_now(), + "updated_at": _utc_now(), + } + + +def _normalize_analysis_state(value: Any) -> dict[str, Any]: + state = value if isinstance(value, dict) else {} + status = str(state.get("status") or "idle") + if status not in {"idle", "running", "done", "error"}: status = "idle" + clean = dict(state); clean["status"] = status + if status == "running": + lease = str(clean.get("lease_expires_at") or "") + try: + expired = datetime.now(timezone.utc) > datetime.fromisoformat(lease) + except (TypeError, ValueError): + started = str(clean.get("started_at") or "") + try: expired = (datetime.now(timezone.utc) - datetime.fromisoformat(started)).total_seconds() > _ANALYSIS_TIMEOUT_SECONDS + except (TypeError, ValueError): expired = False + if expired: + clean.update({ + "status": "error", + "error": "Die vorherige Analyse hat ihre Job-Lease verloren oder wurde unterbrochen.", + "message": "Analysejob unterbrochen.", + "finished_at": _utc_now(), + }) + return clean + + +def _load_tracker(plan: dict[str, Any]) -> dict[str, Any]: + tracker = _read_json(_tracker_path(plan["source_file"]), None) + if not isinstance(tracker, dict): + return _default_tracker(plan) + tracker.setdefault("version", TRACKER_SCHEMA_VERSION) + tracker.setdefault("revision", 1) + tracker.setdefault("profile", {}) + tracker.setdefault("progressions", {}) + tracker.setdefault("sessions", {}) + tracker.setdefault("week_statuses", {}) + tracker.setdefault("created_at", _utc_now()) + + # Analysefelder aus älteren Plugin-Versionen werden weder gelesen noch + # wieder gespeichert. Die eigentliche Bereinigung bestehender Dateien kann + # separat erfolgen; jeder neue Autosave schreibt bereits das saubere Modell. + tracker.pop("analysis_cache", None) + tracker.pop("analysis_state", None) + tracker.pop("analyses", None) + tracker["version"] = TRACKER_SCHEMA_VERSION + tracker["source_file"] = plan["source_file"] + tracker["source_changed"] = tracker.get("source_hash") not in (None, plan["source_hash"]) + return tracker + + +def _sanitize(value: Any, depth: int = 0) -> Any: + """Begrenzt Nutzdaten ohne die flexible Tracker-Struktur zu zerstören.""" + if depth > 12: + return None + if value is None or isinstance(value, (bool, int, float)): + return value + if isinstance(value, str): + return value[:20000] + if isinstance(value, list): + return [_sanitize(item, depth + 1) for item in value[:2000]] + if isinstance(value, dict): + result = {} + for index, (key, item) in enumerate(value.items()): + if index >= 5000: + break + clean_key = str(key)[:200] + result[clean_key] = _sanitize(item, depth + 1) + return result + return str(value)[:20000] + + +def _result_number(value: Any) -> float | None: + if value in (None, "") or isinstance(value, bool): + return None + try: + number = float(str(value).replace(",", ".")) + except (TypeError, ValueError): + return None + if number < 0 or number > 100000: + return None + return round(number, 3) + + +def _result_values(value: Any, limit: int = 20) -> list[float | None]: + if not isinstance(value, list): + return [] + return [_result_number(item) for item in value[:limit]] + + +def _sanitize_result_data(value: Any) -> dict[str, Any] | None: + if not isinstance(value, dict): + return None + mode = str(value.get("mode") or "") + if mode not in {"reps", "seconds", "minutes"}: + return None + laterality = str(value.get("laterality") or "bilateral") + if laterality not in {"bilateral", "unilateral"}: + laterality = "bilateral" + sides_mode = str(value.get("sides_mode") or "same") + if sides_mode not in {"same", "separate"}: + sides_mode = "same" + try: + sets = max(1, min(20, int(value.get("sets") or 1))) + except (TypeError, ValueError): + sets = 1 + clean: dict[str, Any] = { + "version": RESULT_DATA_VERSION, + "mode": mode, + "laterality": laterality, + "sides_mode": sides_mode if laterality == "unilateral" else "same", + "sets": sets, + "weight_kg": _result_number(value.get("weight_kg")), + "values": _result_values(value.get("values")), + "left_values": _result_values(value.get("left_values")), + "right_values": _result_values(value.get("right_values")), + } + for key in ("values", "left_values", "right_values"): + values = clean[key][:sets] + values.extend([None] * (sets - len(values))) + clean[key] = values + has_values = any(number is not None for number in clean["values"] + clean["left_values"] + clean["right_values"]) + if not has_values and clean["weight_kg"] is None: + return None + return clean + + +def _format_result_number(value: Any) -> str: + number = _result_number(value) + if number is None: + return "" + return str(int(number)) if float(number).is_integer() else f"{number:.3f}".rstrip("0").rstrip(".") + + +def _format_result_data(value: Any) -> str: + data = _sanitize_result_data(value) + if not data: + return "" + unit = " s" if data["mode"] == "seconds" else (" min" if data["mode"] == "minutes" else " Reps") + weight = _format_result_number(data.get("weight_kg")) + prefix = f"{weight} kg · " if weight else "" + + def joined(values: list[Any]) -> str: + return "/".join(_format_result_number(item) or "–" for item in values) + + if data["laterality"] == "unilateral" and data["sides_mode"] == "separate": + return f"{prefix}L {joined(data['left_values'])} · R {joined(data['right_values'])}{unit}".strip() + side = " je Seite" if data["laterality"] == "unilateral" else "" + return f"{prefix}{joined(data['values'])}{unit}{side}".strip() + + +def _normalize_tracker_payload(plan: dict[str, Any], incoming: Any, existing: dict[str, Any]) -> dict[str, Any]: + if not isinstance(incoming, dict): + raise ValueError("Tracker-Daten müssen ein JSON-Objekt sein") + clean = _sanitize(incoming) + if not isinstance(clean, dict): + raise ValueError("Ungültige Tracker-Daten") + clean["version"] = TRACKER_SCHEMA_VERSION + clean["source_file"] = plan["source_file"] + clean["source_hash"] = plan["source_hash"] + clean["plan_id"] = plan.get("plan_id") + clean["plan_revision"] = plan.get("published_revision") + clean["created_at"] = existing.get("created_at") or _utc_now() + clean["updated_at"] = _utc_now() + clean.pop("source_changed", None) + clean.pop("analysis_cache", None); clean.pop("analysis_state", None); clean.pop("analyses", None) + if not isinstance(clean.get("profile"), dict): clean["profile"] = {} + if not isinstance(clean.get("progressions"), dict): clean["progressions"] = {} + if not isinstance(clean.get("sessions"), dict): clean["sessions"] = {} + if not isinstance(clean.get("week_statuses"), dict): clean["week_statuses"] = {} + valid_statuses = {"planned", "in_progress", "stopped", "completed"} + for session in clean["sessions"].values(): + if not isinstance(session, dict): continue + session.setdefault("plan_id", existing.get("plan_id") or plan.get("plan_id")) + session.setdefault("plan_revision", existing.get("plan_revision") or plan.get("published_revision")) + status = str(session.get("status") or "planned") + session["status"] = status if status in valid_statuses else "planned" + items = session.get("items") if isinstance(session.get("items"), dict) else {} + session["items"] = items + for item in items.values(): + if not isinstance(item, dict): continue + item_status = str(item.get("completion_status") or ("completed" if item.get("done") else "planned")) + item["completion_status"] = item_status if item_status in VALID_ITEM_STATUSES else "planned" + item["done"] = item["completion_status"] in {"completed", "partial"} + if item["completion_status"] != "skipped": item.pop("skip_reason", None) + if isinstance(item.get("result_data"), dict): + data = _sanitize_result_data(item.get("result_data")) + if data is not None: + item["result_data"] = data; item["result"] = _format_result_data(data) + else: item.pop("result_data", None) + for week, status in list(clean["week_statuses"].items()): + if not isinstance(status, dict): clean["week_statuses"].pop(week, None); continue + status["status"] = "closed" if status.get("status") == "closed" else "open" + return clean + + +def _save_tracker(plan: dict[str, Any], incoming: Any) -> dict[str, Any]: + existing = _load_tracker(plan) + expected = incoming.get("expected_revision") if isinstance(incoming, dict) else None + if expected is not None and int(expected) != int(existing.get("revision") or 1): + raise RuntimeError(f"revision_conflict:{int(existing.get('revision') or 1)}") + payload = incoming.get("tracker") if isinstance(incoming, dict) and isinstance(incoming.get("tracker"), dict) else incoming + clean = _normalize_tracker_payload(plan, payload, existing) + clean["revision"] = int(existing.get("revision") or 1) + 1 + _atomic_json_write(_tracker_path(plan["source_file"]), clean) + return clean + + +def _patch_tracker(plan: dict[str, Any], payload: Any) -> dict[str, Any]: + if not isinstance(payload, dict): raise ValueError("Ungültiger Session-Patch") + existing = _load_tracker(plan) + session_payload = payload.get("session") + if isinstance(session_payload, dict): + session_payload = deepcopy(session_payload) + session_payload.setdefault("plan_id", plan.get("plan_id")) + session_payload.setdefault("plan_revision", plan.get("published_revision")) + payload = dict(payload) + payload["session"] = session_payload + try: + merged = merge_session_patch(existing, payload, _utc_now()) + except RuntimeError: + raise + clean = _normalize_tracker_payload(plan, merged, existing) + clean["revision"] = int(merged.get("revision") or int(existing.get("revision") or 1) + 1) + _atomic_json_write(_tracker_path(plan["source_file"]), clean) + return clean + + +def _openai_ready() -> bool: + return bool(os.environ.get("OPENAI_API_KEY", "").strip() and os.environ.get("OPENAI_MODEL", "").strip()) + + +def _stable_hash(value: Any) -> str: + raw = json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8") + return hashlib.sha256(raw).hexdigest() + + +def _session_key_parts(key: str) -> tuple[int, int]: + match = re.fullmatch(r"w(\d+)-d(\d+)", str(key or "")) + if not match: + return (9999, 9999) + return int(match.group(1)), int(match.group(2)) + + +def _session_date(profile: dict[str, Any], week: int, day_num: int) -> str: + raw = str(profile.get("start_date") or "").strip() + if not raw: + return "" + try: + start = date.fromisoformat(raw) + except ValueError: + return "" + return (start + timedelta(days=(week - 1) * 7 + day_num - 1)).isoformat() + + +def _phase_for_week(plan: dict[str, Any], week: int) -> dict[str, Any]: + phase_map = plan.get("phase_by_week") if isinstance(plan.get("phase_by_week"), list) else [] + phases = plan.get("phases") if isinstance(plan.get("phases"), list) else [] + index = phase_map[max(0, week - 1)] if week - 1 < len(phase_map) else 0 + if isinstance(index, int) and 0 <= index < len(phases): + return phases[index] + return phases[0] if phases else {"name": "", "params": ""} + + +def _has_text(value: Any) -> bool: + return bool(str(value or "").strip()) + + +def _session_records(plan: dict[str, Any], tracker: dict[str, Any]) -> list[dict[str, Any]]: + """Normalisiert ausschließlich tatsächlich befüllte Sessiondaten.""" + days_by_num = {int(day.get("num", 0)): day for day in plan.get("days", []) if isinstance(day, dict)} + profile = tracker.get("profile") if isinstance(tracker.get("profile"), dict) else {} + sessions_out: list[dict[str, Any]] = [] + sessions = tracker.get("sessions") if isinstance(tracker.get("sessions"), dict) else {} + + for key, session in sorted(sessions.items(), key=lambda pair: _session_key_parts(pair[0])): + if not isinstance(session, dict): + continue + week, day_num = _session_key_parts(key) + if week == 9999: + continue + day = days_by_num.get(day_num, {}) + exercise_by_id: dict[str, dict[str, Any]] = {} + for rotation in day.get("rotations", []) if isinstance(day.get("rotations"), list) else []: + if not isinstance(rotation, dict): + continue + for exercise in rotation.get("exercises", []) if isinstance(rotation.get("exercises"), list) else []: + if isinstance(exercise, dict): + enriched = dict(exercise) + enriched["rotation"] = _plain_text(rotation.get("label") or "") + enriched["kind"] = "exercise" + exercise_by_id[str(exercise.get("id") or "")] = enriched + legacy_id = str(exercise.get("legacy_id") or "") + if legacy_id: + exercise_by_id[legacy_id] = enriched + for routine_name in ("warmup", "cooldown", "stretch"): + routine = day.get(routine_name) if isinstance(day.get(routine_name), dict) else {} + for index, label in enumerate(routine.get("items", []) if isinstance(routine.get("items"), list) else []): + exercise_by_id[f"{routine_name}-{index}"] = { + "name": _plain_text(label), "progression_id": "", "cue": "", + "rotation": routine_name, "kind": routine_name, + } + + items_out: list[dict[str, Any]] = [] + items = session.get("items") if isinstance(session.get("items"), dict) else {} + for item_id, item in items.items(): + if not isinstance(item, dict): + continue + item_status = str(item.get("completion_status") or ("completed" if item.get("done") else "planned")) + meaningful = item_status != "planned" or isinstance(item.get("result_data"), dict) or any(_has_text(item.get(field)) for field in ( + "result", "note", "progression", "exercise_name", "progression_id", "skip_reason" + )) + if not meaningful: + continue + exercise = exercise_by_id.get(str(item_id), {}) + items_out.append({ + "item_id": str(item_id), + "exercise": _plain_text(item.get("exercise_name") or exercise.get("name") or item_id), + "progression_id": str(item.get("progression_id") or exercise.get("progression_id") or ""), + "progression": _plain_text(item.get("progression") or ""), + "result": _plain_text(item.get("result") or ""), + "result_data": deepcopy(item.get("result_data")) if isinstance(item.get("result_data"), dict) else None, + "note": _plain_text(item.get("note") or ""), + "done": bool(item.get("done")), + "completion_status": item_status, + "skip_reason": _plain_text(item.get("skip_reason") or ""), + "progression_step_id": str(item.get("progression_step_id") or ""), + "exercise_id": str(exercise.get("exercise_id") or ""), + "movement_cluster": str(exercise.get("movement_cluster") or ""), + "cue": _plain_text(exercise.get("cue") or ""), + "rotation": _plain_text(exercise.get("rotation") or ""), + "kind": str(exercise.get("kind") or "other"), + }) + + section_notes = { + name: _plain_text(session.get(f"{name}_note") or "") + for name in ("warmup", "cooldown", "stretch") + if _has_text(session.get(f"{name}_note")) + } + meaningful_session = ( + session.get("status") in {"in_progress", "stopped", "completed"} + or _has_text(session.get("note")) or bool(section_notes) or bool(items_out) + ) + if not meaningful_session: + continue + phase = _phase_for_week(plan, week) + sessions_out.append({ + "session_key": key, + "week": week, + "day": day_num, + "date": _session_date(profile, week, day_num), + "focus": _plain_text(day.get("focus") or f"Tag {day_num}"), + "light": bool(day.get("light")), + "phase": _plain_text(phase.get("name") or ""), + "phase_instructions": _plain_text(phase.get("params") or ""), + "status": str(session.get("status") or "planned"), + "plan_id": str(session.get("plan_id") or tracker.get("plan_id") or plan.get("plan_id") or ""), + "plan_revision": int(session.get("plan_revision") or tracker.get("plan_revision") or plan.get("published_revision") or 1), + "started_at": str(session.get("started_at") or ""), + "stopped_at": str(session.get("stopped_at") or ""), + "completed_at": str(session.get("completed_at") or ""), + "session_note": _plain_text(session.get("note") or ""), + "section_notes": section_notes, + "items": items_out, + }) + return sessions_out + + +def _plan_context( + plan: dict[str, Any], *, week: int | None = None, progression_ids: set[str] | None = None +) -> dict[str, Any]: + stages = plan.get("stages") if isinstance(plan.get("stages"), dict) else {} + if progression_ids: + stages = {key: value for key, value in stages.items() if key in progression_ids} + phase = _phase_for_week(plan, week) if week else None + return { + "plan_id": plan.get("plan_id"), + "published_revision": plan.get("published_revision"), + "schema_version": plan.get("schema_version"), + "contract_version": plan.get("contract_version"), + "title": plan.get("title") or plan.get("name"), + "subtitle": plan.get("subtitle") or "", + "weeks": plan.get("weeks"), + "selected_week": week, + "selected_phase": { + "name": _plain_text(phase.get("name") or ""), + "instructions": _plain_text(phase.get("params") or ""), + } if phase else None, + "training_format": deepcopy(plan.get("training_format") or {}), + "guidance": { + "goals": _plain_text(plan.get("front", {}).get("goals_box") or ""), + "goals_note": _plain_text(plan.get("front", {}).get("goals_note") or ""), + "session_method": _plain_text(plan.get("front", {}).get("session_how_body") or ""), + "timer_note": _plain_text(plan.get("front", {}).get("timer_note") or ""), + "reminders": [_plain_text(item) for item in plan.get("front", {}).get("reminders", [])], + }, + "days": [ + { + "day": day.get("num"), + "focus": _plain_text(day.get("focus") or ""), + "light": bool(day.get("light")), + "note": _plain_text(day.get("note") or ""), + "warmup": [_plain_text(item) for item in day.get("warmup", {}).get("items", [])], + "cooldown": [_plain_text(item) for item in day.get("cooldown", {}).get("items", [])], + "stretch": [_plain_text(item) for item in day.get("stretch", {}).get("items", [])], + "blocks": [ + { + "label": _plain_text(rotation.get("label") or ""), + "exercises": [ + { + "id": exercise.get("id") or "", + "exercise_id": exercise.get("exercise_id") or "", + "name": _plain_text(exercise.get("name") or ""), + "progression_id": exercise.get("progression_id") or "", + "movement_cluster": exercise.get("movement_cluster") or "", + "cue": _plain_text(exercise.get("cue") or ""), + } + for exercise in rotation.get("exercises", []) if isinstance(exercise, dict) + ], + } + for rotation in day.get("rotations", []) if isinstance(rotation, dict) + ], + } + for day in plan.get("days", []) if isinstance(day, dict) + ], + "phases": [ + { + "name": _plain_text(item.get("name") or ""), + "instructions": _plain_text(item.get("params") or ""), + "start_week": item.get("start_week"), + "end_week": item.get("end_week"), + } + for item in plan.get("phases", []) if isinstance(item, dict) + ], + "progression_stages": stages, + "exercise_catalog": deepcopy(plan.get("exercise_catalog") or {}), + } + + +def _latest_state_before(records: list[dict[str, Any]], week: int) -> list[dict[str, Any]]: + latest: dict[str, dict[str, Any]] = {} + for session in records: + if int(session.get("week") or 0) >= week: + continue + for item in session.get("items", []): + progression_id = str(item.get("progression_id") or "") + if not progression_id: + continue + latest[progression_id] = { + "progression_id": progression_id, + "exercise": item.get("exercise") or progression_id, + "progression": item.get("progression") or "", + "result": item.get("result") or "", + "note": item.get("note") or "", + "session_key": session.get("session_key") or "", + } + return list(latest.values()) + + +def _plan_bodyweight_kg(plan: dict[str, Any]) -> float | None: + """Liest ein optional im Plantext genanntes Körpergewicht, ohne es zu raten.""" + candidates = [ + plan.get("subtitle"), plan.get("title"), + (plan.get("meta") or {}).get("subtitle") if isinstance(plan.get("meta"), dict) else "", + ] + for candidate in candidates: + for value in re.findall(r"(? dict[str, Any]: + tables = [] + for table in _EQUIVALENCE_TABLES: + mode = table["mode"] + unit = "Referenz-Reps" if mode == "reps" else "Referenzsekunden" + variants = [] + for variant in table.get("variants", []): + factor = float(variant.get("factor") or 1) + if mode == "reps": + if factor <= 1: + needed = round(1 / factor, 1) if factor else 0 + example = f"{needed:g} {'Rep' if needed == 1 else 'Reps'} ≈ 1 Referenz-Rep" + else: + example = f"1 Rep = {factor:g} Referenz-Reps" + else: + example = f"20 s = {round(20 * factor, 1):g} {unit}" + variants.append({ + "label": variant.get("label") or "Variante", + "factor": factor, + "example": example, + }) + tables.append({ + "id": table["id"], "label": table["label"], "mode": mode, + "reference": table["reference"], "variants": variants, + }) + plan_catalog = [] + catalog = plan.get("exercise_catalog") if isinstance(plan.get("exercise_catalog"), dict) else {} + seen_catalog: set[tuple[str, str]] = set() + for entry in catalog.values(): + if not isinstance(entry, dict): + continue + progression_id = str(entry.get("progression_id") or "") + cluster_id = str(entry.get("movement_cluster") or "general") + key = (progression_id, cluster_id) + if key in seen_catalog: + continue + seen_catalog.add(key) + variants = [] + for variant in entry.get("variants", []) if isinstance(entry.get("variants"), list) else []: + if not isinstance(variant, dict): + continue + variants.append({ + "id": str(variant.get("id") or ""), + "name": _plain_text(variant.get("name") or ""), + "factor": float(variant.get("factor") or 1.0), + "movement_cluster": str(variant.get("movement_cluster") or cluster_id), + "result_schema": deepcopy(variant.get("result_schema") if isinstance(variant.get("result_schema"), dict) else {}), + }) + plan_catalog.append({ + "exercise_id": str(entry.get("id") or ""), + "name": _plain_text(entry.get("name") or progression_id), + "progression_id": progression_id, + "movement_cluster": cluster_id, + "movement_label": _plain_text(entry.get("movement_label") or cluster_id), + "variants": variants, + }) + plan_catalog.sort(key=lambda row: (row["movement_label"], row["name"])) + bodyweight = _plan_bodyweight_kg(plan) + return { + "version": PROGRESSION_NORMALIZATION_VERSION, + "title": "Varianten-Cluster und Referenzwerte", + "bodyweight_kg": bodyweight, + "method": ( + "Wiederholungen werden innerhalb desselben Bewegungsmusters mit einem Variantenfaktor in Referenz-Reps umgerechnet. " + "Isometrische Übungen bleiben getrennt und werden als Referenzsekunden ausgewertet. Externe Last wird bei reinen " + "Gewichtsübungen als kg·Reps bzw. kg·s gerechnet. Bei Bodyweight-Übungen wird Zusatzlast nur dann relativ berücksichtigt, " + "wenn das Körpergewicht ausdrücklich im Plan steht." + ), + "caveat": ( + "Die Faktoren sind transparente Analyseheuristiken, keine exakten physiologischen Gleichungen und keine Vorgabe für die " + "Trainingssteuerung. Technik, ROM, Tempo und Nähe zum Versagen bleiben zusätzlich entscheidend." + ), + "tables": tables, + "plan_catalog": plan_catalog, + "sources": deepcopy(_EQUIVALENCE_SOURCES), + } + + +def _extract_training_measure(text: Any) -> dict[str, Any] | None: + """Extrahiert Messwerte bevorzugt aus ``result_data``, sonst aus Freitext. + + Beispiele: ``8/7/6``, ``3x8``, ``6x20 s``, ``4 kg, 6/6/5 je Seite``. + Die Rohangabe bleibt zusätzlich erhalten; unklare Felder werden nicht erfunden. + """ + if isinstance(text, dict): + structured = text.get("result_data") if isinstance(text.get("result_data"), dict) else text + data = _sanitize_result_data(structured) + if data: + if data["laterality"] == "unilateral" and data["sides_mode"] == "separate": + left = [value for value in data["left_values"] if value is not None] + right = [value for value in data["right_values"] if value is not None] + values = left + right + total = sum(values) + per_side = False + else: + values = [value for value in data["values"] if value is not None] + per_side = data["laterality"] == "unilateral" + total = sum(values) * (2 if per_side else 1) + if values: + measure_mode = "seconds" if data["mode"] == "minutes" else data["mode"] + if data["mode"] == "minutes": + values = [round(value * 60, 3) for value in values] + total = total * 60 + return { + "mode": measure_mode, "total": round(total, 3), "values": values, + "sets": data["sets"], "weight_kg": data.get("weight_kg"), + "per_side": per_side, "raw": _format_result_data(data), "structured": True, + } + text = text.get("result") or "" + + raw = _plain_text(text or "").strip() + if not raw: + return None + low = raw.casefold().replace("×", "x") + kg_values = [float(v.replace(",", ".")) for v in re.findall(r"(\d+(?:[.,]\d+)?)\s*kg\b", low)] + weight_kg = max(kg_values) if kg_values else None + per_side = bool(re.search(r"\b(?:je|pro)\s+seite\b|/\s*seite\b", low)) + side_multiplier = 2 if per_side else 1 + + # Dauer ist eindeutig, sobald eine Sekunden-/Minuten-Einheit vorkommt. + has_seconds = bool(re.search(r"\b(?:s|sek\.?|sekunden|min\.?|minuten)\b", low)) + if has_seconds: + work = re.sub(r"\d+(?:[.,]\d+)?\s*kg\b", " ", low) + set_duration = re.search( + r"(\d+(?:[.,]\d+)?)\s*x\s*(\d+(?:[.,]\d+)?)\s*(s|sek\.?|sekunden|min\.?|minuten)\b", + work, + ) + if set_duration: + sets = float(set_duration.group(1).replace(",", ".")) + duration = float(set_duration.group(2).replace(",", ".")) + if set_duration.group(3).startswith("min"): + duration *= 60 + total = sets * duration * side_multiplier + values = [duration] * max(1, int(round(sets))) + return { + "mode": "seconds", "total": round(total, 3), "values": values, + "sets": sets, "weight_kg": weight_kg, "per_side": per_side, "raw": raw, + } + durations: list[float] = [] + for number, unit in re.findall(r"(\d+(?:[.,]\d+)?)\s*(s|sek\.?|sekunden|min\.?|minuten)\b", work): + value = float(number.replace(",", ".")) + if unit.startswith("min"): + value *= 60 + durations.append(value) + if durations: + return { + "mode": "seconds", "total": round(sum(durations) * side_multiplier, 3), + "values": durations, "sets": len(durations), "weight_kg": weight_kg, + "per_side": per_side, "raw": raw, + } + # Formate wie „20/20/18 s“: Einheit einmal am Ende. + numbers = [float(v.replace(",", ".")) for v in re.findall(r"\d+(?:[.,]\d+)?", work)] + if numbers: + return { + "mode": "seconds", "total": round(sum(numbers) * side_multiplier, 3), + "values": numbers, "sets": len(numbers), "weight_kg": weight_kg, + "per_side": per_side, "raw": raw, + } + return None + + # Reps: Gewichtsangaben werden vor dem Auslesen entfernt, damit 10 kg nicht + # versehentlich als zehn Wiederholungen zählen. + work = re.sub(r"\d+(?:[.,]\d+)?\s*kg\b", " ", low) + set_reps = re.search(r"(\d+(?:[.,]\d+)?)\s*x\s*(\d+(?:[.,]\d+)?)", work) + if set_reps: + sets = float(set_reps.group(1).replace(",", ".")) + reps = float(set_reps.group(2).replace(",", ".")) + total = sets * reps * side_multiplier + return { + "mode": "reps", "total": round(total, 3), "values": [reps] * max(1, int(round(sets))), + "sets": sets, "weight_kg": weight_kg, "per_side": per_side, "raw": raw, + } + numbers = [float(v.replace(",", ".")) for v in re.findall(r"\d+(?:[.,]\d+)?", work)] + if not numbers: + return None + return { + "mode": "reps", "total": round(sum(numbers) * side_multiplier, 3), + "values": numbers, "sets": len(numbers), "weight_kg": weight_kg, + "per_side": per_side, "raw": raw, + } + + +def _numeric_measure(text: Any) -> dict[str, Any] | None: + """Kompatible Kurzform für bestehende Aufrufer und Tests.""" + measure = _extract_training_measure(text) + if not measure: + return None + unit = "Sekunden" if measure["mode"] == "seconds" else "Reps" + return {"value": measure["total"], "unit": unit, "values": measure["values"]} + + +def _catalog_entry(plan: dict[str, Any], item: dict[str, Any]) -> dict[str, Any] | None: + catalog = plan.get("exercise_catalog") if isinstance(plan.get("exercise_catalog"), dict) else {} + exercise_id = str(item.get("exercise_id") or "") + if exercise_id and isinstance(catalog.get(exercise_id), dict): + return catalog[exercise_id] + progression_id = str(item.get("progression_id") or "") + for entry in catalog.values(): + if isinstance(entry, dict) and progression_id and str(entry.get("progression_id") or "") == progression_id: + return entry + return None + + +def _explicit_cluster_spec(plan: dict[str, Any], item: dict[str, Any], measure: dict[str, Any]) -> dict[str, Any] | None: + entry = _catalog_entry(plan, item) or {} + cluster_id = str(item.get("movement_cluster") or entry.get("movement_cluster") or "") + name = _plain_text(item.get("exercise") or item.get("exercise_name") or entry.get("name") or "") + text = f"{name} {_plain_text(item.get('progression') or '')}".casefold() + seconds = measure.get("mode") == "seconds" + if not cluster_id or cluster_id == "general": + return None + + if cluster_id == "horizontal_push": + return {"id": "push_support_hold" if seconds else "push_up", "label": "Push-up Startposition / High Plank" if seconds else "Push-up", "mode": measure["mode"], "priority": 100} + if cluster_id == "horizontal_pull": + external = bool(measure.get("weight_kg")) or any(token in text for token in ("kb", "kettlebell", "rucksack", "hantel")) + return {"id": "loaded_row" if external else "inverted_row", "label": "Rudern" if external else "Inverted Row", "mode": measure["mode"], "priority": 92 if not external else 84, "external_load": external} + if cluster_id == "vertical_pull": + return {"id": "active_hang" if seconds else "pull_up", "label": "Active Hang" if seconds else "Pull-up", "mode": measure["mode"], "priority": 100} + if cluster_id == "vertical_push": + if "dip" in text: + return {"id": "dip_support_hold" if seconds else "dip", "label": "Dip Support Hold" if seconds else "Dip", "mode": measure["mode"], "priority": 82} + if "pike" in text: + return {"id": "pike_hold" if seconds else "pike_press", "label": "Pike Hold" if seconds else "Pike Push-up", "mode": measure["mode"], "priority": 78} + return {"id": "loaded_press", "label": "Loaded Press", "mode": measure["mode"], "priority": 72, "external_load": True} + if cluster_id == "knee_dominant": + if seconds: + if "wall" in text or "wandsitz" in text: + return {"id": "wall_sit", "label": "Wall Sit", "mode": "seconds", "priority": 75} + return {"id": "deep_squat_hold", "label": "Deep Squat Hold", "mode": "seconds", "priority": 90} + if "lunge" in text or "ausfallschritt" in text: + return {"id": "lunge", "label": "Ausfallschritt", "mode": "reps", "priority": 78} + return {"id": "squat", "label": "Squat", "mode": "reps", "priority": 100} + if cluster_id == "hip_hinge": + if "bridge" in text: + return {"id": "bridge_hold" if seconds else "bridge", "label": "Glute Bridge Hold" if seconds else "Glute Bridge", "mode": measure["mode"], "priority": 74} + if "swing" in text: + return {"id": "swing", "label": "Kettlebell Swing", "mode": measure["mode"], "priority": 72, "external_load": True} + return {"id": "hinge", "label": "Hinge / Deadlift", "mode": measure["mode"], "priority": 88, "external_load": True} + if cluster_id == "core": + if seconds: + if "side" in text or "seit" in text: + return {"id": "side_plank", "label": "Side Plank", "mode": "seconds", "priority": 82} + if "hollow" in text: + return {"id": "hollow_hold", "label": "Hollow Hold", "mode": "seconds", "priority": 82} + return {"id": "plank", "label": name or "Core Hold", "mode": "seconds", "priority": 100} + if "dead bug" in text: + return {"id": "dead_bug", "label": "Dead Bug", "mode": "reps", "priority": 66} + if "beinheben" in text or "leg raise" in text: + return {"id": "leg_raise", "label": "Beinheben", "mode": "reps", "priority": 70} + if cluster_id == "elbow_flexion": + return {"id": "curl", "label": "Bizeps-Curl", "mode": measure["mode"], "priority": 52, "external_load": True} + if cluster_id == "calf": + return {"id": "calf_raise", "label": "Wadenheben", "mode": measure["mode"], "priority": 58} + if cluster_id == "loaded_mobility": + return {"id": "grip_hold" if seconds else f"exercise:{_slug(name)}", "label": name or "Kettlebell-Kontrolle", "mode": measure["mode"], "priority": 55, "external_load": True} + return None + + +def _cluster_spec(plan: dict[str, Any], item: dict[str, Any], measure: dict[str, Any]) -> dict[str, Any]: + explicit = _explicit_cluster_spec(plan, item, measure) + if explicit: + return explicit + name = _plain_text(item.get("exercise") or item.get("exercise_name") or "") + progression = _plain_text(item.get("progression") or "") + pid = str(item.get("progression_id") or "") + text = f"{name} {progression} {pid}".casefold() + seconds = measure.get("mode") == "seconds" + + def found(*patterns: str) -> bool: + return any(re.search(pattern, text, flags=re.I) for pattern in patterns) + + if seconds: + if found(r"side plank", r"seit.*plank", r"\bsp\b"): + return {"id": "side_plank", "label": "Side Plank", "mode": "seconds", "priority": 82} + if found(r"hollow", r"\bholl\b"): + return {"id": "hollow_hold", "label": "Hollow Hold", "mode": "seconds", "priority": 82} + if found(r"wall sit", r"wandsitz", r"\bwall\b"): + return {"id": "wall_sit", "label": "Wall Sit", "mode": "seconds", "priority": 75} + if found(r"deep squat", r"squat hold", r"tiefe.*kniebeuge.*halt"): + return {"id": "deep_squat_hold", "label": "Deep Squat Hold", "mode": "seconds", "priority": 90} + if found(r"active hang", r"hang", r"hängen"): + return {"id": "active_hang", "label": "Active Hang", "mode": "seconds", "priority": 92} + if found(r"push.?up", r"liegestütz") and found(r"plank", r"hold", r"startposition"): + return {"id": "push_support_hold", "label": "Push-up Startposition / High Plank", "mode": "seconds", "priority": 90} + if found(r"pike"): + return {"id": "pike_hold", "label": "Pike Hold", "mode": "seconds", "priority": 76} + if found(r"dip") and found(r"support", r"hold", r"stütz"): + return {"id": "dip_support_hold", "label": "Dip Support Hold", "mode": "seconds", "priority": 78} + if found(r"plank"): + return {"id": "plank", "label": "Plank", "mode": "seconds", "priority": 100} + if found(r"superman", r"y-t-w", r"ytw"): + return {"id": "back_hold", "label": "Superman / Y-T-W Hold", "mode": "seconds", "priority": 68} + if found(r"glute bridge", r"bridge"): + return {"id": "bridge_hold", "label": "Glute Bridge Hold", "mode": "seconds", "priority": 72} + if found(r"bottoms.?up", r"carry", r"griff"): + return {"id": "grip_hold", "label": "Griff- / Carry-Hold", "mode": "seconds", "priority": 62} + fallback = pid or _slug(name) or "isometric" + return {"id": f"iso:{fallback}", "label": name or "Isometrischer Hold", "mode": "seconds", "priority": 45} + + if found(r"push.?up", r"liegestütz") and not found(r"pike"): + return {"id": "push_up", "label": "Push-up", "mode": "reps", "priority": 100} + if found(r"dip"): + return {"id": "dip", "label": "Dip", "mode": "reps", "priority": 82} + if found(r"pike"): + return {"id": "pike_press", "label": "Pike Push-up", "mode": "reps", "priority": 78} + if found(r"pull.?up", r"klimmzug"): + return {"id": "pull_up", "label": "Pull-up", "mode": "reps", "priority": 100} + if found(r"inverted row", r"body row", r"bent leg row", r"straight leg row"): + return {"id": "inverted_row", "label": "Inverted Row", "mode": "reps", "priority": 92} + if found(r"squat", r"kniebeuge") and not found(r"wall sit"): + return {"id": "squat", "label": "Squat", "mode": "reps", "priority": 100} + if found(r"ausfallschritt", r"lunge"): + return {"id": "lunge", "label": "Ausfallschritt", "mode": "reps", "priority": 78} + if found(r"rdl", r"deadlift", r"hip hinge", r"kreuzheben"): + return {"id": "hinge", "label": "Hinge / Deadlift", "mode": "reps", "priority": 88, "external_load": True} + if found(r"glute bridge", r"bridge"): + return {"id": "bridge", "label": "Glute Bridge", "mode": "reps", "priority": 74} + if found(r"wadenheben", r"calf"): + return {"id": "calf_raise", "label": "Wadenheben", "mode": "reps", "priority": 58} + if found(r"rudern.*eng", r"row.*eng"): + return {"id": "row_lat", "label": "Einarm-Rudern eng", "mode": "reps", "priority": 82, "external_load": True} + if found(r"rudern.*breit", r"row.*breit"): + return {"id": "row_upper", "label": "Einarm-Rudern breit", "mode": "reps", "priority": 78, "external_load": True} + if found(r"rudern", r"\brow\b"): + return {"id": "loaded_row", "label": "Rudern", "mode": "reps", "priority": 84, "external_load": True} + if found(r"curl", r"bizeps"): + return {"id": "curl", "label": "Bizeps-Curl", "mode": "reps", "priority": 52, "external_load": True} + if found(r"seitheben", r"lateral raise"): + return {"id": "lateral_raise", "label": "Seitheben", "mode": "reps", "priority": 50, "external_load": True} + if found(r"floor press", r"overhead press", r"schulterdrücken"): + return {"id": "loaded_press", "label": "Loaded Press", "mode": "reps", "priority": 72, "external_load": True} + if found(r"swing"): + return {"id": "swing", "label": "Kettlebell Swing", "mode": "reps", "priority": 72, "external_load": True} + if found(r"mountain climber"): + return {"id": "mountain_climber", "label": "Mountain Climbers", "mode": "reps", "priority": 52} + if found(r"dead bug"): + return {"id": "dead_bug", "label": "Dead Bug", "mode": "reps", "priority": 66} + if found(r"beinheben", r"leg raise"): + return {"id": "leg_raise", "label": "Beinheben", "mode": "reps", "priority": 70} + if found(r"bicycle"): + return {"id": "bicycle", "label": "Bicycle Crunch", "mode": "reps", "priority": 48} + if found(r"russian twist"): + return {"id": "russian_twist", "label": "Russian Twist", "mode": "reps", "priority": 48} + fallback = pid or _slug(name) or "exercise" + return {"id": f"exercise:{fallback}", "label": name or fallback, "mode": "reps", "priority": 40} + +def _equivalence_table(cluster_id: str) -> dict[str, Any] | None: + return next((table for table in _EQUIVALENCE_TABLES if table.get("id") == cluster_id), None) + + +def _stage_factor(plan: dict[str, Any], item: dict[str, Any], mode: str) -> float: + pid = str(item.get("progression_id") or "") + progression = _slug(item.get("progression") or "") + step_id = str(item.get("progression_step_id") or "") + stage = plan.get("stages", {}).get(pid) if isinstance(plan.get("stages"), dict) else None + steps = stage.get("steps") if isinstance(stage, dict) and isinstance(stage.get("steps"), list) else [] + if not steps: + return 1.0 + index = None + selected: dict[str, Any] | None = None + for i, raw_step in enumerate(steps): + step = raw_step if isinstance(raw_step, dict) else {"name": str(raw_step or "")} + normalized = _slug(step.get("name") or "") + if (step_id and str(step.get("id") or "") == step_id) or (progression and (progression == normalized or progression in normalized or normalized in progression)): + index = i + selected = step + break + if selected: + try: + explicit = float(selected.get("factor")) + except (TypeError, ValueError): + explicit = 0.0 + if explicit > 0: + return round(explicit, 3) + if index is None or len(steps) <= 1: + return 1.0 + if mode == "seconds": + factors = [0.65 + (0.50 * i / (len(steps) - 1)) for i in range(len(steps))] + else: + factors = [0.25 * (4 ** (i / (len(steps) - 1))) for i in range(len(steps))] + return round(factors[index], 3) + +def _variation_factor(plan: dict[str, Any], item: dict[str, Any], cluster: dict[str, Any]) -> tuple[float, str]: + entry = _catalog_entry(plan, item) + step_id = str(item.get("progression_step_id") or "") + progression = _slug(item.get("progression") or "") + if entry: + variants = entry.get("variants") if isinstance(entry.get("variants"), list) else [] + for variant in variants: + if not isinstance(variant, dict): + continue + variant_name = _slug(variant.get("name") or "") + if (step_id and str(variant.get("id") or "") == step_id) or (progression and variant_name and (progression == variant_name or progression in variant_name or variant_name in progression)): + try: + factor = float(variant.get("factor") or 1.0) + except (TypeError, ValueError): + factor = 1.0 + return max(0.001, factor), _plain_text(variant.get("name") or item.get("progression") or "Variante") + if cluster.get("external_load"): + return 1.0, _plain_text(item.get("progression") or "Last aus Ergebnisfeld") + table = _equivalence_table(cluster["id"]) + text = f"{_plain_text(item.get('progression') or '')} {_plain_text(item.get('exercise') or item.get('exercise_name') or '')}".casefold() + matches: list[tuple[float, str]] = [] + if table: + for variant in table.get("variants", []): + if any(re.search(pattern, text, flags=re.I) for pattern in variant.get("patterns", [])): + matches.append((float(variant.get("factor") or 1), str(variant.get("label") or "Variante"))) + if matches: + return max(matches, key=lambda entry: entry[0]) + factor = _stage_factor(plan, item, cluster["mode"]) + return factor, _plain_text(item.get("progression") or "nicht näher bezeichnet") + +def _normalized_point(plan: dict[str, Any], item: dict[str, Any], measure: dict[str, Any], cluster: dict[str, Any]) -> dict[str, Any]: + factor, variant = _variation_factor(plan, item, cluster) + total = float(measure.get("total") or 0) + weight_kg = measure.get("weight_kg") + bodyweight_kg = _plan_bodyweight_kg(plan) + load_modifier = 1.0 + unit = "Referenzsekunden" if measure["mode"] == "seconds" else "Referenz-Reps" + + if cluster.get("external_load") and weight_kg: + value = total * float(weight_kg) * factor + unit = "kg·s" if measure["mode"] == "seconds" else "kg·Reps" + else: + # Zusatzlast bei Bodyweight-Bewegungen wird nur mit explizitem + # Körpergewicht verwendet. Ohne diese Information bleibt sie sichtbar, + # fließt aber nicht über eine erfundene Annahme ein. + if weight_kg and bodyweight_kg and cluster["id"] in { + "push_up", "dip", "pike_press", "pull_up", "inverted_row", "squat", + "lunge", "plank", "side_plank", "deep_squat_hold", + }: + load_modifier = 1 + float(weight_kg) / bodyweight_kg + value = total * factor * load_modifier + return { + "value": round(value, 2), "unit": unit, "factor": round(factor, 3), + "variant": variant, "raw_total": round(total, 2), "raw": measure.get("raw") or "", + "weight_kg": weight_kg, "load_modifier": round(load_modifier, 3), + } + + +def _cluster_progression_series(plan: dict[str, Any], records: list[dict[str, Any]], limit: int = 10) -> list[dict[str, Any]]: + sessions = sorted(records, key=lambda row: (int(row.get("week") or 0), int(row.get("day") or 0), str(row.get("session_key") or ""))) + groups: dict[tuple[str, str], dict[str, Any]] = {} + for session in sessions: + session_points: dict[tuple[str, str], dict[str, Any]] = {} + for item in session.get("items", []): + measure = _extract_training_measure(item) + if not measure: + continue + cluster = _cluster_spec(plan, item, measure) + point = _normalized_point(plan, item, measure, cluster) + key = (cluster["id"], point["unit"]) + bucket = session_points.setdefault(key, { + "cluster": cluster, "value": 0.0, "raw": [], "factors": [], "variants": [], "weights": [], + }) + bucket["value"] += point["value"] + bucket["raw"].append(point["raw"]) + bucket["factors"].append(point["factor"]) + bucket["variants"].append(point["variant"]) + if point.get("weight_kg") is not None: + bucket["weights"].append(point["weight_kg"]) + for key, bucket in session_points.items(): + cluster = bucket["cluster"] + group = groups.setdefault(key, { + "id": cluster["id"], "label": cluster["label"], "mode": cluster["mode"], + "unit": key[1], "priority": int(cluster.get("priority") or 40), + "reference": (_equivalence_table(cluster["id"]) or {}).get("reference", "planinterne Stufenheuristik"), + "points": [], + }) + label = f"W{session.get('week')}/T{session.get('day')}" + group["points"].append({ + "label": label, "week": session.get("week"), "day": session.get("day"), + "session_key": session.get("session_key"), "value": round(bucket["value"], 2), + "raw": " + ".join(bucket["raw"]), + "variant": " / ".join(dict.fromkeys(v for v in bucket["variants"] if v)), + "factor": round(max(bucket["factors"] or [1]), 3), + "weight_kg": max(bucket["weights"]) if bucket["weights"] else None, + }) + series = [] + for group in groups.values(): + points = group["points"] + if not points: + continue + first = float(points[0]["value"] or 0) + last = float(points[-1]["value"] or 0) + change_pct = round((last - first) / first * 100, 1) if first else None + group["data_points"] = len(points) + group["first_value"] = first + group["last_value"] = last + group["change_pct"] = change_pct + group["latest_variant"] = points[-1].get("variant") or "" + group["importance"] = group["priority"] + min(len(points), 8) * 6 + series.append(group) + series.sort(key=lambda row: (row["importance"], row["data_points"], row["label"]), reverse=True) + return series[:limit] + + +def _local_metrics(plan: dict[str, Any], records: list[dict[str, Any]], week: int | None = None) -> dict[str, Any]: + selected = [item for item in records if week is None or item.get("week") == week] + completed = sum(1 for item in selected if item.get("status") == "completed") + planned = len(plan.get("days") or []) if week is not None else max(1, len({item.get("week") for item in selected})) * len(plan.get("days") or []) + progression_ids = { + str(item.get("progression_id") or "") + for session in selected for item in session.get("items", []) + if str(item.get("progression_id") or "") + } + clusters = _cluster_progression_series(plan, selected, limit=50) + return { + "sessions_with_data": len(selected), + "completed_sessions": completed, + "planned_sessions": planned, + "completion_rate_pct": round(completed / planned * 100) if planned else 0, + "documented_exercises": len(progression_ids), + "measurable_progression_clusters": len(clusters), + } + + +def _flow_for_progression(plan: dict[str, Any], progression_id: str, exercise: str, current: str) -> dict[str, Any] | None: + stage = plan.get("stages", {}).get(progression_id) if isinstance(plan.get("stages"), dict) else None + steps = stage.get("steps") if isinstance(stage, dict) and isinstance(stage.get("steps"), list) else [] + if not steps: + return None + normalized_current = _slug(current) + current_index = None + for index, step in enumerate(steps): + step_name = step.get("name", "") if isinstance(step, dict) else step + normalized_step = _slug(step_name) + if normalized_current and (normalized_current == normalized_step or normalized_current in normalized_step or normalized_step in normalized_current): + current_index = index + break + nodes = [] + for index, step in enumerate(steps): + step_name = step.get("name", "") if isinstance(step, dict) else step + status = "" + if current_index is not None: + if index < current_index: + status = "done" + elif index == current_index: + status = "current" + elif index == current_index + 1: + status = "next" + nodes.append({"id": str(step.get("id") if isinstance(step, dict) else f"n{index + 1}"), "label": _plain_text(step_name), "status": status}) + return { + "title": exercise, + "nodes": nodes, + "edges": [{"from": nodes[i]["id"], "to": nodes[i + 1]["id"]} for i in range(len(nodes) - 1)], + "insight": f"Dokumentierter Stand: {current}" if current else "Noch keine eindeutige Stufe dokumentiert.", + } + + +def _local_visuals(plan: dict[str, Any], records: list[dict[str, Any]], *, overall: bool) -> dict[str, Any]: + normalized = _cluster_progression_series(plan, records, limit=6) + charts = [] + for group in normalized: + if len(group["points"]) < 2: + continue + labels = [point["label"] if overall else f"T{point['day']}" for point in group["points"]] + values = [point["value"] for point in group["points"]] + first_variant = group["points"][0].get("variant") or "unbekannt" + last_variant = group["points"][-1].get("variant") or "unbekannt" + charts.append({ + "title": f"{group['label']} · normalisierter Verlauf", + "type": "line", "unit": group["unit"], "labels": labels, + "series": [{"name": group["label"], "values": values}], + "insight": ( + f"{first_variant} → {last_variant}. Werte sind über Variantenfaktoren vergleichbar; " + f"Rohdaten bleiben im Analysearchiv erhalten." + ), + }) + latest: dict[str, dict[str, Any]] = {} + for session in records: + for item in session.get("items", []): + progression_id = str(item.get("progression_id") or "") + if progression_id: + latest[progression_id] = item + flows = [] + for progression_id, item in latest.items(): + flow = _flow_for_progression( + plan, progression_id, str(item.get("exercise") or progression_id), str(item.get("progression") or "") + ) + if flow: + flows.append(flow) + if len(flows) >= 4: + break + return { + "charts": charts, "flowcharts": flows, + "normalization_version": PROGRESSION_NORMALIZATION_VERSION, + "cluster_count": len(normalized), + } + + +def _week_dataset(plan: dict[str, Any], tracker: dict[str, Any], week: int) -> dict[str, Any]: + records = _session_records(plan, tracker) + sessions = [item for item in records if item.get("week") == week] + if not sessions: + raise ValueError(f"Woche {week} enthält noch keine befüllten Sessiondaten.") + progression_ids = { + str(item.get("progression_id") or "") + for session in sessions for item in session.get("items", []) + if str(item.get("progression_id") or "") + } + profile = tracker.get("profile") if isinstance(tracker.get("profile"), dict) else {} + week_state = (tracker.get("week_statuses") or {}).get(str(week), {}) if isinstance(tracker.get("week_statuses"), dict) else {} + return { + "analysis_type": "week", + "analysis_kind": "final" if week_state.get("status") == "closed" else "interim", + "week_status": deepcopy(week_state), + "prompt_version": PROMPT_VERSION, + "analysis_schema_version": ANALYSIS_SCHEMA_VERSION, + "normalization_version": PROGRESSION_NORMALIZATION_VERSION, + "model": os.environ.get("OPENAI_MODEL", "gpt-5.5").strip() or "gpt-5.5", + "week": week, + "plan": _plan_context(plan, week=week, progression_ids=progression_ids), + "profile": { + "display_name": _plain_text(profile.get("display_name") or ""), + "start_date": str(profile.get("start_date") or ""), + "plan_notes": _plain_text(profile.get("plan_notes") or ""), + }, + "previous_end_state": _latest_state_before(records, week), + "local_metrics": _local_metrics(plan, records, week), + "progression_normalization": { + "version": PROGRESSION_NORMALIZATION_VERSION, + "clusters": _cluster_progression_series(plan, sessions, limit=10), + }, + "sessions": sessions, + } + + +def _compact_week_result(record: dict[str, Any]) -> dict[str, Any]: + result = record.get("result") if isinstance(record.get("result"), dict) else {} + return { + "week": record.get("week"), + "source_hash": record.get("source_hash"), + "headline": result.get("headline") or "", + "summary": result.get("summary") or result.get("overview", {}).get("summary", ""), + "metrics": result.get("metrics", [])[:4] if isinstance(result.get("metrics"), list) else [], + "exercise_updates": result.get("exercise_updates", [])[:6] if isinstance(result.get("exercise_updates"), list) else [], + "plan_adjustments": result.get("plan_adjustments", [])[:3] if isinstance(result.get("plan_adjustments"), list) else [], + "warnings": result.get("warnings", [])[:2] if isinstance(result.get("warnings"), list) else [], + } + + +def _overall_dataset(plan: dict[str, Any], tracker: dict[str, Any]) -> dict[str, Any]: + records = _session_records(plan, tracker) + cache = _read_analysis_cache(plan) + weeks = sorted({int(item.get("week") or 0) for item in records if int(item.get("week") or 0) > 0}) + summaries = [] + for week in weeks: + record = cache["weeks"].get(str(week)) + if not isinstance(record, dict): + raise ValueError(f"Für Woche {week} fehlt eine aktuelle Wochenanalyse.") + summaries.append(_compact_week_result(record)) + if not summaries: + raise ValueError("Noch keine Wochenanalyse für eine Gesamtanalyse vorhanden.") + weekly_coverage = [] + for week in weeks: + metrics = _local_metrics(plan, records, week) + weekly_coverage.append({"week": week, **metrics}) + return { + "analysis_type": "overall", + "prompt_version": PROMPT_VERSION, + "analysis_schema_version": ANALYSIS_SCHEMA_VERSION, + "normalization_version": PROGRESSION_NORMALIZATION_VERSION, + "model": os.environ.get("OPENAI_MODEL", "gpt-5.5").strip() or "gpt-5.5", + "week_statuses": deepcopy(tracker.get("week_statuses") or {}), + "plan": _plan_context(plan), + "weekly_analyses": summaries, + "local_aggregates": { + "coverage_by_week": weekly_coverage, + "overall": _local_metrics(plan, records), + "current_progressions": _latest_state_before(records, max(weeks) + 1), + "progression_normalization": { + "version": PROGRESSION_NORMALIZATION_VERSION, + "clusters": _cluster_progression_series(plan, records, limit=10), + }, + }, + } + + +def _meaningful_weeks(plan: dict[str, Any], tracker: dict[str, Any]) -> list[int]: + return sorted({int(item.get("week") or 0) for item in _session_records(plan, tracker) if int(item.get("week") or 0) > 0}) + + +def _analysis_catalog(plan: dict[str, Any], tracker: dict[str, Any]) -> dict[str, Any]: + cache = _read_analysis_cache(plan) + week_entries = [] + all_current = True + for week in _meaningful_weeks(plan, tracker): + dataset = _week_dataset(plan, tracker, week) + source_hash = _stable_hash(dataset) + record = cache["weeks"].get(str(week)) + if not isinstance(record, dict): + status = "missing" + elif record.get("source_hash") != source_hash: + status = "stale" + else: + status = "current" + if status != "current": + all_current = False + week_entries.append({ + "week": week, "status": status, + "week_status": dataset.get("week_status", {}).get("status", "open"), + "analysis_kind": dataset.get("analysis_kind", "interim"), + "sessions": len(dataset.get("sessions", [])), + "created_at": record.get("created_at") if isinstance(record, dict) else "", + "record_id": record.get("id") if isinstance(record, dict) else "", + }) + overall_record = cache.get("overall") + if not week_entries: + overall_status = "missing" + overall_hash = "" + elif not all_current: + overall_status = "needs_weeks" + overall_hash = "" + else: + overall_dataset = _overall_dataset(plan, tracker) + overall_hash = _stable_hash(overall_dataset) + if not isinstance(overall_record, dict): + overall_status = "missing" + elif overall_record.get("source_hash") != overall_hash: + overall_status = "stale" + else: + overall_status = "current" + return { + "weeks": week_entries, + "overall": { + "status": overall_status, + "weeks": [entry["week"] for entry in week_entries], + "created_at": overall_record.get("created_at") if isinstance(overall_record, dict) else "", + "record_id": overall_record.get("id") if isinstance(overall_record, dict) else "", + "pending_weeks": [entry["week"] for entry in week_entries if entry["status"] != "current"], + }, + } + + +def _call_ai(messages: list[dict[str, str]], model: str) -> Any: + from core import ai as core_ai + return core_ai.chat_json(messages, model=model) + + +def _violates_fixed_interval(text: Any) -> bool: + low = _plain_text(text).casefold() + return bool(re.search( + r"(?:haltezeit|arbeitszeit|intervall|hold).{0,30}(?:verläng|erhöh|länger)|(?:länger|mehr)\s*(?:als\s*)?\d*\s*(?:s|sek)", + low, + )) + + +def _compact_ai_result(result: Any, training_format: dict[str, Any]) -> dict[str, Any]: + if not isinstance(result, dict): + raise ValueError("Die KI-Antwort besitzt nicht das erwartete JSON-Format") + clean = _sanitize(result) + if not isinstance(clean, dict): + raise ValueError("Ungültige KI-Antwort") + clean["metrics"] = clean.get("metrics", [])[:4] if isinstance(clean.get("metrics"), list) else [] + clean["exercise_updates"] = clean.get("exercise_updates", [])[:6] if isinstance(clean.get("exercise_updates"), list) else [] + clean["plan_adjustments"] = clean.get("plan_adjustments", [])[:3] if isinstance(clean.get("plan_adjustments"), list) else [] + for index, adjustment in enumerate(clean["plan_adjustments"]): + if isinstance(adjustment, dict): + adjustment.setdefault("id", f"proposal-{index+1}") + adjustment.setdefault("action", "review") + clean["warnings"] = clean.get("warnings", [])[:2] if isinstance(clean.get("warnings"), list) else [] + if training_format.get("fixed_interval"): + replacement = ( + "Arbeitsintervall unverändert lassen; nur die im Plan erlaubte Übungsstufe, das Gewicht, " + "den kontrollierten Bewegungsumfang oder die technische Qualität anpassen." + ) + for exercise in clean["exercise_updates"]: + if isinstance(exercise, dict) and _violates_fixed_interval(exercise.get("next_action")): + exercise["next_action"] = replacement + for adjustment in clean["plan_adjustments"]: + if isinstance(adjustment, dict): + combined = " ".join(str(adjustment.get(field) or "") for field in ("suggested_change", "manual_step")) + if _violates_fixed_interval(combined): + adjustment["suggested_change"] = replacement + adjustment["manual_step"] = "Im Trainingsplan-Modul nur Progressionsstufe oder Last anpassen; Tabata-Timer unverändert lassen." + return clean + + +def _run_progress_analysis(dataset: dict[str, Any], scope: str) -> dict[str, Any]: + model = os.environ.get("OPENAI_MODEL", "gpt-5.5").strip() or "gpt-5.5" + schema = { + "headline": "Kurzer Titel", + "summary": "Kompakte Einordnung in höchstens 4 Sätzen", + "data_quality": "Ein kurzer Satz zur Aussagekraft", + "metrics": [{"label": "Kennzahl", "value": "Wert", "detail": "kurze Einordnung"}], + "exercise_updates": [{ + "name": "Übungscluster", "trend": "up|stable|down|unclear", + "current_level": "dokumentierter Stand", "evidence": "knapper Beleg aus Referenzwerten und Rohdaten", + "next_action": "nächster planverträglicher Schritt", "criterion": "messbares Kriterium", + }], + "plan_adjustments": [{ + "id": "stabile kurze Vorschlags-ID", + "action": "change_progression|change_load|change_variant|keep|review", + "target": "Woche/Tag/Übung oder Block", + "target_exercise_id": "ID aus plan.days.blocks.exercises, wenn eindeutig", + "target_progression_id": "Progressions-ID, wenn eindeutig", + "target_step_id": "aktuelle oder vorgeschlagene Stufen-ID, wenn eindeutig", + "suggested_change": "konkreter Vorschlag", + "reason": "kurze Begründung", "manual_step": "was im Trainingsplan-Modul manuell zu ändern wäre", + "condition": "nur wenn/sonst beibehalten", + }], + "warnings": ["nur echte Auffälligkeiten"], + "conclusion": "ein knapper Abschlusssatz", + } + target = ( + "Bewerte genau diese Planwoche und formuliere höchstens drei konkrete, optionale Anpassungen für die nächste Woche." + if scope == "week" else + "Bewerte die Entwicklung über die vorhandenen Wochen und formuliere höchstens drei konkrete, optionale Anpassungen für die nächste Woche beziehungsweise den nächsten Planabschnitt." + ) + messages = [ + { + "role": "system", + "content": ( + "Du analysierst ein Trainingsprotokoll auf Deutsch. Der übergebene Trainingsplan ist bindend und muss die Empfehlungen bestimmen. " + "Verändere niemals eigenmächtig Sessionformat, Arbeitsintervall, Pausen, Rundenzahl, Satzanzahl oder Progressionslogik. " + "Bei Tabata oder anderen festen Intervallen darfst du insbesondere keine längeren Holds oder längere Arbeitszeiten empfehlen. " + "Progression erfolgt dann nur planverträglich, etwa über die vorgesehene schwierigere Variante, Gewicht, ROM, Tempo oder Technik. " + "Nutze ausschließlich die Daten im Input, erfinde keine Leistungen und gib keine medizinische Diagnose. " + "Eine Session mit Status stopped wurde vorzeitig beendet und darf nicht als abgeschlossen gewertet werden; vorhandene Teildaten dürfen vorsichtig berücksichtigt werden. " + "Nutze die deterministisch berechneten progression_normalization-Cluster als maßgebliche Zeitreihe und rechne die Faktoren nicht selbst neu. " + "Referenz-Reps vergleichen Varianten derselben dynamischen Bewegung; Referenzsekunden vergleichen ausschließlich isometrische Varianten. " + "kg·Reps und kg·s sind Lastvolumen und dürfen nicht mit Bodyweight-Referenzwerten vermischt werden. " + "Wähle die wichtigsten Cluster nach Planrelevanz und Datenpunkten. Bei mindestens vier brauchbaren Clustern sollst du vier bis sechs davon knapp bewerten. " + "Die Analyse soll trotzdem kompakt bleiben: maximal 4 Kennzahlen, 6 kurze Übungsupdates, 3 Plananpassungen und 2 Warnungen. " + "Plananpassungen sind nur Vorschläge zur manuellen Übernahme im Trainingsplan-Modul; behaupte nie, den Plan geändert zu haben. " + "Nutze für Vorschläge nach Möglichkeit die im Datensatz vorhandenen stabilen exercise_id-, progression_id- und step_id-Werte. " + + target + " Antworte ausschließlich als JSON im vorgegebenen Schema." + ), + }, + { + "role": "user", + "content": "Gewünschtes JSON-Schema:\n" + json.dumps(schema, ensure_ascii=False) + + "\n\nAnalyse-Datensatz:\n" + json.dumps(dataset, ensure_ascii=False), + }, + ] + result = _call_ai(messages, model) + training_format = dataset.get("plan", {}).get("training_format", {}) if isinstance(dataset.get("plan"), dict) else {} + return { + "model": model, + "request": { + "scope": scope, + "schema": schema, + "messages": messages, + "dataset": deepcopy(dataset), + }, + "result": _compact_ai_result(result, training_format), + } + + +def _week_record(plan: dict[str, Any], tracker: dict[str, Any], week: int) -> dict[str, Any]: + dataset = _week_dataset(plan, tracker, week) + source_hash = _stable_hash(dataset) + output = _run_progress_analysis(dataset, "week") + created_at = _utc_now() + return { + "id": f"week-{week:02d}", + "type": "week", "week": week, "created_at": created_at, + "analysis_kind": dataset.get("analysis_kind", "interim"), + "prompt_version": PROMPT_VERSION, "analysis_schema_version": ANALYSIS_SCHEMA_VERSION, + "normalization_version": PROGRESSION_NORMALIZATION_VERSION, + "model": output["model"], "source_hash": source_hash, + "sessions_considered": len(dataset["sessions"]), + "request": output["request"], + "response": output["result"], + "visuals": _local_visuals(plan, dataset["sessions"], overall=False), + } + + +def _overall_record(plan: dict[str, Any], tracker: dict[str, Any]) -> dict[str, Any]: + dataset = _overall_dataset(plan, tracker) + source_hash = _stable_hash(dataset) + output = _run_progress_analysis(dataset, "overall") + records = _session_records(plan, tracker) + weeks = sorted({item.get("week") for item in records}) + created_at = _utc_now() + return { + "id": "overall", + "type": "overall", "created_at": created_at, + "prompt_version": PROMPT_VERSION, "analysis_schema_version": ANALYSIS_SCHEMA_VERSION, + "normalization_version": PROGRESSION_NORMALIZATION_VERSION, + "model": output["model"], "source_hash": source_hash, + "sessions_considered": len(records), "weeks_considered": weeks, + "request": output["request"], + "response": output["result"], + "visuals": _local_visuals(plan, records, overall=True), + } + + +def _job_state(plan: dict[str, Any], job_id: str, **changes: Any) -> dict[str, Any]: + state = _read_analysis_state(plan) + if state.get("job_id") != job_id: + return state + now = datetime.now(timezone.utc) + state.update(changes) + state["heartbeat_at"] = now.isoformat(timespec="seconds") + state["lease_expires_at"] = (now + timedelta(seconds=_ANALYSIS_TIMEOUT_SECONDS)).isoformat(timespec="seconds") + return _write_analysis_state(plan, state) + + +def _analysis_heartbeat(plan_filename: str, job_id: str, stop_event: threading.Event) -> None: + while not stop_event.wait(_ANALYSIS_HEARTBEAT_SECONDS): + try: + path = _validated_plan_path(plan_filename) + if path is None: + return + plan = _normalize_plan(path) + state = _read_analysis_state(plan) + if state.get("job_id") != job_id or state.get("status") != "running": + return + _job_state(plan, job_id) + except Exception: + # Der Hauptworker entscheidet über Erfolg oder Fehler. Ein einzelner + # Heartbeat-Fehler darf den API-Aufruf nicht abbrechen. + pass + + +def _analysis_worker(plan_filename: str, job_id: str, scope: str, week: int | None) -> None: + heartbeat_stop = threading.Event() + heartbeat = threading.Thread( + target=_analysis_heartbeat, + args=(plan_filename, job_id, heartbeat_stop), + name=f"trainingstracker-heartbeat-{job_id}", daemon=True, + ) + heartbeat.start() + try: + path = _validated_plan_path(plan_filename) + if path is None: + raise ValueError("Trainingsplan nicht mehr vorhanden.") + plan = _normalize_plan(path) + tracker = _load_tracker(plan) + if scope == "week": + assert week is not None + _job_state(plan, job_id, message=f"Woche {week} wird analysiert …", step=0, total_steps=1) + record = _week_record(plan, tracker, week) + _archive_analysis_record(plan, record) + selection = f"week:{week}" + else: + catalog = _analysis_catalog(plan, tracker) + pending = list(catalog.get("overall", {}).get("pending_weeks", [])) + total = len(pending) + 1 + for index, pending_week in enumerate(pending, start=1): + _job_state( + plan, job_id, message=f"Woche {pending_week} wird aktualisiert …", + step=index - 1, total_steps=total, + ) + tracker = _load_tracker(plan) + record = _week_record(plan, tracker, pending_week) + _archive_analysis_record(plan, record) + _job_state(plan, job_id, message="Gesamtanalyse wird erstellt …", step=max(0, total - 1), total_steps=total) + tracker = _load_tracker(plan) + record = _overall_record(plan, tracker) + _archive_analysis_record(plan, record) + selection = "overall" + finished_total = 1 if scope == "week" else total + _job_state( + plan, job_id, status="done", message="Analyse abgeschlossen.", + selection=selection, step=finished_total, total_steps=finished_total, + finished_at=_utc_now(), error="", + ) + except Exception as exc: # noqa: BLE001 + try: + path = _validated_plan_path(plan_filename) + if path is not None: + plan = _normalize_plan(path) + _job_state( + plan, job_id, status="error", message="Analyse fehlgeschlagen.", + error=str(exc), finished_at=_utc_now(), + ) + except Exception: + pass + finally: + heartbeat_stop.set() + with _ANALYSIS_GUARD: + _ANALYSIS_THREADS.pop(plan_filename, None) + + +def _start_analysis(plan: dict[str, Any], tracker: dict[str, Any], scope: str, week: int | None) -> dict[str, Any]: + catalog = _analysis_catalog(plan, tracker) + if scope == "week": + entry = next((item for item in catalog["weeks"] if item["week"] == week), None) + if entry is None: + raise ValueError(f"Woche {week} enthält noch keine befüllten Sessiondaten.") + if entry["status"] == "current": + return {"cached": True, "selection": f"week:{week}"} + label = f"Woche {week}" + total_steps = 1 + elif scope == "overall": + if not catalog["weeks"]: + raise ValueError("Noch keine befüllten Sessiondaten für eine Analyse vorhanden.") + if catalog["overall"]["status"] == "current": + return {"cached": True, "selection": "overall"} + pending = catalog["overall"].get("pending_weeks", []) + label = "Gesamtanalyse" + total_steps = len(pending) + 1 + else: + raise ValueError("Unbekannter Analysebereich.") + + with _analysis_file_lock(plan["source_file"]): + current_state = _read_analysis_state(plan) + if current_state.get("status") == "running": + raise RuntimeError("Es läuft bereits eine Progressionsanalyse.") + job_id = datetime.now(timezone.utc).strftime("job-%Y%m%dT%H%M%S%fZ") + state = { + "status": "running", "job_id": job_id, "scope": scope, "week": week, + "label": label, "message": "Analyse wird vorbereitet …", "step": 0, + "total_steps": total_steps, "started_at": _utc_now(), "heartbeat_at": _utc_now(), + "lease_expires_at": (datetime.now(timezone.utc) + timedelta(seconds=_ANALYSIS_TIMEOUT_SECONDS)).isoformat(timespec="seconds"), + "owner_pid": os.getpid(), "error": "", + } + _write_analysis_state(plan, state) + thread = threading.Thread( + target=_analysis_worker, + args=(plan["source_file"], job_id, scope, week), + name=f"trainingstracker-{job_id}", daemon=True, + ) + with _ANALYSIS_GUARD: + _ANALYSIS_THREADS[plan["source_file"]] = thread + thread.start() + return {"cached": False, "job": state} + + +# --------------------------------------------------------------------------- +# HTTP +# --------------------------------------------------------------------------- +@app.after_request +def no_cache(response): + if request.path.startswith("/api/"): + response.headers["Cache-Control"] = "no-store" + return response + + +@app.route("/") +def index(): + index_path = STATIC_DIR / "index.html" + source = index_path.read_text(encoding="utf-8") + base = request.script_root.rstrip("/") + source = source.replace("__APP_BASE__", base) + return Response(source, mimetype="text/html") + + +@app.route("/api/health") +def api_health(): + return jsonify({ + "ok": True, + "plans_dir": str(PLANS_DIR), + "sessions_dir": str(SESSIONS_DIR), + "analyses_dir": str(ANALYSES_DIR), + "plans_dir_exists": PLANS_DIR.is_dir(), + "openai_configured": _openai_ready(), + "versions": { + "plan_schema": PLAN_SCHEMA_VERSION, + "training_contract": CONTRACT_VERSION, + "tracker_schema": TRACKER_SCHEMA_VERSION, + "result_data": RESULT_DATA_VERSION, + "prompt": PROMPT_VERSION, + "analysis_schema": ANALYSIS_SCHEMA_VERSION, + "normalization": PROGRESSION_NORMALIZATION_VERSION, + }, + }) + + +@app.route("/api/plans") +def api_plans(): + settings = _read_settings() + selected = settings.get("selected_plan") + plans = [] + for filename, path in _known_plan_files().items(): + try: + plan = _normalize_plan(path) + tracker = _load_tracker(plan) + completed = sum( + 1 for session in tracker.get("sessions", {}).values() + if isinstance(session, dict) and session.get("status") == "completed" + ) + plans.append({ + "id": filename, + "filename": filename, + "name": plan["name"], + "title": plan["title"], + "subtitle": plan["subtitle"], + "weeks": plan["weeks"], + "days": len(plan["days"]), + "tracked": _tracker_path(filename).exists(), + "start_date": tracker.get("profile", {}).get("start_date", ""), + "completed_sessions": completed, + "source_changed": bool(tracker.get("source_changed")), + "plan_id": plan.get("plan_id"), + "published_revision": plan.get("published_revision"), + "tracker_revision": tracker.get("revision", 1), + }) + except Exception: + continue + if selected not in {item["id"] for item in plans}: + selected = plans[0]["id"] if plans else None + return jsonify({"plans": plans, "selected": selected}) + + +@app.route("/api/plans//select", methods=["POST"]) +def api_select_plan(plan_id: str): + if _validated_plan_path(plan_id) is None: + return jsonify({"error": "Trainingsplan nicht gefunden"}), 404 + _save_selected(plan_id) + return jsonify({"ok": True, "selected": plan_id}) + + +@app.route("/api/plans/") +def api_plan(plan_id: str): + path = _validated_plan_path(plan_id) + if path is None: + return jsonify({"error": "Trainingsplan nicht gefunden"}), 404 + try: + plan = _normalize_plan(path) + tracker = _load_tracker(plan) + # Nur für die UI zusammensetzen; diese Felder existieren nicht in der Sessiondatei. + tracker["analysis_cache"] = _read_analysis_cache(plan) + tracker["analysis_state"] = _read_analysis_state(plan) + except Exception as exc: + return jsonify({"error": f"Plan konnte nicht gelesen werden: {exc}"}), 400 + return jsonify({"plan": plan, "tracker": tracker, "analysis_catalog": _analysis_catalog(plan, tracker), "equivalence_guide": _public_equivalence_guide(plan), "capabilities": {"openai": _openai_ready()}}) + + +@app.route("/api/plans//tracker", methods=["PUT"]) +def api_save_tracker(plan_id: str): + path = _validated_plan_path(plan_id) + if path is None: + return jsonify({"error": "Trainingsplan nicht gefunden"}), 404 + if request.content_length and request.content_length > 5 * 1024 * 1024: + return jsonify({"error": "Tracker-Daten sind zu groß"}), 413 + try: + plan = _normalize_plan(path) + payload = request.get_json(force=True) + saved = _save_tracker(plan, payload) + except RuntimeError as exc: + if str(exc).startswith("revision_conflict:"): + current = str(exc).split(":", 1)[1] + return jsonify({"error": "Die Sessiondaten wurden in einem anderen Tab oder Gerät geändert.", "code": "revision_conflict", "current_revision": int(current)}), 409 + return jsonify({"error": str(exc)}), 409 + except (ValueError, TypeError) as exc: + return jsonify({"error": str(exc)}), 400 + return jsonify({"ok": True, "updated_at": saved["updated_at"], "revision": saved.get("revision", 1), "analysis_catalog": _analysis_catalog(plan, saved), "analysis_state": _read_analysis_state(plan)}) + + +@app.route("/api/plans//tracker/session", methods=["PATCH"]) +def api_patch_tracker_session(plan_id: str): + path = _validated_plan_path(plan_id) + if path is None: + return jsonify({"error": "Trainingsplan nicht gefunden"}), 404 + try: + plan = _normalize_plan(path) + saved = _patch_tracker(plan, request.get_json(force=True)) + except RuntimeError as exc: + if str(exc).startswith("revision_conflict:"): + current = str(exc).split(":", 1)[1] + return jsonify({"error": "Die Session wurde in einem anderen Tab oder Gerät geändert.", "code": "revision_conflict", "current_revision": int(current)}), 409 + return jsonify({"error": str(exc)}), 409 + except (ValueError, TypeError) as exc: + return jsonify({"error": str(exc)}), 400 + return jsonify({ + "ok": True, "updated_at": saved["updated_at"], "revision": saved.get("revision", 1), + "analysis_catalog": _analysis_catalog(plan, saved), "analysis_state": _read_analysis_state(plan), + }) + + +@app.route("/api/plans//analysis", methods=["POST"]) +def api_progress_analysis(plan_id: str): + """Startet auf expliziten Button-Druck eine Wochen- oder Gesamtanalyse.""" + path = _validated_plan_path(plan_id) + if path is None: + return jsonify({"error": "Trainingsplan nicht gefunden"}), 404 + if not _openai_ready(): + return jsonify({"error": "OpenAI-Key oder Modell fehlt – siehe boehmitools-Einstellungen."}), 400 + try: + payload = request.get_json(silent=True) or {} + scope = str(payload.get("scope") or "overall") + week = _as_int(payload.get("week"), 0) if scope == "week" else None + if scope == "week" and week <= 0: + raise ValueError("Eine gültige Woche muss ausgewählt werden.") + plan = _normalize_plan(path) + tracker = _load_tracker(plan) + outcome = _start_analysis(plan, tracker, scope, week) + latest = _load_tracker(plan) + catalog = _analysis_catalog(plan, latest) + analysis_state = _read_analysis_state(plan) + analysis_cache = _read_analysis_cache(plan) + except RuntimeError as exc: + return jsonify({"error": str(exc), "analysis_state": _read_analysis_state(plan)}), 409 + except (ValueError, TypeError) as exc: + return jsonify({"error": str(exc)}), 400 + if outcome.get("cached"): + return jsonify({ + "ok": True, "cached": True, "selection": outcome.get("selection"), + "analysis_state": analysis_state, + "analysis_cache": analysis_cache, + "analysis_catalog": catalog, + }) + return jsonify({ + "ok": True, "cached": False, "job": outcome.get("job"), + "analysis_state": analysis_state, + "analysis_catalog": catalog, + }), 202 + + +@app.route("/api/plans//analysis/status") +def api_progress_analysis_status(plan_id: str): + path = _validated_plan_path(plan_id) + if path is None: + return jsonify({"error": "Trainingsplan nicht gefunden"}), 404 + try: + plan = _normalize_plan(path) + tracker = _load_tracker(plan) + catalog = _analysis_catalog(plan, tracker) + except Exception as exc: # noqa: BLE001 + return jsonify({"error": f"Analysestatus konnte nicht gelesen werden: {exc}"}), 400 + return jsonify({ + "analysis_state": _read_analysis_state(plan), + "analysis_cache": _read_analysis_cache(plan), + "analysis_catalog": catalog, + "updated_at": tracker.get("updated_at", ""), + }) + + +@app.route("/api/plans//tracker/export") +def api_export_tracker(plan_id: str): + path = _validated_plan_path(plan_id) + if path is None: + return jsonify({"error": "Trainingsplan nicht gefunden"}), 404 + tracker_path = _tracker_path(path.name) + if not tracker_path.exists(): + plan = _normalize_plan(path) + _atomic_json_write(tracker_path, _default_tracker(plan)) + return send_file( + tracker_path, + mimetype="application/json", + as_attachment=True, + download_name=f"tracking-{path.name}", + ) + + +if __name__ == "__main__": + app.run( + host=os.environ.get("HOST", "0.0.0.0"), + port=int(os.environ.get("PORT", "8081")), + debug=os.environ.get("DEBUG") == "1", + ) diff --git a/plugins/trainingstracker/backend.py b/plugins/trainingstracker/backend.py new file mode 100644 index 0000000..8f2464b --- /dev/null +++ b/plugins/trainingstracker/backend.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +"""Plugin-Adapter für den Trainings-Session-Tracker.""" +from __future__ import annotations + +import os +from pathlib import Path + +from a2wsgi import WSGIMiddleware +from core.loader import load_module + + +def create_app(ctx): + """Hängt die Flask-App unter /plugins/trainingstracker ein. + + Es werden ausschließlich Pfade relativ zum boehmitools-Datenordner benutzt: + data/trainingsplan/plans (nur lesen) + data/trainingstracker/sessions (Sessiondaten schreiben) + data/trainingstracker/analyses (Prompts, Antworten, Cache und Jobstatus) + """ + tracker_data_dir = Path(ctx.data_dir) + plans_dir = tracker_data_dir.parent / "trainingsplan" / "plans" + + previous_data = os.environ.get("TRAININGSTRACKER_DATA_DIR") + previous_plans = os.environ.get("TRAININGSTRACKER_PLANS_DIR") + os.environ["TRAININGSTRACKER_DATA_DIR"] = str(tracker_data_dir) + os.environ["TRAININGSTRACKER_PLANS_DIR"] = str(plans_dir) + try: + module = load_module(ctx.path("app.py"), f"btp_{ctx.id}_app") + finally: + if previous_data is None: + os.environ.pop("TRAININGSTRACKER_DATA_DIR", None) + else: + os.environ["TRAININGSTRACKER_DATA_DIR"] = previous_data + if previous_plans is None: + os.environ.pop("TRAININGSTRACKER_PLANS_DIR", None) + else: + os.environ["TRAININGSTRACKER_PLANS_DIR"] = previous_plans + + return WSGIMiddleware(module.app) diff --git a/plugins/trainingstracker/contract.py b/plugins/trainingstracker/contract.py new file mode 100644 index 0000000..dec8ec1 --- /dev/null +++ b/plugins/trainingstracker/contract.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +"""Shared constants for the versioned plan/tracker contract.""" +PLAN_SCHEMA_VERSION = 3 +CONTRACT_VERSION = 2 +TRACKER_SCHEMA_VERSION = 7 +RESULT_DATA_VERSION = 2 +PROMPT_VERSION = 5 +ANALYSIS_SCHEMA_VERSION = 3 +PROGRESSION_NORMALIZATION_VERSION = 5 diff --git a/plugins/trainingstracker/plugin.json b/plugins/trainingstracker/plugin.json new file mode 100644 index 0000000..6dc2302 --- /dev/null +++ b/plugins/trainingstracker/plugin.json @@ -0,0 +1,37 @@ +{ + "id": "trainingstracker", + "name": "Trainings-Session-Tracker", + "summary": "Sessions mobil erfassen und planbewusst über Wochen analysieren", + "description": "Mobiler Tracker für veröffentlichte Trainingspläne mit stabilen Übungs-IDs, progressionsabhängigen Ergebnisfeldern, Session-Patches, Wochenabschluss, Variantenclustern und genau einer überschreibbaren KI-Analyse pro Woche beziehungsweise Gesamtplan.", + "icon": "📱", + "category": "Planung", + "version": "2.0.0", + "entrypoint": "backend:create_app", + "order": 11, + "requires": [], + "plugin_dependencies": { + "trainingsplan": ">=2.0.0" + }, + "contract": { + "plan_schema": 3, + "training_contract": 2, + "tracker_schema": 7, + "result_data": 2 + }, + "features": [ + "Stabile Plan-, Übungs-, Progressions- und Stufen-IDs", + "Alte Positionsschlüssel werden automatisch migriert", + "Session-Patches mit Revisionskonfliktschutz", + "Planrevision wird pro Session gespeichert", + "Erledigt, teilweise und übersprungen mit Grund", + "Expliziter Wochenabschluss mit Zwischen- oder Abschlussanalyse", + "Genau eine Wochenanalyse je Woche und eine Gesamtanalyse, jeweils überschreibbar", + "Keine Analysehistorie", + "Persistente Analysejobs mit Dateisperre, Heartbeat und Lease", + "Planbezogene Übungsbibliothek und Variantencluster", + "Strukturierte KI-Vorschläge für den Planeditor", + "Letzter Tab, Woche, Tag und Auswahl bleiben erhalten", + "Trainingsplan bleibt read-only" + ], + "docs": "README.md" +} diff --git a/plugins/trainingstracker/session_store.py b/plugins/trainingstracker/session_store.py new file mode 100644 index 0000000..422e61d --- /dev/null +++ b/plugins/trainingstracker/session_store.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +from __future__ import annotations +from copy import deepcopy +from typing import Any + +VALID_SESSION_STATUSES = {"planned", "in_progress", "stopped", "completed"} +VALID_ITEM_STATUSES = {"planned", "completed", "partial", "skipped"} + + +def merge_session_patch(existing: dict[str, Any], payload: dict[str, Any], now: str) -> dict[str, Any]: + current_revision = int(existing.get("revision") or 1) + expected = payload.get("expected_revision") + if expected is not None and int(expected) != current_revision: + raise RuntimeError(f"revision_conflict:{current_revision}") + result = deepcopy(existing) + profile = payload.get("profile") + if isinstance(profile, dict): + result["profile"] = deepcopy(profile) + week_statuses = payload.get("week_statuses") + if isinstance(week_statuses, dict): + result["week_statuses"] = deepcopy(week_statuses) + key = str(payload.get("session_key") or "") + session = payload.get("session") + if key and isinstance(session, dict): + result.setdefault("sessions", {})[key] = deepcopy(session) + result["revision"] = current_revision + 1 + result["updated_at"] = now + return result diff --git a/plugins/trainingstracker/static/index.html b/plugins/trainingstracker/static/index.html new file mode 100644 index 0000000..4f34840 --- /dev/null +++ b/plugins/trainingstracker/static/index.html @@ -0,0 +1,1634 @@ + + + + + + + + + Trainings-Session-Tracker + + + + + +
+
+ Aktiver Trainingsplan + Trainingsplan wird geladen +
+
+ Bereit + +
+
+ + + +
+ +
+ + + +
+ + + + + + + diff --git a/plugins/trainingstracker/tests/test_app.py b/plugins/trainingstracker/tests/test_app.py new file mode 100644 index 0000000..cca414d --- /dev/null +++ b/plugins/trainingstracker/tests/test_app.py @@ -0,0 +1,460 @@ +from __future__ import annotations + +import importlib.util +import json +import os +import time +import uuid +from pathlib import Path + + +def load_module(module_path: Path, data_dir: Path, plans_dir: Path): + try: + import flask # noqa: F401 + except ModuleNotFoundError: + import pytest + pytest.skip("Flask ist in dieser isolierten Testumgebung nicht installiert") + os.environ["TRAININGSTRACKER_DATA_DIR"] = str(data_dir) + os.environ["TRAININGSTRACKER_PLANS_DIR"] = str(plans_dir) + name = f"tracker_test_app_{uuid.uuid4().hex}" + spec = importlib.util.spec_from_file_location(name, module_path) + module = importlib.util.module_from_spec(spec) + assert spec.loader + spec.loader.exec_module(module) + return module + + +def sample_plan() -> dict: + return { + "name": "Testplan", + "config": { + "meta": {"title": "Test", "weeks": 2}, + "front": { + "session_how_body": "Tabata: 8× (20 Sek Arbeit / 10 Sek Pause).", + "timer_note": "20/10 unverändert", + "goals_note": "Stufen sauber steigern", + }, + "days": [ + { + "num": 1, + "focus": "Ganzkörper", + "rotations": [{ + "label": "Rotation A — 3 Sätze", + "exercises": [ + {"name": "Squat", "key": "sq", "cue": "20 s sauber"}, + {"name": "Push-up", "key": "push", "cue": "20 s stabil"}, + ], + }], + }, + { + "num": 3, + "focus": "Ganzkörper B", + "rotations": [{ + "label": "Rotation A — 3 Sätze", + "exercises": [ + {"name": "Squat", "key": "sq", "cue": "20 s sauber"}, + {"name": "Push-up", "key": "push", "cue": "20 s stabil"}, + ], + }], + }, + ], + "prepost": { + "1": {"warmup": "Kreisen · Squat", "cooldown": "Gehen", "stretch": "Wade"}, + "3": {"warmup": "Kreisen", "cooldown": "Gehen", "stretch": "Brust"}, + }, + "phases": {"items": [{"name": "Phase & Technik", "params": "Sauber & kontrolliert", "weeks": 2}]}, + "stages": { + "sq": {"name": "Squat", "steps": ["assistiert", "voll", "Goblet schwerer"]}, + "push": {"name": "Push-up", "steps": ["erhöht", "voll"]}, + }, + }, + } + + +def wait_for_analysis(client, plan_id: str, timeout: float = 3.0): + deadline = time.time() + timeout + while time.time() < deadline: + payload = client.get(f"/api/plans/{plan_id}/analysis/status").get_json() + if payload["analysis_state"]["status"] != "running": + return payload + time.sleep(0.02) + raise AssertionError("Analyse blieb im Test zu lange aktiv") + + +def test_source_is_read_only_and_session_file_strips_all_analysis_fields(tmp_path: Path): + plans = tmp_path / "data" / "trainingsplan" / "plans" + tracker_data = tmp_path / "data" / "trainingstracker" + plans.mkdir(parents=True) + training = plans / "training.json" + training.write_text(json.dumps(sample_plan()), encoding="utf-8") + original = training.read_bytes() + (plans / "food.json").write_text(json.dumps({"name": "Essen", "config": {"type": "recipe", "days": []}}), encoding="utf-8") + + module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans) + client = module.app.test_client() + result = client.get("/api/plans").get_json() + assert [item["id"] for item in result["plans"]] == ["training.json"] + + detail = client.get("/api/plans/training.json").get_json() + assert detail["plan"]["phases"][0]["name"] == "Phase & Technik" + assert detail["plan"]["training_format"]["is_tabata"] is True + tracker = detail["tracker"] + tracker["sessions"] = {"w01-d01": {"status": "completed", "note": "lief gut", "items": {}}} + tracker["analysis_cache"] = {"weeks": {"1": {"bad": True}}} + tracker["analysis_state"] = {"status": "running"} + tracker["analyses"] = [{"legacy": True}] + assert client.put("/api/plans/training.json/tracker", json=tracker).status_code == 200 + + raw = json.loads((tracker_data / "sessions" / "training.json").read_text(encoding="utf-8")) + assert raw["version"] == 7 + assert "analysis_cache" not in raw + assert "analysis_state" not in raw + assert "analyses" not in raw + assert raw["sessions"]["w01-d01"]["note"] == "lief gut" + assert training.read_bytes() == original + + +def test_week_archive_contains_prompt_dataset_response_and_cache_uses_index(tmp_path: Path, monkeypatch): + plans = tmp_path / "data" / "trainingsplan" / "plans" + tracker_data = tmp_path / "data" / "trainingstracker" + plans.mkdir(parents=True) + training = plans / "training.json" + training.write_text(json.dumps(sample_plan()), encoding="utf-8") + + monkeypatch.setenv("OPENAI_API_KEY", "test-key") + monkeypatch.setenv("OPENAI_MODEL", "test-model") + module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans) + client = module.app.test_client() + + tracker = client.get("/api/plans/training.json").get_json()["tracker"] + tracker["profile"]["start_date"] = "2026-07-27" + tracker["sessions"] = { + "w01-d01": {"status": "completed", "note": "Woche eins", "items": { + "d1-r0-e0": {"done": True, "progression_id": "sq", "exercise_name": "Squat", "progression": "assistiert", "result": "8 / 7", "note": "sauber"} + }}, + "w02-d01": {"status": "completed", "note": "Woche zwei", "items": { + "d1-r0-e0": {"done": True, "progression_id": "sq", "exercise_name": "Squat", "progression": "voll", "result": "8 / 8", "note": "gut"} + }}, + } + assert client.put("/api/plans/training.json/tracker", json=tracker).status_code == 200 + + datasets = [] + + def fake_call(messages, model): + dataset = json.loads(messages[1]["content"].split("Analyse-Datensatz:\n", 1)[1]) + datasets.append(dataset) + return { + "headline": "Kompakt", "summary": "Kurze Analyse.", "data_quality": "Ausreichend.", + "metrics": [{"label": "Sessions", "value": "1", "detail": "Woche"}], + "exercise_updates": [{ + "name": "Squat", "trend": "up", "current_level": "voll", "evidence": "8 / 8", + "next_action": "Haltezeit auf 30 Sekunden verlängern", "criterion": "sauber", + }], + "plan_adjustments": [{ + "target": "Squat", "suggested_change": "Arbeitsintervall auf 30 Sekunden erhöhen", + "reason": "mehr Belastung", "manual_step": "Timer ändern", "condition": "wenn sauber", + }], + "warnings": [], "conclusion": "Weiter.", + } + + module._call_ai = fake_call + + started = client.post("/api/plans/training.json/analysis", json={"scope": "week", "week": 1}) + assert started.status_code == 202 + status = wait_for_analysis(client, "training.json") + assert status["analysis_state"]["status"] == "done" + assert len(datasets) == 1 + week_record = status["analysis_cache"]["weeks"]["1"] + assert "unverändert" in week_record["result"]["exercise_updates"][0]["next_action"] + + plan_dir = tracker_data / "analyses" / "training.json" + archive_files = list(plan_dir.glob("week-01.json")) + assert len(archive_files) == 1 + archive = json.loads(archive_files[0].read_text(encoding="utf-8")) + assert archive["request"]["scope"] == "week" + assert archive["request"]["dataset"]["analysis_type"] == "week" + assert archive["request"]["messages"] + assert archive["response"]["headline"] == "Kompakt" + index = json.loads((plan_dir / "index.json").read_text(encoding="utf-8")) + assert index["latest"]["weeks"]["1"] == archive_files[0].name + assert "history" not in index + assert (plan_dir / "state.json").exists() + + session_raw = json.loads((tracker_data / "sessions" / "training.json").read_text(encoding="utf-8")) + assert "analysis_cache" not in session_raw and "analysis_state" not in session_raw + + cached = client.post("/api/plans/training.json/analysis", json={"scope": "week", "week": 1}) + assert cached.status_code == 200 + assert cached.get_json()["cached"] is True + assert len(datasets) == 1 + + overall_start = client.post("/api/plans/training.json/analysis", json={"scope": "overall"}) + assert overall_start.status_code == 202 + final = wait_for_analysis(client, "training.json") + assert final["analysis_state"]["status"] == "done" + assert len(datasets) == 3 + overall_dataset = datasets[-1] + assert overall_dataset["analysis_type"] == "overall" + assert "weekly_analyses" in overall_dataset + assert "sessions" not in overall_dataset + assert len(list(plan_dir.glob("overall.json"))) == 1 + + +def test_frontend_persists_navigation_and_polls_running_job(): + source = (Path(__file__).parents[1] / "static" / "index.html").read_text(encoding="utf-8") + assert "localStorage.setItem" in source + assert "analysisSelection" in source + assert "analysis/status" in source + assert "state.tracker.analysis_state.status === 'running'" in source + + +def test_stopped_session_is_valid_and_included_in_analysis_records(tmp_path: Path): + plans = tmp_path / "data" / "trainingsplan" / "plans" + tracker_data = tmp_path / "data" / "trainingstracker" + plans.mkdir(parents=True) + training = plans / "training.json" + training.write_text(json.dumps(sample_plan()), encoding="utf-8") + + module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans) + client = module.app.test_client() + tracker = client.get("/api/plans/training.json").get_json()["tracker"] + tracker["sessions"] = { + "w01-d01": { + "status": "stopped", + "started_at": "2026-07-27T08:00:00+00:00", + "stopped_at": "2026-07-27T08:20:00+00:00", + "completed_at": "", + "items": {}, + "note": "Vorzeitig beendet", + } + } + assert client.put("/api/plans/training.json/tracker", json=tracker).status_code == 200 + plan = module._normalize_plan(training) + saved = module._load_tracker(plan) + records = module._session_records(plan, saved) + assert records[0]["status"] == "stopped" + assert records[0]["stopped_at"] == "2026-07-27T08:20:00+00:00" + assert module._local_metrics(plan, records, week=1)["completed_sessions"] == 0 + + +def test_frontend_supports_stop_continue_and_confirmed_reset(): + source = (Path(__file__).parents[1] / "static" / "index.html").read_text(encoding="utf-8") + assert 'id="stopSession"' in source + assert 'id="resetSession"' in source + assert "session.status = 'stopped'" in source + assert "Session fortsetzen" in source + assert "window.confirm" in source + assert "state.tracker.sessions[sessionKey(state.week, state.dayNum)] = blankSession()" in source + + +def test_variant_clusters_create_reference_reps_and_reference_seconds(tmp_path: Path): + plans = tmp_path / "data" / "trainingsplan" / "plans" + tracker_data = tmp_path / "data" / "trainingstracker" + plans.mkdir(parents=True) + training = plans / "training.json" + training.write_text(json.dumps(sample_plan()), encoding="utf-8") + module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans) + plan = module._normalize_plan(training) + records = [ + {"week": 1, "day": 1, "session_key": "w01-d01", "items": [ + {"exercise": "Push-up", "progression_id": "push", "progression": "Inkline (hoch)", "result": "10"}, + {"exercise": "Plank", "progression_id": "plank", "progression": "auf Knien 15–20 s", "result": "6x20 s"}, + ]}, + {"week": 1, "day": 3, "session_key": "w01-d03", "items": [ + {"exercise": "Push-up", "progression_id": "push", "progression": "Inkline mittel", "result": "5"}, + {"exercise": "Plank", "progression_id": "plank", "progression": "voll 20–30 s", "result": "3x20 s"}, + ]}, + {"week": 2, "day": 1, "session_key": "w02-d01", "items": [ + {"exercise": "Push-up", "progression_id": "push", "progression": "volle Push-ups", "result": "1"}, + ]}, + ] + series = module._cluster_progression_series(plan, records, limit=20) + push = next(row for row in series if row["id"] == "push_up") + assert [point["value"] for point in push["points"]] == [1.0, 1.0, 1.0] + plank = next(row for row in series if row["id"] == "plank") + assert plank["unit"] == "Referenzsekunden" + assert plank["points"][0]["value"] == 78.0 # 6×20 s × 0,65 + assert plank["points"][1]["value"] == 60.0 # 3×20 s × 1,00 + + +def test_equivalence_guide_and_mobile_faq_are_exposed(tmp_path: Path): + plans = tmp_path / "data" / "trainingsplan" / "plans" + tracker_data = tmp_path / "data" / "trainingstracker" + plans.mkdir(parents=True) + training = plans / "training.json" + training.write_text(json.dumps(sample_plan()), encoding="utf-8") + module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans) + plan = module._normalize_plan(training) + guide = module._public_equivalence_guide(plan) + push = next(table for table in guide["tables"] if table["id"] == "push_up") + examples = {row["label"]: row["example"] for row in push["variants"]} + assert examples["Incline hoch, ca. 60 cm"].startswith("10 Reps") + assert examples["Incline mittel"].startswith("5 Reps") + assert examples["Voller Push-up"].startswith("1 Rep") + + source = (Path(__file__).parents[1] / "static" / "index.html").read_text(encoding="utf-8") + assert 'data-view="faq"' in source + assert 'id="faqView"' in source + assert "renderFAQ()" in source + assert "grid-template-columns: minmax(0,1fr) auto" in source + assert ".history-item button { width: 100%; min-width: 0;" in source + assert "overflow-x: hidden" in source + + + +def test_plan_infers_structured_result_schemas(tmp_path: Path): + plans = tmp_path / "data" / "trainingsplan" / "plans" + tracker_data = tmp_path / "data" / "trainingstracker" + plans.mkdir(parents=True) + plan_payload = sample_plan() + plan_payload["config"]["days"][0]["rotations"][0]["exercises"] = [ + {"name": "KB Floor Press", "key": "press", "cue": "sauber drücken"}, + {"name": "Side Plank (Wechsel)", "key": "side", "cue": "links / rechts halten"}, + {"name": "Einarm-Rudern (KB)", "key": "row", "cue": "DG1 links / DG2 rechts"}, + {"name": "Regeneration prüfen", "key": "", "cue": "Schlaf und Gelenke prüfen"}, + ] + plan_payload["config"]["stages"].update({ + "press": {"name": "Press", "steps": ["leicht", "schwerer"]}, + "side": {"name": "Side Plank", "steps": ["auf Knien", "voll, länger"]}, + "row": {"name": "Row", "steps": ["leicht", "mehr"]}, + }) + training = plans / "training.json" + training.write_text(json.dumps(plan_payload), encoding="utf-8") + + module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans) + plan = module._normalize_plan(training) + exercises = {exercise["name"]: exercise for exercise in plan["days"][0]["rotations"][0]["exercises"]} + + press = exercises["KB Floor Press"]["result_schema"] + assert press["mode"] == "reps" and press["weight_mode"] == "required" + assert press["laterality"] == "bilateral" and press["locked_sets"] is True + + side = exercises["Side Plank (Wechsel)"]["result_schema"] + assert side["mode"] == "seconds" and side["laterality"] == "unilateral" + assert side["sides_mode"] == "separate" + + row = exercises["Einarm-Rudern (KB)"]["result_schema"] + assert row["mode"] == "reps" and row["weight_mode"] == "required" + assert row["laterality"] == "unilateral" and row["sides_mode"] == "separate" + + recovery = exercises["Regeneration prüfen"]["result_schema"] + assert recovery["mode"] == "none" + + +def test_structured_result_is_saved_canonically_and_used_by_analysis(tmp_path: Path): + plans = tmp_path / "data" / "trainingsplan" / "plans" + tracker_data = tmp_path / "data" / "trainingstracker" + plans.mkdir(parents=True) + training = plans / "training.json" + training.write_text(json.dumps(sample_plan()), encoding="utf-8") + + module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans) + client = module.app.test_client() + tracker = client.get("/api/plans/training.json").get_json()["tracker"] + tracker["sessions"] = { + "w01-d01": { + "status": "completed", + "items": { + "d1-r0-e0": { + "done": True, + "exercise_name": "Einarm-Rudern (KB)", + "progression_id": "row", + "progression": "leicht", + "result": "dieser Alttext wird ersetzt", + "result_data": { + "version": 1, + "mode": "reps", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "weight_kg": "4,0", + "values": [], + "left_values": [9, 8, 8], + "right_values": [9, 8, 9], + }, + } + }, + } + } + assert client.put("/api/plans/training.json/tracker", json=tracker).status_code == 200 + + raw = json.loads((tracker_data / "sessions" / "training.json").read_text(encoding="utf-8")) + item = raw["sessions"]["w01-d01"]["items"]["d1-r0-e0"] + assert item["result"] == "4 kg · L 9/8/8 · R 9/8/9 Reps" + assert item["result_data"]["weight_kg"] == 4.0 + assert item["result_data"]["left_values"] == [9.0, 8.0, 8.0] + + measure = module._extract_training_measure(item) + assert measure["mode"] == "reps" + assert measure["total"] == 51.0 + assert measure["weight_kg"] == 4.0 + assert measure["structured"] is True + + +def test_frontend_uses_exercise_specific_result_editor_and_legacy_fallback(): + source = (Path(__file__).parents[1] / "static" / "index.html").read_text(encoding="utf-8") + assert "Ergebnis heute" in source + assert "data-result-value" in source + assert "data-result-weight" in source + assert "data-result-sides" in source + assert "data-result-weight-mode" in source + assert "parseLegacyResultData" in source + assert "result_data" in source + assert "Bitte mindestens einen Satz bzw. ein Intervall eintragen" in source + assert "data-item-result" not in source + + +def test_explicit_training_format_and_stage_result_schemas(tmp_path: Path): + plans = tmp_path / "data" / "trainingsplan" / "plans" + tracker_data = tmp_path / "data" / "trainingstracker" + plans.mkdir(parents=True) + payload = sample_plan() + payload["config"]["training_format"] = { + "mode": "tabata", "fixed_interval": True, + "work_seconds": 20, "rest_seconds": 10, "rounds": 8, + } + payload["config"]["days"][0]["rotations"][0]["exercises"][0]["result_schema"] = { + "mode": "auto", "weight_mode": "optional", "laterality": "bilateral" + } + payload["config"]["stages"]["sq"] = { + "name": "Squat-Progression", + "steps": ["Deep Squat Hold", "Negative Squats", "Goblet Squat"], + "result_schemas": [ + {"mode": "seconds", "weight_mode": "optional", "laterality": "bilateral"}, + {"mode": "reps", "weight_mode": "none", "laterality": "bilateral"}, + {"mode": "reps", "weight_mode": "required", "laterality": "bilateral"}, + ], + } + training = plans / "training.json" + training.write_text(json.dumps(payload), encoding="utf-8") + + module = load_module(Path(__file__).parents[1] / "app.py", tracker_data, plans) + plan = module._normalize_plan(training) + assert plan["training_format"]["source"] == "plan" + assert plan["training_format"]["mode"] == "tabata" + assert plan["training_format"]["rounds"] == 8 + assert plan["training_format"]["work_seconds"] == 20 + assert plan["stages"]["sq"]["result_schemas"][0]["mode"] == "seconds" + assert plan["stages"]["sq"]["result_schemas"][1]["mode"] == "reps" + assert plan["stages"]["sq"]["result_schemas"][2]["weight_mode"] == "required" + + source = (Path(__file__).parents[1] / "static" / "index.html").read_text(encoding="utf-8") + assert "progressionResultSchema" in source + assert "durch Progressionsstufe vorgegeben" in source + + +def test_analysis_store_uses_one_deterministic_file_per_scope(tmp_path: Path): + import sys + sys.path.insert(0, str(Path(__file__).parents[1])) + from analysis_store import current_filename + assert current_filename({"type": "week", "week": 3}) == "week-03.json" + assert current_filename({"type": "overall"}) == "overall.json" + + +def test_frontend_contains_explicit_week_status_and_item_skip_states(): + source = (Path(__file__).parents[1] / "static" / "index.html").read_text(encoding="utf-8") + assert "Woche abschließen" in source + assert "Woche wieder öffnen" in source + assert "Übersprungen" in source + assert "Teilweise" in source + assert "eine neue analyse überschreibt die vorherige" in source.lower() diff --git a/plugins/trainingstracker/tests/test_stability.py b/plugins/trainingstracker/tests/test_stability.py new file mode 100644 index 0000000..678836c --- /dev/null +++ b/plugins/trainingstracker/tests/test_stability.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +import json +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).parents[1])) + +from analysis_store import cleanup_legacy_files, current_filename +from session_store import merge_session_patch + + +def test_session_patch_preserves_other_sessions_and_detects_conflicts(): + existing = { + "revision": 4, + "profile": {"start_date": "2026-01-01"}, + "sessions": {"w01-d01": {"status": "completed"}, "w01-d02": {"status": "planned"}}, + } + merged = merge_session_patch(existing, { + "expected_revision": 4, + "session_key": "w01-d02", + "session": {"status": "stopped"}, + }, "2026-01-02T00:00:00Z") + assert merged["revision"] == 5 + assert merged["sessions"]["w01-d01"]["status"] == "completed" + assert merged["sessions"]["w01-d02"]["status"] == "stopped" + try: + merge_session_patch(merged, {"expected_revision": 4}, "2026-01-02T00:00:01Z") + except RuntimeError as exc: + assert str(exc) == "revision_conflict:5" + else: + raise AssertionError("Revisionskonflikt wurde nicht erkannt") + + +def test_analysis_cleanup_keeps_exactly_current_scope(tmp_path: Path): + records = [ + "week-01_20260101T100000Z.json", + "week-01_20260102T100000Z.json", + "week-02_20260101T100000Z.json", + "overall_20260101T100000Z.json", + "index.json", + "state.json", + ] + for name in records: + (tmp_path / name).write_text(json.dumps({"name": name}), encoding="utf-8") + keep = current_filename({"type": "week", "week": 1}) + (tmp_path / keep).write_text("{}", encoding="utf-8") + cleanup_legacy_files(tmp_path, {"type": "week", "week": 1}, keep) + names = {path.name for path in tmp_path.glob("*.json")} + assert keep in names + assert "week-02_20260101T100000Z.json" in names + assert "overall_20260101T100000Z.json" in names + assert "week-01_20260101T100000Z.json" not in names + assert "week-01_20260102T100000Z.json" not in names diff --git a/testdata/analyses/phase-1/overall.json b/testdata/analyses/phase-1/overall.json new file mode 100644 index 0000000..c7a241c --- /dev/null +++ b/testdata/analyses/phase-1/overall.json @@ -0,0 +1,1517 @@ +{ + "id": "overall_20260723T120133714864Z", + "type": "overall", + "created_at": "2026-07-23T12:01:33+00:00", + "model": "gpt-5.5", + "source_hash": "2df1ea003a9bb659e84030ca8aa958f1d5bfbb37b04c097cf98a29b68c0bc683", + "sessions_considered": 6, + "weeks_considered": [ + 1 + ], + "request": { + "scope": "overall", + "schema": { + "headline": "Kurzer Titel", + "summary": "Kompakte Einordnung in höchstens 4 Sätzen", + "data_quality": "Ein kurzer Satz zur Aussagekraft", + "metrics": [ + { + "label": "Kennzahl", + "value": "Wert", + "detail": "kurze Einordnung" + } + ], + "exercise_updates": [ + { + "name": "Übung", + "trend": "up|stable|down|unclear", + "current_level": "dokumentierter Stand", + "evidence": "ein konkreter Beleg", + "next_action": "nächster planverträglicher Schritt", + "criterion": "messbares Kriterium" + } + ], + "plan_adjustments": [ + { + "target": "Woche/Tag/Übung oder Block", + "suggested_change": "konkreter Vorschlag", + "reason": "kurze Begründung", + "manual_step": "was im Trainingsplan-Modul manuell zu ändern wäre", + "condition": "nur wenn/sonst beibehalten" + } + ], + "warnings": [ + "nur echte Auffälligkeiten" + ], + "conclusion": "ein knapper Abschlusssatz" + }, + "messages": [ + { + "role": "system", + "content": "Du analysierst ein Trainingsprotokoll auf Deutsch. Der übergebene Trainingsplan ist bindend und muss die Empfehlungen bestimmen. Verändere niemals eigenmächtig Sessionformat, Arbeitsintervall, Pausen, Rundenzahl, Satzanzahl oder Progressionslogik. Bei Tabata oder anderen festen Intervallen darfst du insbesondere keine längeren Holds oder längere Arbeitszeiten empfehlen. Progression erfolgt dann nur planverträglich, etwa über die vorgesehene schwierigere Variante, Gewicht, ROM, Tempo oder Technik. Nutze ausschließlich die Daten im Input, erfinde keine Leistungen und gib keine medizinische Diagnose. Die Analyse soll kompakt bleiben: maximal 4 Kennzahlen, 4 Übungsupdates, 3 Plananpassungen und 2 Warnungen. Plananpassungen sind nur Vorschläge zur manuellen Übernahme im Trainingsplan-Modul; behaupte nie, den Plan geändert zu haben. Bewerte die Entwicklung über die vorhandenen Wochen und formuliere höchstens drei konkrete, optionale Anpassungen für die nächste Woche beziehungsweise den nächsten Planabschnitt. Antworte ausschließlich als JSON im vorgegebenen Schema." + }, + { + "role": "user", + "content": "Gewünschtes JSON-Schema:\n{\"headline\": \"Kurzer Titel\", \"summary\": \"Kompakte Einordnung in höchstens 4 Sätzen\", \"data_quality\": \"Ein kurzer Satz zur Aussagekraft\", \"metrics\": [{\"label\": \"Kennzahl\", \"value\": \"Wert\", \"detail\": \"kurze Einordnung\"}], \"exercise_updates\": [{\"name\": \"Übung\", \"trend\": \"up|stable|down|unclear\", \"current_level\": \"dokumentierter Stand\", \"evidence\": \"ein konkreter Beleg\", \"next_action\": \"nächster planverträglicher Schritt\", \"criterion\": \"messbares Kriterium\"}], \"plan_adjustments\": [{\"target\": \"Woche/Tag/Übung oder Block\", \"suggested_change\": \"konkreter Vorschlag\", \"reason\": \"kurze Begründung\", \"manual_step\": \"was im Trainingsplan-Modul manuell zu ändern wäre\", \"condition\": \"nur wenn/sonst beibehalten\"}], \"warnings\": [\"nur echte Auffälligkeiten\"], \"conclusion\": \"ein knapper Abschlusssatz\"}\n\nAnalyse-Datensatz:\n{\"analysis_type\": \"overall\", \"plan\": {\"title\": \"Calisthenics-Basis – 8-Wochen Komplettplan\", \"subtitle\": \"Blutiger Anfänger · 1,90 m · 95 kg · Rucksack + Couch + Kettlebell · tägliche ~30-Min Tabata-Session\", \"weeks\": 8, \"selected_week\": null, \"selected_phase\": null, \"training_format\": {\"mode\": \"tabata\", \"is_tabata\": true, \"fixed_interval\": true, \"work_seconds\": 20, \"rest_seconds\": 10, \"rounds\": 8, \"session_method\": \"Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.\", \"timer_note\": \"Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.\", \"constraints\": [\"Arbeitsintervalle bleiben fest bei 20 Sekunden; keine längeren Holds oder verlängerten Arbeitsintervalle empfehlen.\", \"Progression nur innerhalb des Planformats: planmäßige schwierigere Variante, mehr Gewicht, saubererer ROM/Tempo oder bessere Technik; Pausen und Rundenzahl nicht eigenmächtig ändern.\"]}, \"guidance\": {\"goals\": \"Du startest bei 2–3 Liegestützen und 0 Dips. Realistisch und ehrlich nach 8 Wochen sauberer Arbeit:\\n•  Liegestütze: von 2–3 auf erste saubere volle Reps (Rest in Knie-/Negativ-Form).\\n•  Bank-Dips: saubere Reps mit Füßen etwas weiter weg.   Pike Push-up: am Boden mit kleinem ROM.\\n•  Plank ~45–60 s, Wall Sit ~60 s, viele saubere Goblet-Kniebeugen, sauberer Swing + spürbar bessere Griffkraft.\\n•  Noch NICHT dran: Diamond Push-ups, L-Sit, Handstand, schwerer Turkish Get-Up, Snatch – Stoff für Woche 12+.\", \"goals_note\": \"Wichtig: Die Wochen-Stufen sind ein Rahmen, kein Muss. Bleib auf einer Stufe, solange sie fordert. Aufstiegs-Regel: erst hoch (oder schwerere Glocke), wenn du eine Übung 2 Sessions in Folge in allen Sätzen sauber über die vollen 20s schaffst. KB = Kettlebell bevorzugt; Rucksack bleibt als Alternative.\", \"session_method\": \"Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.\", \"timer_note\": \"Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.\", \"reminders\": [\"Form vor Reps. Wähle die Stufe / Glocke, bei der du die 20s kontrolliert durcharbeitest – mit 1–2 Reps Reserve.\", \"Swing-Technik first. Der Swing ist ein Hüft-Schnapp, KEIN Kniebeuge und KEIN Armheben. Erst Dead-Stop-Swings, bis die Hüftstreckung sitzt.\", \"Einseitige Übungen (Tag 2): Durchgang 1 = links, Durchgang 2 = rechts – so ist jede Seite automatisch ausbalanciert.\", \"Täglich ist viel. Tag 5 leicht, Tag 7 frei. Bei starkem Muskelkater einen Extra-Ruhetag – gehört zum Aufbau.\", \"Schlaf & Eiweiß. 7–9 h Schlaf, ~1,6 g Eiweiß pro kg. Stechender Schmerz = stoppen.\"]}, \"days\": [{\"day\": 1, \"focus\": \"Drücken + Beine\", \"light\": false, \"note\": \"\", \"warmup\": [\"Handflächen-Wippen\", \"Armkreisen/Wand-Slides\", \"Hüftkreisen + Beinschwünge\", \"Knie-über-Zeh an der Wand\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Türrahmen (Brust/Schulter, je 30–40s)\", \"Trizeps (je 30s)\", \"Seitheben/Lat-Seitdehnung (je 30s)\", \"Quad im Stand (je 30s)\", \"Knieender Hüftbeuger (je 30–40s)\", \"Wade an der Wand (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Liegestütze\", \"progression_id\": \"ls\", \"cue\": \"Brust kontrolliert senken & drücken\"}, {\"name\": \"Bank-Dips (Couch)\", \"progression_id\": \"dips\", \"cue\": \"Po vor der Couch senken, hochdrücken\"}, {\"name\": \"Pike\", \"progression_id\": \"pike\", \"cue\": \"umgek. V, Kopf senken (Schultern)\"}, {\"name\": \"Seitheben (KB/Rucksack)\", \"progression_id\": \"lraise\", \"cue\": \"Arme seitlich bis Schulterhöhe, kontrolliert\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Goblet Squat (KB/Rucksack)\", \"progression_id\": \"gobletkb\", \"cue\": \"Last umarmen, tief\"}, {\"name\": \"Ausfallschritt rückw.\", \"progression_id\": \"lunge\", \"cue\": \"großer Schritt, Knie Richtung Boden\"}, {\"name\": \"Glute Bridge\", \"progression_id\": \"bridge\", \"cue\": \"Hüfte hochdrücken, Po fest\"}, {\"name\": \"Wadenheben\", \"progression_id\": \"calf\", \"cue\": \"auf Zehenspitzen hoch/runter\"}]}]}, {\"day\": 2, \"focus\": \"Ziehen + Core\", \"light\": false, \"note\": \"\", \"warmup\": [\"Handflächen-Wippen\", \"Handtuch-Dislocates (8–10×)\", \"Cat-Cow\", \"Hüftkreisen + Beinschwünge\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Lat-/Seitdehnung (je 30s)\", \"Türrahmen (je 30–40s)\", \"BWS über die Couch (30–40s)\", \"Cobra/Sphinx (Bauch, 30s)\", \"Unterarm (je 20s)\", \"Knieender Hüftbeuger (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Einarm-Rudern ENG (KB)\", \"progression_id\": \"rowe\", \"cue\": \"1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts\"}, {\"name\": \"Bizeps-Curls (KB/Rucksack)\", \"progression_id\": \"load\", \"cue\": \"Oberarme fix, zu den Schultern\"}, {\"name\": \"Einarm-Rudern BREIT (KB)\", \"progression_id\": \"row2\", \"cue\": \"1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts\"}, {\"name\": \"Superman / Y-T-W\", \"progression_id\": \"sup\", \"cue\": \"bäuchlings Arme+Beine heben\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Plank\", \"progression_id\": \"plank\", \"cue\": \"gerade Linie halten\"}, {\"name\": \"Mountain Climbers\", \"progression_id\": \"mtn\", \"cue\": \"Knie zügig zur Brust\"}, {\"name\": \"Dead Bug\", \"progression_id\": \"db\", \"cue\": \"gegengleich, Rücken am Boden\"}, {\"name\": \"Beinheben\", \"progression_id\": \"lr\", \"cue\": \"Beine heben/senken\"}]}]}, {\"day\": 3, \"focus\": \"Beine + Core\", \"light\": false, \"note\": \"\", \"warmup\": [\"Hüftkreisen + Beinschwünge\", \"Knie-über-Zeh an der Wand\", \"Cat-Cow\", \"Handflächen-Wippen\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Hamstring-Dehnung (Couch, je 30–40s)\", \"Quad im Stand (je 30s)\", \"Knieender Hüftbeuger (je 30–40s)\", \"Figure-4/Taube (je 30–40s)\", \"Cobra + liegende Wirbelsäulen-Rotation (je 30s)\", \"Wade an der Wand (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Goblet Squat (KB/Rucksack)\", \"progression_id\": \"gobletkb\", \"cue\": \"Last umarmen, tief\"}, {\"name\": \"RDL / Hip Hinge (KB/Rucksack)\", \"progression_id\": \"load\", \"cue\": \"Hüfte zurück, Rücken gerade, Hamstrings\"}, {\"name\": \"Glute Bridge 1-beinig\", \"progression_id\": \"bridge\", \"cue\": \"ein Bein gestreckt\"}, {\"name\": \"Wall Sit\", \"progression_id\": \"wall\", \"cue\": \"Rücken an die Wand, halten\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Hollow Hold\", \"progression_id\": \"holl\", \"cue\": \"unterer Rücken am Boden\"}, {\"name\": \"Side Plank (Wechsel)\", \"progression_id\": \"sp\", \"cue\": \"seitlich auf einem Unterarm\"}, {\"name\": \"Bicycle Crunch\", \"progression_id\": \"bic\", \"cue\": \"Ellbogen zum Gegenknie\"}, {\"name\": \"Russian Twist (KB)\", \"progression_id\": \"tw\", \"cue\": \"sitzend Rumpf drehen\"}]}]}, {\"day\": 4, \"focus\": \"Oberkörper: Drücken + Ziehen\", \"light\": false, \"note\": \"\", \"warmup\": [\"Handflächen-Wippen\", \"Handtuch-Dislocates (8–10×)\", \"Armkreisen/Wand-Slides\", \"Cat-Cow\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Türrahmen (je 30–40s)\", \"Trizeps (je 30s)\", \"Lat-/Seitdehnung (je 30s)\", \"BWS über die Couch (30–40s)\", \"Unterarm (je 20s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Liegestütze\", \"progression_id\": \"ls\", \"cue\": \"Brust kontrolliert senken & drücken\"}, {\"name\": \"Bank-Dips (Couch)\", \"progression_id\": \"dips\", \"cue\": \"Po vor der Couch senken\"}, {\"name\": \"Pike\", \"progression_id\": \"pike\", \"cue\": \"umgek. V, Kopf senken\"}, {\"name\": \"KB Floor Press\", \"progression_id\": \"press\", \"cue\": \"Glocke vom Boden weg drücken\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Einarm-Rudern ENG (KB)\", \"progression_id\": \"rowe\", \"cue\": \"1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts\"}, {\"name\": \"Bizeps-Curls (KB/Rucksack)\", \"progression_id\": \"load\", \"cue\": \"Oberarme fix, zu den Schultern\"}, {\"name\": \"Einarm-Rudern BREIT (KB)\", \"progression_id\": \"row2\", \"cue\": \"1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts\"}, {\"name\": \"Y-T-W / Superman\", \"progression_id\": \"sup\", \"cue\": \"bäuchlings, Rücken\"}]}]}, {\"day\": 5, \"focus\": \"Ganzkörper LEICHT + Mobility\", \"light\": true, \"note\": \"\", \"warmup\": [\"Cat-Cow\", \"Hüftkreisen + Beinschwünge\", \"Armkreisen/Wand-Slides\", \"Knie-über-Zeh an der Wand\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Türrahmen (je 30s)\", \"Knieender Hüftbeuger (je 30s)\", \"BWS über die Couch (30–40s)\", \"Figure-4/Taube (je 30s)\", \"Wade an der Wand (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Kniebeuge (leicht)\", \"progression_id\": \"name:kniebeuge-leicht\", \"cue\": \"locker, sauber\"}, {\"name\": \"Liegestütze (1 Stufe leichter)\", \"progression_id\": \"name:liegestuetze-1-stufe-leichter\", \"cue\": \"leichtere Variante\"}, {\"name\": \"Rudern (leicht)\", \"progression_id\": \"name:rudern-leicht\", \"cue\": \"wenig Gewicht\"}, {\"name\": \"Mountain Climbers (locker)\", \"progression_id\": \"name:mountain-climbers-locker\", \"cue\": \"ruhiges Tempo\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Inchworm\", \"progression_id\": \"name:inchworm\", \"cue\": \"in den Stütz wandern, zurück\"}, {\"name\": \"Cat-Cow\", \"progression_id\": \"name:cat-cow\", \"cue\": \"Rücken ründen/strecken\"}, {\"name\": \"Bird Dog\", \"progression_id\": \"name:bird-dog\", \"cue\": \"gegengleich Arm+Bein, kurz halten\"}, {\"name\": \"KB-Halo (leicht)\", \"progression_id\": \"name:kb-halo-leicht\", \"cue\": \"langsam um den Kopf kreisen\"}]}]}, {\"day\": 6, \"focus\": \"KETTLEBELL Ganzkörper\", \"light\": false, \"note\": \"Reine Kettlebell-Session. Swing = Hüft-Schnapp, kein Armheben. Glocke so wählen, dass alle Reps sauber sind.\", \"warmup\": [\"Halo (leicht)\", \"Hüftkreisen + Beinschwünge\", \"Cat-Cow\", \"Handflächen-Wippen\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Hamstring-Dehnung (Couch, je 30–40s)\", \"Knieender Hüftbeuger (je 30–40s)\", \"Figure-4/Taube (je 30–40s)\", \"Unterarm (je 20s)\", \"BWS über die Couch (30–40s)\", \"Lat-/Seitdehnung (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"KB Swing (zweiarmig)\", \"progression_id\": \"swing\", \"cue\": \"Hüft-Schnapp, kein Armheben\"}, {\"name\": \"Goblet Squat (KB)\", \"progression_id\": \"gobletkb\", \"cue\": \"Glocke vor der Brust, tief\"}, {\"name\": \"KB-Deadlift (beidbeinig)\", \"progression_id\": \"kbdl\", \"cue\": \"Glocke zwischen den Füßen, Hüfte hinge\"}, {\"name\": \"Halo\", \"progression_id\": \"halo\", \"cue\": \"eng um den Kopf kreisen\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"KB Floor Press\", \"progression_id\": \"press\", \"cue\": \"Glocke vom Boden weg drücken\"}, {\"name\": \"Einarmiges Rudern (KB)\", \"progression_id\": \"row1\", \"cue\": \"Rücken gerade, zum Bauch\"}, {\"name\": \"Bottoms-up KB-Hold (Griff)\", \"progression_id\": \"carry\", \"cue\": \"Glocke Boden nach oben, Unterarm senkrecht, still halten\"}, {\"name\": \"Around-the-World + Uppercut (KB)\", \"progression_id\": \"atw\", \"cue\": \"Glocke um den Körper, dann diagonal zur Gegen-Schulter, andere Hand fängt\"}]}]}], \"phases\": [{\"name\": \"Phase 1 · Fundament\", \"instructions\": \"Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.\", \"start_week\": 1, \"end_week\": 2}, {\"name\": \"Phase 2 · Aufbau\", \"instructions\": \"Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.\", \"start_week\": 3, \"end_week\": 4}, {\"name\": \"Phase 3 · Steigerung\", \"instructions\": \"Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.\", \"start_week\": 5, \"end_week\": 6}, {\"name\": \"Phase 4 · Festigen\", \"instructions\": \"Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.\", \"start_week\": 7, \"end_week\": 8}], \"progression_stages\": {\"atw\": {\"name\": \"Around-the-World + Uppercut (KB)\", \"steps\": [\"nur Around-the-World, leicht\", \"Around-the-World, mehr Reps\", \"+ Uppercut, leicht\", \"+ Uppercut, flüssig\"]}, \"bic\": {\"name\": \"Bicycle Crunch\", \"steps\": [\"langsam\", \"moderat\", \"moderat\", \"zügig\"]}, \"bridge\": {\"name\": \"Glute Bridge\", \"steps\": [\"beidbeinig\", \"beidbeinig, 2s halten\", \"einbeinig\", \"einbeinig, 2s halten\"]}, \"calf\": {\"name\": \"Wadenheben\", \"steps\": [\"Körpergewicht\", \"langsam\", \"langsam, mehr Reps\", \"1-beinig\"]}, \"carry\": {\"name\": \"Bottoms-up KB-Hold (Griff)\", \"steps\": [\"im Stand 10–15s\", \"15–20s, ruhig\", \"bis Schulterhöhe 15s\", \"länger / ein paar Schritte\"]}, \"db\": {\"name\": \"Dead Bug\", \"steps\": [\"Arme ODER Beine einzeln\", \"gegengleich\", \"gegengleich + Pause\", \"Beine weiter gestreckt\"]}, \"dips\": {\"name\": \"Bank-Dips (Couch)\", \"steps\": [\"Knie stark gebeugt, klein\", \"Knie gebeugt, voll\", \"Füße etwas weiter\", \"Füße weiter weg\"]}, \"gobletkb\": {\"name\": \"Goblet Squat (KB/Rucksack)\", \"steps\": [\"leicht, Tiefe lernen\", \"leicht, mehr Reps\", \"mittel\", \"mittel, 3s runter\"]}, \"halo\": {\"name\": \"Halo\", \"steps\": [\"leicht, langsam\", \"leicht\", \"mittel\", \"mittel, langsam\"]}, \"holl\": {\"name\": \"Hollow Hold\", \"steps\": [\"Tuck (Knie an)\", \"Tuck, länger\", \"halb gestreckt\", \"halb/voll\"]}, \"kbdl\": {\"name\": \"KB-Deadlift (beidbeinig)\", \"steps\": [\"leicht, Technik\", \"leicht, mehr Reps\", \"mittel\", \"schwerer, kontrolliert\"]}, \"load\": {\"name\": \"Bizeps-Curls (KB/Rucksack)\", \"steps\": [\"leicht\", \"etwas mehr\", \"mehr\", \"fordernd\"]}, \"lr\": {\"name\": \"Beinheben\", \"steps\": [\"Knie gebeugt\", \"Knie gebeugt, langsam\", \"halb gestreckt\", \"gestreckt\"]}, \"lraise\": {\"name\": \"Seitheben (KB/Rucksack)\", \"steps\": [\"leicht (Technik)\", \"leicht, mehr Reps\", \"mittel\", \"mittel, langsam\"]}, \"ls\": {\"name\": \"Liegestütze\", \"steps\": [\"Inkline (hoch)\", \"Inkline tiefer / Knie\", \"Knie / Negativ\", \"Negativ + erste Volle\"]}, \"lseng\": {\"name\": \"Inkline (hoch)\", \"steps\": [\"Inkline (hoch)\", \"Inkline mittel\", \"Inkline tief\", \"Knie-Liegestütze\"]}, \"lunge\": {\"name\": \"Ausfallschritt rückw.\", \"steps\": [\"Körpergewicht (ggf. festhalten)\", \"Körpergewicht\", \"mehr Reps\", \"+ Last leicht\"]}, \"mtn\": {\"name\": \"Mountain Climbers\", \"steps\": [\"moderat\", \"moderat\", \"zügig\", \"zügig\"]}, \"pike\": {\"name\": \"Pike\", \"steps\": [\"Pike Hold\", \"Hold + kleine Senkung\", \"Push-up Hände erhöht\", \"Push-up Boden, klein\"]}, \"plank\": {\"name\": \"Plank\", \"steps\": [\"auf Knien 15–20 s\", \"voll 20–30 s\", \"voll + Schulter-Taps\", \"voll, länger\"]}, \"press\": {\"name\": \"KB Floor Press\", \"steps\": [\"leicht (Floor Press)\", \"Floor Press mehr\", \"Overhead leicht\", \"Overhead mittel\"]}, \"row1\": {\"name\": \"Einarmiges Rudern (KB)\", \"steps\": [\"leicht\", \"etwas mehr\", \"einarmig leicht\", \"einarmig mittel\"]}, \"row2\": {\"name\": \"Einarm-Rudern BREIT (KB)\", \"steps\": [\"leicht (Technik)\", \"leicht, mehr Reps\", \"mittel\", \"mittel, Pause oben\"]}, \"rowe\": {\"name\": \"Einarm-Rudern ENG (KB)\", \"steps\": [\"leicht\", \"etwas mehr\", \"mehr\", \"fordernd\"]}, \"sp\": {\"name\": \"Side Plank (Wechsel)\", \"steps\": [\"auf Knien\", \"auf Knien, länger\", \"voll\", \"voll, länger\"]}, \"squat\": {\"name\": \"Körpergewicht\", \"steps\": [\"Körpergewicht\", \"Körpergewicht, mehr Reps\", \"Goblet leicht\", \"Goblet mittel\"]}, \"sup\": {\"name\": \"Superman / Y-T-W\", \"steps\": [\"Superman Hold\", \"Superman Hold länger\", \"Reverse Snow Angels\", \"Y-T-W Raises\"]}, \"swing\": {\"name\": \"KB Swing (zweiarmig)\", \"steps\": [\"Dead-Stop, leicht (Technik)\", \"Dead-Stop, mehr Reps\", \"durchgehend, mittel\", \"durchgehend, schwerer\"]}, \"tsquat\": {\"name\": \"3s runter\", \"steps\": [\"3s runter\", \"3s + Pause unten\", \"Goblet 3s\", \"Goblet 3s + Pause\"]}, \"tw\": {\"name\": \"Russian Twist (KB)\", \"steps\": [\"ohne Gewicht\", \"ohne Gewicht\", \"KB leicht\", \"KB\"]}, \"wall\": {\"name\": \"Wall Sit\", \"steps\": [\"15–25 s\", \"25–40 s\", \"40–55 s\", \"55–70 s\"]}}}, \"weekly_analyses\": [{\"week\": 1, \"source_hash\": \"815dc58aad775b90e6302fb9a687d2c80c8c76f19b9008d0072953d1a160dee8\", \"headline\": \"Woche 1: starker Einstieg, Progression vorsichtig\", \"summary\": \"Die Woche wurde sehr konsequent gestartet; effektiv sind 5 von 6 geplanten Trainingstagen mit Übungen dokumentiert. Die meisten Übungen liegen sinnvoll auf den vorgesehenen Anfänger-Stufen der Phase 1. Erste stabile Muster zeigen sich bei Pike Holds, Rudern und Core-Holds, während Liegestütze noch klar auf Konsolidierung statt Progression stehen. Für Woche 2 sollten nur planverträgliche Variantenwechsel genutzt werden, keine längeren Arbeitsintervalle.\", \"metrics\": [{\"label\": \"Durchführung\", \"value\": \"System 6/6, effektiv 5/6\", \"detail\": \"Tag 5 hat die Notiz „Nicht durchgeführt“ und keine erledigten Übungen.\"}, {\"label\": \"Dokumentationsumfang\", \"value\": \"36 dokumentierte Übungen\", \"detail\": \"Genug Daten für erste Übungstendenzen, aber noch keine langfristigen Trends.\"}, {\"label\": \"Tabata-Format\", \"value\": \"20/10 beibehalten\", \"detail\": \"Die dokumentierten Holds bleiben im festen 20-Sekunden-Arbeitsintervall.\"}, {\"label\": \"Belastungsniveau\", \"value\": \"überwiegend Startstufen\", \"detail\": \"Passt zur Phase 1 mit Technikfokus und leichter Last.\"}], \"exercise_updates\": [{\"name\": \"Liegestütze\", \"trend\": \"stable\", \"current_level\": \"Inkline hoch\", \"evidence\": \"Tag 1: 8/7/7/7/6/6/5; Tag 4: 8/7/7/6/6/6\", \"next_action\": \"Stufe beibehalten und sauberen ROM sowie gleichmäßigere Wiederholungen priorisieren.\", \"criterion\": \"Erst auf Inkline tiefer/Knie wechseln, wenn 2 Sessions in Folge alle vorgesehenen 20s-Intervalle sauber mit stabiler Körperspannung geschafft werden.\"}, {\"name\": \"Pike\", \"trend\": \"up\", \"current_level\": \"Pike Hold\", \"evidence\": \"Tag 1 und Tag 4 jeweils 6x20 s dokumentiert.\", \"next_action\": \"Optional nächste planmäßige Stufe „Hold + kleine Senkung“ innerhalb der 20s testen.\", \"criterion\": \"Nur übernehmen, wenn 6/6 Intervalle ohne Hüftabsacken und ohne Technikverlust sauber bleiben.\"}, {\"name\": \"Einarm-Rudern ENG (KB)\", \"trend\": \"up\", \"current_level\": \"leicht, 4 kg\", \"evidence\": \"Tag 2: 4kg, 9/9/8/8/8/9; Tag 4: 4kg, 9/9/9/8/8/9\", \"next_action\": \"Bei sauberem Zug zur Hüfte planverträglich auf „etwas mehr“ bzw. leicht höhere Last wechseln.\", \"criterion\": \"2 Sessions in Folge alle dokumentierten Intervalle je Seite kontrolliert mit mindestens ähnlicher Wiederholungszahl abschließen.\"}, {\"name\": \"KB Swing (zweiarmig)\", \"trend\": \"unclear\", \"current_level\": \"Dead-Stop, leicht; 10 kg\", \"evidence\": \"Tag 6: 10kg, 15 dokumentiert.\", \"next_action\": \"Dead-Stop-Technik beibehalten und pro 20s-Intervall genauer dokumentieren.\", \"criterion\": \"Erst steigern, wenn alle Swing-Intervalle in 2 Sessions sauber als Hüft-Schnapp ohne Armheben dokumentiert sind.\"}], \"plan_adjustments\": [{\"target\": \"Woche 2 / Pike\", \"suggested_change\": \"Progressionsstufe von „Pike Hold“ auf „Hold + kleine Senkung“ setzen.\", \"reason\": \"Zwei dokumentierte Einheiten mit 6x20 s Pike Hold sprechen für einen vorsichtigen Variantenwechsel.\", \"manual_step\": \"Im Trainingsplan-Modul bei Pike die nächste Progressionsstufe auswählen.\", \"condition\": \"Nur wenn beide Pike-Einheiten technisch sauber waren; sonst Pike Hold beibehalten.\"}, {\"target\": \"Woche 2 / Einarm-Rudern ENG\", \"suggested_change\": \"Von „leicht“ auf „etwas mehr“ wechseln, z.B. etwas höhere La" + } + ], + "dataset": { + "analysis_type": "overall", + "plan": { + "title": "Calisthenics-Basis – 8-Wochen Komplettplan", + "subtitle": "Blutiger Anfänger · 1,90 m · 95 kg · Rucksack + Couch + Kettlebell · tägliche ~30-Min Tabata-Session", + "weeks": 8, + "selected_week": null, + "selected_phase": null, + "training_format": { + "mode": "tabata", + "is_tabata": true, + "fixed_interval": true, + "work_seconds": 20, + "rest_seconds": 10, + "rounds": 8, + "session_method": "Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.", + "timer_note": "Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.", + "constraints": [ + "Arbeitsintervalle bleiben fest bei 20 Sekunden; keine längeren Holds oder verlängerten Arbeitsintervalle empfehlen.", + "Progression nur innerhalb des Planformats: planmäßige schwierigere Variante, mehr Gewicht, saubererer ROM/Tempo oder bessere Technik; Pausen und Rundenzahl nicht eigenmächtig ändern." + ] + }, + "guidance": { + "goals": "Du startest bei 2–3 Liegestützen und 0 Dips. Realistisch und ehrlich nach 8 Wochen sauberer Arbeit:\n•  Liegestütze: von 2–3 auf erste saubere volle Reps (Rest in Knie-/Negativ-Form).\n•  Bank-Dips: saubere Reps mit Füßen etwas weiter weg.   Pike Push-up: am Boden mit kleinem ROM.\n•  Plank ~45–60 s, Wall Sit ~60 s, viele saubere Goblet-Kniebeugen, sauberer Swing + spürbar bessere Griffkraft.\n•  Noch NICHT dran: Diamond Push-ups, L-Sit, Handstand, schwerer Turkish Get-Up, Snatch – Stoff für Woche 12+.", + "goals_note": "Wichtig: Die Wochen-Stufen sind ein Rahmen, kein Muss. Bleib auf einer Stufe, solange sie fordert. Aufstiegs-Regel: erst hoch (oder schwerere Glocke), wenn du eine Übung 2 Sessions in Folge in allen Sätzen sauber über die vollen 20s schaffst. KB = Kettlebell bevorzugt; Rucksack bleibt als Alternative.", + "session_method": "Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.", + "timer_note": "Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.", + "reminders": [ + "Form vor Reps. Wähle die Stufe / Glocke, bei der du die 20s kontrolliert durcharbeitest – mit 1–2 Reps Reserve.", + "Swing-Technik first. Der Swing ist ein Hüft-Schnapp, KEIN Kniebeuge und KEIN Armheben. Erst Dead-Stop-Swings, bis die Hüftstreckung sitzt.", + "Einseitige Übungen (Tag 2): Durchgang 1 = links, Durchgang 2 = rechts – so ist jede Seite automatisch ausbalanciert.", + "Täglich ist viel. Tag 5 leicht, Tag 7 frei. Bei starkem Muskelkater einen Extra-Ruhetag – gehört zum Aufbau.", + "Schlaf & Eiweiß. 7–9 h Schlaf, ~1,6 g Eiweiß pro kg. Stechender Schmerz = stoppen." + ] + }, + "days": [ + { + "day": 1, + "focus": "Drücken + Beine", + "light": false, + "note": "", + "warmup": [ + "Handflächen-Wippen", + "Armkreisen/Wand-Slides", + "Hüftkreisen + Beinschwünge", + "Knie-über-Zeh an der Wand" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Türrahmen (Brust/Schulter, je 30–40s)", + "Trizeps (je 30s)", + "Seitheben/Lat-Seitdehnung (je 30s)", + "Quad im Stand (je 30s)", + "Knieender Hüftbeuger (je 30–40s)", + "Wade an der Wand (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Liegestütze", + "progression_id": "ls", + "cue": "Brust kontrolliert senken & drücken" + }, + { + "name": "Bank-Dips (Couch)", + "progression_id": "dips", + "cue": "Po vor der Couch senken, hochdrücken" + }, + { + "name": "Pike", + "progression_id": "pike", + "cue": "umgek. V, Kopf senken (Schultern)" + }, + { + "name": "Seitheben (KB/Rucksack)", + "progression_id": "lraise", + "cue": "Arme seitlich bis Schulterhöhe, kontrolliert" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "cue": "Last umarmen, tief" + }, + { + "name": "Ausfallschritt rückw.", + "progression_id": "lunge", + "cue": "großer Schritt, Knie Richtung Boden" + }, + { + "name": "Glute Bridge", + "progression_id": "bridge", + "cue": "Hüfte hochdrücken, Po fest" + }, + { + "name": "Wadenheben", + "progression_id": "calf", + "cue": "auf Zehenspitzen hoch/runter" + } + ] + } + ] + }, + { + "day": 2, + "focus": "Ziehen + Core", + "light": false, + "note": "", + "warmup": [ + "Handflächen-Wippen", + "Handtuch-Dislocates (8–10×)", + "Cat-Cow", + "Hüftkreisen + Beinschwünge" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Lat-/Seitdehnung (je 30s)", + "Türrahmen (je 30–40s)", + "BWS über die Couch (30–40s)", + "Cobra/Sphinx (Bauch, 30s)", + "Unterarm (je 20s)", + "Knieender Hüftbeuger (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts" + }, + { + "name": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "cue": "Oberarme fix, zu den Schultern" + }, + { + "name": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts" + }, + { + "name": "Superman / Y-T-W", + "progression_id": "sup", + "cue": "bäuchlings Arme+Beine heben" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Plank", + "progression_id": "plank", + "cue": "gerade Linie halten" + }, + { + "name": "Mountain Climbers", + "progression_id": "mtn", + "cue": "Knie zügig zur Brust" + }, + { + "name": "Dead Bug", + "progression_id": "db", + "cue": "gegengleich, Rücken am Boden" + }, + { + "name": "Beinheben", + "progression_id": "lr", + "cue": "Beine heben/senken" + } + ] + } + ] + }, + { + "day": 3, + "focus": "Beine + Core", + "light": false, + "note": "", + "warmup": [ + "Hüftkreisen + Beinschwünge", + "Knie-über-Zeh an der Wand", + "Cat-Cow", + "Handflächen-Wippen" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Hamstring-Dehnung (Couch, je 30–40s)", + "Quad im Stand (je 30s)", + "Knieender Hüftbeuger (je 30–40s)", + "Figure-4/Taube (je 30–40s)", + "Cobra + liegende Wirbelsäulen-Rotation (je 30s)", + "Wade an der Wand (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "cue": "Last umarmen, tief" + }, + { + "name": "RDL / Hip Hinge (KB/Rucksack)", + "progression_id": "load", + "cue": "Hüfte zurück, Rücken gerade, Hamstrings" + }, + { + "name": "Glute Bridge 1-beinig", + "progression_id": "bridge", + "cue": "ein Bein gestreckt" + }, + { + "name": "Wall Sit", + "progression_id": "wall", + "cue": "Rücken an die Wand, halten" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Hollow Hold", + "progression_id": "holl", + "cue": "unterer Rücken am Boden" + }, + { + "name": "Side Plank (Wechsel)", + "progression_id": "sp", + "cue": "seitlich auf einem Unterarm" + }, + { + "name": "Bicycle Crunch", + "progression_id": "bic", + "cue": "Ellbogen zum Gegenknie" + }, + { + "name": "Russian Twist (KB)", + "progression_id": "tw", + "cue": "sitzend Rumpf drehen" + } + ] + } + ] + }, + { + "day": 4, + "focus": "Oberkörper: Drücken + Ziehen", + "light": false, + "note": "", + "warmup": [ + "Handflächen-Wippen", + "Handtuch-Dislocates (8–10×)", + "Armkreisen/Wand-Slides", + "Cat-Cow" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Türrahmen (je 30–40s)", + "Trizeps (je 30s)", + "Lat-/Seitdehnung (je 30s)", + "BWS über die Couch (30–40s)", + "Unterarm (je 20s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Liegestütze", + "progression_id": "ls", + "cue": "Brust kontrolliert senken & drücken" + }, + { + "name": "Bank-Dips (Couch)", + "progression_id": "dips", + "cue": "Po vor der Couch senken" + }, + { + "name": "Pike", + "progression_id": "pike", + "cue": "umgek. V, Kopf senken" + }, + { + "name": "KB Floor Press", + "progression_id": "press", + "cue": "Glocke vom Boden weg drücken" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts" + }, + { + "name": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "cue": "Oberarme fix, zu den Schultern" + }, + { + "name": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts" + }, + { + "name": "Y-T-W / Superman", + "progression_id": "sup", + "cue": "bäuchlings, Rücken" + } + ] + } + ] + }, + { + "day": 5, + "focus": "Ganzkörper LEICHT + Mobility", + "light": true, + "note": "", + "warmup": [ + "Cat-Cow", + "Hüftkreisen + Beinschwünge", + "Armkreisen/Wand-Slides", + "Knie-über-Zeh an der Wand" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Türrahmen (je 30s)", + "Knieender Hüftbeuger (je 30s)", + "BWS über die Couch (30–40s)", + "Figure-4/Taube (je 30s)", + "Wade an der Wand (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Kniebeuge (leicht)", + "progression_id": "name:kniebeuge-leicht", + "cue": "locker, sauber" + }, + { + "name": "Liegestütze (1 Stufe leichter)", + "progression_id": "name:liegestuetze-1-stufe-leichter", + "cue": "leichtere Variante" + }, + { + "name": "Rudern (leicht)", + "progression_id": "name:rudern-leicht", + "cue": "wenig Gewicht" + }, + { + "name": "Mountain Climbers (locker)", + "progression_id": "name:mountain-climbers-locker", + "cue": "ruhiges Tempo" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Inchworm", + "progression_id": "name:inchworm", + "cue": "in den Stütz wandern, zurück" + }, + { + "name": "Cat-Cow", + "progression_id": "name:cat-cow", + "cue": "Rücken ründen/strecken" + }, + { + "name": "Bird Dog", + "progression_id": "name:bird-dog", + "cue": "gegengleich Arm+Bein, kurz halten" + }, + { + "name": "KB-Halo (leicht)", + "progression_id": "name:kb-halo-leicht", + "cue": "langsam um den Kopf kreisen" + } + ] + } + ] + }, + { + "day": 6, + "focus": "KETTLEBELL Ganzkörper", + "light": false, + "note": "Reine Kettlebell-Session. Swing = Hüft-Schnapp, kein Armheben. Glocke so wählen, dass alle Reps sauber sind.", + "warmup": [ + "Halo (leicht)", + "Hüftkreisen + Beinschwünge", + "Cat-Cow", + "Handflächen-Wippen" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Hamstring-Dehnung (Couch, je 30–40s)", + "Knieender Hüftbeuger (je 30–40s)", + "Figure-4/Taube (je 30–40s)", + "Unterarm (je 20s)", + "BWS über die Couch (30–40s)", + "Lat-/Seitdehnung (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "KB Swing (zweiarmig)", + "progression_id": "swing", + "cue": "Hüft-Schnapp, kein Armheben" + }, + { + "name": "Goblet Squat (KB)", + "progression_id": "gobletkb", + "cue": "Glocke vor der Brust, tief" + }, + { + "name": "KB-Deadlift (beidbeinig)", + "progression_id": "kbdl", + "cue": "Glocke zwischen den Füßen, Hüfte hinge" + }, + { + "name": "Halo", + "progression_id": "halo", + "cue": "eng um den Kopf kreisen" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "KB Floor Press", + "progression_id": "press", + "cue": "Glocke vom Boden weg drücken" + }, + { + "name": "Einarmiges Rudern (KB)", + "progression_id": "row1", + "cue": "Rücken gerade, zum Bauch" + }, + { + "name": "Bottoms-up KB-Hold (Griff)", + "progression_id": "carry", + "cue": "Glocke Boden nach oben, Unterarm senkrecht, still halten" + }, + { + "name": "Around-the-World + Uppercut (KB)", + "progression_id": "atw", + "cue": "Glocke um den Körper, dann diagonal zur Gegen-Schulter, andere Hand fängt" + } + ] + } + ] + } + ], + "phases": [ + { + "name": "Phase 1 · Fundament", + "instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "start_week": 1, + "end_week": 2 + }, + { + "name": "Phase 2 · Aufbau", + "instructions": "Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.", + "start_week": 3, + "end_week": 4 + }, + { + "name": "Phase 3 · Steigerung", + "instructions": "Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.", + "start_week": 5, + "end_week": 6 + }, + { + "name": "Phase 4 · Festigen", + "instructions": "Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.", + "start_week": 7, + "end_week": 8 + } + ], + "progression_stages": { + "atw": { + "name": "Around-the-World + Uppercut (KB)", + "steps": [ + "nur Around-the-World, leicht", + "Around-the-World, mehr Reps", + "+ Uppercut, leicht", + "+ Uppercut, flüssig" + ] + }, + "bic": { + "name": "Bicycle Crunch", + "steps": [ + "langsam", + "moderat", + "moderat", + "zügig" + ] + }, + "bridge": { + "name": "Glute Bridge", + "steps": [ + "beidbeinig", + "beidbeinig, 2s halten", + "einbeinig", + "einbeinig, 2s halten" + ] + }, + "calf": { + "name": "Wadenheben", + "steps": [ + "Körpergewicht", + "langsam", + "langsam, mehr Reps", + "1-beinig" + ] + }, + "carry": { + "name": "Bottoms-up KB-Hold (Griff)", + "steps": [ + "im Stand 10–15s", + "15–20s, ruhig", + "bis Schulterhöhe 15s", + "länger / ein paar Schritte" + ] + }, + "db": { + "name": "Dead Bug", + "steps": [ + "Arme ODER Beine einzeln", + "gegengleich", + "gegengleich + Pause", + "Beine weiter gestreckt" + ] + }, + "dips": { + "name": "Bank-Dips (Couch)", + "steps": [ + "Knie stark gebeugt, klein", + "Knie gebeugt, voll", + "Füße etwas weiter", + "Füße weiter weg" + ] + }, + "gobletkb": { + "name": "Goblet Squat (KB/Rucksack)", + "steps": [ + "leicht, Tiefe lernen", + "leicht, mehr Reps", + "mittel", + "mittel, 3s runter" + ] + }, + "halo": { + "name": "Halo", + "steps": [ + "leicht, langsam", + "leicht", + "mittel", + "mittel, langsam" + ] + }, + "holl": { + "name": "Hollow Hold", + "steps": [ + "Tuck (Knie an)", + "Tuck, länger", + "halb gestreckt", + "halb/voll" + ] + }, + "kbdl": { + "name": "KB-Deadlift (beidbeinig)", + "steps": [ + "leicht, Technik", + "leicht, mehr Reps", + "mittel", + "schwerer, kontrolliert" + ] + }, + "load": { + "name": "Bizeps-Curls (KB/Rucksack)", + "steps": [ + "leicht", + "etwas mehr", + "mehr", + "fordernd" + ] + }, + "lr": { + "name": "Beinheben", + "steps": [ + "Knie gebeugt", + "Knie gebeugt, langsam", + "halb gestreckt", + "gestreckt" + ] + }, + "lraise": { + "name": "Seitheben (KB/Rucksack)", + "steps": [ + "leicht (Technik)", + "leicht, mehr Reps", + "mittel", + "mittel, langsam" + ] + }, + "ls": { + "name": "Liegestütze", + "steps": [ + "Inkline (hoch)", + "Inkline tiefer / Knie", + "Knie / Negativ", + "Negativ + erste Volle" + ] + }, + "lseng": { + "name": "Inkline (hoch)", + "steps": [ + "Inkline (hoch)", + "Inkline mittel", + "Inkline tief", + "Knie-Liegestütze" + ] + }, + "lunge": { + "name": "Ausfallschritt rückw.", + "steps": [ + "Körpergewicht (ggf. festhalten)", + "Körpergewicht", + "mehr Reps", + "+ Last leicht" + ] + }, + "mtn": { + "name": "Mountain Climbers", + "steps": [ + "moderat", + "moderat", + "zügig", + "zügig" + ] + }, + "pike": { + "name": "Pike", + "steps": [ + "Pike Hold", + "Hold + kleine Senkung", + "Push-up Hände erhöht", + "Push-up Boden, klein" + ] + }, + "plank": { + "name": "Plank", + "steps": [ + "auf Knien 15–20 s", + "voll 20–30 s", + "voll + Schulter-Taps", + "voll, länger" + ] + }, + "press": { + "name": "KB Floor Press", + "steps": [ + "leicht (Floor Press)", + "Floor Press mehr", + "Overhead leicht", + "Overhead mittel" + ] + }, + "row1": { + "name": "Einarmiges Rudern (KB)", + "steps": [ + "leicht", + "etwas mehr", + "einarmig leicht", + "einarmig mittel" + ] + }, + "row2": { + "name": "Einarm-Rudern BREIT (KB)", + "steps": [ + "leicht (Technik)", + "leicht, mehr Reps", + "mittel", + "mittel, Pause oben" + ] + }, + "rowe": { + "name": "Einarm-Rudern ENG (KB)", + "steps": [ + "leicht", + "etwas mehr", + "mehr", + "fordernd" + ] + }, + "sp": { + "name": "Side Plank (Wechsel)", + "steps": [ + "auf Knien", + "auf Knien, länger", + "voll", + "voll, länger" + ] + }, + "squat": { + "name": "Körpergewicht", + "steps": [ + "Körpergewicht", + "Körpergewicht, mehr Reps", + "Goblet leicht", + "Goblet mittel" + ] + }, + "sup": { + "name": "Superman / Y-T-W", + "steps": [ + "Superman Hold", + "Superman Hold länger", + "Reverse Snow Angels", + "Y-T-W Raises" + ] + }, + "swing": { + "name": "KB Swing (zweiarmig)", + "steps": [ + "Dead-Stop, leicht (Technik)", + "Dead-Stop, mehr Reps", + "durchgehend, mittel", + "durchgehend, schwerer" + ] + }, + "tsquat": { + "name": "3s runter", + "steps": [ + "3s runter", + "3s + Pause unten", + "Goblet 3s", + "Goblet 3s + Pause" + ] + }, + "tw": { + "name": "Russian Twist (KB)", + "steps": [ + "ohne Gewicht", + "ohne Gewicht", + "KB leicht", + "KB" + ] + }, + "wall": { + "name": "Wall Sit", + "steps": [ + "15–25 s", + "25–40 s", + "40–55 s", + "55–70 s" + ] + } + } + }, + "weekly_analyses": [ + { + "week": 1, + "source_hash": "815dc58aad775b90e6302fb9a687d2c80c8c76f19b9008d0072953d1a160dee8", + "headline": "Woche 1: starker Einstieg, Progression vorsichtig", + "summary": "Die Woche wurde sehr konsequent gestartet; effektiv sind 5 von 6 geplanten Trainingstagen mit Übungen dokumentiert. Die meisten Übungen liegen sinnvoll auf den vorgesehenen Anfänger-Stufen der Phase 1. Erste stabile Muster zeigen sich bei Pike Holds, Rudern und Core-Holds, während Liegestütze noch klar auf Konsolidierung statt Progression stehen. Für Woche 2 sollten nur planverträgliche Variantenwechsel genutzt werden, keine längeren Arbeitsintervalle.", + "metrics": [ + { + "label": "Durchführung", + "value": "System 6/6, effektiv 5/6", + "detail": "Tag 5 hat die Notiz „Nicht durchgeführt“ und keine erledigten Übungen." + }, + { + "label": "Dokumentationsumfang", + "value": "36 dokumentierte Übungen", + "detail": "Genug Daten für erste Übungstendenzen, aber noch keine langfristigen Trends." + }, + { + "label": "Tabata-Format", + "value": "20/10 beibehalten", + "detail": "Die dokumentierten Holds bleiben im festen 20-Sekunden-Arbeitsintervall." + }, + { + "label": "Belastungsniveau", + "value": "überwiegend Startstufen", + "detail": "Passt zur Phase 1 mit Technikfokus und leichter Last." + } + ], + "exercise_updates": [ + { + "name": "Liegestütze", + "trend": "stable", + "current_level": "Inkline hoch", + "evidence": "Tag 1: 8/7/7/7/6/6/5; Tag 4: 8/7/7/6/6/6", + "next_action": "Stufe beibehalten und sauberen ROM sowie gleichmäßigere Wiederholungen priorisieren.", + "criterion": "Erst auf Inkline tiefer/Knie wechseln, wenn 2 Sessions in Folge alle vorgesehenen 20s-Intervalle sauber mit stabiler Körperspannung geschafft werden." + }, + { + "name": "Pike", + "trend": "up", + "current_level": "Pike Hold", + "evidence": "Tag 1 und Tag 4 jeweils 6x20 s dokumentiert.", + "next_action": "Optional nächste planmäßige Stufe „Hold + kleine Senkung“ innerhalb der 20s testen.", + "criterion": "Nur übernehmen, wenn 6/6 Intervalle ohne Hüftabsacken und ohne Technikverlust sauber bleiben." + }, + { + "name": "Einarm-Rudern ENG (KB)", + "trend": "up", + "current_level": "leicht, 4 kg", + "evidence": "Tag 2: 4kg, 9/9/8/8/8/9; Tag 4: 4kg, 9/9/9/8/8/9", + "next_action": "Bei sauberem Zug zur Hüfte planverträglich auf „etwas mehr“ bzw. leicht höhere Last wechseln.", + "criterion": "2 Sessions in Folge alle dokumentierten Intervalle je Seite kontrolliert mit mindestens ähnlicher Wiederholungszahl abschließen." + }, + { + "name": "KB Swing (zweiarmig)", + "trend": "unclear", + "current_level": "Dead-Stop, leicht; 10 kg", + "evidence": "Tag 6: 10kg, 15 dokumentiert.", + "next_action": "Dead-Stop-Technik beibehalten und pro 20s-Intervall genauer dokumentieren.", + "criterion": "Erst steigern, wenn alle Swing-Intervalle in 2 Sessions sauber als Hüft-Schnapp ohne Armheben dokumentiert sind." + } + ], + "plan_adjustments": [ + { + "target": "Woche 2 / Pike", + "suggested_change": "Progressionsstufe von „Pike Hold“ auf „Hold + kleine Senkung“ setzen.", + "reason": "Zwei dokumentierte Einheiten mit 6x20 s Pike Hold sprechen für einen vorsichtigen Variantenwechsel.", + "manual_step": "Im Trainingsplan-Modul bei Pike die nächste Progressionsstufe auswählen.", + "condition": "Nur wenn beide Pike-Einheiten technisch sauber waren; sonst Pike Hold beibehalten." + }, + { + "target": "Woche 2 / Einarm-Rudern ENG", + "suggested_change": "Von „leicht“ auf „etwas mehr“ wechseln, z.B. etwas höhere Last oder kontrollierterer Zug bei gleicher Tabata-Struktur.", + "reason": "Die Wiederholungen lagen in beiden Einheiten stabil bei etwa 8–9 pro Intervall.", + "manual_step": "Im Trainingsplan-Modul die nächste Rudern-ENG-Progression bzw. passende Last eintragen.", + "condition": "Nur wenn Rückenposition und Zugweg sauber bleiben; sonst 4 kg beibehalten." + }, + { + "target": "Woche 2 / Tag 5 leicht", + "suggested_change": "Tag 5 entweder wirklich leicht durchführen oder klar als nicht durchgeführt/Ruhe dokumentieren.", + "reason": "Diese Woche ist Tag 5 als completed markiert, enthält aber keine Übungen.", + "manual_step": "Im Trainingsplan-Modul den Status/Notiz für Tag 5 konsistent setzen.", + "condition": "Nur relevant, wenn Tag 5 erneut ausfällt; sonst normalen leichten Mobility-Tag beibehalten." + } + ], + "warnings": [ + "Tag 5 ist als abgeschlossen markiert, obwohl „Nicht durchgeführt“ notiert ist und alle Übungen offen sind.", + "Beim KB Swing reicht die Angabe „10kg, 15“ noch nicht, um Intervallqualität oder Technikprogression sicher zu bewerten." + ] + } + ], + "local_aggregates": { + "coverage_by_week": [ + { + "week": 1, + "sessions_with_data": 6, + "completed_sessions": 6, + "planned_sessions": 6, + "completion_rate_pct": 100, + "documented_exercises": 36 + } + ], + "overall": { + "sessions_with_data": 6, + "completed_sessions": 6, + "planned_sessions": 6, + "completion_rate_pct": 100, + "documented_exercises": 36 + }, + "current_progressions": [ + { + "progression_id": "ls", + "exercise": "Liegestütze", + "progression": "Inkline (hoch)", + "result": "8/7/7/6/6/6", + "note": "", + "session_key": "w01-d04" + }, + { + "progression_id": "dips", + "exercise": "Bank-Dips (Couch)", + "progression": "Knie stark gebeugt, klein", + "result": "8/7/7/7/6/6", + "note": "", + "session_key": "w01-d04" + }, + { + "progression_id": "pike", + "exercise": "Pike", + "progression": "Pike Hold", + "result": "6x20 s", + "note": "", + "session_key": "w01-d04" + }, + { + "progression_id": "lraise", + "exercise": "Seitheben (KB/Rucksack)", + "progression": "leicht (Technik)", + "result": "4kg, 6/6/5 je Seite", + "note": "", + "session_key": "w01-d01" + }, + { + "progression_id": "gobletkb", + "exercise": "Goblet Squat (KB)", + "progression": "leicht, Tiefe lernen", + "result": "8kg, 6/6/7/8/6/6", + "note": "", + "session_key": "w01-d06" + }, + { + "progression_id": "lunge", + "exercise": "Ausfallschritt rückw.", + "progression": "Körpergewicht (ggf. festhalten)", + "result": "3x4 je Seite", + "note": "", + "session_key": "w01-d01" + }, + { + "progression_id": "bridge", + "exercise": "Glute Bridge 1-beinig", + "progression": "beidbeinig", + "result": "6x20 s", + "note": "", + "session_key": "w01-d03" + }, + { + "progression_id": "calf", + "exercise": "Wadenheben", + "progression": "Körpergewicht", + "result": "10kg, 10/10/10/10/10/10", + "note": "", + "session_key": "w01-d01" + }, + { + "progression_id": "rowe", + "exercise": "Einarm-Rudern ENG (KB)", + "progression": "leicht", + "result": "4kg, 9/9/9/8/8/9", + "note": "", + "session_key": "w01-d04" + }, + { + "progression_id": "load", + "exercise": "Bizeps-Curls (KB/Rucksack)", + "progression": "leicht", + "result": "8kg, 7/7/7/7/7/8", + "note": "", + "session_key": "w01-d04" + }, + { + "progression_id": "row2", + "exercise": "Einarm-Rudern BREIT (KB)", + "progression": "leicht (Technik)", + "result": "4kg, 7/7/6/7/7/8", + "note": "", + "session_key": "w01-d04" + }, + { + "progression_id": "sup", + "exercise": "Y-T-W / Superman", + "progression": "Superman Hold", + "result": "6x20 s", + "note": "", + "session_key": "w01-d04" + }, + { + "progression_id": "plank", + "exercise": "Plank", + "progression": "auf Knien 15–20 s", + "result": "6x20 s", + "note": "", + "session_key": "w01-d02" + }, + { + "progression_id": "mtn", + "exercise": "Mountain Climbers", + "progression": "moderat", + "result": "6x20 s", + "note": "", + "session_key": "w01-d02" + }, + { + "progression_id": "db", + "exercise": "Dead Bug", + "progression": "Arme ODER Beine einzeln", + "result": "6x20 s", + "note": "", + "session_key": "w01-d02" + }, + { + "progression_id": "lr", + "exercise": "Beinheben", + "progression": "Knie gebeugt", + "result": "5/5/4/4/4/3", + "note": "", + "session_key": "w01-d02" + }, + { + "progression_id": "wall", + "exercise": "Wall Sit", + "progression": "15–25 s", + "result": "6x20 s", + "note": "", + "session_key": "w01-d03" + }, + { + "progression_id": "holl", + "exercise": "Hollow Hold", + "progression": "Tuck (Knie an)", + "result": "6x20 s", + "note": "", + "session_key": "w01-d03" + }, + { + "progression_id": "sp", + "exercise": "Side Plank (Wechsel)", + "progression": "auf Knien", + "result": "6x20s", + "note": "", + "session_key": "w01-d03" + }, + { + "progression_id": "bic", + "exercise": "Bicycle Crunch", + "progression": "langsam", + "result": "6x20s", + "note": "", + "session_key": "w01-d03" + }, + { + "progression_id": "tw", + "exercise": "Russian Twist (KB)", + "progression": "ohne Gewicht", + "result": "4kg, 4/4/4 je Seite", + "note": "", + "session_key": "w01-d03" + }, + { + "progression_id": "press", + "exercise": "KB Floor Press", + "progression": "leicht (Floor Press)", + "result": "10kg, 7/7/8/6/5/6", + "note": "", + "session_key": "w01-d06" + }, + { + "progression_id": "name:kniebeuge-leicht", + "exercise": "Kniebeuge (leicht)", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "name:liegestuetze-1-stufe-leichter", + "exercise": "Liegestütze (1 Stufe leichter)", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "name:rudern-leicht", + "exercise": "Rudern (leicht)", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "name:mountain-climbers-locker", + "exercise": "Mountain Climbers (locker)", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "name:inchworm", + "exercise": "Inchworm", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "name:cat-cow", + "exercise": "Cat-Cow", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "name:bird-dog", + "exercise": "Bird Dog", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "name:kb-halo-leicht", + "exercise": "KB-Halo (leicht)", + "progression": "", + "result": "", + "note": "", + "session_key": "w01-d05" + }, + { + "progression_id": "swing", + "exercise": "KB Swing (zweiarmig)", + "progression": "Dead-Stop, leicht (Technik)", + "result": "10kg, 15", + "note": "", + "session_key": "w01-d06" + }, + { + "progression_id": "kbdl", + "exercise": "KB-Deadlift (beidbeinig)", + "progression": "leicht, Technik", + "result": "10kg, 5/5/6/6/6/5", + "note": "", + "session_key": "w01-d06" + }, + { + "progression_id": "halo", + "exercise": "Halo", + "progression": "leicht, langsam", + "result": "4kg, 5/4/4 je Seite", + "note": "", + "session_key": "w01-d06" + }, + { + "progression_id": "row1", + "exercise": "Einarmiges Rudern (KB)", + "progression": "leicht", + "result": "6kg, 6/7/8 je Seite", + "note": "", + "session_key": "w01-d06" + }, + { + "progression_id": "carry", + "exercise": "Bottoms-up KB-Hold (Griff)", + "progression": "im Stand 10–15s", + "result": "4kg, 3x20 s je Seite", + "note": "", + "session_key": "w01-d06" + }, + { + "progression_id": "atw", + "exercise": "Around-the-World + Uppercut (KB)", + "progression": "nur Around-the-World, leicht", + "result": "4kg, 5/5/5 je Seite", + "note": "", + "session_key": "w01-d06" + } + ] + } + } + }, + "response": { + "headline": "Woche 1: konsequenter Start, Progression vorsichtig", + "summary": "Der 8-Wochen-Plan wurde in Woche 1 breit dokumentiert und überwiegend passend zur Fundament-Phase umgesetzt. Effektiv sind 5 von 6 geplanten Trainingstagen mit Leistungsdaten belegt, da Tag 5 zwar als abgeschlossen erscheint, aber ohne Übungen dokumentiert ist. Mehrere Holds und Zugübungen wirken stabil, während Liegestütze und Swing-Technik noch klar konsolidiert werden sollten. Das feste Tabata-Format 20/10 bleibt unverändert; Progression sollte nur über Varianten, Last, ROM, Tempo oder Technik erfolgen.", + "data_quality": "Die Aussagekraft ist für Woche 1 solide, aber wegen nur einer Trainingswoche und unvollständigem Tag 5 noch begrenzt.", + "metrics": [ + { + "label": "Effektive Durchführung", + "value": "5/6 Trainingstage mit Übungsdaten", + "detail": "Tag 5 ist ohne erledigte Übungen dokumentiert." + }, + { + "label": "Dokumentationsumfang", + "value": "36 dokumentierte Übungen", + "detail": "Genug für erste Tendenzen, noch nicht für langfristige Trends." + }, + { + "label": "Format-Treue", + "value": "Tabata 20/10", + "detail": "Die dokumentierten Holds und Intervalle passen zum festen Arbeitsintervall." + }, + { + "label": "Aktuelle Phase", + "value": "Phase 1 · Fundament", + "detail": "Startstufen und Technikfokus passen zum Planrahmen." + } + ], + "exercise_updates": [ + { + "name": "Liegestütze", + "trend": "stable", + "current_level": "Inkline hoch", + "evidence": "Tag 4: 8/7/7/6/6/6 nach Tag 1: 8/7/7/7/6/6/5.", + "next_action": "Stufe beibehalten und auf sauberen ROM sowie stabile Körperspannung in jedem 20s-Intervall achten.", + "criterion": "Erst auf Inkline tiefer/Knie wechseln, wenn 2 Sessions in Folge alle vorgesehenen Intervalle sauber über 20s gelingen." + }, + { + "name": "Pike", + "trend": "up", + "current_level": "Pike Hold", + "evidence": "Tag 1 und Tag 4 jeweils 6x20 s dokumentiert.", + "next_action": "Optional planmäßig „Hold + kleine Senkung“ innerhalb der 20s testen.", + "criterion": "Nur übernehmen, wenn 6/6 Intervalle ohne Hüftabsacken und ohne Technikverlust sauber bleiben." + }, + { + "name": "Einarm-Rudern ENG (KB)", + "trend": "up", + "current_level": "leicht, 4 kg", + "evidence": "Tag 4: 4kg, 9/9/9/8/8/9 nach Tag 2: 4kg, 9/9/8/8/8/9.", + "next_action": "Bei sauberem Zug zur Hüfte optional auf „etwas mehr“ bzw. leicht höhere Last wechseln.", + "criterion": "2 Sessions in Folge je Seite kontrolliert arbeiten und mindestens ähnliche Wiederholungszahlen halten." + }, + { + "name": "KB Swing (zweiarmig)", + "trend": "unclear", + "current_level": "Dead-Stop, leicht; 10 kg", + "evidence": "Tag 6: 10kg, 15 dokumentiert.", + "next_action": "Dead-Stop-Technik beibehalten und pro 20s-Intervall genauer dokumentieren.", + "criterion": "Erst steigern, wenn alle Swing-Intervalle in 2 Sessions sauber als Hüft-Schnapp ohne Armheben dokumentiert sind." + } + ], + "plan_adjustments": [ + { + "target": "Woche 2 / Pike", + "suggested_change": "Progressionsstufe von „Pike Hold“ auf „Hold + kleine Senkung“ setzen.", + "reason": "Zwei Einheiten mit 6x20 s Pike Hold sprechen für einen vorsichtigen Variantenwechsel.", + "manual_step": "Im Trainingsplan-Modul bei Pike die nächste Progressionsstufe auswählen.", + "condition": "Nur wenn beide Pike-Einheiten technisch sauber waren; sonst Pike Hold beibehalten." + }, + { + "target": "Woche 2 / Einarm-Rudern ENG", + "suggested_change": "Von „leicht“ auf „etwas mehr“ wechseln, z.B. leicht höhere Last oder kontrollierterer Zug bei gleicher Tabata-Struktur.", + "reason": "Die Wiederholungen lagen in beiden Einheiten stabil bei etwa 8–9 pro Intervall.", + "manual_step": "Im Trainingsplan-Modul die nächste Rudern-ENG-Progression oder passende Last eintragen.", + "condition": "Nur wenn Rückenposition und Zugweg sauber bleiben; sonst 4 kg beibehalten." + }, + { + "target": "Woche 2 / Tag 5 leicht", + "suggested_change": "Tag 5 entweder als leichten Mobility-Tag durchführen oder klar als Ruhetag/nicht durchgeführt dokumentieren.", + "reason": "In Woche 1 ist Tag 5 als completed markiert, enthält aber keine Übungsdaten.", + "manual_step": "Im Trainingsplan-Modul Status und Notiz für Tag 5 konsistent setzen.", + "condition": "Nur relevant, wenn Tag 5 erneut ausfällt; sonst den normalen leichten Tag beibehalten." + } + ], + "warnings": [ + "Tag 5 ist als abgeschlossen markiert, obwohl „Nicht durchgeführt“ notiert ist und keine Übungen erledigt sind.", + "Beim KB Swing reicht die Angabe „10kg, 15“ noch nicht, um Intervallqualität oder Technikprogression sicher zu bewerten." + ], + "conclusion": "Für Woche 2: solide weitermachen, nur einzelne saubere Progressionen testen und das 20/10-Format unverändert lassen." + }, + "visuals": { + "charts": [ + { + "title": "Liegestütze · Verlauf", + "type": "line", + "unit": "Reps", + "labels": [ + "W1/T1", + "W1/T4" + ], + "series": [ + { + "name": "Liegestütze", + "values": [ + 6.57, + 6.67 + ] + } + ], + "insight": "Aus den dokumentierten Ergebnisfeldern berechnet." + }, + { + "title": "Bank-Dips (Couch) · Verlauf", + "type": "line", + "unit": "Reps", + "labels": [ + "W1/T1", + "W1/T4" + ], + "series": [ + { + "name": "Bank-Dips (Couch)", + "values": [ + 6.83, + 6.83 + ] + } + ], + "insight": "Aus den dokumentierten Ergebnisfeldern berechnet." + } + ], + "flowcharts": [ + { + "title": "Liegestütze", + "nodes": [ + { + "id": "n1", + "label": "Inkline (hoch)", + "status": "current" + }, + { + "id": "n2", + "label": "Inkline tiefer / Knie", + "status": "next" + }, + { + "id": "n3", + "label": "Knie / Negativ", + "status": "" + }, + { + "id": "n4", + "label": "Negativ + erste Volle", + "status": "" + } + ], + "edges": [ + { + "from": "n1", + "to": "n2" + }, + { + "from": "n2", + "to": "n3" + }, + { + "from": "n3", + "to": "n4" + } + ], + "insight": "Dokumentierter Stand: Inkline (hoch)" + }, + { + "title": "Bank-Dips (Couch)", + "nodes": [ + { + "id": "n1", + "label": "Knie stark gebeugt, klein", + "status": "current" + }, + { + "id": "n2", + "label": "Knie gebeugt, voll", + "status": "next" + }, + { + "id": "n3", + "label": "Füße etwas weiter", + "status": "" + }, + { + "id": "n4", + "label": "Füße weiter weg", + "status": "" + } + ], + "edges": [ + { + "from": "n1", + "to": "n2" + }, + { + "from": "n2", + "to": "n3" + }, + { + "from": "n3", + "to": "n4" + } + ], + "insight": "Dokumentierter Stand: Knie stark gebeugt, klein" + }, + { + "title": "Pike", + "nodes": [ + { + "id": "n1", + "label": "Pike Hold", + "status": "current" + }, + { + "id": "n2", + "label": "Hold + kleine Senkung", + "status": "next" + }, + { + "id": "n3", + "label": "Push-up Hände erhöht", + "status": "" + }, + { + "id": "n4", + "label": "Push-up Boden, klein", + "status": "" + } + ], + "edges": [ + { + "from": "n1", + "to": "n2" + }, + { + "from": "n2", + "to": "n3" + }, + { + "from": "n3", + "to": "n4" + } + ], + "insight": "Dokumentierter Stand: Pike Hold" + } + ] + }, + "version": 1, + "status": "success", + "source_file": "phase-1.json", + "archive_file": "overall.json", + "sample_note": "Beispieldatensatz für die Android-Implementierung; in Version 2.0 wird diese Datei bei einer Neuanalyse überschrieben." +} diff --git a/testdata/analyses/phase-1/week-01.json b/testdata/analyses/phase-1/week-01.json new file mode 100644 index 0000000..5e4b02a --- /dev/null +++ b/testdata/analyses/phase-1/week-01.json @@ -0,0 +1,2493 @@ +{ + "id": "week-01_20260723T120111028996Z", + "type": "week", + "week": 1, + "created_at": "2026-07-23T12:01:11+00:00", + "model": "gpt-5.5", + "source_hash": "815dc58aad775b90e6302fb9a687d2c80c8c76f19b9008d0072953d1a160dee8", + "sessions_considered": 6, + "request": { + "scope": "week", + "schema": { + "headline": "Kurzer Titel", + "summary": "Kompakte Einordnung in höchstens 4 Sätzen", + "data_quality": "Ein kurzer Satz zur Aussagekraft", + "metrics": [ + { + "label": "Kennzahl", + "value": "Wert", + "detail": "kurze Einordnung" + } + ], + "exercise_updates": [ + { + "name": "Übung", + "trend": "up|stable|down|unclear", + "current_level": "dokumentierter Stand", + "evidence": "ein konkreter Beleg", + "next_action": "nächster planverträglicher Schritt", + "criterion": "messbares Kriterium" + } + ], + "plan_adjustments": [ + { + "target": "Woche/Tag/Übung oder Block", + "suggested_change": "konkreter Vorschlag", + "reason": "kurze Begründung", + "manual_step": "was im Trainingsplan-Modul manuell zu ändern wäre", + "condition": "nur wenn/sonst beibehalten" + } + ], + "warnings": [ + "nur echte Auffälligkeiten" + ], + "conclusion": "ein knapper Abschlusssatz" + }, + "messages": [ + { + "role": "system", + "content": "Du analysierst ein Trainingsprotokoll auf Deutsch. Der übergebene Trainingsplan ist bindend und muss die Empfehlungen bestimmen. Verändere niemals eigenmächtig Sessionformat, Arbeitsintervall, Pausen, Rundenzahl, Satzanzahl oder Progressionslogik. Bei Tabata oder anderen festen Intervallen darfst du insbesondere keine längeren Holds oder längere Arbeitszeiten empfehlen. Progression erfolgt dann nur planverträglich, etwa über die vorgesehene schwierigere Variante, Gewicht, ROM, Tempo oder Technik. Nutze ausschließlich die Daten im Input, erfinde keine Leistungen und gib keine medizinische Diagnose. Die Analyse soll kompakt bleiben: maximal 4 Kennzahlen, 4 Übungsupdates, 3 Plananpassungen und 2 Warnungen. Plananpassungen sind nur Vorschläge zur manuellen Übernahme im Trainingsplan-Modul; behaupte nie, den Plan geändert zu haben. Bewerte genau diese Planwoche und formuliere höchstens drei konkrete, optionale Anpassungen für die nächste Woche. Antworte ausschließlich als JSON im vorgegebenen Schema." + }, + { + "role": "user", + "content": "Gewünschtes JSON-Schema:\n{\"headline\": \"Kurzer Titel\", \"summary\": \"Kompakte Einordnung in höchstens 4 Sätzen\", \"data_quality\": \"Ein kurzer Satz zur Aussagekraft\", \"metrics\": [{\"label\": \"Kennzahl\", \"value\": \"Wert\", \"detail\": \"kurze Einordnung\"}], \"exercise_updates\": [{\"name\": \"Übung\", \"trend\": \"up|stable|down|unclear\", \"current_level\": \"dokumentierter Stand\", \"evidence\": \"ein konkreter Beleg\", \"next_action\": \"nächster planverträglicher Schritt\", \"criterion\": \"messbares Kriterium\"}], \"plan_adjustments\": [{\"target\": \"Woche/Tag/Übung oder Block\", \"suggested_change\": \"konkreter Vorschlag\", \"reason\": \"kurze Begründung\", \"manual_step\": \"was im Trainingsplan-Modul manuell zu ändern wäre\", \"condition\": \"nur wenn/sonst beibehalten\"}], \"warnings\": [\"nur echte Auffälligkeiten\"], \"conclusion\": \"ein knapper Abschlusssatz\"}\n\nAnalyse-Datensatz:\n{\"analysis_type\": \"week\", \"week\": 1, \"plan\": {\"title\": \"Calisthenics-Basis – 8-Wochen Komplettplan\", \"subtitle\": \"Blutiger Anfänger · 1,90 m · 95 kg · Rucksack + Couch + Kettlebell · tägliche ~30-Min Tabata-Session\", \"weeks\": 8, \"selected_week\": 1, \"selected_phase\": {\"name\": \"Phase 1 · Fundament\", \"instructions\": \"Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.\"}, \"training_format\": {\"mode\": \"tabata\", \"is_tabata\": true, \"fixed_interval\": true, \"work_seconds\": 20, \"rest_seconds\": 10, \"rounds\": 8, \"session_method\": \"Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.\", \"timer_note\": \"Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.\", \"constraints\": [\"Arbeitsintervalle bleiben fest bei 20 Sekunden; keine längeren Holds oder verlängerten Arbeitsintervalle empfehlen.\", \"Progression nur innerhalb des Planformats: planmäßige schwierigere Variante, mehr Gewicht, saubererer ROM/Tempo oder bessere Technik; Pausen und Rundenzahl nicht eigenmächtig ändern.\"]}, \"guidance\": {\"goals\": \"Du startest bei 2–3 Liegestützen und 0 Dips. Realistisch und ehrlich nach 8 Wochen sauberer Arbeit:\\n•  Liegestütze: von 2–3 auf erste saubere volle Reps (Rest in Knie-/Negativ-Form).\\n•  Bank-Dips: saubere Reps mit Füßen etwas weiter weg.   Pike Push-up: am Boden mit kleinem ROM.\\n•  Plank ~45–60 s, Wall Sit ~60 s, viele saubere Goblet-Kniebeugen, sauberer Swing + spürbar bessere Griffkraft.\\n•  Noch NICHT dran: Diamond Push-ups, L-Sit, Handstand, schwerer Turkish Get-Up, Snatch – Stoff für Woche 12+.\", \"goals_note\": \"Wichtig: Die Wochen-Stufen sind ein Rahmen, kein Muss. Bleib auf einer Stufe, solange sie fordert. Aufstiegs-Regel: erst hoch (oder schwerere Glocke), wenn du eine Übung 2 Sessions in Folge in allen Sätzen sauber über die vollen 20s schaffst. KB = Kettlebell bevorzugt; Rucksack bleibt als Alternative.\", \"session_method\": \"Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.\", \"timer_note\": \"Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.\", \"reminders\": [\"Form vor Reps. Wähle die Stufe / Glocke, bei der du die 20s kontrolliert durcharbeitest – mit 1–2 Reps Reserve.\", \"Swing-Technik first. Der Swing ist ein Hüft-Schnapp, KEIN Kniebeuge und KEIN Armheben. Erst Dead-Stop-Swings, bis die Hüftstreckung sitzt.\", \"Einseitige Übungen (Tag 2): Durchgang 1 = links, Durchgang 2 = rechts – so ist jede Seite automatisch ausbalanciert.\", \"Täglich ist viel. Tag 5 leicht, Tag 7 frei. Bei starkem Muskelkater einen Extra-Ruhetag – gehört zum Aufbau.\", \"Schlaf & Eiweiß. 7–9 h Schlaf, ~1,6 g Eiweiß pro kg. Stechender Schmerz = stoppen.\"]}, \"days\": [{\"day\": 1, \"focus\": \"Drücken + Beine\", \"light\": false, \"note\": \"\", \"warmup\": [\"Handflächen-Wippen\", \"Armkreisen/Wand-Slides\", \"Hüftkreisen + Beinschwünge\", \"Knie-über-Zeh an der Wand\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Türrahmen (Brust/Schulter, je 30–40s)\", \"Trizeps (je 30s)\", \"Seitheben/Lat-Seitdehnung (je 30s)\", \"Quad im Stand (je 30s)\", \"Knieender Hüftbeuger (je 30–40s)\", \"Wade an der Wand (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Liegestütze\", \"progression_id\": \"ls\", \"cue\": \"Brust kontrolliert senken & drücken\"}, {\"name\": \"Bank-Dips (Couch)\", \"progression_id\": \"dips\", \"cue\": \"Po vor der Couch senken, hochdrücken\"}, {\"name\": \"Pike\", \"progression_id\": \"pike\", \"cue\": \"umgek. V, Kopf senken (Schultern)\"}, {\"name\": \"Seitheben (KB/Rucksack)\", \"progression_id\": \"lraise\", \"cue\": \"Arme seitlich bis Schulterhöhe, kontrolliert\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Goblet Squat (KB/Rucksack)\", \"progression_id\": \"gobletkb\", \"cue\": \"Last umarmen, tief\"}, {\"name\": \"Ausfallschritt rückw.\", \"progression_id\": \"lunge\", \"cue\": \"großer Schritt, Knie Richtung Boden\"}, {\"name\": \"Glute Bridge\", \"progression_id\": \"bridge\", \"cue\": \"Hüfte hochdrücken, Po fest\"}, {\"name\": \"Wadenheben\", \"progression_id\": \"calf\", \"cue\": \"auf Zehenspitzen hoch/runter\"}]}]}, {\"day\": 2, \"focus\": \"Ziehen + Core\", \"light\": false, \"note\": \"\", \"warmup\": [\"Handflächen-Wippen\", \"Handtuch-Dislocates (8–10×)\", \"Cat-Cow\", \"Hüftkreisen + Beinschwünge\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Lat-/Seitdehnung (je 30s)\", \"Türrahmen (je 30–40s)\", \"BWS über die Couch (30–40s)\", \"Cobra/Sphinx (Bauch, 30s)\", \"Unterarm (je 20s)\", \"Knieender Hüftbeuger (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Einarm-Rudern ENG (KB)\", \"progression_id\": \"rowe\", \"cue\": \"1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts\"}, {\"name\": \"Bizeps-Curls (KB/Rucksack)\", \"progression_id\": \"load\", \"cue\": \"Oberarme fix, zu den Schultern\"}, {\"name\": \"Einarm-Rudern BREIT (KB)\", \"progression_id\": \"row2\", \"cue\": \"1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts\"}, {\"name\": \"Superman / Y-T-W\", \"progression_id\": \"sup\", \"cue\": \"bäuchlings Arme+Beine heben\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Plank\", \"progression_id\": \"plank\", \"cue\": \"gerade Linie halten\"}, {\"name\": \"Mountain Climbers\", \"progression_id\": \"mtn\", \"cue\": \"Knie zügig zur Brust\"}, {\"name\": \"Dead Bug\", \"progression_id\": \"db\", \"cue\": \"gegengleich, Rücken am Boden\"}, {\"name\": \"Beinheben\", \"progression_id\": \"lr\", \"cue\": \"Beine heben/senken\"}]}]}, {\"day\": 3, \"focus\": \"Beine + Core\", \"light\": false, \"note\": \"\", \"warmup\": [\"Hüftkreisen + Beinschwünge\", \"Knie-über-Zeh an der Wand\", \"Cat-Cow\", \"Handflächen-Wippen\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Hamstring-Dehnung (Couch, je 30–40s)\", \"Quad im Stand (je 30s)\", \"Knieender Hüftbeuger (je 30–40s)\", \"Figure-4/Taube (je 30–40s)\", \"Cobra + liegende Wirbelsäulen-Rotation (je 30s)\", \"Wade an der Wand (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Goblet Squat (KB/Rucksack)\", \"progression_id\": \"gobletkb\", \"cue\": \"Last umarmen, tief\"}, {\"name\": \"RDL / Hip Hinge (KB/Rucksack)\", \"progression_id\": \"load\", \"cue\": \"Hüfte zurück, Rücken gerade, Hamstrings\"}, {\"name\": \"Glute Bridge 1-beinig\", \"progression_id\": \"bridge\", \"cue\": \"ein Bein gestreckt\"}, {\"name\": \"Wall Sit\", \"progression_id\": \"wall\", \"cue\": \"Rücken an die Wand, halten\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Hollow Hold\", \"progression_id\": \"holl\", \"cue\": \"unterer Rücken am Boden\"}, {\"name\": \"Side Plank (Wechsel)\", \"progression_id\": \"sp\", \"cue\": \"seitlich auf einem Unterarm\"}, {\"name\": \"Bicycle Crunch\", \"progression_id\": \"bic\", \"cue\": \"Ellbogen zum Gegenknie\"}, {\"name\": \"Russian Twist (KB)\", \"progression_id\": \"tw\", \"cue\": \"sitzend Rumpf drehen\"}]}]}, {\"day\": 4, \"focus\": \"Oberkörper: Drücken + Ziehen\", \"light\": false, \"note\": \"\", \"warmup\": [\"Handflächen-Wippen\", \"Handtuch-Dislocates (8–10×)\", \"Armkreisen/Wand-Slides\", \"Cat-Cow\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Türrahmen (je 30–40s)\", \"Trizeps (je 30s)\", \"Lat-/Seitdehnung (je 30s)\", \"BWS über die Couch (30–40s)\", \"Unterarm (je 20s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Liegestütze\", \"progression_id\": \"ls\", \"cue\": \"Brust kontrolliert senken & drücken\"}, {\"name\": \"Bank-Dips (Couch)\", \"progression_id\": \"dips\", \"cue\": \"Po vor der Couch senken\"}, {\"name\": \"Pike\", \"progression_id\": \"pike\", \"cue\": \"umgek. V, Kopf senken\"}, {\"name\": \"KB Floor Press\", \"progression_id\": \"press\", \"cue\": \"Glocke vom Boden weg drücken\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Einarm-Rudern ENG (KB)\", \"progression_id\": \"rowe\", \"cue\": \"1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts\"}, {\"name\": \"Bizeps-Curls (KB/Rucksack)\", \"progression_id\": \"load\", \"cue\": \"Oberarme fix, zu den Schultern\"}, {\"name\": \"Einarm-Rudern BREIT (KB)\", \"progression_id\": \"row2\", \"cue\": \"1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts\"}, {\"name\": \"Y-T-W / Superman\", \"progression_id\": \"sup\", \"cue\": \"bäuchlings, Rücken\"}]}]}, {\"day\": 5, \"focus\": \"Ganzkörper LEICHT + Mobility\", \"light\": true, \"note\": \"\", \"warmup\": [\"Cat-Cow\", \"Hüftkreisen + Beinschwünge\", \"Armkreisen/Wand-Slides\", \"Knie-über-Zeh an der Wand\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Türrahmen (je 30s)\", \"Knieender Hüftbeuger (je 30s)\", \"BWS über die Couch (30–40s)\", \"Figure-4/Taube (je 30s)\", \"Wade an der Wand (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"Kniebeuge (leicht)\", \"progression_id\": \"name:kniebeuge-leicht\", \"cue\": \"locker, sauber\"}, {\"name\": \"Liegestütze (1 Stufe leichter)\", \"progression_id\": \"name:liegestuetze-1-stufe-leichter\", \"cue\": \"leichtere Variante\"}, {\"name\": \"Rudern (leicht)\", \"progression_id\": \"name:rudern-leicht\", \"cue\": \"wenig Gewicht\"}, {\"name\": \"Mountain Climbers (locker)\", \"progression_id\": \"name:mountain-climbers-locker\", \"cue\": \"ruhiges Tempo\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"Inchworm\", \"progression_id\": \"name:inchworm\", \"cue\": \"in den Stütz wandern, zurück\"}, {\"name\": \"Cat-Cow\", \"progression_id\": \"name:cat-cow\", \"cue\": \"Rücken ründen/strecken\"}, {\"name\": \"Bird Dog\", \"progression_id\": \"name:bird-dog\", \"cue\": \"gegengleich Arm+Bein, kurz halten\"}, {\"name\": \"KB-Halo (leicht)\", \"progression_id\": \"name:kb-halo-leicht\", \"cue\": \"langsam um den Kopf kreisen\"}]}]}, {\"day\": 6, \"focus\": \"KETTLEBELL Ganzkörper\", \"light\": false, \"note\": \"Reine Kettlebell-Session. Swing = Hüft-Schnapp, kein Armheben. Glocke so wählen, dass alle Reps sauber sind.\", \"warmup\": [\"Halo (leicht)\", \"Hüftkreisen + Beinschwünge\", \"Cat-Cow\", \"Handflächen-Wippen\"], \"cooldown\": [\"Ausschütteln + Gehen (60–90s)\", \"Box-Atmung (4–5 Runden)\"], \"stretch\": [\"Hamstring-Dehnung (Couch, je 30–40s)\", \"Knieender Hüftbeuger (je 30–40s)\", \"Figure-4/Taube (je 30–40s)\", \"Unterarm (je 20s)\", \"BWS über die Couch (30–40s)\", \"Lat-/Seitdehnung (je 30s)\"], \"blocks\": [{\"label\": \"ROTATION A — 3 Sätze\", \"exercises\": [{\"name\": \"KB Swing (zweiarmig)\", \"progression_id\": \"swing\", \"cue\": \"Hüft-Schnapp, kein Armheben\"}, {\"name\": \"Goblet Squat (KB)\", \"progression_id\": \"gobletkb\", \"cue\": \"Glocke vor der Brust, tief\"}, {\"name\": \"KB-Deadlift (beidbeinig)\", \"progression_id\": \"kbdl\", \"cue\": \"Glocke zwischen den Füßen, Hüfte hinge\"}, {\"name\": \"Halo\", \"progression_id\": \"halo\", \"cue\": \"eng um den Kopf kreisen\"}]}, {\"label\": \"ROTATION B — 3 Sätze\", \"exercises\": [{\"name\": \"KB Floor Press\", \"progression_id\": \"press\", \"cue\": \"Glocke vom Boden weg drücken\"}, {\"name\": \"Einarmiges Rudern (KB)\", \"progression_id\": \"row1\", \"cue\": \"Rücken gerade, zum Bauch\"}, {\"name\": \"Bottoms-up KB-Hold (Griff)\", \"progression_id\": \"carry\", \"cue\": \"Glocke Boden nach oben, Unterarm senkrecht, still halten\"}, {\"name\": \"Around-the-World + Uppercut (KB)\", \"progression_id\": \"atw\", \"cue\": \"Glocke um den Körper, dann diagonal zur Gegen-Schulter, andere Hand fängt\"}]}]}], \"phases\": [{\"name\": \"Phase 1 · Fundament\", \"instructions\": \"Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.\", \"start_week\": 1, \"end_week\": 2}, {\"name\": \"Phase 2 · Aufbau\", \"instructions\": \"Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.\", \"start_week\": 3, \"end_week\": 4}, {\"name\": \"Phase 3 · Steigerung\", \"instructions\": \"Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.\", \"start_week\": 5, \"end_week\": 6}, {\"name\": \"Phase 4 · Festigen\", \"instructions\": \"Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.\", \"start_week\": 7, \"end_week\": 8}], \"progression_stages\": {\"atw\": {\"name\": \"Around-the-World + Uppercut (KB)\", \"steps\": [\"nur Around-the-World, leicht\", \"Around-the-World, mehr Reps\", \"+ Uppercut, leicht\", \"+ Uppercut, flüssig\"]}, \"bic\": {\"name\": \"Bicycle Crunch\", \"steps\": [\"langsam\", \"moderat\", \"moderat\", \"zügig\"]}, \"bridge\": {\"name\": \"Glute Bridge\", \"steps\": [\"beidbeinig\", \"beidbeinig, 2s halten\", \"einbeinig\", \"einbeinig, 2s halten\"]}, \"calf\": {\"name\": \"Wadenheben\", \"steps\": [\"Körpergewicht\", \"langsam\", \"langsam, mehr Reps\", \"1-beinig\"]}, \"carry\": {\"name\": \"Bottoms-up KB-Hold (Griff)\", \"steps\": [\"im Stand 10–15s\", \"15–20s, ruhig\", \"bis Schulterhöhe 15s\", \"länger / ein paar Schritte\"]}, \"db\": {\"name\": \"Dead Bug\", \"steps\": [\"Arme ODER Beine einzeln\", \"gegengleich\", \"gegengleich + Pause\", \"Beine weiter gestreckt\"]}, \"dips\": {\"name\": \"Bank-Dips (Couch)\", \"steps\": [\"Knie stark gebeugt, klein\", \"Knie gebeugt, voll\", \"Füße etwas weiter\", \"Füße weiter weg\"]}, \"gobletkb\": {\"name\": \"Goblet Squat (KB/Rucksack)\", \"steps\": [\"leicht, Tiefe lernen\", \"leicht, mehr Reps\", \"mittel\", \"mittel, 3s runter\"]}, \"halo\": {\"name\": \"Halo\", \"steps\": [\"leicht, langsam\", \"leicht\", \"mittel\", \"mittel, langsam\"]}, \"holl\": {\"name\": \"Hollow Hold\", \"steps\": [\"Tuck (Knie an)\", \"Tuck, länger\", \"halb gestreckt\", \"halb/voll\"]}, \"kbdl\": {\"name\": \"KB-Deadlift (beidbeinig)\", \"steps\": [\"leicht, Technik\", \"leicht, mehr Reps\", \"mittel\", \"schwerer, kontrolliert\"]}, \"load\": {\"name\": \"Bizeps-Curls (KB/Rucksack)\", \"steps\": [\"leicht\", \"etwas mehr\", \"mehr\", \"fordernd\"]}, \"lr\": {\"name\": \"Beinheben\", \"steps\": [\"Knie gebeugt\", \"Knie gebeugt, langsam\", \"halb gestreckt\", \"gestreckt\"]}, \"lraise\": {\"name\": \"Seitheben (KB/Rucksack)\", \"steps\": [\"leicht (Technik)\", \"leicht, mehr Reps\", \"mittel\", \"mittel, langsam\"]}, \"ls\": {\"name\": \"Liegestütze\", \"steps\": [\"Inkline (hoch)\", \"Inkline tiefer / Knie\", \"Knie / Negativ\", \"Negativ + erste Volle\"]}, \"lunge\": {\"name\": \"Ausfallschritt rückw.\", \"steps\": [\"Körpergewicht (ggf. festhalten)\", \"Körpergewicht\", \"mehr Reps\", \"+ Last leicht\"]}, \"mtn\": {\"name\": \"Mountain Climbers\", \"steps\": [\"moderat\", \"moderat\", \"zügig\", \"zügig\"]}, \"pike\": {\"name\": \"Pike\", \"steps\": [\"Pike Hold\", \"Hold + kleine Senkung\", \"Push-up Hände erhöht\", \"Push-up Boden, klein\"]}, \"plank\": {\"name\": \"Plank\", \"steps\": [\"auf Knien 15–20 s\", \"voll 20–30 s\", \"voll + Schulter-Taps\", \"voll, länger\"]}, \"press\": {\"name\": \"KB Floor Press\", \"steps\": [\"leicht (Floor Press)\", \"Floor Press mehr\", \"Overhead leicht\", \"Overhead mittel\"]}, \"row1\": {\"name\": \"Einarmiges Rudern (KB)\", \"steps\": [\"leicht\", \"etwas mehr\", \"einarmig leicht\", \"einarmig mittel\"]}, \"row2\": {\"name\": \"Einarm-Rudern BREIT (KB)\", \"steps\": [\"leicht (Technik)\", \"leicht, mehr Reps\", \"mittel\", \"mittel, Pause oben\"]}, \"rowe\": {\"name\": \"Einarm-Rudern ENG (KB)\", \"steps\": [\"leicht\", \"etwas mehr\", \"mehr\", \"fordernd\"]}, \"sp\": {\"name\": \"Side Plank (Wechsel)\", \"steps\": [\"auf Knien\", \"auf Knien, länger\", \"voll\", \"voll, länger\"]}, \"sup\": {\"name\": \"Superman / Y-T-W\", \"steps\": [\"Superman Hold\", \"Superman Hold länger\", \"Reverse Snow Angels\", \"Y-T-W Raises\"]}, \"swing\": {\"name\": \"KB Swing (zweiarmig)\", \"steps\": [\"Dead-Stop, leicht (Technik)\", \"Dead-Stop, mehr Reps\", \"durchgehend, mittel\", \"durchgehend, schwerer\"]}, \"tw\": {\"name\": \"Russian Twist (KB)\", \"steps\": [\"ohne Gewicht\", \"ohne Gewicht\", \"KB leicht\", \"KB\"]}, \"wall\": {\"name\": \"Wall Sit\", \"steps\": [\"15–25 s\", \"25–40 s\", \"40–55 s\", \"55–70 s\"]}}}, \"profile\": {\"display_name\": \"\", \"start_date\": \"\", \"plan_notes\": \"\"}, \"previous_end_state\": [], \"local_metrics\": {\"sessions_with_data\": 6, \"completed_sessions\": 6, \"planned_sessions\": 6, \"completion_rate_pct\": 100, \"documented_exercises\": 36}, \"sessions\": [{\"session_key\": \"w01-d01\", \"week\": 1, \"day\": 1, \"date\": \"\", \"focus\": \"Drücken + Beine\", \"light\": false, \"phase\": \"Phase 1 · Fundament\", \"phase_instructions\": \"Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.\", \"status\": \"completed\", \"started_at\": \"2026-07-23T10:01:58.783Z\", \"completed_at\": \"2026-07-23T10:01:58.783Z\", \"session_note\": \"\", \"section_notes\": {}, \"items\": [{\"item_id\": \"cooldown-0\", \"exercise\": \"Ausschütteln + Gehen (60–90s)\", \"progression_id\": \"\", \"progression\": \"\", \"result\": \"\", \"note\": \"\", \"done\": true, \"cue\": \"\", \"rotation\": \"cooldown\", \"kind\": \"cooldown\"}, {\"item_id\": \"cooldown-1\", \"exercise\": \"Box-Atmung (4–5 Runden)\", \"progression_id\": \"\", \"progression\": \"\", \"result\": \"\", \"note\": \"\", \"done\": true, \"cue\": \"\", \"rotation\": \"cooldown\", \"kind\": \"cooldown\"}, {\"item_id\": \"d1-r0-e0\", \"exercise\": \"Liegestütze\", \"progression_id\": \"ls\", \"progression\": \"Inkline (hoch)\", \"result\": \"8/7/7/7/6/6/5\", \"note\": \"\", \"done\": true, \"cue\": \"Brust kontrolliert senken & drücken\", \"rotation\": \"ROTATION A — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"d1-r0-e1\", \"exercise\": \"Bank-Dips (Couch)\", \"progression_id\": \"dips\", \"progression\": \"Knie stark gebeugt, klein\", \"result\": \"8/7/7/7/6/6\", \"note\": \"\", \"done\": true, \"cue\": \"Po vor der Couch senken, hochdrücken\", \"rotation\": \"ROTATION A — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"d1-r0-e2\", \"exercise\": \"Pike\", \"progression_id\": \"pike\", \"progression\": \"Pike Hold\", \"result\": \"6x20 s\", \"note\": \"\", \"done\": true, \"cue\": \"umgek. V, Kopf senken (Schultern)\", \"rotation\": \"ROTATION A — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"d1-r0-e3\", \"exercise\": \"Seitheben (KB/Rucksack)\", \"progression_id\": \"lraise\", \"progression\": \"leicht (Technik)\", \"result\": \"4kg, 6/6/5 je Seite\", \"note\": \"\", \"done\": true, \"cue\": \"Arme seitlich bis Schulterhöhe, kontrolliert\", \"rotation\": \"ROTATION A — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"d1-r1-e0\", \"exercise\": \"Goblet Squat (KB/Rucksack)\", \"progression_id\": \"gobletkb\", \"progression\": \"leicht, Tiefe lernen\", \"result\": \"0kg, 8/8/7/7/6/7\", \"note\": \"\", \"done\": true, \"cue\": \"Last umarmen, tief\", \"rotation\": \"ROTATION B — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"d1-r1-e1\", \"exercise\": \"Ausfallschritt rückw.\", \"progression_id\": \"lunge\", \"progression\": \"Körpergewicht (ggf. festhalten)\", \"result\": \"3x4 je Seite\", \"note\": \"\", \"done\": true, \"cue\": \"großer Schritt, Knie Richtung Boden\", \"rotation\": \"ROTATION B — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"d1-r1-e2\", \"exercise\": \"Glute Bridge\", \"progression_id\": \"bridge\", \"progression\": \"beidbeinig\", \"result\": \"6x 20s\", \"note\": \"\", \"done\": true, \"cue\": \"Hüfte hochdrücken, Po fest\", \"rotation\": \"ROTATION B — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"d1-r1-e3\", \"exercise\": \"Wadenheben\", \"progression_id\": \"calf\", \"progression\": \"Körpergewicht\", \"result\": \"10kg, 10/10/10/10/10/10\", \"note\": \"\", \"done\": true, \"cue\": \"auf Zehenspitzen hoch/runter\", \"rotation\": \"ROTATION B — 3 Sätze\", \"kind\": \"exercise\"}, {\"item_id\": \"stretch-0\", \"exercise\": \"Türrahmen (Brust/Schulter, je 30–40s)\", \"progression_id\": \"\", \"progression\": \"\", \"result\": \"\", \"note\": \"\", \"done\": true, \"cue\": \"\", \"rotation\": \"stretch\", \"kind\": \"stretch\"}, {\"item_id\"" + } + ], + "dataset": { + "analysis_type": "week", + "week": 1, + "plan": { + "title": "Calisthenics-Basis – 8-Wochen Komplettplan", + "subtitle": "Blutiger Anfänger · 1,90 m · 95 kg · Rucksack + Couch + Kettlebell · tägliche ~30-Min Tabata-Session", + "weeks": 8, + "selected_week": 1, + "selected_phase": { + "name": "Phase 1 · Fundament", + "instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation." + }, + "training_format": { + "mode": "tabata", + "is_tabata": true, + "fixed_interval": true, + "work_seconds": 20, + "rest_seconds": 10, + "rounds": 8, + "session_method": "Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.", + "timer_note": "Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.", + "constraints": [ + "Arbeitsintervalle bleiben fest bei 20 Sekunden; keine längeren Holds oder verlängerten Arbeitsintervalle empfehlen.", + "Progression nur innerhalb des Planformats: planmäßige schwierigere Variante, mehr Gewicht, saubererer ROM/Tempo oder bessere Technik; Pausen und Rundenzahl nicht eigenmächtig ändern." + ] + }, + "guidance": { + "goals": "Du startest bei 2–3 Liegestützen und 0 Dips. Realistisch und ehrlich nach 8 Wochen sauberer Arbeit:\n•  Liegestütze: von 2–3 auf erste saubere volle Reps (Rest in Knie-/Negativ-Form).\n•  Bank-Dips: saubere Reps mit Füßen etwas weiter weg.   Pike Push-up: am Boden mit kleinem ROM.\n•  Plank ~45–60 s, Wall Sit ~60 s, viele saubere Goblet-Kniebeugen, sauberer Swing + spürbar bessere Griffkraft.\n•  Noch NICHT dran: Diamond Push-ups, L-Sit, Handstand, schwerer Turkish Get-Up, Snatch – Stoff für Woche 12+.", + "goals_note": "Wichtig: Die Wochen-Stufen sind ein Rahmen, kein Muss. Bleib auf einer Stufe, solange sie fordert. Aufstiegs-Regel: erst hoch (oder schwerere Glocke), wenn du eine Übung 2 Sessions in Folge in allen Sätzen sauber über die vollen 20s schaffst. KB = Kettlebell bevorzugt; Rucksack bleibt als Alternative.", + "session_method": "Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.", + "timer_note": "Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10.", + "reminders": [ + "Form vor Reps. Wähle die Stufe / Glocke, bei der du die 20s kontrolliert durcharbeitest – mit 1–2 Reps Reserve.", + "Swing-Technik first. Der Swing ist ein Hüft-Schnapp, KEIN Kniebeuge und KEIN Armheben. Erst Dead-Stop-Swings, bis die Hüftstreckung sitzt.", + "Einseitige Übungen (Tag 2): Durchgang 1 = links, Durchgang 2 = rechts – so ist jede Seite automatisch ausbalanciert.", + "Täglich ist viel. Tag 5 leicht, Tag 7 frei. Bei starkem Muskelkater einen Extra-Ruhetag – gehört zum Aufbau.", + "Schlaf & Eiweiß. 7–9 h Schlaf, ~1,6 g Eiweiß pro kg. Stechender Schmerz = stoppen." + ] + }, + "days": [ + { + "day": 1, + "focus": "Drücken + Beine", + "light": false, + "note": "", + "warmup": [ + "Handflächen-Wippen", + "Armkreisen/Wand-Slides", + "Hüftkreisen + Beinschwünge", + "Knie-über-Zeh an der Wand" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Türrahmen (Brust/Schulter, je 30–40s)", + "Trizeps (je 30s)", + "Seitheben/Lat-Seitdehnung (je 30s)", + "Quad im Stand (je 30s)", + "Knieender Hüftbeuger (je 30–40s)", + "Wade an der Wand (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Liegestütze", + "progression_id": "ls", + "cue": "Brust kontrolliert senken & drücken" + }, + { + "name": "Bank-Dips (Couch)", + "progression_id": "dips", + "cue": "Po vor der Couch senken, hochdrücken" + }, + { + "name": "Pike", + "progression_id": "pike", + "cue": "umgek. V, Kopf senken (Schultern)" + }, + { + "name": "Seitheben (KB/Rucksack)", + "progression_id": "lraise", + "cue": "Arme seitlich bis Schulterhöhe, kontrolliert" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "cue": "Last umarmen, tief" + }, + { + "name": "Ausfallschritt rückw.", + "progression_id": "lunge", + "cue": "großer Schritt, Knie Richtung Boden" + }, + { + "name": "Glute Bridge", + "progression_id": "bridge", + "cue": "Hüfte hochdrücken, Po fest" + }, + { + "name": "Wadenheben", + "progression_id": "calf", + "cue": "auf Zehenspitzen hoch/runter" + } + ] + } + ] + }, + { + "day": 2, + "focus": "Ziehen + Core", + "light": false, + "note": "", + "warmup": [ + "Handflächen-Wippen", + "Handtuch-Dislocates (8–10×)", + "Cat-Cow", + "Hüftkreisen + Beinschwünge" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Lat-/Seitdehnung (je 30s)", + "Türrahmen (je 30–40s)", + "BWS über die Couch (30–40s)", + "Cobra/Sphinx (Bauch, 30s)", + "Unterarm (je 20s)", + "Knieender Hüftbeuger (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts" + }, + { + "name": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "cue": "Oberarme fix, zu den Schultern" + }, + { + "name": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts" + }, + { + "name": "Superman / Y-T-W", + "progression_id": "sup", + "cue": "bäuchlings Arme+Beine heben" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Plank", + "progression_id": "plank", + "cue": "gerade Linie halten" + }, + { + "name": "Mountain Climbers", + "progression_id": "mtn", + "cue": "Knie zügig zur Brust" + }, + { + "name": "Dead Bug", + "progression_id": "db", + "cue": "gegengleich, Rücken am Boden" + }, + { + "name": "Beinheben", + "progression_id": "lr", + "cue": "Beine heben/senken" + } + ] + } + ] + }, + { + "day": 3, + "focus": "Beine + Core", + "light": false, + "note": "", + "warmup": [ + "Hüftkreisen + Beinschwünge", + "Knie-über-Zeh an der Wand", + "Cat-Cow", + "Handflächen-Wippen" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Hamstring-Dehnung (Couch, je 30–40s)", + "Quad im Stand (je 30s)", + "Knieender Hüftbeuger (je 30–40s)", + "Figure-4/Taube (je 30–40s)", + "Cobra + liegende Wirbelsäulen-Rotation (je 30s)", + "Wade an der Wand (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "cue": "Last umarmen, tief" + }, + { + "name": "RDL / Hip Hinge (KB/Rucksack)", + "progression_id": "load", + "cue": "Hüfte zurück, Rücken gerade, Hamstrings" + }, + { + "name": "Glute Bridge 1-beinig", + "progression_id": "bridge", + "cue": "ein Bein gestreckt" + }, + { + "name": "Wall Sit", + "progression_id": "wall", + "cue": "Rücken an die Wand, halten" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Hollow Hold", + "progression_id": "holl", + "cue": "unterer Rücken am Boden" + }, + { + "name": "Side Plank (Wechsel)", + "progression_id": "sp", + "cue": "seitlich auf einem Unterarm" + }, + { + "name": "Bicycle Crunch", + "progression_id": "bic", + "cue": "Ellbogen zum Gegenknie" + }, + { + "name": "Russian Twist (KB)", + "progression_id": "tw", + "cue": "sitzend Rumpf drehen" + } + ] + } + ] + }, + { + "day": 4, + "focus": "Oberkörper: Drücken + Ziehen", + "light": false, + "note": "", + "warmup": [ + "Handflächen-Wippen", + "Handtuch-Dislocates (8–10×)", + "Armkreisen/Wand-Slides", + "Cat-Cow" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Türrahmen (je 30–40s)", + "Trizeps (je 30s)", + "Lat-/Seitdehnung (je 30s)", + "BWS über die Couch (30–40s)", + "Unterarm (je 20s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Liegestütze", + "progression_id": "ls", + "cue": "Brust kontrolliert senken & drücken" + }, + { + "name": "Bank-Dips (Couch)", + "progression_id": "dips", + "cue": "Po vor der Couch senken" + }, + { + "name": "Pike", + "progression_id": "pike", + "cue": "umgek. V, Kopf senken" + }, + { + "name": "KB Floor Press", + "progression_id": "press", + "cue": "Glocke vom Boden weg drücken" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts" + }, + { + "name": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "cue": "Oberarme fix, zu den Schultern" + }, + { + "name": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts" + }, + { + "name": "Y-T-W / Superman", + "progression_id": "sup", + "cue": "bäuchlings, Rücken" + } + ] + } + ] + }, + { + "day": 5, + "focus": "Ganzkörper LEICHT + Mobility", + "light": true, + "note": "", + "warmup": [ + "Cat-Cow", + "Hüftkreisen + Beinschwünge", + "Armkreisen/Wand-Slides", + "Knie-über-Zeh an der Wand" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Türrahmen (je 30s)", + "Knieender Hüftbeuger (je 30s)", + "BWS über die Couch (30–40s)", + "Figure-4/Taube (je 30s)", + "Wade an der Wand (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "Kniebeuge (leicht)", + "progression_id": "name:kniebeuge-leicht", + "cue": "locker, sauber" + }, + { + "name": "Liegestütze (1 Stufe leichter)", + "progression_id": "name:liegestuetze-1-stufe-leichter", + "cue": "leichtere Variante" + }, + { + "name": "Rudern (leicht)", + "progression_id": "name:rudern-leicht", + "cue": "wenig Gewicht" + }, + { + "name": "Mountain Climbers (locker)", + "progression_id": "name:mountain-climbers-locker", + "cue": "ruhiges Tempo" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "Inchworm", + "progression_id": "name:inchworm", + "cue": "in den Stütz wandern, zurück" + }, + { + "name": "Cat-Cow", + "progression_id": "name:cat-cow", + "cue": "Rücken ründen/strecken" + }, + { + "name": "Bird Dog", + "progression_id": "name:bird-dog", + "cue": "gegengleich Arm+Bein, kurz halten" + }, + { + "name": "KB-Halo (leicht)", + "progression_id": "name:kb-halo-leicht", + "cue": "langsam um den Kopf kreisen" + } + ] + } + ] + }, + { + "day": 6, + "focus": "KETTLEBELL Ganzkörper", + "light": false, + "note": "Reine Kettlebell-Session. Swing = Hüft-Schnapp, kein Armheben. Glocke so wählen, dass alle Reps sauber sind.", + "warmup": [ + "Halo (leicht)", + "Hüftkreisen + Beinschwünge", + "Cat-Cow", + "Handflächen-Wippen" + ], + "cooldown": [ + "Ausschütteln + Gehen (60–90s)", + "Box-Atmung (4–5 Runden)" + ], + "stretch": [ + "Hamstring-Dehnung (Couch, je 30–40s)", + "Knieender Hüftbeuger (je 30–40s)", + "Figure-4/Taube (je 30–40s)", + "Unterarm (je 20s)", + "BWS über die Couch (30–40s)", + "Lat-/Seitdehnung (je 30s)" + ], + "blocks": [ + { + "label": "ROTATION A — 3 Sätze", + "exercises": [ + { + "name": "KB Swing (zweiarmig)", + "progression_id": "swing", + "cue": "Hüft-Schnapp, kein Armheben" + }, + { + "name": "Goblet Squat (KB)", + "progression_id": "gobletkb", + "cue": "Glocke vor der Brust, tief" + }, + { + "name": "KB-Deadlift (beidbeinig)", + "progression_id": "kbdl", + "cue": "Glocke zwischen den Füßen, Hüfte hinge" + }, + { + "name": "Halo", + "progression_id": "halo", + "cue": "eng um den Kopf kreisen" + } + ] + }, + { + "label": "ROTATION B — 3 Sätze", + "exercises": [ + { + "name": "KB Floor Press", + "progression_id": "press", + "cue": "Glocke vom Boden weg drücken" + }, + { + "name": "Einarmiges Rudern (KB)", + "progression_id": "row1", + "cue": "Rücken gerade, zum Bauch" + }, + { + "name": "Bottoms-up KB-Hold (Griff)", + "progression_id": "carry", + "cue": "Glocke Boden nach oben, Unterarm senkrecht, still halten" + }, + { + "name": "Around-the-World + Uppercut (KB)", + "progression_id": "atw", + "cue": "Glocke um den Körper, dann diagonal zur Gegen-Schulter, andere Hand fängt" + } + ] + } + ] + } + ], + "phases": [ + { + "name": "Phase 1 · Fundament", + "instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "start_week": 1, + "end_week": 2 + }, + { + "name": "Phase 2 · Aufbau", + "instructions": "Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.", + "start_week": 3, + "end_week": 4 + }, + { + "name": "Phase 3 · Steigerung", + "instructions": "Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.", + "start_week": 5, + "end_week": 6 + }, + { + "name": "Phase 4 · Festigen", + "instructions": "Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.", + "start_week": 7, + "end_week": 8 + } + ], + "progression_stages": { + "atw": { + "name": "Around-the-World + Uppercut (KB)", + "steps": [ + "nur Around-the-World, leicht", + "Around-the-World, mehr Reps", + "+ Uppercut, leicht", + "+ Uppercut, flüssig" + ] + }, + "bic": { + "name": "Bicycle Crunch", + "steps": [ + "langsam", + "moderat", + "moderat", + "zügig" + ] + }, + "bridge": { + "name": "Glute Bridge", + "steps": [ + "beidbeinig", + "beidbeinig, 2s halten", + "einbeinig", + "einbeinig, 2s halten" + ] + }, + "calf": { + "name": "Wadenheben", + "steps": [ + "Körpergewicht", + "langsam", + "langsam, mehr Reps", + "1-beinig" + ] + }, + "carry": { + "name": "Bottoms-up KB-Hold (Griff)", + "steps": [ + "im Stand 10–15s", + "15–20s, ruhig", + "bis Schulterhöhe 15s", + "länger / ein paar Schritte" + ] + }, + "db": { + "name": "Dead Bug", + "steps": [ + "Arme ODER Beine einzeln", + "gegengleich", + "gegengleich + Pause", + "Beine weiter gestreckt" + ] + }, + "dips": { + "name": "Bank-Dips (Couch)", + "steps": [ + "Knie stark gebeugt, klein", + "Knie gebeugt, voll", + "Füße etwas weiter", + "Füße weiter weg" + ] + }, + "gobletkb": { + "name": "Goblet Squat (KB/Rucksack)", + "steps": [ + "leicht, Tiefe lernen", + "leicht, mehr Reps", + "mittel", + "mittel, 3s runter" + ] + }, + "halo": { + "name": "Halo", + "steps": [ + "leicht, langsam", + "leicht", + "mittel", + "mittel, langsam" + ] + }, + "holl": { + "name": "Hollow Hold", + "steps": [ + "Tuck (Knie an)", + "Tuck, länger", + "halb gestreckt", + "halb/voll" + ] + }, + "kbdl": { + "name": "KB-Deadlift (beidbeinig)", + "steps": [ + "leicht, Technik", + "leicht, mehr Reps", + "mittel", + "schwerer, kontrolliert" + ] + }, + "load": { + "name": "Bizeps-Curls (KB/Rucksack)", + "steps": [ + "leicht", + "etwas mehr", + "mehr", + "fordernd" + ] + }, + "lr": { + "name": "Beinheben", + "steps": [ + "Knie gebeugt", + "Knie gebeugt, langsam", + "halb gestreckt", + "gestreckt" + ] + }, + "lraise": { + "name": "Seitheben (KB/Rucksack)", + "steps": [ + "leicht (Technik)", + "leicht, mehr Reps", + "mittel", + "mittel, langsam" + ] + }, + "ls": { + "name": "Liegestütze", + "steps": [ + "Inkline (hoch)", + "Inkline tiefer / Knie", + "Knie / Negativ", + "Negativ + erste Volle" + ] + }, + "lunge": { + "name": "Ausfallschritt rückw.", + "steps": [ + "Körpergewicht (ggf. festhalten)", + "Körpergewicht", + "mehr Reps", + "+ Last leicht" + ] + }, + "mtn": { + "name": "Mountain Climbers", + "steps": [ + "moderat", + "moderat", + "zügig", + "zügig" + ] + }, + "pike": { + "name": "Pike", + "steps": [ + "Pike Hold", + "Hold + kleine Senkung", + "Push-up Hände erhöht", + "Push-up Boden, klein" + ] + }, + "plank": { + "name": "Plank", + "steps": [ + "auf Knien 15–20 s", + "voll 20–30 s", + "voll + Schulter-Taps", + "voll, länger" + ] + }, + "press": { + "name": "KB Floor Press", + "steps": [ + "leicht (Floor Press)", + "Floor Press mehr", + "Overhead leicht", + "Overhead mittel" + ] + }, + "row1": { + "name": "Einarmiges Rudern (KB)", + "steps": [ + "leicht", + "etwas mehr", + "einarmig leicht", + "einarmig mittel" + ] + }, + "row2": { + "name": "Einarm-Rudern BREIT (KB)", + "steps": [ + "leicht (Technik)", + "leicht, mehr Reps", + "mittel", + "mittel, Pause oben" + ] + }, + "rowe": { + "name": "Einarm-Rudern ENG (KB)", + "steps": [ + "leicht", + "etwas mehr", + "mehr", + "fordernd" + ] + }, + "sp": { + "name": "Side Plank (Wechsel)", + "steps": [ + "auf Knien", + "auf Knien, länger", + "voll", + "voll, länger" + ] + }, + "sup": { + "name": "Superman / Y-T-W", + "steps": [ + "Superman Hold", + "Superman Hold länger", + "Reverse Snow Angels", + "Y-T-W Raises" + ] + }, + "swing": { + "name": "KB Swing (zweiarmig)", + "steps": [ + "Dead-Stop, leicht (Technik)", + "Dead-Stop, mehr Reps", + "durchgehend, mittel", + "durchgehend, schwerer" + ] + }, + "tw": { + "name": "Russian Twist (KB)", + "steps": [ + "ohne Gewicht", + "ohne Gewicht", + "KB leicht", + "KB" + ] + }, + "wall": { + "name": "Wall Sit", + "steps": [ + "15–25 s", + "25–40 s", + "40–55 s", + "55–70 s" + ] + } + } + }, + "profile": { + "display_name": "", + "start_date": "", + "plan_notes": "" + }, + "previous_end_state": [], + "local_metrics": { + "sessions_with_data": 6, + "completed_sessions": 6, + "planned_sessions": 6, + "completion_rate_pct": 100, + "documented_exercises": 36 + }, + "sessions": [ + { + "session_key": "w01-d01", + "week": 1, + "day": 1, + "date": "", + "focus": "Drücken + Beine", + "light": false, + "phase": "Phase 1 · Fundament", + "phase_instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "status": "completed", + "started_at": "2026-07-23T10:01:58.783Z", + "completed_at": "2026-07-23T10:01:58.783Z", + "session_note": "", + "section_notes": {}, + "items": [ + { + "item_id": "cooldown-0", + "exercise": "Ausschütteln + Gehen (60–90s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "cooldown-1", + "exercise": "Box-Atmung (4–5 Runden)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "d1-r0-e0", + "exercise": "Liegestütze", + "progression_id": "ls", + "progression": "Inkline (hoch)", + "result": "8/7/7/7/6/6/5", + "note": "", + "done": true, + "cue": "Brust kontrolliert senken & drücken", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d1-r0-e1", + "exercise": "Bank-Dips (Couch)", + "progression_id": "dips", + "progression": "Knie stark gebeugt, klein", + "result": "8/7/7/7/6/6", + "note": "", + "done": true, + "cue": "Po vor der Couch senken, hochdrücken", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d1-r0-e2", + "exercise": "Pike", + "progression_id": "pike", + "progression": "Pike Hold", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "umgek. V, Kopf senken (Schultern)", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d1-r0-e3", + "exercise": "Seitheben (KB/Rucksack)", + "progression_id": "lraise", + "progression": "leicht (Technik)", + "result": "4kg, 6/6/5 je Seite", + "note": "", + "done": true, + "cue": "Arme seitlich bis Schulterhöhe, kontrolliert", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d1-r1-e0", + "exercise": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "progression": "leicht, Tiefe lernen", + "result": "0kg, 8/8/7/7/6/7", + "note": "", + "done": true, + "cue": "Last umarmen, tief", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d1-r1-e1", + "exercise": "Ausfallschritt rückw.", + "progression_id": "lunge", + "progression": "Körpergewicht (ggf. festhalten)", + "result": "3x4 je Seite", + "note": "", + "done": true, + "cue": "großer Schritt, Knie Richtung Boden", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d1-r1-e2", + "exercise": "Glute Bridge", + "progression_id": "bridge", + "progression": "beidbeinig", + "result": "6x 20s", + "note": "", + "done": true, + "cue": "Hüfte hochdrücken, Po fest", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d1-r1-e3", + "exercise": "Wadenheben", + "progression_id": "calf", + "progression": "Körpergewicht", + "result": "10kg, 10/10/10/10/10/10", + "note": "", + "done": true, + "cue": "auf Zehenspitzen hoch/runter", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "stretch-0", + "exercise": "Türrahmen (Brust/Schulter, je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-1", + "exercise": "Trizeps (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-2", + "exercise": "Seitheben/Lat-Seitdehnung (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-3", + "exercise": "Quad im Stand (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-4", + "exercise": "Knieender Hüftbeuger (je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-5", + "exercise": "Wade an der Wand (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "warmup-0", + "exercise": "Handflächen-Wippen", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-1", + "exercise": "Armkreisen/Wand-Slides", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-2", + "exercise": "Hüftkreisen + Beinschwünge", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-3", + "exercise": "Knie-über-Zeh an der Wand", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + } + ] + }, + { + "session_key": "w01-d02", + "week": 1, + "day": 2, + "date": "", + "focus": "Ziehen + Core", + "light": false, + "phase": "Phase 1 · Fundament", + "phase_instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "status": "completed", + "started_at": "2026-07-23T10:03:32.468Z", + "completed_at": "2026-07-23T10:03:32.468Z", + "session_note": "", + "section_notes": {}, + "items": [ + { + "item_id": "cooldown-0", + "exercise": "Ausschütteln + Gehen (60–90s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "cooldown-1", + "exercise": "Box-Atmung (4–5 Runden)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "d2-r0-e0", + "exercise": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "progression": "leicht", + "result": "4kg, 9/9/8/8/8/9", + "note": "", + "done": true, + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d2-r0-e1", + "exercise": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "progression": "leicht", + "result": "4kg, 8/8/8/8/8/7", + "note": "", + "done": true, + "cue": "Oberarme fix, zu den Schultern", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d2-r0-e2", + "exercise": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "progression": "leicht (Technik)", + "result": "4kg, 7/7/6/7/6/8", + "note": "", + "done": true, + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d2-r0-e3", + "exercise": "Superman / Y-T-W", + "progression_id": "sup", + "progression": "Superman Hold", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "bäuchlings Arme+Beine heben", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d2-r1-e0", + "exercise": "Plank", + "progression_id": "plank", + "progression": "auf Knien 15–20 s", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "gerade Linie halten", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d2-r1-e1", + "exercise": "Mountain Climbers", + "progression_id": "mtn", + "progression": "moderat", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "Knie zügig zur Brust", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d2-r1-e2", + "exercise": "Dead Bug", + "progression_id": "db", + "progression": "Arme ODER Beine einzeln", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "gegengleich, Rücken am Boden", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d2-r1-e3", + "exercise": "Beinheben", + "progression_id": "lr", + "progression": "Knie gebeugt", + "result": "5/5/4/4/4/3", + "note": "", + "done": true, + "cue": "Beine heben/senken", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "stretch-0", + "exercise": "Lat-/Seitdehnung (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-1", + "exercise": "Türrahmen (je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-2", + "exercise": "BWS über die Couch (30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-3", + "exercise": "Cobra/Sphinx (Bauch, 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-4", + "exercise": "Unterarm (je 20s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-5", + "exercise": "Knieender Hüftbeuger (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "warmup-0", + "exercise": "Handflächen-Wippen", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-1", + "exercise": "Handtuch-Dislocates (8–10×)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-2", + "exercise": "Cat-Cow", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-3", + "exercise": "Hüftkreisen + Beinschwünge", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + } + ] + }, + { + "session_key": "w01-d03", + "week": 1, + "day": 3, + "date": "", + "focus": "Beine + Core", + "light": false, + "phase": "Phase 1 · Fundament", + "phase_instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "status": "completed", + "started_at": "2026-07-23T10:06:49.678Z", + "completed_at": "2026-07-23T10:06:49.678Z", + "session_note": "", + "section_notes": {}, + "items": [ + { + "item_id": "cooldown-0", + "exercise": "Ausschütteln + Gehen (60–90s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "cooldown-1", + "exercise": "Box-Atmung (4–5 Runden)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "d3-r0-e0", + "exercise": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "progression": "leicht, Tiefe lernen", + "result": "6kg, 8/8/7/7/6/7", + "note": "", + "done": true, + "cue": "Last umarmen, tief", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d3-r0-e1", + "exercise": "RDL / Hip Hinge (KB/Rucksack)", + "progression_id": "load", + "progression": "leicht", + "result": "8kg, 7/7/7/7/7/7", + "note": "", + "done": true, + "cue": "Hüfte zurück, Rücken gerade, Hamstrings", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d3-r0-e2", + "exercise": "Glute Bridge 1-beinig", + "progression_id": "bridge", + "progression": "beidbeinig", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "ein Bein gestreckt", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d3-r0-e3", + "exercise": "Wall Sit", + "progression_id": "wall", + "progression": "15–25 s", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "Rücken an die Wand, halten", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d3-r1-e0", + "exercise": "Hollow Hold", + "progression_id": "holl", + "progression": "Tuck (Knie an)", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "unterer Rücken am Boden", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d3-r1-e1", + "exercise": "Side Plank (Wechsel)", + "progression_id": "sp", + "progression": "auf Knien", + "result": "6x20s", + "note": "", + "done": true, + "cue": "seitlich auf einem Unterarm", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d3-r1-e2", + "exercise": "Bicycle Crunch", + "progression_id": "bic", + "progression": "langsam", + "result": "6x20s", + "note": "", + "done": true, + "cue": "Ellbogen zum Gegenknie", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d3-r1-e3", + "exercise": "Russian Twist (KB)", + "progression_id": "tw", + "progression": "ohne Gewicht", + "result": "4kg, 4/4/4 je Seite", + "note": "", + "done": true, + "cue": "sitzend Rumpf drehen", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "stretch-0", + "exercise": "Hamstring-Dehnung (Couch, je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-1", + "exercise": "Quad im Stand (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-2", + "exercise": "Knieender Hüftbeuger (je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-3", + "exercise": "Figure-4/Taube (je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-4", + "exercise": "Cobra + liegende Wirbelsäulen-Rotation (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-5", + "exercise": "Wade an der Wand (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "warmup-0", + "exercise": "Hüftkreisen + Beinschwünge", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-1", + "exercise": "Knie-über-Zeh an der Wand", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-2", + "exercise": "Cat-Cow", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-3", + "exercise": "Handflächen-Wippen", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + } + ] + }, + { + "session_key": "w01-d04", + "week": 1, + "day": 4, + "date": "", + "focus": "Oberkörper: Drücken + Ziehen", + "light": false, + "phase": "Phase 1 · Fundament", + "phase_instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "status": "completed", + "started_at": "2026-07-23T10:11:52.028Z", + "completed_at": "2026-07-23T10:11:52.028Z", + "session_note": "", + "section_notes": {}, + "items": [ + { + "item_id": "warmup-0", + "exercise": "Handflächen-Wippen", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-1", + "exercise": "Handtuch-Dislocates (8–10×)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-2", + "exercise": "Armkreisen/Wand-Slides", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-3", + "exercise": "Cat-Cow", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "d4-r0-e0", + "exercise": "Liegestütze", + "progression_id": "ls", + "progression": "Inkline (hoch)", + "result": "8/7/7/6/6/6", + "note": "", + "done": true, + "cue": "Brust kontrolliert senken & drücken", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d4-r0-e1", + "exercise": "Bank-Dips (Couch)", + "progression_id": "dips", + "progression": "Knie stark gebeugt, klein", + "result": "8/7/7/7/6/6", + "note": "", + "done": true, + "cue": "Po vor der Couch senken", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d4-r0-e2", + "exercise": "Pike", + "progression_id": "pike", + "progression": "Pike Hold", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "umgek. V, Kopf senken", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d4-r0-e3", + "exercise": "KB Floor Press", + "progression_id": "press", + "progression": "leicht (Floor Press)", + "result": "6x", + "note": "", + "done": true, + "cue": "Glocke vom Boden weg drücken", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d4-r1-e0", + "exercise": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "progression": "leicht", + "result": "4kg, 9/9/9/8/8/9", + "note": "", + "done": true, + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d4-r1-e1", + "exercise": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "progression": "leicht", + "result": "8kg, 7/7/7/7/7/8", + "note": "", + "done": true, + "cue": "Oberarme fix, zu den Schultern", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d4-r1-e2", + "exercise": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "progression": "leicht (Technik)", + "result": "4kg, 7/7/6/7/7/8", + "note": "", + "done": true, + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d4-r1-e3", + "exercise": "Y-T-W / Superman", + "progression_id": "sup", + "progression": "Superman Hold", + "result": "6x20 s", + "note": "", + "done": true, + "cue": "bäuchlings, Rücken", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "cooldown-0", + "exercise": "Ausschütteln + Gehen (60–90s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "cooldown-1", + "exercise": "Box-Atmung (4–5 Runden)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "stretch-0", + "exercise": "Türrahmen (je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-1", + "exercise": "Trizeps (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-2", + "exercise": "Lat-/Seitdehnung (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-3", + "exercise": "BWS über die Couch (30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-4", + "exercise": "Unterarm (je 20s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + } + ] + }, + { + "session_key": "w01-d05", + "week": 1, + "day": 5, + "date": "", + "focus": "Ganzkörper LEICHT + Mobility", + "light": true, + "phase": "Phase 1 · Fundament", + "phase_instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "status": "completed", + "started_at": "2026-07-23T10:12:40.782Z", + "completed_at": "2026-07-23T10:12:40.782Z", + "session_note": "Nicht durchgeführt", + "section_notes": {}, + "items": [ + { + "item_id": "d5-r0-e0", + "exercise": "Kniebeuge (leicht)", + "progression_id": "name:kniebeuge-leicht", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "locker, sauber", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d5-r0-e1", + "exercise": "Liegestütze (1 Stufe leichter)", + "progression_id": "name:liegestuetze-1-stufe-leichter", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "leichtere Variante", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d5-r0-e2", + "exercise": "Rudern (leicht)", + "progression_id": "name:rudern-leicht", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "wenig Gewicht", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d5-r0-e3", + "exercise": "Mountain Climbers (locker)", + "progression_id": "name:mountain-climbers-locker", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "ruhiges Tempo", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d5-r1-e0", + "exercise": "Inchworm", + "progression_id": "name:inchworm", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "in den Stütz wandern, zurück", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d5-r1-e1", + "exercise": "Cat-Cow", + "progression_id": "name:cat-cow", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "Rücken ründen/strecken", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d5-r1-e2", + "exercise": "Bird Dog", + "progression_id": "name:bird-dog", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "gegengleich Arm+Bein, kurz halten", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d5-r1-e3", + "exercise": "KB-Halo (leicht)", + "progression_id": "name:kb-halo-leicht", + "progression": "", + "result": "", + "note": "", + "done": false, + "cue": "langsam um den Kopf kreisen", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + } + ] + }, + { + "session_key": "w01-d06", + "week": 1, + "day": 6, + "date": "", + "focus": "KETTLEBELL Ganzkörper", + "light": false, + "phase": "Phase 1 · Fundament", + "phase_instructions": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "status": "completed", + "started_at": "2026-07-23T10:14:51.035Z", + "completed_at": "2026-07-23T10:14:51.035Z", + "session_note": "", + "section_notes": {}, + "items": [ + { + "item_id": "warmup-0", + "exercise": "Halo (leicht)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-1", + "exercise": "Hüftkreisen + Beinschwünge", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-2", + "exercise": "Cat-Cow", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "warmup-3", + "exercise": "Handflächen-Wippen", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "warmup", + "kind": "warmup" + }, + { + "item_id": "d6-r0-e0", + "exercise": "KB Swing (zweiarmig)", + "progression_id": "swing", + "progression": "Dead-Stop, leicht (Technik)", + "result": "10kg, 15", + "note": "", + "done": true, + "cue": "Hüft-Schnapp, kein Armheben", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d6-r0-e1", + "exercise": "Goblet Squat (KB)", + "progression_id": "gobletkb", + "progression": "leicht, Tiefe lernen", + "result": "8kg, 6/6/7/8/6/6", + "note": "", + "done": true, + "cue": "Glocke vor der Brust, tief", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d6-r0-e2", + "exercise": "KB-Deadlift (beidbeinig)", + "progression_id": "kbdl", + "progression": "leicht, Technik", + "result": "10kg, 5/5/6/6/6/5", + "note": "", + "done": true, + "cue": "Glocke zwischen den Füßen, Hüfte hinge", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d6-r0-e3", + "exercise": "Halo", + "progression_id": "halo", + "progression": "leicht, langsam", + "result": "4kg, 5/4/4 je Seite", + "note": "", + "done": true, + "cue": "eng um den Kopf kreisen", + "rotation": "ROTATION A — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d6-r1-e0", + "exercise": "KB Floor Press", + "progression_id": "press", + "progression": "leicht (Floor Press)", + "result": "10kg, 7/7/8/6/5/6", + "note": "", + "done": true, + "cue": "Glocke vom Boden weg drücken", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d6-r1-e1", + "exercise": "Einarmiges Rudern (KB)", + "progression_id": "row1", + "progression": "leicht", + "result": "6kg, 6/7/8 je Seite", + "note": "", + "done": true, + "cue": "Rücken gerade, zum Bauch", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d6-r1-e2", + "exercise": "Bottoms-up KB-Hold (Griff)", + "progression_id": "carry", + "progression": "im Stand 10–15s", + "result": "4kg, 3x20 s je Seite", + "note": "", + "done": true, + "cue": "Glocke Boden nach oben, Unterarm senkrecht, still halten", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "d6-r1-e3", + "exercise": "Around-the-World + Uppercut (KB)", + "progression_id": "atw", + "progression": "nur Around-the-World, leicht", + "result": "4kg, 5/5/5 je Seite", + "note": "", + "done": true, + "cue": "Glocke um den Körper, dann diagonal zur Gegen-Schulter, andere Hand fängt", + "rotation": "ROTATION B — 3 Sätze", + "kind": "exercise" + }, + { + "item_id": "cooldown-0", + "exercise": "Ausschütteln + Gehen (60–90s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "cooldown-1", + "exercise": "Box-Atmung (4–5 Runden)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "cooldown", + "kind": "cooldown" + }, + { + "item_id": "stretch-0", + "exercise": "Hamstring-Dehnung (Couch, je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-1", + "exercise": "Knieender Hüftbeuger (je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-2", + "exercise": "Figure-4/Taube (je 30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-3", + "exercise": "Unterarm (je 20s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-4", + "exercise": "BWS über die Couch (30–40s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + }, + { + "item_id": "stretch-5", + "exercise": "Lat-/Seitdehnung (je 30s)", + "progression_id": "", + "progression": "", + "result": "", + "note": "", + "done": true, + "cue": "", + "rotation": "stretch", + "kind": "stretch" + } + ] + } + ] + } + }, + "response": { + "headline": "Woche 1: starker Einstieg, Progression vorsichtig", + "summary": "Die Woche wurde sehr konsequent gestartet; effektiv sind 5 von 6 geplanten Trainingstagen mit Übungen dokumentiert. Die meisten Übungen liegen sinnvoll auf den vorgesehenen Anfänger-Stufen der Phase 1. Erste stabile Muster zeigen sich bei Pike Holds, Rudern und Core-Holds, während Liegestütze noch klar auf Konsolidierung statt Progression stehen. Für Woche 2 sollten nur planverträgliche Variantenwechsel genutzt werden, keine längeren Arbeitsintervalle.", + "data_quality": "Aussagekraft gut für Woche 1, aber Tag 5 ist widersprüchlich als abgeschlossen markiert, obwohl alle Übungen fehlen.", + "metrics": [ + { + "label": "Durchführung", + "value": "System 6/6, effektiv 5/6", + "detail": "Tag 5 hat die Notiz „Nicht durchgeführt“ und keine erledigten Übungen." + }, + { + "label": "Dokumentationsumfang", + "value": "36 dokumentierte Übungen", + "detail": "Genug Daten für erste Übungstendenzen, aber noch keine langfristigen Trends." + }, + { + "label": "Tabata-Format", + "value": "20/10 beibehalten", + "detail": "Die dokumentierten Holds bleiben im festen 20-Sekunden-Arbeitsintervall." + }, + { + "label": "Belastungsniveau", + "value": "überwiegend Startstufen", + "detail": "Passt zur Phase 1 mit Technikfokus und leichter Last." + } + ], + "exercise_updates": [ + { + "name": "Liegestütze", + "trend": "stable", + "current_level": "Inkline hoch", + "evidence": "Tag 1: 8/7/7/7/6/6/5; Tag 4: 8/7/7/6/6/6", + "next_action": "Stufe beibehalten und sauberen ROM sowie gleichmäßigere Wiederholungen priorisieren.", + "criterion": "Erst auf Inkline tiefer/Knie wechseln, wenn 2 Sessions in Folge alle vorgesehenen 20s-Intervalle sauber mit stabiler Körperspannung geschafft werden." + }, + { + "name": "Pike", + "trend": "up", + "current_level": "Pike Hold", + "evidence": "Tag 1 und Tag 4 jeweils 6x20 s dokumentiert.", + "next_action": "Optional nächste planmäßige Stufe „Hold + kleine Senkung“ innerhalb der 20s testen.", + "criterion": "Nur übernehmen, wenn 6/6 Intervalle ohne Hüftabsacken und ohne Technikverlust sauber bleiben." + }, + { + "name": "Einarm-Rudern ENG (KB)", + "trend": "up", + "current_level": "leicht, 4 kg", + "evidence": "Tag 2: 4kg, 9/9/8/8/8/9; Tag 4: 4kg, 9/9/9/8/8/9", + "next_action": "Bei sauberem Zug zur Hüfte planverträglich auf „etwas mehr“ bzw. leicht höhere Last wechseln.", + "criterion": "2 Sessions in Folge alle dokumentierten Intervalle je Seite kontrolliert mit mindestens ähnlicher Wiederholungszahl abschließen." + }, + { + "name": "KB Swing (zweiarmig)", + "trend": "unclear", + "current_level": "Dead-Stop, leicht; 10 kg", + "evidence": "Tag 6: 10kg, 15 dokumentiert.", + "next_action": "Dead-Stop-Technik beibehalten und pro 20s-Intervall genauer dokumentieren.", + "criterion": "Erst steigern, wenn alle Swing-Intervalle in 2 Sessions sauber als Hüft-Schnapp ohne Armheben dokumentiert sind." + } + ], + "plan_adjustments": [ + { + "target": "Woche 2 / Pike", + "suggested_change": "Progressionsstufe von „Pike Hold“ auf „Hold + kleine Senkung“ setzen.", + "reason": "Zwei dokumentierte Einheiten mit 6x20 s Pike Hold sprechen für einen vorsichtigen Variantenwechsel.", + "manual_step": "Im Trainingsplan-Modul bei Pike die nächste Progressionsstufe auswählen.", + "condition": "Nur wenn beide Pike-Einheiten technisch sauber waren; sonst Pike Hold beibehalten." + }, + { + "target": "Woche 2 / Einarm-Rudern ENG", + "suggested_change": "Von „leicht“ auf „etwas mehr“ wechseln, z.B. etwas höhere Last oder kontrollierterer Zug bei gleicher Tabata-Struktur.", + "reason": "Die Wiederholungen lagen in beiden Einheiten stabil bei etwa 8–9 pro Intervall.", + "manual_step": "Im Trainingsplan-Modul die nächste Rudern-ENG-Progression bzw. passende Last eintragen.", + "condition": "Nur wenn Rückenposition und Zugweg sauber bleiben; sonst 4 kg beibehalten." + }, + { + "target": "Woche 2 / Tag 5 leicht", + "suggested_change": "Tag 5 entweder wirklich leicht durchführen oder klar als nicht durchgeführt/Ruhe dokumentieren.", + "reason": "Diese Woche ist Tag 5 als completed markiert, enthält aber keine Übungen.", + "manual_step": "Im Trainingsplan-Modul den Status/Notiz für Tag 5 konsistent setzen.", + "condition": "Nur relevant, wenn Tag 5 erneut ausfällt; sonst normalen leichten Mobility-Tag beibehalten." + } + ], + "warnings": [ + "Tag 5 ist als abgeschlossen markiert, obwohl „Nicht durchgeführt“ notiert ist und alle Übungen offen sind.", + "Beim KB Swing reicht die Angabe „10kg, 15“ noch nicht, um Intervallqualität oder Technikprogression sicher zu bewerten." + ], + "conclusion": "Woche 1 ist ein solider Start; für Woche 2 nur dort steigern, wo Technik über komplette 20s-Intervalle stabil bleibt." + }, + "visuals": { + "charts": [ + { + "title": "Liegestütze · Verlauf", + "type": "line", + "unit": "Reps", + "labels": [ + "T1", + "T4" + ], + "series": [ + { + "name": "Liegestütze", + "values": [ + 6.57, + 6.67 + ] + } + ], + "insight": "Aus den dokumentierten Ergebnisfeldern berechnet." + }, + { + "title": "Bank-Dips (Couch) · Verlauf", + "type": "line", + "unit": "Reps", + "labels": [ + "T1", + "T4" + ], + "series": [ + { + "name": "Bank-Dips (Couch)", + "values": [ + 6.83, + 6.83 + ] + } + ], + "insight": "Aus den dokumentierten Ergebnisfeldern berechnet." + } + ], + "flowcharts": [ + { + "title": "Liegestütze", + "nodes": [ + { + "id": "n1", + "label": "Inkline (hoch)", + "status": "current" + }, + { + "id": "n2", + "label": "Inkline tiefer / Knie", + "status": "next" + }, + { + "id": "n3", + "label": "Knie / Negativ", + "status": "" + }, + { + "id": "n4", + "label": "Negativ + erste Volle", + "status": "" + } + ], + "edges": [ + { + "from": "n1", + "to": "n2" + }, + { + "from": "n2", + "to": "n3" + }, + { + "from": "n3", + "to": "n4" + } + ], + "insight": "Dokumentierter Stand: Inkline (hoch)" + }, + { + "title": "Bank-Dips (Couch)", + "nodes": [ + { + "id": "n1", + "label": "Knie stark gebeugt, klein", + "status": "current" + }, + { + "id": "n2", + "label": "Knie gebeugt, voll", + "status": "next" + }, + { + "id": "n3", + "label": "Füße etwas weiter", + "status": "" + }, + { + "id": "n4", + "label": "Füße weiter weg", + "status": "" + } + ], + "edges": [ + { + "from": "n1", + "to": "n2" + }, + { + "from": "n2", + "to": "n3" + }, + { + "from": "n3", + "to": "n4" + } + ], + "insight": "Dokumentierter Stand: Knie stark gebeugt, klein" + }, + { + "title": "Pike", + "nodes": [ + { + "id": "n1", + "label": "Pike Hold", + "status": "current" + }, + { + "id": "n2", + "label": "Hold + kleine Senkung", + "status": "next" + }, + { + "id": "n3", + "label": "Push-up Hände erhöht", + "status": "" + }, + { + "id": "n4", + "label": "Push-up Boden, klein", + "status": "" + } + ], + "edges": [ + { + "from": "n1", + "to": "n2" + }, + { + "from": "n2", + "to": "n3" + }, + { + "from": "n3", + "to": "n4" + } + ], + "insight": "Dokumentierter Stand: Pike Hold" + } + ] + }, + "version": 1, + "status": "success", + "source_file": "phase-1.json", + "archive_file": "week-01.json", + "sample_note": "Beispieldatensatz für die Android-Implementierung; in Version 2.0 wird diese Datei bei einer Neuanalyse überschrieben." +} diff --git a/testdata/plans/calihoss-newbie-8-wochen.json b/testdata/plans/calihoss-newbie-8-wochen.json new file mode 100644 index 0000000..e861e5b --- /dev/null +++ b/testdata/plans/calihoss-newbie-8-wochen.json @@ -0,0 +1,11135 @@ +{ + "plan_id": "calihoss-newbie-8-wochen", + "name": "CaliHoss Newbie · 8 Wochen", + "revision": 1, + "published_revision": 1, + "created_at": "2026-07-24T09:43:48+00:00", + "updated_at": "2026-07-24T09:43:48+00:00", + "published_at": "2026-07-24T09:43:48+00:00", + "config": { + "colors": { + "ACC2": "#0F766E", + "ACCENT": "#C2410C", + "DARK": "#23201C", + "GOALBG": "#FBF1E9", + "GRID": "#CFC9BF", + "INK": "#1A1A1A", + "KBBG": "#F1ECFB", + "KBCOL": "#6D28D9", + "LIGHT": "#F4F1EC", + "LINK": "#1565C0", + "MUTE": "#6B6256", + "WARMBG": "#EAF1F0" + }, + "days": [ + { + "badge": { + "color": "#0F766E", + "text": "" + }, + "focus": "Ganzkörper A · Push + Pull + Beine + Core", + "num": 1, + "rotations": [ + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-e16264eee6", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d1-r0-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-e944165e56", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d1-r0-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-b91cc1e59f", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r0-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-5ec1c8e378", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d1-r0-e3" + } + ], + "label": "ARBEITSSATZ 1 — 4–8 Reps / Holds nach persönlicher Stufe", + "id": "rotation-arbeitssatz-1-4-8-reps-holds-n-93a7b5d3a5" + }, + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-afde40398a", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d1-r1-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-9ec914e9e2", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d1-r1-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-1ae469095c", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r1-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-750dd86258", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d1-r1-e3" + } + ], + "label": "ARBEITSSATZ 2 — gleiche persönliche Stufe", + "id": "rotation-arbeitssatz-2-gleiche-persoenl-9ea5c29794" + } + ], + "id": "day-ganzkoerper-a-push-pull-beine--425f3fef77" + }, + { + "badge": { + "color": "#6B6256", + "text": "" + }, + "focus": "Regeneration · optional Schulter Conditioning", + "num": 2, + "rotations": [ + { + "exercises": [ + { + "cue": "Playlist 5 exakt videogegeführt; bei Trainingsstart bis zu 3×/Woche, Volumen leicht halten", + "key": "name:bulletproof-shoulders", + "name": "Bulletproof Shoulders", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:bulletproof-shoulders", + "id": "exercise-item-9805179e68", + "exercise_id": "movement-bulletproof-shoulders-c0a078d108", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e0" + }, + { + "cue": "Nur kontrolliert und schmerzfrei; kein Schwung, Nacken lang", + "key": "name:prone-y-t-w-technik", + "name": "Prone Y-T-W Technik", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:prone-y-t-w-technik", + "id": "exercise-1-941443ade3", + "exercise_id": "movement-prone-y-t-w-technik-80560abe41", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e1" + }, + { + "cue": "20–40 Min optional; Gesprächs-Tempo, kein Intervalltraining", + "key": "name:lockerer-spaziergang", + "name": "Lockerer Spaziergang", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:lockerer-spaziergang", + "id": "exercise-2-1156e4d54e", + "exercise_id": "movement-lockerer-spaziergang-cd93978ea1", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e2" + }, + { + "cue": "Schlaf, Muskelkater, Handgelenke, Ellbogen und Schultern beurteilen", + "key": "name:regeneration-pruefen", + "name": "Regeneration prüfen", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:regeneration-pruefen", + "id": "exercise-3-730e1ffe5e", + "exercise_id": "movement-regeneration-pruefen-3d83efff8f", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e3" + } + ], + "label": "KEIN KRAFTTRAINING — OPTIONAL", + "id": "rotation-kein-krafttraining-optional-81a22c40d8" + }, + { + "exercises": [ + { + "cue": "Playlist 4: vier Warm-up-Drills; Finger Plank ergänzen", + "key": "name:wrist-conditioning", + "name": "Wrist Conditioning", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:wrist-conditioning", + "id": "exercise-item-434ab0d6cd", + "exercise_id": "movement-wrist-conditioning-ee8f71f37e", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e0" + }, + { + "cue": "10–30 s in passender Variante; <10 s leichter, bei 30 s schwerer", + "key": "name:finger-plank", + "name": "Finger Plank", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:finger-plank", + "id": "exercise-1-2fe41cea5a", + "exercise_id": "movement-finger-plank-456618869c", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d2-r1-e1" + }, + { + "cue": "Playlist 8 optional; separat und ruhig, nicht als Kraftzirkel", + "key": "name:full-body-mobility", + "name": "Full Body Mobility", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:full-body-mobility", + "id": "exercise-2-9ecfe09b02", + "exercise_id": "movement-full-body-mobility-f4ab17f25a", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e2" + }, + { + "cue": "Keine Zusatz-Sätze für Push, Pull, Beine oder Core", + "key": "name:sonst-vollstaendig-pausieren", + "name": "Sonst vollständig pausieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:sonst-vollstaendig-pausieren", + "id": "exercise-3-aa830acb25", + "exercise_id": "movement-sonst-vollstaendig-pausieren-b291d0cf69", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e3" + } + ], + "label": "OPTIONALE PFLEGE — NICHT BIS ZUR ERMÜDUNG", + "id": "rotation-optionale-pflege-nicht-bis-zur-5f931f935f" + } + ], + "id": "day-regeneration-optional-schulter-8a933efccd" + }, + { + "badge": { + "color": "#0F766E", + "text": "" + }, + "focus": "Ganzkörper B · bessere Ausführung", + "num": 3, + "rotations": [ + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-ef06de6209", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d3-r0-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-e91b0eb7ea", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d3-r0-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-b83bb1b189", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r0-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-3ef2e70f5f", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r0-e3" + } + ], + "label": "ARBEITSSATZ 1 — 4–8 Reps / Holds nach persönlicher Stufe", + "id": "rotation-arbeitssatz-1-4-8-reps-holds-n-31d3e1cf81" + }, + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-7e9d3b8778", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d3-r1-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-0576793b2d", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d3-r1-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-2cc9126f3c", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r1-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-442575f355", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r1-e3" + } + ], + "label": "ARBEITSSATZ 2 — gleiche persönliche Stufe", + "id": "rotation-arbeitssatz-2-gleiche-persoenl-3cf874b60c" + } + ], + "id": "day-ganzkoerper-b-bessere-ausfuehr-cbccb5a18c" + }, + { + "badge": { + "color": "#6B6256", + "text": "" + }, + "focus": "Regeneration · optional Mobility", + "num": 4, + "rotations": [ + { + "exercises": [ + { + "cue": "Videoroutine der Playlist 8; ruhig, kontrolliert, ohne Kraftziel", + "key": "name:full-body-mobility", + "name": "Full Body Mobility", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:full-body-mobility", + "id": "exercise-item-61699773e3", + "exercise_id": "movement-full-body-mobility-f4ab17f25a", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e0" + }, + { + "cue": "Nur schmerzfreier ROM; unterstützt eine saubere Kniebeuge", + "key": "name:sprunggelenk-mobilitaet", + "name": "Sprunggelenk-Mobilität", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:sprunggelenk-mobilitaet", + "id": "exercise-1-9a77e1bd9b", + "exercise_id": "movement-sprunggelenk-mobilitaet-ef05527f38", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e1" + }, + { + "cue": "Kontrollierte Bewegungen, kein aggressives Dehnen", + "key": "name:hueft-mobilitaet", + "name": "Hüft-Mobilität", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:hueft-mobilitaet", + "id": "exercise-2-e78ed44a90", + "exercise_id": "movement-hueft-mobilitaet-7836e93f5c", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e2" + }, + { + "cue": "Sanft bewegen; keine Zusatzermüdung für die nächste Einheit", + "key": "name:brustwirbelsaeule-schultern", + "name": "Brustwirbelsäule / Schultern", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:brustwirbelsaeule-schultern", + "id": "exercise-3-e8e41d6352", + "exercise_id": "movement-brustwirbelsaeule-schultern-02622f8d13", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e3" + } + ], + "label": "OPTIONAL: PLAYLIST 8 — MOBILITY", + "id": "rotation-optional-playlist-8-mobility-5e1ae6ee55" + }, + { + "exercises": [ + { + "cue": "Optional locker bewegen", + "key": "name:spaziergang", + "name": "Spaziergang", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:spaziergang", + "id": "exercise-item-67777b2931", + "exercise_id": "movement-spaziergang-feb087e265", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e0" + }, + { + "cue": "Regeneration ist Teil der Progression", + "key": "name:schlaf-priorisieren", + "name": "Schlaf priorisieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:schlaf-priorisieren", + "id": "exercise-1-72400ba5c9", + "exercise_id": "movement-schlaf-priorisieren-b6845205e5", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e1" + }, + { + "cue": "War die letzte Einheit näher am Versagen, nächste Einheit leichter wählen", + "key": "name:1-2-reps-reserve-pruefen", + "name": "1–2 Reps Reserve prüfen", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:1-2-reps-reserve-pruefen", + "id": "exercise-2-db67a28ad8", + "exercise_id": "movement-1-2-reps-reserve-pruefen-85e3b5af37", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e2" + }, + { + "cue": "Stechender oder zunehmender Gelenkschmerz: Übung anpassen oder pausieren", + "key": "name:schmerzcheck", + "name": "Schmerzcheck", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:schmerzcheck", + "id": "exercise-3-b06e0c5872", + "exercise_id": "movement-schmerzcheck-f658f713e7", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e3" + } + ], + "label": "REGENERATION — MINIMAL HALTEN", + "id": "rotation-regeneration-minimal-halten-c450e97a0b" + } + ], + "id": "day-regeneration-optional-mobility-0f3d34036d" + }, + { + "badge": { + "color": "#0F766E", + "text": "" + }, + "focus": "Ganzkörper C · Wochenabschluss", + "light": false, + "num": 5, + "rotations": [ + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-cbbb28ae57", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d5-r0-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-b5cdc5acfc", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d5-r0-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-c8c3bf00a5", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d5-r0-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-72a4e18f04", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d5-r0-e3" + } + ], + "label": "ARBEITSSATZ 1 — 4–8 Reps / Holds nach persönlicher Stufe", + "id": "rotation-arbeitssatz-1-4-8-reps-holds-n-a937682d98" + }, + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-3f953adfdb", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d5-r1-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-5fda83a424", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d5-r1-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-b40c4b6a58", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d5-r1-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-ce921d5b08", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d5-r1-e3" + } + ], + "label": "ARBEITSSATZ 2 — gleiche persönliche Stufe", + "id": "rotation-arbeitssatz-2-gleiche-persoenl-c713162cb9" + } + ], + "id": "day-ganzkoerper-c-wochenabschluss-f1d536f095" + }, + { + "badge": { + "color": "#6B6256", + "text": "" + }, + "color": "#6B6256", + "focus": "Vollständige Erholung · Wochencheck", + "note": "Kein Krafttraining. Notiere pro Hauptübung die persönliche Stufe, Reps/Haltezeit, Unterstützung und eine technische Verbesserung. Sonntag bleibt ebenfalls frei bzw. sehr locker.", + "note_bg": "#F4F1EC", + "num": 6, + "rotations": [ + { + "exercises": [ + { + "cue": "Stufe, 2 Sätze, Reps/Haltezeit und Techniknotiz", + "key": "name:push-protokollieren", + "name": "Push protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:push-protokollieren", + "id": "exercise-item-5e98211225", + "exercise_id": "movement-push-protokollieren-e0770774a1", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r0-e0" + }, + { + "cue": "Stufe, Unterstützung/Band/Fußhilfe und Techniknotiz", + "key": "name:pull-protokollieren", + "name": "Pull protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:pull-protokollieren", + "id": "exercise-1-4ffe56788d", + "exercise_id": "movement-pull-protokollieren-71a9be182e", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r0-e1" + }, + { + "cue": "Stufe, Tiefe, Unterstützung bzw. Kettlebell-Gewicht", + "key": "name:beine-protokollieren", + "name": "Beine protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:beine-protokollieren", + "id": "exercise-2-d163fb171a", + "exercise_id": "movement-beine-protokollieren-712a79f464", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r0-e2" + }, + { + "cue": "Plank-Variante und saubere Haltezeit", + "key": "name:core-protokollieren", + "name": "Core protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:core-protokollieren", + "id": "exercise-3-a6dff34f5c", + "exercise_id": "movement-core-protokollieren-279940b6d0", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d6-r0-e3" + } + ], + "label": "WOCHENCHECK — NUR DOKUMENTIEREN", + "id": "rotation-wochencheck-nur-dokumentieren-46027202a0" + }, + { + "exercises": [ + { + "cue": "Mindestens 10 s; bis 30 s halten, sonst Schwierigkeit senken", + "key": "name:startposition-pruefen", + "name": "Startposition prüfen", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:startposition-pruefen", + "id": "exercise-item-faff57b16d", + "exercise_id": "movement-startposition-pruefen-0676749716", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e0" + }, + { + "cue": "Bis 8 saubere Wiederholungen; vorher nicht überspringen", + "key": "name:negative-pruefen", + "name": "Negative prüfen", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:negative-pruefen", + "id": "exercise-1-2c33847f25", + "exercise_id": "movement-negative-pruefen-363284950d", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e1" + }, + { + "cue": "Unter 4: assistiert bevorzugen; ab 4 volle Reps trainieren", + "key": "name:volle-reps-pruefen", + "name": "Volle Reps prüfen", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:volle-reps-pruefen", + "id": "exercise-2-f432054dde", + "exercise_id": "movement-volle-reps-pruefen-4955104cc2", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e2" + }, + { + "cue": "Erst nach 8 sauberen vollen Reps; dann Zyklus neu starten", + "key": "name:schwierigkeit-erhoehen", + "name": "Schwierigkeit erhöhen", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:schwierigkeit-erhoehen", + "id": "exercise-3-00836121d2", + "exercise_id": "movement-schwierigkeit-erhoehen-2b4e278d42", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e3" + } + ], + "label": "ENTSCHEIDUNG FÜR NÄCHSTE WOCHE", + "id": "rotation-entscheidung-fuer-naechste-woc-51b5dca3e8" + } + ], + "id": "day-vollstaendige-erholung-wochenc-b3998121a6" + } + ], + "front": { + "baseline_head": "Standortbestimmung & Tracking", + "baseline_intro": "Ermittle vor Einheit 1 für jede Kategorie die erste nicht bestandene Stufe des Flowcharts. In Woche 8 unter identischen Bedingungen wiederholen.", + "baseline_rows": [ + [ + "Test", + "Tag 0", + "Woche 8" + ], + [ + "Push: aktuelle Stufe + saubere Reps/Haltezeit", + "", + "" + ], + [ + "High Plank (saubere Sekunden, max. 30 notieren)", + "", + "" + ], + [ + "Pull: aktuelle Stufe + Unterstützung", + "", + "" + ], + [ + "Active Hang (saubere Sekunden, max. 30 notieren)", + "", + "" + ], + [ + "Beine: aktuelle Stufe + saubere Reps/Haltezeit", + "", + "" + ], + [ + "Plank: Variante + saubere Sekunden", + "", + "" + ], + [ + "Absolvierte Kraftsessions (von 24)", + "0", + "" + ], + [ + "Körpergewicht (kg)", + "95", + "" + ] + ], + "flow_rows": [ + [ + "Teil", + "Inhalt", + "Zeit" + ], + [ + "Warm-up", + "Ganzkörper + Wrists + Scapula, nicht ermüdend", + "5–10 Min" + ], + [ + "Arbeitssatz 1", + "Push · Pull · Beine · Core", + "individuell" + ], + [ + "Arbeitssatz 2", + "gleiche Stufen; volle Erholung zulassen", + "individuell" + ], + [ + "Optional", + "Playlist 5 Shoulders / Playlist 8 Mobility separat", + "nach Bedarf" + ], + [ + "", + "Gesamt Krafttraining", + "~25–40 Min" + ] + ], + "goals_box": "Nach 8 Wochen zählt kein künstliches Wiederholungsziel, sondern die Newbie-Kriterien:
•  3 Ganzkörpereinheiten pro Woche sind zur stabilen Gewohnheit geworden.
•  Du beherrschst je eine saubere, wiederholbare Push-, Pull-, Bein- und Core-Variante.
•  Du kennst bei jeder Bewegung deine aktuelle Stufe und kannst sie über Technik, Reps, Haltezeit, ROM oder weniger Unterstützung steigern.
•  Ein Wechsel zur Beginner Stage erfolgt nur, wenn die Grundbewegungen nicht mehr unbeholfen wirken und die Regeneration stabil bleibt.", + "goals_head": "Ziel der 8 Wochen: Newbie Stage sauber abschließen", + "goals_note": "Flowchart-Regel für jede Übung separat: Startposition mindestens 10 s, bis 30 s ausbauen. Ausnahme Beine: Deep Squat Hold jeden 2. Tag, 1–2×60 s ungewichtet oder 1–2×30 s gewichtet; erst in der tiefen Position sicher und komfortabel werden. Dann bis 8 saubere Negative. Danach volle Reps testen: unter 4 volle Reps assistiert bevorzugen, Teil-Reps nur alternativ; ab 4 volle Reps trainieren, bis 8 erreicht sind. Erst dann Schwierigkeit erhöhen und den Zyklus neu starten. Die Kalenderwoche erzwingt keinen Aufstieg.", + "kb_box": "Die Kettlebell bleibt in dieser Newbie Stage ein Progressionswerkzeug, kein zweites Programm:
•  Hauptübung Beine bleibt die persönliche Squat-Stufe.
•  Sobald 8 saubere Bodyweight Squats kontrolliert möglich sind, kann ein leichter Goblet Squat die Schwierigkeit erhöhen.
•  Keine zusätzlichen Swings, Presses, Rows oder Kettlebell-Zirkel in diesem Block. So bleiben Umfang und Übungsauswahl beim CaliHoss-Newbie-Schema.", + "kb_head": "Rolle der Kettlebell", + "lib_head": "CaliHoss-Playlists & Übungsbibliothek", + "lib_intro": "Die Links führen direkt zur Kursseite bzw. zum passenden Playlist-Abschnitt. Die Videos enthalten die eigentliche Technikschulung; dieses Dokument strukturiert die Anwendung.", + "reminder_head": "Unveränderliche Regeln", + "reminders": [ + "3× pro Woche Ganzkörper. Je Einheit genau eine Hauptübung aus Push, Pull, Beine und Core.", + "2 Arbeitssätze. Dynamisch 4–8 Reps; isometrisch normalerweise 10–30 s; Deep Squat Hold als Sonderregel 1–2×60 s ungewichtet oder 1–2×30 s gewichtet jeden 2. Tag. Etwa 1–2 saubere Reps vor dem Versagen stoppen.", + "Technik vor Zahlen. Pro Einheit mindestens eine Variable verbessern: Position, Alignment, ROM, Tempo, Spannung, Reps, Haltezeit oder Unterstützung.", + "Kein Kalenderaufstieg. Jede Kategorie folgt unabhängig dem Flowchart und bleibt auf der ersten nicht bestandenen Stufe.", + "Nach 2 stagnierenden Sessions prüfen: Erholung, Ermüdung, Übungsstufe und Unterstützung anpassen." + ], + "session_how_body": "Kein Tabata. Nach dem 5–10-minütigen Ganzkörper-Warm-up absolvierst du Arbeitssatz 1 für Push, Pull, Beine und Core mit ausreichenden Pausen. Danach folgt Arbeitssatz 2 derselben vier persönlichen Stufen. Dynamische Übungen: 4–8 saubere Reps; Holds normalerweise 10–30 s; Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet jeden 2. Tag. Intensität: 80–90 % bzw. 1–2 Reps Reserve. Pausiere so lange, bis die nächste Ausführung sauber ist.", + "session_how_head": "So funktioniert eine Ganzkörpereinheit", + "sheets_note": "Wochenrhythmus: Tag 1, 3 und 5 Krafttraining; dazwischen Regeneration. Tag 2/4 enthalten nur optionale Conditioning-/Mobility-Hinweise, Tag 6 ist vollständig frei. Alle Hauptübungen folgen dem persönlichen Progressions-Flowchart.", + "timer_note": "Timer: normale Stoppuhr für Holds und Pausen; kein 20/10-Intervalltimer." + }, + "library": [ + { + "color": "ACC2", + "group": "KURS & PROGRAMM", + "items": [ + { + "desc": "Programmstufen, Grundübungen, Begriffe und 5–10-minütiges bewegungsspezifisches Warm-up.", + "name": "Playlist 1: Foundation", + "url": "https://calihoss.me/learn/calisthenics/#playlist-1-foundation" + }, + { + "desc": "Technik, Bewegungsentwicklung, Volumen, Mikroprogression und das Progression Decision Flowchart.", + "name": "Playlist 2: Progression", + "url": "https://calihoss.me/learn/calisthenics/#playlist-2-progression" + }, + { + "desc": "3× Ganzkörper pro Woche; je eine Übung aus Push, Pull, Beine und Core; 2×4–8 bei 80–90 % Intensität.", + "name": "Newbie Stage", + "url": "https://calihoss.me/learn/calisthenics/#newbie-stage" + }, + { + "desc": "Klimmzugstangen, Unterstützungsmöglichkeiten und niedrige Stangen/Parallettes.", + "name": "Playlist 3: Equipment", + "url": "https://calihoss.me/learn/calisthenics/#playlist-3-equipment" + } + ] + }, + { + "color": "ACC2", + "group": "WARM-UP · WRISTS · SHOULDERS · SCAPULA", + "items": [ + { + "desc": "Vier Wrist-Warm-up-Übungen in jeder Einheit; Finger Planks ausdrücklich ergänzen.", + "name": "Playlist 4: Wrists and Forearms", + "url": "https://calihoss.me/learn/calisthenics/#playlist-4-wrists-and-forearms" + }, + { + "desc": "Passende Variante 10–30 s. Erst nach 30 s pro Position zu schwereren Varianten oder Combos wechseln.", + "name": "Finger Plank", + "url": "https://calihoss.me/learn/calisthenics/#wrist-warm-up-routine" + }, + { + "desc": "Bulletproof Shoulders, Prone Y-T-W und Serratus-/Scapula-Kontrolle; bei Trainingsstart bis zu 3×/Woche.", + "name": "Playlist 5: Shoulders", + "url": "https://calihoss.me/learn/calisthenics/#playlist-5-shoulders" + }, + { + "desc": "Push-up: Protraktion. Row: Retraction. Dip: Depression. Pike/Handstand: Elevation.", + "name": "Scapula-Grundrichtungen", + "url": "https://calihoss.me/learn/calisthenics/#the-scapula" + } + ] + }, + { + "color": "ACC2", + "group": "PUSH · PUSH-UP-PROGRESSION", + "items": [ + { + "desc": "Auswahl, erste Push-up-Progressionen, Knee Push-up, Form, Handposition und Tempo.", + "name": "Playlist 15: Push-ups", + "url": "https://calihoss.me/learn/calisthenics/#playlist-15-push-ups" + }, + { + "desc": "10 s beherrschen, dann bis 30 s ausbauen. Bei <10 s Hände erhöhen oder Knie verwenden.", + "name": "Startposition: High Plank", + "url": "https://calihoss.me/learn/calisthenics/#progression-decision-flowchart" + }, + { + "desc": "Langsam durch den kontrollierbaren vollen ROM absenken; Ziel 4–8, Übergang nach 8 sauberen Negativen.", + "name": "Negative Push-ups", + "url": "https://calihoss.me/learn/calisthenics/#beginner-push-up-workout" + }, + { + "desc": "Unter 4 volle Reps: Knie-/erhöhte Push-ups bevorzugen. Ab 4 volle Reps trainieren; bei 8 erschweren.", + "name": "Assistiert → volle Push-ups", + "url": "https://calihoss.me/learn/calisthenics/#first-push-up-progressions" + } + ] + }, + { + "color": "ACC2", + "group": "PULL · PULL-UP-PROGRESSION", + "items": [ + { + "desc": "Erster Pull-up, Scapular Pull-ups, Hangs und Unterstützung. Für Full Body werden assistierte Pull-ups empfohlen.", + "name": "Playlist 14: Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#playlist-14-pull-ups" + }, + { + "desc": "Lats durch Außenrotation aktivieren; leichte Scapula-Depression, nicht maximal nach unten ziehen.", + "name": "Startposition: Active Hang", + "url": "https://calihoss.me/learn/calisthenics/#hangs-technique" + }, + { + "desc": "Erste Pull-up-Progression: Ellbogen gestreckt, Schulterblätter kontrolliert depressieren, Lats aktivieren.", + "name": "Scapular Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#scapular-pull-ups-technique" + }, + { + "desc": "Obere Position per Hocker erreichen, langsam absenken; 4–8 saubere Wiederholungen.", + "name": "Negative Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#first-pull-up-progression" + }, + { + "desc": "Fuß- oder Bandhilfe so wählen, dass 4–8 volle kontrollierte Reps mit 1–2 Reps Reserve möglich sind.", + "name": "Assistierte Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#first-pull-up-progression" + }, + { + "desc": "Ab 4 sauberen vollen Reps diese trainieren; bei 8 Schwierigkeit erhöhen und Flowchart neu beginnen.", + "name": "Volle Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#progression-decision-flowchart" + }, + { + "desc": "Nur als Alternative, falls keine Pull-up-Möglichkeit vorhanden ist; Newbies dürfen zunächst eine Pull-Bewegung wählen.", + "name": "Playlist 13: Inverted Rows", + "url": "https://calihoss.me/learn/calisthenics/#playlist-13-inverted-rows" + } + ] + }, + { + "color": "ACC2", + "group": "BEINE · CORE", + "items": [ + { + "desc": "Beintraining als Fundament; im Full-Body-Plan mindestens Squat oder Hinge abdecken.", + "name": "Playlist 10: Legs", + "url": "https://calihoss.me/learn/calisthenics/#playlist-10-legs" + }, + { + "desc": "Zuerst Deep Squat Hold jeden 2. Tag: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet. Erst komfortabel, dann Negative, assistierte und volle Squats 4–8.", + "name": "Squat-Progression", + "url": "https://calihoss.me/learn/calisthenics/#progression-decision-flowchart" + }, + { + "desc": "Erst nach 8 sauberen Bodyweight Squats als Schwierigkeitserhöhung; leichte Glocke und voller kontrollierter ROM.", + "name": "Goblet Squat mit Kettlebell", + "url": "https://calihoss.me/learn/calisthenics/#develop-the-movement" + }, + { + "desc": "Core als Stabilisation und Anti-Bewegung; für Full Body kann eine Core-Übung je Einheit ergänzt werden.", + "name": "Playlist 9: Core", + "url": "https://calihoss.me/learn/calisthenics/#playlist-9-core" + }, + { + "desc": "Plank als fundamentale Core-Übung; 10–30 s, danach Schwierigkeit oder Widerstand erhöhen.", + "name": "Playlist 12: Planks", + "url": "https://calihoss.me/learn/calisthenics/#playlist-12-planks" + } + ] + }, + { + "color": "ACC2", + "group": "OPTIONAL · SPÄTER", + "items": [ + { + "desc": "Separate Full-Body-Mobility-Routine 2–4×/Woche möglich; nicht als zusätzliche Kraftsession behandeln.", + "name": "Playlist 8: Mobility", + "url": "https://calihoss.me/learn/calisthenics/#playlist-8-mobility" + }, + { + "desc": "Noch nicht Hauptübung dieses Blocks. Später: Dip Support Hold → Scapular Dips → Negative → assistierte → volle Dips.", + "name": "Playlist 16: Dips", + "url": "https://calihoss.me/learn/calisthenics/#playlist-16-dips" + } + ] + } + ], + "meta": { + "days": 6, + "doc_title": "CaliHoss Newbie Stage · 8-Wochen-Plan", + "footer_left": "CaliHoss Newbie Stage · 3× Ganzkörper · Progression per Flowchart", + "output_name": "CaliHoss-Newbie-8-Wochen.pdf", + "subtitle": "Blutiger Anfänger · 3× Ganzkörper/Woche · 2×4–8 Reps · Holds meist 10–30 s · Deep Squat 60/30 s · Kettlebell nur als Progression", + "title": "CaliHoss Newbie Stage – 8 Wochen", + "weeks": 8 + }, + "phases": { + "items": [ + { + "name": "Phase 1 · Level finden & Technik", + "params": "Für jede Kategorie die erste nicht bestandene Flowchart-Stufe bestimmen. 2 Sätze, 4–8 Reps bzw. meist 10–30 s, 1–2 Reps Reserve. Deep Squat Hold separat jeden 2. Tag: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet. Kein automatischer Aufstieg.", + "weeks": 1, + "id": "phase-phase-1-level-finden-amp-techn-ae1454813c" + }, + { + "name": "Phase 2 · Level finden & Technik", + "params": "Für jede Kategorie die erste nicht bestandene Flowchart-Stufe bestimmen. 2 Sätze, 4–8 Reps bzw. meist 10–30 s, 1–2 Reps Reserve. Deep Squat Hold separat jeden 2. Tag: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet. Kein automatischer Aufstieg.", + "weeks": 1, + "id": "phase-phase-2-level-finden-amp-techn-bc8640a305" + }, + { + "name": "Phase 3 · Stabilisieren & mikroprogressieren", + "params": "Persönliche Stufen beibehalten und pro Session mindestens eine Variable verbessern: Position, Alignment, ROM, Tempo, Spannung, Reps, Haltezeit oder weniger Hilfe.", + "weeks": 1, + "id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec" + }, + { + "name": "Phase 4 · Stabilisieren & mikroprogressieren", + "params": "Persönliche Stufen beibehalten und pro Session mindestens eine Variable verbessern: Position, Alignment, ROM, Tempo, Spannung, Reps, Haltezeit oder weniger Hilfe.", + "weeks": 1, + "id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9" + }, + { + "name": "Phase 5 · Kriterienbasiert aufsteigen", + "params": "Nur nach erfülltem Kriterium wechseln: 30 s Startposition, 8 Negative, 4 volle Reps zum Einstieg, 8 volle Reps vor der nächsten Schwierigkeit. Jede Übung separat.", + "weeks": 1, + "id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734" + }, + { + "name": "Phase 6 · Kriterienbasiert aufsteigen", + "params": "Nur nach erfülltem Kriterium wechseln: 30 s Startposition, 8 Negative, 4 volle Reps zum Einstieg, 8 volle Reps vor der nächsten Schwierigkeit. Jede Übung separat.", + "weeks": 1, + "id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8" + }, + { + "name": "Phase 7 · Festigen & neu testen", + "params": "Aktuelle Varianten sauber wiederholen. Ende Woche 8 dieselben Tests wie an Tag 0. Wechsel zur Beginner Stage nur bei sauberer, wiederholbarer Technik und stabiler 3×/Woche-Gewohnheit.", + "weeks": 1, + "id": "phase-phase-7-festigen-amp-neu-teste-65823eedde" + }, + { + "name": "Phase 8 · Festigen & neu testen", + "params": "Aktuelle Varianten sauber wiederholen. Ende Woche 8 dieselben Tests wie an Tag 0. Wechsel zur Beginner Stage nur bei sauberer, wiederholbarer Technik und stabiler 3×/Woche-Gewohnheit.", + "weeks": 1, + "id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3" + } + ], + "param_light": "Regenerationstag: kein Krafttraining; optional leichte Mobility oder Conditioning ohne Ermüdungsziel." + }, + "prepost": { + "1": { + "cooldown": "optional 2–5 Min locker gehen und Atmung beruhigen", + "stretch": "Optional separat: Playlist 8 Full Body Mobility; nicht bis zur Ermüdung. Bulletproof Shoulders nicht hier, sondern nur als eigener Conditioning-Block am Regenerationstag.", + "warmup": "1–2 Min locker bewegen · Playlist 4 Wrist Conditioning (4 Übungen) + Finger Plank · Wall Scapular Push-ups 4–8 · Active Hang/Scapular Pull-up sehr leicht · 1–2 kontrollierte Squat-Rehearsals; Deep Squat Hold ggf. als Bein-Hauptübung, nicht als Warm-up" + }, + "2": { + "cooldown": "Regeneration · optional lockerer Spaziergang", + "stretch": "Kein zweiter Schulterdurchgang im Cool-down: Bulletproof Shoulders nur einmal als separater Conditioning-Block oben; Wrist Conditioning bei Bedarf leicht.", + "warmup": "Kein Warm-up nötig · kein Krafttraining" + }, + "3": { + "cooldown": "optional 2–5 Min locker gehen und Atmung beruhigen", + "stretch": "Optional separat: Playlist 8 Full Body Mobility; nicht bis zur Ermüdung. Bulletproof Shoulders nicht hier, sondern nur als eigener Conditioning-Block am Regenerationstag.", + "warmup": "1–2 Min locker bewegen · Playlist 4 Wrist Conditioning (4 Übungen) + Finger Plank · Wall Scapular Push-ups 4–8 · Active Hang/Scapular Pull-up sehr leicht · 1–2 kontrollierte Squat-Rehearsals; Deep Squat Hold ggf. als Bein-Hauptübung, nicht als Warm-up" + }, + "4": { + "cooldown": "Ruhig ausklingen, keine Zusatzbelastung", + "stretch": "Optional: Playlist 8 Full Body Mobility, kontrolliert und schmerzfrei", + "warmup": "Kein Warm-up nötig · Mobility bei Bedarf kurz vorbereiten" + }, + "5": { + "cooldown": "optional 2–5 Min locker gehen und Atmung beruhigen", + "stretch": "Optional separat: Playlist 8 Full Body Mobility; nicht bis zur Ermüdung. Bulletproof Shoulders nicht hier, sondern nur als eigener Conditioning-Block am Regenerationstag.", + "warmup": "1–2 Min locker bewegen · Playlist 4 Wrist Conditioning (4 Übungen) + Finger Plank · Wall Scapular Push-ups 4–8 · Active Hang/Scapular Pull-up sehr leicht · 1–2 kontrollierte Squat-Rehearsals; Deep Squat Hold ggf. als Bein-Hauptübung, nicht als Warm-up" + }, + "6": { + "cooldown": "Vollständige Erholung", + "stretch": "Nur Wochenprotokoll und Flowchart-Entscheidung für die nächste Woche", + "warmup": "Kein Training" + } + }, + "stages": { + "atw": { + "name": "Progressionsregel", + "steps": [ + { + "name": "Startposition meist 10–30 s; Deep Squat siehe Sonderregel", + "id": "step-startposition-meist-10-30-s-de-cf24fe6b0a", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Startposition meist 10–30 s; Deep Squat siehe Sonderregel", + "id": "step-startposition-meist-10-30-s-de-7f8eec1c9e", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Negative bis 8 sauber", + "id": "step-negative-bis-8-sauber-2ee7d2ec3a", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Negative bis 8 sauber", + "id": "step-negative-bis-8-sauber-5aa0d823f3", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "assistiert bevorzugt / Teil-Reps alternativ", + "id": "step-assistiert-bevorzugt-teil-reps-3cfdfbf0c9", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "assistiert bevorzugt / Teil-Reps alternativ", + "id": "step-assistiert-bevorzugt-teil-reps-7086055dcf", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "volle Reps 4–8, dann erschweren", + "id": "step-volle-reps-4-8-dann-erschweren-29ef936c03", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "volle Reps 4–8, dann erschweren", + "id": "step-volle-reps-4-8-dann-erschweren-1d2e3ec8a8", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-progressionsregel-2fbf7c1952", + "key": "atw" + }, + "bic": { + "name": "Technik-Fokus", + "steps": [ + { + "name": "Position & Alignment", + "id": "step-position-amp-alignment-ccfec71b67", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Position & Alignment", + "id": "step-position-amp-alignment-a0240a6a9f", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ROM & Bewegungspfad", + "id": "step-rom-amp-bewegungspfad-113bd351b3", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ROM & Bewegungspfad", + "id": "step-rom-amp-bewegungspfad-9739ca3bcd", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Tempo & Spannung", + "id": "step-tempo-amp-spannung-788af93895", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Tempo & Spannung", + "id": "step-tempo-amp-spannung-66e06e922b", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bracing, Atmung & Fokus", + "id": "step-bracing-atmung-amp-fokus-fcc6cb89d5", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bracing, Atmung & Fokus", + "id": "step-bracing-atmung-amp-fokus-76bffcdf46", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-technik-fokus-e875e1dfde", + "key": "bic" + }, + "bridge": { + "name": "Mikroprogression", + "steps": [ + { + "name": "Technik verbessern", + "id": "step-technik-verbessern-18e45c232f", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Technik verbessern", + "id": "step-technik-verbessern-af64a8d335", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+1 saubere Rep / +Sekunden", + "id": "step-1-saubere-rep-sekunden-bea0a6f0f2", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+1 saubere Rep / +Sekunden", + "id": "step-1-saubere-rep-sekunden-9df740632e", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "etwas weniger Unterstützung", + "id": "step-etwas-weniger-unterstuetzung-18e44cfff5", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "etwas weniger Unterstützung", + "id": "step-etwas-weniger-unterstuetzung-a927afae34", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "schwierigere Variante", + "id": "step-schwierigere-variante-75dc6896d9", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "schwierigere Variante", + "id": "step-schwierigere-variante-7856fc9277", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-mikroprogression-b0dc3add0c", + "key": "bridge" + }, + "calf": { + "name": "Stagnationscheck", + "steps": [ + { + "name": "Schlaf & Erholung prüfen", + "id": "step-schlaf-amp-erholung-pruefen-84e12e7461", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Schlaf & Erholung prüfen", + "id": "step-schlaf-amp-erholung-pruefen-06ccaffb49", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Fatigue / Zusatztraining prüfen", + "id": "step-fatigue-zusatztraining-pruefen-6a01f09acf", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Fatigue / Zusatztraining prüfen", + "id": "step-fatigue-zusatztraining-pruefen-cc528ace63", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "leichtere Stufe oder mehr Hilfe", + "id": "step-leichtere-stufe-oder-mehr-hilf-ea4b6f2f82", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "leichtere Stufe oder mehr Hilfe", + "id": "step-leichtere-stufe-oder-mehr-hilf-53b23ce8bd", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "bei 2 Sessions ohne Fortschritt anpassen", + "id": "step-bei-2-sessions-ohne-fortschrit-551809a978", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "bei 2 Sessions ohne Fortschritt anpassen", + "id": "step-bei-2-sessions-ohne-fortschrit-609e7a3a40", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + } + ], + "id": "progression-stagnationscheck-5158f16cab", + "key": "calf" + }, + "carry": { + "name": "Finger Plank", + "steps": [ + { + "name": "Wand/Tisch 10–30 s", + "id": "step-wand-tisch-10-30-s-4193ed5643", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Wand/Tisch 10–30 s", + "id": "step-wand-tisch-10-30-s-a021af05f8", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Knievariante 10–30 s", + "id": "step-knievariante-10-30-s-1110175d1e", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Knievariante 10–30 s", + "id": "step-knievariante-10-30-s-3d1f995c08", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Boden 10–30 s", + "id": "step-boden-10-30-s-14d5aa23b6", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Boden 10–30 s", + "id": "step-boden-10-30-s-569d7dc2a6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Combos erst nach 30 s je Position", + "id": "step-combos-erst-nach-30-s-je-posit-05ff31d1fa", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Combos erst nach 30 s je Position", + "id": "step-combos-erst-nach-30-s-je-posit-ba186d50fe", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-finger-plank-f9d5a3010c", + "key": "carry" + }, + "db": { + "name": "Wrist Conditioning", + "steps": [ + { + "name": "4 Warm-up-Drills", + "id": "step-4-warm-up-drills-80a8839e87", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "4 Warm-up-Drills", + "id": "step-4-warm-up-drills-c1d52d55ad", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+ Finger Plank", + "id": "step-finger-plank-84c96599d8", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+ Finger Plank", + "id": "step-finger-plank-6646fd3c94", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ruhig, nicht ermüdend", + "id": "step-ruhig-nicht-ermuedend-a03b9ca9ea", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ruhig, nicht ermüdend", + "id": "step-ruhig-nicht-ermuedend-eb30e4ad28", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bei Schmerz Variante anpassen", + "id": "step-bei-schmerz-variante-anpassen-96b23d3c5d", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bei Schmerz Variante anpassen", + "id": "step-bei-schmerz-variante-anpassen-bad6a9c798", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-wrist-conditioning-67c4bfa8a0", + "key": "db" + }, + "dips": { + "name": "Dip-Progression · später", + "steps": [ + { + "name": "Dip Support Hold 10–30 s", + "id": "step-dip-support-hold-10-30-s-7db82c14d2", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Dip Support Hold 10–30 s", + "id": "step-dip-support-hold-10-30-s-38a65bd556", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Scapular Dips 4–8", + "id": "step-scapular-dips-4-8-54e50e0deb", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Scapular Dips 4–8", + "id": "step-scapular-dips-4-8-9861d79df3", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Negative / assistierte Dips 4–8", + "id": "step-negative-assistierte-dips-4-8-51cef25e2f", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.6 + }, + { + "name": "Negative / assistierte Dips 4–8", + "id": "step-negative-assistierte-dips-4-8-26038f8ce6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.6 + }, + { + "name": "volle Dips 4–8", + "id": "step-volle-dips-4-8-97ebbfbf20", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "volle Dips 4–8", + "id": "step-volle-dips-4-8-9e3f715ddd", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-dip-progression-spaeter-b76ba51f6d", + "key": "dips" + }, + "gobletkb": { + "name": "Goblet Squat · KB", + "steps": [ + { + "name": "erst nach 8 BW-Squats", + "id": "step-erst-nach-8-bw-squats-cb5e6f79b4", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "erst nach 8 BW-Squats", + "id": "step-erst-nach-8-bw-squats-5fb4c9827c", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, voller ROM", + "id": "step-leicht-voller-rom-223d7ba7df", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, voller ROM", + "id": "step-leicht-voller-rom-9cc35c41ef", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 saubere Reps", + "id": "step-4-8-saubere-reps-9cc21ffc71", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 saubere Reps", + "id": "step-4-8-saubere-reps-05d850b4a5", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "bei 8 Schwierigkeit erhöhen", + "id": "step-bei-8-schwierigkeit-erhoehen-77b0f8ad55", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "bei 8 Schwierigkeit erhöhen", + "id": "step-bei-8-schwierigkeit-erhoehen-ea22a8e15c", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-goblet-squat-kb-9d21334b6f", + "key": "gobletkb" + }, + "halo": { + "name": "Bulletproof Shoulders", + "steps": [ + { + "name": "videogeführt leicht", + "id": "step-videogefuehrt-leicht-56eeb28b68", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "videogeführt leicht", + "id": "step-videogefuehrt-leicht-9826cc9337", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis 3×/Woche bei Trainingsstart", + "id": "step-bis-3-woche-bei-trainingsstart-00cccb34ea", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis 3×/Woche bei Trainingsstart", + "id": "step-bis-3-woche-bei-trainingsstart-78b10cb42c", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "kein Zusatzversagen", + "id": "step-kein-zusatzversagen-0c124a2d71", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "kein Zusatzversagen", + "id": "step-kein-zusatzversagen-09f1f363ad", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Volumen an Erholung anpassen", + "id": "step-volumen-an-erholung-anpassen-23680fda4b", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Volumen an Erholung anpassen", + "id": "step-volumen-an-erholung-anpassen-4d39ebbf93", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-bulletproof-shoulders-baa51fb7a5", + "key": "halo" + }, + "holl": { + "name": "Bracing", + "steps": [ + { + "name": "Rumpfspannung finden", + "id": "step-rumpfspannung-finden-715833661a", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Rumpfspannung finden", + "id": "step-rumpfspannung-finden-286734cc2c", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Atmung koordinieren", + "id": "step-atmung-koordinieren-356cc152d1", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Atmung koordinieren", + "id": "step-atmung-koordinieren-7ca2ee55df", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Alignment halten", + "id": "step-alignment-halten-0c7f26b946", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Alignment halten", + "id": "step-alignment-halten-3e1b54d2ac", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Spannung unter Last behalten", + "id": "step-spannung-unter-last-behalten-a0bbb8fda1", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Spannung unter Last behalten", + "id": "step-spannung-unter-last-behalten-a0e1b7b5a5", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-bracing-4a444af3ab", + "key": "holl" + }, + "kbdl": { + "name": "Hinge · erst Beginner Stage", + "steps": [ + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-e6ff14d812", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-62775896bf", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Squat-Stufe priorisieren", + "id": "step-squat-stufe-priorisieren-2eb20659e1", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Squat-Stufe priorisieren", + "id": "step-squat-stufe-priorisieren-f2390d80ab", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "später Hinge ergänzen", + "id": "step-spaeter-hinge-ergaenzen-490b1761f0", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "später Hinge ergänzen", + "id": "step-spaeter-hinge-ergaenzen-91c1162140", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Beginner: Squat + Hinge abdecken", + "id": "step-beginner-squat-hinge-abdecken-2ece0ba9ff", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Beginner: Squat + Hinge abdecken", + "id": "step-beginner-squat-hinge-abdecken-5ab250ae94", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-hinge-erst-beginner-stage-b36fdf9122", + "key": "kbdl" + }, + "load": { + "name": "Intensität", + "steps": [ + { + "name": "2 Reps Reserve", + "id": "step-2-reps-reserve-b3f06959cb", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "2 Reps Reserve", + "id": "step-2-reps-reserve-9e04b18fde", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "1–2 Reps Reserve", + "id": "step-1-2-reps-reserve-3ee69feb2c", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "1–2 Reps Reserve", + "id": "step-1-2-reps-reserve-4277bee1f0", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "80–90 % Intensität", + "id": "step-80-90-intensitaet-f631fb515d", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "80–90 % Intensität", + "id": "step-80-90-intensitaet-ba7745a692", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nie technische Reps erzwingen", + "id": "step-nie-technische-reps-erzwingen-01e23f5285", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nie technische Reps erzwingen", + "id": "step-nie-technische-reps-erzwingen-2fc20f3242", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-intensitaet-ffaebae137", + "key": "load" + }, + "lr": { + "name": "Teilwiederholungen", + "steps": [ + { + "name": "nur messbarer ROM", + "id": "step-nur-messbarer-rom-bb1e5eeeb1", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "nur messbarer ROM", + "id": "step-nur-messbarer-rom-45a26027a1", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "assistiert bevorzugen", + "id": "step-assistiert-bevorzugen-c93f4daab4", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "assistiert bevorzugen", + "id": "step-assistiert-bevorzugen-1e4bdf997d", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "ROM schrittweise erweitern", + "id": "step-rom-schrittweise-erweitern-02942f4f3c", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "ROM schrittweise erweitern", + "id": "step-rom-schrittweise-erweitern-0ac01e22f3", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Reps übergehen", + "id": "step-zu-vollen-reps-uebergehen-7b9eb05593", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Reps übergehen", + "id": "step-zu-vollen-reps-uebergehen-8f44aa56b7", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-teilwiederholungen-ce2394a937", + "key": "lr" + }, + "lraise": { + "name": "Scapular Protraction", + "steps": [ + { + "name": "Wall Scapular Push-up", + "id": "step-wall-scapular-push-up-fb0d9aa50e", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Wall Scapular Push-up", + "id": "step-wall-scapular-push-up-285b10eed8", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Ellbogen gestreckt", + "id": "step-ellbogen-gestreckt-665588a205", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Ellbogen gestreckt", + "id": "step-ellbogen-gestreckt-d3fc2e4a06", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schulterblätter spreizen", + "id": "step-schulterblaetter-spreizen-81e80cf408", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schulterblätter spreizen", + "id": "step-schulterblaetter-spreizen-81a36d2150", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "auf Push-up übertragen", + "id": "step-auf-push-up-uebertragen-cb7a342eb7", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "auf Push-up übertragen", + "id": "step-auf-push-up-uebertragen-26e3e559ac", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-scapular-protraction-39e62dc680", + "key": "lraise" + }, + "ls": { + "name": "Push-up-Progression", + "steps": [ + { + "name": "High Plank 10–30 s", + "id": "step-high-plank-10-30-s-cdd4db3e1a", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "High Plank 10–30 s", + "id": "step-high-plank-10-30-s-32710670d3", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "Negative Push-ups 4–8", + "id": "step-negative-push-ups-4-8-32248836e0", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "Negative Push-ups 4–8", + "id": "step-negative-push-ups-4-8-8375d11117", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "assistiert 4–8; Teil-Reps alternativ", + "id": "step-assistiert-4-8-teil-reps-alter-221168d839", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.55 + }, + { + "name": "assistiert 4–8; Teil-Reps alternativ", + "id": "step-assistiert-4-8-teil-reps-alter-be5708f622", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.55 + }, + { + "name": "volle Push-ups 4–8; bei 8 erschweren", + "id": "step-volle-push-ups-4-8-bei-8-ersch-293be75f2a", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "volle Push-ups 4–8; bei 8 erschweren", + "id": "step-volle-push-ups-4-8-bei-8-ersch-d000d16e0e", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + } + ], + "id": "progression-push-up-progression-359ecd088e", + "key": "ls" + }, + "lseng": { + "name": "Push-up-Startposition", + "steps": [ + { + "name": "Hände hoch / Knie, falls <10 s", + "id": "step-haende-hoch-knie-falls-10-s-1f36cd4622", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.45 + }, + { + "name": "Hände hoch / Knie, falls <10 s", + "id": "step-haende-hoch-knie-falls-10-s-b6c830cc39", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.45 + }, + { + "name": "High Plank bis 30 s", + "id": "step-high-plank-bis-30-s-23bc4b6dd7", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "High Plank bis 30 s", + "id": "step-high-plank-bis-30-s-8b154532b9", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "volle Körperspannung", + "id": "step-volle-koerperspannung-8633516312", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "volle Körperspannung", + "id": "step-volle-koerperspannung-276c3bf86e", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "danach Negative beginnen", + "id": "step-danach-negative-beginnen-26cc88afec", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "danach Negative beginnen", + "id": "step-danach-negative-beginnen-e608d8c7cf", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + } + ], + "id": "progression-push-up-startposition-90c54c14cd", + "key": "lseng" + }, + "lunge": { + "name": "Squat-Unterstützung", + "steps": [ + { + "name": "Türrahmen / Stange", + "id": "step-tuerrahmen-stange-4c3df7c28f", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Türrahmen / Stange", + "id": "step-tuerrahmen-stange-caf422d72a", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "volle kontrollierte Tiefe", + "id": "step-volle-kontrollierte-tiefe-6f677356fa", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "volle kontrollierte Tiefe", + "id": "step-volle-kontrollierte-tiefe-c04f6daa26", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Hilfe schrittweise reduzieren", + "id": "step-hilfe-schrittweise-reduzieren-51ef8c056f", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Hilfe schrittweise reduzieren", + "id": "step-hilfe-schrittweise-reduzieren-54e5047e9e", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Squats wechseln", + "id": "step-zu-vollen-squats-wechseln-82d34ee4f5", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Squats wechseln", + "id": "step-zu-vollen-squats-wechseln-e10129b753", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-squat-unterstuetzung-222f2deff0", + "key": "lunge" + }, + "mtn": { + "name": "Regeneration", + "steps": [ + { + "name": "kein Zusatztraining", + "id": "step-kein-zusatztraining-fabc8decb1", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kein Zusatztraining", + "id": "step-kein-zusatztraining-da35555351", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "locker bewegen", + "id": "step-locker-bewegen-7e2aef635d", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "locker bewegen", + "id": "step-locker-bewegen-3094be8801", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schmerz / Müdigkeit prüfen", + "id": "step-schmerz-muedigkeit-pruefen-350663ec9f", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schmerz / Müdigkeit prüfen", + "id": "step-schmerz-muedigkeit-pruefen-a7e0aa4799", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bereit für nächste Einheit", + "id": "step-bereit-fuer-naechste-einheit-72427afb09", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bereit für nächste Einheit", + "id": "step-bereit-fuer-naechste-einheit-9d674da4d6", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-regeneration-f844bb7be6", + "key": "mtn" + }, + "pike": { + "name": "Scapular Elevation · später", + "steps": [ + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-140468d1e4", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-40a4ff1fd3", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "erst Schulterkontrolle", + "id": "step-erst-schulterkontrolle-2403f03a7d", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "erst Schulterkontrolle", + "id": "step-erst-schulterkontrolle-5cbef19162", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Pike gehört zur Handstandlinie", + "id": "step-pike-gehoert-zur-handstandlini-8e1b9bc569", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Pike gehört zur Handstandlinie", + "id": "step-pike-gehoert-zur-handstandlini-51b7deea32", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "später als zweite Push-Bewegung", + "id": "step-spaeter-als-zweite-push-bewegu-06cdaa4a23", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "später als zweite Push-Bewegung", + "id": "step-spaeter-als-zweite-push-bewegu-6ef900cf7a", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-scapular-elevation-spaeter-8e6c8a5e64", + "key": "pike" + }, + "plank": { + "name": "Plank-Progression", + "steps": [ + { + "name": "erhöht/Knie 10–30 s", + "id": "step-erhoeht-knie-10-30-s-9156743958", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "erhöht/Knie 10–30 s", + "id": "step-erhoeht-knie-10-30-s-90f0f841d9", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "volle Plank 10–30 s", + "id": "step-volle-plank-10-30-s-6995092df7", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "volle Plank 10–30 s", + "id": "step-volle-plank-10-30-s-7bdfab3260", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "schwierigere Hebelposition", + "id": "step-schwierigere-hebelposition-7929e48e37", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "schwierigere Hebelposition", + "id": "step-schwierigere-hebelposition-78087749e1", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Widerstand, stets 10–30 s", + "id": "step-widerstand-stets-10-30-s-60e1148a07", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Widerstand, stets 10–30 s", + "id": "step-widerstand-stets-10-30-s-e8b858d1c6", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-plank-progression-7010c23671", + "key": "plank" + }, + "press": { + "name": "Ganzkörper-Warm-up", + "steps": [ + { + "name": "5–10 Min", + "id": "step-5-10-min-93bfa2e9b4", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "5–10 Min", + "id": "step-5-10-min-5b6351f175", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Wrists vorbereiten", + "id": "step-wrists-vorbereiten-3bcfe9cd93", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Wrists vorbereiten", + "id": "step-wrists-vorbereiten-c9ff45ae10", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Scapula aktivieren", + "id": "step-scapula-aktivieren-358d0c340e", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Scapula aktivieren", + "id": "step-scapula-aktivieren-aff239eab9", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bewegungen kurz rehearsen", + "id": "step-bewegungen-kurz-rehearsen-ff239741c8", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bewegungen kurz rehearsen", + "id": "step-bewegungen-kurz-rehearsen-61010dbc8f", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-ganzkoerper-warm-up-eb6587906d", + "key": "press" + }, + "row1": { + "name": "Inverted Row · Alternative", + "steps": [ + { + "name": "Scapular Retraction", + "id": "step-scapular-retraction-30fef1c0b2", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Scapular Retraction", + "id": "step-scapular-retraction-5f45079e2d", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "aufrechte assistierte Row", + "id": "step-aufrechte-assistierte-row-4bf42c3506", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 0.25 + }, + { + "name": "aufrechte assistierte Row", + "id": "step-aufrechte-assistierte-row-e76285811f", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 0.25 + }, + { + "name": "schrägere Row 4–8", + "id": "step-schraegere-row-4-8-4a327eb423", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "schrägere Row 4–8", + "id": "step-schraegere-row-4-8-ac387863bc", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "volle Row 4–8", + "id": "step-volle-row-4-8-c9b44ddc9b", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "volle Row 4–8", + "id": "step-volle-row-4-8-068ddaea25", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-inverted-row-alternative-fe88e8737a", + "key": "row1" + }, + "row2": { + "name": "Scapular Retraction", + "steps": [ + { + "name": "Schulterblätter zusammenführen", + "id": "step-schulterblaetter-zusammenfuehr-24fe3466f7", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Schulterblätter zusammenführen", + "id": "step-schulterblaetter-zusammenfuehr-f269293661", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Ellbogen zunächst gestreckt", + "id": "step-ellbogen-zunaechst-gestreckt-8127b3e7fc", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Ellbogen zunächst gestreckt", + "id": "step-ellbogen-zunaechst-gestreckt-8ec16a7674", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "kontrolliert lösen", + "id": "step-kontrolliert-loesen-f6a1697ea4", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "kontrolliert lösen", + "id": "step-kontrolliert-loesen-8cdb5043d6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "auf Rows übertragen", + "id": "step-auf-rows-uebertragen-ae42ecf07c", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "auf Rows übertragen", + "id": "step-auf-rows-uebertragen-7b13c2423a", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-scapular-retraction-07a7ad4883", + "key": "row2" + }, + "rowe": { + "name": "Pull-up-Progression", + "steps": [ + { + "name": "Active Hang 10–30 s", + "id": "step-active-hang-10-30-s-5f0fb67e39", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.2 + }, + { + "name": "Active Hang 10–30 s", + "id": "step-active-hang-10-30-s-17217b9335", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.2 + }, + { + "name": "Scapular Pull-ups / Negative 4–8", + "id": "step-scapular-pull-ups-negative-4-8-9f93cb86a6", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.25 + }, + { + "name": "Scapular Pull-ups / Negative 4–8", + "id": "step-scapular-pull-ups-negative-4-8-aae37fad91", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.25 + }, + { + "name": "assistierte Pull-ups 4–8", + "id": "step-assistierte-pull-ups-4-8-0f04d54425", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.55 + }, + { + "name": "assistierte Pull-ups 4–8", + "id": "step-assistierte-pull-ups-4-8-75a3804b03", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.55 + }, + { + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "id": "step-volle-pull-ups-4-8-bei-8-ersch-13657948ce", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "id": "step-volle-pull-ups-4-8-bei-8-ersch-a12929a96c", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + } + ], + "id": "progression-pull-up-progression-e4ad50ac31", + "key": "rowe" + }, + "sp": { + "name": "Side Plank · später", + "steps": [ + { + "name": "Knie 10–30 s", + "id": "step-knie-10-30-s-a04fda4182", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Knie 10–30 s", + "id": "step-knie-10-30-s-fa5c3f0156", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "volle Side Plank 10–30 s", + "id": "step-volle-side-plank-10-30-s-34820f11a3", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "volle Side Plank 10–30 s", + "id": "step-volle-side-plank-10-30-s-be34794e30", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Hebel erschweren", + "id": "step-hebel-erschweren-e870313a11", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Hebel erschweren", + "id": "step-hebel-erschweren-4db785c23f", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "lateral chain ergänzen", + "id": "step-lateral-chain-ergaenzen-d5bf33ed6e", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "lateral chain ergänzen", + "id": "step-lateral-chain-ergaenzen-91ce5310d7", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-side-plank-spaeter-6dfa6a3b0d", + "key": "sp" + }, + "squat": { + "name": "Squat-Progression", + "steps": [ + { + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "id": "step-deep-squat-hold-60-s-bw-30-s-g-5829d21464", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "id": "step-deep-squat-hold-60-s-bw-30-s-g-ccda7124ba", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-a42ec5fa5c", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-97729a1ee3", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Negative / assistierte Squats 4–8", + "id": "step-negative-assistierte-squats-4--599708fcee", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.75 + }, + { + "name": "Negative / assistierte Squats 4–8", + "id": "step-negative-assistierte-squats-4--2141de14f1", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.75 + }, + { + "name": "volle Squats 4–8; bei 8 KB möglich", + "id": "step-volle-squats-4-8-bei-8-kb-moeg-42273d4c69", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "volle Squats 4–8; bei 8 KB möglich", + "id": "step-volle-squats-4-8-bei-8-kb-moeg-4bad8f3b1f", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-squat-progression-5cf9160e5b", + "key": "squat" + }, + "sup": { + "name": "Prone Y-T-W", + "steps": [ + { + "name": "saubere Y-Position", + "id": "step-saubere-y-position-529d1cd763", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere Y-Position", + "id": "step-saubere-y-position-a9b851db2a", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere T-Position", + "id": "step-saubere-t-position-83a7145889", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere T-Position", + "id": "step-saubere-t-position-435f2fd83b", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere W-Position", + "id": "step-saubere-w-position-87966ac5b9", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere W-Position", + "id": "step-saubere-w-position-3e6795ac07", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter Y-T-W-Durchgang", + "id": "step-kontrollierter-y-t-w-durchgang-8a7eec7df8", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter Y-T-W-Durchgang", + "id": "step-kontrollierter-y-t-w-durchgang-b9b672cb47", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-prone-y-t-w-3a24eaf55c", + "key": "sup" + }, + "swing": { + "name": "Kettlebell in diesem Block", + "steps": [ + { + "name": "kein Swing-Zirkel", + "id": "step-kein-swing-zirkel-1cb890712e", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "kein Swing-Zirkel", + "id": "step-kein-swing-zirkel-f528b2dc38", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "nur Squat ggf. beladen", + "id": "step-nur-squat-ggf-beladen-7b222c3c81", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "nur Squat ggf. beladen", + "id": "step-nur-squat-ggf-beladen-37e386b7bc", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Volumen minimal halten", + "id": "step-volumen-minimal-halten-09fd077fa1", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Volumen minimal halten", + "id": "step-volumen-minimal-halten-0e5b8af5c0", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "KB bleibt Progressionswerkzeug", + "id": "step-kb-bleibt-progressionswerkzeug-8593f7b221", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "KB bleibt Progressionswerkzeug", + "id": "step-kb-bleibt-progressionswerkzeug-64625bcf5d", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-kettlebell-in-diesem-block-c5679bd0d4", + "key": "swing" + }, + "tsquat": { + "name": "Negative Squats", + "steps": [ + { + "name": "langsam absenken", + "id": "step-langsam-absenken-b250a57257", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "langsam absenken", + "id": "step-langsam-absenken-acd3a9f13b", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "unterstützt aufstehen", + "id": "step-unterstuetzt-aufstehen-e3f0b8a12a", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "unterstützt aufstehen", + "id": "step-unterstuetzt-aufstehen-47f8c015d9", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 sauber", + "id": "step-4-8-sauber-bc48af55e6", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 sauber", + "id": "step-4-8-sauber-68c4019fd2", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "nach 8 assistierte/volle Reps", + "id": "step-nach-8-assistierte-volle-reps-5cfb95beec", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "nach 8 assistierte/volle Reps", + "id": "step-nach-8-assistierte-volle-reps-a9e8b3c9b0", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + } + ], + "id": "progression-negative-squats-83b51fc468", + "key": "tsquat" + }, + "tw": { + "name": "Playlist 7 Mobility", + "steps": [ + { + "name": "optional separat", + "id": "step-optional-separat-7b29ebf83e", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "optional separat", + "id": "step-optional-separat-11ae49b683", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter ROM", + "id": "step-kontrollierter-rom-cb99075262", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter ROM", + "id": "step-kontrollierter-rom-b2993f6eaf", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "2–4×/Woche möglich", + "id": "step-2-4-woche-moeglich-1d1637eb3b", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "2–4×/Woche möglich", + "id": "step-2-4-woche-moeglich-2fbef46ba6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nicht als Krafttraining", + "id": "step-nicht-als-krafttraining-6da59933d1", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nicht als Krafttraining", + "id": "step-nicht-als-krafttraining-6f87b4cdd0", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-playlist-7-mobility-cf0245d08b", + "key": "tw" + }, + "wall": { + "name": "Active Hang", + "steps": [ + { + "name": "mit Fußhilfe, falls <10 s", + "id": "step-mit-fusshilfe-falls-10-s-51b27519a1", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "mit Fußhilfe, falls <10 s", + "id": "step-mit-fusshilfe-falls-10-s-d6138fdd57", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "10–30 s halten", + "id": "step-10-30-s-halten-bec653e06c", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "10–30 s halten", + "id": "step-10-30-s-halten-099397e967", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "Außenrotation + Lat-Aktivierung", + "id": "step-aussenrotation-lat-aktivierung-c0b5612e97", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "Außenrotation + Lat-Aktivierung", + "id": "step-aussenrotation-lat-aktivierung-9b17309383", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "keine maximale Depression", + "id": "step-keine-maximale-depression-6eff5e8f20", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "keine maximale Depression", + "id": "step-keine-maximale-depression-cfa68fca9a", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + } + ], + "id": "progression-active-hang-ae19c88465", + "key": "wall" + } + }, + "training_format": { + "mode": "sets_reps", + "fixed_interval": false, + "work_seconds": null, + "rest_seconds": null, + "rounds": null, + "rounds_scope": "block", + "schema_version": 1 + }, + "schema_version": 3, + "contract_version": 2, + "plan_id": "calihoss-newbie-8-wochen", + "exercise_catalog": { + "movement-push-up-progression-118a55be62": { + "id": "movement-push-up-progression-118a55be62", + "name": "Push-up-Progression", + "progression_id": "ls", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-high-plank-10-30-s-cdd4db3e1a", + "name": "High Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-high-plank-10-30-s-32710670d3", + "name": "High Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-push-ups-4-8-32248836e0", + "name": "Negative Push-ups 4–8", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-push-ups-4-8-8375d11117", + "name": "Negative Push-ups 4–8", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistiert-4-8-teil-reps-alter-221168d839", + "name": "assistiert 4–8; Teil-Reps alternativ", + "factor": 0.55, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistiert-4-8-teil-reps-alter-be5708f622", + "name": "assistiert 4–8; Teil-Reps alternativ", + "factor": 0.55, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-push-ups-4-8-bei-8-ersch-293be75f2a", + "name": "volle Push-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-push-ups-4-8-bei-8-ersch-d000d16e0e", + "name": "volle Push-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-pull-up-progression-68d78ccd63": { + "id": "movement-pull-up-progression-68d78ccd63", + "name": "Pull-up-Progression", + "progression_id": "rowe", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-active-hang-10-30-s-5f0fb67e39", + "name": "Active Hang 10–30 s", + "factor": 0.2, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-active-hang-10-30-s-17217b9335", + "name": "Active Hang 10–30 s", + "factor": 0.2, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-scapular-pull-ups-negative-4-8-9f93cb86a6", + "name": "Scapular Pull-ups / Negative 4–8", + "factor": 0.25, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-scapular-pull-ups-negative-4-8-aae37fad91", + "name": "Scapular Pull-ups / Negative 4–8", + "factor": 0.25, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistierte-pull-ups-4-8-0f04d54425", + "name": "assistierte Pull-ups 4–8", + "factor": 0.55, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistierte-pull-ups-4-8-75a3804b03", + "name": "assistierte Pull-ups 4–8", + "factor": 0.55, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-pull-ups-4-8-bei-8-ersch-13657948ce", + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-pull-ups-4-8-bei-8-ersch-a12929a96c", + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-squat-progression-b93d6a593b": { + "id": "movement-squat-progression-b93d6a593b", + "name": "Squat-Progression", + "progression_id": "squat", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-deep-squat-hold-60-s-bw-30-s-g-5829d21464", + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-deep-squat-hold-60-s-bw-30-s-g-ccda7124ba", + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-a42ec5fa5c", + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-97729a1ee3", + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-assistierte-squats-4--599708fcee", + "name": "Negative / assistierte Squats 4–8", + "factor": 0.75, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-assistierte-squats-4--2141de14f1", + "name": "Negative / assistierte Squats 4–8", + "factor": 0.75, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-squats-4-8-bei-8-kb-moeg-42273d4c69", + "name": "volle Squats 4–8; bei 8 KB möglich", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-squats-4-8-bei-8-kb-moeg-4bad8f3b1f", + "name": "volle Squats 4–8; bei 8 KB möglich", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-plank-progression-31156b972b": { + "id": "movement-plank-progression-31156b972b", + "name": "Plank-Progression", + "progression_id": "plank", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-erhoeht-knie-10-30-s-9156743958", + "name": "erhöht/Knie 10–30 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-erhoeht-knie-10-30-s-90f0f841d9", + "name": "erhöht/Knie 10–30 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-plank-10-30-s-6995092df7", + "name": "volle Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-plank-10-30-s-7bdfab3260", + "name": "volle Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwierigere-hebelposition-7929e48e37", + "name": "schwierigere Hebelposition", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwierigere-hebelposition-78087749e1", + "name": "schwierigere Hebelposition", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-widerstand-stets-10-30-s-60e1148a07", + "name": "Widerstand, stets 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-widerstand-stets-10-30-s-e8b858d1c6", + "name": "Widerstand, stets 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-bulletproof-shoulders-c0a078d108": { + "id": "movement-bulletproof-shoulders-c0a078d108", + "name": "Bulletproof Shoulders", + "progression_id": "name:bulletproof-shoulders", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-prone-y-t-w-technik-80560abe41": { + "id": "movement-prone-y-t-w-technik-80560abe41", + "name": "Prone Y-T-W Technik", + "progression_id": "name:prone-y-t-w-technik", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-lockerer-spaziergang-cd93978ea1": { + "id": "movement-lockerer-spaziergang-cd93978ea1", + "name": "Lockerer Spaziergang", + "progression_id": "name:lockerer-spaziergang", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-regeneration-pruefen-3d83efff8f": { + "id": "movement-regeneration-pruefen-3d83efff8f", + "name": "Regeneration prüfen", + "progression_id": "name:regeneration-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-wrist-conditioning-ee8f71f37e": { + "id": "movement-wrist-conditioning-ee8f71f37e", + "name": "Wrist Conditioning", + "progression_id": "name:wrist-conditioning", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-finger-plank-456618869c": { + "id": "movement-finger-plank-456618869c", + "name": "Finger Plank", + "progression_id": "name:finger-plank", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-full-body-mobility-f4ab17f25a": { + "id": "movement-full-body-mobility-f4ab17f25a", + "name": "Full Body Mobility", + "progression_id": "name:full-body-mobility", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-sonst-vollstaendig-pausieren-b291d0cf69": { + "id": "movement-sonst-vollstaendig-pausieren-b291d0cf69", + "name": "Sonst vollständig pausieren", + "progression_id": "name:sonst-vollstaendig-pausieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-sprunggelenk-mobilitaet-ef05527f38": { + "id": "movement-sprunggelenk-mobilitaet-ef05527f38", + "name": "Sprunggelenk-Mobilität", + "progression_id": "name:sprunggelenk-mobilitaet", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-hueft-mobilitaet-7836e93f5c": { + "id": "movement-hueft-mobilitaet-7836e93f5c", + "name": "Hüft-Mobilität", + "progression_id": "name:hueft-mobilitaet", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-brustwirbelsaeule-schultern-02622f8d13": { + "id": "movement-brustwirbelsaeule-schultern-02622f8d13", + "name": "Brustwirbelsäule / Schultern", + "progression_id": "name:brustwirbelsaeule-schultern", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-spaziergang-feb087e265": { + "id": "movement-spaziergang-feb087e265", + "name": "Spaziergang", + "progression_id": "name:spaziergang", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-schlaf-priorisieren-b6845205e5": { + "id": "movement-schlaf-priorisieren-b6845205e5", + "name": "Schlaf priorisieren", + "progression_id": "name:schlaf-priorisieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-1-2-reps-reserve-pruefen-85e3b5af37": { + "id": "movement-1-2-reps-reserve-pruefen-85e3b5af37", + "name": "1–2 Reps Reserve prüfen", + "progression_id": "name:1-2-reps-reserve-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-schmerzcheck-f658f713e7": { + "id": "movement-schmerzcheck-f658f713e7", + "name": "Schmerzcheck", + "progression_id": "name:schmerzcheck", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-push-protokollieren-e0770774a1": { + "id": "movement-push-protokollieren-e0770774a1", + "name": "Push protokollieren", + "progression_id": "name:push-protokollieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-pull-protokollieren-71a9be182e": { + "id": "movement-pull-protokollieren-71a9be182e", + "name": "Pull protokollieren", + "progression_id": "name:pull-protokollieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-beine-protokollieren-712a79f464": { + "id": "movement-beine-protokollieren-712a79f464", + "name": "Beine protokollieren", + "progression_id": "name:beine-protokollieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-core-protokollieren-279940b6d0": { + "id": "movement-core-protokollieren-279940b6d0", + "name": "Core protokollieren", + "progression_id": "name:core-protokollieren", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-startposition-pruefen-0676749716": { + "id": "movement-startposition-pruefen-0676749716", + "name": "Startposition prüfen", + "progression_id": "name:startposition-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-negative-pruefen-363284950d": { + "id": "movement-negative-pruefen-363284950d", + "name": "Negative prüfen", + "progression_id": "name:negative-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-volle-reps-pruefen-4955104cc2": { + "id": "movement-volle-reps-pruefen-4955104cc2", + "name": "Volle Reps prüfen", + "progression_id": "name:volle-reps-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-schwierigkeit-erhoehen-2b4e278d42": { + "id": "movement-schwierigkeit-erhoehen-2b4e278d42", + "name": "Schwierigkeit erhöhen", + "progression_id": "name:schwierigkeit-erhoehen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + } + } + }, + "draft": { + "colors": { + "ACC2": "#0F766E", + "ACCENT": "#C2410C", + "DARK": "#23201C", + "GOALBG": "#FBF1E9", + "GRID": "#CFC9BF", + "INK": "#1A1A1A", + "KBBG": "#F1ECFB", + "KBCOL": "#6D28D9", + "LIGHT": "#F4F1EC", + "LINK": "#1565C0", + "MUTE": "#6B6256", + "WARMBG": "#EAF1F0" + }, + "days": [ + { + "badge": { + "color": "#0F766E", + "text": "" + }, + "focus": "Ganzkörper A · Push + Pull + Beine + Core", + "num": 1, + "rotations": [ + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-e16264eee6", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d1-r0-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-e944165e56", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d1-r0-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-b91cc1e59f", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r0-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-5ec1c8e378", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d1-r0-e3" + } + ], + "label": "ARBEITSSATZ 1 — 4–8 Reps / Holds nach persönlicher Stufe", + "id": "rotation-arbeitssatz-1-4-8-reps-holds-n-93a7b5d3a5" + }, + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-afde40398a", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d1-r1-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-9ec914e9e2", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d1-r1-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-1ae469095c", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r1-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-750dd86258", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d1-r1-e3" + } + ], + "label": "ARBEITSSATZ 2 — gleiche persönliche Stufe", + "id": "rotation-arbeitssatz-2-gleiche-persoenl-9ea5c29794" + } + ], + "id": "day-ganzkoerper-a-push-pull-beine--425f3fef77" + }, + { + "badge": { + "color": "#6B6256", + "text": "" + }, + "focus": "Regeneration · optional Schulter Conditioning", + "num": 2, + "rotations": [ + { + "exercises": [ + { + "cue": "Playlist 5 exakt videogegeführt; bei Trainingsstart bis zu 3×/Woche, Volumen leicht halten", + "key": "name:bulletproof-shoulders", + "name": "Bulletproof Shoulders", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:bulletproof-shoulders", + "id": "exercise-item-9805179e68", + "exercise_id": "movement-bulletproof-shoulders-c0a078d108", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e0" + }, + { + "cue": "Nur kontrolliert und schmerzfrei; kein Schwung, Nacken lang", + "key": "name:prone-y-t-w-technik", + "name": "Prone Y-T-W Technik", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:prone-y-t-w-technik", + "id": "exercise-1-941443ade3", + "exercise_id": "movement-prone-y-t-w-technik-80560abe41", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e1" + }, + { + "cue": "20–40 Min optional; Gesprächs-Tempo, kein Intervalltraining", + "key": "name:lockerer-spaziergang", + "name": "Lockerer Spaziergang", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:lockerer-spaziergang", + "id": "exercise-2-1156e4d54e", + "exercise_id": "movement-lockerer-spaziergang-cd93978ea1", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e2" + }, + { + "cue": "Schlaf, Muskelkater, Handgelenke, Ellbogen und Schultern beurteilen", + "key": "name:regeneration-pruefen", + "name": "Regeneration prüfen", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:regeneration-pruefen", + "id": "exercise-3-730e1ffe5e", + "exercise_id": "movement-regeneration-pruefen-3d83efff8f", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e3" + } + ], + "label": "KEIN KRAFTTRAINING — OPTIONAL", + "id": "rotation-kein-krafttraining-optional-81a22c40d8" + }, + { + "exercises": [ + { + "cue": "Playlist 4: vier Warm-up-Drills; Finger Plank ergänzen", + "key": "name:wrist-conditioning", + "name": "Wrist Conditioning", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:wrist-conditioning", + "id": "exercise-item-434ab0d6cd", + "exercise_id": "movement-wrist-conditioning-ee8f71f37e", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e0" + }, + { + "cue": "10–30 s in passender Variante; <10 s leichter, bei 30 s schwerer", + "key": "name:finger-plank", + "name": "Finger Plank", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:finger-plank", + "id": "exercise-1-2fe41cea5a", + "exercise_id": "movement-finger-plank-456618869c", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d2-r1-e1" + }, + { + "cue": "Playlist 8 optional; separat und ruhig, nicht als Kraftzirkel", + "key": "name:full-body-mobility", + "name": "Full Body Mobility", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:full-body-mobility", + "id": "exercise-2-9ecfe09b02", + "exercise_id": "movement-full-body-mobility-f4ab17f25a", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e2" + }, + { + "cue": "Keine Zusatz-Sätze für Push, Pull, Beine oder Core", + "key": "name:sonst-vollstaendig-pausieren", + "name": "Sonst vollständig pausieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:sonst-vollstaendig-pausieren", + "id": "exercise-3-aa830acb25", + "exercise_id": "movement-sonst-vollstaendig-pausieren-b291d0cf69", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e3" + } + ], + "label": "OPTIONALE PFLEGE — NICHT BIS ZUR ERMÜDUNG", + "id": "rotation-optionale-pflege-nicht-bis-zur-5f931f935f" + } + ], + "id": "day-regeneration-optional-schulter-8a933efccd" + }, + { + "badge": { + "color": "#0F766E", + "text": "" + }, + "focus": "Ganzkörper B · bessere Ausführung", + "num": 3, + "rotations": [ + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-ef06de6209", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d3-r0-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-e91b0eb7ea", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d3-r0-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-b83bb1b189", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r0-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-3ef2e70f5f", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r0-e3" + } + ], + "label": "ARBEITSSATZ 1 — 4–8 Reps / Holds nach persönlicher Stufe", + "id": "rotation-arbeitssatz-1-4-8-reps-holds-n-31d3e1cf81" + }, + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-7e9d3b8778", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d3-r1-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-0576793b2d", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d3-r1-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-2cc9126f3c", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r1-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-442575f355", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r1-e3" + } + ], + "label": "ARBEITSSATZ 2 — gleiche persönliche Stufe", + "id": "rotation-arbeitssatz-2-gleiche-persoenl-3cf874b60c" + } + ], + "id": "day-ganzkoerper-b-bessere-ausfuehr-cbccb5a18c" + }, + { + "badge": { + "color": "#6B6256", + "text": "" + }, + "focus": "Regeneration · optional Mobility", + "num": 4, + "rotations": [ + { + "exercises": [ + { + "cue": "Videoroutine der Playlist 8; ruhig, kontrolliert, ohne Kraftziel", + "key": "name:full-body-mobility", + "name": "Full Body Mobility", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:full-body-mobility", + "id": "exercise-item-61699773e3", + "exercise_id": "movement-full-body-mobility-f4ab17f25a", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e0" + }, + { + "cue": "Nur schmerzfreier ROM; unterstützt eine saubere Kniebeuge", + "key": "name:sprunggelenk-mobilitaet", + "name": "Sprunggelenk-Mobilität", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:sprunggelenk-mobilitaet", + "id": "exercise-1-9a77e1bd9b", + "exercise_id": "movement-sprunggelenk-mobilitaet-ef05527f38", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e1" + }, + { + "cue": "Kontrollierte Bewegungen, kein aggressives Dehnen", + "key": "name:hueft-mobilitaet", + "name": "Hüft-Mobilität", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:hueft-mobilitaet", + "id": "exercise-2-e78ed44a90", + "exercise_id": "movement-hueft-mobilitaet-7836e93f5c", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e2" + }, + { + "cue": "Sanft bewegen; keine Zusatzermüdung für die nächste Einheit", + "key": "name:brustwirbelsaeule-schultern", + "name": "Brustwirbelsäule / Schultern", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:brustwirbelsaeule-schultern", + "id": "exercise-3-e8e41d6352", + "exercise_id": "movement-brustwirbelsaeule-schultern-02622f8d13", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r0-e3" + } + ], + "label": "OPTIONAL: PLAYLIST 8 — MOBILITY", + "id": "rotation-optional-playlist-8-mobility-5e1ae6ee55" + }, + { + "exercises": [ + { + "cue": "Optional locker bewegen", + "key": "name:spaziergang", + "name": "Spaziergang", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:spaziergang", + "id": "exercise-item-67777b2931", + "exercise_id": "movement-spaziergang-feb087e265", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e0" + }, + { + "cue": "Regeneration ist Teil der Progression", + "key": "name:schlaf-priorisieren", + "name": "Schlaf priorisieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:schlaf-priorisieren", + "id": "exercise-1-72400ba5c9", + "exercise_id": "movement-schlaf-priorisieren-b6845205e5", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e1" + }, + { + "cue": "War die letzte Einheit näher am Versagen, nächste Einheit leichter wählen", + "key": "name:1-2-reps-reserve-pruefen", + "name": "1–2 Reps Reserve prüfen", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:1-2-reps-reserve-pruefen", + "id": "exercise-2-db67a28ad8", + "exercise_id": "movement-1-2-reps-reserve-pruefen-85e3b5af37", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e2" + }, + { + "cue": "Stechender oder zunehmender Gelenkschmerz: Übung anpassen oder pausieren", + "key": "name:schmerzcheck", + "name": "Schmerzcheck", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:schmerzcheck", + "id": "exercise-3-b06e0c5872", + "exercise_id": "movement-schmerzcheck-f658f713e7", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e3" + } + ], + "label": "REGENERATION — MINIMAL HALTEN", + "id": "rotation-regeneration-minimal-halten-c450e97a0b" + } + ], + "id": "day-regeneration-optional-mobility-0f3d34036d" + }, + { + "badge": { + "color": "#0F766E", + "text": "" + }, + "focus": "Ganzkörper C · Wochenabschluss", + "light": false, + "num": 5, + "rotations": [ + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-cbbb28ae57", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d5-r0-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-b5cdc5acfc", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d5-r0-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-c8c3bf00a5", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d5-r0-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-72a4e18f04", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d5-r0-e3" + } + ], + "label": "ARBEITSSATZ 1 — 4–8 Reps / Holds nach persönlicher Stufe", + "id": "rotation-arbeitssatz-1-4-8-reps-holds-n-a937682d98" + }, + { + "exercises": [ + { + "cue": "High Plank 10–30 s → Negative 4–8 → assistiert bevorzugt / Teil-Reps alternativ → volle Reps 4–8", + "key": "ls", + "name": "Push-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-3f953adfdb", + "exercise_id": "movement-push-up-progression-118a55be62", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d5-r1-e0" + }, + { + "cue": "Active Hang 10–30 s → Scapular Pull-ups → Negative 4–8 → assistiert 4–8 → volle Reps 4–8", + "key": "rowe", + "name": "Pull-up-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "rowe", + "id": "exercise-1-5fda83a424", + "exercise_id": "movement-pull-up-progression-68d78ccd63", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "legacy_id": "d5-r1-e1" + }, + { + "cue": "Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet, jeden 2. Tag; erst komfortabel → Negative → assistiert → volle Squats 4–8; KB-Goblet erst nach 8 sauberen Reps", + "key": "squat", + "name": "Squat-Progression", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "squat", + "id": "exercise-2-b40c4b6a58", + "exercise_id": "movement-squat-progression-b93d6a593b", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d5-r1-e2" + }, + { + "cue": "Passende Variante 10–30 s; bei <10 s leichter, bei 30 s Schwierigkeit erhöhen", + "key": "plank", + "name": "Plank-Progression", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-3-ce921d5b08", + "exercise_id": "movement-plank-progression-31156b972b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d5-r1-e3" + } + ], + "label": "ARBEITSSATZ 2 — gleiche persönliche Stufe", + "id": "rotation-arbeitssatz-2-gleiche-persoenl-c713162cb9" + } + ], + "id": "day-ganzkoerper-c-wochenabschluss-f1d536f095" + }, + { + "badge": { + "color": "#6B6256", + "text": "" + }, + "color": "#6B6256", + "focus": "Vollständige Erholung · Wochencheck", + "note": "Kein Krafttraining. Notiere pro Hauptübung die persönliche Stufe, Reps/Haltezeit, Unterstützung und eine technische Verbesserung. Sonntag bleibt ebenfalls frei bzw. sehr locker.", + "note_bg": "#F4F1EC", + "num": 6, + "rotations": [ + { + "exercises": [ + { + "cue": "Stufe, 2 Sätze, Reps/Haltezeit und Techniknotiz", + "key": "name:push-protokollieren", + "name": "Push protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:push-protokollieren", + "id": "exercise-item-5e98211225", + "exercise_id": "movement-push-protokollieren-e0770774a1", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r0-e0" + }, + { + "cue": "Stufe, Unterstützung/Band/Fußhilfe und Techniknotiz", + "key": "name:pull-protokollieren", + "name": "Pull protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:pull-protokollieren", + "id": "exercise-1-4ffe56788d", + "exercise_id": "movement-pull-protokollieren-71a9be182e", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r0-e1" + }, + { + "cue": "Stufe, Tiefe, Unterstützung bzw. Kettlebell-Gewicht", + "key": "name:beine-protokollieren", + "name": "Beine protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:beine-protokollieren", + "id": "exercise-2-d163fb171a", + "exercise_id": "movement-beine-protokollieren-712a79f464", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r0-e2" + }, + { + "cue": "Plank-Variante und saubere Haltezeit", + "key": "name:core-protokollieren", + "name": "Core protokollieren", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:core-protokollieren", + "id": "exercise-3-a6dff34f5c", + "exercise_id": "movement-core-protokollieren-279940b6d0", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d6-r0-e3" + } + ], + "label": "WOCHENCHECK — NUR DOKUMENTIEREN", + "id": "rotation-wochencheck-nur-dokumentieren-46027202a0" + }, + { + "exercises": [ + { + "cue": "Mindestens 10 s; bis 30 s halten, sonst Schwierigkeit senken", + "key": "name:startposition-pruefen", + "name": "Startposition prüfen", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:startposition-pruefen", + "id": "exercise-item-faff57b16d", + "exercise_id": "movement-startposition-pruefen-0676749716", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e0" + }, + { + "cue": "Bis 8 saubere Wiederholungen; vorher nicht überspringen", + "key": "name:negative-pruefen", + "name": "Negative prüfen", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:negative-pruefen", + "id": "exercise-1-2c33847f25", + "exercise_id": "movement-negative-pruefen-363284950d", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e1" + }, + { + "cue": "Unter 4: assistiert bevorzugen; ab 4 volle Reps trainieren", + "key": "name:volle-reps-pruefen", + "name": "Volle Reps prüfen", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:volle-reps-pruefen", + "id": "exercise-2-f432054dde", + "exercise_id": "movement-volle-reps-pruefen-4955104cc2", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e2" + }, + { + "cue": "Erst nach 8 sauberen vollen Reps; dann Zyklus neu starten", + "key": "name:schwierigkeit-erhoehen", + "name": "Schwierigkeit erhöhen", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:schwierigkeit-erhoehen", + "id": "exercise-3-00836121d2", + "exercise_id": "movement-schwierigkeit-erhoehen-2b4e278d42", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d6-r1-e3" + } + ], + "label": "ENTSCHEIDUNG FÜR NÄCHSTE WOCHE", + "id": "rotation-entscheidung-fuer-naechste-woc-51b5dca3e8" + } + ], + "id": "day-vollstaendige-erholung-wochenc-b3998121a6" + } + ], + "front": { + "baseline_head": "Standortbestimmung & Tracking", + "baseline_intro": "Ermittle vor Einheit 1 für jede Kategorie die erste nicht bestandene Stufe des Flowcharts. In Woche 8 unter identischen Bedingungen wiederholen.", + "baseline_rows": [ + [ + "Test", + "Tag 0", + "Woche 8" + ], + [ + "Push: aktuelle Stufe + saubere Reps/Haltezeit", + "", + "" + ], + [ + "High Plank (saubere Sekunden, max. 30 notieren)", + "", + "" + ], + [ + "Pull: aktuelle Stufe + Unterstützung", + "", + "" + ], + [ + "Active Hang (saubere Sekunden, max. 30 notieren)", + "", + "" + ], + [ + "Beine: aktuelle Stufe + saubere Reps/Haltezeit", + "", + "" + ], + [ + "Plank: Variante + saubere Sekunden", + "", + "" + ], + [ + "Absolvierte Kraftsessions (von 24)", + "0", + "" + ], + [ + "Körpergewicht (kg)", + "95", + "" + ] + ], + "flow_rows": [ + [ + "Teil", + "Inhalt", + "Zeit" + ], + [ + "Warm-up", + "Ganzkörper + Wrists + Scapula, nicht ermüdend", + "5–10 Min" + ], + [ + "Arbeitssatz 1", + "Push · Pull · Beine · Core", + "individuell" + ], + [ + "Arbeitssatz 2", + "gleiche Stufen; volle Erholung zulassen", + "individuell" + ], + [ + "Optional", + "Playlist 5 Shoulders / Playlist 8 Mobility separat", + "nach Bedarf" + ], + [ + "", + "Gesamt Krafttraining", + "~25–40 Min" + ] + ], + "goals_box": "Nach 8 Wochen zählt kein künstliches Wiederholungsziel, sondern die Newbie-Kriterien:
•  3 Ganzkörpereinheiten pro Woche sind zur stabilen Gewohnheit geworden.
•  Du beherrschst je eine saubere, wiederholbare Push-, Pull-, Bein- und Core-Variante.
•  Du kennst bei jeder Bewegung deine aktuelle Stufe und kannst sie über Technik, Reps, Haltezeit, ROM oder weniger Unterstützung steigern.
•  Ein Wechsel zur Beginner Stage erfolgt nur, wenn die Grundbewegungen nicht mehr unbeholfen wirken und die Regeneration stabil bleibt.", + "goals_head": "Ziel der 8 Wochen: Newbie Stage sauber abschließen", + "goals_note": "Flowchart-Regel für jede Übung separat: Startposition mindestens 10 s, bis 30 s ausbauen. Ausnahme Beine: Deep Squat Hold jeden 2. Tag, 1–2×60 s ungewichtet oder 1–2×30 s gewichtet; erst in der tiefen Position sicher und komfortabel werden. Dann bis 8 saubere Negative. Danach volle Reps testen: unter 4 volle Reps assistiert bevorzugen, Teil-Reps nur alternativ; ab 4 volle Reps trainieren, bis 8 erreicht sind. Erst dann Schwierigkeit erhöhen und den Zyklus neu starten. Die Kalenderwoche erzwingt keinen Aufstieg.", + "kb_box": "Die Kettlebell bleibt in dieser Newbie Stage ein Progressionswerkzeug, kein zweites Programm:
•  Hauptübung Beine bleibt die persönliche Squat-Stufe.
•  Sobald 8 saubere Bodyweight Squats kontrolliert möglich sind, kann ein leichter Goblet Squat die Schwierigkeit erhöhen.
•  Keine zusätzlichen Swings, Presses, Rows oder Kettlebell-Zirkel in diesem Block. So bleiben Umfang und Übungsauswahl beim CaliHoss-Newbie-Schema.", + "kb_head": "Rolle der Kettlebell", + "lib_head": "CaliHoss-Playlists & Übungsbibliothek", + "lib_intro": "Die Links führen direkt zur Kursseite bzw. zum passenden Playlist-Abschnitt. Die Videos enthalten die eigentliche Technikschulung; dieses Dokument strukturiert die Anwendung.", + "reminder_head": "Unveränderliche Regeln", + "reminders": [ + "3× pro Woche Ganzkörper. Je Einheit genau eine Hauptübung aus Push, Pull, Beine und Core.", + "2 Arbeitssätze. Dynamisch 4–8 Reps; isometrisch normalerweise 10–30 s; Deep Squat Hold als Sonderregel 1–2×60 s ungewichtet oder 1–2×30 s gewichtet jeden 2. Tag. Etwa 1–2 saubere Reps vor dem Versagen stoppen.", + "Technik vor Zahlen. Pro Einheit mindestens eine Variable verbessern: Position, Alignment, ROM, Tempo, Spannung, Reps, Haltezeit oder Unterstützung.", + "Kein Kalenderaufstieg. Jede Kategorie folgt unabhängig dem Flowchart und bleibt auf der ersten nicht bestandenen Stufe.", + "Nach 2 stagnierenden Sessions prüfen: Erholung, Ermüdung, Übungsstufe und Unterstützung anpassen." + ], + "session_how_body": "Kein Tabata. Nach dem 5–10-minütigen Ganzkörper-Warm-up absolvierst du Arbeitssatz 1 für Push, Pull, Beine und Core mit ausreichenden Pausen. Danach folgt Arbeitssatz 2 derselben vier persönlichen Stufen. Dynamische Übungen: 4–8 saubere Reps; Holds normalerweise 10–30 s; Deep Squat Hold: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet jeden 2. Tag. Intensität: 80–90 % bzw. 1–2 Reps Reserve. Pausiere so lange, bis die nächste Ausführung sauber ist.", + "session_how_head": "So funktioniert eine Ganzkörpereinheit", + "sheets_note": "Wochenrhythmus: Tag 1, 3 und 5 Krafttraining; dazwischen Regeneration. Tag 2/4 enthalten nur optionale Conditioning-/Mobility-Hinweise, Tag 6 ist vollständig frei. Alle Hauptübungen folgen dem persönlichen Progressions-Flowchart.", + "timer_note": "Timer: normale Stoppuhr für Holds und Pausen; kein 20/10-Intervalltimer." + }, + "library": [ + { + "color": "ACC2", + "group": "KURS & PROGRAMM", + "items": [ + { + "desc": "Programmstufen, Grundübungen, Begriffe und 5–10-minütiges bewegungsspezifisches Warm-up.", + "name": "Playlist 1: Foundation", + "url": "https://calihoss.me/learn/calisthenics/#playlist-1-foundation" + }, + { + "desc": "Technik, Bewegungsentwicklung, Volumen, Mikroprogression und das Progression Decision Flowchart.", + "name": "Playlist 2: Progression", + "url": "https://calihoss.me/learn/calisthenics/#playlist-2-progression" + }, + { + "desc": "3× Ganzkörper pro Woche; je eine Übung aus Push, Pull, Beine und Core; 2×4–8 bei 80–90 % Intensität.", + "name": "Newbie Stage", + "url": "https://calihoss.me/learn/calisthenics/#newbie-stage" + }, + { + "desc": "Klimmzugstangen, Unterstützungsmöglichkeiten und niedrige Stangen/Parallettes.", + "name": "Playlist 3: Equipment", + "url": "https://calihoss.me/learn/calisthenics/#playlist-3-equipment" + } + ] + }, + { + "color": "ACC2", + "group": "WARM-UP · WRISTS · SHOULDERS · SCAPULA", + "items": [ + { + "desc": "Vier Wrist-Warm-up-Übungen in jeder Einheit; Finger Planks ausdrücklich ergänzen.", + "name": "Playlist 4: Wrists and Forearms", + "url": "https://calihoss.me/learn/calisthenics/#playlist-4-wrists-and-forearms" + }, + { + "desc": "Passende Variante 10–30 s. Erst nach 30 s pro Position zu schwereren Varianten oder Combos wechseln.", + "name": "Finger Plank", + "url": "https://calihoss.me/learn/calisthenics/#wrist-warm-up-routine" + }, + { + "desc": "Bulletproof Shoulders, Prone Y-T-W und Serratus-/Scapula-Kontrolle; bei Trainingsstart bis zu 3×/Woche.", + "name": "Playlist 5: Shoulders", + "url": "https://calihoss.me/learn/calisthenics/#playlist-5-shoulders" + }, + { + "desc": "Push-up: Protraktion. Row: Retraction. Dip: Depression. Pike/Handstand: Elevation.", + "name": "Scapula-Grundrichtungen", + "url": "https://calihoss.me/learn/calisthenics/#the-scapula" + } + ] + }, + { + "color": "ACC2", + "group": "PUSH · PUSH-UP-PROGRESSION", + "items": [ + { + "desc": "Auswahl, erste Push-up-Progressionen, Knee Push-up, Form, Handposition und Tempo.", + "name": "Playlist 15: Push-ups", + "url": "https://calihoss.me/learn/calisthenics/#playlist-15-push-ups" + }, + { + "desc": "10 s beherrschen, dann bis 30 s ausbauen. Bei <10 s Hände erhöhen oder Knie verwenden.", + "name": "Startposition: High Plank", + "url": "https://calihoss.me/learn/calisthenics/#progression-decision-flowchart" + }, + { + "desc": "Langsam durch den kontrollierbaren vollen ROM absenken; Ziel 4–8, Übergang nach 8 sauberen Negativen.", + "name": "Negative Push-ups", + "url": "https://calihoss.me/learn/calisthenics/#beginner-push-up-workout" + }, + { + "desc": "Unter 4 volle Reps: Knie-/erhöhte Push-ups bevorzugen. Ab 4 volle Reps trainieren; bei 8 erschweren.", + "name": "Assistiert → volle Push-ups", + "url": "https://calihoss.me/learn/calisthenics/#first-push-up-progressions" + } + ] + }, + { + "color": "ACC2", + "group": "PULL · PULL-UP-PROGRESSION", + "items": [ + { + "desc": "Erster Pull-up, Scapular Pull-ups, Hangs und Unterstützung. Für Full Body werden assistierte Pull-ups empfohlen.", + "name": "Playlist 14: Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#playlist-14-pull-ups" + }, + { + "desc": "Lats durch Außenrotation aktivieren; leichte Scapula-Depression, nicht maximal nach unten ziehen.", + "name": "Startposition: Active Hang", + "url": "https://calihoss.me/learn/calisthenics/#hangs-technique" + }, + { + "desc": "Erste Pull-up-Progression: Ellbogen gestreckt, Schulterblätter kontrolliert depressieren, Lats aktivieren.", + "name": "Scapular Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#scapular-pull-ups-technique" + }, + { + "desc": "Obere Position per Hocker erreichen, langsam absenken; 4–8 saubere Wiederholungen.", + "name": "Negative Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#first-pull-up-progression" + }, + { + "desc": "Fuß- oder Bandhilfe so wählen, dass 4–8 volle kontrollierte Reps mit 1–2 Reps Reserve möglich sind.", + "name": "Assistierte Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#first-pull-up-progression" + }, + { + "desc": "Ab 4 sauberen vollen Reps diese trainieren; bei 8 Schwierigkeit erhöhen und Flowchart neu beginnen.", + "name": "Volle Pull-ups", + "url": "https://calihoss.me/learn/calisthenics/#progression-decision-flowchart" + }, + { + "desc": "Nur als Alternative, falls keine Pull-up-Möglichkeit vorhanden ist; Newbies dürfen zunächst eine Pull-Bewegung wählen.", + "name": "Playlist 13: Inverted Rows", + "url": "https://calihoss.me/learn/calisthenics/#playlist-13-inverted-rows" + } + ] + }, + { + "color": "ACC2", + "group": "BEINE · CORE", + "items": [ + { + "desc": "Beintraining als Fundament; im Full-Body-Plan mindestens Squat oder Hinge abdecken.", + "name": "Playlist 10: Legs", + "url": "https://calihoss.me/learn/calisthenics/#playlist-10-legs" + }, + { + "desc": "Zuerst Deep Squat Hold jeden 2. Tag: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet. Erst komfortabel, dann Negative, assistierte und volle Squats 4–8.", + "name": "Squat-Progression", + "url": "https://calihoss.me/learn/calisthenics/#progression-decision-flowchart" + }, + { + "desc": "Erst nach 8 sauberen Bodyweight Squats als Schwierigkeitserhöhung; leichte Glocke und voller kontrollierter ROM.", + "name": "Goblet Squat mit Kettlebell", + "url": "https://calihoss.me/learn/calisthenics/#develop-the-movement" + }, + { + "desc": "Core als Stabilisation und Anti-Bewegung; für Full Body kann eine Core-Übung je Einheit ergänzt werden.", + "name": "Playlist 9: Core", + "url": "https://calihoss.me/learn/calisthenics/#playlist-9-core" + }, + { + "desc": "Plank als fundamentale Core-Übung; 10–30 s, danach Schwierigkeit oder Widerstand erhöhen.", + "name": "Playlist 12: Planks", + "url": "https://calihoss.me/learn/calisthenics/#playlist-12-planks" + } + ] + }, + { + "color": "ACC2", + "group": "OPTIONAL · SPÄTER", + "items": [ + { + "desc": "Separate Full-Body-Mobility-Routine 2–4×/Woche möglich; nicht als zusätzliche Kraftsession behandeln.", + "name": "Playlist 8: Mobility", + "url": "https://calihoss.me/learn/calisthenics/#playlist-8-mobility" + }, + { + "desc": "Noch nicht Hauptübung dieses Blocks. Später: Dip Support Hold → Scapular Dips → Negative → assistierte → volle Dips.", + "name": "Playlist 16: Dips", + "url": "https://calihoss.me/learn/calisthenics/#playlist-16-dips" + } + ] + } + ], + "meta": { + "days": 6, + "doc_title": "CaliHoss Newbie Stage · 8-Wochen-Plan", + "footer_left": "CaliHoss Newbie Stage · 3× Ganzkörper · Progression per Flowchart", + "output_name": "CaliHoss-Newbie-8-Wochen.pdf", + "subtitle": "Blutiger Anfänger · 3× Ganzkörper/Woche · 2×4–8 Reps · Holds meist 10–30 s · Deep Squat 60/30 s · Kettlebell nur als Progression", + "title": "CaliHoss Newbie Stage – 8 Wochen", + "weeks": 8 + }, + "phases": { + "items": [ + { + "name": "Phase 1 · Level finden & Technik", + "params": "Für jede Kategorie die erste nicht bestandene Flowchart-Stufe bestimmen. 2 Sätze, 4–8 Reps bzw. meist 10–30 s, 1–2 Reps Reserve. Deep Squat Hold separat jeden 2. Tag: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet. Kein automatischer Aufstieg.", + "weeks": 1, + "id": "phase-phase-1-level-finden-amp-techn-ae1454813c" + }, + { + "name": "Phase 2 · Level finden & Technik", + "params": "Für jede Kategorie die erste nicht bestandene Flowchart-Stufe bestimmen. 2 Sätze, 4–8 Reps bzw. meist 10–30 s, 1–2 Reps Reserve. Deep Squat Hold separat jeden 2. Tag: 1–2×60 s ungewichtet oder 1–2×30 s gewichtet. Kein automatischer Aufstieg.", + "weeks": 1, + "id": "phase-phase-2-level-finden-amp-techn-bc8640a305" + }, + { + "name": "Phase 3 · Stabilisieren & mikroprogressieren", + "params": "Persönliche Stufen beibehalten und pro Session mindestens eine Variable verbessern: Position, Alignment, ROM, Tempo, Spannung, Reps, Haltezeit oder weniger Hilfe.", + "weeks": 1, + "id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec" + }, + { + "name": "Phase 4 · Stabilisieren & mikroprogressieren", + "params": "Persönliche Stufen beibehalten und pro Session mindestens eine Variable verbessern: Position, Alignment, ROM, Tempo, Spannung, Reps, Haltezeit oder weniger Hilfe.", + "weeks": 1, + "id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9" + }, + { + "name": "Phase 5 · Kriterienbasiert aufsteigen", + "params": "Nur nach erfülltem Kriterium wechseln: 30 s Startposition, 8 Negative, 4 volle Reps zum Einstieg, 8 volle Reps vor der nächsten Schwierigkeit. Jede Übung separat.", + "weeks": 1, + "id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734" + }, + { + "name": "Phase 6 · Kriterienbasiert aufsteigen", + "params": "Nur nach erfülltem Kriterium wechseln: 30 s Startposition, 8 Negative, 4 volle Reps zum Einstieg, 8 volle Reps vor der nächsten Schwierigkeit. Jede Übung separat.", + "weeks": 1, + "id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8" + }, + { + "name": "Phase 7 · Festigen & neu testen", + "params": "Aktuelle Varianten sauber wiederholen. Ende Woche 8 dieselben Tests wie an Tag 0. Wechsel zur Beginner Stage nur bei sauberer, wiederholbarer Technik und stabiler 3×/Woche-Gewohnheit.", + "weeks": 1, + "id": "phase-phase-7-festigen-amp-neu-teste-65823eedde" + }, + { + "name": "Phase 8 · Festigen & neu testen", + "params": "Aktuelle Varianten sauber wiederholen. Ende Woche 8 dieselben Tests wie an Tag 0. Wechsel zur Beginner Stage nur bei sauberer, wiederholbarer Technik und stabiler 3×/Woche-Gewohnheit.", + "weeks": 1, + "id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3" + } + ], + "param_light": "Regenerationstag: kein Krafttraining; optional leichte Mobility oder Conditioning ohne Ermüdungsziel." + }, + "prepost": { + "1": { + "cooldown": "optional 2–5 Min locker gehen und Atmung beruhigen", + "stretch": "Optional separat: Playlist 8 Full Body Mobility; nicht bis zur Ermüdung. Bulletproof Shoulders nicht hier, sondern nur als eigener Conditioning-Block am Regenerationstag.", + "warmup": "1–2 Min locker bewegen · Playlist 4 Wrist Conditioning (4 Übungen) + Finger Plank · Wall Scapular Push-ups 4–8 · Active Hang/Scapular Pull-up sehr leicht · 1–2 kontrollierte Squat-Rehearsals; Deep Squat Hold ggf. als Bein-Hauptübung, nicht als Warm-up" + }, + "2": { + "cooldown": "Regeneration · optional lockerer Spaziergang", + "stretch": "Kein zweiter Schulterdurchgang im Cool-down: Bulletproof Shoulders nur einmal als separater Conditioning-Block oben; Wrist Conditioning bei Bedarf leicht.", + "warmup": "Kein Warm-up nötig · kein Krafttraining" + }, + "3": { + "cooldown": "optional 2–5 Min locker gehen und Atmung beruhigen", + "stretch": "Optional separat: Playlist 8 Full Body Mobility; nicht bis zur Ermüdung. Bulletproof Shoulders nicht hier, sondern nur als eigener Conditioning-Block am Regenerationstag.", + "warmup": "1–2 Min locker bewegen · Playlist 4 Wrist Conditioning (4 Übungen) + Finger Plank · Wall Scapular Push-ups 4–8 · Active Hang/Scapular Pull-up sehr leicht · 1–2 kontrollierte Squat-Rehearsals; Deep Squat Hold ggf. als Bein-Hauptübung, nicht als Warm-up" + }, + "4": { + "cooldown": "Ruhig ausklingen, keine Zusatzbelastung", + "stretch": "Optional: Playlist 8 Full Body Mobility, kontrolliert und schmerzfrei", + "warmup": "Kein Warm-up nötig · Mobility bei Bedarf kurz vorbereiten" + }, + "5": { + "cooldown": "optional 2–5 Min locker gehen und Atmung beruhigen", + "stretch": "Optional separat: Playlist 8 Full Body Mobility; nicht bis zur Ermüdung. Bulletproof Shoulders nicht hier, sondern nur als eigener Conditioning-Block am Regenerationstag.", + "warmup": "1–2 Min locker bewegen · Playlist 4 Wrist Conditioning (4 Übungen) + Finger Plank · Wall Scapular Push-ups 4–8 · Active Hang/Scapular Pull-up sehr leicht · 1–2 kontrollierte Squat-Rehearsals; Deep Squat Hold ggf. als Bein-Hauptübung, nicht als Warm-up" + }, + "6": { + "cooldown": "Vollständige Erholung", + "stretch": "Nur Wochenprotokoll und Flowchart-Entscheidung für die nächste Woche", + "warmup": "Kein Training" + } + }, + "stages": { + "atw": { + "name": "Progressionsregel", + "steps": [ + { + "name": "Startposition meist 10–30 s; Deep Squat siehe Sonderregel", + "id": "step-startposition-meist-10-30-s-de-cf24fe6b0a", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Startposition meist 10–30 s; Deep Squat siehe Sonderregel", + "id": "step-startposition-meist-10-30-s-de-7f8eec1c9e", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Negative bis 8 sauber", + "id": "step-negative-bis-8-sauber-2ee7d2ec3a", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Negative bis 8 sauber", + "id": "step-negative-bis-8-sauber-5aa0d823f3", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "assistiert bevorzugt / Teil-Reps alternativ", + "id": "step-assistiert-bevorzugt-teil-reps-3cfdfbf0c9", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "assistiert bevorzugt / Teil-Reps alternativ", + "id": "step-assistiert-bevorzugt-teil-reps-7086055dcf", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "volle Reps 4–8, dann erschweren", + "id": "step-volle-reps-4-8-dann-erschweren-29ef936c03", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "volle Reps 4–8, dann erschweren", + "id": "step-volle-reps-4-8-dann-erschweren-1d2e3ec8a8", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-progressionsregel-2fbf7c1952", + "key": "atw" + }, + "bic": { + "name": "Technik-Fokus", + "steps": [ + { + "name": "Position & Alignment", + "id": "step-position-amp-alignment-ccfec71b67", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Position & Alignment", + "id": "step-position-amp-alignment-a0240a6a9f", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ROM & Bewegungspfad", + "id": "step-rom-amp-bewegungspfad-113bd351b3", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ROM & Bewegungspfad", + "id": "step-rom-amp-bewegungspfad-9739ca3bcd", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Tempo & Spannung", + "id": "step-tempo-amp-spannung-788af93895", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Tempo & Spannung", + "id": "step-tempo-amp-spannung-66e06e922b", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bracing, Atmung & Fokus", + "id": "step-bracing-atmung-amp-fokus-fcc6cb89d5", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bracing, Atmung & Fokus", + "id": "step-bracing-atmung-amp-fokus-76bffcdf46", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-technik-fokus-e875e1dfde", + "key": "bic" + }, + "bridge": { + "name": "Mikroprogression", + "steps": [ + { + "name": "Technik verbessern", + "id": "step-technik-verbessern-18e45c232f", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Technik verbessern", + "id": "step-technik-verbessern-af64a8d335", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+1 saubere Rep / +Sekunden", + "id": "step-1-saubere-rep-sekunden-bea0a6f0f2", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+1 saubere Rep / +Sekunden", + "id": "step-1-saubere-rep-sekunden-9df740632e", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "etwas weniger Unterstützung", + "id": "step-etwas-weniger-unterstuetzung-18e44cfff5", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "etwas weniger Unterstützung", + "id": "step-etwas-weniger-unterstuetzung-a927afae34", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "schwierigere Variante", + "id": "step-schwierigere-variante-75dc6896d9", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "schwierigere Variante", + "id": "step-schwierigere-variante-7856fc9277", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-mikroprogression-b0dc3add0c", + "key": "bridge" + }, + "calf": { + "name": "Stagnationscheck", + "steps": [ + { + "name": "Schlaf & Erholung prüfen", + "id": "step-schlaf-amp-erholung-pruefen-84e12e7461", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Schlaf & Erholung prüfen", + "id": "step-schlaf-amp-erholung-pruefen-06ccaffb49", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Fatigue / Zusatztraining prüfen", + "id": "step-fatigue-zusatztraining-pruefen-6a01f09acf", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Fatigue / Zusatztraining prüfen", + "id": "step-fatigue-zusatztraining-pruefen-cc528ace63", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "leichtere Stufe oder mehr Hilfe", + "id": "step-leichtere-stufe-oder-mehr-hilf-ea4b6f2f82", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "leichtere Stufe oder mehr Hilfe", + "id": "step-leichtere-stufe-oder-mehr-hilf-53b23ce8bd", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "bei 2 Sessions ohne Fortschritt anpassen", + "id": "step-bei-2-sessions-ohne-fortschrit-551809a978", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "bei 2 Sessions ohne Fortschritt anpassen", + "id": "step-bei-2-sessions-ohne-fortschrit-609e7a3a40", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + } + ], + "id": "progression-stagnationscheck-5158f16cab", + "key": "calf" + }, + "carry": { + "name": "Finger Plank", + "steps": [ + { + "name": "Wand/Tisch 10–30 s", + "id": "step-wand-tisch-10-30-s-4193ed5643", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Wand/Tisch 10–30 s", + "id": "step-wand-tisch-10-30-s-a021af05f8", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Knievariante 10–30 s", + "id": "step-knievariante-10-30-s-1110175d1e", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Knievariante 10–30 s", + "id": "step-knievariante-10-30-s-3d1f995c08", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Boden 10–30 s", + "id": "step-boden-10-30-s-14d5aa23b6", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Boden 10–30 s", + "id": "step-boden-10-30-s-569d7dc2a6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Combos erst nach 30 s je Position", + "id": "step-combos-erst-nach-30-s-je-posit-05ff31d1fa", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Combos erst nach 30 s je Position", + "id": "step-combos-erst-nach-30-s-je-posit-ba186d50fe", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-finger-plank-f9d5a3010c", + "key": "carry" + }, + "db": { + "name": "Wrist Conditioning", + "steps": [ + { + "name": "4 Warm-up-Drills", + "id": "step-4-warm-up-drills-80a8839e87", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "4 Warm-up-Drills", + "id": "step-4-warm-up-drills-c1d52d55ad", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+ Finger Plank", + "id": "step-finger-plank-84c96599d8", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "+ Finger Plank", + "id": "step-finger-plank-6646fd3c94", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ruhig, nicht ermüdend", + "id": "step-ruhig-nicht-ermuedend-a03b9ca9ea", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ruhig, nicht ermüdend", + "id": "step-ruhig-nicht-ermuedend-eb30e4ad28", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bei Schmerz Variante anpassen", + "id": "step-bei-schmerz-variante-anpassen-96b23d3c5d", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bei Schmerz Variante anpassen", + "id": "step-bei-schmerz-variante-anpassen-bad6a9c798", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-wrist-conditioning-67c4bfa8a0", + "key": "db" + }, + "dips": { + "name": "Dip-Progression · später", + "steps": [ + { + "name": "Dip Support Hold 10–30 s", + "id": "step-dip-support-hold-10-30-s-7db82c14d2", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Dip Support Hold 10–30 s", + "id": "step-dip-support-hold-10-30-s-38a65bd556", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Scapular Dips 4–8", + "id": "step-scapular-dips-4-8-54e50e0deb", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Scapular Dips 4–8", + "id": "step-scapular-dips-4-8-9861d79df3", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Negative / assistierte Dips 4–8", + "id": "step-negative-assistierte-dips-4-8-51cef25e2f", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.6 + }, + { + "name": "Negative / assistierte Dips 4–8", + "id": "step-negative-assistierte-dips-4-8-26038f8ce6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.6 + }, + { + "name": "volle Dips 4–8", + "id": "step-volle-dips-4-8-97ebbfbf20", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "volle Dips 4–8", + "id": "step-volle-dips-4-8-9e3f715ddd", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-dip-progression-spaeter-b76ba51f6d", + "key": "dips" + }, + "gobletkb": { + "name": "Goblet Squat · KB", + "steps": [ + { + "name": "erst nach 8 BW-Squats", + "id": "step-erst-nach-8-bw-squats-cb5e6f79b4", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "erst nach 8 BW-Squats", + "id": "step-erst-nach-8-bw-squats-5fb4c9827c", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, voller ROM", + "id": "step-leicht-voller-rom-223d7ba7df", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, voller ROM", + "id": "step-leicht-voller-rom-9cc35c41ef", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 saubere Reps", + "id": "step-4-8-saubere-reps-9cc21ffc71", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 saubere Reps", + "id": "step-4-8-saubere-reps-05d850b4a5", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "bei 8 Schwierigkeit erhöhen", + "id": "step-bei-8-schwierigkeit-erhoehen-77b0f8ad55", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "bei 8 Schwierigkeit erhöhen", + "id": "step-bei-8-schwierigkeit-erhoehen-ea22a8e15c", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-goblet-squat-kb-9d21334b6f", + "key": "gobletkb" + }, + "halo": { + "name": "Bulletproof Shoulders", + "steps": [ + { + "name": "videogeführt leicht", + "id": "step-videogefuehrt-leicht-56eeb28b68", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "videogeführt leicht", + "id": "step-videogefuehrt-leicht-9826cc9337", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis 3×/Woche bei Trainingsstart", + "id": "step-bis-3-woche-bei-trainingsstart-00cccb34ea", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis 3×/Woche bei Trainingsstart", + "id": "step-bis-3-woche-bei-trainingsstart-78b10cb42c", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "kein Zusatzversagen", + "id": "step-kein-zusatzversagen-0c124a2d71", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "kein Zusatzversagen", + "id": "step-kein-zusatzversagen-09f1f363ad", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Volumen an Erholung anpassen", + "id": "step-volumen-an-erholung-anpassen-23680fda4b", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Volumen an Erholung anpassen", + "id": "step-volumen-an-erholung-anpassen-4d39ebbf93", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-bulletproof-shoulders-baa51fb7a5", + "key": "halo" + }, + "holl": { + "name": "Bracing", + "steps": [ + { + "name": "Rumpfspannung finden", + "id": "step-rumpfspannung-finden-715833661a", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Rumpfspannung finden", + "id": "step-rumpfspannung-finden-286734cc2c", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Atmung koordinieren", + "id": "step-atmung-koordinieren-356cc152d1", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Atmung koordinieren", + "id": "step-atmung-koordinieren-7ca2ee55df", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Alignment halten", + "id": "step-alignment-halten-0c7f26b946", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Alignment halten", + "id": "step-alignment-halten-3e1b54d2ac", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Spannung unter Last behalten", + "id": "step-spannung-unter-last-behalten-a0bbb8fda1", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Spannung unter Last behalten", + "id": "step-spannung-unter-last-behalten-a0e1b7b5a5", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-bracing-4a444af3ab", + "key": "holl" + }, + "kbdl": { + "name": "Hinge · erst Beginner Stage", + "steps": [ + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-e6ff14d812", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-62775896bf", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Squat-Stufe priorisieren", + "id": "step-squat-stufe-priorisieren-2eb20659e1", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Squat-Stufe priorisieren", + "id": "step-squat-stufe-priorisieren-f2390d80ab", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "später Hinge ergänzen", + "id": "step-spaeter-hinge-ergaenzen-490b1761f0", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "später Hinge ergänzen", + "id": "step-spaeter-hinge-ergaenzen-91c1162140", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Beginner: Squat + Hinge abdecken", + "id": "step-beginner-squat-hinge-abdecken-2ece0ba9ff", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Beginner: Squat + Hinge abdecken", + "id": "step-beginner-squat-hinge-abdecken-5ab250ae94", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-hinge-erst-beginner-stage-b36fdf9122", + "key": "kbdl" + }, + "load": { + "name": "Intensität", + "steps": [ + { + "name": "2 Reps Reserve", + "id": "step-2-reps-reserve-b3f06959cb", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "2 Reps Reserve", + "id": "step-2-reps-reserve-9e04b18fde", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "1–2 Reps Reserve", + "id": "step-1-2-reps-reserve-3ee69feb2c", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "1–2 Reps Reserve", + "id": "step-1-2-reps-reserve-4277bee1f0", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "80–90 % Intensität", + "id": "step-80-90-intensitaet-f631fb515d", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "80–90 % Intensität", + "id": "step-80-90-intensitaet-ba7745a692", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nie technische Reps erzwingen", + "id": "step-nie-technische-reps-erzwingen-01e23f5285", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nie technische Reps erzwingen", + "id": "step-nie-technische-reps-erzwingen-2fc20f3242", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-intensitaet-ffaebae137", + "key": "load" + }, + "lr": { + "name": "Teilwiederholungen", + "steps": [ + { + "name": "nur messbarer ROM", + "id": "step-nur-messbarer-rom-bb1e5eeeb1", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "nur messbarer ROM", + "id": "step-nur-messbarer-rom-45a26027a1", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "assistiert bevorzugen", + "id": "step-assistiert-bevorzugen-c93f4daab4", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "assistiert bevorzugen", + "id": "step-assistiert-bevorzugen-1e4bdf997d", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "ROM schrittweise erweitern", + "id": "step-rom-schrittweise-erweitern-02942f4f3c", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "ROM schrittweise erweitern", + "id": "step-rom-schrittweise-erweitern-0ac01e22f3", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Reps übergehen", + "id": "step-zu-vollen-reps-uebergehen-7b9eb05593", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Reps übergehen", + "id": "step-zu-vollen-reps-uebergehen-8f44aa56b7", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-teilwiederholungen-ce2394a937", + "key": "lr" + }, + "lraise": { + "name": "Scapular Protraction", + "steps": [ + { + "name": "Wall Scapular Push-up", + "id": "step-wall-scapular-push-up-fb0d9aa50e", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Wall Scapular Push-up", + "id": "step-wall-scapular-push-up-285b10eed8", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Ellbogen gestreckt", + "id": "step-ellbogen-gestreckt-665588a205", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Ellbogen gestreckt", + "id": "step-ellbogen-gestreckt-d3fc2e4a06", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schulterblätter spreizen", + "id": "step-schulterblaetter-spreizen-81e80cf408", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schulterblätter spreizen", + "id": "step-schulterblaetter-spreizen-81a36d2150", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "auf Push-up übertragen", + "id": "step-auf-push-up-uebertragen-cb7a342eb7", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "auf Push-up übertragen", + "id": "step-auf-push-up-uebertragen-26e3e559ac", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-scapular-protraction-39e62dc680", + "key": "lraise" + }, + "ls": { + "name": "Push-up-Progression", + "steps": [ + { + "name": "High Plank 10–30 s", + "id": "step-high-plank-10-30-s-cdd4db3e1a", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "High Plank 10–30 s", + "id": "step-high-plank-10-30-s-32710670d3", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "Negative Push-ups 4–8", + "id": "step-negative-push-ups-4-8-32248836e0", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "Negative Push-ups 4–8", + "id": "step-negative-push-ups-4-8-8375d11117", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "assistiert 4–8; Teil-Reps alternativ", + "id": "step-assistiert-4-8-teil-reps-alter-221168d839", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.55 + }, + { + "name": "assistiert 4–8; Teil-Reps alternativ", + "id": "step-assistiert-4-8-teil-reps-alter-be5708f622", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.55 + }, + { + "name": "volle Push-ups 4–8; bei 8 erschweren", + "id": "step-volle-push-ups-4-8-bei-8-ersch-293be75f2a", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "volle Push-ups 4–8; bei 8 erschweren", + "id": "step-volle-push-ups-4-8-bei-8-ersch-d000d16e0e", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + } + ], + "id": "progression-push-up-progression-359ecd088e", + "key": "ls" + }, + "lseng": { + "name": "Push-up-Startposition", + "steps": [ + { + "name": "Hände hoch / Knie, falls <10 s", + "id": "step-haende-hoch-knie-falls-10-s-1f36cd4622", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.45 + }, + { + "name": "Hände hoch / Knie, falls <10 s", + "id": "step-haende-hoch-knie-falls-10-s-b6c830cc39", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.45 + }, + { + "name": "High Plank bis 30 s", + "id": "step-high-plank-bis-30-s-23bc4b6dd7", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "High Plank bis 30 s", + "id": "step-high-plank-bis-30-s-8b154532b9", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "volle Körperspannung", + "id": "step-volle-koerperspannung-8633516312", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "volle Körperspannung", + "id": "step-volle-koerperspannung-276c3bf86e", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 1.0 + }, + { + "name": "danach Negative beginnen", + "id": "step-danach-negative-beginnen-26cc88afec", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "danach Negative beginnen", + "id": "step-danach-negative-beginnen-e608d8c7cf", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + } + ], + "id": "progression-push-up-startposition-90c54c14cd", + "key": "lseng" + }, + "lunge": { + "name": "Squat-Unterstützung", + "steps": [ + { + "name": "Türrahmen / Stange", + "id": "step-tuerrahmen-stange-4c3df7c28f", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Türrahmen / Stange", + "id": "step-tuerrahmen-stange-caf422d72a", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "volle kontrollierte Tiefe", + "id": "step-volle-kontrollierte-tiefe-6f677356fa", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "volle kontrollierte Tiefe", + "id": "step-volle-kontrollierte-tiefe-c04f6daa26", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Hilfe schrittweise reduzieren", + "id": "step-hilfe-schrittweise-reduzieren-51ef8c056f", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Hilfe schrittweise reduzieren", + "id": "step-hilfe-schrittweise-reduzieren-54e5047e9e", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Squats wechseln", + "id": "step-zu-vollen-squats-wechseln-82d34ee4f5", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "zu vollen Squats wechseln", + "id": "step-zu-vollen-squats-wechseln-e10129b753", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-squat-unterstuetzung-222f2deff0", + "key": "lunge" + }, + "mtn": { + "name": "Regeneration", + "steps": [ + { + "name": "kein Zusatztraining", + "id": "step-kein-zusatztraining-fabc8decb1", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kein Zusatztraining", + "id": "step-kein-zusatztraining-da35555351", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "locker bewegen", + "id": "step-locker-bewegen-7e2aef635d", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "locker bewegen", + "id": "step-locker-bewegen-3094be8801", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schmerz / Müdigkeit prüfen", + "id": "step-schmerz-muedigkeit-pruefen-350663ec9f", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Schmerz / Müdigkeit prüfen", + "id": "step-schmerz-muedigkeit-pruefen-a7e0aa4799", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bereit für nächste Einheit", + "id": "step-bereit-fuer-naechste-einheit-72427afb09", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "bereit für nächste Einheit", + "id": "step-bereit-fuer-naechste-einheit-9d674da4d6", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-regeneration-f844bb7be6", + "key": "mtn" + }, + "pike": { + "name": "Scapular Elevation · später", + "steps": [ + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-140468d1e4", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "nicht Hauptübung dieses Blocks", + "id": "step-nicht-hauptuebung-dieses-block-40a4ff1fd3", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "erst Schulterkontrolle", + "id": "step-erst-schulterkontrolle-2403f03a7d", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "erst Schulterkontrolle", + "id": "step-erst-schulterkontrolle-5cbef19162", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Pike gehört zur Handstandlinie", + "id": "step-pike-gehoert-zur-handstandlini-8e1b9bc569", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Pike gehört zur Handstandlinie", + "id": "step-pike-gehoert-zur-handstandlini-51b7deea32", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "später als zweite Push-Bewegung", + "id": "step-spaeter-als-zweite-push-bewegu-06cdaa4a23", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "später als zweite Push-Bewegung", + "id": "step-spaeter-als-zweite-push-bewegu-6ef900cf7a", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-scapular-elevation-spaeter-8e6c8a5e64", + "key": "pike" + }, + "plank": { + "name": "Plank-Progression", + "steps": [ + { + "name": "erhöht/Knie 10–30 s", + "id": "step-erhoeht-knie-10-30-s-9156743958", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "erhöht/Knie 10–30 s", + "id": "step-erhoeht-knie-10-30-s-90f0f841d9", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "volle Plank 10–30 s", + "id": "step-volle-plank-10-30-s-6995092df7", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "volle Plank 10–30 s", + "id": "step-volle-plank-10-30-s-7bdfab3260", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "schwierigere Hebelposition", + "id": "step-schwierigere-hebelposition-7929e48e37", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "schwierigere Hebelposition", + "id": "step-schwierigere-hebelposition-78087749e1", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Widerstand, stets 10–30 s", + "id": "step-widerstand-stets-10-30-s-60e1148a07", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Widerstand, stets 10–30 s", + "id": "step-widerstand-stets-10-30-s-e8b858d1c6", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-plank-progression-7010c23671", + "key": "plank" + }, + "press": { + "name": "Ganzkörper-Warm-up", + "steps": [ + { + "name": "5–10 Min", + "id": "step-5-10-min-93bfa2e9b4", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "5–10 Min", + "id": "step-5-10-min-5b6351f175", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Wrists vorbereiten", + "id": "step-wrists-vorbereiten-3bcfe9cd93", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Wrists vorbereiten", + "id": "step-wrists-vorbereiten-c9ff45ae10", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Scapula aktivieren", + "id": "step-scapula-aktivieren-358d0c340e", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Scapula aktivieren", + "id": "step-scapula-aktivieren-aff239eab9", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bewegungen kurz rehearsen", + "id": "step-bewegungen-kurz-rehearsen-ff239741c8", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Bewegungen kurz rehearsen", + "id": "step-bewegungen-kurz-rehearsen-61010dbc8f", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-ganzkoerper-warm-up-eb6587906d", + "key": "press" + }, + "row1": { + "name": "Inverted Row · Alternative", + "steps": [ + { + "name": "Scapular Retraction", + "id": "step-scapular-retraction-30fef1c0b2", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Scapular Retraction", + "id": "step-scapular-retraction-5f45079e2d", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "aufrechte assistierte Row", + "id": "step-aufrechte-assistierte-row-4bf42c3506", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 0.25 + }, + { + "name": "aufrechte assistierte Row", + "id": "step-aufrechte-assistierte-row-e76285811f", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 0.25 + }, + { + "name": "schrägere Row 4–8", + "id": "step-schraegere-row-4-8-4a327eb423", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "schrägere Row 4–8", + "id": "step-schraegere-row-4-8-ac387863bc", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "volle Row 4–8", + "id": "step-volle-row-4-8-c9b44ddc9b", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "volle Row 4–8", + "id": "step-volle-row-4-8-068ddaea25", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-inverted-row-alternative-fe88e8737a", + "key": "row1" + }, + "row2": { + "name": "Scapular Retraction", + "steps": [ + { + "name": "Schulterblätter zusammenführen", + "id": "step-schulterblaetter-zusammenfuehr-24fe3466f7", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Schulterblätter zusammenführen", + "id": "step-schulterblaetter-zusammenfuehr-f269293661", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Ellbogen zunächst gestreckt", + "id": "step-ellbogen-zunaechst-gestreckt-8127b3e7fc", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "Ellbogen zunächst gestreckt", + "id": "step-ellbogen-zunaechst-gestreckt-8ec16a7674", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "kontrolliert lösen", + "id": "step-kontrolliert-loesen-f6a1697ea4", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "kontrolliert lösen", + "id": "step-kontrolliert-loesen-8cdb5043d6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "auf Rows übertragen", + "id": "step-auf-rows-uebertragen-ae42ecf07c", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "auf Rows übertragen", + "id": "step-auf-rows-uebertragen-7b13c2423a", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-scapular-retraction-07a7ad4883", + "key": "row2" + }, + "rowe": { + "name": "Pull-up-Progression", + "steps": [ + { + "name": "Active Hang 10–30 s", + "id": "step-active-hang-10-30-s-5f0fb67e39", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.2 + }, + { + "name": "Active Hang 10–30 s", + "id": "step-active-hang-10-30-s-17217b9335", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.2 + }, + { + "name": "Scapular Pull-ups / Negative 4–8", + "id": "step-scapular-pull-ups-negative-4-8-9f93cb86a6", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.25 + }, + { + "name": "Scapular Pull-ups / Negative 4–8", + "id": "step-scapular-pull-ups-negative-4-8-aae37fad91", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.25 + }, + { + "name": "assistierte Pull-ups 4–8", + "id": "step-assistierte-pull-ups-4-8-0f04d54425", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.55 + }, + { + "name": "assistierte Pull-ups 4–8", + "id": "step-assistierte-pull-ups-4-8-75a3804b03", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 0.55 + }, + { + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "id": "step-volle-pull-ups-4-8-bei-8-ersch-13657948ce", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "id": "step-volle-pull-ups-4-8-bei-8-ersch-a12929a96c", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + } + ], + "id": "progression-pull-up-progression-e4ad50ac31", + "key": "rowe" + }, + "sp": { + "name": "Side Plank · später", + "steps": [ + { + "name": "Knie 10–30 s", + "id": "step-knie-10-30-s-a04fda4182", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Knie 10–30 s", + "id": "step-knie-10-30-s-fa5c3f0156", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "volle Side Plank 10–30 s", + "id": "step-volle-side-plank-10-30-s-34820f11a3", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "volle Side Plank 10–30 s", + "id": "step-volle-side-plank-10-30-s-be34794e30", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Hebel erschweren", + "id": "step-hebel-erschweren-e870313a11", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Hebel erschweren", + "id": "step-hebel-erschweren-4db785c23f", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "lateral chain ergänzen", + "id": "step-lateral-chain-ergaenzen-d5bf33ed6e", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "lateral chain ergänzen", + "id": "step-lateral-chain-ergaenzen-91ce5310d7", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-side-plank-spaeter-6dfa6a3b0d", + "key": "sp" + }, + "squat": { + "name": "Squat-Progression", + "steps": [ + { + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "id": "step-deep-squat-hold-60-s-bw-30-s-g-5829d21464", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "id": "step-deep-squat-hold-60-s-bw-30-s-g-ccda7124ba", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-a42ec5fa5c", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-97729a1ee3", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Negative / assistierte Squats 4–8", + "id": "step-negative-assistierte-squats-4--599708fcee", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.75 + }, + { + "name": "Negative / assistierte Squats 4–8", + "id": "step-negative-assistierte-squats-4--2141de14f1", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.75 + }, + { + "name": "volle Squats 4–8; bei 8 KB möglich", + "id": "step-volle-squats-4-8-bei-8-kb-moeg-42273d4c69", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "volle Squats 4–8; bei 8 KB möglich", + "id": "step-volle-squats-4-8-bei-8-kb-moeg-4bad8f3b1f", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-squat-progression-5cf9160e5b", + "key": "squat" + }, + "sup": { + "name": "Prone Y-T-W", + "steps": [ + { + "name": "saubere Y-Position", + "id": "step-saubere-y-position-529d1cd763", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere Y-Position", + "id": "step-saubere-y-position-a9b851db2a", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere T-Position", + "id": "step-saubere-t-position-83a7145889", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere T-Position", + "id": "step-saubere-t-position-435f2fd83b", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere W-Position", + "id": "step-saubere-w-position-87966ac5b9", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "saubere W-Position", + "id": "step-saubere-w-position-3e6795ac07", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter Y-T-W-Durchgang", + "id": "step-kontrollierter-y-t-w-durchgang-8a7eec7df8", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter Y-T-W-Durchgang", + "id": "step-kontrollierter-y-t-w-durchgang-b9b672cb47", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-prone-y-t-w-3a24eaf55c", + "key": "sup" + }, + "swing": { + "name": "Kettlebell in diesem Block", + "steps": [ + { + "name": "kein Swing-Zirkel", + "id": "step-kein-swing-zirkel-1cb890712e", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "kein Swing-Zirkel", + "id": "step-kein-swing-zirkel-f528b2dc38", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "nur Squat ggf. beladen", + "id": "step-nur-squat-ggf-beladen-7b222c3c81", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "nur Squat ggf. beladen", + "id": "step-nur-squat-ggf-beladen-37e386b7bc", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Volumen minimal halten", + "id": "step-volumen-minimal-halten-09fd077fa1", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Volumen minimal halten", + "id": "step-volumen-minimal-halten-0e5b8af5c0", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "KB bleibt Progressionswerkzeug", + "id": "step-kb-bleibt-progressionswerkzeug-8593f7b221", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "KB bleibt Progressionswerkzeug", + "id": "step-kb-bleibt-progressionswerkzeug-64625bcf5d", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-kettlebell-in-diesem-block-c5679bd0d4", + "key": "swing" + }, + "tsquat": { + "name": "Negative Squats", + "steps": [ + { + "name": "langsam absenken", + "id": "step-langsam-absenken-b250a57257", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "langsam absenken", + "id": "step-langsam-absenken-acd3a9f13b", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "unterstützt aufstehen", + "id": "step-unterstuetzt-aufstehen-e3f0b8a12a", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "unterstützt aufstehen", + "id": "step-unterstuetzt-aufstehen-47f8c015d9", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 sauber", + "id": "step-4-8-sauber-bc48af55e6", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "4–8 sauber", + "id": "step-4-8-sauber-68c4019fd2", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "nach 8 assistierte/volle Reps", + "id": "step-nach-8-assistierte-volle-reps-5cfb95beec", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "nach 8 assistierte/volle Reps", + "id": "step-nach-8-assistierte-volle-reps-a9e8b3c9b0", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + } + ], + "id": "progression-negative-squats-83b51fc468", + "key": "tsquat" + }, + "tw": { + "name": "Playlist 7 Mobility", + "steps": [ + { + "name": "optional separat", + "id": "step-optional-separat-7b29ebf83e", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "optional separat", + "id": "step-optional-separat-11ae49b683", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter ROM", + "id": "step-kontrollierter-rom-cb99075262", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "kontrollierter ROM", + "id": "step-kontrollierter-rom-b2993f6eaf", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "2–4×/Woche möglich", + "id": "step-2-4-woche-moeglich-1d1637eb3b", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "2–4×/Woche möglich", + "id": "step-2-4-woche-moeglich-2fbef46ba6", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nicht als Krafttraining", + "id": "step-nicht-als-krafttraining-6da59933d1", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "nicht als Krafttraining", + "id": "step-nicht-als-krafttraining-6f87b4cdd0", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-playlist-7-mobility-cf0245d08b", + "key": "tw" + }, + "wall": { + "name": "Active Hang", + "steps": [ + { + "name": "mit Fußhilfe, falls <10 s", + "id": "step-mit-fusshilfe-falls-10-s-51b27519a1", + "phase_id": "phase-phase-1-level-finden-amp-techn-ae1454813c", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "mit Fußhilfe, falls <10 s", + "id": "step-mit-fusshilfe-falls-10-s-d6138fdd57", + "phase_id": "phase-phase-2-level-finden-amp-techn-bc8640a305", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "10–30 s halten", + "id": "step-10-30-s-halten-bec653e06c", + "phase_id": "phase-phase-3-stabilisieren-amp-mikr-0d23157aec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "10–30 s halten", + "id": "step-10-30-s-halten-099397e967", + "phase_id": "phase-phase-4-stabilisieren-amp-mikr-bcd6e4dab9", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "Außenrotation + Lat-Aktivierung", + "id": "step-aussenrotation-lat-aktivierung-c0b5612e97", + "phase_id": "phase-phase-5-kriterienbasiert-aufst-f83f7d2734", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "Außenrotation + Lat-Aktivierung", + "id": "step-aussenrotation-lat-aktivierung-9b17309383", + "phase_id": "phase-phase-6-kriterienbasiert-aufst-946cac84c8", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "keine maximale Depression", + "id": "step-keine-maximale-depression-6eff5e8f20", + "phase_id": "phase-phase-7-festigen-amp-neu-teste-65823eedde", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + }, + { + "name": "keine maximale Depression", + "id": "step-keine-maximale-depression-cfa68fca9a", + "phase_id": "phase-phase-8-festigen-amp-neu-teste-bc626766b3", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_pull", + "factor": 1.0 + } + ], + "id": "progression-active-hang-ae19c88465", + "key": "wall" + } + }, + "training_format": { + "mode": "sets_reps", + "fixed_interval": false, + "work_seconds": null, + "rest_seconds": null, + "rounds": null, + "rounds_scope": "block", + "schema_version": 1 + }, + "schema_version": 3, + "contract_version": 2, + "plan_id": "calihoss-newbie-8-wochen", + "exercise_catalog": { + "movement-push-up-progression-118a55be62": { + "id": "movement-push-up-progression-118a55be62", + "name": "Push-up-Progression", + "progression_id": "ls", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-high-plank-10-30-s-cdd4db3e1a", + "name": "High Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-high-plank-10-30-s-32710670d3", + "name": "High Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-push-ups-4-8-32248836e0", + "name": "Negative Push-ups 4–8", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-push-ups-4-8-8375d11117", + "name": "Negative Push-ups 4–8", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistiert-4-8-teil-reps-alter-221168d839", + "name": "assistiert 4–8; Teil-Reps alternativ", + "factor": 0.55, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistiert-4-8-teil-reps-alter-be5708f622", + "name": "assistiert 4–8; Teil-Reps alternativ", + "factor": 0.55, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-push-ups-4-8-bei-8-ersch-293be75f2a", + "name": "volle Push-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-push-ups-4-8-bei-8-ersch-d000d16e0e", + "name": "volle Push-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-pull-up-progression-68d78ccd63": { + "id": "movement-pull-up-progression-68d78ccd63", + "name": "Pull-up-Progression", + "progression_id": "rowe", + "movement_cluster": "vertical_pull", + "movement_label": "Pull-up / Hang", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-active-hang-10-30-s-5f0fb67e39", + "name": "Active Hang 10–30 s", + "factor": 0.2, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-active-hang-10-30-s-17217b9335", + "name": "Active Hang 10–30 s", + "factor": 0.2, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-scapular-pull-ups-negative-4-8-9f93cb86a6", + "name": "Scapular Pull-ups / Negative 4–8", + "factor": 0.25, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-scapular-pull-ups-negative-4-8-aae37fad91", + "name": "Scapular Pull-ups / Negative 4–8", + "factor": 0.25, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistierte-pull-ups-4-8-0f04d54425", + "name": "assistierte Pull-ups 4–8", + "factor": 0.55, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-assistierte-pull-ups-4-8-75a3804b03", + "name": "assistierte Pull-ups 4–8", + "factor": 0.55, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-pull-ups-4-8-bei-8-ersch-13657948ce", + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-pull-ups-4-8-bei-8-ersch-a12929a96c", + "name": "volle Pull-ups 4–8; bei 8 erschweren", + "factor": 1.0, + "movement_cluster": "vertical_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-squat-progression-b93d6a593b": { + "id": "movement-squat-progression-b93d6a593b", + "name": "Squat-Progression", + "progression_id": "squat", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "auto", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-deep-squat-hold-60-s-bw-30-s-g-5829d21464", + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-deep-squat-hold-60-s-bw-30-s-g-ccda7124ba", + "name": "Deep Squat Hold: 60 s BW / 30 s gewichtet", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-a42ec5fa5c", + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-1-2-saetze-jeden-2-tag-erst-ko-97729a1ee3", + "name": "1–2 Sätze jeden 2. Tag; erst komfortabel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-assistierte-squats-4--599708fcee", + "name": "Negative / assistierte Squats 4–8", + "factor": 0.75, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negative-assistierte-squats-4--2141de14f1", + "name": "Negative / assistierte Squats 4–8", + "factor": 0.75, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-squats-4-8-bei-8-kb-moeg-42273d4c69", + "name": "volle Squats 4–8; bei 8 KB möglich", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-squats-4-8-bei-8-kb-moeg-4bad8f3b1f", + "name": "volle Squats 4–8; bei 8 KB möglich", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-plank-progression-31156b972b": { + "id": "movement-plank-progression-31156b972b", + "name": "Plank-Progression", + "progression_id": "plank", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-erhoeht-knie-10-30-s-9156743958", + "name": "erhöht/Knie 10–30 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-erhoeht-knie-10-30-s-90f0f841d9", + "name": "erhöht/Knie 10–30 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-plank-10-30-s-6995092df7", + "name": "volle Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-volle-plank-10-30-s-7bdfab3260", + "name": "volle Plank 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwierigere-hebelposition-7929e48e37", + "name": "schwierigere Hebelposition", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwierigere-hebelposition-78087749e1", + "name": "schwierigere Hebelposition", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-widerstand-stets-10-30-s-60e1148a07", + "name": "Widerstand, stets 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-widerstand-stets-10-30-s-e8b858d1c6", + "name": "Widerstand, stets 10–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-bulletproof-shoulders-c0a078d108": { + "id": "movement-bulletproof-shoulders-c0a078d108", + "name": "Bulletproof Shoulders", + "progression_id": "name:bulletproof-shoulders", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-prone-y-t-w-technik-80560abe41": { + "id": "movement-prone-y-t-w-technik-80560abe41", + "name": "Prone Y-T-W Technik", + "progression_id": "name:prone-y-t-w-technik", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-lockerer-spaziergang-cd93978ea1": { + "id": "movement-lockerer-spaziergang-cd93978ea1", + "name": "Lockerer Spaziergang", + "progression_id": "name:lockerer-spaziergang", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-regeneration-pruefen-3d83efff8f": { + "id": "movement-regeneration-pruefen-3d83efff8f", + "name": "Regeneration prüfen", + "progression_id": "name:regeneration-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-wrist-conditioning-ee8f71f37e": { + "id": "movement-wrist-conditioning-ee8f71f37e", + "name": "Wrist Conditioning", + "progression_id": "name:wrist-conditioning", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-finger-plank-456618869c": { + "id": "movement-finger-plank-456618869c", + "name": "Finger Plank", + "progression_id": "name:finger-plank", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-full-body-mobility-f4ab17f25a": { + "id": "movement-full-body-mobility-f4ab17f25a", + "name": "Full Body Mobility", + "progression_id": "name:full-body-mobility", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-sonst-vollstaendig-pausieren-b291d0cf69": { + "id": "movement-sonst-vollstaendig-pausieren-b291d0cf69", + "name": "Sonst vollständig pausieren", + "progression_id": "name:sonst-vollstaendig-pausieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-sprunggelenk-mobilitaet-ef05527f38": { + "id": "movement-sprunggelenk-mobilitaet-ef05527f38", + "name": "Sprunggelenk-Mobilität", + "progression_id": "name:sprunggelenk-mobilitaet", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-hueft-mobilitaet-7836e93f5c": { + "id": "movement-hueft-mobilitaet-7836e93f5c", + "name": "Hüft-Mobilität", + "progression_id": "name:hueft-mobilitaet", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-brustwirbelsaeule-schultern-02622f8d13": { + "id": "movement-brustwirbelsaeule-schultern-02622f8d13", + "name": "Brustwirbelsäule / Schultern", + "progression_id": "name:brustwirbelsaeule-schultern", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-spaziergang-feb087e265": { + "id": "movement-spaziergang-feb087e265", + "name": "Spaziergang", + "progression_id": "name:spaziergang", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "minutes", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-schlaf-priorisieren-b6845205e5": { + "id": "movement-schlaf-priorisieren-b6845205e5", + "name": "Schlaf priorisieren", + "progression_id": "name:schlaf-priorisieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-1-2-reps-reserve-pruefen-85e3b5af37": { + "id": "movement-1-2-reps-reserve-pruefen-85e3b5af37", + "name": "1–2 Reps Reserve prüfen", + "progression_id": "name:1-2-reps-reserve-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-schmerzcheck-f658f713e7": { + "id": "movement-schmerzcheck-f658f713e7", + "name": "Schmerzcheck", + "progression_id": "name:schmerzcheck", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-push-protokollieren-e0770774a1": { + "id": "movement-push-protokollieren-e0770774a1", + "name": "Push protokollieren", + "progression_id": "name:push-protokollieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-pull-protokollieren-71a9be182e": { + "id": "movement-pull-protokollieren-71a9be182e", + "name": "Pull protokollieren", + "progression_id": "name:pull-protokollieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-beine-protokollieren-712a79f464": { + "id": "movement-beine-protokollieren-712a79f464", + "name": "Beine protokollieren", + "progression_id": "name:beine-protokollieren", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-core-protokollieren-279940b6d0": { + "id": "movement-core-protokollieren-279940b6d0", + "name": "Core protokollieren", + "progression_id": "name:core-protokollieren", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-startposition-pruefen-0676749716": { + "id": "movement-startposition-pruefen-0676749716", + "name": "Startposition prüfen", + "progression_id": "name:startposition-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-negative-pruefen-363284950d": { + "id": "movement-negative-pruefen-363284950d", + "name": "Negative prüfen", + "progression_id": "name:negative-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-volle-reps-pruefen-4955104cc2": { + "id": "movement-volle-reps-pruefen-4955104cc2", + "name": "Volle Reps prüfen", + "progression_id": "name:volle-reps-pruefen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-schwierigkeit-erhoehen-2b4e278d42": { + "id": "movement-schwierigkeit-erhoehen-2b4e278d42", + "name": "Schwierigkeit erhöhen", + "progression_id": "name:schwierigkeit-erhoehen", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "none", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + } + } + } +} diff --git a/testdata/plans/phase-1.json b/testdata/plans/phase-1.json new file mode 100644 index 0000000..c06dd15 --- /dev/null +++ b/testdata/plans/phase-1.json @@ -0,0 +1,16995 @@ +{ + "plan_id": "phase-1", + "name": "Phase 1", + "revision": 1, + "published_revision": 1, + "created_at": "2026-07-24T09:43:48+00:00", + "updated_at": "2026-07-24T09:43:48+00:00", + "published_at": "2026-07-24T09:43:48+00:00", + "config": { + "colors": { + "ACC2": "#0F766E", + "ACCENT": "#C2410C", + "DARK": "#23201C", + "GOALBG": "#FBF1E9", + "GRID": "#CFC9BF", + "INK": "#1A1A1A", + "KBBG": "#F1ECFB", + "KBCOL": "#6D28D9", + "LIGHT": "#F4F1EC", + "LINK": "#1565C0", + "MUTE": "#6B6256", + "WARMBG": "#EAF1F0" + }, + "days": [ + { + "focus": "Drücken + Beine", + "num": 1, + "rotations": [ + { + "exercises": [ + { + "cue": "Brust kontrolliert senken & drücken", + "key": "ls", + "name": "Liegestütze", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-b5c3e6155f", + "exercise_id": "movement-liegestuetze-57eacf74ee", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d1-r0-e0" + }, + { + "cue": "Po vor der Couch senken, hochdrücken", + "key": "dips", + "name": "Bank-Dips (Couch)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "dips", + "id": "exercise-1-ada8cf48d3", + "exercise_id": "movement-bank-dips-couch-ad6c829e1b", + "movement_cluster": "vertical_push", + "movement_label": "Dip", + "legacy_id": "d1-r0-e1" + }, + { + "cue": "umgek. V, Kopf senken (Schultern)", + "key": "pike", + "name": "Pike", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "pike", + "id": "exercise-2-1cb8b9bbd7", + "exercise_id": "movement-pike-17f7716e00", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d1-r0-e2" + }, + { + "cue": "Arme seitlich bis Schulterhöhe, kontrolliert", + "key": "lraise", + "name": "Seitheben (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "lraise", + "id": "exercise-3-2908dafdf9", + "exercise_id": "movement-seitheben-kb-rucksack-91de5dc2f5", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d1-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-7bd8374b98" + }, + { + "exercises": [ + { + "cue": "Last umarmen, tief", + "key": "gobletkb", + "name": "Goblet Squat (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "gobletkb", + "id": "exercise-item-7816b10b95", + "exercise_id": "movement-goblet-squat-kb-rucksack-6ff9f90901", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r1-e0" + }, + { + "cue": "großer Schritt, Knie Richtung Boden", + "key": "lunge", + "name": "Ausfallschritt rückw.", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "lunge", + "id": "exercise-1-232e3c9065", + "exercise_id": "movement-ausfallschritt-rueckw-2a4d06b799", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r1-e1" + }, + { + "cue": "Hüfte hochdrücken, Po fest", + "key": "bridge", + "name": "Glute Bridge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "bridge", + "id": "exercise-2-0a8da65f1b", + "exercise_id": "movement-glute-bridge-1c0a7bf699", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d1-r1-e2" + }, + { + "cue": "auf Zehenspitzen hoch/runter", + "key": "calf", + "name": "Wadenheben", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "calf", + "id": "exercise-3-bfc4e8f9c3", + "exercise_id": "movement-wadenheben-0c7839551b", + "movement_cluster": "calf", + "movement_label": "Waden", + "legacy_id": "d1-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-b9e0a052af" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-druecken-beine-9f04ffe00b" + }, + { + "focus": "Ziehen + Core", + "num": 2, + "rotations": [ + { + "exercises": [ + { + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts", + "key": "rowe", + "name": "Einarm-Rudern ENG (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "rowe", + "id": "exercise-item-8b823b51cb", + "exercise_id": "movement-einarm-rudern-eng-kb-4907fbb5c7", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d2-r0-e0" + }, + { + "cue": "Oberarme fix, zu den Schultern", + "key": "load", + "name": "Bizeps-Curls (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "load", + "id": "exercise-1-1a66ac153c", + "exercise_id": "movement-bizeps-curls-kb-rucksack-67944f00d5", + "movement_cluster": "elbow_flexion", + "movement_label": "Bizeps", + "legacy_id": "d2-r0-e1" + }, + { + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts", + "key": "row2", + "name": "Einarm-Rudern BREIT (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "row2", + "id": "exercise-2-ab58589731", + "exercise_id": "movement-einarm-rudern-breit-kb-0146abcfbb", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d2-r0-e2" + }, + { + "cue": "bäuchlings Arme+Beine heben", + "key": "sup", + "name": "Superman / Y-T-W", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "sup", + "id": "exercise-3-8974cdd4a3", + "exercise_id": "movement-superman-y-t-w-279f03a544", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-024cf0041d" + }, + { + "exercises": [ + { + "cue": "gerade Linie halten", + "key": "plank", + "name": "Plank", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-item-40e37731b0", + "exercise_id": "movement-plank-c8449f2937", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d2-r1-e0" + }, + { + "cue": "Knie zügig zur Brust", + "key": "mtn", + "name": "Mountain Climbers", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "mtn", + "id": "exercise-1-69ed72c9c3", + "exercise_id": "movement-mountain-climbers-79efdb62dd", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e1" + }, + { + "cue": "gegengleich, Rücken am Boden", + "key": "db", + "name": "Dead Bug", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "db", + "id": "exercise-2-0fc9fa4d38", + "exercise_id": "movement-dead-bug-21d4b207d5", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d2-r1-e2" + }, + { + "cue": "Beine heben/senken", + "key": "lr", + "name": "Beinheben", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "lr", + "id": "exercise-3-1373648dc9", + "exercise_id": "movement-beinheben-faf19a629b", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-f767e75f57" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-ziehen-core-26dd9ab93d" + }, + { + "focus": "Beine + Core", + "num": 3, + "rotations": [ + { + "exercises": [ + { + "cue": "Last umarmen, tief", + "key": "gobletkb", + "name": "Goblet Squat (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "gobletkb", + "id": "exercise-item-0264c1e44a", + "exercise_id": "movement-goblet-squat-kb-rucksack-6ff9f90901", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r0-e0" + }, + { + "cue": "Hüfte zurück, Rücken gerade, Hamstrings", + "key": "load", + "name": "RDL / Hip Hinge (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "load", + "id": "exercise-1-9d276268ca", + "exercise_id": "movement-rdl-hip-hinge-kb-rucksack-9ada5a67c3", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d3-r0-e1" + }, + { + "cue": "ein Bein gestreckt", + "key": "bridge", + "name": "Glute Bridge 1-beinig", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "bridge", + "id": "exercise-2-4b6e8a5bc6", + "exercise_id": "movement-glute-bridge-1-beinig-ccc8c9fb53", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d3-r0-e2" + }, + { + "cue": "Rücken an die Wand, halten", + "key": "wall", + "name": "Wall Sit", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "wall", + "id": "exercise-3-add8004d7e", + "exercise_id": "movement-wall-sit-6b5f695f6a", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-de121c91a0" + }, + { + "exercises": [ + { + "cue": "unterer Rücken am Boden", + "key": "holl", + "name": "Hollow Hold", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "holl", + "id": "exercise-item-072e8d32be", + "exercise_id": "movement-hollow-hold-54c452f77b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r1-e0" + }, + { + "cue": "seitlich auf einem Unterarm", + "key": "sp", + "name": "Side Plank (Wechsel)", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "sp", + "id": "exercise-1-cedec1156e", + "exercise_id": "movement-side-plank-wechsel-309b0bc82c", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r1-e1" + }, + { + "cue": "Ellbogen zum Gegenknie", + "key": "bic", + "name": "Bicycle Crunch", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "bic", + "id": "exercise-2-fe1a6ad3ca", + "exercise_id": "movement-bicycle-crunch-15c87e28c5", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d3-r1-e2" + }, + { + "cue": "sitzend Rumpf drehen", + "key": "tw", + "name": "Russian Twist (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "tw", + "id": "exercise-3-bd5b54584a", + "exercise_id": "movement-russian-twist-kb-d6e382fb3c", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d3-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-017be520c7" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-beine-core-13963d0eea" + }, + { + "focus": "Oberkörper: Drücken + Ziehen", + "num": 4, + "rotations": [ + { + "exercises": [ + { + "cue": "Brust kontrolliert senken & drücken", + "key": "ls", + "name": "Liegestütze", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-3cc877a2d9", + "exercise_id": "movement-liegestuetze-57eacf74ee", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d4-r0-e0" + }, + { + "cue": "Po vor der Couch senken", + "key": "dips", + "name": "Bank-Dips (Couch)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "dips", + "id": "exercise-1-57f3db08a5", + "exercise_id": "movement-bank-dips-couch-ad6c829e1b", + "movement_cluster": "vertical_push", + "movement_label": "Dip", + "legacy_id": "d4-r0-e1" + }, + { + "cue": "umgek. V, Kopf senken", + "key": "pike", + "name": "Pike", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "pike", + "id": "exercise-2-f8e46033f7", + "exercise_id": "movement-pike-17f7716e00", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d4-r0-e2" + }, + { + "cue": "Glocke vom Boden weg drücken", + "key": "press", + "name": "KB Floor Press", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "press", + "id": "exercise-3-1bfa657543", + "exercise_id": "movement-kb-floor-press-060586ebd6", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d4-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-761392c073" + }, + { + "exercises": [ + { + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts", + "key": "rowe", + "name": "Einarm-Rudern ENG (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "rowe", + "id": "exercise-item-3eecbcd916", + "exercise_id": "movement-einarm-rudern-eng-kb-4907fbb5c7", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d4-r1-e0" + }, + { + "cue": "Oberarme fix, zu den Schultern", + "key": "load", + "name": "Bizeps-Curls (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "load", + "id": "exercise-1-5503931f9a", + "exercise_id": "movement-bizeps-curls-kb-rucksack-67944f00d5", + "movement_cluster": "elbow_flexion", + "movement_label": "Bizeps", + "legacy_id": "d4-r1-e1" + }, + { + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts", + "key": "row2", + "name": "Einarm-Rudern BREIT (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "row2", + "id": "exercise-2-7d45b03b10", + "exercise_id": "movement-einarm-rudern-breit-kb-0146abcfbb", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d4-r1-e2" + }, + { + "cue": "bäuchlings, Rücken", + "key": "sup", + "name": "Y-T-W / Superman", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "sup", + "id": "exercise-3-ffc825e874", + "exercise_id": "movement-y-t-w-superman-1d331ef219", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-62363a4e87" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-oberkoerper-druecken-ziehen-15d0e7f677" + }, + { + "focus": "Ganzkörper LEICHT + Mobility", + "light": true, + "num": 5, + "rotations": [ + { + "exercises": [ + { + "cue": "locker, sauber", + "key": "name:kniebeuge-leicht", + "name": "Kniebeuge (leicht)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:kniebeuge-leicht", + "id": "exercise-item-4b0e2395e9", + "exercise_id": "movement-kniebeuge-leicht-4ae3fb9fc0", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d5-r0-e0" + }, + { + "cue": "leichtere Variante", + "key": "name:liegestuetze-1-stufe-leichter", + "name": "Liegestütze (1 Stufe leichter)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:liegestuetze-1-stufe-leichter", + "id": "exercise-1-9303fa85bc", + "exercise_id": "movement-liegestuetze-1-stufe-leichter-6033b9cb92", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d5-r0-e1" + }, + { + "cue": "wenig Gewicht", + "key": "name:rudern-leicht", + "name": "Rudern (leicht)", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:rudern-leicht", + "id": "exercise-2-5224ba2082", + "exercise_id": "movement-rudern-leicht-566f8de8f2", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d5-r0-e2" + }, + { + "cue": "ruhiges Tempo", + "key": "name:mountain-climbers-locker", + "name": "Mountain Climbers (locker)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:mountain-climbers-locker", + "id": "exercise-3-3f1a039ad3", + "exercise_id": "movement-mountain-climbers-locker-e4a7854ab9", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-1750a46419" + }, + { + "exercises": [ + { + "cue": "in den Stütz wandern, zurück", + "key": "name:inchworm", + "name": "Inchworm", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:inchworm", + "id": "exercise-item-03be6e8387", + "exercise_id": "movement-inchworm-50016ff2a4", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r1-e0" + }, + { + "cue": "Rücken ründen/strecken", + "key": "name:cat-cow", + "name": "Cat-Cow", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:cat-cow", + "id": "exercise-1-92862f7d6a", + "exercise_id": "movement-cat-cow-d8e060c65f", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r1-e1" + }, + { + "cue": "gegengleich Arm+Bein, kurz halten", + "key": "name:bird-dog", + "name": "Bird Dog", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "name:bird-dog", + "id": "exercise-2-46d9abeab7", + "exercise_id": "movement-bird-dog-f2c9d49c80", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r1-e2" + }, + { + "cue": "langsam um den Kopf kreisen", + "key": "name:kb-halo-leicht", + "name": "KB-Halo (leicht)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "name:kb-halo-leicht", + "id": "exercise-3-e6cefad215", + "exercise_id": "movement-kb-halo-leicht-e3c156d79e", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d5-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-dde0c1adc1" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-ganzkoerper-leicht-mobility-ff8134fa48" + }, + { + "color": "#6D28D9", + "focus": "KETTLEBELL Ganzkörper", + "note": "Reine Kettlebell-Session. Swing = Hüft-Schnapp, kein Armheben. Glocke so wählen, dass alle Reps sauber sind.", + "note_bg": "#F1ECFB", + "num": 6, + "rotations": [ + { + "exercises": [ + { + "cue": "Hüft-Schnapp, kein Armheben", + "key": "swing", + "name": "KB Swing (zweiarmig)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "swing", + "id": "exercise-item-2fbf3bf70d", + "exercise_id": "movement-kb-swing-zweiarmig-327d7b5ad6", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d6-r0-e0" + }, + { + "cue": "Glocke vor der Brust, tief", + "key": "gobletkb", + "name": "Goblet Squat (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "gobletkb", + "id": "exercise-1-8542eb2e51", + "exercise_id": "movement-goblet-squat-kb-f065dfd5b6", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d6-r0-e1" + }, + { + "cue": "Glocke zwischen den Füßen, Hüfte hinge", + "key": "kbdl", + "name": "KB-Deadlift (beidbeinig)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "kbdl", + "id": "exercise-2-f072cf1841", + "exercise_id": "movement-kb-deadlift-beidbeinig-6f7c8bca76", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d6-r0-e2" + }, + { + "cue": "eng um den Kopf kreisen", + "key": "halo", + "name": "Halo", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "halo", + "id": "exercise-3-031200e160", + "exercise_id": "movement-halo-faca9a5cc9", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d6-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-1165003f5a" + }, + { + "exercises": [ + { + "cue": "Glocke vom Boden weg drücken", + "key": "press", + "name": "KB Floor Press", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "press", + "id": "exercise-item-04a3fceec5", + "exercise_id": "movement-kb-floor-press-060586ebd6", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d6-r1-e0" + }, + { + "cue": "Rücken gerade, zum Bauch", + "key": "row1", + "name": "Einarmiges Rudern (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "row1", + "id": "exercise-1-01087258d2", + "exercise_id": "movement-einarmiges-rudern-kb-0ec1c11b35", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d6-r1-e1" + }, + { + "cue": "Glocke Boden nach oben, Unterarm senkrecht, still halten", + "key": "carry", + "name": "Bottoms-up KB-Hold (Griff)", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "carry", + "id": "exercise-2-0a835e4b76", + "exercise_id": "movement-bottoms-up-kb-hold-griff-4a5f657f41", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d6-r1-e2" + }, + { + "cue": "Glocke um den Körper, dann diagonal zur Gegen-Schulter, andere Hand fängt", + "key": "atw", + "name": "Around-the-World + Uppercut (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "atw", + "id": "exercise-3-6386738afd", + "exercise_id": "movement-around-the-world-uppercut-kb-7ea726bbde", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d6-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-c851db6efb" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-kettlebell-ganzkoerper-e53128a814" + } + ], + "front": { + "baseline_head": "Standortbestimmung & Tracking", + "baseline_intro": "Mach die Tests vor Tag 1 und in Woche 8 identisch nochmal – dein Vorher/Nachher.", + "baseline_rows": [ + [ + "Test", + "Tag 0", + "Woche 8" + ], + [ + "Liegestütze (max, saubere Reps)", + "", + "" + ], + [ + "Bank-Dips an der Couch (max)", + "", + "" + ], + [ + "Kniebeugen in 60 Sekunden", + "", + "" + ], + [ + "Ausfallschritte (max gesamt)", + "", + "" + ], + [ + "Plank (max Sekunden)", + "", + "" + ], + [ + "Wall Sit (max Sekunden)", + "", + "" + ], + [ + "KB Farmer-Hold (max Sekunden, Griff)", + "", + "" + ], + [ + "Körpergewicht (kg)", + "95", + "" + ] + ], + "flow_rows": [ + [ + "Teil", + "Inhalt", + "Zeit" + ], + [ + "Warm-up", + "Gelenk-Prep (dynamisch)", + "~3 Min" + ], + [ + "Rotation A", + "4 Übungen, 3 Sätze (kurze Pausen)", + "~14 Min" + ], + [ + "Rotation B", + "4 Übungen, 3 Sätze (kurze Pausen)", + "~14 Min" + ], + [ + "Cool-down", + "Ausklingen + Dehnen", + "~6 Min" + ], + [ + "", + "Gesamt", + "~35 Min" + ] + ], + "goals_box": "Du startest bei 2–3 Liegestützen und 0 Dips. Realistisch und ehrlich nach 8 Wochen sauberer Arbeit:
•  Liegestütze: von 2–3 auf erste saubere volle Reps (Rest in Knie-/Negativ-Form).
•  Bank-Dips: saubere Reps mit Füßen etwas weiter weg.   Pike Push-up: am Boden mit kleinem ROM.
•  Plank ~45–60 s, Wall Sit ~60 s, viele saubere Goblet-Kniebeugen, sauberer Swing + spürbar bessere Griffkraft.
•  Noch NICHT dran: Diamond Push-ups, L-Sit, Handstand, schwerer Turkish Get-Up, Snatch – Stoff für Woche 12+.", + "goals_head": "Realistische Ziele nach 8 Wochen", + "goals_note": "Wichtig: Die Wochen-Stufen sind ein Rahmen, kein Muss. Bleib auf einer Stufe, solange sie fordert. Aufstiegs-Regel: erst hoch (oder schwerere Glocke), wenn du eine Übung 2 Sessions in Folge in allen Sätzen sauber über die vollen 20s schaffst. KB = Kettlebell bevorzugt; Rucksack bleibt als Alternative.", + "kb_box": "Du trainierst für Calisthenics, nicht für Maximalkraft. Genau da liegen die belegten Stärken der Kettlebell – ohne dass dich ihr dokumentierter Schwachpunkt (Maximalkraft/Hypertrophie wegen fixer Gewichtssprünge) trifft:
•  Griffkraft (Studie BELL: +6–7 kg) – dein größter Mehrwert auf dem Weg zu Klimmzug & Dip.
•  Power / ballistische Hüftstreckung (Swing) – explosive Basis für spätere Skills.
•  Kondition & Körperfett – wirksam belegt; du startest mit wenig Ausdauer.
•  Ganzkörper, ein Gerät, zeiteffizient – passt ideal ins 30-Min-Tabata-Format; niedriges Einstiegs-Verletzungsrisiko bei sauberer Technik.
Ehrlich: Reines KB ersetzt kein Maximalkraft-/Muskelaufbau-Programm. Für dein Ziel (Basis-Fundament) liegt das ohnehin unter der relevanten Schwelle.", + "kb_head": "Warum Kettlebell – und warum es zu deinem Ziel passt", + "lib_head": "Übungs-Bibliothek (mit Anleitungen)", + "lib_intro": "Für Übungen, die du noch nicht kennst, ist ein Link zu einer bebilderten Anleitung dabei. Lila Gruppe = Kettlebell-Übungen.", + "reminder_head": "Kurz-Reminder", + "reminders": [ + "Form vor Reps. Wähle die Stufe / Glocke, bei der du die 20s kontrolliert durcharbeitest – mit 1–2 Reps Reserve.", + "Swing-Technik first. Der Swing ist ein Hüft-Schnapp, KEIN Kniebeuge und KEIN Armheben. Erst Dead-Stop-Swings, bis die Hüftstreckung sitzt.", + "Einseitige Übungen (Tag 2): Durchgang 1 = links, Durchgang 2 = rechts – so ist jede Seite automatisch ausbalanciert.", + "Täglich ist viel. Tag 5 leicht, Tag 7 frei. Bei starkem Muskelkater einen Extra-Ruhetag – gehört zum Aufbau.", + "Schlaf & Eiweiß. 7–9 h Schlaf, ~1,6 g Eiweiß pro kg. Stechender Schmerz = stoppen." + ], + "session_how_body": "Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.", + "session_how_head": "So funktioniert eine Session", + "sheets_note": "Ab der nächsten Seite: 1 Druckblatt pro Session (Woche 1–8). Tag 6 = reine Kettlebell-Session. Warm-up steht über, Cool-down + Dehnen unter den Übungen. Bei Vorerkrankungen/Verletzungen vorher ärztlich abklären.", + "timer_note": "Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10." + }, + "library": [ + { + "color": "ACC2", + "group": "DRÜCKEN (Brust / Schultern / Trizeps)", + "items": [ + { + "desc": "Stütz, Körper gerade, Brust kontrolliert senken & drücken. Stufen: Inkline (Couch), Knie, Negativ, Voll.", + "name": "Liegestütze", + "url": "https://blog.calimove.com/2023/01/25/push-up-variations-beginner-expert/" + }, + { + "desc": "Rücken zur Couch, Hände auf der Kante, Po davor absenken (Ellbogen nach hinten), hochdrücken. Knie beugen = leichter.", + "name": "Bank-Dips (Couch)", + "url": "https://www.healthline.com/health/fitness-exercise/bench-dips" + }, + { + "desc": "Umgekehrtes V (Hintern hoch), Kopf Richtung Boden senken, hochdrücken. Schulter-Fokus. Hände erhöht = leichter.", + "name": "Pike Push-up / Hold", + "url": "https://www.hingehealth.com/resources/articles/pike-push-up/" + }, + { + "desc": "Arme seitlich bis Schulterhöhe heben, Ellbogen leicht gebeugt, kontrolliert senken. Seitlicher Deltamuskel.", + "name": "Seitheben (KB/Rucksack)", + "url": "" + } + ] + }, + { + "color": "ACC2", + "group": "ZIEHEN (Rücken / Bizeps – KB oder Rucksack + Boden)", + "items": [ + { + "desc": "Hüfte beugen, Rücken GERADE, abgestützt. Ellbogen an den Rippen, Zug zur Hüfte – Lat.", + "name": "Einarm-Rudern ENG", + "url": "https://www.puregym.com/exercises/back/rows/dumbbell-bent-over-row/" + }, + { + "desc": "Gleiche Position, aber Ellbogen ~45° geöffnet, Zug zum Brustkorb – oberer Rücken / hinterer Delta.", + "name": "Einarm-Rudern BREIT", + "url": "https://www.puregym.com/exercises/back/rows/dumbbell-bent-over-row/" + }, + { + "desc": "Last unten greifen, Oberarme fix am Körper, kontrolliert zu den Schultern curlen.", + "name": "Bizeps-Curls (KB/Rucksack)", + "url": "" + }, + { + "desc": "Bäuchlings Arme+Beine heben (Superman). Snow Angels = Arme am Boden auf/ab. Rücken ohne Stange.", + "name": "Superman / Reverse Snow Angels / Y-T-W", + "url": "https://www.calixpert.com/exercises/hollow-body-to-superman-hold" + } + ] + }, + { + "color": "ACC2", + "group": "BEINE", + "items": [ + { + "desc": "Hüfte zurück, Oberschenkel parallel, Fersen am Boden. Goblet = Glocke/Rucksack vor der Brust umarmen.", + "name": "Kniebeuge / Goblet Squat (KB/Rucksack)", + "url": "" + }, + { + "desc": "Großer Schritt (rückwärts = knieschonend), hinteres Knie Richtung Boden. Anfangs ggf. festhalten.", + "name": "Ausfallschritte", + "url": "" + }, + { + "desc": "Knie leicht gebeugt, Hüfte nach hinten, Rücken gerade, Last entlang der Beine senken bis Dehnung hinten (Hamstrings).", + "name": "RDL / Hip Hinge (KB/Rucksack)", + "url": "" + }, + { + "desc": "Bridge: Hüfte hochdrücken (1-beinig steigern). Waden: Zehenspitzen hoch/runter. Wall Sit: Rücken an die Wand, halten.", + "name": "Glute Bridge / Wadenheben / Wall Sit", + "url": "" + } + ] + }, + { + "color": "KBCOL", + "group": "KETTLEBELL (Power / Griff / Ganzkörper)", + "items": [ + { + "desc": "Wichtigste KB-Übung. Hüfthinge, Glocke aus der Hüfte explosiv auf Brusthöhe schnappen (kein Armheben), oben Po+Bauch fest. Dead-Stop zum Lernen.", + "name": "Swing (zweiarmig, Hardstyle)", + "url": "https://www.onepeloton.com/blog/kettlebell-swing/" + }, + { + "desc": "Glocke an den Hörnern vor der Brust umarmen, tief und aufrecht hocken. Beste Einsteiger-KB-Kniebeuge.", + "name": "Goblet Squat (KB)", + "url": "https://www.onepeloton.com/blog/goblet-squat/" + }, + { + "desc": "Glocke zwischen den Füßen, Hüfte hinge, Rücken gerade, vom Boden heben und Hüfte durchstrecken. Stabile Swing-Vorbereitung.", + "name": "KB-Deadlift (beidbeinig)", + "url": "https://www.onepeloton.com/blog/kettlebell-deadlift/" + }, + { + "desc": "Rücklings am Boden, Glocke vom Boden weg nach oben drücken. Schulterschonend, gut für Anfänger.", + "name": "KB Floor Press", + "url": "https://www.masterclass.com/articles/kettlebell-floor-press-guide" + }, + { + "desc": "Glocke (oder Rucksack) vor der Brust, langsam eng um den Kopf kreisen. Schulter-Mobility + Core.", + "name": "Halo", + "url": "https://www.tomsguide.com/how-to/kettlebell-halo-exercise-how-to-do-it-and-the-benefits-for-your-core-and-shoulder-muscles" + }, + { + "desc": "Glocke am Griff, Boden nach oben, Unterarm senkrecht, still halten. Schwierigkeit kommt aus der Balance – fordert Griff + Handgelenk auch mit leichter Glocke.", + "name": "Bottoms-up KB-Hold / Carry", + "url": "https://www.onepeloton.com/blog/farmers-carry/" + }, + { + "desc": "Glocke kreist um den Körper (Around-the-World); danach diagonal nach oben zur gegenüberliegenden Schulter/Brust führen, die andere Hand fängt ab (wie ein Uppercut von unten). Rotatorischer Core + Koordination. Erst nur das Umkreisen lernen, Uppercut später.", + "name": "Around-the-World + Uppercut", + "url": "" + } + ] + }, + { + "color": "ACC2", + "group": "CORE", + "items": [ + { + "desc": "Unterarmstütz, Körper gerade. Anfangs auf den Knien. Side Plank seitlich auf einem Unterarm.", + "name": "Plank / Side Plank", + "url": "" + }, + { + "desc": "Auf dem Rücken, unterer Rücken am Boden, Arme+Beine leicht angehoben. Anfänger: angewinkelt (Tuck).", + "name": "Hollow Hold", + "url": "https://www.calixpert.com/exercises/hollow-body-to-superman-hold" + }, + { + "desc": "Auf dem Rücken, Arme+Knie 90°, gegengleich Arm+Bein langsam ausstrecken, unterer Rücken bleibt am Boden.", + "name": "Dead Bug", + "url": "https://dieringe.com/exercises/dead-bug" + }, + { + "desc": "Vierfüßler, gegengleich Arm+Bein ausstrecken, Rumpf stabil, kurz halten.", + "name": "Bird Dog", + "url": "https://www.hingehealth.com/resources/articles/bird-dog/" + }, + { + "desc": "Climbers: Knie zügig zur Brust. Bicycle: Ellbogen zum Gegenknie. Twist: sitzend drehen (KB). Beinheben: Knie gebeugt = leichter.", + "name": "Mountain Climbers / Bicycle / Russian Twist / Beinheben", + "url": "" + } + ] + }, + { + "color": "ACC2", + "group": "WARM-UP & MOBILITY", + "items": [ + { + "desc": "Aus dem Stand vorbeugen, Hände nach vorne in den Stütz wandern, dann zurück.", + "name": "Inchworm", + "url": "https://www.onepeloton.com/blog/inchworm-exercise" + }, + { + "desc": "Klassische dynamische Aufwärm-Bewegungen – locker, zum Warmwerden.", + "name": "Jacks / High Knees / Kreisen / Cat-Cow", + "url": "" + } + ] + } + ], + "meta": { + "days": 6, + "doc_title": "Calisthenics 8-Wochen Komplettplan (mit Kettlebell)", + "footer_left": "Calisthenics 8-Wochen Komplettplan · mit Kettlebell", + "output_name": "Calisthenics-8-Wochen-Komplett.pdf", + "subtitle": "Blutiger Anfänger · 1,90 m · 95 kg · Rucksack + Couch + Kettlebell · tägliche ~30-Min Tabata-Session", + "title": "Calisthenics-Basis – 8-Wochen Komplettplan", + "weeks": 8 + }, + "phases": { + "items": [ + { + "name": "Phase 1 · Fundament", + "params": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "weeks": 1, + "id": "phase-phase-1-fundament-9d3418c792" + }, + { + "name": "Phase 2 · Fundament", + "params": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "weeks": 1, + "id": "phase-phase-2-fundament-f815c541ec" + }, + { + "name": "Phase 3 · Aufbau", + "params": "Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.", + "weeks": 1, + "id": "phase-phase-3-aufbau-fa21a3469e" + }, + { + "name": "Phase 4 · Aufbau", + "params": "Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.", + "weeks": 1, + "id": "phase-phase-4-aufbau-98dc807ce2" + }, + { + "name": "Phase 5 · Steigerung", + "params": "Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.", + "weeks": 1, + "id": "phase-phase-5-steigerung-f023eda6a2" + }, + { + "name": "Phase 6 · Steigerung", + "params": "Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.", + "weeks": 1, + "id": "phase-phase-6-steigerung-3dc7eb5c32" + }, + { + "name": "Phase 7 · Festigen", + "params": "Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.", + "weeks": 1, + "id": "phase-phase-7-festigen-d8284c58fc" + }, + { + "name": "Phase 8 · Festigen", + "params": "Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.", + "weeks": 1, + "id": "phase-phase-8-festigen-bfaebd57b4" + } + ], + "param_light": "Bewusst locker – aktive Erholung. Saubere, leichte Wiederholungen, keine Belastung bis zum Limit." + }, + "prepost": { + "1": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Türrahmen (Brust/Schulter, je 30–40s) · Trizeps (je 30s) · Seitheben/Lat-Seitdehnung (je 30s) · Quad im Stand (je 30s) · Knieender Hüftbeuger (je 30–40s) · Wade an der Wand (je 30s)", + "warmup": "Handflächen-Wippen · Armkreisen/Wand-Slides · Hüftkreisen + Beinschwünge · Knie-über-Zeh an der Wand" + }, + "2": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Lat-/Seitdehnung (je 30s) · Türrahmen (je 30–40s) · BWS über die Couch (30–40s) · Cobra/Sphinx (Bauch, 30s) · Unterarm (je 20s) · Knieender Hüftbeuger (je 30s)", + "warmup": "Handflächen-Wippen · Handtuch-Dislocates (8–10×) · Cat-Cow · Hüftkreisen + Beinschwünge" + }, + "3": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Hamstring-Dehnung (Couch, je 30–40s) · Quad im Stand (je 30s) · Knieender Hüftbeuger (je 30–40s) · Figure-4/Taube (je 30–40s) · Cobra + liegende Wirbelsäulen-Rotation (je 30s) · Wade an der Wand (je 30s)", + "warmup": "Hüftkreisen + Beinschwünge · Knie-über-Zeh an der Wand · Cat-Cow · Handflächen-Wippen" + }, + "4": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Türrahmen (je 30–40s) · Trizeps (je 30s) · Lat-/Seitdehnung (je 30s) · BWS über die Couch (30–40s) · Unterarm (je 20s)", + "warmup": "Handflächen-Wippen · Handtuch-Dislocates (8–10×) · Armkreisen/Wand-Slides · Cat-Cow" + }, + "5": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Türrahmen (je 30s) · Knieender Hüftbeuger (je 30s) · BWS über die Couch (30–40s) · Figure-4/Taube (je 30s) · Wade an der Wand (je 30s)", + "warmup": "Cat-Cow · Hüftkreisen + Beinschwünge · Armkreisen/Wand-Slides · Knie-über-Zeh an der Wand" + }, + "6": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Hamstring-Dehnung (Couch, je 30–40s) · Knieender Hüftbeuger (je 30–40s) · Figure-4/Taube (je 30–40s) · Unterarm (je 20s) · BWS über die Couch (30–40s) · Lat-/Seitdehnung (je 30s)", + "warmup": "Halo (leicht) · Hüftkreisen + Beinschwünge · Cat-Cow · Handflächen-Wippen" + } + }, + "stages": { + "atw": { + "name": "Around-the-World + Uppercut (KB)", + "steps": [ + { + "name": "nur Around-the-World, leicht", + "id": "step-nur-around-the-world-leicht-97c90b17c5", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "nur Around-the-World, leicht", + "id": "step-nur-around-the-world-leicht-fd2fab13cb", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Around-the-World, mehr Reps", + "id": "step-around-the-world-mehr-reps-7af159bd81", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Around-the-World, mehr Reps", + "id": "step-around-the-world-mehr-reps-43f1762f06", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, leicht", + "id": "step-uppercut-leicht-cb2ce4fe29", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, leicht", + "id": "step-uppercut-leicht-db86827e21", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, flüssig", + "id": "step-uppercut-fluessig-4b3453d5f9", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, flüssig", + "id": "step-uppercut-fluessig-2463ac3c0e", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-around-the-world-uppercut-kb-3cca10157e", + "key": "atw" + }, + "bic": { + "name": "Bicycle Crunch", + "steps": [ + { + "name": "langsam", + "id": "step-langsam-b16ce156f7", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "langsam", + "id": "step-langsam-3bc865247d", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-9b37c0536d", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-6a0ed59bbd", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-1a29fed611", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-60b7d1ac93", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-85e9262405", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-2b4933822c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-bicycle-crunch-026823904f", + "key": "bic" + }, + "bridge": { + "name": "Glute Bridge", + "steps": [ + { + "name": "beidbeinig", + "id": "step-beidbeinig-36eb2634d6", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "beidbeinig", + "id": "step-beidbeinig-8a52d360c3", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "beidbeinig, 2s halten", + "id": "step-beidbeinig-2s-halten-1ad3dfd97b", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "beidbeinig, 2s halten", + "id": "step-beidbeinig-2s-halten-6087f8f6d1", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig", + "id": "step-einbeinig-3a0c0abdfa", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig", + "id": "step-einbeinig-43807e1138", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig, 2s halten", + "id": "step-einbeinig-2s-halten-4e42109f20", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig, 2s halten", + "id": "step-einbeinig-2s-halten-6335420028", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-glute-bridge-830b37f200", + "key": "bridge" + }, + "calf": { + "name": "Wadenheben", + "steps": [ + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-e8311d7913", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-95b36efc85", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam", + "id": "step-langsam-9e41650da8", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam", + "id": "step-langsam-74db877975", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam, mehr Reps", + "id": "step-langsam-mehr-reps-16ced31e33", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam, mehr Reps", + "id": "step-langsam-mehr-reps-bd0e8e15c4", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "1-beinig", + "id": "step-1-beinig-0c2e9de2f7", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "1-beinig", + "id": "step-1-beinig-f823fd2149", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "calf", + "factor": 1.0 + } + ], + "id": "progression-wadenheben-3833cd235f", + "key": "calf" + }, + "carry": { + "name": "Bottoms-up KB-Hold (Griff)", + "steps": [ + { + "name": "im Stand 10–15s", + "id": "step-im-stand-10-15s-e90dd57ff1", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "im Stand 10–15s", + "id": "step-im-stand-10-15s-db8eec9df0", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "15–20s, ruhig", + "id": "step-15-20s-ruhig-321e582ab2", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "15–20s, ruhig", + "id": "step-15-20s-ruhig-7db6eb1a01", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis Schulterhöhe 15s", + "id": "step-bis-schulterhoehe-15s-f1763ea9e5", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis Schulterhöhe 15s", + "id": "step-bis-schulterhoehe-15s-21c8fd4d01", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "länger / ein paar Schritte", + "id": "step-laenger-ein-paar-schritte-fa5838da65", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "länger / ein paar Schritte", + "id": "step-laenger-ein-paar-schritte-ec93f04473", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-bottoms-up-kb-hold-griff-7c46cda772", + "key": "carry" + }, + "db": { + "name": "Dead Bug", + "steps": [ + { + "name": "Arme ODER Beine einzeln", + "id": "step-arme-oder-beine-einzeln-7a6a9d581b", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Arme ODER Beine einzeln", + "id": "step-arme-oder-beine-einzeln-94f8e1785d", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich", + "id": "step-gegengleich-41e02caee7", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich", + "id": "step-gegengleich-31e878a2f9", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich + Pause", + "id": "step-gegengleich-pause-9e84e14ecc", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich + Pause", + "id": "step-gegengleich-pause-bf8ebc0b7e", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Beine weiter gestreckt", + "id": "step-beine-weiter-gestreckt-453d185cf7", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Beine weiter gestreckt", + "id": "step-beine-weiter-gestreckt-1c5f566001", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-dead-bug-e390771cf1", + "key": "db" + }, + "dips": { + "name": "Bank-Dips (Couch)", + "steps": [ + { + "name": "Knie stark gebeugt, klein", + "id": "step-knie-stark-gebeugt-klein-72a7bffc76", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Knie stark gebeugt, klein", + "id": "step-knie-stark-gebeugt-klein-56482dde46", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, voll", + "id": "step-knie-gebeugt-voll-07cc34cd51", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, voll", + "id": "step-knie-gebeugt-voll-80070f8ecf", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße etwas weiter", + "id": "step-fuesse-etwas-weiter-42e0983b7f", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße etwas weiter", + "id": "step-fuesse-etwas-weiter-7b887f1140", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße weiter weg", + "id": "step-fuesse-weiter-weg-60c057769a", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße weiter weg", + "id": "step-fuesse-weiter-weg-2959fb77fb", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-bank-dips-couch-64352512e0", + "key": "dips" + }, + "gobletkb": { + "name": "Goblet Squat (KB/Rucksack)", + "steps": [ + { + "name": "leicht, Tiefe lernen", + "id": "step-leicht-tiefe-lernen-77201f2b6b", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, Tiefe lernen", + "id": "step-leicht-tiefe-lernen-70ea0f2e9b", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-57415830b2", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-abf827246a", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-2d542b9c09", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-eda93ad7ea", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel, 3s runter", + "id": "step-mittel-3s-runter-9dd7fec573", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel, 3s runter", + "id": "step-mittel-3s-runter-3f76b2391b", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-goblet-squat-kb-rucksack-0e2a5969bc", + "key": "gobletkb" + }, + "halo": { + "name": "Halo", + "steps": [ + { + "name": "leicht, langsam", + "id": "step-leicht-langsam-5d15ba8134", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "leicht, langsam", + "id": "step-leicht-langsam-c2a5c59e62", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-81a736180a", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-fe775a4a23", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-c5c0bf8989", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-3898a7cea7", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-c9db464d00", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-e1a2e46fd3", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-halo-8470d68816", + "key": "halo" + }, + "holl": { + "name": "Hollow Hold", + "steps": [ + { + "name": "Tuck (Knie an)", + "id": "step-tuck-knie-an-be15e1c436", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Tuck (Knie an)", + "id": "step-tuck-knie-an-5f62fcb360", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Tuck, länger", + "id": "step-tuck-laenger-b05dd43bd8", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Tuck, länger", + "id": "step-tuck-laenger-c70e4ba401", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-39bccf6e6b", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-9147433141", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + }, + { + "name": "halb/voll", + "id": "step-halb-voll-13f8ebb8c0", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + }, + { + "name": "halb/voll", + "id": "step-halb-voll-cde344cba9", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + } + ], + "id": "progression-hollow-hold-7e44b4d78e", + "key": "holl" + }, + "kbdl": { + "name": "KB-Deadlift (beidbeinig)", + "steps": [ + { + "name": "leicht, Technik", + "id": "step-leicht-technik-54ca22fe3d", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "leicht, Technik", + "id": "step-leicht-technik-fcd2dc3979", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-db29d97bd7", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-fd7e87b98b", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-7ed7284a10", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-e9887f19f9", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "schwerer, kontrolliert", + "id": "step-schwerer-kontrolliert-dd0f4f4538", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "schwerer, kontrolliert", + "id": "step-schwerer-kontrolliert-ab6af6caea", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-kb-deadlift-beidbeinig-0e27bd039a", + "key": "kbdl" + }, + "load": { + "name": "Bizeps-Curls (KB/Rucksack)", + "steps": [ + { + "name": "leicht", + "id": "step-leicht-273b789e0f", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-93f0046a45", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-bd37c72d4b", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-31ffb19393", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-ca55180c8e", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-d15ecf67a9", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-b1f6c26c72", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-b575d29101", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + } + ], + "id": "progression-bizeps-curls-kb-rucksack-b6ad39301a", + "key": "load" + }, + "lr": { + "name": "Beinheben", + "steps": [ + { + "name": "Knie gebeugt", + "id": "step-knie-gebeugt-1855b4de0f", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie gebeugt", + "id": "step-knie-gebeugt-863780bc32", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, langsam", + "id": "step-knie-gebeugt-langsam-425f61fdb6", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, langsam", + "id": "step-knie-gebeugt-langsam-aad675fda3", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-c84cc4988c", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-298f78b046", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "gestreckt", + "id": "step-gestreckt-a4f6fc11b8", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "gestreckt", + "id": "step-gestreckt-d43210faa3", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-beinheben-9e54736d11", + "key": "lr" + }, + "lraise": { + "name": "Seitheben (KB/Rucksack)", + "steps": [ + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-a0504bbbb1", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-a691c813e1", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-82a36d8e86", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-c736c1e695", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-3c1675e579", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-82cacf1f60", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-6b417be46f", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-b3e2812063", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-seitheben-kb-rucksack-98cf022c83", + "key": "lraise" + }, + "ls": { + "name": "Liegestütze", + "steps": [ + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-7495b7e9cc", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.1 + }, + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-231f998085", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.1 + }, + { + "name": "Inkline tiefer / Knie", + "id": "step-inkline-tiefer-knie-631d725178", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.35 + }, + { + "name": "Inkline tiefer / Knie", + "id": "step-inkline-tiefer-knie-2f351c5335", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.35 + }, + { + "name": "Knie / Negativ", + "id": "step-knie-negativ-0cf75923cb", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "Knie / Negativ", + "id": "step-knie-negativ-d225638aef", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "Negativ + erste Volle", + "id": "step-negativ-erste-volle-d68abebd8f", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.85 + }, + { + "name": "Negativ + erste Volle", + "id": "step-negativ-erste-volle-d93d10d94d", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.85 + } + ], + "id": "progression-liegestuetze-8c8a5e07de", + "key": "ls" + }, + "lseng": { + "name": "Inkline (hoch)", + "steps": [ + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-7d971a7291", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-59da475437", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline mittel", + "id": "step-inkline-mittel-821fadec71", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline mittel", + "id": "step-inkline-mittel-199e2aaf70", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline tief", + "id": "step-inkline-tief-6173a85a04", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline tief", + "id": "step-inkline-tief-54d7a4a0ff", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie-Liegestütze", + "id": "step-knie-liegestuetze-7de6b6dcfb", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie-Liegestütze", + "id": "step-knie-liegestuetze-2d4d23db17", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-inkline-hoch-e770797129", + "key": "lseng" + }, + "lunge": { + "name": "Ausfallschritt rückw.", + "steps": [ + { + "name": "Körpergewicht (ggf. festhalten)", + "id": "step-koerpergewicht-ggf-festhalten-3d6301d1bd", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "Körpergewicht (ggf. festhalten)", + "id": "step-koerpergewicht-ggf-festhalten-0d19269674", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-180853b5dd", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-0f7d4a63bd", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mehr Reps", + "id": "step-mehr-reps-d19d6c0eab", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mehr Reps", + "id": "step-mehr-reps-9235507680", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "+ Last leicht", + "id": "step-last-leicht-10d2acd532", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "+ Last leicht", + "id": "step-last-leicht-6ecdd4c919", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-ausfallschritt-rueckw-52ab461616", + "key": "lunge" + }, + "mtn": { + "name": "Mountain Climbers", + "steps": [ + { + "name": "moderat", + "id": "step-moderat-f76617e319", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-a73e3b0549", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-99c93498d6", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-a3b745efcb", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-c304accace", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-23a81c4905", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-25e355b703", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-4d2a2fd39c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-mountain-climbers-97f6359644", + "key": "mtn" + }, + "pike": { + "name": "Pike", + "steps": [ + { + "name": "Pike Hold", + "id": "step-pike-hold-d46aa2e93f", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Pike Hold", + "id": "step-pike-hold-cc334c7fc6", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Hold + kleine Senkung", + "id": "step-hold-kleine-senkung-4f3bfac4b2", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Hold + kleine Senkung", + "id": "step-hold-kleine-senkung-b93d503cb1", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Push-up Hände erhöht", + "id": "step-push-up-haende-erhoeht-7c94ab03f7", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Push-up Hände erhöht", + "id": "step-push-up-haende-erhoeht-5af7a607db", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Push-up Boden, klein", + "id": "step-push-up-boden-klein-0a27dd0a33", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Push-up Boden, klein", + "id": "step-push-up-boden-klein-44741d722a", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-pike-9726e02fef", + "key": "pike" + }, + "plank": { + "name": "Plank", + "steps": [ + { + "name": "auf Knien 15–20 s", + "id": "step-auf-knien-15-20-s-66aa989041", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien 15–20 s", + "id": "step-auf-knien-15-20-s-28c28cffdc", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "voll 20–30 s", + "id": "step-voll-20-30-s-ebbb758203", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll 20–30 s", + "id": "step-voll-20-30-s-465691f146", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll + Schulter-Taps", + "id": "step-voll-schulter-taps-9fbac9d915", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll + Schulter-Taps", + "id": "step-voll-schulter-taps-738cd33982", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-3da43cf026", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-827f28be3a", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-plank-88a9d6bafb", + "key": "plank" + }, + "press": { + "name": "KB Floor Press", + "steps": [ + { + "name": "leicht (Floor Press)", + "id": "step-leicht-floor-press-de744d6e05", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "leicht (Floor Press)", + "id": "step-leicht-floor-press-5987ddc1d2", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Floor Press mehr", + "id": "step-floor-press-mehr-3b454ea446", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Floor Press mehr", + "id": "step-floor-press-mehr-b34710868b", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead leicht", + "id": "step-overhead-leicht-7f9274fd29", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead leicht", + "id": "step-overhead-leicht-3413a54a3a", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead mittel", + "id": "step-overhead-mittel-4b236720a7", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead mittel", + "id": "step-overhead-mittel-ea724f9a42", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-kb-floor-press-0f387c469f", + "key": "press" + }, + "row1": { + "name": "Einarmiges Rudern (KB)", + "steps": [ + { + "name": "leicht", + "id": "step-leicht-db8c75b358", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-d1e4feaafa", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-19ded398d6", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-0ce270eb41", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig leicht", + "id": "step-einarmig-leicht-a6f388b349", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig leicht", + "id": "step-einarmig-leicht-a9539fbadb", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig mittel", + "id": "step-einarmig-mittel-74f0fe3e1e", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig mittel", + "id": "step-einarmig-mittel-3f961280f6", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-einarmiges-rudern-kb-c142975b5b", + "key": "row1" + }, + "row2": { + "name": "Einarm-Rudern BREIT (KB)", + "steps": [ + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-32d83f79d0", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-76c26b9c7f", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-3bbb6ef617", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-1fea9ba00a", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-5f7bc5489c", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-d4c65b81c6", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel, Pause oben", + "id": "step-mittel-pause-oben-d5cdfdfe94", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel, Pause oben", + "id": "step-mittel-pause-oben-8f7a255ad4", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-einarm-rudern-breit-kb-a06708cc5d", + "key": "row2" + }, + "rowe": { + "name": "Einarm-Rudern ENG (KB)", + "steps": [ + { + "name": "leicht", + "id": "step-leicht-7638fa3210", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-d1490519d3", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-4f2775725c", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-35098ff9fa", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-cd74b90448", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-558d6deb5b", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-fec615da4c", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-60a8a2aedd", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-einarm-rudern-eng-kb-b622a3fbfc", + "key": "rowe" + }, + "sp": { + "name": "Side Plank (Wechsel)", + "steps": [ + { + "name": "auf Knien", + "id": "step-auf-knien-3ed08acc27", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien", + "id": "step-auf-knien-000b6c864b", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien, länger", + "id": "step-auf-knien-laenger-d1c3028780", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien, länger", + "id": "step-auf-knien-laenger-227ec9b5f7", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "voll", + "id": "step-voll-6702e0693f", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll", + "id": "step-voll-727fcc161a", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-8c287bb42a", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-ef549f755a", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-side-plank-wechsel-335152ff85", + "key": "sp" + }, + "squat": { + "name": "Körpergewicht", + "steps": [ + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-e4bfd724af", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-cf13a813b5", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht, mehr Reps", + "id": "step-koerpergewicht-mehr-reps-0156c8a851", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht, mehr Reps", + "id": "step-koerpergewicht-mehr-reps-9fccf7b6ed", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet leicht", + "id": "step-goblet-leicht-e52d2635ab", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet leicht", + "id": "step-goblet-leicht-758736b15a", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet mittel", + "id": "step-goblet-mittel-50a2e4ec3e", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet mittel", + "id": "step-goblet-mittel-0c01361ea0", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-koerpergewicht-6b7808a004", + "key": "squat" + }, + "sup": { + "name": "Superman / Y-T-W", + "steps": [ + { + "name": "Superman Hold", + "id": "step-superman-hold-06efa2683a", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Superman Hold", + "id": "step-superman-hold-34af38f543", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Superman Hold länger", + "id": "step-superman-hold-laenger-b383f0e540", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Superman Hold länger", + "id": "step-superman-hold-laenger-6b326b0a11", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Reverse Snow Angels", + "id": "step-reverse-snow-angels-871916bcee", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Reverse Snow Angels", + "id": "step-reverse-snow-angels-78c24f8835", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Y-T-W Raises", + "id": "step-y-t-w-raises-b7c40c3579", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Y-T-W Raises", + "id": "step-y-t-w-raises-a6d76cb15c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-superman-y-t-w-06938b356b", + "key": "sup" + }, + "swing": { + "name": "KB Swing (zweiarmig)", + "steps": [ + { + "name": "Dead-Stop, leicht (Technik)", + "id": "step-dead-stop-leicht-technik-02548f1a05", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Dead-Stop, leicht (Technik)", + "id": "step-dead-stop-leicht-technik-3b9715b104", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Dead-Stop, mehr Reps", + "id": "step-dead-stop-mehr-reps-7be430a756", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Dead-Stop, mehr Reps", + "id": "step-dead-stop-mehr-reps-5c68075818", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, mittel", + "id": "step-durchgehend-mittel-183cc7dfa6", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, mittel", + "id": "step-durchgehend-mittel-7762c8f240", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, schwerer", + "id": "step-durchgehend-schwerer-6da670bed8", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, schwerer", + "id": "step-durchgehend-schwerer-a1609b5bad", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-kb-swing-zweiarmig-b9b86d8de1", + "key": "swing" + }, + "tsquat": { + "name": "3s runter", + "steps": [ + { + "name": "3s runter", + "id": "step-3s-runter-1cafcb49f1", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "3s runter", + "id": "step-3s-runter-c29275cc81", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "3s + Pause unten", + "id": "step-3s-pause-unten-8b4f16c542", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "3s + Pause unten", + "id": "step-3s-pause-unten-d51468b370", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s", + "id": "step-goblet-3s-ebd1293f2a", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s", + "id": "step-goblet-3s-06003d3bdd", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s + Pause", + "id": "step-goblet-3s-pause-a70c738b23", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s + Pause", + "id": "step-goblet-3s-pause-40369412a4", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-3s-runter-b85ef7f990", + "key": "tsquat" + }, + "tw": { + "name": "Russian Twist (KB)", + "steps": [ + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-3bd115d2ec", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-478be9efcd", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-32fc20afc1", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-fbe33d8a04", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB leicht", + "id": "step-kb-leicht-cf37c395cd", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB leicht", + "id": "step-kb-leicht-5cca613a86", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB", + "id": "step-kb-f50c52e3e3", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB", + "id": "step-kb-eb15afb22c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-russian-twist-kb-4ae08fd89a", + "key": "tw" + }, + "wall": { + "name": "Wall Sit", + "steps": [ + { + "name": "15–25 s", + "id": "step-15-25-s-c7aec770d4", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "15–25 s", + "id": "step-15-25-s-dbdcddfcac", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "25–40 s", + "id": "step-25-40-s-7a71cfd7e1", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "25–40 s", + "id": "step-25-40-s-851e536e20", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "40–55 s", + "id": "step-40-55-s-96e1d7939b", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "40–55 s", + "id": "step-40-55-s-ebe34a6bbd", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "55–70 s", + "id": "step-55-70-s-3ddd9ace4a", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "55–70 s", + "id": "step-55-70-s-19c7e1d6d2", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-wall-sit-e5b793fbdf", + "key": "wall" + } + }, + "training_format": { + "mode": "tabata", + "fixed_interval": true, + "work_seconds": 20, + "rest_seconds": 10, + "rounds": 8, + "rounds_scope": "block", + "schema_version": 1 + }, + "schema_version": 3, + "contract_version": 2, + "plan_id": "phase-1", + "exercise_catalog": { + "movement-liegestuetze-57eacf74ee": { + "id": "movement-liegestuetze-57eacf74ee", + "name": "Liegestütze", + "progression_id": "ls", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-inkline-hoch-7495b7e9cc", + "name": "Inkline (hoch)", + "factor": 0.1, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-inkline-hoch-231f998085", + "name": "Inkline (hoch)", + "factor": 0.1, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-inkline-tiefer-knie-631d725178", + "name": "Inkline tiefer / Knie", + "factor": 0.35, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-inkline-tiefer-knie-2f351c5335", + "name": "Inkline tiefer / Knie", + "factor": 0.35, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-negativ-0cf75923cb", + "name": "Knie / Negativ", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-negativ-d225638aef", + "name": "Knie / Negativ", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negativ-erste-volle-d68abebd8f", + "name": "Negativ + erste Volle", + "factor": 0.85, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negativ-erste-volle-d93d10d94d", + "name": "Negativ + erste Volle", + "factor": 0.85, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-bank-dips-couch-ad6c829e1b": { + "id": "movement-bank-dips-couch-ad6c829e1b", + "name": "Bank-Dips (Couch)", + "progression_id": "dips", + "movement_cluster": "vertical_push", + "movement_label": "Dip", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-knie-stark-gebeugt-klein-72a7bffc76", + "name": "Knie stark gebeugt, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-stark-gebeugt-klein-56482dde46", + "name": "Knie stark gebeugt, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-voll-07cc34cd51", + "name": "Knie gebeugt, voll", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-voll-80070f8ecf", + "name": "Knie gebeugt, voll", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-etwas-weiter-42e0983b7f", + "name": "Füße etwas weiter", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-etwas-weiter-7b887f1140", + "name": "Füße etwas weiter", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-weiter-weg-60c057769a", + "name": "Füße weiter weg", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-weiter-weg-2959fb77fb", + "name": "Füße weiter weg", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-pike-17f7716e00": { + "id": "movement-pike-17f7716e00", + "name": "Pike", + "progression_id": "pike", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-pike-hold-d46aa2e93f", + "name": "Pike Hold", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-pike-hold-cc334c7fc6", + "name": "Pike Hold", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-hold-kleine-senkung-4f3bfac4b2", + "name": "Hold + kleine Senkung", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-hold-kleine-senkung-b93d503cb1", + "name": "Hold + kleine Senkung", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-haende-erhoeht-7c94ab03f7", + "name": "Push-up Hände erhöht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-haende-erhoeht-5af7a607db", + "name": "Push-up Hände erhöht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-boden-klein-0a27dd0a33", + "name": "Push-up Boden, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-boden-klein-44741d722a", + "name": "Push-up Boden, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-seitheben-kb-rucksack-91de5dc2f5": { + "id": "movement-seitheben-kb-rucksack-91de5dc2f5", + "name": "Seitheben (KB/Rucksack)", + "progression_id": "lraise", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-technik-a0504bbbb1", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-technik-a691c813e1", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-82a36d8e86", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-c736c1e695", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-3c1675e579", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-82cacf1f60", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-6b417be46f", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-b3e2812063", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-goblet-squat-kb-rucksack-6ff9f90901": { + "id": "movement-goblet-squat-kb-rucksack-6ff9f90901", + "name": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-tiefe-lernen-77201f2b6b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-tiefe-lernen-70ea0f2e9b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-57415830b2", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-abf827246a", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-2d542b9c09", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-eda93ad7ea", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-9dd7fec573", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-3f76b2391b", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-ausfallschritt-rueckw-2a4d06b799": { + "id": "movement-ausfallschritt-rueckw-2a4d06b799", + "name": "Ausfallschritt rückw.", + "progression_id": "lunge", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-koerpergewicht-ggf-festhalten-3d6301d1bd", + "name": "Körpergewicht (ggf. festhalten)", + "factor": 0.55, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-koerpergewicht-ggf-festhalten-0d19269674", + "name": "Körpergewicht (ggf. festhalten)", + "factor": 0.55, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-koerpergewicht-180853b5dd", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-koerpergewicht-0f7d4a63bd", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-reps-d19d6c0eab", + "name": "mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-reps-9235507680", + "name": "mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-last-leicht-10d2acd532", + "name": "+ Last leicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-last-leicht-6ecdd4c919", + "name": "+ Last leicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-glute-bridge-1c0a7bf699": { + "id": "movement-glute-bridge-1c0a7bf699", + "name": "Glute Bridge", + "progression_id": "bridge", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-beidbeinig-36eb2634d6", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-8a52d360c3", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-1ad3dfd97b", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-6087f8f6d1", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-einbeinig-3a0c0abdfa", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-43807e1138", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-4e42109f20", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-6335420028", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + } + ] + }, + "movement-wadenheben-0c7839551b": { + "id": "movement-wadenheben-0c7839551b", + "name": "Wadenheben", + "progression_id": "calf", + "movement_cluster": "calf", + "movement_label": "Waden", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-koerpergewicht-e8311d7913", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-koerpergewicht-95b36efc85", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-9e41650da8", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-74db877975", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-mehr-reps-16ced31e33", + "name": "langsam, mehr Reps", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-mehr-reps-bd0e8e15c4", + "name": "langsam, mehr Reps", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-1-beinig-0c2e9de2f7", + "name": "1-beinig", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-1-beinig-f823fd2149", + "name": "1-beinig", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + } + ] + }, + "movement-einarm-rudern-eng-kb-4907fbb5c7": { + "id": "movement-einarm-rudern-eng-kb-4907fbb5c7", + "name": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-7638fa3210", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-d1490519d3", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-4f2775725c", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-35098ff9fa", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-cd74b90448", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-558d6deb5b", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-fordernd-fec615da4c", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-fordernd-60a8a2aedd", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-bizeps-curls-kb-rucksack-67944f00d5": { + "id": "movement-bizeps-curls-kb-rucksack-67944f00d5", + "name": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "movement_cluster": "elbow_flexion", + "movement_label": "Bizeps", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-273b789e0f", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-93f0046a45", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-bd37c72d4b", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-31ffb19393", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-ca55180c8e", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-d15ecf67a9", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b1f6c26c72", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b575d29101", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-einarm-rudern-breit-kb-0146abcfbb": { + "id": "movement-einarm-rudern-breit-kb-0146abcfbb", + "name": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-technik-32d83f79d0", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-technik-76c26b9c7f", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-3bbb6ef617", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-1fea9ba00a", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-5f7bc5489c", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-d4c65b81c6", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-pause-oben-d5cdfdfe94", + "name": "mittel, Pause oben", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-pause-oben-8f7a255ad4", + "name": "mittel, Pause oben", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-superman-y-t-w-279f03a544": { + "id": "movement-superman-y-t-w-279f03a544", + "name": "Superman / Y-T-W", + "progression_id": "sup", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-superman-hold-06efa2683a", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-34af38f543", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-b383f0e540", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-6b326b0a11", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-871916bcee", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-78c24f8835", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-b7c40c3579", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-a6d76cb15c", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-plank-c8449f2937": { + "id": "movement-plank-c8449f2937", + "name": "Plank", + "progression_id": "plank", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-auf-knien-15-20-s-66aa989041", + "name": "auf Knien 15–20 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-auf-knien-15-20-s-28c28cffdc", + "name": "auf Knien 15–20 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-20-30-s-ebbb758203", + "name": "voll 20–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-20-30-s-465691f146", + "name": "voll 20–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-schulter-taps-9fbac9d915", + "name": "voll + Schulter-Taps", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-schulter-taps-738cd33982", + "name": "voll + Schulter-Taps", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-laenger-3da43cf026", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-laenger-827f28be3a", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-mountain-climbers-79efdb62dd": { + "id": "movement-mountain-climbers-79efdb62dd", + "name": "Mountain Climbers", + "progression_id": "mtn", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-moderat-f76617e319", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-a73e3b0549", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-99c93498d6", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-a3b745efcb", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-c304accace", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-23a81c4905", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-25e355b703", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-4d2a2fd39c", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-dead-bug-21d4b207d5": { + "id": "movement-dead-bug-21d4b207d5", + "name": "Dead Bug", + "progression_id": "db", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-arme-oder-beine-einzeln-7a6a9d581b", + "name": "Arme ODER Beine einzeln", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-arme-oder-beine-einzeln-94f8e1785d", + "name": "Arme ODER Beine einzeln", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-41e02caee7", + "name": "gegengleich", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-31e878a2f9", + "name": "gegengleich", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-pause-9e84e14ecc", + "name": "gegengleich + Pause", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-pause-bf8ebc0b7e", + "name": "gegengleich + Pause", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beine-weiter-gestreckt-453d185cf7", + "name": "Beine weiter gestreckt", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beine-weiter-gestreckt-1c5f566001", + "name": "Beine weiter gestreckt", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-beinheben-faf19a629b": { + "id": "movement-beinheben-faf19a629b", + "name": "Beinheben", + "progression_id": "lr", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-knie-gebeugt-1855b4de0f", + "name": "Knie gebeugt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-863780bc32", + "name": "Knie gebeugt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-langsam-425f61fdb6", + "name": "Knie gebeugt, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-langsam-aad675fda3", + "name": "Knie gebeugt, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-c84cc4988c", + "name": "halb gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-298f78b046", + "name": "halb gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gestreckt-a4f6fc11b8", + "name": "gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gestreckt-d43210faa3", + "name": "gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-rdl-hip-hinge-kb-rucksack-9ada5a67c3": { + "id": "movement-rdl-hip-hinge-kb-rucksack-9ada5a67c3", + "name": "RDL / Hip Hinge (KB/Rucksack)", + "progression_id": "load", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-273b789e0f", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-93f0046a45", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-bd37c72d4b", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-31ffb19393", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-ca55180c8e", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-d15ecf67a9", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b1f6c26c72", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b575d29101", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-glute-bridge-1-beinig-ccc8c9fb53": { + "id": "movement-glute-bridge-1-beinig-ccc8c9fb53", + "name": "Glute Bridge 1-beinig", + "progression_id": "bridge", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-beidbeinig-36eb2634d6", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-8a52d360c3", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-1ad3dfd97b", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-6087f8f6d1", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-einbeinig-3a0c0abdfa", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-43807e1138", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-4e42109f20", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-6335420028", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + } + ] + }, + "movement-wall-sit-6b5f695f6a": { + "id": "movement-wall-sit-6b5f695f6a", + "name": "Wall Sit", + "progression_id": "wall", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-15-25-s-c7aec770d4", + "name": "15–25 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-15-25-s-dbdcddfcac", + "name": "15–25 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-25-40-s-7a71cfd7e1", + "name": "25–40 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-25-40-s-851e536e20", + "name": "25–40 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-40-55-s-96e1d7939b", + "name": "40–55 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-40-55-s-ebe34a6bbd", + "name": "40–55 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-55-70-s-3ddd9ace4a", + "name": "55–70 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-55-70-s-19c7e1d6d2", + "name": "55–70 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-hollow-hold-54c452f77b": { + "id": "movement-hollow-hold-54c452f77b", + "name": "Hollow Hold", + "progression_id": "holl", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-tuck-knie-an-be15e1c436", + "name": "Tuck (Knie an)", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-tuck-knie-an-5f62fcb360", + "name": "Tuck (Knie an)", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-tuck-laenger-b05dd43bd8", + "name": "Tuck, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-tuck-laenger-c70e4ba401", + "name": "Tuck, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-39bccf6e6b", + "name": "halb gestreckt", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-9147433141", + "name": "halb gestreckt", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-voll-13f8ebb8c0", + "name": "halb/voll", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-voll-cde344cba9", + "name": "halb/voll", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-side-plank-wechsel-309b0bc82c": { + "id": "movement-side-plank-wechsel-309b0bc82c", + "name": "Side Plank (Wechsel)", + "progression_id": "sp", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-auf-knien-3ed08acc27", + "name": "auf Knien", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-auf-knien-000b6c864b", + "name": "auf Knien", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-auf-knien-laenger-d1c3028780", + "name": "auf Knien, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-auf-knien-laenger-227ec9b5f7", + "name": "auf Knien, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-6702e0693f", + "name": "voll", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-727fcc161a", + "name": "voll", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-laenger-8c287bb42a", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-laenger-ef549f755a", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-bicycle-crunch-15c87e28c5": { + "id": "movement-bicycle-crunch-15c87e28c5", + "name": "Bicycle Crunch", + "progression_id": "bic", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-langsam-b16ce156f7", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-3bc865247d", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-9b37c0536d", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-6a0ed59bbd", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-1a29fed611", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-60b7d1ac93", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-85e9262405", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-2b4933822c", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-russian-twist-kb-d6e382fb3c": { + "id": "movement-russian-twist-kb-d6e382fb3c", + "name": "Russian Twist (KB)", + "progression_id": "tw", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-ohne-gewicht-3bd115d2ec", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-ohne-gewicht-478be9efcd", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-ohne-gewicht-32fc20afc1", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-ohne-gewicht-fbe33d8a04", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-leicht-cf37c395cd", + "name": "KB leicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-leicht-5cca613a86", + "name": "KB leicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-f50c52e3e3", + "name": "KB", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-eb15afb22c", + "name": "KB", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-kb-floor-press-060586ebd6": { + "id": "movement-kb-floor-press-060586ebd6", + "name": "KB Floor Press", + "progression_id": "press", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-floor-press-de744d6e05", + "name": "leicht (Floor Press)", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-floor-press-5987ddc1d2", + "name": "leicht (Floor Press)", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-floor-press-mehr-3b454ea446", + "name": "Floor Press mehr", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-floor-press-mehr-b34710868b", + "name": "Floor Press mehr", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-leicht-7f9274fd29", + "name": "Overhead leicht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-leicht-3413a54a3a", + "name": "Overhead leicht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-mittel-4b236720a7", + "name": "Overhead mittel", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-mittel-ea724f9a42", + "name": "Overhead mittel", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-y-t-w-superman-1d331ef219": { + "id": "movement-y-t-w-superman-1d331ef219", + "name": "Y-T-W / Superman", + "progression_id": "sup", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-superman-hold-06efa2683a", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-34af38f543", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-b383f0e540", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-6b326b0a11", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-871916bcee", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-78c24f8835", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-b7c40c3579", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-a6d76cb15c", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-kniebeuge-leicht-4ae3fb9fc0": { + "id": "movement-kniebeuge-leicht-4ae3fb9fc0", + "name": "Kniebeuge (leicht)", + "progression_id": "name:kniebeuge-leicht", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-liegestuetze-1-stufe-leichter-6033b9cb92": { + "id": "movement-liegestuetze-1-stufe-leichter-6033b9cb92", + "name": "Liegestütze (1 Stufe leichter)", + "progression_id": "name:liegestuetze-1-stufe-leichter", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-rudern-leicht-566f8de8f2": { + "id": "movement-rudern-leicht-566f8de8f2", + "name": "Rudern (leicht)", + "progression_id": "name:rudern-leicht", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-mountain-climbers-locker-e4a7854ab9": { + "id": "movement-mountain-climbers-locker-e4a7854ab9", + "name": "Mountain Climbers (locker)", + "progression_id": "name:mountain-climbers-locker", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-inchworm-50016ff2a4": { + "id": "movement-inchworm-50016ff2a4", + "name": "Inchworm", + "progression_id": "name:inchworm", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-cat-cow-d8e060c65f": { + "id": "movement-cat-cow-d8e060c65f", + "name": "Cat-Cow", + "progression_id": "name:cat-cow", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-bird-dog-f2c9d49c80": { + "id": "movement-bird-dog-f2c9d49c80", + "name": "Bird Dog", + "progression_id": "name:bird-dog", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-kb-halo-leicht-e3c156d79e": { + "id": "movement-kb-halo-leicht-e3c156d79e", + "name": "KB-Halo (leicht)", + "progression_id": "name:kb-halo-leicht", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [] + }, + "movement-kb-swing-zweiarmig-327d7b5ad6": { + "id": "movement-kb-swing-zweiarmig-327d7b5ad6", + "name": "KB Swing (zweiarmig)", + "progression_id": "swing", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-dead-stop-leicht-technik-02548f1a05", + "name": "Dead-Stop, leicht (Technik)", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-dead-stop-leicht-technik-3b9715b104", + "name": "Dead-Stop, leicht (Technik)", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-dead-stop-mehr-reps-7be430a756", + "name": "Dead-Stop, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-dead-stop-mehr-reps-5c68075818", + "name": "Dead-Stop, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-mittel-183cc7dfa6", + "name": "durchgehend, mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-mittel-7762c8f240", + "name": "durchgehend, mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-schwerer-6da670bed8", + "name": "durchgehend, schwerer", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-schwerer-a1609b5bad", + "name": "durchgehend, schwerer", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-goblet-squat-kb-f065dfd5b6": { + "id": "movement-goblet-squat-kb-f065dfd5b6", + "name": "Goblet Squat (KB)", + "progression_id": "gobletkb", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-tiefe-lernen-77201f2b6b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-tiefe-lernen-70ea0f2e9b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-57415830b2", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-abf827246a", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-2d542b9c09", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-eda93ad7ea", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-9dd7fec573", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-3f76b2391b", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-kb-deadlift-beidbeinig-6f7c8bca76": { + "id": "movement-kb-deadlift-beidbeinig-6f7c8bca76", + "name": "KB-Deadlift (beidbeinig)", + "progression_id": "kbdl", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-technik-54ca22fe3d", + "name": "leicht, Technik", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-technik-fcd2dc3979", + "name": "leicht, Technik", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-db29d97bd7", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-fd7e87b98b", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-7ed7284a10", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-e9887f19f9", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwerer-kontrolliert-dd0f4f4538", + "name": "schwerer, kontrolliert", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwerer-kontrolliert-ab6af6caea", + "name": "schwerer, kontrolliert", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-halo-faca9a5cc9": { + "id": "movement-halo-faca9a5cc9", + "name": "Halo", + "progression_id": "halo", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-langsam-5d15ba8134", + "name": "leicht, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-langsam-c2a5c59e62", + "name": "leicht, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-81a736180a", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-fe775a4a23", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-c5c0bf8989", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-3898a7cea7", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-c9db464d00", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-e1a2e46fd3", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-einarmiges-rudern-kb-0ec1c11b35": { + "id": "movement-einarmiges-rudern-kb-0ec1c11b35", + "name": "Einarmiges Rudern (KB)", + "progression_id": "row1", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-db8c75b358", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-d1e4feaafa", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-19ded398d6", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-0ce270eb41", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-leicht-a6f388b349", + "name": "einarmig leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-leicht-a9539fbadb", + "name": "einarmig leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-mittel-74f0fe3e1e", + "name": "einarmig mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-mittel-3f961280f6", + "name": "einarmig mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-bottoms-up-kb-hold-griff-4a5f657f41": { + "id": "movement-bottoms-up-kb-hold-griff-4a5f657f41", + "name": "Bottoms-up KB-Hold (Griff)", + "progression_id": "carry", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-im-stand-10-15s-e90dd57ff1", + "name": "im Stand 10–15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-im-stand-10-15s-db8eec9df0", + "name": "im Stand 10–15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-15-20s-ruhig-321e582ab2", + "name": "15–20s, ruhig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-15-20s-ruhig-7db6eb1a01", + "name": "15–20s, ruhig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-bis-schulterhoehe-15s-f1763ea9e5", + "name": "bis Schulterhöhe 15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-bis-schulterhoehe-15s-21c8fd4d01", + "name": "bis Schulterhöhe 15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-laenger-ein-paar-schritte-fa5838da65", + "name": "länger / ein paar Schritte", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-laenger-ein-paar-schritte-ec93f04473", + "name": "länger / ein paar Schritte", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-around-the-world-uppercut-kb-7ea726bbde": { + "id": "movement-around-the-world-uppercut-kb-7ea726bbde", + "name": "Around-the-World + Uppercut (KB)", + "progression_id": "atw", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-nur-around-the-world-leicht-97c90b17c5", + "name": "nur Around-the-World, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-nur-around-the-world-leicht-fd2fab13cb", + "name": "nur Around-the-World, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-around-the-world-mehr-reps-7af159bd81", + "name": "Around-the-World, mehr Reps", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-around-the-world-mehr-reps-43f1762f06", + "name": "Around-the-World, mehr Reps", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-leicht-cb2ce4fe29", + "name": "+ Uppercut, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-leicht-db86827e21", + "name": "+ Uppercut, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-fluessig-4b3453d5f9", + "name": "+ Uppercut, flüssig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-fluessig-2463ac3c0e", + "name": "+ Uppercut, flüssig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + } + } + }, + "draft": { + "colors": { + "ACC2": "#0F766E", + "ACCENT": "#C2410C", + "DARK": "#23201C", + "GOALBG": "#FBF1E9", + "GRID": "#CFC9BF", + "INK": "#1A1A1A", + "KBBG": "#F1ECFB", + "KBCOL": "#6D28D9", + "LIGHT": "#F4F1EC", + "LINK": "#1565C0", + "MUTE": "#6B6256", + "WARMBG": "#EAF1F0" + }, + "days": [ + { + "focus": "Drücken + Beine", + "num": 1, + "rotations": [ + { + "exercises": [ + { + "cue": "Brust kontrolliert senken & drücken", + "key": "ls", + "name": "Liegestütze", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-b5c3e6155f", + "exercise_id": "movement-liegestuetze-57eacf74ee", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d1-r0-e0" + }, + { + "cue": "Po vor der Couch senken, hochdrücken", + "key": "dips", + "name": "Bank-Dips (Couch)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "dips", + "id": "exercise-1-ada8cf48d3", + "exercise_id": "movement-bank-dips-couch-ad6c829e1b", + "movement_cluster": "vertical_push", + "movement_label": "Dip", + "legacy_id": "d1-r0-e1" + }, + { + "cue": "umgek. V, Kopf senken (Schultern)", + "key": "pike", + "name": "Pike", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "pike", + "id": "exercise-2-1cb8b9bbd7", + "exercise_id": "movement-pike-17f7716e00", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d1-r0-e2" + }, + { + "cue": "Arme seitlich bis Schulterhöhe, kontrolliert", + "key": "lraise", + "name": "Seitheben (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "lraise", + "id": "exercise-3-2908dafdf9", + "exercise_id": "movement-seitheben-kb-rucksack-91de5dc2f5", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d1-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-7bd8374b98" + }, + { + "exercises": [ + { + "cue": "Last umarmen, tief", + "key": "gobletkb", + "name": "Goblet Squat (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "gobletkb", + "id": "exercise-item-7816b10b95", + "exercise_id": "movement-goblet-squat-kb-rucksack-6ff9f90901", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r1-e0" + }, + { + "cue": "großer Schritt, Knie Richtung Boden", + "key": "lunge", + "name": "Ausfallschritt rückw.", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "lunge", + "id": "exercise-1-232e3c9065", + "exercise_id": "movement-ausfallschritt-rueckw-2a4d06b799", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d1-r1-e1" + }, + { + "cue": "Hüfte hochdrücken, Po fest", + "key": "bridge", + "name": "Glute Bridge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "bridge", + "id": "exercise-2-0a8da65f1b", + "exercise_id": "movement-glute-bridge-1c0a7bf699", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d1-r1-e2" + }, + { + "cue": "auf Zehenspitzen hoch/runter", + "key": "calf", + "name": "Wadenheben", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "calf", + "id": "exercise-3-bfc4e8f9c3", + "exercise_id": "movement-wadenheben-0c7839551b", + "movement_cluster": "calf", + "movement_label": "Waden", + "legacy_id": "d1-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-b9e0a052af" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-druecken-beine-9f04ffe00b" + }, + { + "focus": "Ziehen + Core", + "num": 2, + "rotations": [ + { + "exercises": [ + { + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts", + "key": "rowe", + "name": "Einarm-Rudern ENG (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "rowe", + "id": "exercise-item-8b823b51cb", + "exercise_id": "movement-einarm-rudern-eng-kb-4907fbb5c7", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d2-r0-e0" + }, + { + "cue": "Oberarme fix, zu den Schultern", + "key": "load", + "name": "Bizeps-Curls (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "load", + "id": "exercise-1-1a66ac153c", + "exercise_id": "movement-bizeps-curls-kb-rucksack-67944f00d5", + "movement_cluster": "elbow_flexion", + "movement_label": "Bizeps", + "legacy_id": "d2-r0-e1" + }, + { + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts", + "key": "row2", + "name": "Einarm-Rudern BREIT (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "row2", + "id": "exercise-2-ab58589731", + "exercise_id": "movement-einarm-rudern-breit-kb-0146abcfbb", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d2-r0-e2" + }, + { + "cue": "bäuchlings Arme+Beine heben", + "key": "sup", + "name": "Superman / Y-T-W", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "sup", + "id": "exercise-3-8974cdd4a3", + "exercise_id": "movement-superman-y-t-w-279f03a544", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-024cf0041d" + }, + { + "exercises": [ + { + "cue": "gerade Linie halten", + "key": "plank", + "name": "Plank", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "plank", + "id": "exercise-item-40e37731b0", + "exercise_id": "movement-plank-c8449f2937", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d2-r1-e0" + }, + { + "cue": "Knie zügig zur Brust", + "key": "mtn", + "name": "Mountain Climbers", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "mtn", + "id": "exercise-1-69ed72c9c3", + "exercise_id": "movement-mountain-climbers-79efdb62dd", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e1" + }, + { + "cue": "gegengleich, Rücken am Boden", + "key": "db", + "name": "Dead Bug", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "db", + "id": "exercise-2-0fc9fa4d38", + "exercise_id": "movement-dead-bug-21d4b207d5", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d2-r1-e2" + }, + { + "cue": "Beine heben/senken", + "key": "lr", + "name": "Beinheben", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "lr", + "id": "exercise-3-1373648dc9", + "exercise_id": "movement-beinheben-faf19a629b", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d2-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-f767e75f57" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-ziehen-core-26dd9ab93d" + }, + { + "focus": "Beine + Core", + "num": 3, + "rotations": [ + { + "exercises": [ + { + "cue": "Last umarmen, tief", + "key": "gobletkb", + "name": "Goblet Squat (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "gobletkb", + "id": "exercise-item-0264c1e44a", + "exercise_id": "movement-goblet-squat-kb-rucksack-6ff9f90901", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r0-e0" + }, + { + "cue": "Hüfte zurück, Rücken gerade, Hamstrings", + "key": "load", + "name": "RDL / Hip Hinge (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "load", + "id": "exercise-1-9d276268ca", + "exercise_id": "movement-rdl-hip-hinge-kb-rucksack-9ada5a67c3", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d3-r0-e1" + }, + { + "cue": "ein Bein gestreckt", + "key": "bridge", + "name": "Glute Bridge 1-beinig", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "bridge", + "id": "exercise-2-4b6e8a5bc6", + "exercise_id": "movement-glute-bridge-1-beinig-ccc8c9fb53", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d3-r0-e2" + }, + { + "cue": "Rücken an die Wand, halten", + "key": "wall", + "name": "Wall Sit", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "wall", + "id": "exercise-3-add8004d7e", + "exercise_id": "movement-wall-sit-6b5f695f6a", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d3-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-de121c91a0" + }, + { + "exercises": [ + { + "cue": "unterer Rücken am Boden", + "key": "holl", + "name": "Hollow Hold", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "holl", + "id": "exercise-item-072e8d32be", + "exercise_id": "movement-hollow-hold-54c452f77b", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r1-e0" + }, + { + "cue": "seitlich auf einem Unterarm", + "key": "sp", + "name": "Side Plank (Wechsel)", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "sp", + "id": "exercise-1-cedec1156e", + "exercise_id": "movement-side-plank-wechsel-309b0bc82c", + "movement_cluster": "core", + "movement_label": "Core", + "legacy_id": "d3-r1-e1" + }, + { + "cue": "Ellbogen zum Gegenknie", + "key": "bic", + "name": "Bicycle Crunch", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "bic", + "id": "exercise-2-fe1a6ad3ca", + "exercise_id": "movement-bicycle-crunch-15c87e28c5", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d3-r1-e2" + }, + { + "cue": "sitzend Rumpf drehen", + "key": "tw", + "name": "Russian Twist (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "tw", + "id": "exercise-3-bd5b54584a", + "exercise_id": "movement-russian-twist-kb-d6e382fb3c", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d3-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-017be520c7" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-beine-core-13963d0eea" + }, + { + "focus": "Oberkörper: Drücken + Ziehen", + "num": 4, + "rotations": [ + { + "exercises": [ + { + "cue": "Brust kontrolliert senken & drücken", + "key": "ls", + "name": "Liegestütze", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "ls", + "id": "exercise-item-3cc877a2d9", + "exercise_id": "movement-liegestuetze-57eacf74ee", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d4-r0-e0" + }, + { + "cue": "Po vor der Couch senken", + "key": "dips", + "name": "Bank-Dips (Couch)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "dips", + "id": "exercise-1-57f3db08a5", + "exercise_id": "movement-bank-dips-couch-ad6c829e1b", + "movement_cluster": "vertical_push", + "movement_label": "Dip", + "legacy_id": "d4-r0-e1" + }, + { + "cue": "umgek. V, Kopf senken", + "key": "pike", + "name": "Pike", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "pike", + "id": "exercise-2-f8e46033f7", + "exercise_id": "movement-pike-17f7716e00", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d4-r0-e2" + }, + { + "cue": "Glocke vom Boden weg drücken", + "key": "press", + "name": "KB Floor Press", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "press", + "id": "exercise-3-1bfa657543", + "exercise_id": "movement-kb-floor-press-060586ebd6", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d4-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-761392c073" + }, + { + "exercises": [ + { + "cue": "1 Arm, Ellbogen an den Rippen, Zug zur Hüfte · Lat · DG1 links / DG2 rechts", + "key": "rowe", + "name": "Einarm-Rudern ENG (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "rowe", + "id": "exercise-item-3eecbcd916", + "exercise_id": "movement-einarm-rudern-eng-kb-4907fbb5c7", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d4-r1-e0" + }, + { + "cue": "Oberarme fix, zu den Schultern", + "key": "load", + "name": "Bizeps-Curls (KB/Rucksack)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "load", + "id": "exercise-1-5503931f9a", + "exercise_id": "movement-bizeps-curls-kb-rucksack-67944f00d5", + "movement_cluster": "elbow_flexion", + "movement_label": "Bizeps", + "legacy_id": "d4-r1-e1" + }, + { + "cue": "1 Arm, Ellbogen ~45° auf, Zug zum Brustkorb · oberer Rücken · DG1 links / DG2 rechts", + "key": "row2", + "name": "Einarm-Rudern BREIT (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "row2", + "id": "exercise-2-7d45b03b10", + "exercise_id": "movement-einarm-rudern-breit-kb-0146abcfbb", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d4-r1-e2" + }, + { + "cue": "bäuchlings, Rücken", + "key": "sup", + "name": "Y-T-W / Superman", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "sup", + "id": "exercise-3-ffc825e874", + "exercise_id": "movement-y-t-w-superman-1d331ef219", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d4-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-62363a4e87" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-oberkoerper-druecken-ziehen-15d0e7f677" + }, + { + "focus": "Ganzkörper LEICHT + Mobility", + "light": true, + "num": 5, + "rotations": [ + { + "exercises": [ + { + "cue": "locker, sauber", + "key": "name:kniebeuge-leicht", + "name": "Kniebeuge (leicht)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:kniebeuge-leicht", + "id": "exercise-item-4b0e2395e9", + "exercise_id": "movement-kniebeuge-leicht-4ae3fb9fc0", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d5-r0-e0" + }, + { + "cue": "leichtere Variante", + "key": "name:liegestuetze-1-stufe-leichter", + "name": "Liegestütze (1 Stufe leichter)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:liegestuetze-1-stufe-leichter", + "id": "exercise-1-9303fa85bc", + "exercise_id": "movement-liegestuetze-1-stufe-leichter-6033b9cb92", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "legacy_id": "d5-r0-e1" + }, + { + "cue": "wenig Gewicht", + "key": "name:rudern-leicht", + "name": "Rudern (leicht)", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:rudern-leicht", + "id": "exercise-2-5224ba2082", + "exercise_id": "movement-rudern-leicht-566f8de8f2", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d5-r0-e2" + }, + { + "cue": "ruhiges Tempo", + "key": "name:mountain-climbers-locker", + "name": "Mountain Climbers (locker)", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:mountain-climbers-locker", + "id": "exercise-3-3f1a039ad3", + "exercise_id": "movement-mountain-climbers-locker-e4a7854ab9", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-1750a46419" + }, + { + "exercises": [ + { + "cue": "in den Stütz wandern, zurück", + "key": "name:inchworm", + "name": "Inchworm", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:inchworm", + "id": "exercise-item-03be6e8387", + "exercise_id": "movement-inchworm-50016ff2a4", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r1-e0" + }, + { + "cue": "Rücken ründen/strecken", + "key": "name:cat-cow", + "name": "Cat-Cow", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "name:cat-cow", + "id": "exercise-1-92862f7d6a", + "exercise_id": "movement-cat-cow-d8e060c65f", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r1-e1" + }, + { + "cue": "gegengleich Arm+Bein, kurz halten", + "key": "name:bird-dog", + "name": "Bird Dog", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "progression_id": "name:bird-dog", + "id": "exercise-2-46d9abeab7", + "exercise_id": "movement-bird-dog-f2c9d49c80", + "movement_cluster": "general", + "movement_label": "Allgemein", + "legacy_id": "d5-r1-e2" + }, + { + "cue": "langsam um den Kopf kreisen", + "key": "name:kb-halo-leicht", + "name": "KB-Halo (leicht)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "name:kb-halo-leicht", + "id": "exercise-3-e6cefad215", + "exercise_id": "movement-kb-halo-leicht-e3c156d79e", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d5-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-dde0c1adc1" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-ganzkoerper-leicht-mobility-ff8134fa48" + }, + { + "color": "#6D28D9", + "focus": "KETTLEBELL Ganzkörper", + "note": "Reine Kettlebell-Session. Swing = Hüft-Schnapp, kein Armheben. Glocke so wählen, dass alle Reps sauber sind.", + "note_bg": "#F1ECFB", + "num": 6, + "rotations": [ + { + "exercises": [ + { + "cue": "Hüft-Schnapp, kein Armheben", + "key": "swing", + "name": "KB Swing (zweiarmig)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "swing", + "id": "exercise-item-2fbf3bf70d", + "exercise_id": "movement-kb-swing-zweiarmig-327d7b5ad6", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d6-r0-e0" + }, + { + "cue": "Glocke vor der Brust, tief", + "key": "gobletkb", + "name": "Goblet Squat (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "gobletkb", + "id": "exercise-1-8542eb2e51", + "exercise_id": "movement-goblet-squat-kb-f065dfd5b6", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "legacy_id": "d6-r0-e1" + }, + { + "cue": "Glocke zwischen den Füßen, Hüfte hinge", + "key": "kbdl", + "name": "KB-Deadlift (beidbeinig)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "kbdl", + "id": "exercise-2-f072cf1841", + "exercise_id": "movement-kb-deadlift-beidbeinig-6f7c8bca76", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "legacy_id": "d6-r0-e2" + }, + { + "cue": "eng um den Kopf kreisen", + "key": "halo", + "name": "Halo", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "halo", + "id": "exercise-3-031200e160", + "exercise_id": "movement-halo-faca9a5cc9", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d6-r0-e3" + } + ], + "label": "ROTATION A — 3 Sätze", + "id": "rotation-rotation-a-3-saetze-1165003f5a" + }, + { + "exercises": [ + { + "cue": "Glocke vom Boden weg drücken", + "key": "press", + "name": "KB Floor Press", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "progression_id": "press", + "id": "exercise-item-04a3fceec5", + "exercise_id": "movement-kb-floor-press-060586ebd6", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "legacy_id": "d6-r1-e0" + }, + { + "cue": "Rücken gerade, zum Bauch", + "key": "row1", + "name": "Einarmiges Rudern (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "row1", + "id": "exercise-1-01087258d2", + "exercise_id": "movement-einarmiges-rudern-kb-0ec1c11b35", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "legacy_id": "d6-r1-e1" + }, + { + "cue": "Glocke Boden nach oben, Unterarm senkrecht, still halten", + "key": "carry", + "name": "Bottoms-up KB-Hold (Griff)", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "carry", + "id": "exercise-2-0a835e4b76", + "exercise_id": "movement-bottoms-up-kb-hold-griff-4a5f657f41", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d6-r1-e2" + }, + { + "cue": "Glocke um den Körper, dann diagonal zur Gegen-Schulter, andere Hand fängt", + "key": "atw", + "name": "Around-the-World + Uppercut (KB)", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "progression_id": "atw", + "id": "exercise-3-6386738afd", + "exercise_id": "movement-around-the-world-uppercut-kb-7ea726bbde", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "legacy_id": "d6-r1-e3" + } + ], + "label": "ROTATION B — 3 Sätze", + "id": "rotation-rotation-b-3-saetze-c851db6efb" + } + ], + "badge": { + "text": "", + "color": "#6D28D9" + }, + "id": "day-kettlebell-ganzkoerper-e53128a814" + } + ], + "front": { + "baseline_head": "Standortbestimmung & Tracking", + "baseline_intro": "Mach die Tests vor Tag 1 und in Woche 8 identisch nochmal – dein Vorher/Nachher.", + "baseline_rows": [ + [ + "Test", + "Tag 0", + "Woche 8" + ], + [ + "Liegestütze (max, saubere Reps)", + "", + "" + ], + [ + "Bank-Dips an der Couch (max)", + "", + "" + ], + [ + "Kniebeugen in 60 Sekunden", + "", + "" + ], + [ + "Ausfallschritte (max gesamt)", + "", + "" + ], + [ + "Plank (max Sekunden)", + "", + "" + ], + [ + "Wall Sit (max Sekunden)", + "", + "" + ], + [ + "KB Farmer-Hold (max Sekunden, Griff)", + "", + "" + ], + [ + "Körpergewicht (kg)", + "95", + "" + ] + ], + "flow_rows": [ + [ + "Teil", + "Inhalt", + "Zeit" + ], + [ + "Warm-up", + "Gelenk-Prep (dynamisch)", + "~3 Min" + ], + [ + "Rotation A", + "4 Übungen, 3 Sätze (kurze Pausen)", + "~14 Min" + ], + [ + "Rotation B", + "4 Übungen, 3 Sätze (kurze Pausen)", + "~14 Min" + ], + [ + "Cool-down", + "Ausklingen + Dehnen", + "~6 Min" + ], + [ + "", + "Gesamt", + "~35 Min" + ] + ], + "goals_box": "Du startest bei 2–3 Liegestützen und 0 Dips. Realistisch und ehrlich nach 8 Wochen sauberer Arbeit:
•  Liegestütze: von 2–3 auf erste saubere volle Reps (Rest in Knie-/Negativ-Form).
•  Bank-Dips: saubere Reps mit Füßen etwas weiter weg.   Pike Push-up: am Boden mit kleinem ROM.
•  Plank ~45–60 s, Wall Sit ~60 s, viele saubere Goblet-Kniebeugen, sauberer Swing + spürbar bessere Griffkraft.
•  Noch NICHT dran: Diamond Push-ups, L-Sit, Handstand, schwerer Turkish Get-Up, Snatch – Stoff für Woche 12+.", + "goals_head": "Realistische Ziele nach 8 Wochen", + "goals_note": "Wichtig: Die Wochen-Stufen sind ein Rahmen, kein Muss. Bleib auf einer Stufe, solange sie fordert. Aufstiegs-Regel: erst hoch (oder schwerere Glocke), wenn du eine Übung 2 Sessions in Folge in allen Sätzen sauber über die vollen 20s schaffst. KB = Kettlebell bevorzugt; Rucksack bleibt als Alternative.", + "kb_box": "Du trainierst für Calisthenics, nicht für Maximalkraft. Genau da liegen die belegten Stärken der Kettlebell – ohne dass dich ihr dokumentierter Schwachpunkt (Maximalkraft/Hypertrophie wegen fixer Gewichtssprünge) trifft:
•  Griffkraft (Studie BELL: +6–7 kg) – dein größter Mehrwert auf dem Weg zu Klimmzug & Dip.
•  Power / ballistische Hüftstreckung (Swing) – explosive Basis für spätere Skills.
•  Kondition & Körperfett – wirksam belegt; du startest mit wenig Ausdauer.
•  Ganzkörper, ein Gerät, zeiteffizient – passt ideal ins 30-Min-Tabata-Format; niedriges Einstiegs-Verletzungsrisiko bei sauberer Technik.
Ehrlich: Reines KB ersetzt kein Maximalkraft-/Muskelaufbau-Programm. Für dein Ziel (Basis-Fundament) liegt das ohnehin unter der relevanten Schwelle.", + "kb_head": "Warum Kettlebell – und warum es zu deinem Ziel passt", + "lib_head": "Übungs-Bibliothek (mit Anleitungen)", + "lib_intro": "Für Übungen, die du noch nicht kennst, ist ein Link zu einer bebilderten Anleitung dabei. Lila Gruppe = Kettlebell-Übungen.", + "reminder_head": "Kurz-Reminder", + "reminders": [ + "Form vor Reps. Wähle die Stufe / Glocke, bei der du die 20s kontrolliert durcharbeitest – mit 1–2 Reps Reserve.", + "Swing-Technik first. Der Swing ist ein Hüft-Schnapp, KEIN Kniebeuge und KEIN Armheben. Erst Dead-Stop-Swings, bis die Hüftstreckung sitzt.", + "Einseitige Übungen (Tag 2): Durchgang 1 = links, Durchgang 2 = rechts – so ist jede Seite automatisch ausbalanciert.", + "Täglich ist viel. Tag 5 leicht, Tag 7 frei. Bei starkem Muskelkater einen Extra-Ruhetag – gehört zum Aufbau.", + "Schlaf & Eiweiß. 7–9 h Schlaf, ~1,6 g Eiweiß pro kg. Stechender Schmerz = stoppen." + ], + "session_how_body": "Tabata-Block = 4 Min: 10 Sek einzählen, dann 8× (20 Sek Arbeit / 10 Sek Pause). Pro Block rotieren 4 Übungen, je 2 Durchgänge = 8 Intervalle = 1 Block = 1 Satz. Eine Rotation läuft 3 Sätze (in Woche 1 ruhig mit 2 starten). Warm-up steht über, Cool-down unter den Übungen.", + "session_how_head": "So funktioniert eine Session", + "sheets_note": "Ab der nächsten Seite: 1 Druckblatt pro Session (Woche 1–8). Tag 6 = reine Kettlebell-Session. Warm-up steht über, Cool-down + Dehnen unter den Übungen. Bei Vorerkrankungen/Verletzungen vorher ärztlich abklären.", + "timer_note": "Timer: „Tabata / Interval Timer“ auf 10s Vorlauf, 8 Runden, 20/10." + }, + "library": [ + { + "color": "ACC2", + "group": "DRÜCKEN (Brust / Schultern / Trizeps)", + "items": [ + { + "desc": "Stütz, Körper gerade, Brust kontrolliert senken & drücken. Stufen: Inkline (Couch), Knie, Negativ, Voll.", + "name": "Liegestütze", + "url": "https://blog.calimove.com/2023/01/25/push-up-variations-beginner-expert/" + }, + { + "desc": "Rücken zur Couch, Hände auf der Kante, Po davor absenken (Ellbogen nach hinten), hochdrücken. Knie beugen = leichter.", + "name": "Bank-Dips (Couch)", + "url": "https://www.healthline.com/health/fitness-exercise/bench-dips" + }, + { + "desc": "Umgekehrtes V (Hintern hoch), Kopf Richtung Boden senken, hochdrücken. Schulter-Fokus. Hände erhöht = leichter.", + "name": "Pike Push-up / Hold", + "url": "https://www.hingehealth.com/resources/articles/pike-push-up/" + }, + { + "desc": "Arme seitlich bis Schulterhöhe heben, Ellbogen leicht gebeugt, kontrolliert senken. Seitlicher Deltamuskel.", + "name": "Seitheben (KB/Rucksack)", + "url": "" + } + ] + }, + { + "color": "ACC2", + "group": "ZIEHEN (Rücken / Bizeps – KB oder Rucksack + Boden)", + "items": [ + { + "desc": "Hüfte beugen, Rücken GERADE, abgestützt. Ellbogen an den Rippen, Zug zur Hüfte – Lat.", + "name": "Einarm-Rudern ENG", + "url": "https://www.puregym.com/exercises/back/rows/dumbbell-bent-over-row/" + }, + { + "desc": "Gleiche Position, aber Ellbogen ~45° geöffnet, Zug zum Brustkorb – oberer Rücken / hinterer Delta.", + "name": "Einarm-Rudern BREIT", + "url": "https://www.puregym.com/exercises/back/rows/dumbbell-bent-over-row/" + }, + { + "desc": "Last unten greifen, Oberarme fix am Körper, kontrolliert zu den Schultern curlen.", + "name": "Bizeps-Curls (KB/Rucksack)", + "url": "" + }, + { + "desc": "Bäuchlings Arme+Beine heben (Superman). Snow Angels = Arme am Boden auf/ab. Rücken ohne Stange.", + "name": "Superman / Reverse Snow Angels / Y-T-W", + "url": "https://www.calixpert.com/exercises/hollow-body-to-superman-hold" + } + ] + }, + { + "color": "ACC2", + "group": "BEINE", + "items": [ + { + "desc": "Hüfte zurück, Oberschenkel parallel, Fersen am Boden. Goblet = Glocke/Rucksack vor der Brust umarmen.", + "name": "Kniebeuge / Goblet Squat (KB/Rucksack)", + "url": "" + }, + { + "desc": "Großer Schritt (rückwärts = knieschonend), hinteres Knie Richtung Boden. Anfangs ggf. festhalten.", + "name": "Ausfallschritte", + "url": "" + }, + { + "desc": "Knie leicht gebeugt, Hüfte nach hinten, Rücken gerade, Last entlang der Beine senken bis Dehnung hinten (Hamstrings).", + "name": "RDL / Hip Hinge (KB/Rucksack)", + "url": "" + }, + { + "desc": "Bridge: Hüfte hochdrücken (1-beinig steigern). Waden: Zehenspitzen hoch/runter. Wall Sit: Rücken an die Wand, halten.", + "name": "Glute Bridge / Wadenheben / Wall Sit", + "url": "" + } + ] + }, + { + "color": "KBCOL", + "group": "KETTLEBELL (Power / Griff / Ganzkörper)", + "items": [ + { + "desc": "Wichtigste KB-Übung. Hüfthinge, Glocke aus der Hüfte explosiv auf Brusthöhe schnappen (kein Armheben), oben Po+Bauch fest. Dead-Stop zum Lernen.", + "name": "Swing (zweiarmig, Hardstyle)", + "url": "https://www.onepeloton.com/blog/kettlebell-swing/" + }, + { + "desc": "Glocke an den Hörnern vor der Brust umarmen, tief und aufrecht hocken. Beste Einsteiger-KB-Kniebeuge.", + "name": "Goblet Squat (KB)", + "url": "https://www.onepeloton.com/blog/goblet-squat/" + }, + { + "desc": "Glocke zwischen den Füßen, Hüfte hinge, Rücken gerade, vom Boden heben und Hüfte durchstrecken. Stabile Swing-Vorbereitung.", + "name": "KB-Deadlift (beidbeinig)", + "url": "https://www.onepeloton.com/blog/kettlebell-deadlift/" + }, + { + "desc": "Rücklings am Boden, Glocke vom Boden weg nach oben drücken. Schulterschonend, gut für Anfänger.", + "name": "KB Floor Press", + "url": "https://www.masterclass.com/articles/kettlebell-floor-press-guide" + }, + { + "desc": "Glocke (oder Rucksack) vor der Brust, langsam eng um den Kopf kreisen. Schulter-Mobility + Core.", + "name": "Halo", + "url": "https://www.tomsguide.com/how-to/kettlebell-halo-exercise-how-to-do-it-and-the-benefits-for-your-core-and-shoulder-muscles" + }, + { + "desc": "Glocke am Griff, Boden nach oben, Unterarm senkrecht, still halten. Schwierigkeit kommt aus der Balance – fordert Griff + Handgelenk auch mit leichter Glocke.", + "name": "Bottoms-up KB-Hold / Carry", + "url": "https://www.onepeloton.com/blog/farmers-carry/" + }, + { + "desc": "Glocke kreist um den Körper (Around-the-World); danach diagonal nach oben zur gegenüberliegenden Schulter/Brust führen, die andere Hand fängt ab (wie ein Uppercut von unten). Rotatorischer Core + Koordination. Erst nur das Umkreisen lernen, Uppercut später.", + "name": "Around-the-World + Uppercut", + "url": "" + } + ] + }, + { + "color": "ACC2", + "group": "CORE", + "items": [ + { + "desc": "Unterarmstütz, Körper gerade. Anfangs auf den Knien. Side Plank seitlich auf einem Unterarm.", + "name": "Plank / Side Plank", + "url": "" + }, + { + "desc": "Auf dem Rücken, unterer Rücken am Boden, Arme+Beine leicht angehoben. Anfänger: angewinkelt (Tuck).", + "name": "Hollow Hold", + "url": "https://www.calixpert.com/exercises/hollow-body-to-superman-hold" + }, + { + "desc": "Auf dem Rücken, Arme+Knie 90°, gegengleich Arm+Bein langsam ausstrecken, unterer Rücken bleibt am Boden.", + "name": "Dead Bug", + "url": "https://dieringe.com/exercises/dead-bug" + }, + { + "desc": "Vierfüßler, gegengleich Arm+Bein ausstrecken, Rumpf stabil, kurz halten.", + "name": "Bird Dog", + "url": "https://www.hingehealth.com/resources/articles/bird-dog/" + }, + { + "desc": "Climbers: Knie zügig zur Brust. Bicycle: Ellbogen zum Gegenknie. Twist: sitzend drehen (KB). Beinheben: Knie gebeugt = leichter.", + "name": "Mountain Climbers / Bicycle / Russian Twist / Beinheben", + "url": "" + } + ] + }, + { + "color": "ACC2", + "group": "WARM-UP & MOBILITY", + "items": [ + { + "desc": "Aus dem Stand vorbeugen, Hände nach vorne in den Stütz wandern, dann zurück.", + "name": "Inchworm", + "url": "https://www.onepeloton.com/blog/inchworm-exercise" + }, + { + "desc": "Klassische dynamische Aufwärm-Bewegungen – locker, zum Warmwerden.", + "name": "Jacks / High Knees / Kreisen / Cat-Cow", + "url": "" + } + ] + } + ], + "meta": { + "days": 6, + "doc_title": "Calisthenics 8-Wochen Komplettplan (mit Kettlebell)", + "footer_left": "Calisthenics 8-Wochen Komplettplan · mit Kettlebell", + "output_name": "Calisthenics-8-Wochen-Komplett.pdf", + "subtitle": "Blutiger Anfänger · 1,90 m · 95 kg · Rucksack + Couch + Kettlebell · tägliche ~30-Min Tabata-Session", + "title": "Calisthenics-Basis – 8-Wochen Komplettplan", + "weeks": 8 + }, + "phases": { + "items": [ + { + "name": "Phase 1 · Fundament", + "params": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "weeks": 1, + "id": "phase-phase-1-fundament-9d3418c792" + }, + { + "name": "Phase 2 · Fundament", + "params": "Fokus: Technik (v.a. Swing als Dead-Stop). 2–3 Reps Reserve. Pausen bis ~60s. Leichte Glocke. Woche 1 mit 2 Sätzen pro Rotation.", + "weeks": 1, + "id": "phase-phase-2-fundament-f815c541ec" + }, + { + "name": "Phase 3 · Aufbau", + "params": "Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.", + "weeks": 1, + "id": "phase-phase-3-aufbau-fa21a3469e" + }, + { + "name": "Phase 4 · Aufbau", + "params": "Sauber bleiben, letzter Satz fordernd. Pausen ~45–60s. Etwas schwerere Glocke / mehr Reps. 2–3 Sätze.", + "weeks": 1, + "id": "phase-phase-4-aufbau-98dc807ce2" + }, + { + "name": "Phase 5 · Steigerung", + "params": "Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.", + "weeks": 1, + "id": "phase-phase-5-steigerung-f023eda6a2" + }, + { + "name": "Phase 6 · Steigerung", + "params": "Fordernd, aber 1 Rep Reserve. Pausen ~45s. Schwerere Glocke / einarmig wo angegeben. 3 Sätze.", + "weeks": 1, + "id": "phase-phase-6-steigerung-3dc7eb5c32" + }, + { + "name": "Phase 7 · Festigen", + "params": "Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.", + "weeks": 1, + "id": "phase-phase-7-festigen-d8284c58fc" + }, + { + "name": "Phase 8 · Festigen", + "params": "Fordernd & sauber (NICHT bis zum Muskelversagen). Pausen ~45s. Ende Woche 8 leichter (Deload). 3 Sätze.", + "weeks": 1, + "id": "phase-phase-8-festigen-bfaebd57b4" + } + ], + "param_light": "Bewusst locker – aktive Erholung. Saubere, leichte Wiederholungen, keine Belastung bis zum Limit." + }, + "prepost": { + "1": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Türrahmen (Brust/Schulter, je 30–40s) · Trizeps (je 30s) · Seitheben/Lat-Seitdehnung (je 30s) · Quad im Stand (je 30s) · Knieender Hüftbeuger (je 30–40s) · Wade an der Wand (je 30s)", + "warmup": "Handflächen-Wippen · Armkreisen/Wand-Slides · Hüftkreisen + Beinschwünge · Knie-über-Zeh an der Wand" + }, + "2": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Lat-/Seitdehnung (je 30s) · Türrahmen (je 30–40s) · BWS über die Couch (30–40s) · Cobra/Sphinx (Bauch, 30s) · Unterarm (je 20s) · Knieender Hüftbeuger (je 30s)", + "warmup": "Handflächen-Wippen · Handtuch-Dislocates (8–10×) · Cat-Cow · Hüftkreisen + Beinschwünge" + }, + "3": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Hamstring-Dehnung (Couch, je 30–40s) · Quad im Stand (je 30s) · Knieender Hüftbeuger (je 30–40s) · Figure-4/Taube (je 30–40s) · Cobra + liegende Wirbelsäulen-Rotation (je 30s) · Wade an der Wand (je 30s)", + "warmup": "Hüftkreisen + Beinschwünge · Knie-über-Zeh an der Wand · Cat-Cow · Handflächen-Wippen" + }, + "4": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Türrahmen (je 30–40s) · Trizeps (je 30s) · Lat-/Seitdehnung (je 30s) · BWS über die Couch (30–40s) · Unterarm (je 20s)", + "warmup": "Handflächen-Wippen · Handtuch-Dislocates (8–10×) · Armkreisen/Wand-Slides · Cat-Cow" + }, + "5": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Türrahmen (je 30s) · Knieender Hüftbeuger (je 30s) · BWS über die Couch (30–40s) · Figure-4/Taube (je 30s) · Wade an der Wand (je 30s)", + "warmup": "Cat-Cow · Hüftkreisen + Beinschwünge · Armkreisen/Wand-Slides · Knie-über-Zeh an der Wand" + }, + "6": { + "cooldown": "Ausschütteln + Gehen (60–90s) · Box-Atmung (4–5 Runden)", + "stretch": "Hamstring-Dehnung (Couch, je 30–40s) · Knieender Hüftbeuger (je 30–40s) · Figure-4/Taube (je 30–40s) · Unterarm (je 20s) · BWS über die Couch (30–40s) · Lat-/Seitdehnung (je 30s)", + "warmup": "Halo (leicht) · Hüftkreisen + Beinschwünge · Cat-Cow · Handflächen-Wippen" + } + }, + "stages": { + "atw": { + "name": "Around-the-World + Uppercut (KB)", + "steps": [ + { + "name": "nur Around-the-World, leicht", + "id": "step-nur-around-the-world-leicht-97c90b17c5", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "nur Around-the-World, leicht", + "id": "step-nur-around-the-world-leicht-fd2fab13cb", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Around-the-World, mehr Reps", + "id": "step-around-the-world-mehr-reps-7af159bd81", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "Around-the-World, mehr Reps", + "id": "step-around-the-world-mehr-reps-43f1762f06", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, leicht", + "id": "step-uppercut-leicht-cb2ce4fe29", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, leicht", + "id": "step-uppercut-leicht-db86827e21", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, flüssig", + "id": "step-uppercut-fluessig-4b3453d5f9", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "+ Uppercut, flüssig", + "id": "step-uppercut-fluessig-2463ac3c0e", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-around-the-world-uppercut-kb-3cca10157e", + "key": "atw" + }, + "bic": { + "name": "Bicycle Crunch", + "steps": [ + { + "name": "langsam", + "id": "step-langsam-b16ce156f7", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "langsam", + "id": "step-langsam-3bc865247d", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-9b37c0536d", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-6a0ed59bbd", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-1a29fed611", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-60b7d1ac93", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-85e9262405", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-2b4933822c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-bicycle-crunch-026823904f", + "key": "bic" + }, + "bridge": { + "name": "Glute Bridge", + "steps": [ + { + "name": "beidbeinig", + "id": "step-beidbeinig-36eb2634d6", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "beidbeinig", + "id": "step-beidbeinig-8a52d360c3", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "beidbeinig, 2s halten", + "id": "step-beidbeinig-2s-halten-1ad3dfd97b", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "beidbeinig, 2s halten", + "id": "step-beidbeinig-2s-halten-6087f8f6d1", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig", + "id": "step-einbeinig-3a0c0abdfa", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig", + "id": "step-einbeinig-43807e1138", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig, 2s halten", + "id": "step-einbeinig-2s-halten-4e42109f20", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "einbeinig, 2s halten", + "id": "step-einbeinig-2s-halten-6335420028", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-glute-bridge-830b37f200", + "key": "bridge" + }, + "calf": { + "name": "Wadenheben", + "steps": [ + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-e8311d7913", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-95b36efc85", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam", + "id": "step-langsam-9e41650da8", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam", + "id": "step-langsam-74db877975", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam, mehr Reps", + "id": "step-langsam-mehr-reps-16ced31e33", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "langsam, mehr Reps", + "id": "step-langsam-mehr-reps-bd0e8e15c4", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "1-beinig", + "id": "step-1-beinig-0c2e9de2f7", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "calf", + "factor": 1.0 + }, + { + "name": "1-beinig", + "id": "step-1-beinig-f823fd2149", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + }, + "movement_cluster": "calf", + "factor": 1.0 + } + ], + "id": "progression-wadenheben-3833cd235f", + "key": "calf" + }, + "carry": { + "name": "Bottoms-up KB-Hold (Griff)", + "steps": [ + { + "name": "im Stand 10–15s", + "id": "step-im-stand-10-15s-e90dd57ff1", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "im Stand 10–15s", + "id": "step-im-stand-10-15s-db8eec9df0", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "15–20s, ruhig", + "id": "step-15-20s-ruhig-321e582ab2", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "15–20s, ruhig", + "id": "step-15-20s-ruhig-7db6eb1a01", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis Schulterhöhe 15s", + "id": "step-bis-schulterhoehe-15s-f1763ea9e5", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "bis Schulterhöhe 15s", + "id": "step-bis-schulterhoehe-15s-21c8fd4d01", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "länger / ein paar Schritte", + "id": "step-laenger-ein-paar-schritte-fa5838da65", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "länger / ein paar Schritte", + "id": "step-laenger-ein-paar-schritte-ec93f04473", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-bottoms-up-kb-hold-griff-7c46cda772", + "key": "carry" + }, + "db": { + "name": "Dead Bug", + "steps": [ + { + "name": "Arme ODER Beine einzeln", + "id": "step-arme-oder-beine-einzeln-7a6a9d581b", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Arme ODER Beine einzeln", + "id": "step-arme-oder-beine-einzeln-94f8e1785d", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich", + "id": "step-gegengleich-41e02caee7", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich", + "id": "step-gegengleich-31e878a2f9", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich + Pause", + "id": "step-gegengleich-pause-9e84e14ecc", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "gegengleich + Pause", + "id": "step-gegengleich-pause-bf8ebc0b7e", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Beine weiter gestreckt", + "id": "step-beine-weiter-gestreckt-453d185cf7", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "Beine weiter gestreckt", + "id": "step-beine-weiter-gestreckt-1c5f566001", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-dead-bug-e390771cf1", + "key": "db" + }, + "dips": { + "name": "Bank-Dips (Couch)", + "steps": [ + { + "name": "Knie stark gebeugt, klein", + "id": "step-knie-stark-gebeugt-klein-72a7bffc76", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Knie stark gebeugt, klein", + "id": "step-knie-stark-gebeugt-klein-56482dde46", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, voll", + "id": "step-knie-gebeugt-voll-07cc34cd51", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, voll", + "id": "step-knie-gebeugt-voll-80070f8ecf", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße etwas weiter", + "id": "step-fuesse-etwas-weiter-42e0983b7f", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße etwas weiter", + "id": "step-fuesse-etwas-weiter-7b887f1140", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße weiter weg", + "id": "step-fuesse-weiter-weg-60c057769a", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Füße weiter weg", + "id": "step-fuesse-weiter-weg-2959fb77fb", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-bank-dips-couch-64352512e0", + "key": "dips" + }, + "gobletkb": { + "name": "Goblet Squat (KB/Rucksack)", + "steps": [ + { + "name": "leicht, Tiefe lernen", + "id": "step-leicht-tiefe-lernen-77201f2b6b", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, Tiefe lernen", + "id": "step-leicht-tiefe-lernen-70ea0f2e9b", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-57415830b2", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-abf827246a", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-2d542b9c09", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-eda93ad7ea", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel, 3s runter", + "id": "step-mittel-3s-runter-9dd7fec573", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mittel, 3s runter", + "id": "step-mittel-3s-runter-3f76b2391b", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-goblet-squat-kb-rucksack-0e2a5969bc", + "key": "gobletkb" + }, + "halo": { + "name": "Halo", + "steps": [ + { + "name": "leicht, langsam", + "id": "step-leicht-langsam-5d15ba8134", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "leicht, langsam", + "id": "step-leicht-langsam-c2a5c59e62", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-81a736180a", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-fe775a4a23", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-c5c0bf8989", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-3898a7cea7", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-c9db464d00", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-e1a2e46fd3", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "loaded_mobility", + "factor": 1.0 + } + ], + "id": "progression-halo-8470d68816", + "key": "halo" + }, + "holl": { + "name": "Hollow Hold", + "steps": [ + { + "name": "Tuck (Knie an)", + "id": "step-tuck-knie-an-be15e1c436", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Tuck (Knie an)", + "id": "step-tuck-knie-an-5f62fcb360", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Tuck, länger", + "id": "step-tuck-laenger-b05dd43bd8", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "Tuck, länger", + "id": "step-tuck-laenger-c70e4ba401", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-39bccf6e6b", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-9147433141", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + }, + { + "name": "halb/voll", + "id": "step-halb-voll-13f8ebb8c0", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + }, + { + "name": "halb/voll", + "id": "step-halb-voll-cde344cba9", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.82 + } + ], + "id": "progression-hollow-hold-7e44b4d78e", + "key": "holl" + }, + "kbdl": { + "name": "KB-Deadlift (beidbeinig)", + "steps": [ + { + "name": "leicht, Technik", + "id": "step-leicht-technik-54ca22fe3d", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "leicht, Technik", + "id": "step-leicht-technik-fcd2dc3979", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-db29d97bd7", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-fd7e87b98b", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-7ed7284a10", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-e9887f19f9", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "schwerer, kontrolliert", + "id": "step-schwerer-kontrolliert-dd0f4f4538", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "schwerer, kontrolliert", + "id": "step-schwerer-kontrolliert-ab6af6caea", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-kb-deadlift-beidbeinig-0e27bd039a", + "key": "kbdl" + }, + "load": { + "name": "Bizeps-Curls (KB/Rucksack)", + "steps": [ + { + "name": "leicht", + "id": "step-leicht-273b789e0f", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-93f0046a45", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-bd37c72d4b", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-31ffb19393", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-ca55180c8e", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-d15ecf67a9", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-b1f6c26c72", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-b575d29101", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "elbow_flexion", + "factor": 1.0 + } + ], + "id": "progression-bizeps-curls-kb-rucksack-b6ad39301a", + "key": "load" + }, + "lr": { + "name": "Beinheben", + "steps": [ + { + "name": "Knie gebeugt", + "id": "step-knie-gebeugt-1855b4de0f", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie gebeugt", + "id": "step-knie-gebeugt-863780bc32", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, langsam", + "id": "step-knie-gebeugt-langsam-425f61fdb6", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie gebeugt, langsam", + "id": "step-knie-gebeugt-langsam-aad675fda3", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-c84cc4988c", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "halb gestreckt", + "id": "step-halb-gestreckt-298f78b046", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "gestreckt", + "id": "step-gestreckt-a4f6fc11b8", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "gestreckt", + "id": "step-gestreckt-d43210faa3", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-beinheben-9e54736d11", + "key": "lr" + }, + "lraise": { + "name": "Seitheben (KB/Rucksack)", + "steps": [ + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-a0504bbbb1", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-a691c813e1", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-82a36d8e86", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-c736c1e695", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-3c1675e579", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-82cacf1f60", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-6b417be46f", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "mittel, langsam", + "id": "step-mittel-langsam-b3e2812063", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-seitheben-kb-rucksack-98cf022c83", + "key": "lraise" + }, + "ls": { + "name": "Liegestütze", + "steps": [ + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-7495b7e9cc", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.1 + }, + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-231f998085", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.1 + }, + { + "name": "Inkline tiefer / Knie", + "id": "step-inkline-tiefer-knie-631d725178", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.35 + }, + { + "name": "Inkline tiefer / Knie", + "id": "step-inkline-tiefer-knie-2f351c5335", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.35 + }, + { + "name": "Knie / Negativ", + "id": "step-knie-negativ-0cf75923cb", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "Knie / Negativ", + "id": "step-knie-negativ-d225638aef", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.7 + }, + { + "name": "Negativ + erste Volle", + "id": "step-negativ-erste-volle-d68abebd8f", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.85 + }, + { + "name": "Negativ + erste Volle", + "id": "step-negativ-erste-volle-d93d10d94d", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "horizontal_push", + "factor": 0.85 + } + ], + "id": "progression-liegestuetze-8c8a5e07de", + "key": "ls" + }, + "lseng": { + "name": "Inkline (hoch)", + "steps": [ + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-7d971a7291", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline (hoch)", + "id": "step-inkline-hoch-59da475437", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline mittel", + "id": "step-inkline-mittel-821fadec71", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline mittel", + "id": "step-inkline-mittel-199e2aaf70", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline tief", + "id": "step-inkline-tief-6173a85a04", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Inkline tief", + "id": "step-inkline-tief-54d7a4a0ff", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie-Liegestütze", + "id": "step-knie-liegestuetze-7de6b6dcfb", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Knie-Liegestütze", + "id": "step-knie-liegestuetze-2d4d23db17", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-inkline-hoch-e770797129", + "key": "lseng" + }, + "lunge": { + "name": "Ausfallschritt rückw.", + "steps": [ + { + "name": "Körpergewicht (ggf. festhalten)", + "id": "step-koerpergewicht-ggf-festhalten-3d6301d1bd", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "Körpergewicht (ggf. festhalten)", + "id": "step-koerpergewicht-ggf-festhalten-0d19269674", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 0.55 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-180853b5dd", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-0f7d4a63bd", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mehr Reps", + "id": "step-mehr-reps-d19d6c0eab", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "mehr Reps", + "id": "step-mehr-reps-9235507680", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "+ Last leicht", + "id": "step-last-leicht-10d2acd532", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "+ Last leicht", + "id": "step-last-leicht-6ecdd4c919", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-ausfallschritt-rueckw-52ab461616", + "key": "lunge" + }, + "mtn": { + "name": "Mountain Climbers", + "steps": [ + { + "name": "moderat", + "id": "step-moderat-f76617e319", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-a73e3b0549", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-99c93498d6", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "moderat", + "id": "step-moderat-a3b745efcb", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-c304accace", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-23a81c4905", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-25e355b703", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "zügig", + "id": "step-zuegig-4d2a2fd39c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-mountain-climbers-97f6359644", + "key": "mtn" + }, + "pike": { + "name": "Pike", + "steps": [ + { + "name": "Pike Hold", + "id": "step-pike-hold-d46aa2e93f", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Pike Hold", + "id": "step-pike-hold-cc334c7fc6", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Hold + kleine Senkung", + "id": "step-hold-kleine-senkung-4f3bfac4b2", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Hold + kleine Senkung", + "id": "step-hold-kleine-senkung-b93d503cb1", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 0.3 + }, + { + "name": "Push-up Hände erhöht", + "id": "step-push-up-haende-erhoeht-7c94ab03f7", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Push-up Hände erhöht", + "id": "step-push-up-haende-erhoeht-5af7a607db", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Push-up Boden, klein", + "id": "step-push-up-boden-klein-0a27dd0a33", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Push-up Boden, klein", + "id": "step-push-up-boden-klein-44741d722a", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-pike-9726e02fef", + "key": "pike" + }, + "plank": { + "name": "Plank", + "steps": [ + { + "name": "auf Knien 15–20 s", + "id": "step-auf-knien-15-20-s-66aa989041", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien 15–20 s", + "id": "step-auf-knien-15-20-s-28c28cffdc", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "voll 20–30 s", + "id": "step-voll-20-30-s-ebbb758203", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll 20–30 s", + "id": "step-voll-20-30-s-465691f146", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll + Schulter-Taps", + "id": "step-voll-schulter-taps-9fbac9d915", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll + Schulter-Taps", + "id": "step-voll-schulter-taps-738cd33982", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-3da43cf026", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-827f28be3a", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-plank-88a9d6bafb", + "key": "plank" + }, + "press": { + "name": "KB Floor Press", + "steps": [ + { + "name": "leicht (Floor Press)", + "id": "step-leicht-floor-press-de744d6e05", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "leicht (Floor Press)", + "id": "step-leicht-floor-press-5987ddc1d2", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Floor Press mehr", + "id": "step-floor-press-mehr-3b454ea446", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Floor Press mehr", + "id": "step-floor-press-mehr-b34710868b", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead leicht", + "id": "step-overhead-leicht-7f9274fd29", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead leicht", + "id": "step-overhead-leicht-3413a54a3a", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead mittel", + "id": "step-overhead-mittel-4b236720a7", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + }, + { + "name": "Overhead mittel", + "id": "step-overhead-mittel-ea724f9a42", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "vertical_push", + "factor": 1.0 + } + ], + "id": "progression-kb-floor-press-0f387c469f", + "key": "press" + }, + "row1": { + "name": "Einarmiges Rudern (KB)", + "steps": [ + { + "name": "leicht", + "id": "step-leicht-db8c75b358", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-d1e4feaafa", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-19ded398d6", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-0ce270eb41", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig leicht", + "id": "step-einarmig-leicht-a6f388b349", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig leicht", + "id": "step-einarmig-leicht-a9539fbadb", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig mittel", + "id": "step-einarmig-mittel-74f0fe3e1e", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "einarmig mittel", + "id": "step-einarmig-mittel-3f961280f6", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-einarmiges-rudern-kb-c142975b5b", + "key": "row1" + }, + "row2": { + "name": "Einarm-Rudern BREIT (KB)", + "steps": [ + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-32d83f79d0", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht (Technik)", + "id": "step-leicht-technik-76c26b9c7f", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-3bbb6ef617", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht, mehr Reps", + "id": "step-leicht-mehr-reps-1fea9ba00a", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-5f7bc5489c", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel", + "id": "step-mittel-d4c65b81c6", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel, Pause oben", + "id": "step-mittel-pause-oben-d5cdfdfe94", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mittel, Pause oben", + "id": "step-mittel-pause-oben-8f7a255ad4", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-einarm-rudern-breit-kb-a06708cc5d", + "key": "row2" + }, + "rowe": { + "name": "Einarm-Rudern ENG (KB)", + "steps": [ + { + "name": "leicht", + "id": "step-leicht-7638fa3210", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "leicht", + "id": "step-leicht-d1490519d3", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-4f2775725c", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "etwas mehr", + "id": "step-etwas-mehr-35098ff9fa", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-cd74b90448", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "mehr", + "id": "step-mehr-558d6deb5b", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-fec615da4c", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + }, + { + "name": "fordernd", + "id": "step-fordernd-60a8a2aedd", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "horizontal_pull", + "factor": 1.0 + } + ], + "id": "progression-einarm-rudern-eng-kb-b622a3fbfc", + "key": "rowe" + }, + "sp": { + "name": "Side Plank (Wechsel)", + "steps": [ + { + "name": "auf Knien", + "id": "step-auf-knien-3ed08acc27", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien", + "id": "step-auf-knien-000b6c864b", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien, länger", + "id": "step-auf-knien-laenger-d1c3028780", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "auf Knien, länger", + "id": "step-auf-knien-laenger-227ec9b5f7", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 0.65 + }, + { + "name": "voll", + "id": "step-voll-6702e0693f", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll", + "id": "step-voll-727fcc161a", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-8c287bb42a", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + }, + { + "name": "voll, länger", + "id": "step-voll-laenger-ef549f755a", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "movement_cluster": "core", + "factor": 1.0 + } + ], + "id": "progression-side-plank-wechsel-335152ff85", + "key": "sp" + }, + "squat": { + "name": "Körpergewicht", + "steps": [ + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-e4bfd724af", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht", + "id": "step-koerpergewicht-cf13a813b5", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht, mehr Reps", + "id": "step-koerpergewicht-mehr-reps-0156c8a851", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Körpergewicht, mehr Reps", + "id": "step-koerpergewicht-mehr-reps-9fccf7b6ed", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet leicht", + "id": "step-goblet-leicht-e52d2635ab", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet leicht", + "id": "step-goblet-leicht-758736b15a", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet mittel", + "id": "step-goblet-mittel-50a2e4ec3e", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet mittel", + "id": "step-goblet-mittel-0c01361ea0", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-koerpergewicht-6b7808a004", + "key": "squat" + }, + "sup": { + "name": "Superman / Y-T-W", + "steps": [ + { + "name": "Superman Hold", + "id": "step-superman-hold-06efa2683a", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Superman Hold", + "id": "step-superman-hold-34af38f543", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Superman Hold länger", + "id": "step-superman-hold-laenger-b383f0e540", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Superman Hold länger", + "id": "step-superman-hold-laenger-6b326b0a11", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Reverse Snow Angels", + "id": "step-reverse-snow-angels-871916bcee", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Reverse Snow Angels", + "id": "step-reverse-snow-angels-78c24f8835", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Y-T-W Raises", + "id": "step-y-t-w-raises-b7c40c3579", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "Y-T-W Raises", + "id": "step-y-t-w-raises-a6d76cb15c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-superman-y-t-w-06938b356b", + "key": "sup" + }, + "swing": { + "name": "KB Swing (zweiarmig)", + "steps": [ + { + "name": "Dead-Stop, leicht (Technik)", + "id": "step-dead-stop-leicht-technik-02548f1a05", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Dead-Stop, leicht (Technik)", + "id": "step-dead-stop-leicht-technik-3b9715b104", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Dead-Stop, mehr Reps", + "id": "step-dead-stop-mehr-reps-7be430a756", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "Dead-Stop, mehr Reps", + "id": "step-dead-stop-mehr-reps-5c68075818", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, mittel", + "id": "step-durchgehend-mittel-183cc7dfa6", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, mittel", + "id": "step-durchgehend-mittel-7762c8f240", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, schwerer", + "id": "step-durchgehend-schwerer-6da670bed8", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + }, + { + "name": "durchgehend, schwerer", + "id": "step-durchgehend-schwerer-a1609b5bad", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "hip_hinge", + "factor": 1.0 + } + ], + "id": "progression-kb-swing-zweiarmig-b9b86d8de1", + "key": "swing" + }, + "tsquat": { + "name": "3s runter", + "steps": [ + { + "name": "3s runter", + "id": "step-3s-runter-1cafcb49f1", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "3s runter", + "id": "step-3s-runter-c29275cc81", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "3s + Pause unten", + "id": "step-3s-pause-unten-8b4f16c542", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "3s + Pause unten", + "id": "step-3s-pause-unten-d51468b370", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s", + "id": "step-goblet-3s-ebd1293f2a", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s", + "id": "step-goblet-3s-06003d3bdd", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s + Pause", + "id": "step-goblet-3s-pause-a70c738b23", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "Goblet 3s + Pause", + "id": "step-goblet-3s-pause-40369412a4", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-3s-runter-b85ef7f990", + "key": "tsquat" + }, + "tw": { + "name": "Russian Twist (KB)", + "steps": [ + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-3bd115d2ec", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-478be9efcd", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-32fc20afc1", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "ohne Gewicht", + "id": "step-ohne-gewicht-fbe33d8a04", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB leicht", + "id": "step-kb-leicht-cf37c395cd", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB leicht", + "id": "step-kb-leicht-5cca613a86", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB", + "id": "step-kb-f50c52e3e3", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + }, + { + "name": "KB", + "id": "step-kb-eb15afb22c", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + }, + "movement_cluster": "general", + "factor": 1.0 + } + ], + "id": "progression-russian-twist-kb-4ae08fd89a", + "key": "tw" + }, + "wall": { + "name": "Wall Sit", + "steps": [ + { + "name": "15–25 s", + "id": "step-15-25-s-c7aec770d4", + "phase_id": "phase-phase-1-fundament-9d3418c792", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "15–25 s", + "id": "step-15-25-s-dbdcddfcac", + "phase_id": "phase-phase-2-fundament-f815c541ec", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "25–40 s", + "id": "step-25-40-s-7a71cfd7e1", + "phase_id": "phase-phase-3-aufbau-fa21a3469e", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "25–40 s", + "id": "step-25-40-s-851e536e20", + "phase_id": "phase-phase-4-aufbau-98dc807ce2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "40–55 s", + "id": "step-40-55-s-96e1d7939b", + "phase_id": "phase-phase-5-steigerung-f023eda6a2", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "40–55 s", + "id": "step-40-55-s-ebe34a6bbd", + "phase_id": "phase-phase-6-steigerung-3dc7eb5c32", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "55–70 s", + "id": "step-55-70-s-3ddd9ace4a", + "phase_id": "phase-phase-7-festigen-d8284c58fc", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + }, + { + "name": "55–70 s", + "id": "step-55-70-s-19c7e1d6d2", + "phase_id": "phase-phase-8-festigen-bfaebd57b4", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "movement_cluster": "knee_dominant", + "factor": 1.0 + } + ], + "id": "progression-wall-sit-e5b793fbdf", + "key": "wall" + } + }, + "training_format": { + "mode": "tabata", + "fixed_interval": true, + "work_seconds": 20, + "rest_seconds": 10, + "rounds": 8, + "rounds_scope": "block", + "schema_version": 1 + }, + "schema_version": 3, + "contract_version": 2, + "plan_id": "phase-1", + "exercise_catalog": { + "movement-liegestuetze-57eacf74ee": { + "id": "movement-liegestuetze-57eacf74ee", + "name": "Liegestütze", + "progression_id": "ls", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-inkline-hoch-7495b7e9cc", + "name": "Inkline (hoch)", + "factor": 0.1, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-inkline-hoch-231f998085", + "name": "Inkline (hoch)", + "factor": 0.1, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-inkline-tiefer-knie-631d725178", + "name": "Inkline tiefer / Knie", + "factor": 0.35, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-inkline-tiefer-knie-2f351c5335", + "name": "Inkline tiefer / Knie", + "factor": 0.35, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-negativ-0cf75923cb", + "name": "Knie / Negativ", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-negativ-d225638aef", + "name": "Knie / Negativ", + "factor": 0.7, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negativ-erste-volle-d68abebd8f", + "name": "Negativ + erste Volle", + "factor": 0.85, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-negativ-erste-volle-d93d10d94d", + "name": "Negativ + erste Volle", + "factor": 0.85, + "movement_cluster": "horizontal_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-bank-dips-couch-ad6c829e1b": { + "id": "movement-bank-dips-couch-ad6c829e1b", + "name": "Bank-Dips (Couch)", + "progression_id": "dips", + "movement_cluster": "vertical_push", + "movement_label": "Dip", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-knie-stark-gebeugt-klein-72a7bffc76", + "name": "Knie stark gebeugt, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-stark-gebeugt-klein-56482dde46", + "name": "Knie stark gebeugt, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-voll-07cc34cd51", + "name": "Knie gebeugt, voll", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-voll-80070f8ecf", + "name": "Knie gebeugt, voll", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-etwas-weiter-42e0983b7f", + "name": "Füße etwas weiter", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-etwas-weiter-7b887f1140", + "name": "Füße etwas weiter", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-weiter-weg-60c057769a", + "name": "Füße weiter weg", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fuesse-weiter-weg-2959fb77fb", + "name": "Füße weiter weg", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-pike-17f7716e00": { + "id": "movement-pike-17f7716e00", + "name": "Pike", + "progression_id": "pike", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-pike-hold-d46aa2e93f", + "name": "Pike Hold", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-pike-hold-cc334c7fc6", + "name": "Pike Hold", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-hold-kleine-senkung-4f3bfac4b2", + "name": "Hold + kleine Senkung", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-hold-kleine-senkung-b93d503cb1", + "name": "Hold + kleine Senkung", + "factor": 0.3, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-haende-erhoeht-7c94ab03f7", + "name": "Push-up Hände erhöht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-haende-erhoeht-5af7a607db", + "name": "Push-up Hände erhöht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-boden-klein-0a27dd0a33", + "name": "Push-up Boden, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-push-up-boden-klein-44741d722a", + "name": "Push-up Boden, klein", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-seitheben-kb-rucksack-91de5dc2f5": { + "id": "movement-seitheben-kb-rucksack-91de5dc2f5", + "name": "Seitheben (KB/Rucksack)", + "progression_id": "lraise", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-technik-a0504bbbb1", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-technik-a691c813e1", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-82a36d8e86", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-c736c1e695", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-3c1675e579", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-82cacf1f60", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-6b417be46f", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-b3e2812063", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-goblet-squat-kb-rucksack-6ff9f90901": { + "id": "movement-goblet-squat-kb-rucksack-6ff9f90901", + "name": "Goblet Squat (KB/Rucksack)", + "progression_id": "gobletkb", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-tiefe-lernen-77201f2b6b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-tiefe-lernen-70ea0f2e9b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-57415830b2", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-abf827246a", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-2d542b9c09", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-eda93ad7ea", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-9dd7fec573", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-3f76b2391b", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-ausfallschritt-rueckw-2a4d06b799": { + "id": "movement-ausfallschritt-rueckw-2a4d06b799", + "name": "Ausfallschritt rückw.", + "progression_id": "lunge", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-koerpergewicht-ggf-festhalten-3d6301d1bd", + "name": "Körpergewicht (ggf. festhalten)", + "factor": 0.55, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-koerpergewicht-ggf-festhalten-0d19269674", + "name": "Körpergewicht (ggf. festhalten)", + "factor": 0.55, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-koerpergewicht-180853b5dd", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-koerpergewicht-0f7d4a63bd", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-reps-d19d6c0eab", + "name": "mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-reps-9235507680", + "name": "mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-last-leicht-10d2acd532", + "name": "+ Last leicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-last-leicht-6ecdd4c919", + "name": "+ Last leicht", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-glute-bridge-1c0a7bf699": { + "id": "movement-glute-bridge-1c0a7bf699", + "name": "Glute Bridge", + "progression_id": "bridge", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-beidbeinig-36eb2634d6", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-8a52d360c3", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-1ad3dfd97b", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-6087f8f6d1", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-einbeinig-3a0c0abdfa", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-43807e1138", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-4e42109f20", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-6335420028", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + } + ] + }, + "movement-wadenheben-0c7839551b": { + "id": "movement-wadenheben-0c7839551b", + "name": "Wadenheben", + "progression_id": "calf", + "movement_cluster": "calf", + "movement_label": "Waden", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-koerpergewicht-e8311d7913", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-koerpergewicht-95b36efc85", + "name": "Körpergewicht", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-9e41650da8", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-74db877975", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-mehr-reps-16ced31e33", + "name": "langsam, mehr Reps", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-mehr-reps-bd0e8e15c4", + "name": "langsam, mehr Reps", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-1-beinig-0c2e9de2f7", + "name": "1-beinig", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-1-beinig-f823fd2149", + "name": "1-beinig", + "factor": 1.0, + "movement_cluster": "calf", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + } + ] + }, + "movement-einarm-rudern-eng-kb-4907fbb5c7": { + "id": "movement-einarm-rudern-eng-kb-4907fbb5c7", + "name": "Einarm-Rudern ENG (KB)", + "progression_id": "rowe", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-7638fa3210", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-d1490519d3", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-4f2775725c", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-35098ff9fa", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-cd74b90448", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mehr-558d6deb5b", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-fordernd-fec615da4c", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-fordernd-60a8a2aedd", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-bizeps-curls-kb-rucksack-67944f00d5": { + "id": "movement-bizeps-curls-kb-rucksack-67944f00d5", + "name": "Bizeps-Curls (KB/Rucksack)", + "progression_id": "load", + "movement_cluster": "elbow_flexion", + "movement_label": "Bizeps", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-273b789e0f", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-93f0046a45", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-bd37c72d4b", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-31ffb19393", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-ca55180c8e", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-d15ecf67a9", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b1f6c26c72", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b575d29101", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-einarm-rudern-breit-kb-0146abcfbb": { + "id": "movement-einarm-rudern-breit-kb-0146abcfbb", + "name": "Einarm-Rudern BREIT (KB)", + "progression_id": "row2", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-technik-32d83f79d0", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-technik-76c26b9c7f", + "name": "leicht (Technik)", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-3bbb6ef617", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-mehr-reps-1fea9ba00a", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-5f7bc5489c", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-d4c65b81c6", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-pause-oben-d5cdfdfe94", + "name": "mittel, Pause oben", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-pause-oben-8f7a255ad4", + "name": "mittel, Pause oben", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-superman-y-t-w-279f03a544": { + "id": "movement-superman-y-t-w-279f03a544", + "name": "Superman / Y-T-W", + "progression_id": "sup", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-superman-hold-06efa2683a", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-34af38f543", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-b383f0e540", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-6b326b0a11", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-871916bcee", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-78c24f8835", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-b7c40c3579", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-a6d76cb15c", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-plank-c8449f2937": { + "id": "movement-plank-c8449f2937", + "name": "Plank", + "progression_id": "plank", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-auf-knien-15-20-s-66aa989041", + "name": "auf Knien 15–20 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-auf-knien-15-20-s-28c28cffdc", + "name": "auf Knien 15–20 s", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-20-30-s-ebbb758203", + "name": "voll 20–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-20-30-s-465691f146", + "name": "voll 20–30 s", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-schulter-taps-9fbac9d915", + "name": "voll + Schulter-Taps", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-schulter-taps-738cd33982", + "name": "voll + Schulter-Taps", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-laenger-3da43cf026", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-voll-laenger-827f28be3a", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-mountain-climbers-79efdb62dd": { + "id": "movement-mountain-climbers-79efdb62dd", + "name": "Mountain Climbers", + "progression_id": "mtn", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-moderat-f76617e319", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-a73e3b0549", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-99c93498d6", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-a3b745efcb", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-c304accace", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-23a81c4905", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-25e355b703", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-4d2a2fd39c", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-dead-bug-21d4b207d5": { + "id": "movement-dead-bug-21d4b207d5", + "name": "Dead Bug", + "progression_id": "db", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-arme-oder-beine-einzeln-7a6a9d581b", + "name": "Arme ODER Beine einzeln", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-arme-oder-beine-einzeln-94f8e1785d", + "name": "Arme ODER Beine einzeln", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-41e02caee7", + "name": "gegengleich", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-31e878a2f9", + "name": "gegengleich", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-pause-9e84e14ecc", + "name": "gegengleich + Pause", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gegengleich-pause-bf8ebc0b7e", + "name": "gegengleich + Pause", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beine-weiter-gestreckt-453d185cf7", + "name": "Beine weiter gestreckt", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beine-weiter-gestreckt-1c5f566001", + "name": "Beine weiter gestreckt", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-beinheben-faf19a629b": { + "id": "movement-beinheben-faf19a629b", + "name": "Beinheben", + "progression_id": "lr", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-knie-gebeugt-1855b4de0f", + "name": "Knie gebeugt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-863780bc32", + "name": "Knie gebeugt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-langsam-425f61fdb6", + "name": "Knie gebeugt, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-knie-gebeugt-langsam-aad675fda3", + "name": "Knie gebeugt, langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-c84cc4988c", + "name": "halb gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-298f78b046", + "name": "halb gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gestreckt-a4f6fc11b8", + "name": "gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-gestreckt-d43210faa3", + "name": "gestreckt", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-rdl-hip-hinge-kb-rucksack-9ada5a67c3": { + "id": "movement-rdl-hip-hinge-kb-rucksack-9ada5a67c3", + "name": "RDL / Hip Hinge (KB/Rucksack)", + "progression_id": "load", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-273b789e0f", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-93f0046a45", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-bd37c72d4b", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-etwas-mehr-31ffb19393", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-ca55180c8e", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mehr-d15ecf67a9", + "name": "mehr", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b1f6c26c72", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-fordernd-b575d29101", + "name": "fordernd", + "factor": 1.0, + "movement_cluster": "elbow_flexion", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-glute-bridge-1-beinig-ccc8c9fb53": { + "id": "movement-glute-bridge-1-beinig-ccc8c9fb53", + "name": "Glute Bridge 1-beinig", + "progression_id": "bridge", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-beidbeinig-36eb2634d6", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-8a52d360c3", + "name": "beidbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-1ad3dfd97b", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-beidbeinig-2s-halten-6087f8f6d1", + "name": "beidbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-einbeinig-3a0c0abdfa", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-43807e1138", + "name": "einbeinig", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-4e42109f20", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + }, + { + "id": "step-einbeinig-2s-halten-6335420028", + "name": "einbeinig, 2s halten", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate", + "sets": 3, + "locked_sets": true + } + } + ] + }, + "movement-wall-sit-6b5f695f6a": { + "id": "movement-wall-sit-6b5f695f6a", + "name": "Wall Sit", + "progression_id": "wall", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-15-25-s-c7aec770d4", + "name": "15–25 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-15-25-s-dbdcddfcac", + "name": "15–25 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-25-40-s-7a71cfd7e1", + "name": "25–40 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-25-40-s-851e536e20", + "name": "25–40 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-40-55-s-96e1d7939b", + "name": "40–55 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-40-55-s-ebe34a6bbd", + "name": "40–55 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-55-70-s-3ddd9ace4a", + "name": "55–70 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-55-70-s-19c7e1d6d2", + "name": "55–70 s", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-hollow-hold-54c452f77b": { + "id": "movement-hollow-hold-54c452f77b", + "name": "Hollow Hold", + "progression_id": "holl", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-tuck-knie-an-be15e1c436", + "name": "Tuck (Knie an)", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-tuck-knie-an-5f62fcb360", + "name": "Tuck (Knie an)", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-tuck-laenger-b05dd43bd8", + "name": "Tuck, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-tuck-laenger-c70e4ba401", + "name": "Tuck, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-39bccf6e6b", + "name": "halb gestreckt", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-gestreckt-9147433141", + "name": "halb gestreckt", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-voll-13f8ebb8c0", + "name": "halb/voll", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-halb-voll-cde344cba9", + "name": "halb/voll", + "factor": 0.82, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-side-plank-wechsel-309b0bc82c": { + "id": "movement-side-plank-wechsel-309b0bc82c", + "name": "Side Plank (Wechsel)", + "progression_id": "sp", + "movement_cluster": "core", + "movement_label": "Core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-auf-knien-3ed08acc27", + "name": "auf Knien", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-auf-knien-000b6c864b", + "name": "auf Knien", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-auf-knien-laenger-d1c3028780", + "name": "auf Knien, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-auf-knien-laenger-227ec9b5f7", + "name": "auf Knien, länger", + "factor": 0.65, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-6702e0693f", + "name": "voll", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-727fcc161a", + "name": "voll", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-laenger-8c287bb42a", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-voll-laenger-ef549f755a", + "name": "voll, länger", + "factor": 1.0, + "movement_cluster": "core", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-bicycle-crunch-15c87e28c5": { + "id": "movement-bicycle-crunch-15c87e28c5", + "name": "Bicycle Crunch", + "progression_id": "bic", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-langsam-b16ce156f7", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-langsam-3bc865247d", + "name": "langsam", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-9b37c0536d", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-6a0ed59bbd", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-1a29fed611", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-moderat-60b7d1ac93", + "name": "moderat", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-85e9262405", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-zuegig-2b4933822c", + "name": "zügig", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-russian-twist-kb-d6e382fb3c": { + "id": "movement-russian-twist-kb-d6e382fb3c", + "name": "Russian Twist (KB)", + "progression_id": "tw", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-ohne-gewicht-3bd115d2ec", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-ohne-gewicht-478be9efcd", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-ohne-gewicht-32fc20afc1", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-ohne-gewicht-fbe33d8a04", + "name": "ohne Gewicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-leicht-cf37c395cd", + "name": "KB leicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-leicht-5cca613a86", + "name": "KB leicht", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-f50c52e3e3", + "name": "KB", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + }, + { + "id": "step-kb-eb15afb22c", + "name": "KB", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-kb-floor-press-060586ebd6": { + "id": "movement-kb-floor-press-060586ebd6", + "name": "KB Floor Press", + "progression_id": "press", + "movement_cluster": "vertical_push", + "movement_label": "Schulterdrücken", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-floor-press-de744d6e05", + "name": "leicht (Floor Press)", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-floor-press-5987ddc1d2", + "name": "leicht (Floor Press)", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-floor-press-mehr-3b454ea446", + "name": "Floor Press mehr", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-floor-press-mehr-b34710868b", + "name": "Floor Press mehr", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-leicht-7f9274fd29", + "name": "Overhead leicht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-leicht-3413a54a3a", + "name": "Overhead leicht", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-mittel-4b236720a7", + "name": "Overhead mittel", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-overhead-mittel-ea724f9a42", + "name": "Overhead mittel", + "factor": 1.0, + "movement_cluster": "vertical_push", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-y-t-w-superman-1d331ef219": { + "id": "movement-y-t-w-superman-1d331ef219", + "name": "Y-T-W / Superman", + "progression_id": "sup", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "auto", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-superman-hold-06efa2683a", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-34af38f543", + "name": "Superman Hold", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-b383f0e540", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-superman-hold-laenger-6b326b0a11", + "name": "Superman Hold länger", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "seconds", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-871916bcee", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-reverse-snow-angels-78c24f8835", + "name": "Reverse Snow Angels", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-b7c40c3579", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-y-t-w-raises-a6d76cb15c", + "name": "Y-T-W Raises", + "factor": 1.0, + "movement_cluster": "general", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-kniebeuge-leicht-4ae3fb9fc0": { + "id": "movement-kniebeuge-leicht-4ae3fb9fc0", + "name": "Kniebeuge (leicht)", + "progression_id": "name:kniebeuge-leicht", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-liegestuetze-1-stufe-leichter-6033b9cb92": { + "id": "movement-liegestuetze-1-stufe-leichter-6033b9cb92", + "name": "Liegestütze (1 Stufe leichter)", + "progression_id": "name:liegestuetze-1-stufe-leichter", + "movement_cluster": "horizontal_push", + "movement_label": "Push-up", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-rudern-leicht-566f8de8f2": { + "id": "movement-rudern-leicht-566f8de8f2", + "name": "Rudern (leicht)", + "progression_id": "name:rudern-leicht", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "optional", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-mountain-climbers-locker-e4a7854ab9": { + "id": "movement-mountain-climbers-locker-e4a7854ab9", + "name": "Mountain Climbers (locker)", + "progression_id": "name:mountain-climbers-locker", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-inchworm-50016ff2a4": { + "id": "movement-inchworm-50016ff2a4", + "name": "Inchworm", + "progression_id": "name:inchworm", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-cat-cow-d8e060c65f": { + "id": "movement-cat-cow-d8e060c65f", + "name": "Cat-Cow", + "progression_id": "name:cat-cow", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-bird-dog-f2c9d49c80": { + "id": "movement-bird-dog-f2c9d49c80", + "name": "Bird Dog", + "progression_id": "name:bird-dog", + "movement_cluster": "general", + "movement_label": "Allgemein", + "result_schema": { + "mode": "reps", + "weight_mode": "none", + "laterality": "unilateral", + "sides_mode": "same" + }, + "variants": [] + }, + "movement-kb-halo-leicht-e3c156d79e": { + "id": "movement-kb-halo-leicht-e3c156d79e", + "name": "KB-Halo (leicht)", + "progression_id": "name:kb-halo-leicht", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [] + }, + "movement-kb-swing-zweiarmig-327d7b5ad6": { + "id": "movement-kb-swing-zweiarmig-327d7b5ad6", + "name": "KB Swing (zweiarmig)", + "progression_id": "swing", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-dead-stop-leicht-technik-02548f1a05", + "name": "Dead-Stop, leicht (Technik)", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-dead-stop-leicht-technik-3b9715b104", + "name": "Dead-Stop, leicht (Technik)", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-dead-stop-mehr-reps-7be430a756", + "name": "Dead-Stop, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-dead-stop-mehr-reps-5c68075818", + "name": "Dead-Stop, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-mittel-183cc7dfa6", + "name": "durchgehend, mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-mittel-7762c8f240", + "name": "durchgehend, mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-schwerer-6da670bed8", + "name": "durchgehend, schwerer", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-durchgehend-schwerer-a1609b5bad", + "name": "durchgehend, schwerer", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-goblet-squat-kb-f065dfd5b6": { + "id": "movement-goblet-squat-kb-f065dfd5b6", + "name": "Goblet Squat (KB)", + "progression_id": "gobletkb", + "movement_cluster": "knee_dominant", + "movement_label": "Kniebeuge / Ausfallschritt", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-tiefe-lernen-77201f2b6b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-tiefe-lernen-70ea0f2e9b", + "name": "leicht, Tiefe lernen", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-57415830b2", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-abf827246a", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-2d542b9c09", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-eda93ad7ea", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-9dd7fec573", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-3s-runter-3f76b2391b", + "name": "mittel, 3s runter", + "factor": 1.0, + "movement_cluster": "knee_dominant", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-kb-deadlift-beidbeinig-6f7c8bca76": { + "id": "movement-kb-deadlift-beidbeinig-6f7c8bca76", + "name": "KB-Deadlift (beidbeinig)", + "progression_id": "kbdl", + "movement_cluster": "hip_hinge", + "movement_label": "Hip Hinge / Hüftstreckung", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + }, + "variants": [ + { + "id": "step-leicht-technik-54ca22fe3d", + "name": "leicht, Technik", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-technik-fcd2dc3979", + "name": "leicht, Technik", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-db29d97bd7", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-leicht-mehr-reps-fd7e87b98b", + "name": "leicht, mehr Reps", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-7ed7284a10", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-mittel-e9887f19f9", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwerer-kontrolliert-dd0f4f4538", + "name": "schwerer, kontrolliert", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + }, + { + "id": "step-schwerer-kontrolliert-ab6af6caea", + "name": "schwerer, kontrolliert", + "factor": 1.0, + "movement_cluster": "hip_hinge", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "bilateral", + "sides_mode": "same" + } + } + ] + }, + "movement-halo-faca9a5cc9": { + "id": "movement-halo-faca9a5cc9", + "name": "Halo", + "progression_id": "halo", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-langsam-5d15ba8134", + "name": "leicht, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-langsam-c2a5c59e62", + "name": "leicht, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-81a736180a", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-fe775a4a23", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-c5c0bf8989", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-3898a7cea7", + "name": "mittel", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-c9db464d00", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-mittel-langsam-e1a2e46fd3", + "name": "mittel, langsam", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-einarmiges-rudern-kb-0ec1c11b35": { + "id": "movement-einarmiges-rudern-kb-0ec1c11b35", + "name": "Einarmiges Rudern (KB)", + "progression_id": "row1", + "movement_cluster": "horizontal_pull", + "movement_label": "Rudern", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-leicht-db8c75b358", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-leicht-d1e4feaafa", + "name": "leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-19ded398d6", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-etwas-mehr-0ce270eb41", + "name": "etwas mehr", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-leicht-a6f388b349", + "name": "einarmig leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-leicht-a9539fbadb", + "name": "einarmig leicht", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-mittel-74f0fe3e1e", + "name": "einarmig mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-einarmig-mittel-3f961280f6", + "name": "einarmig mittel", + "factor": 1.0, + "movement_cluster": "horizontal_pull", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-bottoms-up-kb-hold-griff-4a5f657f41": { + "id": "movement-bottoms-up-kb-hold-griff-4a5f657f41", + "name": "Bottoms-up KB-Hold (Griff)", + "progression_id": "carry", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-im-stand-10-15s-e90dd57ff1", + "name": "im Stand 10–15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-im-stand-10-15s-db8eec9df0", + "name": "im Stand 10–15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-15-20s-ruhig-321e582ab2", + "name": "15–20s, ruhig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-15-20s-ruhig-7db6eb1a01", + "name": "15–20s, ruhig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-bis-schulterhoehe-15s-f1763ea9e5", + "name": "bis Schulterhöhe 15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-bis-schulterhoehe-15s-21c8fd4d01", + "name": "bis Schulterhöhe 15s", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-laenger-ein-paar-schritte-fa5838da65", + "name": "länger / ein paar Schritte", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-laenger-ein-paar-schritte-ec93f04473", + "name": "länger / ein paar Schritte", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "seconds", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + }, + "movement-around-the-world-uppercut-kb-7ea726bbde": { + "id": "movement-around-the-world-uppercut-kb-7ea726bbde", + "name": "Around-the-World + Uppercut (KB)", + "progression_id": "atw", + "movement_cluster": "loaded_mobility", + "movement_label": "Kettlebell-Kontrolle", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + }, + "variants": [ + { + "id": "step-nur-around-the-world-leicht-97c90b17c5", + "name": "nur Around-the-World, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-nur-around-the-world-leicht-fd2fab13cb", + "name": "nur Around-the-World, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-around-the-world-mehr-reps-7af159bd81", + "name": "Around-the-World, mehr Reps", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-around-the-world-mehr-reps-43f1762f06", + "name": "Around-the-World, mehr Reps", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-leicht-cb2ce4fe29", + "name": "+ Uppercut, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-leicht-db86827e21", + "name": "+ Uppercut, leicht", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-fluessig-4b3453d5f9", + "name": "+ Uppercut, flüssig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + }, + { + "id": "step-uppercut-fluessig-2463ac3c0e", + "name": "+ Uppercut, flüssig", + "factor": 1.0, + "movement_cluster": "loaded_mobility", + "result_schema": { + "mode": "reps", + "weight_mode": "required", + "laterality": "unilateral", + "sides_mode": "separate" + } + } + ] + } + } + } +} diff --git a/testdata/proposals/phase-1.json b/testdata/proposals/phase-1.json new file mode 100644 index 0000000..3991c50 --- /dev/null +++ b/testdata/proposals/phase-1.json @@ -0,0 +1,43 @@ +{ + "version": 2, + "source_file": "phase-1.json", + "plan_id": "phase-1", + "published_revision": 1, + "updated_at": "2026-07-23T12:01:33+00:00", + "proposals": [ + { + "id": "overall:overall-1", + "status": "open", + "scope_key": "overall:overall", + "analysis_type": "overall", + "week": null, + "target": "Woche 2 / Pike", + "target_label": "Pike: nächste Stufe prüfen", + "target_exercise_id": "movement-pike-17f7716e00", + "target_progression_id": "pike", + "target_step_id": "step-hold-kleine-senkung-4f3bfac4b2", + "suggested_change": "Progressionsstufe von „Pike Hold“ auf „Hold + kleine Senkung“ setzen.", + "reason": "Zwei Einheiten mit 6x20 s Pike Hold sprechen für einen vorsichtigen Variantenwechsel.", + "condition": "Nur wenn beide Pike-Einheiten technisch sauber waren; sonst Pike Hold beibehalten.", + "manual_step": "Im Trainingsplan-Modul bei Pike die nächste Progressionsstufe auswählen.", + "analysis_created_at": "2026-07-23T12:01:33+00:00" + }, + { + "id": "overall:overall-2", + "status": "open", + "scope_key": "overall:overall", + "analysis_type": "overall", + "week": null, + "target": "Woche 2 / Einarm-Rudern ENG", + "target_label": "Einarm-Rudern ENG: Last oder Stufe prüfen", + "target_exercise_id": "movement-einarm-rudern-eng-kb-4907fbb5c7", + "target_progression_id": "rowe", + "target_step_id": "step-etwas-mehr-4f2775725c", + "suggested_change": "Von „leicht“ auf „etwas mehr“ wechseln.", + "reason": "Die Wiederholungen lagen in beiden Einheiten stabil bei etwa 8–9 pro Intervall.", + "condition": "Nur wenn Rückenposition und Zugweg sauber bleiben; sonst aktuelle Stufe beibehalten.", + "manual_step": "Im Trainingsplan-Modul die nächste Rudern-ENG-Progression oder passende Last eintragen.", + "analysis_created_at": "2026-07-23T12:01:33+00:00" + } + ] +} diff --git a/testdata/sessions/calihoss-newbie-8-wochen.json b/testdata/sessions/calihoss-newbie-8-wochen.json new file mode 100644 index 0000000..5c04350 --- /dev/null +++ b/testdata/sessions/calihoss-newbie-8-wochen.json @@ -0,0 +1,612 @@ +{ + "created_at": "2026-07-23T09:14:02+00:00", + "profile": { + "display_name": "", + "plan_notes": "", + "start_date": "" + }, + "progressions": { + "ls": { + "note": "", + "value": "" + }, + "name:1-2-reps-reserve-pruefen": { + "note": "", + "value": "" + }, + "name:beine-protokollieren": { + "note": "", + "value": "" + }, + "name:brustwirbelsaeule-schultern": { + "note": "", + "value": "" + }, + "name:bulletproof-shoulders": { + "note": "", + "value": "" + }, + "name:core-protokollieren": { + "note": "", + "value": "" + }, + "name:finger-plank": { + "note": "", + "value": "" + }, + "name:full-body-mobility": { + "note": "", + "value": "" + }, + "name:hueft-mobilitaet": { + "note": "", + "value": "" + }, + "name:lockerer-spaziergang": { + "note": "", + "value": "" + }, + "name:negative-pruefen": { + "note": "", + "value": "" + }, + "name:prone-y-t-w-technik": { + "note": "", + "value": "" + }, + "name:pull-protokollieren": { + "note": "", + "value": "" + }, + "name:push-protokollieren": { + "note": "", + "value": "" + }, + "name:regeneration-pruefen": { + "note": "", + "value": "" + }, + "name:schlaf-priorisieren": { + "note": "", + "value": "" + }, + "name:schmerzcheck": { + "note": "", + "value": "" + }, + "name:schwierigkeit-erhoehen": { + "note": "", + "value": "" + }, + "name:sonst-vollstaendig-pausieren": { + "note": "", + "value": "" + }, + "name:spaziergang": { + "note": "", + "value": "" + }, + "name:sprunggelenk-mobilitaet": { + "note": "", + "value": "" + }, + "name:startposition-pruefen": { + "note": "", + "value": "" + }, + "name:volle-reps-pruefen": { + "note": "", + "value": "" + }, + "name:wrist-conditioning": { + "note": "", + "value": "" + }, + "plank": { + "note": "", + "value": "" + }, + "rowe": { + "note": "", + "value": "" + }, + "squat": { + "note": "", + "value": "" + } + }, + "sessions": { + "w01-d01": { + "completed_at": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + }, + "warmup-4": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned" + }, + "w02-d01": { + "completed_at": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + }, + "warmup-4": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned" + }, + "w03-d01": { + "completed_at": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + }, + "warmup-4": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned" + }, + "w08-d01": { + "completed_at": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + }, + "warmup-4": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned" + }, + "w01-d02": { + "status": "planned", + "items": { + "warmup-0": { + "done": false, + "result": "", + "note": "" + }, + "warmup-1": { + "done": false, + "result": "", + "note": "" + }, + "d2-r0-e0": { + "done": false, + "result": "", + "note": "" + }, + "d2-r0-e1": { + "done": false, + "result": "", + "note": "" + }, + "d2-r0-e2": { + "done": false, + "result": "", + "note": "" + }, + "d2-r0-e3": { + "done": false, + "result": "", + "note": "" + }, + "d2-r1-e0": { + "done": false, + "result": "", + "note": "" + }, + "d2-r1-e1": { + "done": false, + "result": "", + "note": "" + }, + "d2-r1-e2": { + "done": false, + "result": "", + "note": "" + }, + "d2-r1-e3": { + "done": false, + "result": "", + "note": "" + }, + "cooldown-0": { + "done": false, + "result": "", + "note": "" + }, + "cooldown-1": { + "done": false, + "result": "", + "note": "" + }, + "stretch-0": { + "done": false, + "result": "", + "note": "" + } + }, + "note": "", + "started_at": "", + "completed_at": "" + }, + "w01-d03": { + "status": "planned", + "items": { + "warmup-0": { + "done": false, + "result": "", + "note": "" + }, + "warmup-1": { + "done": false, + "result": "", + "note": "" + }, + "warmup-2": { + "done": false, + "result": "", + "note": "" + }, + "warmup-3": { + "done": false, + "result": "", + "note": "" + }, + "warmup-4": { + "done": false, + "result": "", + "note": "" + }, + "d3-r0-e0": { + "done": false, + "result": "", + "note": "" + }, + "d3-r0-e1": { + "done": false, + "result": "", + "note": "" + }, + "d3-r0-e2": { + "done": false, + "result": "", + "note": "" + }, + "d3-r0-e3": { + "done": false, + "result": "", + "note": "" + }, + "d3-r1-e0": { + "done": false, + "result": "", + "note": "" + }, + "d3-r1-e1": { + "done": false, + "result": "", + "note": "" + }, + "d3-r1-e2": { + "done": false, + "result": "", + "note": "" + }, + "d3-r1-e3": { + "done": false, + "result": "", + "note": "" + }, + "cooldown-0": { + "done": false, + "result": "", + "note": "" + }, + "stretch-0": { + "done": false, + "result": "", + "note": "" + } + }, + "note": "", + "started_at": "", + "completed_at": "" + } + }, + "source_file": "calihoss-newbie-8-wochen.json", + "source_hash": "6d9ab7972384bb711693ced004c6a7053fbe858323e98dfea7e7a6b0c56c83b1", + "updated_at": "2026-07-23T09:42:21+00:00", + "version": 1 +} diff --git a/testdata/sessions/phase-1.json b/testdata/sessions/phase-1.json new file mode 100644 index 0000000..28eb549 --- /dev/null +++ b/testdata/sessions/phase-1.json @@ -0,0 +1,1393 @@ +{ + "created_at": "2026-07-23T09:43:25+00:00", + "profile": { + "display_name": "", + "plan_notes": "", + "start_date": "" + }, + "progressions": { + "atw": { + "note": "", + "value": "" + }, + "bic": { + "note": "", + "value": "" + }, + "bridge": { + "note": "", + "value": "" + }, + "calf": { + "note": "", + "value": "" + }, + "carry": { + "note": "", + "value": "" + }, + "db": { + "note": "", + "value": "" + }, + "dips": { + "note": "", + "value": "" + }, + "gobletkb": { + "note": "", + "value": "" + }, + "halo": { + "note": "", + "value": "" + }, + "holl": { + "note": "", + "value": "" + }, + "kbdl": { + "note": "", + "value": "" + }, + "load": { + "note": "", + "value": "" + }, + "lr": { + "note": "", + "value": "" + }, + "lraise": { + "note": "", + "value": "" + }, + "ls": { + "note": "", + "value": "" + }, + "lunge": { + "note": "", + "value": "" + }, + "mtn": { + "note": "", + "value": "" + }, + "name:bird-dog": { + "note": "", + "value": "" + }, + "name:cat-cow": { + "note": "", + "value": "" + }, + "name:inchworm": { + "note": "", + "value": "" + }, + "name:kb-halo-leicht": { + "note": "", + "value": "" + }, + "name:kniebeuge-leicht": { + "note": "", + "value": "" + }, + "name:liegestuetze-1-stufe-leichter": { + "note": "", + "value": "" + }, + "name:mountain-climbers-locker": { + "note": "", + "value": "" + }, + "name:rudern-leicht": { + "note": "", + "value": "" + }, + "pike": { + "note": "", + "value": "" + }, + "plank": { + "note": "", + "value": "" + }, + "press": { + "note": "", + "value": "" + }, + "row1": { + "note": "", + "value": "" + }, + "row2": { + "note": "", + "value": "" + }, + "rowe": { + "note": "", + "value": "" + }, + "sp": { + "note": "", + "value": "" + }, + "sup": { + "note": "", + "value": "" + }, + "swing": { + "note": "", + "value": "" + }, + "tw": { + "note": "", + "value": "" + }, + "wall": { + "note": "", + "value": "" + } + }, + "sessions": { + "w01-d01": { + "completed_at": "2026-07-23T10:01:58.783Z", + "items": { + "cooldown-0": { + "done": true, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": true, + "note": "", + "result": "" + }, + "d1-r0-e0": { + "done": true, + "exercise_name": "Liegestütze", + "inherited_from": "", + "note": "", + "progression": "Inkline (hoch)", + "progression_id": "ls", + "result": "8/7/7/7/6/6/5" + }, + "d1-r0-e1": { + "done": true, + "exercise_name": "Bank-Dips (Couch)", + "inherited_from": "", + "note": "", + "progression": "Knie stark gebeugt, klein", + "progression_id": "dips", + "result": "8/7/7/7/6/6" + }, + "d1-r0-e2": { + "done": true, + "exercise_name": "Pike", + "inherited_from": "", + "note": "", + "progression": "Pike Hold", + "progression_id": "pike", + "result": "6x20 s" + }, + "d1-r0-e3": { + "done": true, + "exercise_name": "Seitheben (KB/Rucksack)", + "inherited_from": "", + "note": "", + "progression": "leicht (Technik)", + "progression_id": "lraise", + "result": "4kg, 6/6/5 je Seite" + }, + "d1-r1-e0": { + "done": true, + "exercise_name": "Goblet Squat (KB/Rucksack)", + "inherited_from": "", + "note": "", + "progression": "leicht, Tiefe lernen", + "progression_id": "gobletkb", + "result": "0kg, 8/8/7/7/6/7" + }, + "d1-r1-e1": { + "done": true, + "exercise_name": "Ausfallschritt rückw.", + "inherited_from": "", + "note": "", + "progression": "Körpergewicht (ggf. festhalten)", + "progression_id": "lunge", + "result": "3x4 je Seite" + }, + "d1-r1-e2": { + "done": true, + "exercise_name": "Glute Bridge", + "inherited_from": "", + "note": "", + "progression": "beidbeinig", + "progression_id": "bridge", + "result": "6x 20s" + }, + "d1-r1-e3": { + "done": true, + "exercise_name": "Wadenheben", + "inherited_from": "", + "note": "", + "progression": "Körpergewicht", + "progression_id": "calf", + "result": "10kg, 10/10/10/10/10/10" + }, + "stretch-0": { + "done": true, + "note": "", + "result": "" + }, + "stretch-1": { + "done": true, + "note": "", + "result": "" + }, + "stretch-2": { + "done": true, + "note": "", + "result": "" + }, + "stretch-3": { + "done": true, + "note": "", + "result": "" + }, + "stretch-4": { + "done": true, + "note": "", + "result": "" + }, + "stretch-5": { + "done": true, + "note": "", + "result": "" + }, + "warmup-0": { + "done": true, + "note": "", + "result": "" + }, + "warmup-1": { + "done": true, + "note": "", + "result": "" + }, + "warmup-2": { + "done": true, + "note": "", + "result": "" + }, + "warmup-3": { + "done": true, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "2026-07-23T10:01:58.783Z", + "status": "completed" + }, + "w01-d02": { + "completed_at": "2026-07-23T10:03:32.468Z", + "items": { + "cooldown-0": { + "done": true, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": true, + "note": "", + "result": "" + }, + "d2-r0-e0": { + "done": true, + "exercise_name": "Einarm-Rudern ENG (KB)", + "inherited_from": "", + "note": "", + "progression": "leicht", + "progression_id": "rowe", + "result": "4kg, 9/9/8/8/8/9" + }, + "d2-r0-e1": { + "done": true, + "exercise_name": "Bizeps-Curls (KB/Rucksack)", + "inherited_from": "", + "note": "", + "progression": "leicht", + "progression_id": "load", + "result": "4kg, 8/8/8/8/8/7" + }, + "d2-r0-e2": { + "done": true, + "exercise_name": "Einarm-Rudern BREIT (KB)", + "inherited_from": "", + "note": "", + "progression": "leicht (Technik)", + "progression_id": "row2", + "result": "4kg, 7/7/6/7/6/8" + }, + "d2-r0-e3": { + "done": true, + "exercise_name": "Superman / Y-T-W", + "inherited_from": "", + "note": "", + "progression": "Superman Hold", + "progression_id": "sup", + "result": "6x20 s" + }, + "d2-r1-e0": { + "done": true, + "exercise_name": "Plank", + "inherited_from": "", + "note": "", + "progression": "auf Knien 15–20 s", + "progression_id": "plank", + "result": "6x20 s" + }, + "d2-r1-e1": { + "done": true, + "exercise_name": "Mountain Climbers", + "inherited_from": "", + "note": "", + "progression": "moderat", + "progression_id": "mtn", + "result": "6x20 s" + }, + "d2-r1-e2": { + "done": true, + "exercise_name": "Dead Bug", + "inherited_from": "", + "note": "", + "progression": "Arme ODER Beine einzeln", + "progression_id": "db", + "result": "6x20 s" + }, + "d2-r1-e3": { + "done": true, + "exercise_name": "Beinheben", + "inherited_from": "", + "note": "", + "progression": "Knie gebeugt", + "progression_id": "lr", + "result": "5/5/4/4/4/3" + }, + "stretch-0": { + "done": true, + "note": "", + "result": "" + }, + "stretch-1": { + "done": true, + "note": "", + "result": "" + }, + "stretch-2": { + "done": true, + "note": "", + "result": "" + }, + "stretch-3": { + "done": true, + "note": "", + "result": "" + }, + "stretch-4": { + "done": true, + "note": "", + "result": "" + }, + "stretch-5": { + "done": true, + "note": "", + "result": "" + }, + "warmup-0": { + "done": true, + "note": "", + "result": "" + }, + "warmup-1": { + "done": true, + "note": "", + "result": "" + }, + "warmup-2": { + "done": true, + "note": "", + "result": "" + }, + "warmup-3": { + "done": true, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "2026-07-23T10:03:32.468Z", + "status": "completed" + }, + "w01-d03": { + "completed_at": "2026-07-23T10:06:49.678Z", + "items": { + "cooldown-0": { + "done": true, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": true, + "note": "", + "result": "" + }, + "d3-r0-e0": { + "done": true, + "exercise_name": "Goblet Squat (KB/Rucksack)", + "inherited_from": "w01-d01", + "note": "", + "progression": "leicht, Tiefe lernen", + "progression_id": "gobletkb", + "result": "6kg, 8/8/7/7/6/7" + }, + "d3-r0-e1": { + "done": true, + "exercise_name": "RDL / Hip Hinge (KB/Rucksack)", + "inherited_from": "w01-d02", + "note": "", + "progression": "leicht", + "progression_id": "load", + "result": "8kg, 7/7/7/7/7/7" + }, + "d3-r0-e2": { + "done": true, + "exercise_name": "Glute Bridge 1-beinig", + "inherited_from": "w01-d01", + "note": "", + "progression": "beidbeinig", + "progression_id": "bridge", + "result": "6x20 s" + }, + "d3-r0-e3": { + "done": true, + "exercise_name": "Wall Sit", + "inherited_from": "", + "note": "", + "progression": "15–25 s", + "progression_id": "wall", + "result": "6x20 s" + }, + "d3-r1-e0": { + "done": true, + "exercise_name": "Hollow Hold", + "inherited_from": "", + "note": "", + "progression": "Tuck (Knie an)", + "progression_id": "holl", + "result": "6x20 s" + }, + "d3-r1-e1": { + "done": true, + "exercise_name": "Side Plank (Wechsel)", + "inherited_from": "", + "note": "", + "progression": "auf Knien", + "progression_id": "sp", + "result": "6x20s" + }, + "d3-r1-e2": { + "done": true, + "exercise_name": "Bicycle Crunch", + "inherited_from": "", + "note": "", + "progression": "langsam", + "progression_id": "bic", + "result": "6x20s" + }, + "d3-r1-e3": { + "done": true, + "exercise_name": "Russian Twist (KB)", + "inherited_from": "", + "note": "", + "progression": "ohne Gewicht", + "progression_id": "tw", + "result": "4kg, 4/4/4 je Seite" + }, + "stretch-0": { + "done": true, + "note": "", + "result": "" + }, + "stretch-1": { + "done": true, + "note": "", + "result": "" + }, + "stretch-2": { + "done": true, + "note": "", + "result": "" + }, + "stretch-3": { + "done": true, + "note": "", + "result": "" + }, + "stretch-4": { + "done": true, + "note": "", + "result": "" + }, + "stretch-5": { + "done": true, + "note": "", + "result": "" + }, + "warmup-0": { + "done": true, + "note": "", + "result": "" + }, + "warmup-1": { + "done": true, + "note": "", + "result": "" + }, + "warmup-2": { + "done": true, + "note": "", + "result": "" + }, + "warmup-3": { + "done": true, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "2026-07-23T10:06:49.678Z", + "status": "completed" + }, + "w01-d04": { + "completed_at": "2026-07-23T10:11:52.028Z", + "items": { + "cooldown-0": { + "done": true, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": true, + "note": "", + "result": "" + }, + "d4-r0-e0": { + "done": true, + "exercise_name": "Liegestütze", + "inherited_from": "w01-d01", + "note": "", + "progression": "Inkline (hoch)", + "progression_id": "ls", + "result": "8/7/7/6/6/6" + }, + "d4-r0-e1": { + "done": true, + "exercise_name": "Bank-Dips (Couch)", + "inherited_from": "w01-d01", + "note": "", + "progression": "Knie stark gebeugt, klein", + "progression_id": "dips", + "result": "8/7/7/7/6/6" + }, + "d4-r0-e2": { + "done": true, + "exercise_name": "Pike", + "inherited_from": "w01-d01", + "note": "", + "progression": "Pike Hold", + "progression_id": "pike", + "result": "6x20 s" + }, + "d4-r0-e3": { + "done": true, + "exercise_name": "KB Floor Press", + "inherited_from": "", + "note": "", + "progression": "leicht (Floor Press)", + "progression_id": "press", + "result": "10kg, 8/7/7/6/6/7" + }, + "d4-r1-e0": { + "done": true, + "exercise_name": "Einarm-Rudern ENG (KB)", + "inherited_from": "w01-d02", + "note": "", + "progression": "leicht", + "progression_id": "rowe", + "result": "4kg, 9/9/9/8/8/9" + }, + "d4-r1-e1": { + "done": true, + "exercise_name": "Bizeps-Curls (KB/Rucksack)", + "inherited_from": "w01-d03", + "note": "", + "progression": "leicht", + "progression_id": "load", + "result": "8kg, 7/7/7/7/7/8" + }, + "d4-r1-e2": { + "done": true, + "exercise_name": "Einarm-Rudern BREIT (KB)", + "inherited_from": "w01-d02", + "note": "", + "progression": "leicht (Technik)", + "progression_id": "row2", + "result": "4kg, 7/7/6/7/7/8" + }, + "d4-r1-e3": { + "done": true, + "exercise_name": "Y-T-W / Superman", + "inherited_from": "w01-d02", + "note": "", + "progression": "Superman Hold", + "progression_id": "sup", + "result": "6x20 s" + }, + "stretch-0": { + "done": true, + "note": "", + "result": "" + }, + "stretch-1": { + "done": true, + "note": "", + "result": "" + }, + "stretch-2": { + "done": true, + "note": "", + "result": "" + }, + "stretch-3": { + "done": true, + "note": "", + "result": "" + }, + "stretch-4": { + "done": true, + "note": "", + "result": "" + }, + "warmup-0": { + "done": true, + "note": "", + "result": "" + }, + "warmup-1": { + "done": true, + "note": "", + "result": "" + }, + "warmup-2": { + "done": true, + "note": "", + "result": "" + }, + "warmup-3": { + "done": true, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "2026-07-23T10:11:52.028Z", + "status": "completed" + }, + "w01-d05": { + "status": "planned", + "items": { + "warmup-0": { + "done": false, + "result": "", + "note": "" + }, + "warmup-1": { + "done": false, + "result": "", + "note": "" + }, + "warmup-2": { + "done": false, + "result": "", + "note": "" + }, + "warmup-3": { + "done": false, + "result": "", + "note": "" + }, + "d5-r0-e0": { + "done": false, + "result": "", + "note": "" + }, + "d5-r0-e1": { + "done": false, + "result": "", + "note": "" + }, + "d5-r0-e2": { + "done": false, + "result": "", + "note": "" + }, + "d5-r0-e3": { + "done": false, + "result": "", + "note": "" + }, + "d5-r1-e0": { + "done": false, + "result": "", + "note": "" + }, + "d5-r1-e1": { + "done": false, + "result": "", + "note": "" + }, + "d5-r1-e2": { + "done": false, + "result": "", + "note": "" + }, + "d5-r1-e3": { + "done": false, + "result": "", + "note": "" + }, + "cooldown-0": { + "done": false, + "result": "", + "note": "" + }, + "cooldown-1": { + "done": false, + "result": "", + "note": "" + }, + "stretch-0": { + "done": false, + "result": "", + "note": "" + }, + "stretch-1": { + "done": false, + "result": "", + "note": "" + }, + "stretch-2": { + "done": false, + "result": "", + "note": "" + }, + "stretch-3": { + "done": false, + "result": "", + "note": "" + }, + "stretch-4": { + "done": false, + "result": "", + "note": "" + } + }, + "note": "", + "warmup_note": "", + "cooldown_note": "", + "stretch_note": "", + "started_at": "", + "stopped_at": "", + "completed_at": "" + }, + "w01-d06": { + "completed_at": "2026-07-23T10:14:51.035Z", + "items": { + "cooldown-0": { + "done": true, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": true, + "note": "", + "result": "" + }, + "d6-r0-e0": { + "done": true, + "exercise_name": "KB Swing (zweiarmig)", + "inherited_from": "", + "note": "", + "progression": "Dead-Stop, leicht (Technik)", + "progression_id": "swing", + "result": "10kg, 15 reps" + }, + "d6-r0-e1": { + "done": true, + "exercise_name": "Goblet Squat (KB)", + "inherited_from": "w01-d03", + "note": "", + "progression": "leicht, Tiefe lernen", + "progression_id": "gobletkb", + "result": "8kg, 6/6/7/8/6/6" + }, + "d6-r0-e2": { + "done": true, + "exercise_name": "KB-Deadlift (beidbeinig)", + "inherited_from": "", + "note": "", + "progression": "leicht, Technik", + "progression_id": "kbdl", + "result": "10kg, 5/5/6/6/6/5" + }, + "d6-r0-e3": { + "done": true, + "exercise_name": "Halo", + "inherited_from": "", + "note": "", + "progression": "leicht, langsam", + "progression_id": "halo", + "result": "4kg, 5/4/4 je Seite" + }, + "d6-r1-e0": { + "done": true, + "exercise_name": "KB Floor Press", + "inherited_from": "w01-d04", + "note": "", + "progression": "leicht (Floor Press)", + "progression_id": "press", + "result": "10kg, 7/7/8/6/5/6" + }, + "d6-r1-e1": { + "done": true, + "exercise_name": "Einarmiges Rudern (KB)", + "inherited_from": "", + "note": "", + "progression": "leicht", + "progression_id": "row1", + "result": "6kg, 6/7/8 je Seite" + }, + "d6-r1-e2": { + "done": true, + "exercise_name": "Bottoms-up KB-Hold (Griff)", + "inherited_from": "", + "note": "", + "progression": "im Stand 10–15s", + "progression_id": "carry", + "result": "4kg, 3x20 s je Seite" + }, + "d6-r1-e3": { + "done": true, + "exercise_name": "Around-the-World + Uppercut (KB)", + "inherited_from": "", + "note": "", + "progression": "nur Around-the-World, leicht", + "progression_id": "atw", + "result": "4kg, 5/5/5 je Seite" + }, + "stretch-0": { + "done": true, + "note": "", + "result": "" + }, + "stretch-1": { + "done": true, + "note": "", + "result": "" + }, + "stretch-2": { + "done": true, + "note": "", + "result": "" + }, + "stretch-3": { + "done": true, + "note": "", + "result": "" + }, + "stretch-4": { + "done": true, + "note": "", + "result": "" + }, + "stretch-5": { + "done": true, + "note": "", + "result": "" + }, + "warmup-0": { + "done": true, + "note": "", + "result": "" + }, + "warmup-1": { + "done": true, + "note": "", + "result": "" + }, + "warmup-2": { + "done": true, + "note": "", + "result": "" + }, + "warmup-3": { + "done": true, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "2026-07-23T10:14:51.035Z", + "status": "completed" + }, + "w02-d01": { + "completed_at": "", + "cooldown_note": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d1-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "stretch-1": { + "done": false, + "note": "", + "result": "" + }, + "stretch-2": { + "done": false, + "note": "", + "result": "" + }, + "stretch-3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-4": { + "done": false, + "note": "", + "result": "" + }, + "stretch-5": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned", + "stopped_at": "", + "stretch_note": "", + "warmup_note": "" + }, + "w02-d02": { + "completed_at": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": false, + "note": "", + "result": "" + }, + "d2-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d2-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d2-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d2-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d2-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d2-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d2-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d2-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "stretch-1": { + "done": false, + "note": "", + "result": "" + }, + "stretch-2": { + "done": false, + "note": "", + "result": "" + }, + "stretch-3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-4": { + "done": false, + "note": "", + "result": "" + }, + "stretch-5": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned" + }, + "w02-d03": { + "completed_at": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": false, + "note": "", + "result": "" + }, + "d3-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d3-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d3-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d3-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d3-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d3-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d3-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d3-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "stretch-1": { + "done": false, + "note": "", + "result": "" + }, + "stretch-2": { + "done": false, + "note": "", + "result": "" + }, + "stretch-3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-4": { + "done": false, + "note": "", + "result": "" + }, + "stretch-5": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned" + }, + "w02-d04": { + "completed_at": "", + "items": { + "cooldown-0": { + "done": false, + "note": "", + "result": "" + }, + "cooldown-1": { + "done": false, + "note": "", + "result": "" + }, + "d4-r0-e0": { + "done": false, + "note": "", + "result": "" + }, + "d4-r0-e1": { + "done": false, + "note": "", + "result": "" + }, + "d4-r0-e2": { + "done": false, + "note": "", + "result": "" + }, + "d4-r0-e3": { + "done": false, + "note": "", + "result": "" + }, + "d4-r1-e0": { + "done": false, + "note": "", + "result": "" + }, + "d4-r1-e1": { + "done": false, + "note": "", + "result": "" + }, + "d4-r1-e2": { + "done": false, + "note": "", + "result": "" + }, + "d4-r1-e3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-0": { + "done": false, + "note": "", + "result": "" + }, + "stretch-1": { + "done": false, + "note": "", + "result": "" + }, + "stretch-2": { + "done": false, + "note": "", + "result": "" + }, + "stretch-3": { + "done": false, + "note": "", + "result": "" + }, + "stretch-4": { + "done": false, + "note": "", + "result": "" + }, + "warmup-0": { + "done": false, + "note": "", + "result": "" + }, + "warmup-1": { + "done": false, + "note": "", + "result": "" + }, + "warmup-2": { + "done": false, + "note": "", + "result": "" + }, + "warmup-3": { + "done": false, + "note": "", + "result": "" + } + }, + "note": "", + "started_at": "", + "status": "planned" + } + }, + "source_file": "phase-1.json", + "source_hash": "73ab9a0246f001da7a4b6cbd27c21078562e1dbf3ea4d638d298bd53af53264d", + "updated_at": "2026-07-23T21:28:19+00:00", + "version": 5 +}