Compare commits

...

377 Commits

Author SHA1 Message Date
90e8f51566 Update Catalog Prompt Slots Router and Access Layer Exemptions
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m13s
- Added documentation to the `catalog_prompt_slots.py` file to clarify its role as a global admin catalog requiring authentication and admin role, without tenant context.
- Updated the `check_access_layer_hints.py` script to include `catalog_prompt_slots.py` in the list of exempt routers, ensuring proper access control for admin functionalities.
2026-06-15 15:27:03 +02:00
7e5ef4561a Refactor Catalog Prompt Slot Management and Enhance Fallback Logic
Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Failing after 2s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m22s
- Introduced a new function `_resolve_entry_slot_values` to streamline the merging of stored slot values with fallbacks, improving code clarity and maintainability.
- Updated `get_catalog_entry_slots` and `resolve_catalog_prompt_variables` functions to utilize the new fallback logic, enhancing the handling of catalog entries.
- Enhanced the `CatalogPromptSlotsEditor` component to display fallback information for slots, improving user experience in managing catalog prompts.
- Incremented version numbers and updated changelog to reflect the new features and improvements.
2026-06-15 15:18:00 +02:00
53f2b027cc Enhance Planning Catalog Context and Prompt Slot Management
Some checks failed
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Failing after 2s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m20s
- Introduced new catalog context handling in planning prompt functions, allowing for improved integration of planning variables.
- Added optional catalog context parameters in various functions to streamline the merging of planning prompt variables.
- Updated frontend components to include CatalogPromptSlotsEditor for managing prompt slots across different catalog types.
- Enhanced API utilities to support fetching and updating catalog prompt slots, improving backend functionality for catalog management.
- Incremented version numbers and updated changelog to reflect the new features and improvements.
2026-06-15 12:13:15 +02:00
9cee862c32 Implement Planning Prompt Enhancements and LLM Usage Tracking
All checks were successful
Deploy Development / deploy (push) Successful in 47s
Test Suite / pytest-backend (push) Successful in 49s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 15s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m26s
- Added new fields for goal query, user notes, max steps, and search query in the AiPromptPreviewBody to support planning prompts.
- Integrated planning prompt handling in the preview_ai_prompt function, allowing for distinct processing of planning and exercise prompts.
- Introduced LLM usage tracking in openrouter_chat_completion and planning_exercise_suggest functions to monitor AI call metrics.
- Updated frontend components to accommodate new input fields for planning prompts, enhancing user experience and functionality.
2026-06-15 07:50:49 +02:00
0b203489f7 Implement Graph Visibility Promotion Logic and Update UI Components
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 41s
Test Suite / playwright-tests (push) Successful in 1m21s
- Added a new function `_graph_promotion_transition` to determine the necessary exercise visibility changes during graph promotions.
- Updated the `list_visibility_promotion_candidates` endpoint to utilize the new promotion logic, ensuring accurate exercise visibility handling.
- Enhanced the frontend components to prompt users for exercise visibility adjustments based on graph visibility changes, improving user experience.
- Introduced tests for the new promotion logic to ensure correctness and reliability in visibility transitions.
2026-06-14 07:30:26 +02:00
1c67a50ce4 Enhance Exercise Progression Graph Panel with Governance Club Management
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 38s
Test Suite / playwright-tests (push) Successful in 1m12s
- Introduced functionality to manage governance clubs for superadmins, allowing for better club selection and organization within the Exercise Progression Graph Panel.
- Implemented state management for clubs, including sorting and filtering options, to improve user experience and accessibility.
- Enhanced the useEffect hook to fetch governance clubs dynamically, ensuring up-to-date club information is available for selection.
- Updated the club selection dropdown to categorize clubs into "My Clubs" and "Other Clubs," improving clarity and usability for users.
2026-06-14 07:19:35 +02:00
87d9fa9b65 Enhance Exercise Progression Graph Panel with Club Management Features
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 43s
Test Suite / playwright-tests (push) Successful in 1m13s
- Added functionality to select and manage clubs within the Exercise Progression Graph Panel, allowing users to assign clubs to exercises.
- Introduced state management for club selection and manual entry, improving user experience for platform admins.
- Updated visibility handling to ensure proper governance and club association during exercise promotion.
- Enhanced error handling to provide clearer feedback when no club is selected, ensuring users are guided to make necessary selections.
2026-06-14 07:10:11 +02:00
4b9374765b Enhance Progression Graph Management with F15 Features and Evaluation Improvements
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m21s
- Updated `PROJECT_STATUS.md` to reflect the implementation of F15 features, including the unified slot review and handling of `findings_stale`.
- Enhanced `PROGRESSION_GRAPH_SLOT_EDITOR_SPEC.md` with detailed descriptions of new functionalities related to the match dialog and path quality assessments.
- Introduced new functions in `exercise_progression_graphs.py` to validate exercise visibility against progression graph settings, ensuring proper governance.
- Improved frontend components to support new governance parameters (visibility and club_id) in exercise creation workflows.
- Updated documentation in `HANDOVER.md` and `PLANNING_KI_ROADMAP.md` to outline the latest developments and validation results for the F15 features.
- Enhanced utility functions for exercise creation to incorporate governance settings, improving the overall user experience in the path builder and editor.
2026-06-14 06:44:12 +02:00
b629f192ac Refactor ProgressionSlotCard Key Prop for Improved Stability
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m39s
- Updated the key prop in the ProgressionSlotCard component to use a simpler index-based key, enhancing the stability of component rendering during updates.
- This change aims to prevent potential issues with component re-renders and improve overall performance in the ProgressionGraphEditor.
2026-06-13 17:33:36 +02:00
313d613b7c Enhance Path Quality Assessment and Slot Management Features
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m13s
- Added new functions for computing assignment quality scores and counting step assignment statistics, improving the evaluation of steps in path quality assessments.
- Updated existing methods to incorporate the new scoring logic, enhancing the robustness of path evaluations.
- Introduced UI components in the frontend to display detailed quality assessment results, including handling of split dimensions in path evaluations.
- Enhanced tests to cover new functionalities and ensure accuracy in quality scoring and slot management processes.
2026-06-13 17:13:35 +02:00
7265cd5a01 Add findings_stale field to GraphPlanningRoadmapArtifact and update ProgressionGraphEditor for state management
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 45s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m34s
- Introduced `findings_stale` field in `GraphPlanningRoadmapArtifact` to track the freshness of findings.
- Updated `ProgressionGraphEditor` to manage `findingsStale` state across various functions, ensuring accurate representation of evaluation status.
- Modified related utility functions and tests to accommodate the new state, enhancing overall functionality and user feedback in the progression graph management process.
2026-06-13 16:29:17 +02:00
5e5f4ca8d4 Enhance Progression Findings and Graph Editor with Evaluation Staleness Handling
Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 47s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Failing after 2s
Test Suite / playwright-tests (push) Successful in 1m19s
- Added `evaluationStale` state to `ProgressionGraphEditor` and `ProgressionFindingsPanel` to track the freshness of evaluations.
- Updated UI to display a warning when evaluations are stale, prompting users to re-evaluate the graph.
- Modified loading and evaluation functions to manage the `evaluationStale` state effectively, ensuring accurate user feedback during the evaluation process.
- Improved user notifications regarding the need for re-evaluation after changes to the graph.
2026-06-13 16:23:04 +02:00
f0e581a9f5 Implement Off-Topic Slot Gap Specification and Unified Slot Review Enhancements
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m34s
- Introduced `_build_off_topic_slot_gap_spec` to generate specifications for off-topic slots, improving the handling of filled but thematically inappropriate slots.
- Added `_build_unified_slot_review_entry` to streamline the review process for slots, incorporating various parameters for better evaluation and suggestions.
- Enhanced existing logic in slot management to improve the robustness of path evaluations and user feedback.
- Added tests for the new off-topic slot gap specification to ensure functionality and correctness.
2026-06-13 12:43:59 +02:00
cd457e3ea0 Enhance Slot Evaluation and Scoring Mechanisms
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m26s
- Introduced new functions `_off_topic_semantic_scores_by_slot` and `_score_exercise_stage_fit_for_spec` to improve the evaluation of off-topic steps and exercise stage fit, enhancing the quality assessment process.
- Updated `_run_unified_slot_improvement_review` to incorporate off-topic scores and exercise stage fit scoring, refining the decision-making process for slot suggestions.
- Enhanced existing logic to streamline the handling of slot scores and improve the overall robustness of slot management in path evaluations.
2026-06-13 12:33:16 +02:00
e9bf5bd1a5 Enhance Path Evaluation and Slot Management Features
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m13s
- Introduced `_parse_slot_refs_from_text` to extract and convert slot references from text, improving the handling of user input in path evaluations.
- Updated `_problematic_slots_from_path_qa` to utilize the new parsing function, enhancing the identification of problematic slots based on various hints and issues.
- Enhanced `ProgressionGraphEditor` and `ProgressionOptimizeCompareModal` to better display identified problem slots and their associated reasons, improving user feedback during evaluations.
- Added tests for new parsing functionality and its integration with existing slot management processes, ensuring robustness in slot reference handling.
2026-06-13 12:17:58 +02:00
3468b2066e Enhance Path QA and Progression Review Logic
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 41s
Test Suite / playwright-tests (push) Successful in 1m27s
- Introduced `_resolve_hint_major_index` to accurately map hints to major step indices, improving the handling of optimization hints in path evaluations.
- Added `_problematic_slots_from_path_qa` to identify and categorize problematic slots based on baseline QA, enhancing the quality assessment process.
- Updated `_slot_suggestion_accepted` to incorporate new parameters for slot problems and stage specifications, refining the decision-making process for slot suggestions.
- Enhanced `ProgressionGraphEditor` to improve user notifications regarding identified issues and suggestions, ensuring clearer communication of path evaluation results.
- Modified `buildProgressionComparePayload` and `buildUnifiedSlotReviewComparePayload` to support baseline evaluations, streamlining the comparison process for proposed paths.
2026-06-13 10:39:52 +02:00
a1e4ad66df Implement Quick Evaluation and Quality Scoring for Path QA
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m11s
- Added `_quick_evaluate_steps_qa` function to streamline path quality assessment without recursive API calls, enhancing performance for slot comparisons.
- Introduced `compute_deterministic_path_quality_score` to provide a heuristic quality score based on gaps and off-topic steps, improving evaluation accuracy.
- Updated `_run_unified_slot_improvement_review` to utilize the new quick evaluation method, optimizing the review process and integrating quality scoring.
- Enhanced `build_path_qa_summary` to include quality score calculations, ensuring comprehensive feedback on path evaluations.
- Refactored related functions for improved clarity and efficiency in handling path quality assessments.
2026-06-13 10:27:07 +02:00
85fccdd093 Enhance Progression Path Comparison and Slot Evaluation Features
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m12s
- Introduced new fields in `ProgressionPathSuggestRequest` for baseline evaluation and incremental scoring, improving the assessment of proposed paths.
- Implemented `_apply_slot_diff_to_steps` and `_score_incremental_slot_diffs` functions to manage slot differences and evaluate their impact on quality scores.
- Updated `ProgressionGraphEditor` to streamline the match comparison flow, integrating new evaluation parameters and improving user notifications.
- Enhanced `ProgressionOptimizeCompareModal` to better display proposed path suggestions, including pro/con evaluations and quality delta metrics.
- Refactored utility functions for clearer handling of slot differences and improved overall data management in the progression graph editor.
2026-06-13 10:11:10 +02:00
19bbcdaf50 Refactor Progression Comparison Logic and Enhance UI Components
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m19s
- Introduced new utility functions for comparing slot differences, including `compareDiffKind`, `annotateCompareDiffKinds`, and various filtering functions to streamline the comparison process.
- Updated `ProgressionGraphEditor` to utilize the new comparison logic, improving the handling of slot differences and user notifications.
- Enhanced `ProgressionOptimizeCompareModal` to better manage proposed path suggestions, including clearer messaging and improved selection handling for optional replacements.
- Adjusted frontend components to reflect changes in comparison logic, ensuring a more intuitive user experience in managing progression paths.
2026-06-13 09:02:15 +02:00
cec96ae473 Implement Progression Comparison Logic and Refactor Fetching Methods
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m13s
- Introduced `buildProgressionComparePayload` to create a structured comparison response from baseline and proposed evaluation results, enhancing clarity in slot differences.
- Refactored `fetchMatchCompare` to `fetchFullMatch` for improved clarity and functionality in fetching progression paths.
- Updated `runMatchCompareFlow` to streamline the evaluation process, integrating baseline and match results for a comprehensive comparison.
- Enhanced utility functions for managing slot differences and gap fill offers, improving overall data handling in the progression graph editor.
- Adjusted frontend components to reflect these changes, ensuring a more intuitive user experience in managing progression paths.
2026-06-13 08:46:10 +02:00
53f1c7161f Refactor AI Gap Fill and Progression Path Evaluation Logic
Some checks failed
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Has been cancelled
- Removed the `try_suggest_ai_stage_step` function from `_enrich_roadmap_unfilled_gap_offers`, simplifying the gap fill offer generation process.
- Updated `_run_evaluate_only_path_qa` and `suggest_progression_path` to disable AI calls and proposals, enhancing control over evaluation parameters.
- Adjusted `ProgressionGraphEditor` to reflect changes in API requests, ensuring consistent handling of evaluation data.
- Added a new test to validate the behavior of proposed QA when no slot differences are present, improving test coverage for comparison logic.
2026-06-13 08:43:02 +02:00
89c6780294 Enhance AI Gap Fill Logic and Progression Path Handling
All checks were successful
Deploy Development / deploy (push) Successful in 49s
Test Suite / pytest-backend (push) Successful in 45s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 15s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
- Integrated `try_suggest_ai_stage_step` to suggest AI-generated gap fill steps based on user input, improving the automation of the planning process.
- Updated `_enrich_roadmap_unfilled_gap_offers` to conditionally include AI gap fill proposals, enhancing the offer generation logic.
- Implemented `_merge_gap_fill_offers_from_steps` to consolidate gap fill offers from various steps, ensuring a comprehensive list of available offers.
- Modified `ProgressionGraphEditor` to utilize the new merging logic for gap fill offers, improving the user experience in managing offers.
- Enhanced utility functions to streamline the collection and filtering of gap fill offers from API responses.
- Bumped version to reflect the new features and improvements.
2026-06-13 08:36:53 +02:00
3f130aa8ad Refactor Progression Path Evaluation and Comparison Logic
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m22s
- Updated `suggest_progression_path` to utilize `evaluate_steps` for improved validation, ensuring at least one evaluation step is provided.
- Modified frontend components to enhance user experience in the comparison process, including clearer messaging and improved dialog handling.
- Adjusted `ProgressionGraphEditor` to streamline the comparison flow and integrate new evaluation parameters.
- Enhanced `ProgressionOptimizeCompareModal` to reflect changes in comparison logic, allowing for better user interaction with proposed path suggestions.
- Bumped version to reflect the new features and improvements.
2026-06-13 08:17:59 +02:00
69ce3f6975 Enhance Rematch Suggestion Logic and Progression Path Evaluation
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 45s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
- Introduced `_baseline_slot_accepts_rematch_suggestion` to filter out filled or invalid slots from rematch suggestions, improving the accuracy of rematch logic.
- Updated `_build_rematch_suggestion_diffs` to skip non-eligible baseline slots, streamlining the rematch suggestion process.
- Added `_evaluate_steps_for_compare_qa` to evaluate steps against the current state, enhancing the quality assessment during progression path suggestions.
- Modified `_build_progression_compare_response` to ensure proper handling of slot differences and quality scores, improving response clarity.
- Updated frontend components to reflect changes in rematch handling and evaluation logic.
- Bumped version to reflect the new features and improvements.
2026-06-13 08:02:44 +02:00
dccb065181 Enhance Slot Difference Annotation and Rematch Suggestion Logic
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m13s
- Introduced `_annotate_slot_diffs` to mark trivial ID swaps in slot differences, improving clarity in comparison results.
- Added `_actionable_slot_diffs` to filter out non-actionable differences, streamlining the evaluation process.
- Implemented `_build_rematch_suggestion_diffs` to generate suggestions based on rematch logs, enhancing the path optimization workflow.
- Updated `_build_progression_compare_response` to incorporate actionable slot differences and rematch suggestions, improving the response structure.
- Enhanced frontend components to display rematch suggestions and handle trivial differences more effectively.
- Bumped version to reflect the new features and improvements.
2026-06-13 07:55:47 +02:00
e828a5da32 Enhance Progression Path Evaluation and Comparison Logic
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 45s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 15s
Test Suite / k6 /health Baseline (push) Successful in 38s
Test Suite / playwright-tests (push) Successful in 1m23s
- Introduced `_steps_to_evaluate_payloads` to convert path steps into evaluation payloads for improved quality assessments.
- Updated `_build_progression_compare_response` to include a new `proposed_eval` parameter, allowing for fair quality assessment comparisons.
- Enhanced `ProgressionGraphEditor` to utilize the new pipeline quality assessment data.
- Modified `ProgressionOptimizeCompareModal` to display detailed comparison results, including handling of trivial slot differences and optimization hints.
- Bumped version to reflect the new features and improvements.
2026-06-13 07:44:01 +02:00
5bca5ef9eb Enhance Progression Path Evaluation and Optimization Features
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 45s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m18s
- Updated `suggest_progression_path` to include additional evaluation parameters, allowing for more comprehensive path assessments.
- Introduced `PathQaPipelineDetails` component to display detailed quality assessment metrics, including rematch and refine logs, in the frontend.
- Enhanced `ProgressionGraphEditor` to manage proposed path evaluations and integrate quality assessment results into the draft workflow.
- Improved `ProgressionOptimizeCompareModal` to present optimization hints and quality tier information for proposed paths.
- Bumped version to reflect the new features and improvements.
2026-06-12 13:33:36 +02:00
5ed06002d9 Implement Comparison Logic for Progression Path Suggestions
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 45s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m18s
- Added `compare_with_assignments` flag to `ProgressionPathSuggestRequest` to enable comparison of proposed paths with existing slot assignments.
- Introduced `_assignment_preservation_active` function to determine if existing assignments should be preserved during path suggestions.
- Enhanced `suggest_progression_path` to handle comparison logic, including validation for minimum slot assignments required for comparison.
- Implemented `_build_progression_compare_response` to structure the response for comparison results, including slot differences and quality scores.
- Updated frontend components to support new comparison features, including handling of slot assignments and optimization comparisons.
- Bumped version to reflect the new features and improvements.
2026-06-12 13:22:04 +02:00
b8f65e04c5 Enhance Rematch Logic and Slot Filtering in Planning Path
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 45s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m13s
- Introduced `filter_rematch_slot_indices` to exclude preserved slots from rematching, improving the accuracy of slot assignments.
- Added `_slot_priority_for_rematch` to prioritize existing slot assignments during rematching, enhancing the robustness of the rematch process.
- Updated `_run_roadmap_rematch_loop` to utilize the new filtering and prioritization logic, ensuring better handling of rematch scenarios.
- Enhanced tests in `test_planning_path_rematch.py` to validate the new filtering behavior and ensure correct exercise restoration when not rejected.
- Bumped version to reflect the new features and improvements.
2026-06-12 12:33:00 +02:00
f3710ac0a1 Enhance Planning Catalog Context Integration in Progression Path
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m15s
- Updated `PROJECT_STATUS.md` to reflect the addition of the Planning AI Progression Graph and its context in the roadmap.
- Enhanced `DOMAIN_MODEL.md` with details on the new `planning_catalog_context` features, allowing trainers to manage curriculum stages and context.
- Added tests in `test_planning_catalog_context.py` to validate the separation of LLM highlights from fix hints during QA processes.
- Updated `HANDOVER.md` and `PLANNING_KI_ROADMAP.md` to reflect the latest app version and improvements in the planning context.
- Enhanced frontend components to support the new planning catalog context, including updates to `ExerciseProgressionPathBuilder` and `ProgressionGraphEditor`.
- Bumped version to 0.8.233 to reflect the new features and improvements.
2026-06-12 12:25:52 +02:00
6ab2f20f08 Enhance Progression Path Suggestion with Planning Catalog Context Integration
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m16s
- Introduced `planning_catalog_context` to `ProgressionPathSuggestRequest` for improved handling of catalog-related data during path suggestions.
- Implemented `_resolve_planning_catalog_context` to retrieve and validate the planning catalog context, enhancing the robustness of the suggestion process.
- Updated `_build_path_target_profile` to incorporate catalog context, enriching target profiles with relevant planning data.
- Enhanced frontend components in `ProgressionGraphEditor` to manage and display planning catalog context, including new selection options for focus areas, style directions, training types, and target groups.
- Added utility functions for parsing and transforming planning catalog context data for API interactions.
- Bumped version to 0.8.233 to reflect the new features and improvements.
2026-06-12 10:16:55 +02:00
a4e73c830f Implement Pruning of Filled Steps from Roadmap Unfilled
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 22s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m13s
- Introduced `_prune_filled_from_roadmap_unfilled` function to remove steps with filled exercises from the unfilled roadmap, preventing outdated references.
- Updated `_run_roadmap_rematch_loop` to incorporate the new pruning logic, ensuring only relevant unfilled steps are retained during rematching.
- Added tests for the pruning function to validate its behavior with various step scenarios.
- Bumped version to 0.8.232 to reflect the new functionality and improvements.
2026-06-12 08:27:39 +02:00
63c99b0ec5 Enhance Roadmap Slot Matching and Gap Offer Logic
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 45s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m22s
- Introduced `_roadmap_step_passes_post_match_gate` to validate steps after matching, ensuring only relevant steps proceed.
- Enhanced `_enrich_roadmap_unfilled_gap_offers` to generate AI gap offers for unfilled roadmap slots, improving exercise suggestions.
- Updated `suggest_progression_path` to incorporate new gap offer logic and streamline the handling of roadmap steps.
- Refined frontend logic in `applyMatchStepsToSlots` to better manage step assignments and improve clarity in slot handling.
- Bumped version to 0.8.231 to reflect the new features and improvements.
2026-06-12 08:05:56 +02:00
d448c3191f Enhance Stage Mismatch Handling and Roadmap Slot Purging
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m15s
- Introduced `_purge_stage_mismatch_roadmap_slots` to clear slots with persistent stage mismatches, improving the relevance of exercise suggestions.
- Updated `collect_gap_fill_specs` to handle stage mismatch issues more effectively, providing clearer rationale and title hints for off-topic exercises.
- Modified `_filter_learning_goal_candidate_ids` to enforce stricter filtering criteria, ensuring only relevant candidates are considered.
- Enhanced `rematch_roadmap_slots` to incorporate slot assignment history, preventing conflicts with previously assigned exercises.
- Bumped version to 0.8.230 to reflect the new features and improvements.
2026-06-12 07:57:19 +02:00
8a4be795f4 Implement Peer Learning Goals and Stage Fit Enhancements
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m33s
- Introduced `_peer_stage_learning_goals` to retrieve learning goals from peer stages, enhancing the ability to filter exercises based on cross-slot collisions.
- Added `_filter_learning_goal_candidate_ids` to refine candidate selection by incorporating peer learning goals and stage fit criteria, improving exercise relevance in suggestions.
- Enhanced `pick_best_path_hit` and `_match_roadmap_slot` to utilize peer learning goals for better exercise selection and to prevent conflicts with titles from other stages.
- Updated `stage_refinement_criteria_from_learning_goal` to provide clearer criteria for stage refinement based on learning goals.
- Bumped version to 0.8.229 to reflect the new features and improvements.
2026-06-12 07:40:26 +02:00
a49987408b Enhance Stage Specification Refinement and Rematch Logic
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m22s
- Updated `max_rematch_rounds` in `ProgressionPathSuggestRequest` to allow for a maximum of 3 rounds, improving flexibility in rematch processes.
- Introduced `_track_rejected` function to track rejected exercises by major step index, enhancing the rematch logic to account for previously rejected exercises.
- Enhanced `_run_roadmap_rematch_loop` to utilize the new rejection tracking, ensuring better handling of off-topic steps during rematching.
- Improved `detect_off_topic_steps` to incorporate refined scoring and reasoning for stage fit, enhancing the accuracy of off-topic detection.
- Updated `refine_stage_spec_artifact` to merge stage exclusion phrases more effectively, improving the clarity of anti-pattern handling.
- Bumped version to 0.8.228 to reflect the new features and improvements.
2026-06-11 22:11:31 +02:00
f36a747efa Enhance Progression Path Suggestion with Stage Specification Refinement
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m16s
- Introduced `auto_refine_stage_spec` to `ProgressionPathSuggestRequest`, enabling optional refinement of stage specifications during the rematch process.
- Updated `_run_roadmap_rematch_loop` to incorporate stage specification refinements, logging changes for better tracking of adjustments made during rematching.
- Enhanced `suggest_progression_path` to include refine logs in the output, providing clearer insights into the refinement process.
- Added utility functions for formatting refine log entries, improving the display of refinement actions in the frontend components.
- Updated frontend components to display refine logs, enhancing user feedback on stage specification adjustments during progression analysis.
- Bumped version to 0.8.227 to reflect the new features and improvements.
2026-06-11 21:43:45 +02:00
de9fdf3ac0 Enhance Progression Findings Panel with Rematch and Optimization Hints
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m13s
- Added support for displaying optimization hints and rematch logs in the Progression Findings Panel, improving user feedback on potential enhancements.
- Introduced new utility functions for formatting rematch log entries and resolving hint slot indices, enhancing clarity in displayed information.
- Updated the Progression Graph Editor to handle rematch actions and display relevant match summaries, ensuring comprehensive insights during progression analysis.
- Enhanced the utility functions to support the new features, ensuring robust handling of optimization actions and rematch logic.
2026-06-11 21:39:16 +02:00
df93da9a03 Enhance Gap Fill and Rematch Logic in Progression Path
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 47s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 39s
Test Suite / playwright-tests (push) Successful in 1m22s
- Introduced `_step_neighbors_at_index` to safely retrieve neighboring steps without causing IndexErrors, improving robustness in gap fill specifications.
- Updated `collect_gap_fill_specs` to utilize the new neighbor retrieval function, ensuring safe access to adjacent steps during gap fill processing.
- Enhanced rematch logic in `_run_roadmap_rematch_loop` to incorporate `max_rematch_rounds`, allowing for controlled iterations during roadmap rematching.
- Improved handling of unfilled roadmap slots in `collect_rematch_slot_indices`, ensuring accurate identification of gaps in the progression path.
- Added tests to validate the new gap fill handling and rematch logic, ensuring reliability in path suggestion features.
2026-06-11 21:20:47 +02:00
de939481ba Enhance Gap Fill Offer Handling and Progression Path Logic
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m24s
- Updated `suggest_progression_path` to ensure unique gap fill offers are collected and added based on their IDs, improving the relevance of suggestions.
- Refined the logic for setting `slot_status` and handling `gap_offer` and `proposal_key` in steps, enhancing clarity in progression path management.
- Improved the `collectGapOffersFromApiResponse` function to consolidate gap offers from various sources, ensuring comprehensive offer retrieval.
- Enhanced the handling of unfilled slots in `applyMatchStepsToSlots`, ensuring proper assignment of proposals and gap offers.
- Added tests to validate the new logic for gap fill offers and slot assignments, ensuring robustness in path suggestion features.
2026-06-11 13:13:46 +02:00
6d130a7e09 Implement Learning Goal Candidate Retrieval and Roadmap Fallback Logic
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m22s
- Added `_safe_tsquery_fragment` to sanitize learning goal input for SQL queries, improving query safety.
- Introduced `_fetch_learning_goal_library_candidate_ids` to retrieve exercise IDs matching learning goals, enhancing exercise relevance in roadmap suggestions.
- Enhanced `_match_roadmap_slot` to utilize learning goal candidates, improving the accuracy of supplemental exercise selection.
- Implemented `_pick_roadmap_rank_fallback` to provide a fallback mechanism for selecting the best exercise when strict matching fails, ensuring better exercise retrieval.
- Updated tests to validate the new learning goal retrieval and fallback logic, ensuring robustness in exercise selection processes.
2026-06-11 12:54:07 +02:00
b2fbf6b4af Refactor Roadmap Step Annotation and Slot Assignment Logic
All checks were successful
Deploy Development / deploy (push) Successful in 49s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m12s
- Updated `_annotate_roadmap_step` to change the condition for setting `slot_status` based on `roadmap_match_source`, improving clarity in slot assignment handling.
- Removed the `_try_reconcile_slot_assignment` function to streamline the slot assignment process, as its logic is now integrated into the main flow.
- Enhanced `_match_roadmap_slot` to conditionally preserve slot assignments based on exercise ID, ensuring better handling of existing assignments.
- Improved the handling of semantic scores in `rank_visible_library_hits` to prioritize the best semantic fit, enhancing exercise retrieval accuracy.
- Added tests to validate the new logic for title equivalence and semantic scoring, ensuring robustness in exercise selection processes.
2026-06-11 12:45:53 +02:00
ca2adbd55e Enhance Exercise Retrieval and Path Handling Logic
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 48s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m23s
- Introduced new functions for handling exercise visibility and retrieval based on progression graph context, including `fetch_exercise_rows_by_ids_for_graph`.
- Updated `_load_supplemental_exercise_rows` to incorporate graph visibility rules, improving the accuracy of exercise retrieval.
- Enhanced `_run_path_step_retrieval` to utilize preloaded supplemental exercise rows, optimizing performance and clarity in path step processing.
- Added `exercise_title_equivalent_to_stage_goal` function to improve title matching against learning goals, enhancing exercise relevance.
- Updated tests to validate new retrieval logic and title equivalence functionality, ensuring robustness in exercise selection processes.
2026-06-11 12:33:02 +02:00
ad051c015f Enhance Progression Path Suggestion with Retrieval Boost and Slot Assignment Logic
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m14s
- Added `preserve_slot_assignments` and `retrieval_boost_exercise_ids` to `ProgressionPathSuggestRequest` for improved handling of exercise suggestions.
- Refactored `_supplemental_exercise_ids_from_body` to incorporate retrieval boost exercise IDs, ensuring they are prioritized over slot assignments.
- Updated `_build_steps_roadmap_first` to conditionally preserve slot assignments based on the new flag.
- Enhanced tests to validate the new retrieval boost logic and its integration with existing slot assignment handling.
2026-06-11 12:20:41 +02:00
b464047c3a Enhance Exercise Progression Graph Functionality and Visibility Logic
All checks were successful
Deploy Development / deploy (push) Successful in 50s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m16s
- Introduced new functions for handling exercise visibility in progression graphs, including `library_content_visibility_for_progression_graph_sql` to manage visibility based on graph context.
- Added `_supplemental_exercise_ids_from_body` to extract exercise IDs from request bodies, improving data handling in path suggestions.
- Implemented visibility promotion candidate retrieval in the API, allowing for the identification of private exercises that need visibility adjustments when promoting graph visibility.
- Enhanced existing SQL queries and retrieval functions to incorporate new visibility logic, ensuring accurate exercise visibility based on user roles and graph settings.
- Updated frontend components to support visibility promotion workflows, including user prompts for managing private exercises during graph visibility changes.
- Added tests to validate new visibility logic and ensure robustness in exercise retrieval and promotion processes.
2026-06-11 12:10:46 +02:00
7203c871fc Add Slot Assignments and Enhance Path Handling Logic
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m17s
- Introduced `slot_assignments` to `ProgressionPathSuggestRequest` for improved handling of existing slot assignments in path building.
- Implemented `_slot_assignments_by_major_index` and `_path_step_from_slot_assignment` functions to facilitate the integration of slot assignments into the path generation process.
- Updated `_build_steps_roadmap_first` to utilize slot assignments, enhancing the accuracy of path steps based on existing exercise slots.
- Enhanced `detect_path_gaps` to skip empty slots, preventing unnecessary errors during gap detection.
- Added tests to validate the new slot assignment handling and ensure robustness in path generation logic.
2026-06-11 12:02:04 +02:00
480890d0c6 Update Dockerfile and requirements for improved dependency management
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m18s
- Added `tzdata` installation in the Dockerfile to support time zone handling in Linux environments.
- Increased `PIP_DEFAULT_TIMEOUT` and added retry logic for pip installations to enhance reliability during dependency installation.
- Updated `requirements.txt` to conditionally include `tzdata` for Windows platforms, ensuring compatibility across different operating systems.
2026-06-11 11:48:25 +02:00
8f1dad53ab Enhance Progression Path Suggestion Logic and UI Feedback
All checks were successful
Test Suite / pytest-backend (push) Successful in 41s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m18s
Deploy Development / deploy (push) Successful in 44s
- Updated `suggest_progression_path` to include AI-generated gap fill offers when exercises are missing, improving the relevance of suggested paths.
- Introduced a match summary to provide insights on library matches and gap fill offers, enhancing user feedback in the `ProgressionGraphEditor`.
- Refined the `pick_best_path_hit` function to ensure proper handling of roadmap stage matches based on primary topics.
- Added tests to validate the new gap fill offer logic and match summary functionality, ensuring robustness in path suggestion features.
2026-06-11 11:17:53 +02:00
044ce2ee60 Implement Primary Topic Resolution in Path Logic
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m22s
- Introduced `resolve_path_primary_topic` function to enhance the determination of primary topics from goal queries and semantic briefs, improving exercise relevance.
- Updated `_match_roadmap_slot` and `detect_off_topic_steps` functions to utilize the new primary topic resolution logic, ensuring accurate topic identification.
- Enhanced tests to validate the functionality of primary topic resolution and its impact on exercise selection and off-topic detection.
- Improved handling of primary topics in the `ExerciseProgressionPathBuilder` and related components for better integration with the overall path-building process.
2026-06-11 11:06:38 +02:00
f63b09fc9c Refine Technique Path Scope Logic and Enhance Test Coverage
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m24s
- Updated the `exercise_passes_technique_path_scope` function to clarify the requirements for technique inclusion, ensuring that the primary technique must appear in the exercise text.
- Enhanced the logic to allow for relaxed matching based on parts of the primary topic, improving flexibility in exercise validation.
- Added new tests to validate the rejection of off-topic exercises, specifically addressing cases where only stage goals mention the primary technique.
- Improved the selection logic in `pick_best_path_hit` to ensure proper handling of roadmap stage matches.
2026-06-11 10:48:36 +02:00
713a344d17 Enhance Roadmap Step Handling and Off-Topic Logic
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Successful in 52s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 41s
Test Suite / playwright-tests (push) Successful in 1m51s
- Improved off-topic step handling by incorporating roadmap major step indices for better indexing and detection.
- Refactored `collect_gap_fill_specs` to streamline the insertion logic for off-topic steps, ensuring correct placement based on major step indices.
- Introduced `_normalize_roadmap_steps_coverage` function to standardize roadmap steps coverage, enhancing the handling of missing slots.
- Added `prune_stripped_after_rematch` function to clean up stripped off-topic steps after rematching, improving the overall rematching process.
- Updated tests to validate new rematching and off-topic handling features, ensuring robustness against edge cases.
- Incremented application version to reflect these updates.
2026-06-11 10:40:25 +02:00
1d94c2ebf1 Enhance Roadmap Slot Matching and Off-Topic Detection
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 45s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 36s
Test Suite / playwright-tests (push) Successful in 1m23s
- Introduced `auto_rematch_after_qa` parameter in `ProgressionPathSuggestRequest` to enable automatic rematching after quality assurance checks.
- Refactored roadmap slot matching logic to improve clarity and functionality, renaming `_build_steps_roadmap_first` to `_match_roadmap_slot`.
- Added `_with_roadmap_major_index` utility to streamline off-topic step detection by incorporating roadmap major step indices.
- Enhanced off-topic detection logic to utilize the new utility for improved clarity in identifying mismatches and exclusions.
- Incremented application version to reflect these updates.
2026-06-11 10:30:48 +02:00
a152218c45 Enhance Path QA and Stage Matching Logic
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m14s
- Introduced multistage path quality assurance (QA) functionality to improve exercise relevance and feedback through structured tiers and optimization hints.
- Updated stage specifications to include `start_state` and `target_state` for better contextualization in roadmap matching.
- Enhanced semantic brief construction with technique sibling exclusions to refine exercise selection based on primary topics.
- Improved path retrieval logic to incorporate new parameters for nuanced matching against learning goals.
- Incremented application version to reflect these updates.
2026-06-11 10:19:58 +02:00
4ef3f00e6b Enhance Planning Intent Context and Stage Specification Finalization
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m13s
- Introduced `intent_context` and `semantic_brief` parameters in `try_llm_stage_specs` to improve context handling for stage specifications.
- Updated `build_goal_analysis` to extract explicit exclusions from goal queries, enhancing constraint management.
- Enhanced `roadmap_context_from_override` to enrich semantic briefs with path constraints and finalize stage specifications with intent context.
- Incremented application version to reflect these updates.
2026-06-11 08:47:26 +02:00
3c12363b8f Enhance Path Exclusion Logic and Semantic Brief Enrichment
Some checks failed
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Has been cancelled
- Introduced `resolve_path_anti_patterns` to improve handling of path exclusions based on explicit negations and semantic briefs.
- Updated `enrich_brief_with_path_constraints` to incorporate path-specific exclusions into semantic briefs, enhancing exercise relevance.
- Modified roadmap step annotation to allow for anti-pattern overrides, improving flexibility in exercise selection.
- Enhanced tests to validate new path exclusion features and ensure correct functionality against learning goals.
- Incremented application version to reflect these updates.
2026-06-11 08:43:59 +02:00
07e147bc76 Enhance Stage Matching and Retrieval Logic in Planning Exercise
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m16s
- Introduced `build_stage_match_brief` to create stage-specific semantic briefs, improving roadmap matching accuracy.
- Updated path retrieval logic to differentiate between general and stage-specific semantic weights, enhancing exercise relevance.
- Added support for anti-patterns and success criteria in stage matching, allowing for more nuanced exercise selection.
- Enhanced tests to validate new stage matching features and ensure correct functionality against learning goals.
- Incremented application version to reflect these updates.
2026-06-10 17:02:21 +02:00
18547613ea Implement Stage Learning Goal Features in Planning Exercise
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m13s
- Added `semantic_brief_for_stage` function to enhance semantic briefs with stage learning goals for improved roadmap matching.
- Introduced `exercise_passes_stage_learning_goal_gate` to validate exercises against stage learning goals, enhancing relevance checks.
- Updated path retrieval and scoring logic to incorporate stage learning goals, allowing for more nuanced exercise selection.
- Enhanced UI to indicate weak matches with stage learning goals, improving user feedback on exercise relevance.
- Incremented application version to reflect these updates.
2026-06-10 16:39:17 +02:00
48d51c07c5 Enhance Exercise Progression Graph Panel and Editor with New Features
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m26s
- Refactored `ExerciseProgressionGraphPanel` to support a create dialog for new progression graphs, improving user experience.
- Integrated `ProgressionGraphListCard` for better visualization of existing graphs and streamlined management.
- Updated `ProgressionGraphEditor` to handle start/target analysis and improved draft hydration with AI suggestions.
- Added utility functions for managing structured responses from AI, enhancing the planning process.
- Incremented application version to reflect these updates.
2026-06-10 16:17:40 +02:00
3b483346de Enhance Progression Graph Editor with Skills Catalog and AI Draft Handling
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m21s
- Introduced skills catalog management in the `ProgressionGraphEditor`, allowing for improved context in AI suggestions.
- Updated the loading mechanism to fetch both focus areas and skills catalog concurrently, enhancing performance.
- Implemented `ensureQuickCreateDraftFromAiSuggestion` utility to streamline the creation of drafts from AI suggestions.
- Enhanced slot management by integrating AI context into the gap fill preparation process, improving user experience.
- Incremented application version to reflect these updates.
2026-06-10 16:04:15 +02:00
e0ddfa6ce5 Add AI Suggestion Handling for Roadmap Gaps and Enhance Progression Graph Components
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 45s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 15s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m17s
- Implemented functions to resolve neighboring steps based on major indices and build AI context for unfilled roadmap stages.
- Enhanced `try_suggest_ai_stage_step` to generate AI proposals for empty roadmap stages, improving user experience in gap filling.
- Updated `build_gap_fill_offer` to utilize major step neighbors for better context in offers related to unfilled slots.
- Added tests to ensure correct functionality of AI suggestion handling in the context of roadmap gaps.
- Incremented application version to reflect these updates.
2026-06-10 15:56:30 +02:00
ee22b22970 Refactor Progression Graph Components and Consolidate UI
All checks were successful
Deploy Development / deploy (push) Successful in 47s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m15s
- Updated the `ProgressionGraphSlotEditorSpec.md` to reflect UI consolidation, removing separate editors and integrating functionalities into `ExerciseProgressionGraphPanel`.
- Refactored `ExerciseProgressionGraphPanel` to streamline the editing experience, removing unused state and logic for better performance.
- Enhanced `ProgressionGraphEditor` to support embedded usage and trigger callbacks on save, improving integration with other components.
- Simplified `ProgressionGraphEditPage` to redirect users to the exercises list with deep-linking support for selected graphs.
- Incremented application version to reflect these updates.
2026-06-10 15:42:29 +02:00
c1bf9279ad Add Gap Offer Handling and UI Enhancements in Progression Graph Components
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m18s
- Implemented `_build_evaluate_empty_slot_gap_specs` function to generate gap offer specifications for unfilled roadmap slots in evaluate-only mode.
- Enhanced `ProgressionFindingsPanel` to display AI offers for empty slots and gaps, improving user interaction and clarity.
- Updated `ProgressionGraphEditor` and `ProgressionSlotCard` components to support new functionalities for managing slots and offers.
- Refactored utility functions in `progressionGraphDraft.js` to streamline slot management and offer handling.
- Incremented application version to reflect these updates.
2026-06-10 15:34:37 +02:00
97efe66306 Implement EvaluateStepPayload and SlotContentEntry for Enhanced Planning Features
Some checks failed
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Failing after 45s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 15s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m21s
- Introduced EvaluateStepPayload class to facilitate evaluation of exercise steps with optional attributes for AI proposals and roadmap details.
- Added SlotContentEntry and SlotExerciseContent classes to manage exercise content within the progression graph planning artifact.
- Updated GraphPlanningRoadmapArtifact to include new slot contents and last findings attributes for improved data handling.
- Enhanced Exercise Progression Graph Panel with links to the new Slot Editor for streamlined editing of progression graphs.
- Incremented application version to reflect these updates.
2026-06-10 13:05:49 +02:00
8d5f0b533c Enhance Exercise Progression Graph Panel and Path Builder with New Features
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 48s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m31s
- Introduced a primary chain selection in the Exercise Progression Graph Panel to streamline exercise path management.
- Updated the ProgressionChainEditor to support single path mode, allowing users to manage a single progression path more effectively.
- Enhanced the ExerciseProgressionPathBuilder with improved logic for merging graph nodes into path steps and filtering gap offers.
- Updated UI elements for better clarity and user experience, including new notifications and styling adjustments.
- Incremented application version to reflect these updates.
2026-06-10 11:17:05 +02:00
800189ff8f Enhance Exercise Progression Graph Panel and Path Builder with New Features
All checks were successful
Deploy Development / deploy (push) Successful in 47s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m16s
- Added `ProgressionChainEditor` to the Exercise Progression Graph Panel for improved management of exercise chains.
- Refactored state management to utilize `useRef` for chain editor references and removed unused sequence step logic.
- Introduced a path insert notice in the Exercise Progression Path Builder to inform users about unsaved changes.
- Updated UI elements to enhance clarity regarding the status of paths before saving.
- Incremented application version to reflect these updates.
2026-06-10 07:55:51 +02:00
3be7606d90 Update documentation and enhance planning features in Progression Graph
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m25s
- Updated the `CLAUDE.md` to reflect changes in the Progression Graph, including the new Ist-Stand and roadmap specifications.
- Enhanced `PLANNING_EXERCISE_SUGGEST_CONTEXT.md` with detailed descriptions of the current state and features of the planning exercise.
- Revised `PLANNING_PROGRESSION_ROADMAP_SPEC.md` to document the implementation status of various phases and their corresponding migrations.
- Incremented application version to 0.8.217 to incorporate recent updates and improvements in the planning context and roadmap functionalities.
2026-06-10 07:50:29 +02:00
ca3a9c6fa4 Enhance Exercise Progression Path Builder with Planning Wizard Stepper
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m13s
- Introduced a new Planning Wizard Stepper component to guide users through the exercise planning process in four steps.
- Implemented logic to compute the maximum reachable step based on user input and current progress.
- Updated state management to track the current wizard step and ensure it aligns with user interactions.
- Enhanced the user interface to improve clarity and navigation through the planning stages.
- Incremented application version to reflect these changes.
2026-06-10 07:30:01 +02:00
5692931d07 Update version to 0.8.217 and enhance Exercise Progression Path Builder with planning roadmap features
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m15s
- Incremented application version to 0.8.217 to reflect recent changes.
- Added support for a planning roadmap in the Exercise Progression Path Builder, allowing users to save and load structured planning artifacts.
- Enhanced the persistence logic for the planning roadmap, ensuring updates are correctly handled during graph modifications.
- Improved the user interface to display saved planning hints, enriching the user experience and interaction with the progression graphs.
2026-06-10 07:25:57 +02:00
98b279fa89 Update version to 0.8.216 and enhance Exercise Progression Path Builder
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m18s
- Incremented application version to 0.8.216 to reflect recent changes.
- Added skill expectations handling in the Exercise Progression Path Builder, improving the integration of expected skills into the roadmap steps.
- Enhanced the mapping of major steps to include load profiles, success criteria, anti-patterns, and exercise types, enriching the user experience and functionality.
2026-06-10 07:14:18 +02:00
1e7941f57b Enhance Gap Fill Goal Text and Skill Expectations Integration
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 48s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m22s
- Updated `build_gap_fill_goal_text` to include expected skills in the generated text, improving clarity for users.
- Enhanced `_roadmap_gap_snapshot_for_spec` to incorporate skill expectations from the progression stage, enriching the roadmap context.
- Modified `_annotate_roadmap_step` to append skill expectations to the step reasons, providing additional insights.
- Updated tests to verify the inclusion of expected skills in the gap fill goal text.
- Incremented application version to 0.8.215 to reflect these changes.
2026-06-10 07:09:46 +02:00
0adf20c9e1 Enhance Gap Planning Context with Stage Overrides and Trainer Supplements
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Successful in 42s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m25s
- Added `stage_learning_goal_override` and `gap_trainer_supplements` parameters to `build_progression_path_gap_planning_context`, allowing for customized learning goals and additional trainer notes.
- Updated `gapOfferContextDisplayLines` to include trainer supplements in the context display.
- Enhanced `ExerciseProgressionPathBuilder` to utilize new parameters for improved gap fill offer handling.
- Incremented application version to 0.8.214 to reflect these changes.
2026-06-10 06:54:49 +02:00
d4b1780193 Enhance Gap Fill Offer with Context Preview and Update Version
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m14s
- Added `context_preview` to the `build_gap_fill_offer` function, providing a structured overview of the roadmap snapshot.
- Introduced `gapOfferContextDisplayLines` utility to format context information for UI display, improving clarity for users.
- Updated `ExerciseProgressionPathBuilder` and related components to utilize the new context preview, enhancing the user experience.
- Incremented application version to 0.8.213 to reflect these changes.
2026-06-09 16:27:03 +02:00
f2650dac57 Enhance Planning Context with Progression Gap Snapshot and Start/Target Analysis
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 42s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 35s
Test Suite / playwright-tests (push) Successful in 1m13s
- Introduced `build_progression_gap_snapshot` function to create a compact roadmap context for gap exercises, integrating start situation, target state, and stage specifications.
- Updated `build_gap_fill_goal_text` to include roadmap snapshot details, enhancing the context for AI-generated exercises.
- Enhanced `ProgressionPathSuggestRequest` and related components to support new structured inputs for start/target analysis, improving user experience and AI suggestions.
- Incremented application version to 0.8.212 to reflect these changes.
2026-06-09 16:22:16 +02:00
fad1058d54 Enhance Progression Path Features with LLM Start/Target Extraction
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m14s
- Added `include_llm_start_target` option to `ProgressionPathSuggestRequest` for improved roadmap suggestions.
- Introduced new classes `StartTargetExtractArtifact` and `StartTargetResolveMeta` to handle LLM extraction results and metadata.
- Implemented `try_llm_start_target_extract` function to extract start and target states from goal queries using LLM.
- Updated `resolve_roadmap_structured_input` to prioritize user inputs, LLM extractions, and regex parsing for start/target resolution.
- Enhanced `ExerciseProgressionPathBuilder` to utilize new structured inputs and display extraction sources.
- Incremented application version to 0.8.211 to reflect these changes.
2026-06-09 12:54:08 +02:00
9dd44ce3ca Add Structured Roadmap Inputs and Enhance Goal Analysis Features
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
- Introduced `RoadmapStructuredInput` to encapsulate structured inputs for start situation, target state, and roadmap notes.
- Updated `ProgressionPathSuggestRequest` to include new fields for structured roadmap inputs.
- Implemented parsing logic for goal queries to extract start and target states, enhancing the goal analysis process.
- Enhanced `build_goal_analysis` to utilize structured inputs, improving the clarity and relevance of generated goals.
- Updated the `ExerciseProgressionPathBuilder` component to support new structured input fields, enhancing user experience.
- Incremented application version to 0.8.210 to reflect these changes.
2026-06-09 11:10:46 +02:00
87f258be38 Enhance Path QA with Roadmap-First Features and Gap Detection Improvements
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m15s
- Introduced `roadmap_qa_mode` to manage QA behavior based on roadmap-first logic, improving gap detection between major steps.
- Updated `detect_path_gaps` to skip gaps for roadmap-planned neighbor pairs, enhancing the accuracy of path assessments.
- Added new helper function `is_roadmap_planned_neighbor_pair` to facilitate roadmap neighbor checks.
- Updated relevant tests to validate new functionality and ensure robustness.
- Incremented application version to 0.8.209 to reflect these changes.
2026-06-09 10:17:30 +02:00
779e2477ba Implement Planning Context Integration for Exercise AI Suggestions
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m13s
- Added `planning_context` to the `suggestExerciseAi` endpoint, enabling structured planning context for new exercise creation.
- Updated relevant components and backend logic to handle the new planning context, enhancing the AI's exercise suggestion capabilities.
- Incremented application version to 0.8.208 to reflect these changes.
2026-06-08 15:15:03 +02:00
f074a8bef0 Implement Roadmap Review Features and Enhance Progression Path Management
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 47s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m13s
- Added support for editable major steps in the roadmap, allowing users to modify phase, learning goals, and order before exercise matching.
- Introduced a new `roadmap_override` feature to facilitate customized retrieval without re-invoking the roadmap AI.
- Updated the `ExerciseProgressionPathBuilder` component to incorporate these new features, enhancing user interaction and flexibility.
- Incremented application version to 0.8.207 to reflect these changes.
2026-06-08 14:59:24 +02:00
0677663268 Enhance Exercise Progression Path Management with Dynamic Step Capacity
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 46s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m13s
- Introduced logic to manage path capacity dynamically, allowing users to expand the maximum number of steps when inserting new offers.
- Implemented confirmation prompts for users when the path is full, enhancing user experience and decision-making.
- Updated the `ExerciseProgressionPathBuilder` component to reflect these changes, improving the handling of gap-fill offers and user interactions.
- Adjusted UI messages to clarify the implications of adding new steps and the conditions under which users can expand the path.
2026-06-08 14:51:15 +02:00
d4e9bded23 Implement Roadmap-First Retrieval and Enhance Planning AI Features
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m26s
- Introduced a roadmap-first approach for retrieval, allowing for structured exercise suggestions based on stage specifications and major steps.
- Added functionality to generate gap-fill offers for unfilled roadmap stages, improving the relevance of exercise recommendations.
- Updated the `ExerciseProgressionPathBuilder` to support the new roadmap-first feature, enhancing user experience with clearer exercise paths.
- Incremented application version to 0.8.206 and updated the database schema version to reflect these changes.
2026-06-08 12:40:17 +02:00
7411543a97 Enhance Planning AI with Roadmap-First Architecture and New Features
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 46s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 40s
Test Suite / playwright-tests (push) Successful in 1m14s
- Introduced a roadmap-first approach for the planning AI, allowing for a structured progression graph that aligns with the overall project roadmap.
- Updated the `ExerciseProgressionPathBuilder` to include a roadmap preview and improved handling of focus areas and skills catalog.
- Added functionality to strip off-topic steps from the exercise path, enhancing the relevance of generated paths.
- Implemented a new method to build detailed goal texts for AI-generated exercises, improving clarity and context.
- Incremented application version to 0.8.205 and updated database schema version to 20260606086 to reflect these changes.
2026-06-08 08:23:33 +02:00
dd0fae4bf5 Enhance Planning AI with Roadmap-First Architecture and New Features
Some checks failed
Deploy Development / deploy (push) Successful in 49s
Test Suite / pytest-backend (push) Failing after 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 15s
Test Suite / k6 /health Baseline (push) Successful in 44s
Test Suite / playwright-tests (push) Successful in 1m15s
- Introduced a roadmap-first approach for the planning AI, allowing for a structured progression graph that aligns with the overall project roadmap.
- Added new functionality to strip off-topic steps from exercise paths, improving the relevance of generated exercise suggestions.
- Implemented a detailed goal text generation for AI proposals, enhancing the context provided for new exercises.
- Updated the ExerciseProgressionPathBuilder component to support new features, including roadmap previews and improved focus area handling.
- Incremented application version to 0.8.205 and updated database schema version to 20260606086 to reflect these changes.
2026-06-08 08:10:53 +02:00
a9a6153ed5 Implement Club Feature Enforcement Logic and Update Versioning
Some checks failed
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Failing after 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m15s
- Introduced a new environment variable `CLUB_FEATURE_ENFORCE` to control club feature access, allowing values of 1, true, or yes for activation.
- Updated the backend logic to check for club feature enforcement, raising HTTP exceptions when access is denied without an active club context.
- Enhanced the admin rights router with a new endpoint to check the enforcement status of club features.
- Incremented application version to 0.8.202 to reflect these changes.
2026-06-07 15:47:49 +02:00
4130a63dfe Implement Registry-First Approach for Rights and Capabilities Management
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 42s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 35s
Test Suite / playwright-tests (push) Successful in 1m51s
- Updated the capability catalog to reflect a registry-first approach, requiring modules to register rights and quotas upon implementation.
- Enhanced the backend to synchronize the rights registry with the database, ensuring only registered capabilities and features are displayed in the admin matrix.
- Modified SQL queries in the admin rights router to filter capabilities and features based on module registration.
- Updated documentation to clarify the new rights and features registry process, replacing the previous catalog-first method.
- Incremented application version to 0.8.201 and updated database schema version to 20260606084 to reflect these changes.
2026-06-07 15:36:31 +02:00
9d52aeab67 Update Membership RBAC Decisions and Enhance Admin Rights Management
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m13s
- Updated the Membership RBAC Decisions document to reflect the latest implementation status and roadmap, including new features and enhancements.
- Incremented application version to 0.8.200 and updated database schema version to 20260606083.
- Added a new API endpoint to clear capability grants for club roles, improving admin rights management.
- Enhanced the Admin Rights page in the frontend to display enforcement status and feature consumption details for capabilities.
- Improved the user interface for better clarity on rights and capabilities management.
2026-06-07 15:27:37 +02:00
b68185842e Enhance Club Feature Consumption Logic and Update Versioning
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 47s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m40s
- Introduced the `consume_club_feature_with_usage` function to standardize feature consumption across endpoints, improving code reusability and clarity.
- Implemented `merge_feature_usage_into_response` to embed feature usage data in API responses, streamlining frontend integration.
- Updated various backend routers to utilize the new consumption logic, ensuring consistent feature usage tracking during AI-related actions.
- Enhanced tests to validate the new consumption and logging behavior.
- Incremented application version to 0.8.199 and updated module version for 'club_features' to 1.6.0 to reflect these changes.
2026-06-07 10:32:49 +02:00
40641594ac Add admin rights router to access layer hints exemption list
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 36s
Test Suite / playwright-tests (push) Successful in 1m20s
- Included "admin_rights.py" in the EXEMPT_ROUTERS frozenset to ensure proper access control for superadmin roles.
- This change enhances the management of admin capabilities and aligns with recent updates to the admin rights management system.
2026-06-07 09:24:16 +02:00
e4cb491d46 Refactor Admin Rights Management and Update Versioning
Some checks failed
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Failing after 1s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Has been cancelled
- Replaced the admin club feature exemptions router with a new admin rights router to streamline capability management.
- Added new API endpoints for managing admin rights, including capability grants and quota bypass for portal roles and profiles.
- Updated the frontend to include navigation and lazy loading for the new Admin Rights page.
- Incremented application version to 0.8.197 to reflect these changes and enhancements.
2026-06-07 09:21:59 +02:00
8404a42b6c Implement Club Feature Quota Bypass and Update Versioning
Some checks failed
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Failing after 2s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 42s
Test Suite / playwright-tests (push) Successful in 1m19s
- Added support for club feature quota bypass based on portal roles and profile grants in the capabilities check.
- Introduced new functions to handle quota bypass logic in club feature access and consumption.
- Updated the FeatureUsageBadge component to reflect platform exemptions for features.
- Incremented application version to 0.8.195 and database schema version to 20260606083 to reflect these changes.
- Enhanced backend routers to include new logic for consuming club features during AI-related actions.
2026-06-07 07:43:35 +02:00
fa10450315 Update Version and Enhance Club Creation Request Management
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m20s
- Incremented application version to 0.8.192 and database schema version to 20260606081.
- Updated club module versions for 'clubs' and 'club_creation_requests' to reflect recent changes.
- Implemented logic to mark approved club creation requests as 'superseded' when the associated club is deleted.
- Refactored frontend components to clear session storage for coach-related keys upon logout and during login checks.
- Enhanced onboarding page to accurately display the status of club creation requests based on their validity.
2026-06-07 07:31:05 +02:00
37785135b1 Refactor Org Inbox Context and Enhance Club Creation Management
Some checks failed
Deploy Development / deploy (push) Successful in 47s
Test Suite / pytest-backend (push) Successful in 42s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Failing after 1m21s
- Updated the OrgInboxContext to include handling for club creation requests, allowing for better management of inbox items.
- Refactored components to utilize the new `canShowInboxNav` and `canAccessClubCreationInbox` flags for improved access control.
- Enhanced the InboxPage to display club creation requests with appropriate actions for approval and rejection.
- Updated the DashboardOrgInboxWidget to show both club creation and join requests, improving the user interface for managing inbox items.
2026-06-07 07:18:43 +02:00
8ee8f52e0f Add Club Creation Request Management Features
Some checks failed
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 42s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Failing after 1m14s
- Introduced endpoints for managing club creation requests, including fetching, creating, and withdrawing requests.
- Updated the onboarding page to allow users to submit new club creation requests and view their existing requests.
- Enhanced the admin interface with navigation and routing for club creation requests management.
- Incremented version to 0.8.191 to reflect these new features and updates in the application.
2026-06-07 07:09:39 +02:00
8718cf5c70 Enhance Authentication and Feature Usage Handling
Some checks failed
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 42s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Failing after 1m15s
- Refactored the logout function in AuthContext to handle asynchronous logout operations, improving session management.
- Updated the FeatureUsageBadge component to display error messages when feature data retrieval fails, enhancing user feedback.
- Replaced lazy loading of OnboardingPage with lazyWithRetry for improved loading reliability.
- Adjusted the EntitlementsContext to determine club ID using utility functions for better governance form handling.
2026-06-07 07:05:02 +02:00
91dae7b614 Update Gitea Workflow to Restrict Test Triggers
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 41s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
- Modified the Gitea workflow configuration to trigger tests only on pushes and pull requests to the 'develop' branch, preventing duplicate test runs on the 'main' branch during merges.
- Added comments to clarify the purpose of the workflow triggers for better understanding.
2026-06-07 06:50:36 +02:00
20927a5969 Fix capability ID reference in migration script
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Successful in 41s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m15s
- Updated the SQL migration script to select the correct capability ID from the capabilities table, ensuring accurate role capability grants during database migrations.
2026-06-07 06:43:01 +02:00
7db77f4738 Improve Deployment Workflow and Database Migration Logic
Some checks failed
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Failing after 43s
Test Suite / pytest-backend (push) Failing after 31s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Has been cancelled
- Enhanced the deployment workflow to include error handling for the DEV API, ensuring logs are captured if the API is unreachable.
- Updated the migration scripts to safely rename existing tables by checking for their existence, preventing potential conflicts during migrations.
- Added exception handling in migration 079 to ensure the prerequisites are met before proceeding with the creation of the capabilities table.
2026-06-07 06:41:22 +02:00
3e87f7515a Enhance Backend Testing Workflow and API Onboarding Logic
Some checks failed
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Has been skipped
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / playwright-tests (push) Has been cancelled
Test Suite / k6 /health Baseline (push) Has been cancelled
- Updated the Gitea workflow to ensure backend tests run only on successful workflow runs or pull requests, improving CI reliability.
- Added a timeout mechanism to wait for the backend container to be ready before executing tests, enhancing test stability.
- Refactored the onboarding gate logic to include a middleware session lookup check, ensuring proper access control based on database state.
- Improved code readability and added comments for better understanding of the onboarding gate functionality.
2026-06-07 06:26:46 +02:00
a2f60d3f46 Update Capability Catalog and Club Membership Documentation
Some checks failed
Deploy Development / deploy (push) Successful in 49s
Test Suite / pytest-backend (push) Failing after 0s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 15s
Test Suite / playwright-tests (push) Has been cancelled
Test Suite / k6 /health Baseline (push) Has been cancelled
- Revised the status in the Capability Catalog to reflect partial implementation (M3).
- Added a new reference to `MEMBERSHIP_RBAC_DECISIONS_2026-06.md` in both the Capability Catalog and Club Membership documentation.
- Enhanced the Club Membership documentation with details on product decisions and onboarding phases.
- Implemented middleware in the backend to restrict access for unverified users and those pending club membership.
- Updated versioning in `version.py` to reflect changes in account lifecycle management.
2026-06-07 05:57:13 +02:00
30dc30c7aa Enhance Tenant Context and Access Control Features
Some checks failed
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Failing after 0s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Failing after 4m0s
Test Suite / playwright-tests (push) Failing after 3m41s
- Introduced `email_verified` and `account_state` attributes in the `TenantContext` to improve user state management.
- Updated the `resolve_tenant_context` function to dynamically fetch `email_verified` status from the database and determine `account_state` based on user roles and memberships.
- Implemented `assert_min_account_state` checks across various endpoints to enforce access control based on user account status.
- Incremented version to 1.1.0 in version.py to reflect these enhancements in tenant context management and access control.
2026-06-06 21:10:52 +02:00
7cfbca40bb Implement Club Feature Access Probing and Inventory Count
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m42s
- Introduced `probe_club_feature_access` to check club feature limits and log access attempts without blocking by default.
- Added `_live_inventory_count` function to retrieve current counts for specific features, enhancing feature limit management.
- Updated various endpoints to utilize the new probing functionality, ensuring compliance with club feature access rules.
- Incremented version to 1.1.0 in version.py to reflect these enhancements in club feature management.
2026-06-06 21:00:42 +02:00
c294c27de8 Update Access Layer and Governance Documentation
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m12s
- Enhanced the ACCESS_LAYER_AND_GOVERNANCE_PLAN.md with new specifications for capability documentation and community features.
- Added references to new documents detailing capability IDs and club membership features.
- Updated MULTI_TENANCY_RBAC_ARCHITECTURE.md to include links to the new specifications.
- Marked certain features as deprecated in backend/auth.py, indicating migration paths for club feature access.
- Incremented DB_SCHEMA_VERSION to 20260606078 in version.py to reflect recent changes.
2026-06-06 20:44:51 +02:00
bd5a409fa7 Add Admin User Content Management Features
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 15s
Test Suite / k6 /health Baseline (push) Successful in 35s
Test Suite / playwright-tests (push) Successful in 1m18s
Test Suite / pytest-backend (pull_request) Successful in 38s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 13s
Test Suite / k6 /health Baseline (pull_request) Successful in 38s
Test Suite / playwright-tests (pull_request) Successful in 1m12s
- Introduced a new admin user content management endpoint for superadmins, allowing for moderation of user-generated content.
- Updated the backend to include new API functions for retrieving, patching, and deleting user content items.
- Enhanced the frontend with a new Admin User Content page and navigation link for easy access to user content management.
- Updated access layer documentation to reflect the new endpoint and its exempt status.
- Incremented version to 0.8.191 and updated changelog to document these additions in admin functionality.
2026-06-06 17:53:25 +02:00
3450a9296a Enhance Planning Exercise Path AI and UI Integration
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m13s
- Updated the AI gap filling logic to include structured offers for unfilled gaps, improving the user experience in the Exercise Progression Path Builder.
- Introduced new functions for detecting off-topic steps and parsing LLM-suggested exercises, enhancing the contextual relevance of exercise suggestions.
- Enhanced the frontend components to support new AI proposal features, including quick creation modals for newly suggested exercises.
- Incremented version to 0.8.190 and updated changelog to reflect these improvements in planning AI functionality.
2026-05-23 12:59:46 +02:00
8d1dd59c3c Refactor Planning Exercise Path Logic and Enhance Semantic Gating
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 41s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m13s
Test Suite / pytest-backend (pull_request) Successful in 38s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 13s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m13s
- Replaced the manual path selection logic with a new `pick_best_path_hit` function to streamline the process of selecting the best exercise based on semantic scores and gating criteria.
- Updated the semantic gating logic to apply a soft penalty for off-topic exercises, improving the flexibility of exercise selection.
- Enhanced the handling of title, summary, and goal parameters in semantic checks to ensure more accurate relevance assessments.
- Incremented version to 0.8.189 and updated changelog to reflect these improvements in planning AI functionality.
2026-05-23 12:50:55 +02:00
5b73d1a1f5 Enhance Planning Exercise Path Builder and Retrieval Logic
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 41s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m23s
- Updated the path selection logic to incorporate semantic gating, ensuring only relevant exercises are considered based on semantic scores.
- Introduced new functions for building path target profiles and resolving semantic skill weights, enhancing the contextual understanding of exercise suggestions.
- Improved the retrieval process by applying dynamic retrieval weights based on semantic strength, refining the accuracy of exercise recommendations.
- Incremented version to 0.8.188 and updated changelog to document these enhancements in planning AI functionality.
2026-05-23 12:38:38 +02:00
c2c736dafc Implement Phase E2 Enhancements for Planning Exercise Suggestion
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 51s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m20s
- Introduced path reordering functionality using LLM with `ordered_step_indices`, allowing for dynamic adjustment of exercise progression paths.
- Added AI gap filling capabilities, enabling the system to propose new exercises when unbridgeable gaps are detected.
- Updated the backend to support new request parameters for path reordering and AI gap filling.
- Enhanced frontend components to reflect these new features, including alerts for AI proposals and adjustments in exercise display.
- Incremented version to 0.8.187 and updated changelog to document these significant enhancements in planning AI functionality.
2026-05-23 12:32:14 +02:00
c6b8c396ad Enhance Planning Exercise Retrieval and Suggestion with Semantic Features
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
- Introduced new functions to load exercise goals and variant names in chunks, improving data retrieval efficiency.
- Integrated semantic scoring into the ranking logic, allowing for more nuanced exercise suggestions based on semantic relevance.
- Updated the planning exercise suggestion process to include semantic brief handling, enriching the context for exercise recommendations.
- Adjusted the retrieval phase to incorporate dynamic retrieval weights based on semantic strength, enhancing the overall suggestion accuracy.
- Incremented version to 0.8.186 and updated changelog to reflect these significant enhancements in planning AI functionality.
2026-05-23 12:02:57 +02:00
a19ed02300 Implement Phase C3 Enhancements for Progression Path Suggestion
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m13s
- Incremented version to 0.8.185, reflecting the implementation of Phase C3 features.
- Introduced the `POST /api/planning/progression-path-suggest` endpoint for generating exercise progression paths.
- Enhanced the ExerciseProgressionGraphPanel with a new ExerciseProgressionPathBuilder for reviewing and saving paths.
- Updated changelog to document the new capabilities in planning AI functionality.
2026-05-23 11:46:25 +02:00
a34e748be5 Implement Phase C2 Enhancements for Exercise Suggestions
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 18s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
Test Suite / pytest-backend (pull_request) Successful in 38s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 13s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m13s
- Incremented version to 0.8.184, reflecting the implementation of Phase C2 features.
- Added support for displaying variant lists and suggested variant names in exercise suggestions.
- Enhanced the ExercisePickerModal to allow selection of exercise variants and improved handling of variant IDs.
- Updated backend logic to enrich planning hits with variant metadata, ensuring accurate exercise variant selection.
- Documented changes in the changelog to highlight the new capabilities in planning AI functionality.
2026-05-23 11:39:18 +02:00
b2157d8a40 Update Planning Exercise Suggestion and Context Handling
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m13s
Test Suite / pytest-backend (pull_request) Successful in 37s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 13s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m15s
- Incremented version to 0.8.183, reflecting the implementation of Phase C1 enhancements.
- Added support for progression graph auto-matching and variant-aware successors in exercise suggestions.
- Updated request and response structures to include `anchor_exercise_variant_id`, `progression_graph_name`, and `suggested_variant_id`.
- Enhanced frontend components to integrate planning AI search capabilities, including a new modal for exercise creation and improved context display in the exercise list.
- Updated changelog to document these significant improvements in planning AI functionality.
2026-05-23 10:42:17 +02:00
50aff849d8 Enhance Planning Exercise Suggestion and Ranking Logic
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m16s
- Introduced a new function `hybrid_ranking_ambiguous` to determine when to rerank candidates based on score proximity, improving the decision-making process for exercise suggestions.
- Updated `should_run_llm_rank_pipeline` to incorporate the new ranking logic and handle scenarios with ambiguous rankings more effectively.
- Adjusted the frontend to always include LLM ranking in requests, ensuring consistent behavior across different query lengths.
- Incremented version to 0.8.182 and updated changelog to reflect these enhancements in planning AI capabilities.
2026-05-23 10:28:03 +02:00
a0a891e550 Implement Phase B Enhancements for Planning Exercise Profiles
Some checks failed
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 41s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Has been cancelled
- Added support for section guidance notes and titles in the planning target profile, enabling richer context for exercise suggestions.
- Introduced deterministic text-to-catalog signal mapping, allowing for improved integration of planning text signals into the exercise retrieval process.
- Implemented a partner-related filter in exercise retrieval, enhancing the relevance of suggested exercises based on user intent.
- Updated the retrieval phase to account for text signals, improving the accuracy of exercise recommendations.
- Incremented version to 0.8.181 and updated changelog to reflect these significant enhancements in planning AI capabilities.
2026-05-23 10:26:03 +02:00
46fae3da33 Enhance Exercise Enrichment Admin Functionality and Update Documentation
All checks were successful
Deploy Development / deploy (push) Successful in 51s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m16s
Test Suite / pytest-backend (pull_request) Successful in 36s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 13s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m23s
- Implemented a maximum of 3 exercises per preview request to prevent Gateway-504 errors, improving the stability of the exercise enrichment process.
- Adjusted batch sizes for applying exercises and previewing to optimize performance and resource management.
- Updated the frontend to reflect changes in preview handling, including user notifications about chunk sizes and potential timeouts.
- Incremented version to 0.8.180 and updated changelog to document these enhancements and fixes.
2026-05-23 07:46:35 +02:00
f4196c3580 Add Exercise Enrichment Admin API and Update Documentation
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m17s
- Introduced the `exercise_enrichment_admin` API for batch exercise enrichment, allowing superadmins to filter candidates, preview, and apply skills.
- Updated the access layer documentation to include the new endpoint and its exempt status.
- Enhanced the frontend with a new admin page for exercise enrichment and updated navigation to include this feature.
- Incremented version to 0.8.179 and updated changelog to reflect these additions and improvements.
2026-05-23 07:35:45 +02:00
d1d8539b42 Refactor Planning Exercise Retrieval and Suggestion Logic
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m12s
- Updated the planning exercise retrieval process to implement a multistage approach, ranking the entire visible library deterministically against the expectation profile.
- Removed the previous profile OR pool mechanism, simplifying the retrieval logic and ensuring full-text search is only used as a scoring signal.
- Adjusted the `compose_retrieval_phase` function to accommodate the new full library ranking strategy.
- Incremented version to 0.8.177 and updated changelog to reflect these changes in planning exercise capabilities.
2026-05-23 06:35:45 +02:00
a8633235f2 Add ExerciseAiQuickCreateTeaser Component and Update ExercisePickerModal
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m17s
- Introduced the ExerciseAiQuickCreateTeaser component for a compact entry point in the exercise creation process.
- Updated ExercisePickerModal to integrate the new teaser, allowing users to expand and create exercises directly from the search results.
- Enhanced the quick create functionality with dynamic headlines and hints based on user input and context.
- Refactored conditional rendering logic to improve user experience when no exercises are found.
2026-05-23 06:16:37 +02:00
5c882985e0 Enhance Planning Exercise Functionality and LLM Integration
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m15s
- Added support for the new planning exercise expectation profile slug in the AI prompt runtime.
- Refactored SQL parameter handling in the planning exercise retrieval process to ensure correct binding for full-text search.
- Updated the planning exercise suggestion logic to incorporate LLM expectation handling, improving the accuracy of exercise recommendations.
- Introduced new functions to determine when to run the LLM expectation pipeline, enhancing the decision-making process for exercise suggestions.
- Incremented version to 0.8.176 and updated changelog to reflect these enhancements in planning AI capabilities.
2026-05-22 23:08:53 +02:00
04cc77d501 Enhance Planning Exercise Profiles and Context Handling
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
- Introduced new functions to generate skill profiles from exercise IDs, improving the ability to summarize skills for both units and sections.
- Updated the planning target profile to incorporate section-specific exercise IDs, allowing for more granular skill tracking and context.
- Enhanced the ExercisePickerModal and related pages to support section context, including titles, guidance notes, and exercise counts.
- Implemented expectation mode handling in the planning target pipeline to differentiate between planning references and query-only scenarios.
- Incremented version to 0.8.174 and updated changelog to reflect these enhancements in planning AI capabilities.
2026-05-22 23:00:31 +02:00
8e68261bc1 Refactor Planning Exercise Suggestion and Enhance LLM Integration
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m16s
- Replaced the previous exercise matching logic with a new multistage planning retrieval process, improving the accuracy of exercise suggestions.
- Introduced LLM gates to limit LLM calls based on query length and intent application, optimizing performance and resource usage.
- Updated the `compose_retrieval_phase` function to include profile preselection, enhancing the retrieval process.
- Incremented version to 0.5.0 and updated changelog to reflect these significant enhancements in planning AI capabilities.
2026-05-22 22:56:28 +02:00
b0611b9f7f Update ExercisePickerModal to Enforce Backend Suggestion Limit
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m17s
- Introduced a constant `PLANNING_SUGGEST_LIMIT` set to 50 to align with backend constraints for exercise suggestions.
- Updated the API request limit in `ExercisePickerModal` to utilize the new constant, ensuring compliance with backend specifications.
2026-05-22 22:46:00 +02:00
614c2dcfaa Enhance Planning Exercise Suggestion with Client Context and Group ID Support
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m16s
- Made `unit_id` and `group_id` optional in `PlanningExerciseSuggestRequest` to support client context without a saved unit.
- Refactored `_load_group_recent_exercise_ids` to handle cases where `exclude_unit_id` is optional.
- Introduced `build_client_planning_context_pack` for improved context handling in client-free searches.
- Updated `suggest_planning_exercises` to utilize the new client context pack when `unit_id` is not provided.
- Incremented version to 0.8.172 and updated changelog to reflect these enhancements in the planning AI capabilities.
2026-05-22 22:38:21 +02:00
f5c886fc13 Enhance ExercisePickerModal with Improved Planning Context Handling
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m39s
- Introduced `planningUnitId` and `expectPlanningSearch` props to better manage planning context for exercise suggestions.
- Refactored logic to resolve planning unit ID and construct active planning context, enhancing the accuracy of exercise suggestions.
- Implemented checks to block planning search when necessary, providing clearer user feedback in the UI.
- Updated `TrainingUnitEditPage` to pass the correct planning unit ID, ensuring seamless integration with the exercise picker.
2026-05-22 22:30:29 +02:00
d019c20338 Refactor ExercisePickerModal for Enhanced Search Functionality
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m16s
- Updated `effectivePickerQuery` logic to improve search handling based on planning context, allowing for a single input field in planning mode.
- Simplified query construction by utilizing `effectivePickerQuery` throughout the component, enhancing clarity and user experience.
- Adjusted UI elements and labels to better reflect the context of the search, providing clearer guidance for users.
- Modified `TrainingUnitEditPage` to ensure proper unit ID resolution, improving integration with the exercise picker.
2026-05-22 22:24:49 +02:00
905bce198f Refactor ExercisePickerModal to Utilize Effective Query for AI Suggestions
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m13s
- Introduced `effectivePickerQuery` to streamline search input handling, combining `debouncedSearch` and `debouncedAi` for improved query accuracy.
- Updated the `useExerciseAiQuickCreateFields` hook to use the new effective query, enhancing the quick create functionality.
- Modified conditional checks to utilize `effectivePickerQuery`, ensuring better user feedback based on search input.
- Improved placeholder text and labels for clarity in the search fields, enhancing user experience during exercise selection.
2026-05-22 22:21:06 +02:00
45e3b5f4f6 Implement Phase 1 of Planning Exercise Suggestion with Scenario Pipeline and LLM Intent Overlay
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m14s
- Introduced the Scenario Pipeline for planning exercises, allowing for more nuanced query handling and exercise suggestions based on user intent.
- Enhanced the `suggestPlanningExercises` API to include `include_llm_intent`, `scenario_kind`, and `query_intent_summary`, improving the context provided to the frontend.
- Updated the `ExercisePickerModal` to display new information related to query intent and scenario classification, enhancing user experience during exercise selection.
- Incremented application version to 0.8.171 and updated changelog to document the new features and improvements in the planning AI capabilities.
2026-05-22 22:15:19 +02:00
207817376d Enhance Planning Exercise Suggestion with LLM-Rerank and Client Overrides
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 47s
Test Suite / playwright-tests (push) Successful in 1m14s
- Implemented optional LLM-Rerank functionality in the planning exercise suggestion process, allowing for improved exercise ranking based on user-defined criteria.
- Updated the `suggestPlanningExercises` API to accept `planned_exercise_ids` for client-side overrides, enhancing flexibility in exercise selection.
- Enhanced the `ExercisePickerModal` to reflect LLM ranking status and support new planning context features.
- Incremented application version to 0.8.170 and updated changelog to document the new features and improvements in the planning AI capabilities.
2026-05-22 22:09:28 +02:00
128a9d752e Enhance Planning Exercise Suggestion Features and Update Application Version to 0.8.169
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
- Implemented Phase 1.1 of the planning exercise suggestion functionality, integrating `ExerciseMatchProfile` and `PlanningTargetProfile` for improved exercise scoring based on profile dimensions.
- Updated the `suggestPlanningExercises` API to include a new `retrieval_phase` and `target_profile_summary`, enhancing the context provided to the frontend.
- Enhanced the `ExercisePickerModal` to display additional information from the planning target profile, including focus areas and top skills, improving user experience during exercise selection.
- Incremented application version to 0.8.169 and updated changelog to reflect the new features and improvements in the planning AI capabilities.
2026-05-22 22:04:34 +02:00
d7d45a8927 Integrate Planning AI Features and Update Application Version to 0.8.167
Some checks failed
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Failing after 0s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Failing after 3m59s
Test Suite / playwright-tests (push) Failing after 3m41s
- Added new planning AI functionality with the introduction of the `suggestPlanningExercises` API endpoint for context-based exercise suggestions.
- Enhanced `ExercisePickerModal` to utilize planning context, allowing for a more tailored exercise selection experience.
- Updated `TrainingUnitEditPage` to pass planning context to the exercise picker, improving integration with the new planning features.
- Incremented application version to 0.8.167 and updated changelog to reflect the new planning AI capabilities and related enhancements.
2026-05-22 21:52:18 +02:00
9d880e2346 Enhance Exercise List Page with AI Assistant Toggle and UI Improvements
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 16s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m13s
Test Suite / pytest-backend (pull_request) Successful in 37s
Test Suite / lint-backend (pull_request) Successful in 1s
Test Suite / build-frontend (pull_request) Successful in 12s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m17s
- Introduced a new AI assistant toggle in the Exercise List Page header, allowing users to enable quick exercise creation via AI suggestions.
- Updated the ExerciseListSearchBar component to remove deprecated AI quick create functionality, streamlining the interface.
- Enhanced CSS styles for the AI assistant toggle, improving visual feedback and user interaction.
- Improved overall layout and spacing in the exercises page for better usability.
2026-05-22 19:44:15 +02:00
c816e50c68 Refactor Exercise Creation Components to Utilize Custom Hook for Quick Create Fields
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m20s
- Updated ExerciseAiQuickCreateOffer to set showSketchField to true by default and introduced sketchOptional prop for improved flexibility in exercise creation.
- Refactored ExercisePickerModal and ExercisesListPageRoot to leverage useExerciseAiQuickCreateFields hook, simplifying state management for quick create fields.
- Removed deprecated parsing logic and streamlined error handling for sketch input, enhancing user experience during exercise creation.
- Improved placeholder text and labels for clarity, ensuring better guidance for users when providing input for AI-generated exercises.
2026-05-22 19:36:22 +02:00
294740b780 Increment application version to 0.8.166 and update changelog for new features in AI exercise creation
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m15s
- Updated APP_VERSION to 0.8.166 and modified BUILD_DATE to reflect recent changes.
- Enhanced AI exercise creation process with a new quick create feature, allowing users to generate exercises based on search input.
- Introduced a rich text editor for editing AI-generated drafts, improving user experience in exercise creation.
- Updated ExercisePickerModal and related components to support the new quick create functionality, including error handling and input validation.
- Added new utility functions for parsing search queries and building exercise payloads from drafts.
2026-05-22 19:24:36 +02:00
675cfa85f0 Enhance AI Quick Create Functionality in ExercisePickerModal
All checks were successful
Deploy Development / deploy (push) Successful in 47s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m20s
- Updated the quick create process to include a preview feature for AI-generated exercises, allowing users to review goals, execution, preparation, and trainer notes.
- Introduced new constants for instruction fields and refactored the payload building function to utilize the preview data.
- Improved error handling to ensure at least one of the goal or execution fields is populated.
- Deprecated the previous payload building function in favor of the new preview-based approach, streamlining the exercise creation workflow.
2026-05-22 19:10:16 +02:00
4725eaa90b Increment application version to 0.8.164 and update changelog for new features in ExercisePickerModal
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m15s
- Updated APP_VERSION to 0.8.164 and added changelog entry for the new version.
- Enhanced ExercisePickerModal to support quick exercise creation using AI, including fields for sketch and focus area.
- Implemented error handling for AI suggestions and improved user prompts for input validation.
- Updated UI elements to reflect changes in exercise creation workflow.
2026-05-22 19:01:01 +02:00
9f4678f418 Implement exercise_instruction_rewrite for AI Prompt System
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 36s
Test Suite / playwright-tests (push) Successful in 1m16s
- Added `exercise_instruction_rewrite` functionality to enhance AI-generated instructions, incorporating fields for goal, execution, preparation, and trainer notes.
- Updated `ExerciseFormAiPromptContext` to include new fields and methods for instruction handling.
- Enhanced the `run_exercise_form_ai_suggestion` function to support instruction rewriting and validation.
- Modified API endpoints and frontend components to integrate instruction features, including a new button for AI instruction revision.
- Incremented application version to 0.8.163 and updated changelog to reflect these changes, including migration details and new functionality.
2026-05-22 18:53:36 +02:00
5331eab39c Implement ExerciseFormAiPromptContext and Refactor AI Prompt Job Functionality
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m23s
- Introduced `ExerciseFormAiPromptContext` for unified handling of prompt-related data, enhancing the admin preview and exercise API.
- Added `run_exercise_form_ai_suggestion` function to streamline AI suggestion processing, integrating with the OpenRouter.
- Updated various modules to utilize the new context model, improving code clarity and reducing redundancy.
- Incremented application version to 0.8.162 and updated changelog to reflect these changes, including migration details and new functionality.
2026-05-22 18:47:09 +02:00
93b8d09d05 Implement OpenRouter Model Support in AI Prompt System
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
- Added `openrouter_model` field to the `ai_prompts` table, allowing for optional model overrides per prompt.
- Updated the `exercise_ai` module to utilize the effective OpenRouter model based on prompt-specific settings, enhancing flexibility in AI interactions.
- Enhanced the admin interface to support OpenRouter model configuration for prompts, improving usability for Superadmins.
- Incremented application version to 0.8.161 and updated changelog to reflect these changes, including migration details and new functionality.
2026-05-22 12:37:43 +02:00
0551bb3d80 Refactor AI Prompt System and Enhance Functionality
All checks were successful
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m18s
- Introduced `load_and_render_ai_prompt` and `render_ai_prompt_template_for_row` in `ai_prompt_runtime` to streamline prompt loading and rendering processes.
- Added `AiPromptUnavailableError` for better error handling when prompts are inactive or missing.
- Created `ai_prompt_job` module with `ExerciseFormAiPromptContext` and `resolve_exercise_form_variables` to support admin preview functionality.
- Updated documentation and target architecture to reflect changes in the AI prompt system.
- Incremented application version to 0.8.160 and updated changelog accordingly.
2026-05-22 12:19:52 +02:00
e22266a18c Update ACCESS_LAYER_ENDPOINT_AUDIT and Matrix Editor Documentation
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m20s
Test Suite / pytest-backend (pull_request) Successful in 35s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 12s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m13s
- Added the `matrix_editor` endpoint to the ACCESS_LAYER_ENDPOINT_AUDIT.md, specifying its access requirements and exempt status for superadmins.
- Updated comments in the `matrix_editor.py` file to clarify its role as a superadmin tool and its access restrictions.
- Included the `matrix_editor.py` in the EXEMPT_ROUTERS list in the access layer hints script, ensuring proper access control documentation.
2026-05-22 11:54:59 +02:00
d58db3d5dd Enhance Maturity Matrix Tools and API Integration
Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Failing after 2s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m19s
- Added new functionality for exporting and importing matrix editor data in JSON and CSV formats within the MaturityMatrixToolsAdmin component.
- Updated the API utility functions to support matrix editor exports and imports, enhancing the backend communication for Superadmin tasks.
- Refactored the client API to streamline request handling and improve code clarity.
- Included new UI elements for file upload and download actions, improving user experience in managing matrix data.
2026-05-22 11:12:50 +02:00
cdeddc7cec Update AI Prompt System and Documentation
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 42s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m18s
- Added a new target architecture document for the AI Prompt System, detailing context types, composition, and planning phases.
- Refactored the backend to utilize a shared function for loading AI prompt rows, reducing SQL duplication in the `exercise_ai` module.
- Incremented the application version to 0.8.159 and updated the changelog to reflect these changes, including enhancements to the AI prompt management and documentation links.
2026-05-22 11:05:35 +02:00
2148d0aa7f Update AI Prompt System and Admin API
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m16s
- Incremented version to 1.1 and updated the status to reflect the implementation of core features including `ai_prompts`, `prompt_resolver`, and the Superadmin HTTP API.
- Documented the current API endpoints for managing AI prompts, including CRUD operations and preview functionality.
- Introduced a new placeholder catalog and preview capabilities for the Superadmin interface.
- Enhanced the backend with new functions for handling AI prompt templates and integrated them into the API.
- Updated frontend components to include navigation and routing for the new Admin AI Prompts page.
- Incremented application version to 0.8.158 and updated changelog to reflect these changes.
2026-05-22 11:02:02 +02:00
f9e295bce0 Remove AI suggestion preview dialog from ExerciseFormPageRoot component to streamline user interface and improve performance. This change eliminates unnecessary complexity in the component's rendering logic.
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 38s
Test Suite / playwright-tests (push) Successful in 1m30s
Test Suite / pytest-backend (pull_request) Successful in 35s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 12s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m31s
2026-05-22 10:29:49 +02:00
888d0bd009 Update environment configuration for AI debugging support
All checks were successful
Deploy Development / deploy (push) Successful in 48s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m16s
- Added `SHINKAN_AI_DEBUG` environment variable to `.env.example`, enabling detailed logging for AI operations in Docker containers.
- Integrated `SHINKAN_AI_DEBUG` into both `docker-compose.dev-env.yml` and `docker-compose.yml` to facilitate debugging during development and production environments.
2026-05-22 10:20:16 +02:00
1942585546 Enhance exercise_ai and openrouter_chat modules with AI debugging and improved content handling
Some checks failed
Test Suite / lint-backend (push) Waiting to run
Test Suite / build-frontend (push) Waiting to run
Test Suite / k6 /health Baseline (push) Waiting to run
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Has been cancelled
- Introduced detailed logging for AI operations in the `exercise_ai` and `openrouter_chat` modules, activated by the `SHINKAN_AI_DEBUG` environment variable, to aid in debugging and performance monitoring.
- Updated the `run_exercise_ai_suggestion` function to log prompt lengths, response sizes, and JSON parsing errors, enhancing transparency in AI interactions.
- Improved the `_flatten_message_content` function to handle nested content structures more effectively, ensuring compatibility with various AI response formats.
- Incremented the application version to 0.8.157 and updated the changelog to reflect these enhancements, including new logging features and content handling improvements.
2026-05-22 10:19:31 +02:00
a28a9d399a Enhance exercise_ai and openrouter_chat modules with improved JSON handling and error management
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m18s
- Added a new function `_first_balanced_json_array` to extract the first complete top-level JSON array from arbitrary text, enhancing robustness in parsing.
- Updated the `run_exercise_ai_suggestion` function to raise clear HTTP exceptions for empty responses from the OpenRouter, ensuring better error handling.
- Introduced `_flatten_message_content` in the `openrouter_chat` module to handle structured message content from OpenAI, improving compatibility with various content formats.
- Incremented the application version to 0.8.156 and updated the changelog to reflect these enhancements, including improved error messages and JSON parsing capabilities.
2026-05-22 10:09:07 +02:00
9be69ace5c Enhance exercise_ai module with skill input sanitization and version update
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m16s
- Introduced a new constant `_MAX_SANITIZE_SKILL_INPUT_ROWS` to limit the number of skill entries processed, improving performance and preventing issues with excessively long skill arrays.
- Updated the `_extract_json_array` and `_sanitize_skill_entries` functions to enforce this limit, ensuring that only a maximum of 250 skill entries are handled and that processing stops after 5 valid entries.
- Incremented the application version to 0.8.155 and updated the changelog to reflect these changes, including a note on the improvements made to the AI endpoint for skill arrays.
2026-05-22 09:59:56 +02:00
286c36e9d7 Document Superadmin API for AI Skill Retrieval Profiles and Update Access Layer
Some checks failed
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 43s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Has been cancelled
- Added documentation for the new Superadmin CRUD endpoints for managing AI Skill Retrieval Profiles (`/api/admin/ai-skill-retrieval-profiles*`).
- Updated the ACCESS_LAYER_ENDPOINT_AUDIT.md to include the new Superadmin API and its exempt status.
- Registered the ai_skill_retrieval_admin router in the backend and updated versioning to reflect the changes.
- Enhanced the frontend with a new Admin page for AI Skill Retrieval, including navigation and API integration for profile management.
2026-05-22 09:57:39 +02:00
294b09a5d9 Implement AI Skill Retrieval Profiles and Enhance Exercise AI Functionality
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m13s
- Introduced migration 068 for `ai_skill_retrieval_profiles`, enabling configurable weights and quotes for skill catalog prioritization in exercise AI suggestions.
- Updated the `POST /api/exercises/ai/suggest` endpoint to include an optional `focus_areas_context` field, allowing for enhanced context in AI-generated suggestions.
- Enhanced the `exercise_ai` module to utilize context-based skill selection, incorporating scoring, category caps, and keyword patches for improved AI responses.
- Updated the ExerciseFormPageRoot component to pass focus area context to the AI suggestion API, streamlining user interaction with AI-generated content.
- Incremented version numbers in `backend/version.py` to reflect the latest changes and ensure accurate tracking in the changelog.
2026-05-22 09:49:08 +02:00
e5291256d0 Enhance AI Exercise Suggestion Functionality and UX
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 38s
Test Suite / playwright-tests (push) Successful in 1m21s
- Updated the AI Exercise Implementation Plan to include a detailed description of the new suggestion dialog for AI proposals, allowing users to preview and selectively adopt AI-generated summaries and skills.
- Implemented a new preview feature in the ExerciseFormPageRoot component, enabling users to review AI suggestions before applying them to the form.
- Enhanced the skill management process by normalizing AI-suggested skills and integrating them into the exercise form, improving user interaction and data handling.
2026-05-22 09:21:44 +02:00
4d36bbf634 Update AI Training Planning Document and Versioning
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 42s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m13s
- Incremented the version number from 0.2 to 0.3 in the AI Training Planning document to reflect the latest changes.
- Added a new reference to the `working/AI_PLANNING_KI_MULTISTAGE_FORECAST.md` document, outlining the architecture preview for the planning AI.
- Updated the changelog in `backend/version.py` to include the latest version entry, ensuring accurate tracking of changes.
2026-05-22 07:56:56 +02:00
e4451e1362 Enhance Exercise Management and AI Integration
Some checks failed
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Failing after 1s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Has been cancelled
- Updated the exercise form to include a tabbed navigation structure, improving user experience with sections for Stammdaten, Anleitung, Einordnung, Varianten, and Medien & Mehr.
- Introduced the concept of **Freigabelevel** (visibility level) in the UI, replacing previous terminology for clarity and consistency across components.
- Implemented new AI endpoints for exercise suggestions and regeneration, allowing for dynamic content generation without direct database writes.
- Removed the legacy `is_primary` flag from exercise skills in the UI, ensuring that intensity levels (`niedrig`, `mittel`, `hoch`) are the primary focus for skill management.
- Enhanced the variant management process with improved saving mechanisms and UI updates to reflect changes more intuitively.
2026-05-22 07:52:31 +02:00
7245bbb1da bug_fix Variantenerzeugung
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m35s
Test Suite / pytest-backend (pull_request) Successful in 36s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 13s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m22s
2026-05-22 07:14:03 +02:00
5f67c01cef Refactor Exercise Visibility Labels for Consistency Across Components
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 42s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m17s
- Replaced hardcoded visibility labels with the new constant EXERCISE_VISIBILITY_FIELD_LABEL in multiple components, ensuring consistent terminology throughout the application.
- Updated UI text to reflect the change from "Sichtbarkeit" to "Freigabelevel" in various contexts, enhancing clarity for users.
- Improved accessibility by standardizing the visibility-related labels in the ExercisePickerModal, ExerciseProgressionGraphPanel, and other related components.
2026-05-21 15:08:35 +02:00
13a1d3a060 Enhance Exercise Form UI and Functionality with New Tab Structure
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m12s
Test Suite / pytest-backend (pull_request) Successful in 35s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 15s
Test Suite / k6 /health Baseline (pull_request) Successful in 34s
Test Suite / playwright-tests (pull_request) Successful in 1m15s
- Introduced a tabbed interface for the exercise form, allowing users to navigate between different sections (Stammdaten, Anleitung, Einordnung, etc.) more intuitively.
- Added new CSS styles for the exercise form, including improved layout and visual differentiation for various sections.
- Implemented dynamic tab management based on exercise type and edit state, enhancing user experience during form interactions.
- Refactored existing components to integrate the new tab structure, ensuring a cohesive design and functionality across the exercise form.
2026-05-21 14:58:14 +02:00
7f62b6ceee Enhance Exercise Form Functionality with Variant Management Improvements
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m22s
- Introduced snapshot and dirty check functions for variant payloads, enabling better tracking of unsaved changes.
- Implemented synchronization of saved variant snapshots to improve data integrity during edits.
- Enhanced the variant saving process with validation for variant names and automatic saving of changes.
- Updated the UI to reflect changes in variant management, ensuring a smoother user experience when editing exercise variants.
2026-05-21 14:54:32 +02:00
9b3f594007 Implement Exercise Form Enhancements with New Meta Panel
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
- Added a new meta panel for exercise classification and target groups, improving the organization of exercise attributes.
- Introduced ExerciseCatalogAssocEditor component to manage focus areas, training styles, and target groups, enhancing user interaction.
- Refactored CSS styles for the new meta panel and associated components, ensuring a cohesive design and improved responsiveness.
- Removed the MultiAssocBlock component to streamline the code and improve maintainability.
2026-05-21 14:40:50 +02:00
5d308b20ba Add VARIANT_DIFFICULTY constant to ExerciseFormPageRoot component
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m15s
- Introduced a new constant, VARIANT_DIFFICULTY, to define difficulty options for exercises.
- Improved code organization by separating the import statements for better readability.
2026-05-21 12:58:18 +02:00
1d698e4b0a Implement Phase 3 Enhancements for Skill Scoring and Profiles
Some checks failed
Deploy Development / deploy (push) Failing after 22s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Failing after 3s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
- Added capabilities for weighted skill profiles, allowing trainers to compare training modules, frameworks, and regression paths based on skill contributions.
- Updated the skill scoring specification to include peer context separation and list filtering, ensuring accurate comparisons among visible artifacts of the same type.
- Enhanced the API to support batch summaries for skill profiles and discovery suggestions, improving data retrieval efficiency.
- Refactored frontend components to display skill metrics, including scores and peer percentages, with improved filtering options for better user experience.
- Updated documentation to reflect the latest changes and enhancements in the skill scoring system.
2026-05-21 12:35:45 +02:00
a7a428745f Enhance SkillTreeMultiSelect Component and CSS Styles
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 48s
Test Suite / playwright-tests (push) Successful in 1m28s
Test Suite / pytest-backend (pull_request) Successful in 35s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 12s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m16s
- Updated the SkillTreeMultiSelect component to support dynamic positioning and improved accessibility through the use of portals.
- Refactored the dropdown panel rendering logic to enhance user experience when selecting skills.
- Added CSS styles for the exercise filter modal to improve layout and responsiveness.
- Introduced new styles for the skill tree multiselect panel, ensuring better visual integration and usability.
2026-05-21 10:31:21 +02:00
2d187447bb Enhance Framework Programs Filtering and UI Components
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
- Updated the FrameworkProgramsFilterBlock to include a search input and filter modal, improving user interaction and accessibility.
- Refactored CSS styles for filter components to ensure consistent layout and spacing.
- Removed deprecated panel open state management, streamlining the component logic.
- Integrated new filtering capabilities in the TrainingPlanningFrameworkImportModal and TrainingModulesListPage, enhancing the overall filtering experience.
- Improved the display of active filters and results count, providing clearer feedback to users.
2026-05-21 10:23:05 +02:00
2de4c0b7c9 Refactor Skill Scoring Functions and Enhance Corpus Handling
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m17s
- Introduced new helper functions for managing artifact type corpus, improving code organization and readability.
- Updated the `compute_club_corpus_reference` function to utilize the new corpus handling methods, enhancing clarity and maintainability.
- Refactored skill profile functions to leverage the new corpus structure, ensuring consistent data retrieval across different artifact types.
- Improved the handling of visibility clauses for library content, streamlining database queries for skill profiles.
- Enhanced the batch skill profile summary function to aggregate reference data by artifact type, improving performance and accuracy.
2026-05-21 10:17:22 +02:00
34966b9e84 Update Skill Profile Summary and Frontend Components
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m13s
- Modified the `compact_profile_summary` function to allow for dynamic skill and category limits, enhancing flexibility in profile data retrieval.
- Updated frontend components to display skill weights and scores more effectively, improving user interaction with skill metrics.
- Adjusted CSS styles for skill KPI tiles to better differentiate between score and percentage displays, ensuring a clearer visual representation.
- Refactored utility functions to streamline skill summary handling, enhancing overall code maintainability and performance.
2026-05-21 09:42:13 +02:00
9a0cf7f823 Enhance Skill Scoring and Profile Functionality
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 42s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m15s
- Introduced a new function to calculate club-specific skill percentages, ensuring values are capped at 100%.
- Updated skill profile calculations to include indicators for the best club performance per skill.
- Enhanced frontend components to display club best indicators and improved layout for skill profiles.
- Refactored CSS styles for skill profile components, ensuring a more cohesive and user-friendly interface.
- Updated tests to validate new functionality and ensure accurate representation of skill metrics.
2026-05-21 09:33:20 +02:00
78c6c51520 Enhance Skill Scoring and Profile Features
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 42s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m15s
- Updated the skill scoring specification to include club-specific metrics and improved aggregation methods for skill profiles.
- Introduced new API endpoints for batch skill profile summaries, allowing for efficient retrieval of compact skill data.
- Enhanced frontend components to display skill profiles with club comparisons, improving user interaction and visibility of skill strengths.
- Added filtering options for skills in the framework programs, enabling users to refine selections based on training weight relative to club maximums.
- Improved CSS styles for skill profile displays, ensuring a cohesive and user-friendly interface across the application.
2026-05-21 09:05:13 +02:00
5200895a73 Update Skill Scoring Specification and Implementation to v1.2
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m27s
- Enhanced the skill scoring system with category grouping and a universal scale for improved comparability across programs.
- Introduced new calculations for artifact share percentage and universal percent, allowing for a more nuanced understanding of skill contributions.
- Updated the API to reflect changes in the skill profile structure, including main category and top skill details.
- Improved frontend components to display skills by main category, enhancing user experience in skill discovery and profile visualization.
- Adjusted tests to validate the new scoring logic and ensure accurate representation of skills and their weights.
2026-05-21 08:37:58 +02:00
8f8bdf6d8b Update Skill Scoring Specification and Implementation to v1.1
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m21s
- Enhanced the skill scoring formula to incorporate intensity and level range factors, improving the accuracy of skill contributions.
- Removed the use of `is_primary` and `development_contribution` from calculations, streamlining the scoring process.
- Updated documentation to reflect changes in the scoring logic and versioning.
- Adjusted frontend components to align with the new scoring criteria, ensuring consistent user experience across the application.
2026-05-21 08:24:23 +02:00
f67bf280c3 Add Skill Profiles API Integration for Phase 3
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 35s
Test Suite / playwright-tests (push) Successful in 1m21s
2026-05-21 08:11:46 +02:00
732b322c52 Implement Phase 3 Features for Skill Profiles and Discovery
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m27s
- Updated the framework program documentation to reflect the completion of Phase 3 v1.0, including new skill scoring and API enhancements.
- Added new API endpoints for skill profile retrieval and suggestions, improving the ability to aggregate and display skills based on training data.
- Introduced new UI components for skill profiles and discovery in the frontend, enhancing user interaction with training frameworks and skills.
- Updated version information to 0.8.151, reflecting the addition of skill profiles and related features.
2026-05-20 16:42:25 +02:00
e382b6ed35 Enhance Training Framework Program Edit Page with New Styling and Structure
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m16s
Test Suite / pytest-backend (pull_request) Successful in 36s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 12s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m16s
- Introduced a new PageFormEditorChrome component for improved layout and user experience.
- Updated CSS styles for the framework editor, including danger zone and delete button hover effects.
- Refactored the TrainingFrameworkProgramEditPage to utilize the new component and streamline the action configuration.
- Enhanced the page title handling for better clarity when creating or editing framework programs.
2026-05-20 16:23:10 +02:00
a4548f5587 Refactor Training Framework Programs List Page with Enhanced Styling and New Utility Functions
Some checks failed
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Has been cancelled
- Updated the TrainingFrameworkProgramsListPage to utilize new CSS classes for improved layout and styling.
- Removed deprecated components and functions, streamlining the codebase for better maintainability.
- Introduced utility functions for splitting aggregated strings, enhancing data handling for framework program attributes.
- Enhanced the user interface with loading and empty state indicators, improving overall user experience.
2026-05-20 16:21:16 +02:00
9d122d4808 Enhance Training Framework Programs List with Filtering and Styling Improvements
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m20s
- Introduced a new filter block for training framework programs, allowing users to refine their selections based on focus areas, training types, and target groups.
- Updated the TrainingFrameworkProgramsListPage to integrate the filter block and manage filter states effectively.
- Enhanced CSS styles for the filter block and program list, improving layout and spacing for better user experience.
- Removed unused filter-related logic from the TrainingPlanningFrameworkImportModal, streamlining the component's functionality.
2026-05-20 16:05:18 +02:00
9c3494a7ea Implement Framework Import Modal with Enhanced Filtering and Styling
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m27s
- Introduced a new modal for importing training framework sessions, featuring a backdrop and panel for improved user experience.
- Added comprehensive filtering options for focus areas, training types, and target groups, utilizing a structured filter state.
- Enhanced session duration handling with distinct duration collection and display, improving clarity in program selection.
- Updated utility functions to support new filtering capabilities and session duration management, ensuring a cohesive user interface.
2026-05-20 15:06:03 +02:00
9353909fda Enhance Training Framework Programs with Session Duration and Filtering Features
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m29s
- Added SQL aggregations for session duration (min/max) and goal titles in the training framework programs query.
- Updated the TrainingPlanningFrameworkImportModal component to include filtering options for focus areas, training types, and target groups.
- Implemented session duration display in the TrainingFrameworkProgramsListPage, improving user visibility of program details.
- Introduced utility functions for formatting session duration ranges, enhancing the overall user experience in training planning.
2026-05-20 13:19:40 +02:00
5a8a212f40 Update version information and add training duration features
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m22s
- Bumped application version to 0.8.150 and updated build date and database schema version.
- Introduced new SQL migration for planned duration fields in training units and sections.
- Added functions to handle focus areas and style directions in training framework programs.
- Enhanced training planning components to support planned duration input and display.
- Updated frontend components to manage and display planned duration for training units and sections.
2026-05-20 13:02:09 +02:00
ab612a5335 Update Skill Tree Picker and Catalog Functions for Clarity
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m15s
Test Suite / pytest-backend (pull_request) Successful in 35s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 12s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m14s
- Modified comments in `SkillTreePickerPanel` and `skillCatalogTree.js` to accurately reflect the default expansion behavior, specifying that only main groups are open by default.
- Refactored `defaultExpandedKeysForSkillTree` to simplify the logic, ensuring only main groups are returned as expanded.
- Adjusted tests in `skillCatalogTree.test.js` to validate the updated behavior, confirming that only main groups are opened in the skill tree.
2026-05-20 11:21:18 +02:00
b2f77ca627 Refactor Skill Tree Picker and Catalog Functions for Clarity and Efficiency
Some checks failed
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Has been cancelled
- Simplified the rendering of skill group labels in `SkillTreePickerPanel` for improved readability.
- Updated comments in `skillCatalogTree.js` to clarify the structure of expandable nodes and default expansion behavior.
- Refactored the skill catalog tree building logic to streamline the mapping of categories and skills, enhancing performance and maintainability.
- Adjusted tests in `skillCatalogTree.test.js` to reflect changes in the structure of skill nodes, ensuring accurate validation of functionality.
2026-05-20 11:18:34 +02:00
39b1fd04f0 Enhance Skills Catalog Admin with Unassigned Skill Handling and Improved Selection Logic
Some checks failed
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Has been cancelled
- Introduced utility functions to count skills without main categories and categories, enhancing data management in the Skills Catalog Admin.
- Updated the SkillsCatalogAdmin component to handle unassigned main and category IDs, improving user experience when managing skills.
- Refactored skill selection logic to utilize new utility functions, ensuring accurate filtering of skills based on selected categories and main categories.
- Enhanced the UI to display unassigned skills clearly, improving overall usability and clarity in skill management.
2026-05-20 11:16:49 +02:00
9020e5eb16 Enhance Skill Tree Components with Improved Group Labeling and Default Expansion
Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Has been cancelled
- Added CSS styles for skill group labels in the skill tree to improve visual hierarchy and readability.
- Updated `SkillTreePickerPanel` and `SkillTreeMultiSelect` components to utilize the new default expansion logic, ensuring main and category nodes are open by default while skill groups remain collapsed.
- Refactored state management in `SkillTreePickerPanel` to align with the new default expansion behavior.
- Enhanced utility functions to support the new default expansion logic for skill trees.
2026-05-20 11:13:34 +02:00
46feb4c867 Add defaultCollapsed prop to SkillTree components for improved user experience
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 41s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m15s
- Introduced a `defaultCollapsed` prop in `SkillTreeMultiSelect` and `SkillTreePickerPanel` to control the initial expansion state of skill trees.
- Updated `SkillTreeSelect` to accept a `defaultCollapsed` prop, enhancing flexibility in component usage.
- Adjusted state management in `SkillTreePickerPanel` to respect the `defaultCollapsed` setting, improving user interaction with skill selection.
2026-05-20 11:05:27 +02:00
3067b2e6a8 Implement Skill Tree Selection Components and Update API Calls
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m15s
- Introduced new `SkillTreeSelect` and `SkillTreeMultiSelect` components for enhanced skill selection in various modals and forms.
- Updated API calls to use `listSkillsCatalog` instead of `listSkills` for improved data retrieval.
- Enhanced CSS styles for skill selection components to improve user experience and visual consistency across the application.
2026-05-20 10:59:17 +02:00
728b37ad5f Export PLANNING_HUB_PATH from navReturnContext.js to enhance navigation context management.
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m18s
Test Suite / pytest-backend (pull_request) Successful in 35s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 13s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m14s
2026-05-20 08:01:53 +02:00
8afdd811db Refactor full-page editor navigation and update return context handling
Some checks failed
Deploy Development / deploy (push) Failing after 23s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Failing after 7s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m19s
- Adjusted `PageFormEditorChrome` to set `showReturn` to false by default, making the back button optional.
- Removed `PageReturnButton` from `TrainingFrameworkProgramEditPage`, `TrainingModuleEditPage`, and `TrainingPlanTemplateEditPage` to streamline navigation.
- Updated documentation to reflect changes in editor actions and return context behavior for improved clarity.
2026-05-20 07:56:22 +02:00
4588ef4c7e Refactor navigation components and enhance return context handling
Some checks failed
Deploy Development / deploy (push) Failing after 24s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Failing after 7s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m22s
- Replaced `PageReturnLink` with `PageReturnButton` for consistent back navigation across various pages.
- Updated multiple components, including `ExercisePeekModal`, `PageFormEditorChrome`, and `ExerciseDetailPage`, to utilize the new return context features.
- Enhanced CSS styles for the new return button to improve visual consistency.
- Improved navigation logic in `TrainingFrameworkProgramEditPage` and `TrainingModuleEditPage` to ensure seamless user experience when navigating back to previous locations.
2026-05-20 07:42:46 +02:00
6e6270b717 Enhance navigation and return context in exercise and training module components
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m17s
- Introduced a new `PageReturnLink` component for consistent back navigation across pages.
- Updated `SaveSelectedExercisesAsModuleModal` and `SaveExercisesAsModuleModal` to utilize `navigateWithAppReturn`, preserving navigation context when redirecting after saving.
- Enhanced `TrainingModuleEditPage` and `TrainingUnitEditPage` with improved return context handling, allowing users to navigate back to their previous locations seamlessly.
- Added CSS styles for the new return link to improve visual consistency and user experience.
2026-05-20 07:25:05 +02:00
14b005e9b8 Enhance exercise selection and display features
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 41s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m18s
- Introduced new CSS styles for exercise cards and selection sections, improving visual feedback for selected exercises.
- Updated ExerciseListCard to support a new `selectionPinned` prop, allowing for a badge display on selected exercises.
- Refactored selection handling in ExercisesListPageRoot to manage selected entries more effectively, replacing the previous Set-based approach.
- Enhanced SaveSelectedExercisesAsModuleModal to support appending exercises to existing modules, improving module management capabilities.
- Updated session state handling to include selected entries, ensuring persistence across sessions.
2026-05-20 07:03:15 +02:00
ef4dd93324 Enhance exercise card interactivity and toolbar functionality
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 41s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m20s
- Added clickable behavior to exercise card body, allowing users to navigate to exercise details by clicking anywhere on the card.
- Introduced keyboard accessibility for exercise cards, enabling navigation via Enter and Space keys.
- Updated ExerciseListBulkToolbar to include a new button for saving selected exercises as a module, enhancing bulk action capabilities.
- Improved CSS styles for clickable exercise card body to indicate interactivity.
2026-05-20 06:56:58 +02:00
e50c18f92e Enhance ExerciseFormPageRoot with save and close functionality
All checks were successful
Deploy Development / deploy (push) Successful in 47s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m14s
Test Suite / pytest-backend (pull_request) Successful in 35s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 12s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m13s
- Added a new `handleSaveAndClose` function to allow users to save and navigate back to the exercise list.
- Updated `performSaveAttempt` to accept a `closeAfter` parameter for conditional navigation.
- Refactored form submission handling to include separate actions for saving and saving with closure.
- Integrated `PageFormEditorChrome` for improved layout and user experience, including a back navigation option.
2026-05-20 06:38:53 +02:00
d19a1061d8 Enhance exercise listing with variant and media counts
Some checks failed
Test Suite / lint-backend (push) Waiting to run
Test Suite / build-frontend (push) Waiting to run
Test Suite / k6 /health Baseline (push) Waiting to run
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Has been cancelled
- Updated the `list_exercises` function to include counts for exercise variants and media, improving data retrieval for exercise details.
- Added new CSS styles for the exercise card footer to display variant and media statistics in a visually appealing manner.
- Implemented `ExerciseCardContentStats` component to conditionally render variant and media counts, enhancing the user interface of exercise cards.
2026-05-20 06:37:40 +02:00
cb868373f4 Enhance PlanningLayout and TrainingUnitEditPage with unsaved changes handling
All checks were successful
Deploy Development / deploy (push) Successful in 47s
Test Suite / pytest-backend (push) Successful in 41s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m16s
Test Suite / pytest-backend (pull_request) Successful in 36s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 13s
Test Suite / k6 /health Baseline (pull_request) Successful in 34s
Test Suite / playwright-tests (pull_request) Successful in 1m13s
- Updated PlanningLayout to conditionally render the PlanningRouteNav based on the current path, improving navigation for planning unit editors.
- Enhanced TrainingUnitEditPage with unsaved changes detection, integrating a prompt for users to confirm before leaving the page with unsaved changes.
- Introduced utility functions for creating a stable snapshot of form data to facilitate dirty-checking, ensuring better user experience during form editing.
- Added tests for the new utility functions to validate their behavior in various scenarios.
2026-05-19 14:39:46 +02:00
472cf1afdb Refactor full-page editor layout and enhance mobile dock functionality
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m20s
- Updated CSS styles for the full-page editor, modifying the header to include a back link and title, while fixing the action dock for all viewports.
- Removed the sticky header and integrated the action bar at the bottom for improved usability across devices.
- Simplified the PageFormEditorChrome component by eliminating unnecessary memoization and restructuring the header layout.
2026-05-19 12:55:08 +02:00
0cb0e81d27 Add AuthContext to App component for authentication management
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m21s
2026-05-19 12:45:08 +02:00
6a9351874f Enhance full-page editor layout and integrate form editor actions
Some checks failed
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Failing after 6m4s
- Updated CSS styles for the full-page editor, introducing a sticky header and mobile dock for improved navigation.
- Refactored App component to include FormEditorActionsProvider and FormEditorBottomSlot for better form handling.
- Simplified TrainingUnitFormShell by removing the FormActionBar, streamlining the form structure and enhancing usability.
2026-05-19 11:18:54 +02:00
734d943d73 Implement full-page planning unit editor layout and enhance form behavior
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m13s
- Added new CSS styles for the planning unit editor to manage scrolling and layout, ensuring a responsive design.
- Updated the TrainingUnitFormShell component to include new class names for better styling and structure.
- Modified the TrainingUnitEditPage to support the new layout, improving user experience when editing training units.
2026-05-19 11:13:18 +02:00
16eaf839e7 Enhance frontend testing setup and refactor TrainingPlanningPageRoot component
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 19s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m21s
- Added Vitest as a testing framework and included test scripts in package.json for improved testing capabilities.
- Refactored TrainingPlanningPageRoot component by removing unused state variables and imports, streamlining the code for better readability and performance.
- Introduced new utility functions for planning routes to enhance navigation within the training planning interface.
2026-05-19 11:02:03 +02:00
295c7e7efc Refactor modal components to use FormModalOverlay for improved consistency and functionality
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m9s
- Replaced div elements with FormModalOverlay in SaveExercisesAsModuleModal, TrainingPlanningUnitFormModal, and TrainingPublishToFrameworkModal for a unified modal structure.
- Enhanced modal styling and behavior, including adjustments for responsiveness and accessibility.
- Introduced new CSS rules to manage overflow and scrolling behavior when modals are active, improving user experience across devices.
2026-05-19 10:47:44 +02:00
c9175bd2fd Refactor modal components and enhance FormActionBar functionality
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
- Updated modal components to utilize a consistent overlay and panel structure, improving layout and responsiveness.
- Enhanced FormActionBar to support short labels for action buttons, improving usability in mobile views.
- Introduced new styling for action buttons and modal titles, ensuring better alignment and visual consistency across forms.
- Improved accessibility by adding aria-labels and titles to buttons for better screen reader support.
2026-05-19 10:20:49 +02:00
f15aa7c415 Update version to 0.8.148 and enhance training plan template functionality
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m7s
- Incremented app version to 0.8.148 and updated changelog to reflect new features.
- Improved the training plan template structure by adding a preview of sections, including support for split sessions.
- Introduced a new editing page for training plan templates, allowing users to modify templates directly.
- Enhanced the TrainingPlanningPageRoot to include a description field when saving templates, improving user guidance.
- Updated permissions to allow editing of training plan templates based on user roles.
2026-05-19 10:13:26 +02:00
1684892bcb Fix layout in TrainingPlanningUnitFormModal by closing the div tag for improved structure and rendering.
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m11s
2026-05-19 10:05:33 +02:00
4fee5a2b47 Implement planning layout and enhance training module functionality
Some checks failed
Deploy Development / deploy (push) Failing after 23s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Failing after 6s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Has been cancelled
- Added a new PlanningLayout component to manage the training planning interface, allowing for better organization of related pages.
- Introduced a FormActionBar component across various modals and forms to standardize action buttons for saving and canceling.
- Updated the TrainingPlanningPageRoot and TrainingPlanningUnitFormModal to utilize the new FormActionBar for improved user experience.
- Enhanced the TrainingModuleEditPage and TrainingFrameworkProgramEditPage with save and close functionality, streamlining the editing process.
- Refactored existing modals to incorporate the new layout and action bar, ensuring consistency across the application.
2026-05-19 10:02:39 +02:00
82705f0c3e Update version to 0.8.146 and add changelog entry for new features
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m12s
- Incremented app version to 0.8.146 and updated changelog to include the new version details.
- Documented the addition of the publish-to-framework feature for training units, enhancing the training planning capabilities.
2026-05-19 09:45:37 +02:00
a51f794945 Update version to 0.8.147 and add functionality to save exercises as training modules
Some checks failed
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Failing after 0s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Has been cancelled
- Incremented app version to 0.8.147 and updated changelog to reflect the new version.
- Introduced a new modal for saving exercises as training modules within the training planning interface.
- Enhanced the TrainingPlanningPageRoot component to manage the new save module functionality, including state management for the modal.
- Updated the TrainingPlanningUnitFormModal to include an option for saving exercises as a module, improving user experience in training planning.
2026-05-19 09:41:27 +02:00
7693139242 Update version to 0.8.146 and implement publish-to-framework feature for training units
All checks were successful
Deploy Development / deploy (push) Successful in 49s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m19s
- Incremented app version to 0.8.146 and updated build date to 2026-05-19.
- Added new API endpoint to publish training units as session blueprints to framework programs.
- Introduced frontend functionality to support publishing training units, including a modal for user interaction.
- Updated changelog to reflect the new feature and its associated changes.
2026-05-19 08:51:48 +02:00
623af621b4 Enhance MediaWiki import functionality with category normalization and skill attributes
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m17s
Test Suite / pytest-backend (pull_request) Successful in 35s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 12s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m8s
- Introduced `_normalize_mw_category` function to clean category names for API calls, ensuring consistent handling of category prefixes.
- Updated `SmwClient` methods to utilize normalized category names, improving data retrieval accuracy.
- Added `_wiki_category_or_default` function to provide default categories based on import type, enhancing user experience during imports.
- Integrated new fields `karate_relevance` and `relevance_level` into various admin components, allowing for better skill management.
- Incremented app version to 0.8.145 and updated changelog to reflect these changes.
2026-05-16 11:05:15 +02:00
949a77fe38 Enhance skill model and import functionality with karate relevance and relevance level
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 15s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m11s
- Added `karate_relevance` and `relevance_level` fields to the SkillCreate and SkillResponse models, allowing for more detailed skill attributes.
- Updated the SMW property mapping to include these new fields, facilitating their integration during data import.
- Implemented parsing logic for relevance levels from Wiki data, ensuring proper handling of values between 1 and 3.
- Modified the upsert and create skill functions to support the new fields, ensuring they are correctly stored and updated in the database.
- Incremented app version to 0.8.143 and updated changelog to reflect these changes.
2026-05-16 10:56:15 +02:00
0275f76432 Implement RBAC for library content management in club tenancy
Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 41s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Has been cancelled
- Introduced new functions for managing edit, delete, and governance transition permissions for library content, aligning with role-based access control (RBAC) principles.
- Updated existing routers to utilize these new functions, ensuring consistent permission checks across training frameworks, modules, and progression graphs.
- Enhanced visibility and governance handling for training plan templates and library content, improving overall content management and user experience.
- Incremented app version to 0.8.142 and updated changelog to reflect these changes.
2026-05-16 10:53:00 +02:00
bc1790bd82 Refactor section movement logic in TrainingUnitSectionsEditor
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m14s
- Streamlined the section movement process by consolidating validation checks and enhancing the handling of parallel phase indices.
- Improved the overall clarity and efficiency of the section management functionality, ensuring a smoother user experience during edits.
2026-05-16 09:04:09 +02:00
8c07cf36ee Enhance section movement functionality in TrainingUnitSectionsEditor
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
Test Suite / pytest-backend (pull_request) Successful in 34s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 12s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m8s
- Updated the onMoveSectionsAcrossSlots function to support additional parameters for improved section movement across slots, including handling for parallel phase indices and insertion points.
- Refined logic for moving sections between slots, ensuring proper handling of parallel streams and enhancing the overall section management experience.
2026-05-16 08:34:30 +02:00
7d2661a8e8 Remove redundant boundary checks for section movement in TrainingUnitSectionsEditor
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m14s
- Eliminated the checks that prevented section movement across slots when crossing the boundary between 'parallel' and 'whole_group' phases, streamlining the section management logic and improving code clarity.
2026-05-16 08:17:35 +02:00
0fdee610ed Enhance section movement validation in TrainingUnitSectionsEditor
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m8s
- Added a new check to prevent section movement across slots that crosses the boundary between 'parallel' and 'whole_group' phases, improving the logic for section management and ensuring valid operations during edits.
2026-05-16 08:09:24 +02:00
f1c470a8a3 Improve section movement validation in TrainingUnitSectionsEditor
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 38s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m12s
- Added a check to ensure that the source slot is not the same as the target slot when moving sections across slots, enhancing the logic for section management and preventing unnecessary operations.
2026-05-16 08:05:10 +02:00
736656bde8 Refactor enrichFrameworkSlotSections to improve section handling in training framework
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Updated the enrichFrameworkSlotSections function to utilize default sections when no sections are provided, enhancing data integrity.
- Simplified the normalization process by directly using base sections, improving code clarity and maintainability.
2026-05-16 07:53:05 +02:00
e441f59bff Add delete functionality for training plan templates
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m22s
2026-05-16 07:45:53 +02:00
c3eb5a62c4 Update version to 0.8.141 and enhance training plan template handling
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m13s
- Incremented app version to 0.8.141 and updated build date to 2026-05-14.
- Modified the planning module version to 0.12.0, improving template section handling with phase metadata.
- Introduced new functions for normalizing and inserting training plan template sections, ensuring accurate phase representation during saves.
- Updated frontend components to utilize new utility functions for managing training plan templates, enhancing user experience and data integrity.
2026-05-16 07:41:08 +02:00
79e748b470 Refactor phase handling in training unit sections for improved data integrity
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Introduced canonicalization for plan locations in phased saves to ensure consistent phase representation.
- Enhanced the `inheritPlanLocForPhasedSave` function to utilize the new canonicalization logic, improving data flow.
- Updated payload building logic to check for canonicalized plan locations, ensuring accurate phase detection during saves.
2026-05-16 07:35:35 +02:00
88c4201f80 Refactor training framework to improve phase management and user experience
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Enhanced phase handling in training unit hydration and insertion processes, ensuring better data integrity.
- Updated frontend components to support phase representation in training framework slots.
- Improved user interface controls for managing parallel phases, optimizing user experience during training program edits.
- Refactored payload building functions to accommodate phase adjustments, enhancing save functionality for training plans.
2026-05-16 07:27:18 +02:00
6e1cc62065 Enhance training framework with phase handling and payload adjustments
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m9s
- Updated backend logic to include phases in training unit hydration and insertion processes, improving data integrity.
- Modified frontend components to support phases in training framework slots, ensuring consistent data representation.
- Refactored payload building functions to accommodate phases, enhancing the save functionality for training plans.
- Improved user interface to enable controls for parallel phases, optimizing the user experience during training program edits.
2026-05-16 07:02:12 +02:00
76cc81a385 Update project documentation and enhance training features for parallel streams
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 20s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Updated CLAUDE.md and PROJECT_STATUS.md to reflect the latest app version (0.8.140) and database schema (20260515063) as of 2026-05-14.
- Enhanced DOMAIN_MODEL.md and PARALLEL_TRAINING_STREAMS_CONCEPT.md to clarify the implementation of phases and parallel streams in training units.
- Improved HANDOVER.md with detailed descriptions of the coaching and breakout functionalities, including rejoin logic and session management.
- Updated FACHLICHE_NUTZERFUNKTIONEN.md to include new features related to training planning and execution, emphasizing the integration of phases and parallel streams.
- Revised FEATURES_DELIVERED_2026-Q2.md to document the latest changes and improvements in the training framework and media management.
2026-05-15 22:11:05 +02:00
a4f11a8225 Enhance TrainingCoachPage and trainingPlanUtils with split rejoin transition logic
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m11s
Test Suite / pytest-backend (pull_request) Successful in 34s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 12s
Test Suite / k6 /health Baseline (pull_request) Successful in 34s
Test Suite / playwright-tests (pull_request) Successful in 1m20s
- Introduced a new utility function to determine when to prompt for a split rejoin transition between phases, improving user guidance during training sessions.
- Updated TrainingCoachPage to incorporate this logic, enhancing the flow of navigation through training timelines.
- Refactored button actions to provide clearer options for users when managing group transitions, optimizing the user experience during training.
2026-05-15 18:52:27 +02:00
5e5350d5ac Enhance TrainingCoachPage and trainingPlanUtils with session storage management and phase handling improvements
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Introduced a new function in TrainingCoachPage to clear session storage for coach steps, improving state management during training sessions.
- Updated the sectionsWithPlanLocForDisplay function in trainingPlanUtils to include logic for determining the maximum phase order index, enhancing phase handling.
- Enhanced the mapping of sections to include default plan locations for parallel phases, optimizing data representation and user experience.
2026-05-15 17:01:00 +02:00
73ac2218c7 Enhance TrainingCoachPage and trainingPlanUtils with split rejoin functionality
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Updated TrainingCoachPage to implement a prompt for users to rejoin parallel phases, improving user guidance during training sessions.
- Refactored step management logic to ensure accurate navigation through the timeline, utilizing safe step calculations.
- Introduced new utility functions in trainingPlanUtils for building save payloads and determining when to prompt for split rejoin, optimizing data handling.
- Enhanced state management for split rejoin prompts, ensuring a seamless user experience during training.
2026-05-15 16:49:05 +02:00
352237bbb9 Refactor TrainingCoachPage and TrainingUnitRunPage to enhance coach branching functionality
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Updated TrainingCoachPage to implement branching logic for coach steps, allowing for improved navigation through training phases.
- Enhanced session storage handling to manage branch picks and streamline state management during training sessions.
- Modified TrainingUnitRunPage to update links for coaching views, reflecting the new branching structure and improving user experience.
- Introduced new utility functions in trainingPlanUtils for managing coach branch picks and timeline navigation, optimizing data handling across components.
2026-05-15 16:31:54 +02:00
4cf7133bce Enhance TrainingCoachPage and TrainingUnitRunPage with improved coach focus handling and UI updates
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m8s
- Updated TrainingCoachPage to incorporate coach focus parameters from search queries, allowing for more precise control over displayed training streams.
- Refactored session storage handling to better manage state related to coach focus, ensuring accurate step tracking during training sessions.
- Enhanced TrainingUnitRunPage with improved layout for stream titles and added links for direct navigation to coaching views, improving user experience.
- Introduced new utility functions in trainingPlanUtils for managing coach stream focus options and duration overrides, streamlining data handling across components.
2026-05-15 16:08:01 +02:00
c182ced7cd Refactor TrainingUnitRunPage and trainingPlanUtils for improved section handling
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m26s
- Updated TrainingUnitRunPage to utilize new utility functions for managing sections with plan locations, enhancing data representation.
- Refactored trainingPlanUtils to introduce functions for building view models from sections and for displaying sections with plan locations, streamlining the data flow.
- Improved logic for handling phase runs and section indices, ensuring accurate representation of training units during rendering.
2026-05-15 14:00:46 +02:00
5338871f36 Enhance TrainingCoachPage and TrainingUnitRunPage with improved context display and print functionality
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Updated TrainingCoachPage to include coach context in timeline entries and section titles, enhancing clarity for users.
- Refactored TrainingUnitRunPage to support printing options for parallel streams, allowing for better organization during printouts.
- Introduced new CSS styles for page breaks and layout adjustments in app.css, improving print formatting for training plans.
- Enhanced utility functions in trainingPlanUtils.js to support new phase and stream management features, streamlining data handling.
2026-05-15 12:21:08 +02:00
3005f1cb3e Refactor TrainingUnitSectionsEditor to support new parallel stream functionality
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 41s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Renamed functions and parameters to clarify the handling of sections within parallel streams, enhancing code readability.
- Updated drag-and-drop event handlers to accommodate additional parameters for managing sections in parallel streams.
- Introduced a new utility function to reorder sections as the first entry in a parallel stream, improving section management.
- Enhanced the visual representation of drop zones for sections, ensuring a better user experience during reordering operations.
2026-05-15 12:10:44 +02:00
72e8f31cff Enhance TrainingUnitSectionsEditor with new drag-and-drop functionality for parallel phases
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Added new event handlers for drag-and-drop operations to manage sections above and below split headers in parallel phases.
- Implemented utility functions to reorder sections as whole groups or as the first entry in parallel phases, improving section management.
- Updated CSS styles to visually represent new drop zones for sections, enhancing user experience during reordering.
- Refactored existing logic to accommodate new features and ensure proper handling of section placements within parallel streams.
2026-05-15 10:59:30 +02:00
73975d3402 Enhance TrainingUnitSectionsEditor and TrainingFrameworkProgramEditPage with parallel stream support
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Updated the TrainingUnitSectionsEditor to include additional parameters for moving sections across slots, allowing for better handling of parallel streams.
- Improved the removeSection function to ensure proper reordering and handling of sections in parallel phases.
- Enhanced the moveSectionsAcrossFrameworkSlots function to apply changes to sections when moved into parallel streams, ensuring accurate updates.
- Refactored the reorderBlocksImmutableWithPlanLoc function to accommodate new logic for managing phase orders and section types during reordering.
2026-05-15 10:21:15 +02:00
4902771772 Enhance TrainingUnitSectionsEditor with improved section visibility logic
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m22s
- Added logic to determine visibility of drop bands for sections in parallel phases, enhancing user experience during reordering.
- Updated the reorderBlocksImmutableWithPlanLoc function to better handle plan location adjustments based on section types and their positions.
- Improved handling of section indices for parallel streams, ensuring accurate representation of active streams and their corresponding sections.
2026-05-15 10:14:17 +02:00
c2efbee4ee Implement drag-and-drop functionality for parallel stream sections in TrainingUnitSectionsEditor
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Added new CSS styles for visual feedback during drag-and-drop operations, enhancing user experience.
- Introduced logic to manage section drops into parallel streams, allowing for intuitive reordering of sections.
- Implemented utility functions to facilitate the movement of sections within parallel streams, ensuring proper plan location adjustments.
- Updated the TrainingUnitSectionsEditor component to utilize the new drag-and-drop features, improving overall functionality and usability.
2026-05-15 09:10:54 +02:00
514b64682c Implement visual enhancements and logic for section drop bands in TrainingUnitSectionsEditor
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m20s
- Added CSS styles for visual representation of section drop bands, differentiating between split and whole group regions.
- Introduced a new function to determine the appropriate drop band class based on section types, improving user experience during section reordering.
- Enhanced drag-and-drop functionality to support parallel phase management, allowing for more intuitive section placement within the editor.
- Updated utility functions to handle the movement of parallel phase runs, ensuring proper plan location adjustments during reordering.
2026-05-15 08:37:16 +02:00
a0a0be8bef Enhance TrainingUnitSectionsEditor with advanced phase management features
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m13s
- Introduced new utility functions for managing phase runs, including the ability to swap adjacent phase runs and move phase runs up or down by their order.
- Updated the TrainingUnitSectionsEditor to incorporate these new functions, allowing for improved reordering of sections within parallel phases.
- Enhanced the moveSection logic to support movement across different phase types, ensuring better user experience when managing sections.
- Refactored the reorderBlocksImmutable function to accommodate plan location adjustments during section reordering, improving overall functionality.
2026-05-15 08:20:43 +02:00
613fedfaff Refactor training phase handling in backend and enhance TrainingUnitSectionsEditor
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m7s
- Updated the backend logic to ensure strict ordering of phase indices, preventing UNIQUE constraint violations when phases are duplicated.
- Enhanced the TrainingUnitSectionsEditor component with new state management for editing phase titles and stream names, improving user interaction.
- Implemented conditional rendering for input fields to facilitate inline editing of phase titles and stream names, streamlining the editing process.
2026-05-15 08:02:18 +02:00
2e761161ef Enhance TrainingUnitSectionsEditor with new section management features
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m7s
- Introduced functions to add and manage parallel phases and sections, allowing for more flexible training unit configurations.
- Implemented logic to handle the addition of whole group sections and parallel streams, improving the user experience in the editor.
- Added utility functions for reordering sections and checking for content within parallel stream buckets.
- Updated state management to ensure proper handling of section titles and removal of streams, enhancing overall functionality.
2026-05-15 07:50:16 +02:00
0a203aaf75 Enhance TrainingUnitSectionsEditor with parallel phase management features
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m10s
- Introduced logic to limit the number of streams per parallel phase, ensuring compliance with the defined maximum.
- Added utility functions for managing stream indices and visual representation of streams.
- Implemented section movement within parallel streams, allowing for reordering while maintaining stream integrity.
- Updated UI components to reflect changes in stream handling, including disabling buttons when limits are reached.
- Enhanced state management for parallel stream tabs, improving user experience in navigating between streams.
2026-05-15 07:37:51 +02:00
f50e9db523 chore(version): update version and changelog for release 0.8.140
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Bumped APP_VERSION to 0.8.140 and updated the changelog to reflect recent changes.
- Enhanced the Training Planning Module with new controls for managing whole group and parallel phases, including the ability to add streams to existing parallel phases.
- Introduced utility functions for handling phase and stream configurations, improving the overall structure and usability of the training unit sections editor.
- Updated the TrainingPlanningUnitFormModal to support the new phase controls, ensuring seamless integration with the frontend components.
2026-05-15 07:25:59 +02:00
749c185e3d chore(version): update version and changelog for release 0.8.139
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m11s
- Bumped APP_VERSION to 0.8.139 and updated the changelog to reflect recent changes.
- Enhanced the Training Planning Module to support new phase handling, including improved labeling for sections in the editor.
- Updated the API payload structure to accommodate parallel streams and phases, ensuring better integration with the frontend components.
- Refactored utility functions for improved clarity and maintainability in handling training unit sections and phases.
2026-05-15 07:12:43 +02:00
214f90d39b chore(version): update version and changelog for release 0.8.138
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m9s
- Bumped APP_VERSION to 0.8.138 and updated the changelog to reflect recent changes.
- Enhanced training unit planning with support for POST/PUT requests including phases and parallel streams.
- Fixed role assignment validation for stream co-trainers and added integration tests for phase handling.
- Updated the training planning API to improve data structure and retrieval for nested phases and sections.
2026-05-15 07:04:24 +02:00
0d34c0a73d Update pytest configuration and documentation for training planning integration
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Enhanced the pytest workflow in `.gitea/workflows/test.yml` to include `TRAINING_PLANNING_INTEGRATION` for improved testing against the PostgreSQL database.
- Updated `pytest.ini` to clarify integration marker usage, specifying both `ACCESS_LAYER_INTEGRATION` and `TRAINING_PLANNING_INTEGRATION`.
- Revised documentation in `test_training_planning_sections_integration.py` to provide clearer activation instructions for local and CI environments.
2026-05-14 22:35:12 +02:00
ae51d201bc chore(version): update version and changelog for release 0.8.137
Some checks failed
Test Suite / pytest-backend (push) Waiting to run
Test Suite / lint-backend (push) Waiting to run
Test Suite / build-frontend (push) Waiting to run
Test Suite / k6 /health Baseline (push) Waiting to run
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Has been cancelled
- Bumped APP_VERSION to 0.8.137 and updated the changelog to reflect recent changes.
- Introduced Migration 063 for training unit phases and parallel streams, enhancing the structure of training units.
- Updated the training planning API to support nested phases and sections, improving data retrieval for UI components.
- Enhanced section handling to accommodate new phase and stream structures, ensuring compatibility with existing workflows.
2026-05-14 22:35:02 +02:00
220a16429c Enhance training unit sections handling and documentation for parallel training streams
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m9s
- Updated the backend to improve the fetching and insertion of training unit sections, including a new function for handling section items.
- Added documentation notes regarding the unique constraint on `training_unit_sections` and the implications for parallel training streams.
- Updated frontend components and utility functions to reflect changes in the training planning API and to prepare for future enhancements related to parallel streams.
2026-05-14 22:24:55 +02:00
e759076a6c chore(version): update version and changelog for release 0.8.136
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Bumped APP_VERSION to 0.8.136 and updated the changelog to reflect recent changes.
- Fixed the Mandanten-Header handling in `api/exercises.js` for improved API requests.
- Continued Frontend Phase 4 with the addition of the `exercises.js` module, enhancing the API structure.
- Updated architecture documentation to include details on the new `exercises.js` API and its integration with the existing client structure.
- Enhanced `utils/api.js` to re-export the new exercises module, streamlining API access.
2026-05-14 21:49:56 +02:00
8175e239b4 chore(version): update version and changelog for release 0.8.134
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m12s
- Bumped APP_VERSION to 0.8.134 and updated the changelog to reflect recent changes.
- Continued Frontend Phase 4 with the introduction of the `frontend/src/api/planning.js` module for training planning.
- Updated architecture documentation to include details on the new `planning.js` API and its integration with the existing client structure.
- Enhanced `utils/api.js` to re-export the new planning module, streamlining API access.
2026-05-14 21:39:45 +02:00
8f5af49a6f chore(version): update version and changelog for release 0.8.133
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m21s
- Bumped APP_VERSION to 0.8.133 and updated the changelog to reflect recent changes.
- Initiated Frontend Phase 4 Welle 1, introducing a centralized HTTP client in `frontend/src/api/client.js` while maintaining `utils/api.js` as a facade.
- Documented the changes in the architecture roadmap and README for clarity on the new API structure.
2026-05-14 21:31:04 +02:00
e7dc6a6cd3 chore(version): update version and changelog for release 0.8.132
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Bumped APP_VERSION to 0.8.132 and updated the changelog to reflect recent changes.
- Removed unused imports and refactored the ExerciseFormPage, ExercisesListPage, and TrainingPlanningPage for improved code clarity and maintainability.
- Enhanced the overall structure of the components by eliminating redundant code and optimizing imports.
2026-05-14 16:14:26 +02:00
e09a2284e9 chore(version): update version and changelog for release 0.8.131
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m8s
- Bumped APP_VERSION to 0.8.131 and updated the changelog to reflect recent changes.
- Added the TrainingPlanningUnitFormModal component to the TrainingPlanningPage for enhanced training unit management.
- Refactored frameworkLineageText utility function for better code organization and reusability in the training planning context.
- Updated BASELINE_SNAPSHOT documentation to include new metrics and logging details for k6 health checks.
2026-05-14 16:02:54 +02:00
b0faa4bfab chore(version): update version and changelog for release 0.8.130
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m9s
- Bumped APP_VERSION to 0.8.130 and updated the changelog to reflect recent changes.
- Fixed PUT/POST for training_units to handle assistant_trainer_profile_ids as JSONB using psycopg2.extras.Json, resolving a ProgrammingError during co-assignment.
2026-05-14 15:40:45 +02:00
a1a3f2e0a1 chore(version): update version and changelog for release 0.8.129
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m8s
- Bumped APP_VERSION to 0.8.129 and updated the changelog to reflect recent changes.
- Added the TrainingPlanningTrainerAssignModal component to the TrainingPlanningPage for enhanced trainer assignment functionality.
- Implemented new callback functions for managing lead trainer and assistant assignments in the training planning process.
2026-05-14 15:32:21 +02:00
45bc049c0d chore(version): update version and changelog for release 0.8.128
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m7s
- Bumped APP_VERSION to 0.8.128 and updated the changelog to reflect recent changes.
- Added the TrainingPlanningModuleApplyModal component to the TrainingPlanningPage for enhanced training module application functionality.
- Implemented a new callback function onModuleApplySectionIndexChange to manage module application section index changes.
2026-05-14 13:44:37 +02:00
e4e362b0a9 chore(version): update version and changelog for release 0.8.126
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 41s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m30s
- Bumped APP_VERSION to 0.8.126 and updated the changelog to reflect recent changes.
- Added the TrainingPlanningFrameworkImportModal component to the TrainingPlanningPage for improved training session management.
- Implemented a new Playwright test to verify the functionality of the framework import dialog in the training planning page.
2026-05-14 13:26:02 +02:00
300d916fad chore(version): update version and changelog for release 0.8.125
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m12s
- Bumped APP_VERSION to 0.8.125 and updated the changelog to reflect recent changes.
- Added new tests for the dashboard API to ensure proper HTTP 200 responses when inner lists are mocked.
- Enhanced the ExerciseListBulkToolbar component with a data-testid for improved testing capabilities.
- Refactored the TrainingPlanningPage by extracting utility functions to trainingPlanningPageHelpers for better code organization.
2026-05-14 12:48:33 +02:00
1631bd2e02 chore(version): update version and changelog for release 0.8.124
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m1s
- Bumped APP_VERSION to 0.8.124 and updated the changelog to reflect recent changes.
- Introduced the ExerciseListBulkToolbar component in ExercisesListPage for improved bulk action handling.
- Enhanced the user interface for selecting and managing exercises in bulk.
2026-05-14 12:30:04 +02:00
2e105a99b8 chore(version): update version and changelog for release 0.8.123
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m2s
Test Suite / pytest-backend (pull_request) Successful in 34s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 12s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m1s
- Bumped APP_VERSION to 0.8.123 and updated the changelog to reflect recent changes.
- Fixed internal calls in GET /api/dashboard/kpis to use unwrap_query_default, preventing 500 errors due to FastAPI query defaults.
- Enhanced list_exercises and list_training_units functions to utilize unwrap_query_default for improved query handling.
- Added unit tests for unwrap_query_default to ensure correct behavior in various scenarios.
2026-05-14 11:48:11 +02:00
4235246cd7 chore(version): update version and changelog for release 0.8.122
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m11s
- Bumped APP_VERSION to 0.8.122 and updated the changelog to reflect new features.
- Integrated useExerciseListCatalogsAndQuery hook in ExercisesListPage for improved exercise list management and data fetching.
- Enhanced documentation to include new concepts for parallel training streams and their technical specifications.
- Updated DOMAIN_MODEL and related technical specs to clarify the structure and functionality of training streams within units.
2026-05-14 11:21:09 +02:00
57a8957c93 chore(version): update version and changelog for release 0.8.121
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m1s
- Bumped APP_VERSION to 0.8.121 and updated the changelog to reflect new features.
- Introduced the ExerciseListFilterModal and ExerciseListBulkModal components, enhancing the exercise list functionality.
- Modularized the ExerciseListPage to improve code organization and maintainability.
- Added Playwright tests for the filter dialog functionality, ensuring proper user interaction and visibility.
2026-05-14 10:58:41 +02:00
930a786315 refactor(ui): enhance styling and structure of training unit sections and combination plan bracket
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m5s
Test Suite / pytest-backend (pull_request) Successful in 34s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 11s
Test Suite / k6 /health Baseline (pull_request) Successful in 33s
Test Suite / playwright-tests (pull_request) Successful in 1m8s
- Updated CSS for training unit sections to improve layout and responsiveness, ensuring combo planning strips are displayed correctly.
- Refactored CombinationPlanBracket component to accept additional class names for better customization.
- Removed unused functions and streamlined imports in TrainingUnitSectionsEditor for cleaner code.
- Reintroduced ExercisePickerModal with improved placement in ExerciseFormPage for better user experience.
2026-05-14 09:05:15 +02:00
9da29a2231 chore(version): update version and changelog for release 0.8.119
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m2s
- Bumped APP_VERSION to 0.8.119 and updated the changelog to reflect new features.
- Introduced the ExerciseListCard component and implemented lazy loading for the Progression Tab using React's Suspense.
- Enhanced the ExercisePickerModal with virtualization for improved performance using @tanstack/react-virtual.
- Updated documentation to reflect the new app version and its corresponding changes.
2026-05-14 08:59:06 +02:00
b06d026dd0 chore(version): update version and changelog for release 0.8.118
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 57s
- Bumped APP_VERSION to 0.8.118 and updated DB_SCHEMA_VERSION to 20260514062.
- Enhanced the dashboard API with a new endpoint that consolidates training home data, allowing for a single request to retrieve upcoming training sessions, planned sessions with notes, and review pending items.
- Updated the frontend Dashboard component to utilize the new API structure, improving data loading efficiency and user experience.
- Added migration details and changelog entries to reflect the latest changes and improvements.
2026-05-14 08:53:09 +02:00
32ba008660 chore(version): update version and changelog for release 0.8.117
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m2s
- Bumped APP_VERSION to 0.8.117 and updated DB_SCHEMA_VERSION to 20260514061.
- Enhanced the training units API with optional keyset pagination, allowing for more efficient data retrieval.
- Updated the changelog to reflect the new features and improvements, including changes to the frontend API integration for training units.
- Adjusted documentation to align with the new app version and its corresponding changes.
2026-05-14 08:44:59 +02:00
657fcc241a chore(version): update version and changelog for release 0.8.116
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m0s
- Bumped APP_VERSION to 0.8.116 and updated the changelog to reflect changes, including the implementation of a new loading strategy for the Org-Inbox that utilizes requestIdleCallback to optimize API calls during dashboard initialization.
- Updated documentation to reflect the new app version and its corresponding changes.
2026-05-14 08:36:31 +02:00
c69edc6952 feat(ci): refactor Gitea workflow to separate k6 health baseline tests
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m5s
- Introduced a new job `k6-health-baseline` in the Gitea CI workflow to run health checks independently from Playwright tests, enhancing clarity and organization.
- Updated documentation to reflect the changes in the CI pipeline, specifying the execution order and purpose of each job.
- Adjusted environment variables and health check logic for both development and production modes, ensuring accurate testing conditions.
2026-05-14 08:32:26 +02:00
789b640ad0 chore(version): update version and changelog for release 0.8.115
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 1m28s
- Bumped APP_VERSION to 0.8.115 and updated the changelog to reflect changes, including the introduction of keyset pagination for the GET /api/exercises endpoint.
- Enhanced the exercises router to support cursor-based pagination using cursor_updated_at and cursor_id, improving performance and user experience.
- Updated frontend components to utilize the new pagination method, removing offset-based loading logic.
2026-05-14 08:24:47 +02:00
14cf8a1a53 feat(tests): enhance smoke test for exercise navigation
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 1m42s
- Added a check to ensure the loading spinner is not visible before navigating to the exercises page, improving test reliability.
- Updated navigation logic to wait for both the URL change and the click event on the exercises link, reducing race conditions.
- Modified the assertion to check for the visibility of the main heading on the exercises page, ensuring stricter validation of page load success.
2026-05-14 08:17:15 +02:00
ea4c1f87f6 chore(version): update version and changelog for release 0.8.114
Some checks failed
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Failing after 1m31s
- Bumped APP_VERSION to 0.8.114 and updated DB_SCHEMA_VERSION to 20260514060.
- Added changelog entry for version 0.8.114, detailing migration 060 for exercise scaling and indexing improvements.
2026-05-14 08:06:39 +02:00
2fa1db55fd chore(version): update version and changelog for release 0.8.113
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / playwright-tests (push) Successful in 1m29s
- Bumped APP_VERSION to 0.8.113 and updated DB_SCHEMA_VERSION to 20260514059.
- Added changelog entry for version 0.8.113, detailing migration 059 for training unit sorting without framework_slot_id.
2026-05-14 08:02:49 +02:00
75ddd06d6a chore(version): update version and changelog for release 0.8.112
Some checks failed
Test Suite / lint-backend (push) Waiting to run
Test Suite / build-frontend (push) Waiting to run
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Has been cancelled
- Bumped APP_VERSION to 0.8.112 and updated DB_SCHEMA_VERSION to 20260514058.
- Added changelog entry for version 0.8.112, detailing migration 058 for exercise sorting indices.
2026-05-14 08:01:52 +02:00
597486bef1 feat(dashboard): add GET /api/dashboard/kpis endpoint and integrate into frontend
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 1m32s
- Implemented a new API endpoint for retrieving dashboard KPIs, providing a consolidated overview of drafts, personal exercises, and year-to-date completed units.
- Updated the Dashboard component to utilize the new endpoint, enhancing data retrieval efficiency and user experience.
- Added a helper function in the exercises router for programmatic access to exercise listings.
- Updated versioning and changelog to reflect the addition of the dashboard feature.
2026-05-14 07:47:27 +02:00
ebad8025f4 fix(ci): update k6 installation script to support multiple architectures
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / playwright-tests (push) Successful in 1m30s
- Modified the k6 installation script in the Gitea CI workflow to dynamically select the appropriate binary for linux-amd64 or linux-arm64 based on the system architecture.
- Updated README.md to reflect the changes in architecture handling for k6 installation, providing clearer guidance for users on different platforms.
2026-05-14 07:03:37 +02:00
c7650cac2f feat(ci): integrate k6 health baseline testing into Gitea workflow
Some checks failed
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Failing after 5s
- Added a new job to the Gitea CI workflow to install k6 and run health baseline tests after the health wait period.
- Updated documentation to reflect the automatic execution of k6 in the CI pipeline and clarified local execution instructions.
- Enhanced architecture documentation to indicate the completion of Phase 0 for the pipeline part, with k6 running after each relevant deploy.
2026-05-14 06:56:50 +02:00
4b2848c7c3 feat(docs): add performance baseline documentation and update architecture references
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 57s
- Introduced a new section for the Performance-Baseline in CLAUDE.md and updated HANDOVER.md to include references to the new BASELINE_SNAPSHOT.md.
- Enhanced architecture documentation in README.md to clarify the purpose of the baseline snapshot and its relevance to the refactor roadmap.
- Refactored OrgInboxContext to implement a unified loading logic for join requests and content reports, improving code maintainability and performance.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 06:53:37 +02:00
255fa45e90 feat(tests): add E2E test for Dashboard API budget and update documentation
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 57s
- Introduced a new E2E test to validate API call counts for `/api/profiles/me` and `/api/training-units` after reloading the Dashboard, ensuring compliance with refactor phase requirements.
- Updated architecture documentation to include details about the new test and its execution within the CI pipeline.
2026-05-14 06:49:15 +02:00
7043addd15 feat(docs): update architecture documentation references and enhance handover details
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 57s
- Added references to the architecture target image, refactor roadmap, and binding Shinkan rules in CLAUDE.md and HANDOVER.md for better project clarity.
- Updated the Dashboard component to improve user authentication handling and optimize data loading, enhancing overall performance and user experience.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-14 06:42:13 +02:00
1c268555f6 feat(App): implement code-splitting for improved performance and user experience
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 56s
- Refactored the App component to utilize React's lazy loading for page components, enhancing load times and performance.
- Introduced a fallback UI with a spinner during component loading, improving user feedback during navigation.
- Updated the AuthContext to use useCallback and useMemo for optimized performance in login and logout functions, reducing unnecessary re-renders.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 22:10:02 +02:00
85163ad440 feat(exercise-detail): add embedded peek functionality for individual exercises
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 1m9s
Test Suite / pytest-backend (pull_request) Successful in 34s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 11s
Test Suite / playwright-tests (pull_request) Successful in 55s
- Introduced `ExercisePeekModal` to allow users to view details of individual exercises without navigating away from the Exercise Detail Page.
- Updated the UI to include a button interaction for peeking at exercises, enhancing user experience and accessibility.
- Modified the description in the Exercise Detail section to clarify the new peek feature and its functionality.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:58:24 +02:00
502dddd3b3 feat(combo-planning): enhance candidate interaction and UI for combination exercises
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 57s
- Introduced new CSS styles for interactive candidate buttons and links in the `CombinationPlanBracket` and `CombinationCoachSlots` components, improving user engagement.
- Updated `CombinationPlanBracket` to conditionally render candidates as buttons or links based on interaction type, enhancing navigation options.
- Refactored candidate handling in `CombinationCoachSlots` to support new interaction methods, streamlining candidate exercise display.
- Enhanced `ExercisePeekModal` and related components to support candidate peek functionality, allowing for a more seamless user experience.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:51:52 +02:00
00edc7a93d feat(exercise-detail): enhance combination exercise display with candidate links and bracket visualization
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 59s
- Introduced `flattenCombinationCandidateLinks` function to streamline the extraction of unique candidate exercises for combination details.
- Updated the `ExerciseDetailPage` to conditionally render a `CombinationPlanBracket` for combination exercises, improving the visual representation of training runs.
- Enhanced the UI to display linked individual exercises associated with combination slots, providing clearer navigation and context for users.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 21:41:32 +02:00
d50bed428b refactor(App): migrate to Data Router for improved routing and unsaved changes handling
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 56s
- Replaced `BrowserRouter` and `Routes` with `createBrowserRouter` and `RouterProvider` to support unsaved changes blocking.
- Restructured route definitions for better organization and clarity, maintaining existing functionality.
- Added comments to clarify the necessity of the Data Router for handling unsaved changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 17:19:59 +02:00
49adb395dd feat(version): bump to 0.8.110 and update project specifications
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 1m15s
- Updated app version to 0.8.110 and database schema version to 20260512057, reflecting recent enhancements.
- Revised project status documentation to include new versioning and next steps for development.
- Enhanced the functional specification for training modules and combination exercises, detailing upcoming features and improvements.
- Improved technical specifications to align with the latest code changes, ensuring consistency across documentation.
- Introduced new UI elements for toast notifications and unsaved changes prompts to enhance user experience.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 16:34:38 +02:00
81fd7d9b3b feat(training-unit-editor): enhance combo planning UI and functionality
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 1m2s
Test Suite / pytest-backend (pull_request) Successful in 33s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 10s
Test Suite / playwright-tests (pull_request) Successful in 1m2s
- Updated CSS for the combo planning strip to improve layout and visual consistency.
- Refactored `compactComboPlanningCaption` to simplify the display of planning status.
- Introduced a new utility function to infer advance mode from stored slot rows, enhancing profile handling.
- Improved merging logic for slot profiles to ensure accurate representation of advance modes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 15:49:14 +02:00
3898e8bc2c feat(training-planning): enhance planning method profile handling and UI updates
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 59s
- Integrated PsycopgJson for improved handling of planning method profiles in the backend.
- Updated CombinationPlanBracket to display primary load labels for better clarity in the UI.
- Enhanced TrainingUnitSectionsEditor and utility functions to ensure proper serialization of planning profiles, preventing potential errors during API interactions.
- Improved CSS for combo plan brackets to enhance visual alignment and presentation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 15:28:37 +02:00
d3ddc52118 feat(combo-planning): enhance combination profile handling and UI improvements
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 59s
- Updated CombinationCoachSlots to integrate a new function for formatting inline profile values, improving data display consistency.
- Refactored CombinationMethodProfileEditor to streamline slot index handling and enhance title clarity.
- Improved CombinationPlanBracket by removing unnecessary elements for a cleaner UI.
- Enhanced ExerciseFullContent to support additional catalog method profile snapshots, improving exercise detail accuracy.
- Updated CSS for combo plan brackets to enhance visual presentation and alignment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:42:01 +02:00
79dabbca5a feat(combo-planning): replace summarizeSlotProfileBrief with effectiveStationTimingSummary
All checks were successful
Deploy Development / deploy (push) Successful in 43s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 57s
- Updated CombinationCoachSlots, CombinationPlanBracket, and TrainingUnitSectionsEditor components to utilize effectiveStationTimingSummary for improved timing display.
- Adjusted station title handling to enhance clarity and consistency across components.
- Refactored utility functions to streamline slot timing summaries and improve overall user experience in combination planning.
2026-05-13 14:34:17 +02:00
ed15f73727 feat(combo-planning): enhance modal UI and styling for combination planning editing
All checks were successful
Deploy Development / deploy (push) Successful in 44s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 58s
- Updated the TrainingUnitSectionsEditor to integrate new CSS classes for the combo planning modal, improving the visual consistency with the exercise preview.
- Added responsive design features to the modal, including max-width and max-height adjustments for better usability on different screen sizes.
- Introduced new toolbar and hint elements to enhance user interaction and provide clearer guidance within the combo planning context.
- Refactored existing modal structure for improved accessibility and user experience.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:25:58 +02:00
a8942a9e4e feat(version): bump to 0.8.110 and enhance combination exercise features
Some checks failed
Test Suite / lint-backend (push) Waiting to run
Test Suite / build-frontend (push) Waiting to run
Test Suite / playwright-tests (push) Waiting to run
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Has been cancelled
- Updated app version to 0.8.110, reflecting recent improvements in combination exercise handling.
- Introduced `load_combination_slots_for_exercise` function to streamline fetching combination slots for exercises.
- Enhanced `TrainingPlanningPage` and `ExercisePeekModal` to utilize the new combination slots functionality, improving user experience.
- Updated changelog to document the latest changes and feature enhancements.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:24:55 +02:00
5dc93d9a8c feat(training-unit-editor): integrate new summary function and enhance combination exercise display
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 56s
- Introduced `summarizeSlotProfileBrief` utility for concise slot profile summaries, improving the display of combination exercises.
- Updated `CombinationCoachSlots` and `ExercisePeekModal` components to utilize the new summary function for better user experience.
- Enhanced `TrainingUnitSectionsEditor` to manage combination slots more effectively, including improved title handling and display options.
- Adjusted `TrainingPlanningPage` to support additional peek context for combination exercises, streamlining the planning process.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 14:11:53 +02:00
805ad3c5a5 feat(training-unit-editor): enhance combination slots handling and UI improvements
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 1m1s
- Added support for `compactPlanningView` and `omitGlobalKeyValueBlock` in the CombinationCoachSlots component to improve display options.
- Updated the TrainingUnitSectionsEditor to fetch and manage combination slots more effectively, including new state management for modal interactions.
- Introduced a new utility function `comboSlotsOutlineForProfileEditor` to streamline the display of combination slots in the editor.
- Enhanced UI elements for better user experience when managing combination exercises and their associated slots.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:58:59 +02:00
13efce6e36 feat(training-unit-editor): enhance combo planning features and UI updates
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 1m0s
- Added a new function `compactComboPlanningCaption` to display planning status with archetype labels.
- Introduced state management for a modal to edit combination planning profiles, improving user interaction.
- Updated UI components to enhance the display and interaction of combination exercises, including styling adjustments for better usability.
- Implemented keyboard event handling for modal closure, enhancing user experience.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 09:06:10 +02:00
cf9932990e feat(version): bump to 0.8.109 and enhance combination exercise features
All checks were successful
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 56s
- Updated app version to 0.8.109, reflecting recent improvements in combination exercise handling.
- Introduced `rep_series_count` for slot profiles, allowing for multiple series in `rep` and `manual` modes, enhancing flexibility in exercise configurations.
- Updated the CombinationMethodProfileEditor and CombinationCoachSlots components to support and display the new series count feature.
- Enhanced ExerciseFormPage to manage series count and intra-series pauses effectively, improving user experience.
- Documented changes in the changelog for better tracking of feature enhancements.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 08:58:41 +02:00
38d84ecdf6 feat(version): bump to 0.8.106 and enhance combination exercise features
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 55s
- Updated app version to 0.8.106, reflecting recent improvements in combination exercise handling.
- Introduced `advance_mode` for slot profiles, allowing for flexible timing options (timed, repetitions, manual) in the CombinationMethodProfileEditor.
- Enhanced the CombinationCoachSlots component to display timing summaries based on the selected advance mode.
- Updated ExerciseFormPage to manage combination slots with new validation and user feedback for exercise selection.
- Documented changes in the changelog for better tracking of feature enhancements.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 08:19:46 +02:00
ce63d46cf4 feat(version): bump to 0.8.105 and enhance combination exercise features
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 56s
- Updated app version to 0.8.105, reflecting recent improvements in combination exercise handling.
- Added support for per-slot timing options in the CombinationMethodProfileEditor, allowing for more flexible exercise configurations.
- Enhanced the ExerciseFormPage to manage combination slots more effectively, including new functions for reordering and merging exercises.
- Updated changelog to document the latest changes and improvements.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 07:53:00 +02:00
435da7f17a feat(version): bump to 0.8.104 and enhance combination exercise features
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 56s
- Updated app version to 0.8.104, reflecting recent improvements in combination exercise handling.
- Enhanced the CombinationMethodProfileEditor to support structured slot timing profiles without requiring JSON input from trainers.
- Introduced quick ratio presets for circuit and interval training methods, improving user experience in setting up training profiles.
- Updated documentation and changelog to reflect new features and integration details.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 07:38:44 +02:00
4e654e50c0 feat(version): bump to 0.8.103 and enhance planning method profile integration
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 59s
- Updated app version to 0.8.103, reflecting recent enhancements in training planning.
- Incremented database schema version to 20260512057, ensuring compatibility with new features.
- Introduced optional `planning_method_profile` for combination exercises, allowing for detailed planning and coaching support.
- Enhanced frontend components to manage and display planning method profiles effectively in the Training Unit Sections Editor and ExerciseFullContent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 07:21:33 +02:00
12fd3926b2 feat(combination-exercises): enhance method profile integration and update specifications
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 1m4s
- Updated app version to 0.8.102, reflecting recent enhancements in combination exercises.
- Introduced structured method profiles for combination exercises, allowing for detailed planning and coaching support.
- Enhanced frontend components to display method profiles in the Exercise and Combination Coach views.
- Updated documentation to include new specifications and implementation details for method archetypes and profiles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 07:16:58 +02:00
919910d52a feat(version): bump to 0.8.101 and update exercise module versions
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 59s
- Updated app version to 0.8.101, reflecting recent enhancements.
- Incremented exercise module version to 2.24.1, improving handling of combination exercises.
- Added changelog entry for new features related to training-coach functionality in combination exercises.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 06:36:50 +02:00
3dc4c9c79e feat(exercises): update to version 0.8.100 and enhance combination exercise handling
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 56s
- Bumped app version to 0.8.100, reflecting recent updates.
- Improved validation logic for combination exercises in the backend, ensuring proper handling of exercise variants.
- Enhanced frontend components, including the ExercisePickerModal, to support filtering and displaying combination exercises.
- Updated API payloads and utility functions to accommodate new exercise types and their properties.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 06:30:53 +02:00
8a9f9f960f feat(exercises): introduce combination exercises and enhance exercise management
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 1m1s
- Updated app version to 0.8.99, reflecting the addition of combination exercises.
- Implemented new data structures and validation for combination slots and archetypes in the backend.
- Enhanced frontend components to support selection and display of combination exercises, including new UI elements for managing slots and archetypes.
- Updated API payload handling to accommodate new exercise types and their associated data.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-13 06:12:47 +02:00
f4f5642c21 feat(profiles): add training planning preferences to user profile
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 56s
- Introduced `training_planning_prefs` field in the ProfileUpdate model to store user-specific UI options for training planning.
- Updated the backend to handle the new preferences during profile updates, ensuring proper validation and storage.
- Enhanced the frontend to allow users to select their preferred display mode for training modules in the Account Settings page.
- Updated version to 0.8.98 and adjusted database schema version accordingly, reflecting the new feature integration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:36:19 +02:00
05042ee9ec feat(training-units): add compact legend and module styling in Training Unit Sections Editor
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 56s
- Introduced new CSS styles for compact module display, enhancing the visual structure of training unit sections.
- Implemented functionality to conditionally render module tags and borders based on the selected UX mode.
- Enhanced the section module legend model to aggregate and display module information effectively.
- Improved the rendering logic to support both compact and standard views, ensuring a flexible user experience.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:30:55 +02:00
e96951728d feat(training-units): enhance training unit sections with new module display and functionality
Some checks failed
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Failing after 30s
- Added new CSS styles for module display, including a structured layout for exercises and notes within the Training Unit Sections Editor.
- Implemented a function to gather and render module outlines, improving the visibility of exercises and notes associated with training modules.
- Enhanced the TrainingPlanningPage to support module search and preview functionality, allowing users to filter and view module details before applying them.
- Improved state management for module application, ensuring a smoother user experience when inserting modules into training plans.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:17:44 +02:00
bfaf532ab2 feat(training-units): enhance section editing with insert functionality
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 56s
- Added new CSS styles for insert slots and buttons to improve UI for adding items between sections.
- Implemented functionality in the TrainingUnitSectionsEditor to allow users to insert notes and exercises at specified positions within sections.
- Updated the TrainingFrameworkProgramEditPage to support the new insert functionality, ensuring seamless integration with existing features.
- Enhanced state management to handle insert positions effectively, improving user experience during section editing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 22:05:22 +02:00
e41908af73 feat(training-planning): enhance training module integration and UI
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 56s
- Introduced a new function to handle optional source training module IDs, ensuring proper validation and integration.
- Updated the backend to include source training module ID and title in section items, allowing for better tracking of module origins.
- Enhanced the frontend to display module bands in the Training Unit Sections Editor, improving user experience by indicating the source of exercises and notes.
- Added functionality to insert training modules at specified positions within sections, providing users with more control over their training plans.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:53:41 +02:00
c1243651bb feat(training-modules): implement training module functionality and UI integration
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 56s
- Added new API endpoints for managing training modules, including listing, creating, updating, and deleting modules.
- Implemented the ability to apply training modules to training units, allowing users to copy module content into specific sections.
- Enhanced the frontend with new pages for managing training modules and integrated modal functionality for applying modules within the training planning page.
- Updated version to 0.8.97 and adjusted database schema version accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:35:07 +02:00
59d53d6154 docs: update project status and add user overview documentation
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 56s
- Updated project status to version 0.8.96 as of 2026-05-12, reflecting recent enhancements and features.
- Added a new section for the user overview in `docs/FACHLICHE_NUTZERFUNKTIONEN.md`, providing a compact perspective for design and product teams.
- Revised references in various documents to include the new user overview and updated project status.
- Enhanced the requirements documentation to link to the user overview for better clarity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 21:20:36 +02:00
98edb282ed chore: bump version to 0.8.96 and enhance legal document features
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 12s
Test Suite / playwright-tests (push) Successful in 59s
- Updated app version to 0.8.96 with a new build date of 2026-05-12.
- Improved legal documents functionality with a live preview feature alongside the editor.
- Added modal for full document preview and updated CSS styles for better layout.
- Enhanced the AdminLegalDocumentsPage to support rendering previews of legal documents.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 11:22:01 +02:00
81b9e8f601 chore: bump version to 0.8.95 and update legal documents features
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 15s
Test Suite / playwright-tests (push) Successful in 56s
- Updated app version to 0.8.95 with a new build date of 2026-05-12.
- Enhanced legal documents functionality to support section numbering and Markdown formatting in the output.
- Updated dependencies in package.json to include 'marked' and 'react-markdown'.
- Added new CSS styles for legal document presentation.
- Refactored PDF generation logic to incorporate new metadata and improved document structure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 10:52:06 +02:00
04663e090a docs: update compliance docs and HANDOVER to reflect P-13 full state (0.8.94)
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 1m3s
Test Suite / pytest-backend (pull_request) Successful in 23s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 6s
Test Suite / playwright-tests (pull_request) Successful in 55s
- compliance-implementation.md: expand P-13 section with all extensions
  (0.8.88-0.8.94: audit log, email notifications, club-admin rights,
  workflow bar, archive split, badge, CI fix); update test summary to
  159 passed; add operativ check items 9-13 for P-13 extensions;
  bump app version header to 0.8.94
- compliance-package-register.md: expand P-13 Letzter Stand with full
  extension list through 0.8.94; update Fortschritt version
- compliance-roadmap.md: update app version to 0.8.94; P-13 row in
  Schnellreferenz marked done (was showing as "next recommended");
  Etappe B P-13 hint updated with full feature summary
- HANDOVER.md: update app version header to 0.8.94; §5b P-13 section
  rewritten with complete feature list including all 0.8.88-0.8.94
  additions; §6 next session updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 06:28:35 +02:00
56fc6d853d fix(P-13): repair 3 failing CI pytest tests for content_reports
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 59s
- Add early 403 in set_legal_hold_from_report for plain admin (before DB
  call), fixing test_legal_hold_from_report_requires_superadmin
- Update test_list_inbox_requires_platform_admin to mock DB COUNT query
  (returns cnt=0) so it exercises the club_admin code path correctly
- Extend test_patch_report_under_review mock row with target_type,
  target_id, resolution_note fields now required by the audit-log path

version: 0.8.94
module:  content_reports 1.5.1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 22:25:23 +02:00
5cf61289ec feat(P-13): Club-Admin Bearbeitung + Archiv-Trennung in Inbox
Some checks failed
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Failing after 40s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 1m3s
Berechtigungen:
- Club-Admins koennen Meldungen zu Vereinsmedien bearbeiten (PATCH + GET-Detail)
- Club-Admins koennen Legal Hold auf Vereinsmedien (visibility != 'official') setzen
- Neue Helfer: _assert_can_manage_report, _assert_can_set_legal_hold_from_report
- set_legal_hold_from_report: Superadmin-Only aufgehoben fuer Vereinsebene

Inbox UI:
- Offene Meldungen (submitted/under_review) im Hauptbereich
- Abgeschlossene Meldungen im kollabierbaren Archiv (standardmaessig zugeklappt)
- Legal-Hold-Button sichtbar fuer Club-Admins bei nicht-offiziellen Medien
- isClubAdmin + isPlatformAdmin aus OrgInboxContext verfuegbar

version: 0.8.93

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 22:15:29 +02:00
9af28faa35 fix(P-13): exercises.name -> exercises.title (500 in Inbox und Detail-Endpoint)
Some checks failed
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Failing after 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 1m4s
Die Tabelle exercises hat keine Spalte 'name', sondern 'title'.
Alle drei Stellen korrigiert: list_inbox (beide Branches) + get_content_report + Email-Lookup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:37:23 +02:00
34e93101f1 feat(P-13): Workflow-Management, Fehleranzeige, Badge-Update, Wieder-öffnen
Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Failing after 41s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 56s
- InboxPage: Workflow-Balken (Eingegangen > In Bearbeitung > Abgeschlossen)
- InboxPage: Meldungen können nach Abschluss wieder geöffnet werden (PATCH status=submitted)
- InboxPage: Bearbeitungskommentar separat speicherbar; Reviewer + Datum sichtbar
- InboxPage: Fehler beim Laden von Meldungen wird angezeigt statt leerem Bereich
- OrgInboxContext: contentReportsError State exposed
- ReportContentModal: onSuccess Callback -> Badge in Medienbibliothek sofort aktuell
- content_reports PATCH: Reviewer-Felder werden beim Wieder-öffnen zurückgesetzt
- content_reports PATCH: Kommentar-Änderungen ohne Statuswechsel werden im Audit-Log protokolliert

version: 0.8.92

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:32:44 +02:00
bb4d927090 fix(P-13): leserliche Journals, readOnly-Felder, Meldungs-Badge auf Medienkacheln
Some checks failed
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Failing after 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 56s
- ReportContentModal: Name/E-Mail readOnly fuer eingeloggte Nutzer
- MediaLibraryPage: content_report_filed im Journal leserlich (Meldegrund, Prioritaet, Status DE)
- MediaLibraryPage: Badge auf Medienkacheln zeigt offene Meldungsanzahl (nur Admins)
- media_assets.py: open_report_count Subquery fuer Admin-Sicht in Listendaten
- Inbox-500-Fix: ma.media_kind -> ma.mime_type (alle 3 Stellen)
- PATCH content_reports: Statuswechsel wird in Audit-Log protokolliert
- E-Mails: Dateiname statt Medium-ID, lesbarer Inhalt

version: 0.8.91

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:16:21 +02:00
8dd748e7d9 fix(P-13): include legal_hold_set/released in audit log constraint (migration 053)
Some checks failed
Deploy Development / deploy (push) Successful in 35s
Test Suite / pytest-backend (push) Failing after 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 58s
Die CHECK-Constraint auf media_asset_audit_log.event_type schloss
legal_hold_set und legal_hold_released aus, die P-11 bereits in
bestehende Zeilen geschrieben hatte (CREATE TABLE IF NOT EXISTS hatte
die Constraint in Migration 050 nie angewendet).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 19:43:23 +02:00
bacba311ae feat(P-13): implement content reporting enhancements, including email notifications and audit log entries
Some checks failed
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Failing after 0s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Failing after 3m41s
2026-05-11 19:36:23 +02:00
24bf3f7035 feat(P-13): update app version to 0.8.89, implement MediaPreviewModal and reporting functionality across media components
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 1m1s
2026-05-11 18:43:05 +02:00
0dbcd4175c feat: add report button for active media items without legal hold
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 1m1s
2026-05-11 18:15:27 +02:00
2f7e1e50ad feat(P-13): add content reporting functionality with modal and update version to 0.8.88
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 56s
2026-05-11 18:08:57 +02:00
60709df615 feat: Implement Content Reporting Backend
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Successful in 58s
- Added new API endpoints for content reporting, including submission, retrieval, and status updates.
- Created database migration for `content_reports` table to store report data.
- Integrated content reports into the existing admin inbox for better management.
- Implemented validation for report submissions, including required fields and email format.
- Added tests for content reporting functionality, covering various scenarios and edge cases.
- Updated frontend API utility to include new content report methods.
- Bumped app version to 0.8.87 and updated relevant page versions.
2026-05-11 17:54:53 +02:00
3c0e63757c feat(compliance): update compliance documents to reflect app version 0.8.86 and finalize P-11 implementation details
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 55s
2026-05-11 13:42:21 +02:00
ee54f8380f feat(P-11): implement legal hold functionality for media assets and update app version to 0.8.86
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 39s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 57s
2026-05-11 13:34:41 +02:00
f79f83e8f9 feat(P-11): enhance media asset handling with legal hold attributes and update version to 0.8.85
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 54s
2026-05-11 13:07:52 +02:00
fb8837574e feat(exercise): add support for exercise variants in ExerciseFullContent and related components
All checks were successful
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 55s
- Updated ExerciseFullContent to accept a new `variantId` prop and display variant-specific information.
- Enhanced TrainingCoachPage to pass `variantId` when rendering ExerciseFullContent.
- Refactored TrainingUnitRunPage to manage exercise context with variant support, including updates to modal handling.
- Improved UI to show variant details, including name, description, and execution changes where applicable.
2026-05-11 12:44:09 +02:00
1ce6d929ce feat(P-11): Implement Legal Hold functionality for media assets
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 54s
- Added backend support for Legal Hold with new endpoints to set and release holds on media assets.
- Introduced new database columns for managing Legal Hold status and reasons.
- Updated frontend to include UI elements for setting and releasing Legal Holds, including a confirmation dialog.
- Enhanced Media Library page to display Legal Hold status and actions for superadmins.
- Implemented comprehensive backend tests covering all aspects of Legal Hold functionality.
- Updated documentation to reflect changes in the upload rights specification and interface models.
- Bumped version to 0.8.84 and updated MediaLibraryPage version to 1.6.0.
2026-05-11 12:33:13 +02:00
1640fe6045 feat(compliance): update compliance documents for app version 0.8.83, including full implementation of P-06 and P-06+ features
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 54s
2026-05-11 12:11:30 +02:00
61baf26da6 feat(version): update app version to 0.8.83 and fix club_admin check in journal/correction endpoints
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 33s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 56s
2026-05-11 11:47:45 +02:00
0edc86e05a refactor(MediaLibraryPage): remove unused lcLabel function for lifecycle state
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 57s
2026-05-11 11:37:46 +02:00
b2b7bd423d feat(audit-log): implement full audit log for media assets, including visibility, copyright, metadata, and lifecycle changes; add correction declaration functionality
Some checks failed
Deploy Development / deploy (push) Failing after 18s
Test Suite / pytest-backend (push) Successful in 32s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Failing after 3s
Test Suite / playwright-tests (push) Successful in 55s
2026-05-11 10:26:50 +02:00
56e952f084 fix(p06): declared-Status deckt alle Sichtbarkeiten ab (kein Level-Vergleich mehr)
All checks were successful
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 58s
- check_rights_coverage: rights_status='declared' gibt immer 'ok' zurück
  (P-06-Erklärung gilt inhaltlich, nicht sichtbarkeitsabhängig)
- assert_rights_for_promotion: 'insufficient'-Pfad entfernt
- Tests: test_declared_private_insufficient_for_club → test_declared_covers_any_visibility

version: 0.8.81
module: media_rights

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:45:06 +02:00
6586d3b68b feat(version): update app version to 0.8.80 and add changelog entries for recent fixes and enhancements
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Successful in 54s
fix(RightsDeclarationDialog): change cancel button to icon for improved UI
feat(MediaLibraryPage): implement rights dialog for visibility promotions and enhance error handling
fix(version): update MediaLibraryPage version to 1.4.0 reflecting rights dialog changes
2026-05-11 09:39:15 +02:00
fff30b49e1 fix(media-library): update username references to name in media asset queries
Some checks failed
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Failing after 1m1s
2026-05-11 09:30:07 +02:00
f544975a6c feat(media-journal): add Superadmin media journal endpoint and UI integration
Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Failing after 1m5s
2026-05-11 09:24:39 +02:00
4bc24b4caf feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 33s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Failing after 1m2s
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.

Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
  RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
  wird in INSERT gespeichert

Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
  Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
  Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets

version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
42aec79ad1 fix(p06): RightsDeclarationDialog als echtes Modal (admin-modal-backdrop)
Some checks failed
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Successful in 47s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 10s
Test Suite / playwright-tests (push) Failing after 1m1s
modal-overlay/modal-content existierten nicht im CSS und hatten kein
Styling -- daher kein Backdrop, keine Zentrierung. Umgestellt auf
admin-modal-backdrop + admin-modal-sheet wie alle anderen Modals in der App.

version: 0.8.76

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 08:51:56 +02:00
9ac8200b41 fix(p06): P-06-Fragen direkt in Exercise-Upload-Modals eingebettet
Some checks failed
Deploy Development / deploy (push) Successful in 42s
Test Suite / pytest-backend (push) Successful in 41s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Failing after 1m41s
Kein zweites Dialog-Overlay mehr. Die Rechte-Erklarung ist jetzt
direkt als Formular-Abschnitt im Upload-Tab von
ExerciseInlineFileMediaModal und ExerciseInlineEmbedModal integriert.

- Datei auswaehlen + Titel + Rechte-Erklaerung in einem Schritt
- Validierung beim Klick auf Hochladen (Fehler inline angezeigt)
- RightsDeclarationDialog-Import entfernt
- radio-Namen eindeutig per Praefix (up-/emb-) damit beide Modals
  unabhaengig voneinander funktionieren

version: 0.8.76

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 08:44:37 +02:00
000e78e976 fix(p06): RightsDeclarationDialog in Exercise-Upload-Modals integriert
Some checks failed
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 35s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Failing after 1m19s
ExerciseInlineFileMediaModal (Upload-Tab) und ExerciseInlineEmbedModal
zeigen jetzt den vollstaendigen P-06-Einwilligungsdialog bevor der
API-Call ausgefuehrt wird. Vorher wurde der Backend-Fehler (400)
als nicht benutzbarer browser alert angezeigt.

- ExerciseInlineFileMediaModal: handleUploadAndInsert oeffnet Dialog,
  doUploadWithDecl haengt die 9 P-06-Felder an FormData an
- ExerciseInlineEmbedModal: submit oeffnet Dialog, doSubmitWithDecl
  haengt P-06-Felder an FormData an
- Backdrop-Click deaktiviert wenn Dialog offen

version: 0.8.76

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 08:25:00 +02:00
34235ef46d feat(compliance): P-06 Upload-Einwilligungsdialog v1-conservative
Some checks failed
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Failing after 1m23s
Implementiert server-seitige Rechteerklärungspflicht für alle Medien-Uploads
und Sichtbarkeits-Promotions (konservative Erstannahme: alle Uploads).

Backend:
- backend/media_rights.py (NEU): Kernmodul — validate_rights_declaration,
  check_rights_coverage, assert_rights_for_promotion, assert_rights_for_exercise_link,
  write_rights_declaration, update_rights_quick_fields
- backend/migrations/048_media_rights_declarations.sql (NEU): Tabelle
  media_asset_rights_declarations (Append-only Audit-Log), Felder
  rights_status/rights_visibility_level in media_assets
- backend/routers/media_assets.py: P-06-Pflichtprüfung in PATCH (single + bulk),
  POST /api/media-assets/{id}/rights-declarations (Re-Deklaration),
  GET /api/admin/media-rights/legacy-summary|legacy-assets (Admin-Endpoints)
- backend/routers/exercises.py: P-06-Felder in upload_exercise_media,
  assert_rights_for_exercise_link in attach_exercise_media_from_asset
- backend/main.py: admin_rights_router registriert

Frontend:
- frontend/src/components/RightsDeclarationDialog.jsx (NEU): 9-Felder-Dialog
  (konservativ: immer alle Fragen), Client-Validierung, VORLÄUFIG-Hinweis
- frontend/src/pages/MediaLibraryPage.jsx: Dialog-Intercept vor Upload,
  Altbestand-Indikator (legacy_unreviewed)
- frontend/src/utils/api.js: P-06-Felder in bulkUploadMediaAssets weitergeleitet

Tests:
- backend/tests/test_media_rights_declaration.py (NEU): 28 Unit-/Integrationstests
- backend/tests/test_media_assets_archive.py: P-06 fetchone-Slots + Mock ergänzt
- backend/tests/test_media_assets_copyright_promotion.py: check_rights_coverage gemockt
- tests/dev-smoke-test.spec.js: 5 P-06 E2E-Tests ergänzt

Dokumentation:
- docs/compliance-implementation.md: P-06-Abschnitt
- docs/compliance-package-register.md: Status ⚠️ teilweise umgesetzt (KRIT-04 offen)
- docs/compliance-roadmap.md: P-06 im Freigaben-Log

Offen: KRIT-04 (rechtliche Finalisierung Einwilligungsformulierung) — technisch
vollständig, Rechtstext VORLÄUFIG.

version: 0.8.75
module: media_rights 1.0.0, media_assets 1.13.0, exercises 2.20.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 08:12:44 +02:00
8cda5c27ec docs(compliance): P-06 fachlich-technische Spezifikation erstellt
Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 33s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 11s
Test Suite / playwright-tests (push) Failing after 53s
- docs/p06-upload-rights-spec.md (neu): vollständige Spezifikation für
  Upload-Einwilligungsdialog (§22 KUG, §8 DSGVO, Minderjährigenschutz)
  — IST-Analyse, Entscheidungsmatrix, Zielmodell (media_asset_rights_declarations
  + 3 neue Felder in media_assets), 10 Ziel-Flows, Legacy-Konzept,
  12 juristische Klärungspunkte, Umsetzungsplan P-06a–P-06e
- compliance-roadmap.md: Blocker 2 (P-06) mit Spec-Hinweis und
  juristischer Klärungsliste; §6 Freigabe-Empfehlung aktualisiert
- compliance-package-register.md: P-06 Letzter Stand mit Spec-Verweis
  und Zielmodell-Zusammenfassung ergänzt
- compliance-implementation.md: P-06 in Tabelle mit Spec-Hinweis

Keine Code-Umsetzung; Umsetzung erst nach juristischer Klärung.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:35:50 +02:00
aff3020b13 docs(compliance): Dokumentation auf Stand P-01b und P-01c aktualisiert (0.8.74)
Some checks failed
Deploy Development / deploy (push) Successful in 39s
Test Suite / pytest-backend (push) Successful in 33s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 18s
Test Suite / playwright-tests (push) Failing after 58s
- compliance-implementation.md: P-01c um copy-as-draft (0.8.72) und jsPDF
  + Abschnitts-Sortierung/-Einfuegen (0.8.74) ergaenzt; Testzusammenfassung
  auf 17 Playwright-Tests aktualisiert; Versionheader 0.8.74
- compliance-roadmap.md: App-Version 0.8.74; P-01-Blocker-Beschreibung
  vollstaendig (alle technischen Faehigkeiten); P-01c-Erweiterungen in
  Abschlussliste; Schnellreferenz aktualisiert
- compliance-package-register.md: P-01 Letzter Stand auf 0.8.74; Verweise
  und Hinweise ergaenzt (copy-as-draft, jsPDF, Sortierung); Fortschritt 0.8.74
- compliance-audit.md: Amendment §14.2 und §14.3 mit aktuellem Umsetzungsstand
  P-01/P-01b/P-01c; historische Befunde unveraendert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 22:17:51 +02:00
456ead72b6 feat(legal): echtes PDF-Download via jsPDF + Abschnitts-Sortierung/-Einfuegen
Some checks failed
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 32s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 13s
Test Suite / playwright-tests (push) Failing after 50s
- LegalPage und AdminLegalDocumentsPage: pdf.save() statt window.open/print
- AdminLegalDocumentsPage: Abschnitte per Pfeil-Buttons verschieben
- AdminLegalDocumentsPage: neuen Abschnitt an beliebiger Stelle einfuegen
- npm: jspdf installiert

version: 0.8.74
module: legal_documents 1.2.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 21:29:21 +02:00
030eb41429 Refactor code structure for improved readability and maintainability
Some checks failed
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 32s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Failing after 49s
2026-05-10 21:02:53 +02:00
5db8f8588c feat(legal): PDF-Export fuer Rechtstexte (Browser-Print)
Some checks failed
Deploy Development / deploy (push) Successful in 47s
Test Suite / pytest-backend (push) Successful in 58s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Failing after 49s
printLegalDocument() oeffnet formatiertes Druckfenster mit Titel,
Versionsnummer, Gueltigkeitsdatum und allen Abschnitten.

AdminLegalDocumentsPage: Drucker-Button laedt Volldokument und druckt.
LegalPage: PDF/Drucken-Button neben h1 wenn veroeffentlichtes Dokument geladen.

version: 0.8.73

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 16:47:52 +02:00
8992c300f1 feat(legal): Als-Entwurf-kopieren für Rechtstexte
Some checks failed
Deploy Development / deploy (push) Successful in 35s
Test Suite / pytest-backend (push) Successful in 33s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 8s
Test Suite / playwright-tests (push) Failing after 51s
POST /api/admin/legal-documents/{id}/copy-as-draft übernimmt Titel +
Inhalt des Quelldokuments und legt einen neuen Entwurf mit
nächster Versionsnummer an. Funktioniert für alle Status (draft/published/archived).

UI: Copy-Button (⎘) in jeder Dokumentzeile; nach Kopie wird die
Liste automatisch aktualisiert und der neue Entwurf ist sichtbar.

version: 0.8.72
module:  legal_documents 1.1.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 12:25:04 +02:00
b9adf6da84 fix(access-layer): legal_documents in EXEMPT_ROUTERS eintragen
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 37s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 43s
Router hat keinen Vereinsbezug (Plattform-Rechtstexte).
Öffentlicher Endpoint ohne Auth; Admin-Endpoints require_auth + is_superadmin().
ACCESS_LAYER_STRICT schlägt jetzt nicht mehr an.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 12:07:52 +02:00
80936b226d feat(compliance): P-01c Admin-konfigurierbare Rechtstexte (0.8.71)
Some checks failed
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Failing after 2s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 50s
DB 047: legal_documents (versioniert, draft/published/archived) +
legal_document_audit (Änderungslog); Partial-Unique-Index garantiert
max. ein published-Dokument pro document_type.

Backend: GET /api/legal-documents/{type}/published (kein Auth);
Superadmin-CRUD + Publish/Archive + Audit unter /api/admin/legal-documents.

Frontend: LegalPage lädt aus API mit Platzhalter-Fallback;
AdminLegalDocumentsPage (/admin/legal-documents) mit Tab-Navigation,
Versionsliste, Entwurf-Editor, Publish/Archive-Workflow, Änderungslog.
AdminPageNav: Link „Rechtstexte" ergänzt.

version: 0.8.71 (backend + frontend)
module:  legal_documents 1.0.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 11:53:07 +02:00
8bed0199b6 docs(compliance): sync P-01b in compliance documents (v0.8.70)
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 31s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Successful in 37s
- compliance-implementation.md: P-01b section added, version to 0.8.70
- compliance-package-register.md: P-01 entry updated with P-01b, version to 0.8.70
- compliance-roadmap.md: P-01b in closed table, Blocker 1 technical stand updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 11:38:57 +02:00
cfab5c2d69 fix(tests): stabilize login() helper with waitForURL before navigation
All checks were successful
Deploy Development / deploy (push) Successful in 35s
Test Suite / pytest-backend (push) Successful in 32s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 37s
Replace waitForLoadState('networkidle') with waitForURL (not /login) to
ensure React auth processing completes before tests proceed with
page.goto(). Prevents race condition where page.goto() interrupted
in-progress token storage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 11:36:10 +02:00
8261fa4420 feat(compliance): P-01b Mobile/PWA-Zugriff auf Rechtstexte via Einstellungen
Some checks failed
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 32s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 8s
Test Suite / playwright-tests (push) Failing after 49s
- SettingsLegalPage.jsx: neue Hub-Seite /settings/legal mit allen 4 Rechtstext-Links
- App.jsx: Route /settings/legal in ProtectedLayout registriert
- AccountSettingsPage.jsx: Link zu /settings/legal unterhalb System-Info
- 3 Playwright-Tests für P-01b (Einstellungen → Rechtliches → Links → Routen)
- Version: 0.8.69 → 0.8.70 (backend + frontend)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 10:51:20 +02:00
75d6a40817 docs(compliance): P-01 Dokumentation nach technischer Umsetzung
All checks were successful
Deploy Development / deploy (push) Successful in 35s
Test Suite / pytest-backend (push) Successful in 32s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 32s
- compliance-implementation.md: P-01-Abschnitt hinzugefügt (technisch
  teilweise umgesetzt), Testergebnisse, Version 0.8.69
- compliance-package-register.md: P-01  open → ⚠️ partially
  implemented; Fortschrittsblock aktualisiert; App-Version 0.8.69
- compliance-roadmap.md: P-01 in teilweise umgesetzte Pakete; Blocker 1
  aktualisiert; §6 nächste Freigabe auf P-06 / Etappe B; Anhang

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 09:47:40 +02:00
d7ed0c0e9b feat(compliance): P-01 Rechtstextseiten technisch anlegen (0.8.69)
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 32s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 32s
Öffentliche Routen /impressum /datenschutz /nutzungsbedingungen
/medienrichtlinie ohne Auth erreichbar. LegalPage-Komponente mit
deutlichem Platzhalterhinweis und strukturierten Pflichtfeldern je
Rechtstext. Links in LoginPage-Footer und DesktopSidebar-Footer.

KRIT-01 technischer Teil geschlossen. Juristische Inhalte bleiben
offen — Betreiber + Rechtsanwalt erforderlich.

- frontend/src/pages/LegalPage.jsx (neu)
- frontend/src/App.jsx: 4 öffentliche Routen
- frontend/src/pages/LoginPage.jsx: Rechtstext-Links im Footer
- frontend/src/components/DesktopSidebar.jsx: Links im Sidebar-Footer
- tests/dev-smoke-test.spec.js: 5 neue P-01-Tests

version: 0.8.69 (backend + frontend)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 09:41:45 +02:00
d73ed13f87 docs(compliance): Dokumentensynchronisierung nach P-12-Abschluss
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 32s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 25s
- compliance-roadmap.md: App-Version 0.8.67→0.8.68 in §2; Etappe A als
  abgeschlossen markiert; §6 nächste Freigabe von P-12 auf P-01 umgestellt;
  Gate 1 P-12-Zeile auf "Bereits erfüllt"; 2× TMG §5 → § 5 DDG;
  Schnellreferenz-Anhang aktualisiert
- compliance-package-register.md: Header App-Version 0.8.67→0.8.68
- compliance-implementation.md: Versionsangaben 0.8.67→0.8.68
- compliance-audit.md: unverändert (historischer Befund)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 09:32:55 +02:00
6abc911e94 fix(test): P-12 Playwright-Test akzeptiert confirm()-Dialog beim Logout
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 32s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 25s
DesktopSidebar-Logout ruft App.jsx handleLogout() auf, welcher
confirm('Wirklich abmelden?') zeigt. In Playwright headless gibt
confirm() standardmäßig false zurück → Logout wurde nie ausgeführt.
page.once('dialog', dialog => dialog.accept()) behebt das.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 09:17:19 +02:00
28ca64b5b4 feat(compliance): P-12 sessionStorage-Bereinigung bei Logout (0.8.68)
Some checks failed
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 31s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Failing after 26s
Sicherheit P-12 (MITT-05): logout() entfernt alle sj_coach_*-Schlüssel
aus sessionStorage gezielt per Präfix-Löschung. Fremde Schlüssel
(Browser-Extensions etc.) bleiben erhalten. Verhindert Datenleak bei
Nutzerwechsel im selben Tab (geteilter Rechner).

- AuthContext.jsx: Präfix-Schleife in logout()
- tests/dev-smoke-test.spec.js: Playwright-Test P-12 (injects/checks 3
  sj_coach_*-Schlüssel + 1 Fremd-Schlüssel; prüft selektive Löschung)

Compliance-Dokumentation:
- docs/compliance-implementation.md: P-12 , Version 0.8.68
- docs/compliance-package-register.md: kanonisches Paketregister (neu)
- docs/compliance-roadmap.md: lebende Steuerungs-Roadmap (neu)
- docs/compliance-audit.md: §20 Paket-ID-Stabilitätsregel

version: 0.8.68 (backend + frontend)
module: auth 1.2.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 09:08:28 +02:00
fc33bfbdeb feat(compliance): update retention policy and enhance password reset validation
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 34s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 8s
Test Suite / playwright-tests (push) Successful in 26s
- Adjusted retention policy to align with compliance requirements:
  - Changed HIDDEN_TO_PURGE_DAYS from 90 to 30 days.
- Enhanced password reset functionality to enforce a minimum password length of 8 characters.
- Updated tests to validate new password requirements and retention logic.
- Corrected umlaut in copyright error messages for clarity.
2026-05-10 08:26:15 +02:00
be0385922d Implement compliance report and workspace configuration
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 31s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 27s
- Added compliance implementation report detailing the status of various packages (P-03, P-04, P-05, P-07, P-23, P-24) and their technical changes, tests, and notes.
- Introduced a new workspace configuration file for the project to streamline development setup.
2026-05-09 22:11:33 +02:00
01be9ffcd4 feat(admin): restrict admin access and enhance navigation for superadmins
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 8s
Test Suite / playwright-tests (push) Successful in 23s
Test Suite / pytest-backend (pull_request) Successful in 24s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 7s
Test Suite / playwright-tests (pull_request) Successful in 24s
- Updated access control to ensure only superadmins can view admin routes and manage users.
- Refactored navigation components to reflect the new role-based access, removing platform admin references.
- Enhanced the admin user management page to streamline functionality for superadmins, including password reset options.
- Improved overall user experience by clarifying navigation paths and access permissions for different user roles.
2026-05-09 13:26:22 +02:00
30c1c259d2 feat(access): enhance visibility handling for club-related content
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 8s
Test Suite / playwright-tests (push) Successful in 23s
Test Suite / pytest-backend (pull_request) Successful in 23s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 6s
Test Suite / playwright-tests (pull_request) Successful in 30s
- Updated visibility logic for exercises, media assets, and training programs to ensure access is correctly managed based on active club memberships.
- Refactored SQL queries to streamline visibility checks for platform admins and club members, ensuring only relevant content is displayed.
- Improved user interface elements to reflect the status of club memberships, including visual indicators for inactive memberships.
- Enhanced test cases to validate the new visibility logic and ensure proper access control across various components.
2026-05-09 10:55:58 +02:00
24c70c5ea0 feat(memberships, profiles, clubs): enhance active club membership handling
All checks were successful
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
- Introduced a new utility function to filter and return only active club memberships, improving role management and access control.
- Updated various components and pages to utilize the new active club memberships function, ensuring only relevant memberships are considered.
- Enhanced user interface elements to reflect the status of club memberships, including visual indicators for inactive memberships.
- Improved backend logic for resolving tenant contexts and managing club roles based on active memberships.
2026-05-09 10:42:56 +02:00
624c19dcba feat(auth, profiles, club_memberships): enhance password reset and club admin management
All checks were successful
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
- Integrated a new password reset mechanism for user accounts, allowing admins to send reset links via email.
- Updated the management password reset functionality to differentiate between direct password setting and email link requests.
- Added validation to ensure at least one active club admin remains when modifying club member roles.
- Improved the user interface for password management in the admin panel, providing clearer feedback and options for password resets.
2026-05-09 10:32:33 +02:00
f54372d7b5 feat(profiles): implement management password reset functionality for admins
All checks were successful
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 24s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
- Added a new endpoint for superadmins and platform admins to reset passwords for other profiles.
- Introduced a management password reset feature in the admin user management page, allowing for secure password updates.
- Enhanced user interface to support password reset actions, including validation and feedback for successful updates.
- Updated API utility functions to handle the new password reset request.
2026-05-09 10:15:16 +02:00
c46f5f99be feat(admin): enhance admin navigation and user management features
Some checks failed
Deploy Development / deploy (push) Successful in 38s
Test Suite / pytest-backend (push) Failing after 27s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 8s
Test Suite / playwright-tests (push) Successful in 26s
- Updated admin navigation to conditionally display links based on user roles, including new components for platform admin routes.
- Refactored user management page to support club-specific roles and improved access control for platform and club admins.
- Introduced visibility clauses for media assets based on user roles and club memberships.
- Enhanced media library page to reflect user permissions and provide appropriate navigation options.
- Improved overall user experience with better role handling and navigation structure.
2026-05-09 10:02:56 +02:00
58a38702b9 feat(org-inbox): implement join request inbox for platform and club admins
All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 30s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 24s
- Added new API endpoint to retrieve join requests accessible by platform admins and club admins.
- Implemented frontend components to display join requests in the inbox, including navigation updates and badge notifications.
- Enhanced sidebar and navigation to conditionally show inbox based on user permissions.
- Updated styles for inbox components and added responsive design for dashboard integration.
- Introduced context management for inbox state and notifications on join request actions.
2026-05-09 09:13:38 +02:00
59fb8a5527 feat(exercises): bump version to 0.8.65 and enhance club media copyright handling
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 30s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 8s
Test Suite / playwright-tests (push) Successful in 25s
Test Suite / pytest-backend (pull_request) Successful in 23s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 6s
Test Suite / playwright-tests (pull_request) Successful in 23s
- Incremented application version to 0.8.65 and updated changelog with new features.
- Added support for setting default copyright notices for club exercises, allowing users to apply a common copyright notice to linked media assets.
- Enhanced error handling to prompt users for copyright information when required.
- Updated tests to verify the new copyright handling functionality.
2026-05-09 09:03:21 +02:00
a7cecca36f feat(clubs): enhance co-trainer ID handling for training groups
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 8s
Test Suite / playwright-tests (push) Successful in 23s
Test Suite / pytest-backend (pull_request) Successful in 23s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 7s
Test Suite / playwright-tests (pull_request) Successful in 26s
- Introduced new functions to normalize and convert co-trainer IDs for JSONB storage.
- Updated `create_training_group` and `update_training_group` methods to utilize the new JSONB handling for co-trainer IDs.
- Ensured invalid entries are discarded and only unique, positive integers are stored.
2026-05-08 13:37:30 +02:00
bab9b178a4 feat(exercises): update to version 0.8.64 and enhance inline media functionality
All checks were successful
Deploy Development / deploy (push) Successful in 35s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Successful in 23s
Test Suite / pytest-backend (pull_request) Successful in 23s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 14s
Test Suite / playwright-tests (pull_request) Successful in 22s
- Incremented application version to 0.8.64 and updated changelog with new features.
- Implemented inline media support in Rich Text Editor, allowing for drag-and-drop functionality and auto-scrolling.
- Enhanced media handling with a modal picker for media insertion, size selection, and improved user experience.
- Updated documentation to reflect changes in media handling and inline media specifications.
- Adjusted various API specifications to support new inline media features.
2026-05-08 13:27:15 +02:00
5cf775c920 feat(exercises): bump version to 0.8.64 and enhance media handling
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
Test Suite / pytest-backend (pull_request) Successful in 23s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 7s
Test Suite / playwright-tests (pull_request) Successful in 23s
- Incremented application version to 0.8.64 and updated changelog with new features.
- Improved media handling in the Rich Text Editor with auto-scrolling during drag-and-drop.
- Added new CSS styles for video thumbnails and enhanced layout for media items.
- Removed deprecated `ExerciseAttachmentMediaStrip` from the ExerciseFullContent component.
- Updated ExerciseFormPage to manage form dirty state and prevent data loss on navigation.
2026-05-08 12:35:28 +02:00
337f29401b feat(exercises): update inline media functionality and version bump to 0.8.63
All checks were successful
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 24s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 30s
- Incremented application version to 0.8.63 and updated changelog with new features.
- Enhanced inline media handling in the Rich Text Editor, including support for captions.
- Introduced new CSS styles for improved media display and layout in the editor.
- Replaced `ExerciseMediaEmbed` with `ExerciseAttachmentMediaStrip` for better media management in exercise content.
2026-05-08 12:20:24 +02:00
311a106d93 feat(exercises): enhance inline media functionality and update styles
All checks were successful
Deploy Development / deploy (push) Successful in 33s
Test Suite / pytest-backend (push) Successful in 24s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 27s
- Updated inline media markup to include a new data attribute for media size.
- Enhanced the Rich Text Editor to support media size selection when inserting inline media.
- Improved CSS styles for inline media display, accommodating different sizes (small, medium, full).
- Bumped version to 0.8.62 and updated changelog to reflect these changes.
2026-05-08 12:00:02 +02:00
979e328cef chore(release): bump version to 0.8.61 and update changelog
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
- Updated application version to 0.8.61.
- Added changelog entry for version 0.8.61, detailing the new inline help feature for the "Bild/Video im Text" functionality, including a visible 📎-chip in the editor and prompts for failed insertions.
2026-05-08 11:51:28 +02:00
cc51b0f08f feat(exercises): implement inline media support in exercise content
All checks were successful
Deploy Development / deploy (push) Successful in 37s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 24s
- Added functionality for inline media references in exercise text using `{{exerciseMedia:id}}` syntax, which normalizes to a canonical `<span>` element.
- Updated the frontend to utilize `ExerciseRichTextBlock` for rendering exercise content, allowing for embedded media display.
- Enhanced the Rich Text Editor to support inserting inline media placeholders.
- Version bump to 0.8.60 to reflect these changes in media handling and exercise content management.
2026-05-08 11:44:29 +02:00
f745e5d082 feat(nginx): add media location handling with Content-Security-Policy
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 24s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
Test Suite / pytest-backend (pull_request) Successful in 24s
Test Suite / lint-backend (pull_request) Successful in 0s
Test Suite / build-frontend (pull_request) Successful in 6s
Test Suite / playwright-tests (pull_request) Successful in 22s
- Implemented specific location blocks for `/media` and `/media/` to serve the React media library correctly.
- Added Content-Security-Policy headers to enhance security for media resources.
- Ensured proper handling of requests to prevent 404 errors on media reloads.
2026-05-08 11:15:19 +02:00
b6de1f15ea feat(media): implement centralized media archive and inline media linking
All checks were successful
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
- Introduced a centralized media archive (`/media`) with lifecycle management, including soft delete and recovery options.
- Enhanced media upload functionality to support multiple files and automatic type inference.
- Updated documentation to reflect the new media architecture and inline media linking specifications.
- Version bump to 0.8.59 to accommodate changes in media handling and database schema.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 10:56:43 +02:00
3ff47779e0 feat(tenant_context): enhance effective_club_id resolution for platform admins without header
All checks were successful
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 24s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 31s
- Updated resolve_tenant_context to use stored active_club_id if the club exists when no header is provided.
- Adjusted comments for clarity regarding platform admin behavior.
- Added unit tests to verify new behavior for platform admins in test_access_layer.py.

version bump to 1.0.5 for tenant_context module.
2026-05-08 10:45:58 +02:00
d3055f6f2f Medien: offizielle Assets nur Superadmin verwaltbar; Lesemodus; Upload-Verein vorausfüllen
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 28s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 10:35:28 +02:00
f354bd9f77 Governance: official nur Superadmin; Privat-Archiv Verein wählbar; Club-Übung Copyright; gleiche Medienordner
All checks were successful
Deploy Development / deploy (push) Successful in 36s
Test Suite / pytest-backend (push) Successful in 33s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Successful in 47s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 10:20:41 +02:00
01636b5baf Medien: Vereinsordner aus Namen statt library/club/c{id}
All checks were successful
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 24s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 8s
Test Suite / playwright-tests (push) Successful in 24s
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 10:06:05 +02:00
3e4fa0dc39 Medien: library-Pfade nach Medientyp (image/video/pdf/other) unterteilen
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 10:03:00 +02:00
8d85649cf8 refactor(media): Vereinsflache Ablage; private als u{profile} unter Verein
- club: library/club/c{id}/{sha} (kein shared/)
- private: library/club/c{id}/u{profile}/{sha} statt …/private/
- Dedupe private: sha + club_id + uploaded_by_profile_id
- version 0.8.54

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 10:00:37 +02:00
3f0d02edab fix(media): Vereinsordner für private/shared; kein library/private mehr
All checks were successful
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 25s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 8s
Test Suite / playwright-tests (push) Successful in 23s
- library/club/c{id}/private/* und …/shared/* (club visibility)
- Private Archiv-Upload: effective_club_id oder club_id Form; Plattform braucht Header
- media_assets.club_id bei private gesetzt; Dedupe pro Verein
- Übungs-Upload: dedupe_club für private aus exercise.club_id oder Mandant
- PATCH: club_id für private erhalten; Relocate inkl. private
- version 0.8.53

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 09:58:28 +02:00
4fb77d6927 feat(media): library/* Speicherpfade nach Sichtbarkeit und Verein
All checks were successful
Deploy Development / deploy (push) Successful in 35s
Test Suite / pytest-backend (push) Successful in 24s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 23s
- media_storage.library_storage_key + relocate_local_media_file
- Übungs- und Archiv-Upload nutzen library/private|club/c*|official
- PATCH/Bulk-Patch: bei visibility/club_id Datei umziehen, storage_key + exercise_media.file_path
- pytest: test_library_storage_key; Mocks angepasst
- version 0.8.52 / media_assets 1.7.0 / exercises 2.17.0

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-08 09:47:16 +02:00
497 changed files with 112724 additions and 9844 deletions

View File

@ -1,37 +1,43 @@
# Shinkan Jinkendo - Projekt-Status
**Stand:** 2026-05-07
**Version (Code):** 0.8.48 (`backend/version.py`, APP_VERSION)
**DB-Schema-Version:** `20260507045` (u. a. `media_assets`, `platform_media_storage`)
**Stand:** 2026-05-14
**Version (Code):** 0.8.140 (`backend/version.py`, APP_VERSION)
**DB-Schema-Version:** `20260515063` (`backend/version.py`, DB_SCHEMA_VERSION)
**Branch:** develop
---
## Executive Summary
**Aktueller Meilenstein (Medien):** **Medienbibliothek `/media`** ergänzt den Archiv-Picker: **Lifecycle-Filter** (aktiv / Papierkorb / ausgeblendet), **Copyright bearbeiten** (`PATCH`), **Vorschau** inkl. Papierkorb bei Verwaltungsrecht; API-Liste `copyright_notice`; zuvor **§4.2 Promotion official** (Übung + Medien).
**Aktueller Meilenstein (Medien):** Das **Medien-Archiv** (`media_assets` + `exercise_media.media_asset_id`) ist **produktiv nutzbar**: zentrale Bibliothek **`/media`** (Kacheln/Liste, Filter inkl. Lifecycle, Suche/Tags, Copyright, Bulk-Lifecycle und Bulk-PATCH), **Verknüpfung aus dem Archiv** in der Übungsbearbeitung (`POST …/media/from-asset`), **deduplizierter Speicher** unter **`library/…`**, **Papierkorb & Lifecycle**, plus **Inline-Medien im Rich-Text** (Modal-Picker, Größenwahl, Drag-and-Drop mit Auto-Scroll). **Governance:** Sichtbarkeit **`official`** nur **Superadmin** (Übungen und Medien). **Vereinsübungen** mit Datei-Assets: **Copyright-Pflicht** (API/UI). **Aktiver Verein:** Dropdown, Profilfeld `active_club_id`, Header `X-Active-Club-Id` und `effective_club_id` sind nach **0.8.59** synchronisiert.
**Parallel weiter relevant:** **Trainingsrahmenprogramm** (036037), **Progressionsgraph** (032034) — siehe **`TRAINING_FRAMEWORK_SPEC.md`**.
**Melde- und Transparenzpfad (P-13, seit 0.8.87 ff.):** **Inhaltsmeldungen** mit Workflow im Posteingang, Club-Admin-Beteiligung für Vereinsmedien, Legal-Hold-Anbindung, Badges in der Medienbibliothek; Folgepakete P-14P-16 bewusst offen (siehe `docs/HANDOVER.md`).
**Referenz:** [`library/FEATURES_DELIVERED_2026-Q2.md`](library/FEATURES_DELIVERED_2026-Q2.md) · Medien-Norm: [`technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`](technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md)
**Plattform-Rechtstexte (P-01, 0.8.950.8.96):** Admin-Editor mit **Abschnitts- und Vollvorschau** (Markdown); fortlaufende Abschnittsnummerierung in der Anzeige/PDF (Darstellung, nicht DB-persistent).
**Nächste Schritte — Medien & Archiv** (neu priorisiert, Stand 2026-05-07):
**Parallel weiter relevant:** **Trainingsplan Phasen & Streams** (Migration **063**, Coach + Planung **0.8.1370.8.140**; Handover **`docs/HANDOVER.md`** §3); **Trainingsrahmenprogramm** (036037), **Progressionsgraph** (032034) — siehe **`TRAINING_FRAMEWORK_SPEC.md`**. **Planungs-KI Progressionsgraph** (Roadmap-first, Auto-Optimierung, Katalog-Kontext **0.8.233**, **F15** Match-Dialog + getrennte Pfad-QS lokal): Ist-Doku **`docs/architecture/PLANNING_PROGRESSION_GRAPH_KI.md`**, Handover **`docs/HANDOVER.md`** §2.8.
**Referenz:** [`library/FEATURES_DELIVERED_2026-Q2.md`](library/FEATURES_DELIVERED_2026-Q2.md) Abschnitt 12 · Medien-Norm: [`technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`](technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md) (inkl. **Abschnitt 11 Inline-Medien**, umgesetzt) · **Fachlicher Nutzerüberblick:** [`../../docs/FACHLICHE_NUTZERFUNKTIONEN.md`](../../docs/FACHLICHE_NUTZERFUNKTIONEN.md)
**Nächste Schritte — Medien & Archiv** (Stand 2026-05-12, für **neue Session**):
1. ~~**Übung → `official` Promotion** inkl. Medien-Anhebung + **Copyright-Pflicht** bei `official` (Spec §4.2)~~ — umgesetzt (0.8.47).
2. ~~**Eigenständige Medienmanager-Seite**~~**Basis umgesetzt** (`/media`): Filter, Copyright, Lifecycle-Aktionen; Ausbau: Sichtbarkeit bearbeiten, Bulk, Quotas.
3. **Tests & Observability:** gezielte pytest-Abdeckung für Archiv/Verknüpfen; optional Retention-Job-Dry-Run dokumentieren.
2. ~~**Eigenständige Medienmanager-Seite**~~**Basis umgesetzt** (`/media`); Ausbau nach Bedarf: Quotas, feinere Bulk-Workflows, Sichtbarkeits-PATCH in der UI vereinheitlichen.
3. **Tests & Observability:** gezielte pytest-Abdeckung für Archiv/Verknüpfen/Lifecycle; Retention-Job (`scripts/media_retention_job.py`) in Betrieb/Doku verankern.
4. **S3 / externes Backend** hinter Speicher-Abstraktion (Spec §7) — nach stabiler Nutzung lokaler/NAS-Pfade.
5. **Inline-Medien im Fließtext** (Spec §11) — bewusst **nach** Promotion/Copyright und tragfähigem Archiv-Workflow.
5. **Inline-Medien im Fließtext (Spec §11)** — **Basis umgesetzt (0.8.600.8.64)**: Platzhalter-Syntax, zentraler Renderer, Modal-Picker, Drag&Drop + Auto-Scroll; offen: weitere UX-Politur und ggf. strategischer Umbau auf reine Asset-Referenz (separat zu entscheiden).
**Inline:** Leitplanken in **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` §11**; kein Big-Bang vor stabiler Archiv-/Governance-Basis.
**Inline:** verbindliche Leitplanken **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`** Abschnitt 11; Umsetzung aktiv im Produktpfad (RTE + Anzeige).
---
**Nächste Schritte (Auszug — Planung/Rahmen):**
**Nächste Schritte (Auszug — Planung/Rahmen & Kombination):**
1. KalenderUI: „Aus Rahmen übernehmen“ an **`from-framework-slot`** anbinden; ggf. Bulk.
2. Governance: Sichtbarkeit **club/official** für Rahmen so ausprägen, dass andere Trainer kopieren dürfen (Policy + API).
3. Optional Backlog Graph: Alternativgruppen / bessere Visualisierung (**§4**).
4. **Breakout / Coaching (Arbeitspaket):** Backend-Konsistenz `phases`↔`sections`, Run-UI vs. Spec (Stream-Tabs), Vorlagen phasenfähig, E2E-Smoke — siehe **`docs/HANDOVER.md`** (Tabelle „Coaching & Breakout“).
5. **Kombinationsübungen / Coach (Fachspez §10.6):** Coach **Stufe B/C** (archetypgesteuerte Durchführung); **Archetyp-Verwaltung** jenseits Code-Konstanten; **Massen-Vorbelegung** aller Slot-Zeit/Anzahl-Felder; **serverseitige** Validierung Profil ↔ Archetyp — siehe `TRAINING_MODULES_IMPLEMENTATION_PLAN.md` (Pakete **4e4g**) und `COMBINATION_TIMING_PROFILE_PLAN.md`.
---
@ -50,7 +56,7 @@
| **030** | **training_unit_exercises.exercise_variant_id** | ✅ | 🔲 |
| **032034** | **Progressionsgraph Übung→Übung** | ✅ | 🔲 |
| **035037** | **Rahmenprogramm, BibliothekKopf, SlotBlueprintUnits** | ✅ | 🔲 |
| **040045** | **u. a. Mitgliedschaften, Übungs-Governance, `media_assets`, Plattform-Speicherpfad** | ✅ | 🔲 |
| **040046** | **u. a. Mitgliedschaften, Übungs-Governance, `media_assets` (046 z.B. Tags/GIN), Plattform-Speicherpfad** | ✅ Dev | 🔲 Prod |
---
@ -77,7 +83,9 @@ Die exakten Zahlen hängen von der Umgebung ab (siehe Admin/DB). Die Skills/Übu
- [x] **Varianten** (CRUD, Reorder, Voraussetzung) + Anzeige im Detail
- [x] **Progressionsgraph zwischen Übungen** (Bibliotheks-Container, Kanten, Sequenz-Bulk, Varianten-Knoten — Zwischenstand, siehe TRAINING_FRAMEWORK_SPEC §4)
- [x] Medien (Upload/Embed, rollenabhängige Größenlimits)
- [x] Suche & Filter (Multi-Filter, Chips, Fokus beim Suchen)
- [x] Suche & Filter (Multi-Filter, Chips, Fokus beim Suchen; **Freigabelevel** als UI-Begriff für `visibility`)
- [x] **Übungsformular:** Registerkarten (Stammdaten … Medien & Mehr), kompakte Chip-Editoren, Varianten-Speichern über Aktionsleiste
- [x] **Fähigkeiten-Intensität** ohne Primär-Flag (`niedrig`/`mittel`/`hoch`; Backend `is_primary` immer false)
- [x] Exercise Blocks (Bausteine)
- [x] Saved Searches (wo implementiert)
@ -87,6 +95,8 @@ Die exakten Zahlen hängen von der Umgebung ab (siehe Admin/DB). Die Skills/Übu
- [x] **Optionale Zuordnung einer Übungsvariante** pro Eintrag (`exercise_variant_id`)
- [x] **Trainingsrahmenprogramm Bibliothek** (Ziele, Slots, Kontext) + **SlotBlueprints** in `training_units` (036037)
- [x] **Materialisierung** aus RahmenSlot (`POST …/training-units/from-framework-slot`; UIAnbindung optional)
- [x] **Phasenmodell & parallele Streams** pro Einheit (Migration **063**): `training_unit_phases`, `training_unit_parallel_streams`; GET mit **`phases`** + flachen **`sections`**; PUT mit **`phases`** (App **0.8.1370.8.140**)
- [x] **Coaching-Modus** für Breakout: Timeline mit Split-Wahl, Rejoin vor Ganzgruppe/nächstem Split, Nachbereitung speichern → Plan & Ablauf (`TrainingCoachPage`, `trainingPlanUtils.js`)
- [ ] Kalender-View / erweiterte Roadmap (Backlog)
**MediaWiki Import:**
@ -96,6 +106,7 @@ Die exakten Zahlen hängen von der Umgebung ab (siehe Admin/DB). Die Skills/Übu
**Skills-System:**
- [x] Hierarchisches Schema, Fokusbereich-Zuordnung, Exercise-Skill mit Levels
- [x] **Gewichtetes Fähigkeiten-Profil (Phase 3):** Module, Rahmenprogramme, Regressionspfade; Peer-Kontext getrennt; Listen-Filter + Discovery — **`technical/SKILL_SCORING_SPEC.md`**
**Admin-UI:**
@ -103,10 +114,11 @@ Die exakten Zahlen hängen von der Umgebung ab (siehe Admin/DB). Die Skills/Übu
### 🔲 In Arbeit / Backlog
- [x] **Medien:** Papierkorb (§5), Retention-Job, Archiv-API, „Aus Archiv verknüpfen“, Picker/Vorschau in Übungsbearbeitung (Release 0.8.42 ff.)
- [x] **Medien:** Promotion Übung↔Medien + Copyright-Pflicht `official` (Spec §4.2) — 0.8.47
- [x] **Medien:** Medienbibliothek `/media` (Lifecycle-Filter, Copyright PATCH, Vorschau); Ausbau Manager/Bulk/S3 — Roadmap
- [ ] **Medien:** Inline im Fließtext — nach Spec §11, nach Promotion/Archiv-Reife
- [x] **Medien:** Papierkorb (§5), Retention-Job, Archiv-API, „Aus Archiv verknüpfen“, Picker/Vorschau in Übungsbearbeitung (0.8.42 ff.)
- [x] **Medien:** Promotion Übung↔Medien + Copyright-Pflicht `official` / Vereins-Copyright-Regeln (Spec §4.2, Übungen+Assets)
- [x] **Medien:** Medienbibliothek `/media` (Filter, Tags, Copyright, Bulk-Lifecycle/PATCH, Lesemodus für eingeschränkte Rollen bei `official`)
- [x] **Medien:** Speicherpfad-Konvention `library/…`, Plattform-Speicher-Konfiguration (`platform_media_storage`), Mandanten-/Governance-Umzug bei Asset-Änderungen
- [x] **Medien:** Inline im Fließtext — Spec §11 (Platzhalter, Modal-Picker, Größenwahl, Drag&Drop + Auto-Scroll, Rückweg aus Vorschau)
- [ ] Admin-UI für Skill-Kategorien (CRUD) falls noch offen
- [ ] Responsive Design / Dark Mode / PWA
- [ ] KI-Suche (`ai_search`) über reine Volltextsuche hinaus
@ -137,7 +149,7 @@ Die exakten Zahlen hängen von der Umgebung ab (siehe Admin/DB). Die Skills/Übu
### Dev
Branch `develop`; Migrations bis mindestens **045** auf dem aktuellen Entwicklungsstand; Details in `backend/version.py`.
Branch `develop`; Migrations bis mindestens **046** auf dem aktuellen Entwicklungsstand; Details in `backend/version.py`.
### Prod
@ -149,17 +161,19 @@ Deployment der oben genannten Migrationen und Datenabgleich nach internem Prozes
| Dokument | Pfad | Stand | Status |
|----------|------|-------|--------|
| Lieferliste Q2 2026 | `library/FEATURES_DELIVERED_2026-Q2.md` | 2026-05-05 | ✅ Aktualisiert (u. a. 036037) |
| Fachliche Nutzerfunktionen (Design/Product) | `docs/FACHLICHE_NUTZERFUNKTIONEN.md` | 2026-05-14 | Phasen/Coach/Rejoin |
| Lieferliste Q2 2026 | `library/FEATURES_DELIVERED_2026-Q2.md` | 2026-05-14 | §11a Breakout |
| Trainingsrahmen + Graph | `technical/TRAINING_FRAMEWORK_SPEC.md` | 2026-05-05 | ✅ §2 Blueprint |
| Anforderungen (Index) | `functional/SHINKAN_REQUIREMENTS.md` | 2026-04-27 | ✅ Neu |
| Database Schema | `technical/DATABASE_SCHEMA.md` | 2026-05-05 | ✅ Aktualisiert (037) |
| Domain Model | `functional/DOMAIN_MODEL.md` | 2026-05-05 | ✅ Aktualisiert |
| API Übungen | `technical/EXERCISES_API_SPEC.md` | 2026-04-30 | ✅ Ergänzt Progressions-API |
| Anforderungen (Index) | `functional/SHINKAN_REQUIREMENTS.md` | 2026-05-12 | Verweis Nutzerüberblick |
| Database Schema | `technical/DATABASE_SCHEMA.md` | 2026-05-07 | ✅ Hinweis 040046 Medien (Kurz) |
| Domain Model | `functional/DOMAIN_MODEL.md` | 2026-05-14 | Parallele Streams Ist 063 |
| API Übungen | `technical/EXERCISES_API_SPEC.md` | 2026-05-08 | ✅ Medien/Inline-Workflow ergänzt |
| Frontend Routing | `technical/EXERCISES_FRONTEND_ROUTING.md` | 2026-04-30 | ✅ Ergänzt UI-Hinweise |
| Search & Filter | `technical/SEARCH_FILTER_SPEC.md` | 2026-04-27 | ✅ Aktualisiert (Liste UX) |
| Media Upload | `technical/MEDIA_UPLOAD_SPEC.md` | 2026-04-27 | ✅ Aktualisiert (Limits) |
| Medien-Archiv & Lifecycle | `technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` | 2026-05-07 | ✅ §11 Inline-Plan, Drift-Tab |
| Projektstatus | `PROJECT_STATUS.md` | 2026-05-07 | ✅ Medien-Meilenstein aktualisiert |
| Media Upload | `technical/MEDIA_UPLOAD_SPEC.md` | 2026-05-07 | ✅ Verweis Archiv/Inline |
| Medien-Archiv & Lifecycle | `technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` | 2026-05-08 | ✅ Ist-Changelog + §11 Inline erweitert |
| Parallele Streams (Fach/Technik) | `functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md`, `technical/PARALLEL_TRAINING_STREAMS_SPEC.md` | 2026-05-14 | Ist-Stand P1 teils |
| Projektstatus | `PROJECT_STATUS.md` | 2026-05-14 | Keyset, KPIs, Breakout/Coach Kurzverweis |
---
@ -170,4 +184,4 @@ Deployment der oben genannten Migrationen und Datenabgleich nach internem Prozes
---
**Letzte Aktualisierung:** 2026-05-07
**Letzte Aktualisierung:** 2026-05-14 (Version 0.8.140, DB 063, Handover Coaching/Breakout)

View File

@ -0,0 +1,100 @@
# KI-Unterstützung bei Übungen Produkt-Vision
**Version:** 0.1
**Datum:** 2026-05-22
**Status:** Zielbild / Anforderungsgrundlage (nicht gleich Ist-Spec technische Schnittstellen: **`technical/KI_FEATURES_SPEC.md`**, **`technical/AI_PROMPT_SYSTEM_SPEC.md`**, **`technical/AI_TRAINING_PLANNING_CONCEPT.md` §1.1**)
**Zielgruppe:** Product, Trainer-UX, später Admin-Werkzeuge
---
## 1. Übergeordnete Prinzipien
1. **Immer Vorschlag, nie blind überschreiben**
Die KI liefert **Vorschläge** (Änderungen, Ergänzungen, Strukturen). Bestehende Inhalte werden **nicht** still ersetzt. Übernahme erfolgt durch den Nutzer: **teilweise** (Felder/Stellen/Blöcke) oder **komplett** („Vorschlag gesamt akzeptieren“).
2. **Granulare Anforderung im Editor**
Innerhalb einer Übung soll KI-Unterstützung **feldbasiert oder bereichsbasiert** auslösbar sein (z.B. nur „Anleitung schärfen“, nur „Fähigkeiten“, nur „Variantenrahmen“) **oder** als **Komplettüberarbeitung** mit klarem Warnhinweis (Umfang/transparenter Diff).
3. **Nachweisliche Herkunft**
Übernommene KI-Inhalte werden technisch dort abgebildet, wo bereits vorgesehen (z.B. **`summary_ai_generated`**, **`exercise_skills.ai_suggested`**) und um analogen Hinweis für weitergehende Textfelder/Varianten **erweitert**, sobald Implementierung konkret wird.
---
## 2. Funktionsbereiche (Vision)
### 2.1 Von der Idee zur kompletten Übung („Zielausbau“)
**Einstieg minimal:** Kurzbeschreibung oder Stichwort, **Ziel** („was soll erreicht werden?“), wenige **Rahmenparameter** (z.B. Fokusbereich, Trainingszeit, Teilnehmerzahl, Alter, Platzausstattung, Sicherheitshinweise konkrete Dropdowns/Freifelder in UX später festlegen).
**KI-Aufgabe:** aus diesem dünnen Kontext einen **übernehmbaren Entwurf** einer **ganzen Übung** erzeugen: TitelVorschlag, Ziel-/Durchführungstext, Sicherheit/Organisation, ggf. Trainerhinweise **immer als Vorschlagspaket**, nicht als Speicher ohne Bestätigung.
**Abgrenzung:** Kombinationsübungen / komplexe Methodenprofile können **phasenweise** später einbezogen werden (Verweis Fachspez Trainingsmodule).
### 2.2 Anleitung (Durchführung / „Ausführung“) maximal hilfreich
**Ziel:** Die **Ausführungs-/Anleitungsbereiche** sollen sich **didaktisch klar**, **teilbar** und **wieder verwendbar** lesen ohne den Trainer zu entmindigen.
**KI-Aufgabe:** Überarbeitungsvorschlag für Struktur (nummerierte Schritte, Zeiten pro Block, häufige Fehler, Progressionshinweise innerhalb der Übung wo sinnvoll). **Selektiver** Aufruf: nur diese Felder oder nur ein markierter Abschnitt (wenn UX Textauswahl unterstützt).
### 2.3 Kurzbeschreibung (`summary`)
**KI-Aufgabe:** Aus den **relevanten Übungstexten** eine **Liste-/Karte-taugliche** Kurzfassung generieren — wie in **`KI_FEATURES_SPEC.md`** beschrieben, mit **Ablehnen / Bearbeiten / Übernehmen**.
### 2.4 Einordnung primär **Fähigkeiten**
**KI-Aufgabe:** automatische Erkennung und **Zuordnung** zum **globale Skills-Katalog** inklusive:
- **Intensität** (`exercise_skills`)
- **Skill-Level**: `required_level` / `target_level` nach **kanonischen Slugs** (Backend-konform)
- **`is_primary`** / Priorisierung wo fachlich sinnvoll
**Prompt-Kontext für Qualität:** Stammfelder wie `skills.description`, **`karate_relevance`**, **`relevance_level`**, **`focus_areas`**, optional **`skill_level_definitions`** nur für eine **kurze Kandidatenliste** (zweite Runde möglich) keine vollständigen Romane für den gesamten Katalog auf einmal.
### 2.5 Varianten (optional, später prioritär erwägenswert)
**Vision:** Aus Ziel-/Durchführungstext **mehrere sinnvolle Ausprägungen** als **Übungsvarianten** vorschlagen oder einzelne erzeugen (**progression**, **Schwierigkeit**, andere Paararbeit, Gerätevariation) mit **übernehmbarem** Datenmodell gleich dem bestehenden `exercise_variants`.
**Randbedingungen:** Validierung gegen Übungstyp (Kombinationsübungen ohne Varianten laut Produktstand), keine Halluzination fremder IDs.
---
## 3. Kontextbezug später: Nachbearbeitung aus der Trainingsplanung
**Vision:** Hinweise aus der **Nachbearbeitung** einer Trainingseinheit (IstMinuten, Trainer-Notizen, Abweichungen „was lief nicht?“ je nach Datenmodell) fließen **optional** als Kontext in eine **erneute KI-Überarbeitung der betroffenen Übung** ein („Übung aus den Erfahrungen der Gruppe verbessern“).
**Konsequenz technisch später:** Zugriffsrechte, Mandant, keine unzulässige Verknüpfung personenbezogener Sportlerdaten; Aggregation auf **Einheit-/Gruppe** und **bereits dokumentierte Trainer-Insights**.
---
## 4. Admin: Massenverarbeitung und Analyse
**Vision für Plattform-/Vereins-Admins:**
| Thema | Richtungsziel |
|-------|----------------|
| **Massenverarbeitung** | Batch: z.B. Zusammenfassungen nachziehen, fehlende Skills vorschlagen, einheitlicher Stil bei importiertem Bestand — immer mit **Review-Queue**, nicht ohne menschliche Freigabe skalierungskritisch. |
| **Analyse / Qualität** | Werkzeugkasten oder Berichte: **welche Übungen** sollten überarbeitet werden? z.B. leere/kurze `summary`, fehlende `goal`/`execution`, **fehlende oder widersprüchliche Skill-Zuordnung**, Import-Herkunft ohne Plausibilität, Kombi-Slots unvollständig, sehr alte Imports. |
| **Lückenkarten** | Z.B. Abgleich gegen **Skill-Discovery**/Profil-Analysen („keine Übung deckt Fähigkeit X ab“ auf gewähltem Korpus); Verbindung zu **`skill-discovery`** entscheidend später im Detail (kein automatischer Rewrite ohne Policy). |
**Governance:** Sichtbarkeit (`official`, Verein), Rechte (**Superadmin** vs. Vereinsinhalt), Audit der KI-Anwendung bei Massenjobs.
---
## 5. Phasierung (überarbeitungsfähig)
| Phase | Inhalt |
|-------|--------|
| **P0** | KI-Service + Prompts aus DB + **Suggestion-only** UX; Kern: **Summary** + **Skills** (wie Spec-Minimum), **ein Feld / Komplettpaket mit Diff** nach UX. |
| **P1** | **Anleitung überarbeiten** + **„von Idee zur Übung“** (Zielausbau) mit Rahmenparameter-Form |
| **P2** | **Variantenvorschläge** mit strenger Validation |
| **P3** | **Planungs-/Nachbereitungskontext** |
| **P4** | **Admin** Massen-/Analyse (Queue + Reports + Governance) |
---
## 6. Offene Produkt-/Fachfragen
- Minimaler **Parameterbau** beim Zielausbau (Pflicht vs. optional).
- Umgang mit **Medien**/Inline-Verweisen beim KI-Text nichts zerstören, Platzhalter erhalten (siehe Medien-Spec §11).
- **Kombinationsübungen:** welche Teilaspekte dürfen KI anfassen?
- Limits: **Tokens**, **Rate-Limits**, Kostenüberwachung pro Verein/global.

View File

@ -1,7 +1,7 @@
# Shinkan Jinkendo - Fachliches Domänenmodell
**Version:** 0.4.3
**Stand:** 2026-05-05 (Migration **036037:** Rahmen nur Bibliothek; SlotInhalt über Blueprint`training_units` + Sektionen/Items wie Planung — siehe `TRAINING_FRAMEWORK_SPEC.md` §2)
**Version:** 0.4.6
**Stand:** 2026-05-14 (Fachlicher Nutzerüberblick: `docs/FACHLICHE_NUTZERFUNKTIONEN.md`)
**Basis:** `shinkan_anforderungsdokument_entwurf.md` + Fähigkeitsmatrix
---
@ -57,7 +57,7 @@ Haupt-Kategorie (KARATE / ALLGEMEINE)
- Selbstverteidigung ✓
- Gewaltschutz ✓
**Technische Umsetzung:** M:N Beziehungen mit `is_primary` Flag.
**Technische Umsetzung:** M:N-Beziehungen mit optionalem `is_primary`-Flag bei **Fokusbereichen, Stilrichtungen, Trainingsstilen und Zielgruppen** — nicht bei `exercise_skills` (dort nur Intensität `niedrig|mittel|hoch`).
### 3. Hierarchischer Kontext (§8.1)
@ -407,10 +407,9 @@ skill_level_definitions (
- Reaktion (Koordination, target_level: 2, intensity: mittel)
**Attribute pro Fähigkeitsbezug:**
- is_primary (Haupt- oder Nebenfähigkeit)
- intensity (niedrig/mittel/hoch)
- required_level (Voraussetzung, 1-5)
- target_level (Ziel-Level, 1-5)
- `intensity` — Nutzeneinschätzung: **niedrig | mittel | hoch** (Standard **mittel**)
- `required_level` / `target_level` — Stufen-Spanne (kanonische Slugs basis … optimierung)
- `is_primary` — Legacy-Feld; **nicht mehr in der UI**, beim Speichern immer false; Scoring ignoriert es
**🆕 Fokusbereich-Filterung:**
- Bei Übungen mit Fokusbereich "Karate" sollten primär KARATE-Fähigkeiten zugeordnet werden
@ -466,6 +465,8 @@ skill_level_definitions (
**Fachliche Grenze aktuell:** Mehrere gleichwertige „Pakete“ paralleler Alternativen sind **modellierbar** (mehrere ausgehende Kanten), aber noch **nicht** über eine dedizierte „Alternativgruppe“ in der UI trivial pflegbar; siehe `technical/TRAINING_FRAMEWORK_SPEC.md` §4.
**KI-Planung (Workbench, Stand 0.8.233):** Am Graph können Trainer neben Kanten ein **`planning_roadmap`**-Artefakt (Curriculum-Stufen) und **`planning_catalog_context`** (Primärfokus, Stilrichtung, Trainingsstil, Zielgruppe aus den Katalog-Dimensionen §1) pflegen. Die Roadmap-first-Pipeline matcht Übungen pro Stufe; Didaktik und Reihenfolge kommen aus Roadmap + QS, nicht aus Technik-Hardcoding. **Geplant (H1):** Katalog-Dimensionen zusätzlich als **Prompt-Snippets** in LLM-Aufrufen (Priorität Primärfokus → Trainingsstil → Zielgruppe → Stilrichtung) — **`docs/architecture/PLANNING_CATALOG_PROMPT_SNIPPETS.md`**. Technische Details: **`docs/architecture/PLANNING_PROGRESSION_GRAPH_KI.md`**. Für **Trainingsplanung** (Einheit, Abschnitt, Rahmen-Slot) gelten dieselben Katalog- und Retrieval-Bausteine mit anderen Scopes — Phase G, siehe Roadmap **`PLANNING_KI_ROADMAP.md`**.
### TrainingsrahmenVorlage (Rahmenprogramm, CURR002 Stufe2 / CURR009)
**Abgrenzung:** Eine **einzeilige** TrainingsplanMikrovorlage (`training_plan_template`) strukturiert **eine** Einheit; das **Rahmenprogramm** ist eine **eigene Bibliotheksentität** mit **sortierten SessionSlots**, **mindestens einem** formulierten **Entwicklungsziel** (Zielliste, **CURR011**) und einem **vollständigen Ablauf** pro Slot (**`training_unit_sections` + `training_unit_section_items`** wie bei geplanten Einheiten — **CURR010** inhaltlich, technisch seit **037** identisch zur Planungsstruktur). Der persistierte **Progressionsgraph** zwischen Übungen bleibt **optional** (**CURR013**).
@ -474,6 +475,52 @@ skill_level_definitions (
**Konkretisierung (037/API):** `POST /api/training-units/from-framework-slot` legt eine geplante Einheit aus dem SlotBlueprint an; **`origin_framework_slot_id`** dient als Herkunftsreferenz (**Lineage light**; weiteres Feedback/LineageKonzept: Konzeptpapier Schritt **E**).
### Trainingsmodul (Bibliothek)
**Abgrenzung:** Wiederverwendbare **Übungsfolge** (`training_modules` + `training_module_items`) — kein Kalendertermin, kein Rahmen-Slot. Übernahme in geplante Einheiten über Planung (`apply-training-module`).
**Governance:** wie andere Bibliotheksartefakte (`visibility`, `club_id`, `library_content_visibility_sql`).
### Gewichtetes Fähigkeiten-Profil (Planungs-Bausteine, Phase 3)
**Zweck:** Aus den verknüpften Übungen eines Planungsartefakts wird ein **Fähigkeiten-Profil** berechnet (Trainingsgewicht je Fähigkeit). Trainer vergleichen Bausteine **innerhalb desselben Typs**, um z.B. das passendste Modul für eine Ziel-Fähigkeit zu finden.
**Artefakttypen (getrennte Peer-Kontexte):**
| Typ | Vergleich |
|-----|-----------|
| `training_module` | nur sichtbare **Module** |
| `framework_program` | nur sichtbare **Rahmenprogramme** |
| `progression_graph` | nur sichtbare **Regressionspfade** |
**Metriken (Nutzer):**
- **Score / Gewicht** — absolut (Dauer × Häufigkeit × Intensität × Stufen-Spanne)
- **Prozent** — Anteil am stärksten sichtbaren Peer **desselben Typs** für diese Fähigkeit (max. 100 %)
- **★** — stärkster Peer in diesem Kontext
**UI:** Profile in Editoren; KPI-Kacheln und Filter in Listen (`/planning/framework-programs`, `/planning/training-modules`); Discovery auf der Fähigkeiten-Seite.
**Technik:** `backend/skill_scoring.py`, `routers/skill_profiles.py` — Spec **`technical/SKILL_SCORING_SPEC.md`**.
### Parallele Trainingsstreams (Breakout)
**Fachlich:** Eine Kalender**Einheit** kann aus **Phasen** bestehen — z.B. gemeinsamer Block, dann **beliebig viele parallele** „Teilstrecken“ (**Streams**) mit je eigenem Miniplan (Abschnitte/Übungen), erneut gemeinsamer Block. Das ist **nicht** dasselbe wie ein **RahmenprogrammSlot** (SerienSession über Wochen): Slots strukturieren **mehrere Einheiten** in einem Programm; **Streams** strukturieren **gleichzeitige** Abläufe **innerhalb einer** Einheit.
**Sonderfall Stationen:** Rotation kann **innerhalb** einer StreamPlanung über **Kombinationsübungen** (Methodenprofil/Archetyp) abgebildet werden; hallenweit **synchron** getaktete Rotation ist eine **erweiterte** Ausbaustufe (siehe Fachkonzept).
**Umsetzung (2026-05, Migration 063, App 0.8.137 ff.):** Tabellen **`training_unit_phases`** und **`training_unit_parallel_streams`**; **`training_unit_sections`** mit **`phase_id`** und **`parallel_stream_id`** (exakt eine Zuordnung pro Sektion). **`GET /api/training-units/:id`** liefert **`phases`** (verschachtelt) und flache **`sections`**. **Coaching** und **Durchführung** nutzen dieselbe Phasenlogik im Frontend (`trainingPlanUtils.js`).
**Dokumentation:** `functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md`, Umsetzung `technical/PARALLEL_TRAINING_STREAMS_SPEC.md`.
## Medien-Archiv & Übungs-Anhänge (Stand 2026-05-07)
- **`media_assets`:** Zentrale Datei-/Asset-Zeile (technisch u.a. SHADedupe, Sichtbarkeit, `club_id`, Lifecycle, Copyright, Speicherreferenz unter `library/…`). Siehe **`DATABASE_SCHEMA.md`**, **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**.
- **`exercise_media`:** Verknüpfung **Übung ↔ Asset** (`media_asset_id`) oder **Embed** ohne Asset; Felder wie `context` (`ablauf` \| `detail` \| `trainer_hint`), Sortierung, Primär-Medium.
- **`platform_media_storage`:** Konfiguration effektiver Medienwurzel (Superadmin, relativ zu `MEDIA_ROOT`).
- **Produkt:** Medienbibliothek **`/media`**; in der Übungsbearbeitung Upload, Entfernen der Verknüpfung, **Aus Archiv verknüpfen**; Governance **`official`** und Copyright-Regeln wie in der Norm beschrieben.
- **Inline-Verweise** in Fließtextfeldern: **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` §11**, **`docs/HANDOVER.md`** §5.
---
## Methodenbezug (§11.5)
@ -630,12 +677,13 @@ skill_level_definitions (
- [ ] Level-Definitionen aus Fähigkeitsmatrix extrahieren (optional)
- [ ] Skills-Beschreibungen aus Wiki importieren (Migration 024)
- [ ] Admin-UI für Fähigkeiten-Kategorien (CRUD)
- [ ] Skill-Filter in Übungssuche integrieren
- [x] Skill-Filter in Übungssuche (SkillTreeMultiSelect + Stufen)
- [x] Gewichtetes Fähigkeiten-Profil für Planungs-Bausteine (Module, Rahmen, Pfade) — siehe `technical/SKILL_SCORING_SPEC.md`
- [ ] Reifegradmodelle definieren (Kombination Fokusbereich + Stil + Zielgruppe)
- [ ] KI-Unterstützung für Trainingsplanung (basierend auf Fähigkeiten-Level)
---
**Letzte Aktualisierung:** 2026-04-27
**Letzte Aktualisierung:** 2026-05-20
**Verantwortlich:** Claude Code
**Review:** Pending

View File

@ -0,0 +1,114 @@
# Parallele Trainingsstreams (Breakout) — Fachkonzept
**Status:** MVP-Umsetzung **teilweise** (Code) · **Stand:** 2026-05-14
**Ziel:** Planung und Durchführung von Training mit **phasenweise gemeinsamem** Ablauf und **beliebig vielen parallelen Teilstrecken** (Breakout-Sessions), inkl. Sonderfall **rotierende Stationen**.
**Technische Ausarbeitung:** `.claude/docs/technical/PARALLEL_TRAINING_STREAMS_SPEC.md`
**Domänenbegriffe (Überblick):** `.claude/docs/functional/DOMAIN_MODEL.md` (Abschnitt Parallele Streams)
---
## 1. Ausgangslage und Problem
In Kinder- und Breitensport-Training ist ein typischer Ablauf:
1. **Gemeinsam:** Aufwärmen, Koordination, Ansagen.
2. **Getrennt:** Kinder in mehrere Gruppen teilen; **Co-Trainer** leiten jeweils eigene Inhalte **gleichzeitig**.
3. **Gemeinsam:** Abschluss, gemeinsame Übungen, Verabschiedung.
Die aktuelle Shinkan-Planung modelliert pro Termin **eine lineare Folge von Abschnitten und Übungen** pro Einheit. Das genügt nicht, wenn **mehrere gleichzeitige „Unter-Sessions“** mit unterschiedlichen Plänen dokumentiert und auf der Matte geführt werden sollen.
---
## 2. Ziele (fachlich)
| ID | Ziel |
|----|------|
| PT01 | Eine **Kalender-Einheit** bleibt **ein** Termin (eine Halle, eine Gruppe, ein Datum) — kein Splitten in künstlich mehrere Kalendereinträge nur für Parallelität. |
| PT02 | **Unbegrenzte** Anzahl paralleler **Streams** (Teilstrecken) in einer oder mehreren **Parallelphasen**. |
| PT03 | **Phasenmodell:** klar erkennbar **Gemeinsam** vs. **Parallel** vs. wieder **Gemeinsam** (auch mehrfach hintereinander möglich). |
| PT04 | **Rollen:** Leitung (Haupttrainer) und Co-Trainer; Zuordnung der Co-Trainer **soll** an konkrete Streams anschließbar sein (heute: nur flache Liste pro Einheit — siehe technische Spec). |
| PT05 | **Sonderfall Stationen:** rotierender Ablauf (z.B. Wechsel alle 20Min.) **inhaltlich** unterscheiden zwischen (a) Rotation **innerhalb** einer Teilstrecke und (b) **synchron** getakteter Hallen-Rotation — siehe §5. |
| PT06 | **Durchführung:** Trainer können „ihre“ Spur auf dem Gerät abarbeiten; Fortschritt pro Spur nachvollziehbar. |
**Nicht-Ziel (frühe Stufen):** Echtzeit-Synchronisation mehrerer Geräte; individuelles Athleten-Tracking; automatische Raumbelegung.
---
## 3. Begriffe
| Begriff | Definition |
|---------|------------|
| **Einheit / Termin** | Geplante `training_unit` für Gruppe und Datum — übergeordneter Rahmen des Abends. |
| **Phase** | Organisatorischer Block innerhalb der Einheit: entweder **ganze Gruppe** oder **parallel**. |
| **Stream / Teilstrecke** | Innerhalb einer Parallelphase: eine von N **gleichzeitig** stattfindenden Unter-Abläufen mit **eigenem** Miniplan (Abschnitte, Übungen, Notizen — analog heutiger Planung). |
| **Synchronisationspunkt** | Fachlich: alle treffen sich wieder (Beginn einer **Gemeinschaftsphase** nach Parallelität). |
| **Station (Rotation)** | Inhaltlicher Fokus oder Platz, den Teilnehmer **wechselnd** anlaufen; kann als Kombinations-/Zirkellogik oder als koordinierter Hallenrhythmus modelliert werden (§5). |
**Abgrenzung „Rahmenprogramm-Slot“:** Ein Slot im **Rahmenprogramm** ist eine **Session in einer Serie** (z.B. Woche 1 vs. Woche 2), **nicht** „Teilgruppe A gleichzeitig mit Teilgruppe B in derselben Stunde“. Parallele Streams sind **innerhalb einer Einheit**, orthogonal zum Rahmen-Slot.
**Abgrenzung **Kombinationsübung**:** Eine Kombi-Übung bündelt **mehrere Einzelübungen** mit Methodenprofil (Archetyp, ggf. Rotation) **in einem Plan-Item**. Sie ersetzt **nicht** mehrere Trainer mit **jeweils eigenem Gesamtablauf**, kann aber **pro Stream** für Stationslogik genutzt werden.
---
## 4. Szenarien
### 4.1 Klassischer Breakout
30Min. gemeinsam → 25Min. drei parallele Streams (Gruppe an Matte / an Schlagsack / Fußarbeit) → 15Min. gemeinsam.
### 4.2 Viele Kinder, mehrere Co-Trainer
Haupttrainer plant die Gesamtstruktur; jeder Co-Trainer sieht in der Durchführung primär die zugewiesene Teilstrecke.
### 4.3 Rollierendes Stationssystem
Alle Gruppen arbeiten an **verschiedenen Schwerpunkten** und **wechseln** nach festem Intervall die Station — entweder **nur innerhalb einer Spur** oder **hallenweit synchron** (offene fachliche Präzisierung in MVP vs. später, §5).
---
## 5. Sonderfall: Stationen und Kombinationsübungen
### 5.1 Variante A — Rotation innerhalb einer Teilstrecke
Eine Teilgruppe rotiert durch mehrere Übungen (Zeit oder Runden). Das liegt nah an einer **Kombinationsübung** mit Archetyp z.B. „Zirkel / zeitgesteuerte Rotation“ und Parametern (Wechselintervall). **Empfehlung:** Diese Variante über **bestehendes** Kombinationsübungs-Konzept in der jeweiligen **Stream-Planung** abbilden (`planning_method_profile`).
### 5.2 Variante B — Synchron getaktete Hallen-Rotation
Alle Streams (oder alle Kinder insgesamt) **wechseln gleichzeitig** zur nächsten Station; Startstation kann pro Teilgruppe **versetzt** sein. Das ist **organisatorisch** schwerer: es braucht entweder **Phasen-Metadaten** (globaler Takt) oder eine explizite **Rot/Matrix**. **Empfehlung:** In einer **zweiten Ausbaustufe** abbilden; MVP kann bei Variante A starten, sofern fachlich ausreichend.
---
## 6. Rollen und Verantwortlichkeiten
- **Leitungstrainer:** Hält den Faden, startet Gemeinschaftsphasen, koordiniert Parallelbeginn/-ende (fachlich; ggf. später UI-Hinweise).
- **Co-Trainer:** Verantwortlich für **zugeteilte** Streams; Zuordnung soll **pro Stream** möglich werden (Erweiterung gegenüber reiner Einheits-Co-Trainer-Liste).
---
## 7. Offene fachliche Entscheidungen
1. **MVP Umfang:** Reicht **freie Parallelität** ohne **synchronen** Hallenwechsel (Variante B)?
2. **Dauer:** Sollen Phasen oder Streams **Soll-Minuten** tragen (nur Anzeige vs. später Timer)?
3. **Vorlagen:** Müssen `training_plan_templates` parallel-fähig werden **vor** oder **mit** der ersten Implementierung?
4. **Sichtbarkeit:** Dürfen alle Co-Trainer alle Streams sehen, oder „nur meine Spur“?
---
## 9. Umsetzungsstand (kurz, 2026-05-14)
- **Erreicht:** Datenmodell Phasen/Streams (**063**), API **GET/PUT** mit **`phases`**, Planungs-Breakout-UI, Durchführung und Coach nutzen dieselbe Phasen-/Stream-Logik im Frontend (`trainingPlanUtils.js`). **Synchronisationspunkt** fachlich umgesetzt: vor nächster Ganzgruppenphase oder nächstem Split erscheint im Coach die **Rejoin-Karte** (mehrere Streams), sofern nicht am absoluten Planende.
- **Noch offen:** vollständige **Persistenz-Konsistenz** bei nachträglich geänderten Sektionen, **Vorlagen** mit Phasen, **Trainer pro Stream** in der UI, ggf. **Stream-Tabs** in der Durchführungsansicht wie in §5.2 skizziert — siehe **`docs/HANDOVER.md`** (Arbeitspaket-Tabelle).
---
## 10. Verwandte Dokumente
| Dokument | Bezug |
|----------|--------|
| `technical/TRAINING_FRAMEWORK_SPEC.md` | Rahmen-Slots = Serien-Sessions, **nicht** Intra-Einheit-Parallelität |
| `technical/TRAINING_MODULES_AND_COMBINATION_EXERCISES_SPEC.md` | Kombinationsübungen, Archetypen, Stationslogik **im Item** |
| `functional/Shinkan Trainingsmodule Kombinationsuebungen Spezifikation V2.md` | Fachliche Tiefe Kombi |
| `docs/FACHLICHE_NUTZERFUNKTIONEN.md` | Nutzerüberblick |
| `docs/HANDOVER.md` | Ist-Stand Coach, offene Breakout-Punkte |
| `technical/DATABASE_SCHEMA.md` | Aktueller Stand Tabellen |

View File

@ -4,10 +4,17 @@ Ausführliche fachliche Inhalte:
| Dokument | Inhalt |
|----------|--------|
| [**Fachliche Nutzerfunktionen (Ist, Überblick)**](../../../docs/FACHLICHE_NUTZERFUNKTIONEN.md) | Kompakte **Nutzer-/Rollen-Perspektive** zur Übergabe an Design & Product (ohne Implementierungsdetail) |
| [shinkan_anforderungsdokument_entwurf.md](./shinkan_anforderungsdokument_entwurf.md) | Gesamtentwurf Anforderungen |
| [DOMAIN_MODEL.md](./DOMAIN_MODEL.md) | Domänenmodell, Variantenlogik (Abschnitt 11.2) |
| [DOMAIN_MODEL.md](./DOMAIN_MODEL.md) | Domänenmodell, Variantenlogik (Abschnitt 11.2), **Medien-Archiv** (Abschnitt 2026-05) |
| [MEDIA_ASSETS_AND_ARCHIVE_SPEC.md](../technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md) | Medien-Archiv, Lifecycle, **Inline-Medien** (Spec Abschnitt 11, umgesetzt) |
| [MULTI_TENANCY_RBAC_ARCHITECTURE.md](../technical/MULTI_TENANCY_RBAC_ARCHITECTURE.md) | Zielarchitektur Mandanten/Rollen/Membership & Umsetzungsplan |
| [**Trainingsmodule & Kombinationsübungen (Fachspez V3)**](./Shinkan%20Trainingsmodule%20Kombinationsuebungen%20Spezifikation%20V2.md) | Produktlogik Module/Kombinationen, **Methoden-Archetypen**, **Coaching-Stufen (§10.4)**, kanonische Archetyp-IDs **§10.2.1**, **Anhang A** Implementierungsabgleich |
| [**Umsetzungsplan Trainingsmodule & Kombination**](../working/TRAINING_MODULES_IMPLEMENTATION_PLAN.md) | Phase 15, Coaching-Pakete 4a4d, Verweis auf Code-Stand |
| [**Technischer Entwurf Module/Kombination**](../technical/TRAINING_MODULES_AND_COMBINATION_EXERCISES_SPEC.md) | API/Daten-Ideen; aktueller Coach-/Archetyp-Abgleich im Kopfabschnitt |
| [**KI-Unterstützung Übungen (Vision)**](./AI_EXERCISE_ASSISTANT_VISION.md) | Zielbild Zielausbau, Vorschlags-UX (teilweise/komplett), Skills/Varianten, später Planungskontext, Admin-Masse/Qualität |
| [**KI Übungen Umsetzungsplan**](../working/AI_EXERCISE_IMPLEMENTATION_PLAN.md) | Stufen S0S6, Driftschutz-Regeln, Checkliste gegen Specs |
**Lieferstand & Umsetzung (Stand Code):** siehe [`../PROJECT_STATUS.md`](../PROJECT_STATUS.md) und [`../library/FEATURES_DELIVERED_2026-Q2.md`](../library/FEATURES_DELIVERED_2026-Q2.md).
**Lieferstand & Umsetzung (Stand Code):** [`../PROJECT_STATUS.md`](../PROJECT_STATUS.md), [`../library/FEATURES_DELIVERED_2026-Q2.md`](../library/FEATURES_DELIVERED_2026-Q2.md) (Abschnitt 12), Repo-Root **`docs/HANDOVER.md`**, **`docs/FACHLICHE_NUTZERFUNKTIONEN.md`**.
`CLAUDE.md` (Repo-Root) verweist hierher als Einstieg.

View File

@ -0,0 +1,829 @@
# Trainingsmodule und Kombinationsübungen — fachliche Spezifikation V3
**Status:** fachlicher Spezifikationsentwurf
**Stand:** 2026-05-12 (AnhangA App **0.8.110**; ZeitPfad **`COMBINATION_TIMING_PROFILE_PLAN.md`**) · **Coaching/Archetypen:** §10.2.1, §10.410.6, **§5.4/§6.3** Methoden/Archetypen/Zeitschicht · **Anhang A**
**Zweck:** Produkt- und Fachspezifikation für Trainingsmodule, Kombinationsübungen, Trainingsmethodenbezug, Planungsintegration und Coaching-Modus in Shinkan.
**Wichtige Leitlinie dieser Version:**
Diese Spezifikation beschreibt bewusst **keine verbindlichen Tabellen, API-Pfade, Spaltennamen oder konkrete Implementierungsdetails**. Die technische Umsetzung soll durch den Coding Agent auf Basis der bestehenden Codebasis geplant werden. Ziel dieses Dokuments ist es, die fachliche Zielarchitektur, Nutzerlogik, Datenbedeutung und Produktentscheidungen so klar zu beschreiben, dass spätere große Refactorings vermieden werden, ohne die bestehende Anwendung durch zu frühe technische Festlegungen zu destabilisieren.
---
## 1. Ausgangslage
Shinkan ist eine trainerzentrierte App für Übungsverwaltung, Trainingsplanung, Rahmenprogramme und Durchführung. Die bestehende Planung arbeitet fachlich mit Trainingseinheiten, Trainingsabschnitten und Einträgen wie Übungen oder Notizen.
Für die nächste Ausbaustufe werden zwei zusätzliche fachliche Bausteine benötigt:
1. **Kombinationsübungen**
Strukturierte Übungsformen, bei denen mehrere Einzelübungen, Stationen, Rollen oder Schritte methodisch zusammenwirken.
2. **Trainingsmodule**
Wiederverwendbare Planungsbausteine, also gespeicherte Übungsfolgen oder Trainingsblöcke, die in konkrete Trainings oder Rahmenprogramme übernommen werden können.
Zusätzlich muss geklärt werden, wie **Trainingsmethoden**, **Methoden-Archetypen** und **konkrete Ablaufprofile** fachlich voneinander getrennt werden.
---
## 2. Fachliche Grundentscheidungen
### 2.1 Trainingsabschnitte bleiben Makrostruktur
Trainingsabschnitte beschreiben die grobe Struktur einer Trainingseinheit, z. B.:
* Aufwärmen,
* Hauptteil,
* Kumite,
* Kata,
* Selbstschutz,
* Abschluss.
Ein Abschnitt ist damit ein Gliederungselement der gesamten Trainingseinheit.
### 2.2 Kombinationsübungen sind nicht an genau einen Abschnitt gebunden
Eine Kombinationsübung darf nicht fachlich oder technisch auf genau einen Trainingsabschnitt reduziert werden.
Sie kann:
* innerhalb eines Abschnitts verwendet werden,
* einen Abschnitt faktisch ausfüllen,
* zwischen zwei Abschnitten stehen,
* als zentraler Block der Einheit auf Trainingsebene liegen,
* Bestandteil eines Trainingsmoduls sein,
* Bestandteil eines Rahmenprogramms oder Rahmen-Slots sein.
Der Abschnitt kann ein sinnvoller Anzeige- oder Planungskontext sein, ist aber nicht die fachliche Heimat der Kombinationsübung.
### 2.3 Kombinationsübungen gehören fachlich zum Übungsbereich
Eine Kombinationsübung ist eine Sonderform einer Übung. Sie besitzt daher die typischen Eigenschaften einer Übung:
* Titel,
* Ziel,
* Durchführung,
* Trainerhinweise,
* Vorbereitung,
* Hilfsmittel,
* Dauer,
* Zielgruppe,
* Fähigkeiten,
* Methodenbezug,
* Medien,
* Sichtbarkeit,
* Freigabestatus.
Zusätzlich besitzt sie eine interne Struktur:
* Slots,
* Stationen,
* Rollen,
* Schritte,
* Übungspools,
* Methoden-Archetyp,
* Ablaufprofil für Planung und Coaching.
### 2.4 Trainingsmodule gehören fachlich zur Planung
Trainingsmodule sind keine Übungen, sondern wiederverwendbare Planungsbausteine.
Ein Trainingsmodul kann enthalten:
* einzelne Übungen,
* Kombinationsübungen,
* Notizen,
* methodische Hinweise,
* kurze wiederverwendbare Übungsfolgen,
* größere Blöcke innerhalb einer Einheit.
Trainingsmodule sollten deshalb fachlich unter **Planung / Bibliothek / Module** verortet werden.
### 2.5 Einfügen bedeutet Kopie mit Herkunft, nicht Live-Verknüpfung
Wenn ein Trainingsmodul oder eine Kombinationsübung in eine konkrete Trainingseinheit übernommen wird, entsteht eine bearbeitbare Planungsinstanz.
Grundsatz:
> Bibliothek = Vorlage.
> Planung = lokal bearbeitbare Übernahme.
> Durchführung = tatsächliche Nutzung im Training.
Spätere Änderungen an der Vorlage dürfen bereits geplante oder historische Einheiten nicht ungefragt verändern.
---
## 3. Zentrale Begriffe
| Begriff | Fachliche Bedeutung |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------ |
| **Trainingseinheit** | Konkretes geplantes oder durchgeführtes Training. |
| **Trainingsabschnitt** | Makrostruktur der Einheit, z. B. Aufwärmen oder Hauptteil. |
| **Planungsblock** | Zusammenhängender Inhalt innerhalb einer Einheit, z. B. Modul, Kombinationsübung oder manuell gruppierter Block. |
| **Kombinationsübung** | Sonderform einer Übung mit interner Struktur aus Slots, Stationen, Rollen oder Schritten. |
| **Trainingsmodul** | Wiederverwendbarer Planungsbaustein aus Übungen, Kombinationsübungen und Notizen. |
| **Trainingsmethode** | Fachlicher Katalogeintrag, der beschreibt, wie eine Trainingsform didaktisch oder sportmethodisch einzuordnen ist. |
| **Methoden-Archetyp** | Ablaufmuster für Planung und Coaching, z. B. rotierender Zirkel oder lineare Sequenz. |
| **Ablaufprofil** | Konkrete Ausprägung eines Archetyps, z. B. Arbeitszeit, Wechselzeit, Runden oder Erklärphase. |
| **Slot** | Platzhalter innerhalb einer Kombinationsübung, z. B. Station 1, Rolle A oder Schritt 2. |
| **Slot-Pool** | Menge möglicher Übungen für einen Slot, aus denen bei der Planung eine konkrete Auswahl getroffen werden kann. |
---
## 4. Trainingsmethoden: Ablage und Beschreibung
### 4.1 Rolle des Methodenkatalogs
Trainingsmethoden sollen als eigenständige fachliche Katalogobjekte geführt werden.
Sie beschreiben nicht eine konkrete Übung, sondern die methodische Qualität einer Trainingsform.
Beispiele:
* Zirkeltraining,
* Rollenspiel,
* strukturierte Übung,
* Koordinationstraining,
* plyometrisches Training,
* Dauermethode,
* extensive Intervallmethode,
* Partnerübung,
* freie Anwendung,
* Reflexionsformat.
Der Methodenkatalog dient:
* der Übungsbeschreibung,
* der Suche und Filterung,
* der Trainingsplanung,
* der fachlichen Standardisierung im Verein,
* der späteren KI- oder Assistenzunterstützung,
* der Qualitätssicherung bei offiziellen Inhalten.
### 4.2 Abgrenzung: Methode, Archetyp, Ablaufprofil
Die drei Begriffe müssen getrennt bleiben.
| Ebene | Frage | Beispiel |
| --------------------- | ---------------------------------------------- | --------------------------------------------------------- |
| **Trainingsmethode** | Welche fachliche Methode wird verwendet? | Zirkeltraining, Rollenspiel, Intervalltraining. |
| **Methoden-Archetyp** | Nach welchem Ablaufmuster wird gesteuert? | rotierender Zirkel, parallele Stationen, lineare Sequenz. |
| **Ablaufprofil** | Wie ist die konkrete Durchführung eingestellt? | 45 Sekunden Arbeit, 15 Sekunden Wechsel, 3 Runden. |
Wichtig:
> Der Methoden-Archetyp ersetzt nicht die Trainingsmethode. Er ergänzt sie nur dort, wo der Ablauf für Planung oder Coaching maschinenlesbar interpretiert werden muss.
### 4.3 Fachliche Beschreibung einer Trainingsmethode
Eine Trainingsmethode sollte aus Trainersicht so beschrieben werden, dass sie zuverlässig angewendet, gesucht und von anderen Methoden unterschieden werden kann.
Empfohlene fachliche Beschreibungsfelder:
| Feld | Zweck |
| ---------------------------------- | -------------------------------------------------------------------------- |
| **Name** | Eindeutige Bezeichnung der Methode. |
| **Kurzbeschreibung** | Schnelle Orientierung in Listen und Auswahlfeldern. |
| **Langbeschreibung** | Fachliche Erklärung der Methode. |
| **Ziel / Nutzen** | Wofür diese Methode besonders geeignet ist. |
| **Typische Einsatzsituationen** | Wann die Methode sinnvoll eingesetzt wird. |
| **Geeignete Zielgruppen** | Altersgruppen, Leistungsgruppen oder Trainingskontexte. |
| **Organisationsform** | Einzelarbeit, Partnerarbeit, Gruppe, Stationen, Kreis, freie Fläche usw. |
| **Belastungscharakter** | locker, technisch, koordinativ, intensiv, intervallartig, spielerisch usw. |
| **Typische Dauer** | Orientierung für Planung und Zeitmanagement. |
| **Benötigte Rahmenbedingungen** | Platz, Material, Gruppengröße, Sicherheitsabstände. |
| **Trainerhinweise** | Wichtige Hinweise für Anleitung und Steuerung. |
| **Risiken / typische Fehler** | Was bei falscher Anwendung problematisch sein kann. |
| **Geeignete Fähigkeiten** | Fähigkeiten, die mit der Methode häufig adressiert werden. |
| **Verwandte Methoden** | Ähnliche oder kombinierbare Methoden. |
| **Abgrenzung zu anderen Methoden** | Wann eine andere Methode passender wäre. |
| **Optionale Standard-Archetypen** | Falls die Methode häufig mit bestimmten Ablaufmustern genutzt wird. |
| **Status und Sichtbarkeit** | Entwurf, freigegeben, offiziell, vereinsintern usw. |
Diese Felder sind fachliche Anforderungen. Die konkrete technische Ablage soll der Coding Agent anhand der bestehenden Methodendomäne planen.
### 4.4 Haupt- und Nebenmethoden
Eine Übung sollte fachlich mindestens eine Hauptmethode haben können.
Zusätzlich können Nebenmethoden sinnvoll sein, weil eine Übung aus mehreren methodischen Perspektiven beschrieben werden kann.
Beispiel:
* Hauptmethode: Zirkeltraining
* Nebenmethode: plyometrisches Training
* weitere Nebenmethode: Koordinationstraining
Produktentscheidung:
> Übungen und Kombinationsübungen sollen eine Hauptmethode und optional weitere Nebenmethoden unterstützen.
Perspektivisch kann zusätzlich unterschieden werden zwischen:
* sportmethodischer Methode,
* didaktischer Vermittlungsmethode,
* organisatorischer Durchführungsform.
Diese Unterscheidung sollte aber im MVP nicht übermodelliert werden.
### 4.5 Trainingsmethoden in Kombinationsübungen
Bei Kombinationsübungen ist der Methodenbezug besonders wichtig.
Eine Kombinationsübung sollte daher fachlich drei Dinge besitzen:
1. **Methode**
Beispiel: Zirkeltraining.
2. **Archetyp**
Beispiel: rotierender Zeit-Zirkel.
3. **Ablaufprofil**
Beispiel: 6 Stationen, 45 Sekunden Arbeit, 15 Sekunden Wechsel, 3 Runden.
So kann dieselbe Methode unterschiedlich angewendet werden:
| Methode | Archetyp | Beispiel |
| ------------------- | ------------------- | --------------------------------------------------- |
| Zirkeltraining | rotierender Zirkel | Alle Gruppen wechseln gemeinsam weiter. |
| Zirkeltraining | parallele Stationen | Stationen laufen parallel, kein gemeinsamer Umlauf. |
| Intervalltraining | Intervallblock | Gemeinsame Zeitdomäne ohne Stationen. |
| Strukturierte Übung | lineare Sequenz | Schritt 1, Schritt 2, Schritt 3. |
### 4.6 Methoden in Trainingsmodulen
Trainingsmodule können ebenfalls einen Methodenbezug besitzen, aber anders als Übungen.
Ein Modul kann:
* eine dominante Methode haben,
* mehrere Methoden enthalten,
* methodisch neutral sein,
* nur aus einzelnen Übungen bestehen, die selbst Methoden besitzen.
Empfehlung:
> Ein Trainingsmodul darf optional eine primäre methodische Ausrichtung besitzen, sollte aber nicht zwingend eine Methode erzwingen.
Beispiel:
* Modul: „Aktivierung und Reaktion“
* Primäre methodische Ausrichtung: Koordinationstraining
* Enthaltene Übungen: Reaktionsspiel, Sprintsignal, Partneraufgabe
### 4.7 Methoden in der Suche und Planung
Der Methodenkatalog soll in der Nutzung sichtbar werden.
Benötigte Such- und Planungsfunktionen:
* Übungen nach Methode filtern,
* Kombinationsübungen nach Methode und Archetyp filtern,
* Trainingsmodule nach methodischer Ausrichtung filtern,
* in der Planung passende Methoden für ein Trainingsziel finden,
* Methoden als Qualitätsmerkmal offizieller Vereinsinhalte nutzen,
* bei der Auswahl einer Kombinationsübung passende Ablaufmuster vorschlagen.
Beispiel aus Trainersicht:
> „Ich suche eine Übung für Kumite, Jugendliche, Schwerpunkt Beinarbeit, Methode Zirkeltraining oder Koordinationstraining, Dauer maximal 15 Minuten.“
### 4.8 Governance des Methodenkatalogs
Trainingsmethoden sind fachliche Standardobjekte. Daher sollten sie stärker kontrolliert werden als private Trainingsnotizen.
Empfehlung:
* offizielle Methoden werden durch Administratoren oder Inhaltsverantwortliche gepflegt,
* Vereine können eigene Ergänzungen oder Spezialisierungen anlegen,
* Trainer können Vorschläge oder private methodische Hinweise erfassen,
* Änderungen an offiziellen Methoden sollten nicht ungeprüft globale Inhalte verändern.
---
## 5. Methoden-Archetypen für Kombinationsübungen
### 5.1 Zweck
Archetypen beschreiben wiederkehrende Ablaufmuster, die für Planung und Coaching relevant sind.
Sie beantworten nicht die Frage „Welche Methode ist das?“, sondern:
> Wie soll dieser Block im Training durchlaufen oder angezeigt werden?
### 5.2 Empfohlene Start-Archetypen
| Archetyp | Fachliche Bedeutung | Coaching-Idee |
| ------------------------ | -------------------------------------------------------------------- | -------------------------------------------------- |
| **Lineare Sequenz** | Übungen bauen nacheinander aufeinander auf. | Schrittfolge mit optionalem Timer. |
| **Rotierender Zirkel** | Mehrere Stationen, Gruppen wechseln nach Zeit weiter. | Gemeinsamer Timer, Wechselhinweis, Rundenzähler. |
| **Parallele Stationen** | Mehrere Stationen laufen gleichzeitig, aber ohne zwingende Rotation. | Vorher erklären, dann paralleler Betrieb. |
| **Parcours** | Stationen oder Aufgaben entlang eines Wegs oder Ablaufs. | Navigation, Abhaken, flexible Reihenfolge möglich. |
| **Partner-/Paarwechsel** | Rollen oder Aufgaben wechseln gekoppelt. | A/B-Logik, Rollenhinweise, Wechselimpulse. |
| **Intervallblock** | Gemeinsame Zeitdomäne mit wiederholten Belastungsphasen. | Globale Uhr, Intervallanzeige. |
| **Freier Methodenblock** | Methodischer Zusammenhang ohne harte Steuerungslogik. | Kompakte Anzeige, manuelles Abhaken. |
### 5.3 Mindestanforderung an Archetypen
Für jeden Archetyp muss fachlich beschrieben sein:
* wann er verwendet wird,
* welche Informationen der Trainer bei der Planung benötigt,
* welche Informationen im Coaching-Modus angezeigt werden,
* welche Angaben verpflichtend sind,
* welche Angaben optional sind,
* wann ein anderer Archetyp besser geeignet wäre.
Die technische Validierung und konkrete Ablage dieser Angaben soll der Coding Agent planen.
### 5.4 Einordnung: Trainingsformen wie HIIT, Dauer, plyometrisch ↔ Archetypen
**Wichtige Trennung (bleibt fachlich zwingend):**
* **Trainingsmethode im Methodenkatalog** (z.B. HIIT, extensive Intervallmethode, Dauermethode, plyometrisches Training) beschreibt primär den **didaktisch/belastungsmethodischen Kontext („was für eine Trainingsqualität ist das?“)**.
* **`method_archetype`** beschreibt **das Ablaufmuster („wie soll der Trainer den Block strukturieren und im Coach geführt werden?“)** — insbesondere **Parallelität, Rotation, Sequenz, Zeitdomänen**.
Dieselbe Methode kann in der Praxis mit **mehreren** Archetypen sinnvoll kombiniert sein; das ist **kein Widerspruch**.
| Beispiel (Methoden-/Belastungsbegriff) | Typischer Archetyp (Orientierung), nicht Pflicht |
| -------------------------------------- | ---------------------------------------------- |
| **HIIT**, Tabata-ähnlich, Kurzintervalle oft mit hoher Intention | sehr oft **`time_domain_interval`** oder innerhalb eines Zirkels **`circuit_rotate_time`** mit kurzen Arbeitsphasen; Partnerformen zusätzlich **`pair_superset`**. |
| **Klassisches Intervalltraining** (längere Arbeit, definierte Erholung, N Wiederholungen) | überwiegend **`time_domain_interval`** oder **`circuit_rotate_time`**, wenn die „Intervallschicht“ an Stationen gebunden ist. |
| **Dauermethode** (überwiegend durchgehend ohne harte Arbeit-Erholung-Takte) | eher **`free_method_block`** oder **`sequence_linear`** mit optionalen Hinweiten; **weniger** `time_domain_interval`, sofern kein geregeltes Intervallschema gemeint ist. |
| **Plyometrisch**, Explosivblöcke, Technik-Schichtung | häufig **`sequence_linear`** (Progression vor Ort) oder **`circuit_rotate_time`** / **`time_domain_interval`**, wenn klar Zeitfenster oder Wiederholungsblöcke vorgegeben sind. |
| Rein **organisatorisches** Stationslaufen ohne gemeinsamen Intervalltakt | **`circuit_rotate_time`** oder **`station_parcours`**, **`circuit_all_parallel`**, je nach ob rotiert wird oder parallel aktiv ist. |
**Shinkan-Zielrichtung bleibt trainerzentriert:** Es geht **nicht** um individuelle Pulsonomie eines Sportlers, sondern darum, dass der **Trainer Belastungs- und Erholungsphasen, Durchläufe und ggf. Umlauf-/Parallellogik** vorgibt und der **Coach** diese Vorgaben **sichtbar und später steuerbar** macht
(siehe **§6.3** zu Phasen jenseits „nur Gesamtminuten auf dem Planungsitem“).
### 5.5 Erweiterbarkeit von Archetypen (aktuell zurückgestellt)
Die Idee einer **von Superadmins zur Laufzeit editierbare Archetyp-Registry**, die den Coaching-Modus **völlig frei parametrierbar** macht, wird **zurückgestellt**. Vorerst reicht die **festgelegte, versionierte Liste** kanonischer Archetyp-IDs (**§10.2.1**); **weitere Archetypen** können später **wie bisher durch Produkt-/Release entschieden** ergänzt werden (Code oder kuratierter Import), ohne freies „Beliebig-Neuanlegen“ ohne definierten Coach-Verhaltens-Anker.
---
## 6. Kombinationsübungen
### 6.1 Fachliche Beschreibung
Eine Kombinationsübung ist eine wiederverwendbare Übungsform mit interner Struktur.
Beispiele:
* Kumite-Zirkel mit fünf Stationen,
* Koordinationsparcours,
* Selbstschutz-Parcours,
* Partnerwechselübung,
* methodische Sequenz zur Distanzkontrolle,
* Reaktions- und Explosivitätsblock,
* Aufwärmparcours für Kinder.
### 6.2 Bestandteile
Eine Kombinationsübung sollte fachlich enthalten:
* allgemeine Übungsbeschreibung,
* Ziel,
* Durchführung,
* Trainerhinweise,
* Vorbereitung,
* Hilfsmittel,
* Zielgruppe,
* Fähigkeiten,
* Hauptmethode,
* optionale Nebenmethoden,
* Archetyp,
* Slots / Stationen / Rollen / Schritte,
* mögliche Übungen je Slot,
* strukturierte **Zeitphasen und Belastungs-/Erholungsvorgaben** innerhalb der Kombination (**`method_profile`**, Überblick §6.3; Details §10.5) — **zusätzlich** zu allenfalls geplanten **Gesamtminuten am Planungseintrag**,
* optionale klassische Hinweise zu Dauer, Runden oder Wechsel aus der Übung heraus,
* Hinweise für den Coaching-Modus.
### 6.3 Zeitschicht: Phasen innerhalb der Kombination (Bibliothek) und Anpassungen in der Planung
Ein **einzelnes** Feld „Geplante Minuten für diesen Eintrag in der Einheit“ kann die **innenliegende zeitliche Logik** einer Kombinationsübung **nicht** ersetzen. Für Trainersteuerung (und später für Coaching **Stufe C**) soll die Kombination in der Bibliotheksbeschreibung vorsehen können:
**A) Kombinationseinheit („global“ über die Slots)**
* **Arbeits-** und **Erholungszeiten** (Sekunden/Minuten) und **Anzahl der Durchläufe** oder **Intervalle**,
* ggf. **gemeinsamer Takt** für alle Teilnehmenden (z.B. rotierender Zirkel oder eine gemeinsame Intervalluhr),
* **Erklär- oder Aufbauzeit** vor dem eigentlichen Start,
* dort, wo der Archetyp passt: **Runden-/Umlaufzahl** oder vergleichbare Strukturen.
Alle diese Angaben sind **Anweisungen an den Trainer** und **CoachAssistenz**, **keine** individuelle Pulssonde oder ähnliche Personenmessung.
**B) Optional pro Slot oder Schritt**
* wenn fachlich sinnvoll: **von Station zu Station variierende Arbeitsphasen** oder MiniSequenzen innerhalb eines Slots — technisch z.B. als strukturierte Liste in `method_profile` mit Bezug zum `slot_index` (Ausarbeitung Coding Agent).
**Nach Einplanung in eine konkrete Trainingseinheit** muss diese Zeitschicht (oder ihr Abgleich mit der Einheitsposition) für den Trainer **bearbeitbar** bleiben, **ohne** die Bibliotheksvorlage still zu überschreiben (kopier-/instanzbasierte Anpassungen — siehe bereits §2.5 und §8.3).
**Umsetzung in der App (Stand 0.8.110):** Pro Übungszeile in einer Trainingseinheit kann optional ein **JSON-Snapshot** des Ablaufprofils gespeichert werden (`planning_method_profile` in der DB, Migration **057**). **`null`** oder fehlender Key: für **Anzeige und Editor** wirkt das **Zusammenführen** aus **Katalog** (`exercises.method_profile` bzw. Join `catalog_method_profile`) **+** Snapshot — der Katalog wird **nicht** durch ein leeres Planungsobjekt verworfen; fehlende bzw. JSON-`null`-Werte im Snapshot **überschreiben** keine Katalogfelder; `slot_profiles_v1` wird **je `slot_index`** zusammengeführt (inkl. konsistenter Steuerungslogik Zeit vs. ZielWdh.). Persistenz: der Snapshot speichert nur die vom Trainer **gesetzten** Planungsdaten (nach STZ-„Runde“ können leere Objekte als `null` normalisiert werden). **Konkrete Logik:** Frontend `effectiveComboMethodProfile` / `merge` in `frontend/src/utils/comboPlanningMethodProfile.js` (Coach, Planungseditor, Druck/Vorschau konsistent). Bearbeitung in der Planungs-UI: Modal **„Ablauf bearbeiten…“** mit `CombinationMethodProfileEditor` + Vorschau `CombinationPlanBracket`.
**Coach:** soll die wirksamen Werte nach **Übernahme** und **Einheitsübersteuerungen** konsistent nachvollziehen (**§10.4**).
**Geplantes kanonisches Zeitmodell:** Globale Eckwerte (z.B. Anzahl der Durchläufe / Runden, optionale Gesamt-/Einführungszeit als Ziel oder Rechenhilfe) und **pro Platz (Slot)** die Dimensionen „Belastung“, „wie viele gleiche Übung hintereinander“, „kurze Pause dazwischen“, „Übergangszeit zur nächsten Übung/arbeitstation“ — dokumentiert für die technische Angleichung in **`.claude/docs/working/COMBINATION_TIMING_PROFILE_PLAN.md`** (Felder **`slot_profiles_v1`**, `timing_schema`). Archetypen können **Strukturen und typische Schnellwahlen** vorgeben (z.B. Zirkel: Relation Belastungszeit=Übergangszeit oder Erholungsanteil2/3der Belastung); der Archetyp **Freier Methodenblock** bildet den **MaximalPfad** ohne stärkere stille Annahmen. **Pyramidale/abhängige Pausen** (Pause abhängig von vorheriger Belastung) sind **nicht Teil des aktuellen Umsetzungspfads**, können später als eigener Untertyp ergänzt werden.
**Fortschritt pro Slot (Stand 0.8.109):** optional **`advance_mode`** je Eintrag in **`slot_profiles_v1`**: `timed` — Standard (`load_sec` = geplante Arbeitsdauer für Timer im Coach; fehlende Angabe entspricht `timed` ohne Sekundenfeld), **`rep`** — mengenorientiert (Zielzahl über **`consecutive_reps`**; keine verbindliche Arbeitsuhr), **`manual`** — coachgeführt (Fortschritt bewusst per Schritt später im Coach, optional Richtwert über **`consecutive_reps`**). Optional **`rep_series_count`**: Standard **1** (wird im Formular/API explizit geführt); Ausnahmen nur, wenn der **MethodenArchetyp** in `ARCHETYPE_DEFAULT_REP_SERIES_COUNT` eine andere Vorgabe definiert oder der Nutzer eine andere Zahl setzt. ≥2 ermöglicht Pause **zwischen Serien** (`intra_rep_rest_sec`). Bei nur **einer** Serie: kein **`intra_rep_rest_sec`** in UI und Payload; **`transition_after_sec`** = Wechsel zur nächsten Station.
### 6.4 Slot- und Pool-Logik
Slots können fest oder variabel sein.
Beispiel fest:
* Station 1 = Seilspringen
* Station 2 = Liegestütz
* Station 3 = Beinarbeit
Beispiel variabel:
* Station 1 = eine Übung aus Pool „Beinarbeit“
* Station 2 = eine Übung aus Pool „Reaktion“
* Station 3 = eine Übung aus Pool „Konter“
Die konkrete Auswahl kann bei der Planung angepasst werden, ohne die Bibliotheksvorlage zu ändern.
---
## 7. Trainingsmodule
### 7.1 Fachliche Beschreibung
Ein Trainingsmodul ist ein wiederverwendbarer Planungsbaustein.
Beispiele:
* Standard-Aufwärmen für Kinder,
* Mobilisation und Aktivierung,
* Kumite-Beinarbeit 20 Minuten,
* SV-Einstieg Wahrnehmung und Distanz,
* Abschlussritual mit Reflexion,
* prüfungsnaher Kihon-Block.
### 7.2 Bestandteile
Ein Trainingsmodul sollte fachlich enthalten:
* Titel,
* Kurzbeschreibung,
* Ziel,
* empfohlene Dauer,
* empfohlene Zielgruppe,
* optional empfohlener Einsatzbereich,
* optionale methodische Ausrichtung,
* enthaltene Übungen,
* enthaltene Kombinationsübungen,
* Notizen oder Trainerhinweise,
* Sichtbarkeit,
* Freigabestatus.
### 7.3 Keine harte Abschnittsbindung
Ein Modul kann für einen Abschnitt empfohlen sein, z. B. „Aufwärmen“, darf aber nicht technisch darauf beschränkt werden.
Ein Modul kann:
* in einen Abschnitt eingefügt werden,
* als eigener Block auf Einheitsebene eingefügt werden,
* zwischen Abschnitten eingefügt werden,
* in ein Rahmenprogramm übernommen werden.
---
## 8. Planungslogik
### 8.1 Planungsblöcke
Für die Produktlogik braucht Shinkan den Begriff des Planungsblocks.
Ein Planungsblock ist ein zusammengehöriger Inhalt in einer Trainingseinheit.
Planungsblöcke können sein:
* eingefügtes Trainingsmodul,
* eingefügte Kombinationsübung,
* manuell gruppierter Block,
* später ggf. weitere Blocktypen.
### 8.2 Verhältnis zu Abschnitten
Ein Planungsblock kann einem Abschnitt zugeordnet sein, muss aber nicht vollständig in einem Abschnitt aufgehen.
Produktregel:
> Abschnitte gliedern die Einheit. Planungsblöcke gliedern den konkreten Trainingsinhalt.
### 8.3 Lokale Anpassbarkeit
Nach dem Einfügen muss ein Planungsblock lokal angepasst werden können:
* Dauer ändern,
* **bei Kombinationsübungen:** Ablaufprofil **optional nur für diese Platzierung** überschreiben (aktuell: Snapshot parallel zum Katalog-`method_profile`, z.B. Arbeit-, Erholungs- und Runden-/Intervallangaben über die gleichen strukturierten Felder wie im Übungskatalog) — zusätzlich zu den **Gepl.-Min.** am Eintrag; **Stations-/Slot-Austausch** am konkreten Vorkommen weiter über die bestehende Übungs-/Planungslogik, nicht gesondert als „Kombi-Programmierung“ je Zeile,
* Übung austauschen,
* Station ergänzen,
* Hinweise anpassen,
* Reihenfolge ändern,
* Block auflösen,
* Block duplizieren,
* Block als neues Modul speichern.
Diese Änderungen betreffen nur die konkrete Einheit oder den konkreten Rahmen-Slot, nicht automatisch das Bibliotheksexemplar.
---
## 9. UX-Anforderungen
### 9.1 Inhalt hinzufügen
Im Planungseditor sollte der Trainer fachlich klar wählen können:
* Übung hinzufügen,
* Kombinationsübung hinzufügen,
* Trainingsmodul hinzufügen,
* Notiz hinzufügen,
* manuellen Block erstellen.
### 9.2 Modul erstellen
Ein Modul sollte auf mehreren Wegen entstehen können:
* leer anlegen,
* aus bestehendem Abschnitt speichern,
* aus markierten Übungen speichern,
* aus einem Teil eines alten Trainings speichern.
### 9.3 Kombinationsübung erstellen
Eine Kombinationsübung sollte geführt angelegt werden:
1. Grunddaten erfassen,
2. Methode wählen,
3. Archetyp wählen,
4. Slots / Stationen / Rollen definieren,
5. Übungen oder Pools zuordnen,
6. Ablaufprofil festlegen,
7. Coaching-Vorschau prüfen,
8. speichern.
### 9.4 Methoden auswählen
Die Methodenauswahl sollte Trainer unterstützen, nicht belasten.
Empfohlene UX:
* Hauptmethode prominent,
* Nebenmethoden optional,
* passende Methoden vorschlagen,
* Methoden kurz erklären,
* bei Kombinationsübungen passende Archetypen vorschlagen,
* keine Pflicht zur Überklassifizierung bei einfachen Übungen.
---
## 10. Coaching- und Assistenzmodus
### 10.1 Ziel
Der Coaching-Modus soll die Durchführung unterstützen, ohne den Trainer zu zwingen, exakt dem Plan zu folgen.
Grundsatz:
> Der Coaching-Modus gibt Orientierung, Zeitstruktur und Ablaufhilfe, bleibt aber in der Praxis flexibel.
### 10.2 Unterschiedliche Anzeige je Archetyp
| Archetyp | Coaching-Anzeige |
| -------------------- | -------------------------------------------- |
| Lineare Sequenz | Schrittfolge mit Weiter/Zurück. |
| Rotierender Zirkel | Stationen, Arbeitszeit, Wechselzeit, Runden. |
| Parallele Stationen | Erst Erklärübersicht, dann Parallelbetrieb. |
| Parcours | Stationen oder Wegpunkte zum Abhaken. |
| Partner-/Paarwechsel | Rollen, Aufgaben und Wechselhinweise. |
| Intervallblock | Globale Zeit, Intervallzähler, Aufgaben. |
| Freier Methodenblock | Kompakte Übersicht und manuelle Steuerung. |
#### 10.2.1 Kanonische Archetyp-IDs (Abgleich Fachbegriff, UI und API)
Damit Produktbeschreibung, Formularfelder (`method_archetype`), Trainingscoach und BackendValidierung **dieselben Werte** nutzen und es keinen dokumentationsbedingten Drift gibt, gelten diese **festen Schlüssel** (MaschinenIDs):
| Archetyp (fachlicher Name in §5.2) | Schlüssel `method_archetype` (`exercises.method_archetype`) |
| ----------------------------------- | ----------------------------------------------------------- |
| Lineare Sequenz | `sequence_linear` |
| Rotierender Zirkel (Zeit) | `circuit_rotate_time` |
| Parallele Stationen | `circuit_all_parallel` |
| Parcours | `station_parcour` |
| Partner- / Paarwechsel | `pair_superset` |
| Intervallblock (Zeitdomäne) | `time_domain_interval` |
| Freier Methodenblock | `free_method_block` |
Änderungen an dieser Zuordnung nur **gemeinsam** (Produkt, BackendEnum und UIKonstanten); siehe Implementierungsanhang weiter unten.
### 10.3 Durchführungsdokumentation
Perspektivisch sollte dokumentierbar sein:
* was durchgeführt wurde,
* was übersprungen wurde,
* was verändert wurde,
* tatsächliche Dauer,
* Trainerhinweise,
* Reflexion,
* Vorschläge zur Verbesserung einer Übung oder eines Moduls.
Die konkrete technische Umsetzung wird nicht in dieser Spezifikation festgelegt.
### 10.4 Coaching-Reifegrade (Normierung ohne technisches Pflichtenheft)
Archetyp-spezifisches Coaching soll **nicht** als ein einziges UX-„Monolith“ gebaut werden, sondern in **nachvollziehbaren Stufen**, damit frühere Umsetzungen nicht überschrieben wirken und der Fortschritt in Doku/Umsetzungsplan nachverfolgt werden kann:
| Stufe | Bezeichnung (Arbeitstitel) | Inhalt aus Trainersicht | Abgrenzung |
| ----- | ---------------------------- | ------------------------ | ----------- |
| **A** | **Informations-/Struktursicht** | Pro Kombinationsübung: KopfKontext aus Katalog **plus** strukturierte Darstellung der **Slots** und der **einzelnen Kandidatenübungen** (Titel, Kurztext, Detail aufklappbar); **ein zeitlicher Schritt im Coach** entspricht weiter **einem** Planungseintrag (ein Item in der Einheit). | Kein eigener Rundenzähler, kein eigener StationsTimerState pro Archetyp. |
| **B** | **Archetyp-Steuerung in der bestehenden Zeitleiste** | Optionale Aufspaltung: z.B. bei **`sequence_linear`** pro Slot **ein CoachSchritt** (Weiter/Zurück pro Station), ohne die Datenbank-Semantik der Einheit zu zerstückeln (Virtuelle Schritte oder materialisierte HilfsEinträge technische Variante dokumentieren). | Bewusste Produkt-/Architekturentscheidung nötig, damit ISTZeiten und AbschlussPUT konsistent bleiben. |
| **C** | **Interaktive Assistenz je Archetyp** | Gemeinschafts-/StationsTimer, Wechselimpulse (**`circuit_rotate_time`**), Vorab„Erklärphase“Flag (**`circuit_all_parallel`**), Abhaken (**`station_parcour`**), gekoppelte A/BAnsicht (**`pair_superset`**), globale Intervalluhr (**`time_domain_interval`**) — jeweils an Parameter aus **`method_profile`** angebunden, wo diese in Stufe A/B bereits sichtbar gepflegt werden. | Keine verpflichtende KISteuerung; Trainer kann überspringen (Grundsatz §10.1). |
**Aktuelle Zielrichtung:** Stufe **A** soll für **alle** in §10.2.1 genannten Archetypen **inhaltsgleich** die Slot und Kandidateninformation liefern; **unterschiedliche Kopf-/Hilfstexte und UI-Mikrolayouts** nach Archetyp sind Teil von A und sollten gemeinsam mit Stufe B/C wachsen (kein „still“ abweichendes Verhalten ohne DokuUpdate).
### 10.5 Fachliche Mindestinfos im **Ablaufprofil** (`method_profile`) pro Archetyp
`method_profile` ist das **konkretisierende** JSON (o. ä.) zum gewählten Archetyp: Zeiten, Runden, Schalter. Technische Pflichtfelder und Validierung regelt die technische Umsetzung — **fachlich** gilt folgende Minimal-Erwartung, damit Stufe B/C sinnvoll nutzbar ist:
| Archetyp-Schlüssel | Mindest-Parameter (fachlich sinnvoll; Benennung in der Umsetzung kanonisch festlegen). Typische Zuordnung methodischer Überbegriffe: **§5.4** |
| ------------------ | ------------------------------------------------------------------------------------- |
| `sequence_linear` | Orientierungs-Arbeits-/Pausenhinweise je Schritt oder global; Reihenfolge = Slotreihenfolge — u.a. für **Skillschichtungen**, Aufwärmserien ohne festen Rotationstakt oder **Ausdauer-/Technikketten ohne Intervalltakt**. |
| `circuit_rotate_time` | **Arbeit** je Station oder Umlauf, **optional Wechsel/Transition**, **optional Erholung zwischen Runden**, **optional Rundenanzahl**; Kern für rotierenden Zirkel inkl. vieler HIIT-/Zirkelmischformen über Stationen hinweg (**§5.4**). |
| `circuit_all_parallel` | „Erst gemeinsame Erklärung, dann gleichzeitiger Betrieb aller Stationen“; Zeitfenster VorabErklärung optional — z.B. wenn keine Rotation, aber gemeinsamer Startzeitpunkt gewünscht ist. |
| `station_parcour` | Fokus Stationsbeschreibung; optional freie Besuchsreihenfolge (Profil/Archetyp); weniger zentral **feste Arbeit/Erholung-Takte**, mehr Navigation/Abhaken (später Stufe C). |
| `pair_superset` | Arbeit und Wechsel bei **gekoppelten** Rollen; typisch wenn zwei Linien oder Partnerblöcke im Takt gewechselt werden. |
| `time_domain_interval` | Klare Zeitdomäne: **Belastungs-, Erholungsblöcke** und **Anzahl Wiederholungen** der Domäne bzw. **Gesamtblockbegrenzung** — zentrale Schicht für viele Formen aus **„Intervall/HIIT/Zeitschachtelungs“**Methodenkatalog ohne individuelle Messung (**§5.4**). |
| `free_method_block` | Keine zusätzlichen Pflichtparameter; **unterstützt** etwa **reibungsarmere Dauer- oder Spielformen**, wo der Trainer keine starke Taktuhr braucht, aber Stationsideen strukturiert bündeln will. |
#### 10.5.1 Mehrschichtiges Planen (Überblick)
| Ebene | Inhalt zeitlicher Art |
| ----- | --------------------- |
| **Einheit / Planungsitem** | z.B. geplante **Gesamtminuten** dieser Platzierung („der Block soll heute etwa 25Min einnehmen“). |
| **Kombinationsübung (Bibliothek)** | strukturierte **Phasen in `method_profile`** (arbeiten, pausieren, Runden…) — §6.3. |
| **Einheitliche Planungsinstanz** | optionale Abweiche vom Bibliotheksprofil **nur für dieses Training**8.3). |
| **Coach** | liest wirksamen Stand (Bibliothek + Overrides) zur **Orientierung**, später automatisierte Taktassistenz (**§10.4**). |
Solange diese Mindestinfos in der Datenpflege noch **nicht** validiert oder nicht geführt erfasst werden, bleibt Coaching bei **Informations-Schicht und manuellen Timern des bestehenden Coach-Dialogs** die fachlich ehrliche Darstellung (siehe Anhang A).
### 10.6 Offene und geplante Erweiterungen (Produkt-Backlog, Stand 2026-05-12)
Die folgenden Punkte stammen aus **Session-/Chat-Arbeit** an Planung, Klammerdarstellung und Coach **Stufe A**; sie sind **noch nicht** als vollständige Produktfunktion abgeschlossen bzw. bewusst zurückgestellt:
| Thema | Kurzbeschreibung | Status |
| ----- | ---------------- | ------ |
| **Coaching Stufe B/C (individuelle Archetyp-Steuerung)** | Über **Stufe A** (lesend: Slots, Zeiten, Archetyp-Hinweis, Kandidaten-Texte) hinaus: **pro Archetyp** gesteuerte Durchführung (z.B. Substeps bei Sequenz, Stations-/Rotations-Timer beim Zirkel, Erklärphase bei parallelen Stationen, Abhaken Parcours, Intervalluhr). §10.4 Stufe **B** (Zeitleiste) und **C** (Assistenz). | **Offen** — aktuell nur informativ/Orientierung; kein archetypspezifischer Zustand im Coach. |
| **Administrierbarkeit der Archetypen** | Archetypen sind **fest** im Code (`COMBINATION_ARCHETYPE_IDS` Backend, `COMBINATION_ARCHETYPE_OPTIONS` Frontend); **keine** DB-/Admin-Oberfläche für Labels, Defaults, Sichtbarkeit oder club-spezifische Erweiterungen. | **Offen** — Änderungen nur per Release/Code-Review. |
| **Einfache Vorbelegung aller Zeit- und Anzahlfelder** | Teilweise: Schnellwahlen (**Zirkel**, **Intervall**), Serien-Default **1**, Archetyp-Map `ARCHETYPE_DEFAULT_REP_SERIES_COUNT`. **Fehlt:** ein Klick „alle Stationen aus globalen Eckwerten / Archetyp-Muster füllen“, Profil-weite **Reset/Übernehmen**-Presets über alle Slots. | **Teilweise** — Ausbau siehe `COMBINATION_TIMING_PROFILE_PLAN.md` §1 („Archetyp = Struktur + Defaults“). |
| **Archetypbedingte Restriktionen & Server-Validierung** | Client führt geführte Felder; **keine** verbindliche Backend-Prüfung „Profil passt zu Archetyp“ (Pflichtschlüssel, Wertebereiche, unzulässige Slot-Kombinationen). | **Offen** — erhöht Datenqualität und Coach-Verlässlichkeit vor Stufe C. |
| **Governance Archetyp ↔ offizielle Inhalte** | Noch keine getrennte Policy „nur Superadmin darf neue Archetyp-IDs einführen“ (derzeit ohnehin nur Code). | **Offen** — relevant sobald Archetypen konfigurierbar werden. |
**Hinweis:** §13.1 nennt Stufe **A** als MVP-Pflicht und **B/C** als Ausbauschritte — die Tabelle oben präzisiert die **noch offenen** Arbeitspakete aus der Umsetzungspraxis.
---
## 11. Rahmenprogramm-Integration
Trainingsmodule und Kombinationsübungen müssen auch in Rahmenprogrammen nutzbar sein.
Regel:
> Was in einer konkreten Trainingseinheit geplant werden kann, sollte grundsätzlich auch in einem Rahmenprogramm oder Rahmen-Slot vorbereitet werden können, sofern es keine echte Durchführung voraussetzt.
Das betrifft insbesondere:
* Modul einfügen,
* Kombinationsübung einfügen,
* methodische Ausrichtung übernehmen,
* Slot-Pools vorbelegen,
* Dauer anpassen,
* später konkrete Einheit daraus ableiten.
Nicht in den Rahmen gehört:
* echte Durchführung,
* tatsächliche Dauer,
* spontane Trainingsnotizen,
* Nachbereitungsreflexion.
---
## 12. Governance
Für Methoden, Übungen, Kombinationsübungen und Module gelten abgestufte Sichtbarkeiten und Verantwortlichkeiten.
Empfohlene fachliche Ebenen:
* privat,
* Verein,
* offiziell,
* archiviert,
* Entwurf,
* freigegeben.
Normale Trainer sollen Inhalte nutzen und lokal anpassen können. Offizielle oder vereinsweite Vorlagen sollen nicht ungeprüft überschrieben werden.
Für Methoden ist eine besondere Qualitätskontrolle sinnvoll, weil sie als fachlicher Katalog für viele Übungen und Planungen wirken.
---
## 13. MVP-Empfehlung
### 13.1 Muss enthalten sein
* Trainingsmodule anlegen und wiederverwenden,
* Kombinationsübungen als fachliche Sonderform von Übungen,
* Methodenbezug mit Hauptmethode und optionalen Nebenmethoden,
* klare Trennung zwischen Methode, Archetyp und Ablaufprofil,
* mindestens folgende Archetypen:
* lineare Sequenz,
* rotierender Zirkel,
* freier Methodenblock,
* Planungsblöcke als fachliches Konzept,
* lokale Anpassbarkeit nach Einfügen,
* Coaching: mindestens **Stufe A** nach §10.4 für alle Archetypen aus §10.2.1 (strukturierte Slot-/Kombi-Darstellung; Archetyp-Hilfstexte); **zeitliche/mechanische Archetyp-Steuerung (Stufen B/C)** ausdrücklich als Ausbauschritte.
### 13.2 Sollte vorbereitet werden
* parallele Stationen,
* Parcours,
* Partner-/Paarwechsel,
* Intervallblock,
* Durchführungsdokumentation,
* Rückfluss von Erfahrungswissen,
* Offline-/PWA-Nutzung,
* stärkere Suche nach Methoden und Archetypen.
### 13.3 Nicht im MVP
* vollständige technische Event-Historie jeder Planänderung,
* automatische Synchronisation alter Einheiten bei Vorlagenänderung,
* komplexe Verschachtelung von Modulen in Modulen,
* individuelles Athleten-Tracking,
* KI-generierte Trainingsplanung,
* verbindliche technische Tabellen- oder API-Architektur.
---
## 14. Arbeitsauftrag an den Coding Agent — fachliche Leitplanken
Der Coding Agent soll die bestehende Codebasis prüfen und auf dieser Grundlage eine technische Umsetzungsplanung erstellen.
Dabei soll er ausdrücklich:
1. bestehende Strukturen wiederverwenden, soweit sinnvoll,
2. keine unnötigen Refactorings auslösen,
3. bestehende Trainingsplanung nicht destabilisieren,
4. Migrationen schrittweise und rückwärtskompatibel planen,
5. vorhandene Methodendomäne berücksichtigen,
6. die Trennung zwischen Trainingsmethode, Archetyp und Ablaufprofil fachlich erhalten,
7. technische Alternativen mit Vor- und Nachteilen darstellen,
8. erst danach konkrete Tabellen, APIs und UI-Komponenten vorschlagen.
Die Spezifikation ist daher kein technisches Pflichtenheft, sondern ein fachlicher Rahmen.
---
## 15. Zusammenfassung der verbindlichen Produktlogik
1. Trainingsabschnitte sind die Makrostruktur der Einheit.
2. Kombinationsübungen sind keine Abschnitte.
3. Kombinationsübungen sind Sonderformen von Übungen.
4. Trainingsmodule sind Planungsbausteine.
5. Trainingsmethoden sind eigenständige fachliche Katalogobjekte.
6. Eine Übung hat eine Hauptmethode und optional Nebenmethoden.
7. Methoden-Archetypen beschreiben Ablaufmuster, nicht die Methode selbst.
8. Ablaufprofile konkretisieren den Archetyp für Planung und Coaching (siehe §10.5).
9. Einfügen aus Bibliotheken erzeugt lokal bearbeitbare Planungsinhalte.
10. Vorlagenänderungen verändern historische oder konkrete Planungen nicht automatisch.
11. Rahmenprogramme sollen dieselbe Planungslogik nutzen wie konkrete Einheiten.
12. Der Coding Agent entscheidet die technische Umsetzung anhand der bestehenden Codebasis.
13. Archetyp-IDs und Coaching-Stufen (§10.2.1, §10.4) sind die **Referenz gegen Code-Drift**; Änderungen nur mit Anhang A und technischer Doku.
14. **Zeitliche Phasen** einer Kombination liegen vorrangig in **`method_profile`** und **Gesamtzeit am Planungseintrag**; **Übersteuerungen nur in der Planungsinstanz**, nicht still in der Bibliothek (§6.3, §8.3, §10.5.1).
---
## Anhang A — Implementierungsabgleich (Stand Code: App **0.8.110**)
Zweck: dieselbe Tabelle für **Produkt / Architekt / Agent** beim nächsten Schritt; verhindert „wir haben X gebaut, die Spec sagt aber Y“ ohne dass es dokumentiert wird.
| Thema (fachliche Headline aus dieser Spez) | Kurz beschrieben | Stand Code / UX (Referenz nur) | Lücke / nächste sinnvolle Schritte |
|--------------------------------------------|-----------------|---------------------------------|-------------------------------------|
| **Trainingsmodule (Bibliothek)** | Wiederverwendbare Blöcke, Kopier-Einfügen in Einheit | Bibliothek, API, Übernahme-Modal, Lineage-Spalte | **Phase 3** des Umsetzungsplans: erweiterter Übernahmemodus |
| **Kombinationsübung im Katalog** | `exercise_kind=combination`, Slots, Pools (Kandidaten) | Migration 056, CRUD Übung mit `combination_slots`, GET liefert Slots + Kandidatentitel | Fachbezug Haupt-/Nebenmethoden aus §4/§6 dort umsetzen, wo die Domäne es noch nicht abdeckt |
| **Archetyp + Ablaufprofil am Katalogobjekt** | `method_archetype`, JSON `method_profile` + **`slot_profiles_v1`** | Geführtes Profil (`CombinationMethodProfileEditor`), `advance_mode` je Slot (Zeit / ZielWdh. / Coach), API-Build aus `ExerciseFormPage` | **Admin-UI für Archetypen** fehlt (nur Code-Konstanten); **serverseitige Validierung** Profil↔Archetyp offen; **volle Vorbelegung** aller Slots aus Preset/Archetyp nur teilweise (Schnellwahl) |
| **Einplanbarkeit (normale Planung)** | Kombi in Sektionen; Overrides §8.3 | `planning_method_profile` JSONB; Modal **„Ablauf bearbeiten“**; **Merge** Katalog+Planung im Frontend (`effectiveComboMethodProfile`); Payload-Sanitisierung; Backend `Json()` beim Insert | Planungsblöcke Phase 3; **serverseitige** Zusammenführung/Validierung optional (aktuell Merge nur Client) |
| **Darstellung Planung / Lauf / Druck** | Konsistente Zeiten & Wdh. | `CombinationPlanBracket`, `effectiveStationTimingSummary`, Belastungs-Badge je Station; kompakte Kombi-Zeile in `TrainingUnitSectionsEditor` | Feintuning nach Nutzerfeedback |
| **Zeitphasen (global / pro Slot)** | §6.3 | `slot_profiles_v1`, globale Archetyp-Felder, `inferAdvanceModeFromStoredSlotRow` für Legacy-Zeilen | `timing_schema`-Konvergenz laut `COMBINATION_TIMING_PROFILE_PLAN.md` |
| **Coaching Stufe A** | Slots + Kandidaten, Archetyp, Profil lesbar | `ExerciseFullContent` + `CombinationCoachSlots`: Merge Katalog+Planung; **globale Eckdaten mit fachlichen Labels** (`describeGlobalComboProfile`); Stationstexte inkl. „Wdh. ohne Wechsel zur nächsten Station“ / Pausen-Hinweis | Stufe **B/C** weiterhin **offen**10.6) |
| **Coaching Stufe B** | Zeitleiste archetypnah | **Nein** — ein CoachSchritt = ein Planungsitem | Designentscheid: virtuelle Substeps vs. DBMaterialisierung; Auswirkung auf IstZeit pro Item |
| **Coaching Stufe C** | Timer/Wechsel/Abhaken nach Archetyp | Nur **generischer** Minuten-/Ist-Input pro Item; **kein** Stations-Timer-State | Pro Archetyp UI + `method_profile` — Haupt-Backlog |
| **Rahmenprogramm** | Gleiche Inhalte wie Einheit | SlotBlueprint, `from-framework-slot` | Modul-/KombiUX in Rahmen wie in Einheit konsolidieren (Phase 5) |
| **Coaching-Vorschau im Editor** | §9.3 Schritt 7 | **Peek** / Run nutzen `CombinationPlanBracket`; kein eigener „Coach-Sim“-Modus im Übungseditor | Optional: eingebettete read-only Coach-Ansicht |
**Pflege:** Bei jeder relevanten Codeänderung diese Tabelle **in demselben PR / derselben Session** anpassen (kein stiller Drift).

View File

@ -1,9 +1,9 @@
# Gelieferte Features & technische Basis (Q2 2026)
**Stand:** 2026-05-05
**Referenz:** `backend/version.py`**APP_VERSION 0.8.10**, **DB_SCHEMA_VERSION 20260505037**
**Stand:** 2026-05-20
**Referenz:** `backend/version.py`aktuelle **APP_VERSION** / **DB_SCHEMA_VERSION** (Stand Code u. a. **0.8.96**)
Dieses Dokument bündelt die in der Entwicklungsphase erreichten **lieferbaren** Funktionen und die zugehörigen **technischen Artefakte**. TrainingsrahmenBibliothek + SlotBlueprint: **`technical/TRAINING_FRAMEWORK_SPEC.md`** §2. **Progressionsgraph zwischen Übungen** (Zwischenstand, Grenzen): **§§34**. Detail-Spezifikationen bleiben in den verlinkten Pfaden unter `.claude/docs/technical/` und `.claude/docs/functional/`.
Dieses Dokument bündelt die in der Entwicklungsphase erreichten **lieferbaren** Funktionen und die zugehörigen **technischen Artefakte**. TrainingsrahmenBibliothek + SlotBlueprint: **`technical/TRAINING_FRAMEWORK_SPEC.md`** §2. **Progressionsgraph zwischen Übungen** (Zwischenstand, Grenzen): **§§34**. **Medien-Archiv & Bibliothek:** Abschnitt **12** unten + **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**. Detail-Spezifikationen bleiben in den verlinkten Pfaden unter `.claude/docs/technical/` und `.claude/docs/functional/`.
---
@ -17,7 +17,7 @@ Dieses Dokument bündelt die in der Entwicklungsphase erreichten **lieferbaren**
| **030** | `training_unit_exercises.exercise_variant_id` → FK `exercise_variants(id)` ON DELETE SET NULL |
| **035** | **`training_framework_programs`** + Ziele, Slots (+ frühere SlotÜbungstabelle, heute entfallen nach **037**); **`training_plan_templates.visibility`** |
| **036** | Rahmen nur Bibliothek: Kontext + M:N Trainingsarten/Zielgruppen; keine Modus-Spalten / keine Kopf`group_id` |
| **037** | **`training_units.framework_slot_id`**, strukturierter Ablauf wie Planung; Entfall **`training_framework_slot_exercises`**; **`origin_framework_slot_id`** |
| **045046** | **`media_assets`**, `platform_media_storage`, `exercise_media.media_asset_id`, ggf. **Tags/GIN** — siehe `MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` |
---
@ -68,7 +68,7 @@ Logik: `_upload_limit_bytes(session)` vor `read()`-Prüfung.
## 5. Frontend Übungsliste (`ExercisesListPage.jsx`)
- Tabs **Liste** · **Progressionsgraphen** (`ExerciseProgressionGraphPanel`): Graphen anlegen/bearbeiten, Kanten inkl. Sequenz-Bulk und Tabellenansicht.
- **Filter-Modal** (Fokus, Stilrichtung, Trainingsstil, Zielgruppe, Fähigkeit + Stufen von/bis, Sichtbarkeit, Status).
- **Filter-Modal** (Fokus, Stilrichtung, Trainingsstil, Zielgruppe, Fähigkeit + Stufen von/bis, **Freigabelevel**, Status).
- **Filter-Chips** unter der Suchleiste; Klick entfernt einen Filter; Badge am Filter-Button = Anzahl Chips.
- **Kein Vollbild-Spinner** bei jeder Suche: nur noch **`listFetching`** — Suchfelder bleiben im DOM (**Fokus/Cursor** bleiben erhalten); Liste zeigt optional „Aktualisiere Treffer…“.
- **`<datalist>`** mit Titeln der aktuellen Treffer; **`autoComplete="on"`** für Browser-Vorschläge.
@ -76,14 +76,47 @@ Logik: `_upload_limit_bytes(session)` vor `read()`-Prüfung.
---
## 6. Frontend Übung bearbeiten (`ExerciseFormPage.jsx`)
## 6. Frontend Übung bearbeiten (`ExerciseFormPageRoot.jsx`)
- **Varianten-Editor**: eingeklappter Bereich (`<details>`), **eine Variante zur Zeit** über Dropdown oder „Neue Variante“; Felder über **`ExerciseVariantFields`**; Reihenfolge Nach oben/unten; Speichern/Löschen pro Variante.
- **Medien** wie zuvor (Formularteil).
**Routing:** `/exercises/new`, `/exercises/:id/edit` — keine separaten Varianten-Routen.
### 6.1 Tab-Navigation (Registerkarten)
Horizontale **`PageSectionNav`** über **`ExerciseFormTabBar`** / **`ExerciseFormPanel`** (`ExerciseFormLayout.jsx`); farbige linke Panel-Ränder (CSS `.exercise-form-edit`, `.exercise-form-panel--*`).
| Tab | Inhalt |
|-----|--------|
| **Stammdaten** | Titel, Kurztext, Dauer/Gruppe, Equipment, **Freigabelevel** (`visibility`), Status, Verein |
| **Anleitung** | Ziel, Durchführung, Vorbereitung, Trainerhinweise (Rich-Text inkl. Inline-Medien) |
| **Einordnung** | Fokusbereiche, Stilrichtungen, Trainingsstile, Zielgruppen, Altersgruppen, **Fähigkeiten** (kompakte Chip-Editoren) |
| **Kombination** | nur bei `exercise_kind=combination`: Slots, Archetyp, `method_profile` |
| **Varianten** | nur nach erstem Speichern; **nicht** bei Kombinationsübungen |
| **Medien & Mehr** | Medien, Progressionsgraph, KI-Hilfen, Löschen — nach erstem Speichern |
Neue Übungen: Tabs **Varianten** und **Medien & Mehr** deaktiviert bis zur ersten Speicherung.
### 6.2 Freigabelevel (UI-Begriff)
Feld **`exercises.visibility`** heißt in der UI durchgängig **Freigabelevel** (`frontend/src/constants/exerciseGovernanceLabels.js`) — Liste, Filter, Bulk, Picker, Formular. API/DB-Feldname **`visibility`** unverändert.
### 6.3 Fähigkeiten am Übungsobjekt
- Intensität je Fähigkeit: **`niedrig` \| `mittel` \| `hoch`**, Standard **`mittel`** (`exerciseSkillIntensity.js`).
- Kein „Primär“-Schalter mehr in der UI; **`is_primary`** bei `exercise_skills` ist Legacy — Backend speichert immer **`false`**, Scoring ignoriert das Feld.
- Kompakte **Chip-Editoren** für Katalog-Zuordnungen und Fähigkeiten (`ExerciseCatalogAssocEditor`, `ExerciseSkillsEditor`).
### 6.4 Varianten-Editor
- Tab **Varianten**: **eine Variante zur Zeit** (Dropdown oder „Erste Variante anlegen“); Felder über **`ExerciseVariantFields`**; Reihenfolge Nach oben/unten; Löschen pro Variante.
- **Speichern über Aktionsleiste:** `performSaveAttempt` ruft zuerst **`persistPendingVariantChanges()`** auf (geänderte Varianten per PUT, danach optional Entwurf **`createVariantFromDraft()`**).
- Button **„Variante anlegen“** (`type="button"`, kein verschachteltes `<form>`): legt Entwurf sofort per API an; alternativ mitgesichert über **Speichern** in der Aktionsleiste.
- Snapshot **`variantsSavedSnapshotRef`** für Dirty-Erkennung; Hinweis im Panel: Änderungen werden mit Speichern in der Aktionsleiste mitgesichert.
### 6.5 Medien & Progressionsgraph
- **Medien:** Upload/Embed, **Archiv verknüpfen** (`from-asset`), Medienliste mit Vorschau, Reaktivierung bei Archiv-Konflikt — Details **§12**.
- Block **Progressionsgraph** (Edit): Kanten mit Bezug zur aktuellen Übung.
Hinweis: Es gibt **keine** separaten Routen `/exercises/:id/variants/...` — Bearbeitung erfolgt unter **`/exercises/:id/edit`** (Routing-Doku ggf. anpassen).
---
## 7. Frontend Übung Detail (`ExerciseDetailPage.jsx`)
@ -123,22 +156,101 @@ Hinweis: Es gibt **keine** separaten Routen `/exercises/:id/variants/...` — Be
---
## 12. Nächste sinnvolle Schritte (nicht Lieferstand)
## 12. Trainingsplan: Phasen & parallele Streams (DB **063**, App **0.8.1370.8.140**)
- **063:** `training_unit_phases`, `training_unit_parallel_streams`; Sektionen mit `phase_id` / `parallel_stream_id`; Default-Ganzgruppenphase für Bestand.
- **API:** `GET /api/training-units/:id` mit **`phases`** + **`sections`**; `PUT`/`POST` mit **`phases`** für Breakout-Einheiten (**0.8.138**); Rahmen-Slot-Materialisierung kopiert Phasen (**0.8.138**).
- **Frontend:** Planung Breakout-Panel (**0.8.1390.8.140**); **`trainingPlanUtils.js`** — `sectionsWithPlanLocForDisplay`, `flattenPlanTimeline`, `buildCoachSavePlanPayload`, Split-Rejoin (`coachShouldPromptSplitRejoinTransition`); **`TrainingCoachPage`**, **`TrainingUnitRunPage`**.
- **Doku:** `.claude/docs/technical/PARALLEL_TRAINING_STREAMS_SPEC.md`, `docs/HANDOVER.md` §3, Arbeitspaket „offen“.
---
## 13. Medien-Archiv & Medienbibliothek (Migration **045** ff., App ca. **0.8.410.8.64**)
Einzelnorm: **`technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**. Kurzüberblick geliefert:
### 12.1 Datenbank & Router
- **`media_assets`**, **`platform_media_storage`**, **`exercise_media.media_asset_id`** (Dedupe `sha256` + Sichtbarkeit + `club_id` je nach Policy).
- Router **`media_assets.py`**: Listen, PATCH, Lifecycle, Dateiauslieferung, Bulk; Integration in **`exercises.py`** (Upload, `from-asset`, Promotion/Copyright-Regeln bei `official` / Vereinsübungen).
### 12.2 Funktional (Ist)
- Zentrale **Medienbibliothek** Frontend **`/media`**: Filter (Lifecycle, Medientyp, Verein für Admins), Suche, Tags, Copyright bearbeiten (rollengerecht), Kacheln/Liste, Nutzungs-Hinweise (Übungen/Planung wo implementiert).
- **Papierkorb** mehrstufig (`trash_soft` / `trash_hidden`), Recovery, Superadmin-Purge; strukturierte Fehlercodes bei Governance (u.a. Übung **`CLUB_MEDIA_*`**).
- **Speicher:** Pfadkonvention **`library/{vereinssegment}/…`** mit Medienkind-Unterordnern; Umzug bei Sichtbarkeits-/Vereinsänderung; effektives Wurzelverzeichnis `MEDIA_ROOT` + Superadmin-`local_relative_root`.
- **Governance:** **`visibility=official`** für Übungen und schützenswerte Medien-Operationen im Wesentlichen **Superadmin**; Plattform-Admin entspricht nicht automatisch Superadmin.
- **Mandant:** aktiver Verein über Profil + **`X-Active-Club-Id`**; Sync UI nach **0.8.59** (siehe `tenant_context` / `AuthContext` / `activeClub.js`).
### 12.3 Inline-Medien im Fließtext (geliefert in 0.8.600.8.64)
- Platzhalter-Syntax: `{{exerciseMedia:id}}` (normalisiert auf `data-shinkan-exercise-media`), Validierung gegen `exercise_media` der aktuellen Übung.
- Zentraler Anzeigepfad über `ExerciseRichTextBlock` inkl. Sanitize und Lifecycle-Hinweisen.
- RTE-Workflow: Modal „Medien im Text“ (Mediathek + Upload), separates „Embed im Text“-Modal, Größenwahl (`small|medium|full`) und sprechende Platzhalter-Captions.
- Bearbeiten: kompakte Medien-Kacheln + Drag&Drop in Textfelder; Auto-Scroll beim Drag aktiv.
- UX-Schutz: Warnung bei Wechsel „Ansehen“ mit ungespeicherten Änderungen; Detailseite bietet Rückweg zur Bearbeitung.
---
## 14. Nächste sinnvolle Schritte (nicht Lieferstand)
- Trainingsplanung: KalenderUIAnbindung **„aus Rahmen übernehmen“**; Visibility/Policies für geteilte Rahmen (**CURR004** später).
- Progressions-Serien als **Blöcke** (angekündigt; Voraussetzung: `prerequisite_variant_id` / `progression_level` vorhanden).
- Serverseitige **Suchvorschläge** (Autocomplete-Endpoint), falls datalist nicht reicht.
- Optional: Streaming/chunked Upload für sehr große Videos (RAM-Thema).
- **Medien:** Retention-Job-Betrieb; pytest-Abdeckung Archiv; S3-Adapter (Spec §7); ggf. strategischer Entscheid „exercise_media-Verknüpfung vs. reine Asset-Referenz“.
---
## 13. Verweise
## 15. Gewichtetes Fähigkeiten-Scoring (Phase 3, Stand 2026-05-20)
Norm: **`technical/SKILL_SCORING_SPEC.md`**.
### 15.1 Backend
- **`skill_scoring.py`:** Gewichtung (Dauer × Vorkommen × Intensität × Stufen); `compute_planning_corpus_by_type()` mit getrennten Corpora; `universal_percent` capped auf 100 %
- **`routers/skill_profiles.py`:** Profile-GET pro Artefakt; `POST /api/skill-profiles/batch-summaries`; `GET /api/skill-discovery/suggestions`
- Sichtbarkeit: **`library_content_visibility_sql`** (Planungs-Bibliothek, nicht „nur Verein club“)
### 15.2 Frontend
- **Listen:** Rahmenprogramme + Trainingsmodule — Filter-Modal (wie Übungen), Chips, `SkillTreeMultiSelect` (Portal-Dropdown)
- **KPI:** `SkillProfileCompact` — Top je Unterkategorie, Score + Peer-%
- **Editoren + Modal:** `SkillProfilePanel`, `SkillProfileFullModal`
- **Discovery:** `SkillDiscoveryPanel` auf Fähigkeiten-Seite
### 15.3 Offen
- Corpus-Caching; pytest für Typ-Trennung; Filter-Persistenz; Skill-Filter Import-Dialog „Rahmen übernehmen“
---
## 16. Übungen Governance & Berechtigungen (Ist, Stand 2026-05-20)
**Owner:** `exercises.created_by` (Ersteller). **Varianten** haben kein eigenes `created_by` — Rechte leiten sich von der Eltern-Übung ab.
| Aktion | `private` | `club` | `official` |
|--------|-----------|--------|------------|
| **Lesen** | Ersteller; Plattform-Admin | Aktive Vereinsmitglieder des Objekt-`club_id`; Plattform-Admin ohne Mitgliedschaft (Audit) | Plattform-weit |
| **Bearbeiten** (Übung inkl. Varianten/Medien) | Ersteller; Plattform-Admin | Ersteller; Plattform-Admin; **`can_plan_in_club`** im Objekt-Verein (`trainer`, `content_editor`, `division_lead`, `club_admin`) | Plattform-Admin |
| **Löschen** | Ersteller; Vereins-Admin gemeinsamer Vereine mit Ersteller | Nur **`club_admin`** im Objekt-Verein | Nur Plattform-Admin |
**Code:** `backend/club_tenancy.py` (`exercise_visible_to_profile`, `can_plan_in_club`), `backend/routers/exercises.py` (`_assert_can_edit_exercise`, `_assert_can_delete_exercise`).
---
## 17. Verweise
| Thema | Dokument |
|--------|----------|
| Rahmenprogramm / Progressionsgraph | `technical/TRAINING_FRAMEWORK_SPEC.md` |
| Fähigkeiten-Scoring Planung | `technical/SKILL_SCORING_SPEC.md` |
| API Übungen | `technical/EXERCISES_API_SPEC.md` |
| Domänenmodell | `functional/DOMAIN_MODEL.md` |
| Datenbank Überblick | `technical/DATABASE_SCHEMA.md` |
| Upload formal | `technical/MEDIA_UPLOAD_SPEC.md` |
| Medien Upload (Limits, MIME) | `technical/MEDIA_UPLOAD_SPEC.md` |
| Medien-Archiv & Lifecycle | `technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` |
| Parallele Phasen/Streams | `functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md`, `technical/PARALLEL_TRAINING_STREAMS_SPEC.md` |
| Coaching/Breakout-Handover | `docs/HANDOVER.md` |
| Fachlicher Nutzerüberblick | `docs/FACHLICHE_NUTZERFUNKTIONEN.md` (Repo-Root) |
| Projektstatus-Kachel | `../PROJECT_STATUS.md` |

View File

@ -79,16 +79,18 @@ Ausgangslage im Code: `private` \| `club` \| `official` (siehe `club_tenancy`).
### Stufe E Capabilities dokumentieren (ohne UI für Custom Roles)
- Markdown-Tabelle **Capability-Fingerprint**: Kennungen wie `content.share_club`, `planning.edit_unit`, `org.manage_members`, … mit Zuordnung zu den **heutigen** festen Vereinsrollen.
- **Verbindliche Spez v1:** `CAPABILITY_CATALOG.v1.md` — Capability-IDs, Account-Lifecycle, Rollen-Matrix, Endpoint-Mapping.
- Markdown-Tabelle **Capability-Fingerprint**: Kennungen wie `exercises.ai.suggest`, `org.members.manage`, … mit Zuordnung zu den **heutigen** festen Vereinsrollen (siehe Katalog §56).
- Ziel: später `club_custom_roles` nur noch andere Kombination derselben Kennungen keine zweite Philosophie.
### Stufe F Community (eigenes Epic)
- Konzept: Freigabe **additiv** (Flag oder Enum), Moderation, Sichtbarkeit „öffentlich außerhalb meines Vereins“ ohne bestehende `club`-Isolation zu brechen.
### Zurückgestellt Vereinsabo / Limits
### Zurückgestellt Vereinsabo / Limits (Konzept liegt vor)
- Wiederöffnen wenn ACCESS_LAYER Stufe C/D stabil; dann Enforcement vor ausgewählten Writes an einen Billing-Stripe binden.
- **Spez v1:** `CLUB_MEMBERSHIP_AND_FEATURES.v1.md` — Feature-Registry (Mitai-v9c-Pattern), `club_plans`/`club_subscriptions`, Kontingente an `club_id`.
- Implementierung/Billing (Stripe) weiter zurückgestellt; Schema- und Enforcement-Hooks gemäß 4-Phasen-Rollout (Mitai-Vorbild) vorbereiten, sobald Stufe C/D stabil.
---
@ -117,10 +119,28 @@ Ausgangslage im Code: `private` \| `club` \| `official` (siehe `club_tenancy`).
## 7. Referenzen
- **`CAPABILITY_CATALOG.v1.md`** Rollen, Capabilities, CRUD-Mapping, `GET /api/me/entitlements`.
- **`CLUB_MEMBERSHIP_AND_FEATURES.v1.md`** Vereinsabo, Feature-Limits, Mitai-Mapping, Ziel-Schema.
- `.claude/docs/technical/MULTI_TENANCY_RBAC_ARCHITECTURE.md` übergeordnetes Zielbild & Begriffe.
- `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` verbindliche Domänenregeln für **Medien-Assets** (gleiche Sichtbarkeit wie Übungen, Promotion-Kopplung, Copyright, Papierkorb/Lebenszyklus, externer Speicher). Bei Widerspruch zur Sichtbarkeits-Tabelle in §3 dieses Dokuments: §3 für Enums/`library_content_*`-Semantik, Medien-Spez für Asset-spezifische Zusatzregeln.
- `backend/club_tenancy.py` bestehende Bausteine (`assert_club_member`, `exercise_visible_to_profile`, …); Ziel ist Deren schrittweise Zusammenführung unter die neue Zugriffsschicht ohne Big-Bang.
- `backend/club_tenancy.py` bestehende Bausteine (`assert_club_member`, `exercise_visible_to_profile`, `can_plan_in_club`, …); Ziel ist Deren schrittweise Zusammenführung unter die neue Zugriffsschicht ohne Big-Bang.
---
**Letzte Aktualisierung:** 2026-05-07
## 8. Anhang Übungen (Ist-Implementierung, Referenz)
**Stand:** 2026-05-20 · **Detail:** `EXERCISES_API_SPEC.md` Permissions, `FEATURES_DELIVERED_2026-Q2.md` §16
| Feld / Konzept | Semantik |
|----------------|----------|
| `created_by` | Owner der Übung; Varianten erben Rechte |
| `visibility` | UI: **Freigabelevel**`private` \| `club` \| `official` |
| Lesen | `exercise_visible_to_profile``official` global; `private` Ersteller + Plattform-Admin; `club` aktive Mitglieder (+ Plattform-Admin Audit) |
| Bearbeiten | Ersteller; Plattform-Admin; bei `club` zusätzlich `can_plan_in_club` (Trainer, Content-Editor, Spartenleitung, Vereins-Admin) |
| Löschen | `official` → Plattform-Admin; `club``club_admin` im Objekt-Verein; `private` → Ersteller oder Vereins-Admin mit gemeinsamem Verein |
**Hinweis:** Dieser Anhang dokumentiert den **produktiven Code-Pfad** in `exercises.py`; die Roadmap in §4 bleibt für die langfristige Vereinheitlichung aller Bibliotheksartefakte maßgeblich.
---
**Letzte Aktualisierung:** 2026-05-20

View File

@ -1,11 +1,20 @@
# KI-Prompt-System Universelle Admin-Konfiguration
**Version:** 1.0
**Datum:** 2026-04-24
**Status:** DRAFT
**Version:** 1.1
**Datum:** 2026-05-30
**Status:** Kern umgesetzt (`ai_prompts`, `prompt_resolver`, Superadmin-HTTP-API); Kaskaden geplant (Abschnitt 8)
**Zielbild (Roadmap):** `.claude/docs/technical/AI_PROMPT_TARGET_ARCHITECTURE.md` — Kontext-Arten, Composition, Planung/Rahmen, Phasenplan.
**Ist-Stand API (Superadmin):**
- `GET /api/admin/ai-prompts`, `GET /api/admin/ai-prompts/{id}`, `PUT …`, `POST …/preview`, `POST …/reset-template`, `GET /api/admin/ai-prompts/catalog/placeholders`
- Spalte **`openrouter_model`** (Migration **070**): Optional pro Prompt-Zeile; OpenRouter **`model`**-Parameter; **`NULL`/leer ⇒ `OPENROUTER_MODEL`** aus der Umgebung.
**Autor:** Claude Code
**Vorbild:** Mitai Jinkendo Issue #53 + `backend/routers/prompts.py` + Placeholder-System
**Verwandt (Skill-Katalog in Übungs-KI):** `working/AI_SKILL_RETRIEVAL_PROFILES_SPEC.md` — Tabelle **`ai_skill_retrieval_profiles`** (`config`-JSON ergänzt inhaltliche Prompt-/Katalog-Steuerung neben Platzhaltern).
---
## 1. Konzept
@ -28,6 +37,7 @@ steuerbar. Kein KI-Aufruf ist fest im Code verdrahtet.
|-------------|-----------|
| `exercise_summary` | Generiert `exercises.summary` aus goal + execution |
| `exercise_skill_suggestions` | Empfiehlt Skills + Stufen für eine Übung |
| `exercise_instruction_rewrite` | Überarbeitet Anleitung: goal, execution, preparation, trainer_notes (JSON, prägnantes HTML) |
| `exercise_category_suggestions` | Empfiehlt Fokusbereich, Stil, Zielgruppe |
| `model_skill_level_description` | Generiert Stufen-Beschreibung in der Fähigkeitsmatrix |
| `training_plan_notes` | Erzeugt Trainer-Notizen für Trainingseinheiten |
@ -174,10 +184,9 @@ Wähle maximal 5 passende Fähigkeiten. Für jede gib an:
- required_level: Voraussetzung (einsteiger|grundlagen|aufbau|fortgeschritten|experte)
- target_level: Ziel nach regelmäßigem Training (gleiche Werte)
- intensity: Trainingsintensität (niedrig|mittel|hoch)
- is_primary: true wenn Hauptfähigkeit
Antworte NUR als JSON-Array:
[{"skill_id": 1, "required_level": "grundlagen", "target_level": "aufbau", "intensity": "hoch", "is_primary": true}]
[{"skill_id": 1, "required_level": "grundlagen", "target_level": "aufbau", "intensity": "hoch"}]
Wenn keine Fähigkeit passt, antworte mit [].$$,
'exercise', 'json', true, NULL, 2),
@ -597,6 +606,19 @@ AI_PROMPT_SYSTEM_SPEC: ai_service.run_ai_prompt("exercise_summary", ...)
---
**Version:** 1.0
**Datum:** 2026-04-24
**Status:** DRAFT
## 8. Prompt-Kaskaden (geplant — nicht implementiert)
**Ziel:** Vorlagen, die andere Prompts einbinden oder in feste Stufen (System → Fach → Ausgabeformat) zerlegt werden — ohne die DB-Templates mit duplizierten Fliesstexten zu zersplittern.
**Konzeptskizze:**
- Optional neues Feld `base_slug` oder eigene Tabelle `ai_prompt_composition` (Reihenfolge, Rolle: `system|user|prepend`).
- Platzhaltersyntax z. B. `{{include_prompt:slug}}` mit **maximaler Verschachtelungstiefe** und Zykluserkennung.
- Auflösungsreihenfolge: (1) eingebundene Slugs expandieren, (2) Kontext-Variablen wie heute ersetzen.
Bis zur Umsetzung bleiben zusammengesetzte Anweisungen im **einen** Template pro Slug (wie `exercise_skill_suggestions` mit `{{skills_catalog}}`).
---
**Version:** 1.1
**Datum:** 2026-05-30
**Status:** Teile umgesetzt (DB 067/069, Resolver, Superadmin-API + UI); Kaskaden offen

View File

@ -0,0 +1,166 @@
# KI-Prompt-System — Zielarchitektur (Shinkan Jinkendo)
**Version:** 1.0
**Datum:** 2026-05-30
**Status:** VERBINDLICHE ZIELRICHTUNG (Roadmap — nicht alles bereits umgesetzt)
**Ergänzt:** `AI_PROMPT_SYSTEM_SPEC.md` (aktueller Ist-Stand APIs/DB/UI), Mitai-Anleihen aus gleichnamigen Konzepten (Admin-Prompts, Platzhalter)
---
## 1. Zweck
Dieses Dokument beschreibt das **Zielbild**, damit spätere Arbeiten (**Trainingsplanung**, **mehrstufige Rahmenprogramme**, **Phasen/Streams**, weitere KI-Artefakte) **nicht** zu wiederholten Refaktoren von Übungs-KI oder OpenRouter-Anbindung zwingen.
**Leitkriterien:** wenige stabile Schnittflächen, Kontext pro Domäne, komponierbare Prompts, gültige Ausgaben, Betrieb ohne Code-Deploy für kleine Tweaks.
---
## 2. Leitprinzipien
### 2.1 Eine stabile Ausführungsschicht
Alle produktiven KI-Aufrufe sollten mittelfristig über eine **einheitliche Fassade** laufen:
- **Eingabe:** `slug` (+ optional Kontext-Arten-Enum), **serialisierter Domän-Kontext** (Pydantic pro Kind), Konfiguration (Modell, Temperatur, … aus Env/DB).
- **Ausgabe:** Text oder validiertes JSON, Metadaten (`model`, `slug`, ggf. `prompt_version`/Hash), strukturierte Fehler.
Router und Frontend rufen diese Schicht oder schmale Orchestratoren — **nicht** direkt `httpx`/OpenRouter an jeder Ecke verteilt.
**Frühere Konkretisierung (Umsetzung gestartet):** Modul `backend/ai_prompt_runtime.py` (`load_ai_prompt_row`, `load_and_render_ai_prompt`, Kontext-Arten) sowie `backend/ai_prompt_job.py` (Pydantic `ExerciseFormAiPromptContext` fuer Uebungs-Prompts — Admin-Vorschau + erweiterbare Router-Nutzung); `exercise_ai` orchestriert OpenRouter nach dem Rendern.
### 2.2 Trennung: Semantik vs. Transport
- **Semantik:** Was soll das Modell liefern? Das hängt an **Prompt-Definition**, **Ausgabeformat** (`text`/`json`) und nachvollziehbarer Validierung — nicht am HTTP-Client.
- **Transport:** OpenRouter, Modellwahl, Retry, Timeouts bleiben in einem oder wenigen Hilfsmodulen.
### 2.3 Kontext-Namespaces für Platzhalter
Platzhalter und erlaubte Keys sind **pro logischer Kontext-Art** definiert, z.B.:
- `exercise_form_ai` — heute: Übungsformular-Vorschläge.
- später: `training_unit`, `framework_program_slot`, `import_wiki`, …
Damit kann der Katalog wachsen, ohne dass alle Keys in einen globalen Soup-Namespace müssen (`exercise_*` vs. `framework_*` ohne Kollisionen). Optional später **präfixierte** Keys (`exercise.title`, `slot.index`).
### 2.4 Komposition / Kaskade explizit
**Ziel:** Mehrteilige Prompts („System“„Nutzer“Anhänge) und **Einbindung anderer Vorlagen** als **Daten** (Kompositionsmodell), nicht nur als unbearbeiteter Freitext mit `{{include}}`.
Skizzen (noch nicht vollständig umgesetzt):
- Tabelle oder JSON-Spalte `composition`/`ai_prompt_segments`: geordnete Segmente mit `role` (`system` \| `user` \| äquivalent zum jeweiligen API-Shape), Quelle (`inline`, `ref_slug`), optional `ref_slug`, Schema-Version.
- Einbindungen mit **Maximaltiefe** und **Zykluserkennung** — keine unbegrenzten Makro-Ketten.
Bis dahin bleiben zusammenhängende Anweisungen in **einem** DB-Template pro Slug tragbar (`exercise_skill_suggestions` + `{{skills_catalog}}` bleiben gültig).
---
## 3. Zieldatenmodell (Schichten)
### 3.1 Definition (`ai_prompts` — bereits vorhanden, evolviert)
| Konzept | Bedeutung |
|--------|-----------|
| `slug`, `category`, `output_format`, `active` | Adressierung & Schalter |
| `template` | aktueller Inhalt |
| `default_template` | Referenz zum Zurücksetzen (Migration **069**) |
| `output_schema` (JSONB) | optional: JSON-Outputs validieren |
**Ausbaustufen:**
1. Nur `template`-Text (**heute**, plus Mustache über `prompt_resolver`).
2. Zusätzlich **Versionierung**: Historie oder `template_version`/Audit (wer hat wann geändert).
3. **Segmentierte Composition** wie in Abschnitt 2.4.
### 3.2 Kontext-Builder pro Domäne
Pro **Kontext-Art** eine klar genannte Routine (Pattern: registrierbare Builder):
| Kontext-Art | Beispiel-Input aus der App | Beispiel-Platzhalter / Daten |
|-------------|----------------------------|------------------------------|
| `exercise_form_ai` | Titel, Ziel/Durchführung (HTML→Plain), Fokuskontext, Retrieval-Profil-Influenza | `exercise_*`, `skills_catalog` |
| `training_unit` (geplant) | Sektionen, Zeiten, Phasen/Streams, verknüpfte Übungs-IDs | `unit_*`, `sections_summary_*` |
| `framework_program` (geplant) | Ziele pro Woche/Schicht, Slots, bereits geplante Einheiten, Skill-Scores | `framework_*`, `slot_*`, aggregierte KPIs |
**Regel:** Planungs-UI baut keine Prompt-Strings; sie liefert **Domän-DTOs** → Builder erzeugen **Platzhalter-Map + ggf. Anhänge**.
### 3.3 Skill-Retrieval und Prompts
`ai_skill_retrieval_profiles` steuert **KatalogZusammenstellung** vor dem Platzhalter `{{skills_catalog}}` — das bleibt **orthogonal** zur Prompt-Verwaltung: Prompt ändert *Anweisung*, Profil ändert *welche Skills im Kontextfenster sind*.
---
## 4. Trainingsplanung & Rahmen — erwartete Komplexität
Risiken: sehr große Kontexte (viele Slots, Streams, Bibliotheken), wiederholte KI-Anfragen, Token-Limits.
**Vorbereitende Strategien:**
1. **Gestufte Kontexte:** Rohdaten → interne Kurzfassungen (optional zweiter Prompt oder heuristisch) → finale Generator-Prompt nur mit komprimierten Summaries.
2. **Slug-Pro-Use-Case:** z.B. `training_unit_trainer_notes`, `framework_slot_coach_hint` — jeweils schmaler Vertrag statt „ein Prompt für alles“.
3. **Output-Verträge:** JSON-Schema + Server-Validierung vor UI; Fehlermeldungen mit Referenz auf Slug/Version.
4. **Feature-Flags / Modell-Overrides** pro Slug (optional in DB oder Env) für Dev/Prod ohne große Codepfade.
---
## 5. Mitai (Jinkendo)
Konzeptionell **gleiche Bausteine** (admin-konfigurierbare Prompts, Platzhalter, Preview), **andere** Kontext-Builder und ggf. andere Mandanten/Overlays. Eine gemeinsame **Resolver-/Mustache-Ebene** ist wünschenswert; **Shinkan-spezifische** Planungs- und Rahmenkontexte bleiben in Shinkan gekapselt.
---
## 6. Betrieb, Sicherheit, Observability
- **Audit:** `updated_by` / Änderungshistorie für Templates (Backlog), heute: Timestamps.
- **Prompt-Injection:** System-/User-Segmente trennen; sensible Regeln in `system`/`developer`-äquivalenten Blöcken (wenn API das hergibt).
- **Logging:** weiter `SHINKAN_AI_DEBUG`; langfristig Hash/Länge des **aufgelösten** Prompts pro Request (ohne Secrets).
- **Kosten/Latenz:** Timeouts, max. Token-Hinweise pro Slug-Konfiguration.
---
## 7. Phasenplan (empfohlen, ohne Big-Bang)
```mermaid
flowchart LR
subgraph laufzeit
A[ai_prompts DB]
B[prompt_resolver Mustache]
C[ai_prompt_runtime]
J[ai_prompt_job Pydantic]
D[exercise_ai OpenRouter]
end
A --> C
C --> B
J --> D
C --> D
B --> D
```
| Phase | Inhalt |
|-------|--------|
| **P0** | `AiPromptContextKind`, `load_ai_prompt_row` zentral; Übungs-KI über Laufzeit. |
| **P1** | `load_and_render_ai_prompt`, `AiPromptUnavailableError`, `render_ai_prompt_template_for_row`; **`ExerciseFormAiPromptContext`** in `ai_prompt_context.py`; **`run_exercise_form_ai_suggestion`**; Übungs-API und Admin-Vorschau nutzen denselben Kontext. |
| **P2** | Versionierung oder Audit-Spalten; **teilweise:** optionales OpenRouter-Modell pro Zeile (`openrouter_model`, Migration 070, Fallback `OPENROUTER_MODEL`); weitere Overrides (Temperatur) offen. |
| **P3** | Composition/Segmente (JSON Schema Version 1) + UI nur für komplexe Slugs. |
| **P4** | Erste Planungs-/Rahmen-Slugs mit dedizierten Buildern und Token-Budget-Strategien. |
---
## 8. Was bewusst vermieden werden soll
- Vollständige „Workflow-Engine“ mit beliebigen Graphen, bevor 23 konkrete Planungs-Anwendungsfälle live sind.
- Pro-Verein-Prompt-Kopien vor klar definierter Produkt-Anforderung (sonst Daten- und Pflege-Spirale).
- Unbegrenzte `include`-rekursive Textmakros ohne Tiefenschutz.
---
## 9. Querverweise
- Ist-Implementierung Prompts/UI: `AI_PROMPT_SYSTEM_SPEC.md`
- Zugriffsrecht Admin-Prompts: `ACCESS_LAYER_ENDPOINT_AUDIT.md`
- Retrieval-Profile: `.claude/docs/working/AI_SKILL_RETRIEVAL_PROFILES_SPEC.md`
- Übungs-KI-Codepfad: `backend/exercise_ai.py`, `backend/prompt_resolver.py`, `backend/ai_prompt_runtime.py`, `backend/ai_prompt_context.py`, `backend/ai_prompt_job.py`
---
**Version:** 1.0 · **Datum:** 2026-05-30

View File

@ -0,0 +1,202 @@
# KI-gestützte Trainingsplanung Zentrales Konzept
**Version:** 0.3
**Datum:** 2026-05-22
**Status:** Arbeitsdokument (Verfeinerung durch fachliche Konzept-Agentur vorgesehen)
**Ziel:** Einheitlicher Rahmen für **stufenweise** KI-Unterstützung zuerst **Übungsanlage** (Zusammenfassung, Fähigkeiten, Texte), später **Planung** (Abschnitte, Einheiten, Rahmen) ohne vollständigen Übungskatalog im Prompt.
**Maßgebende Version zum Abgleich:** `backend/version.py` (`APP_VERSION`, `DB_SCHEMA_VERSION`, relevante Einträge in `MODULE_VERSIONS`).
**Verwandte Dokumente:**
`functional/DOMAIN_MODEL.md` · **`functional/AI_EXERCISE_ASSISTANT_VISION.md`** (Übungs-KI: Zielbild vor Planungs-KI) · `functional/TRAINING_CURRICULUM_AND_GOVERNANCE_CONCEPT.md` (u.a. CURR-003 zu Progressions-/KI-Automatik) · **`working/AI_PLANNING_KI_MULTISTAGE_FORECAST.md`** (mehrstufige Planungs-KI: Daten-„Graph“, Pipeline-Stufen, Code-Schnitte Vorschau gegen späteres Refactoring) · `technical/TRAINING_FRAMEWORK_SPEC.md` · **`technical/SKILL_SCORING_SPEC.md`** (Fähigkeits-Profilierung, Discovery) · `technical/KI_FEATURES_SPEC.md` · `technical/AI_PROMPT_SYSTEM_SPEC.md` · `technical/SKILLS_MATRIX_SPEC.md` · `docs/FACHLICHE_NUTZERFUNKTIONEN.md` · `docs/HANDOVER.md`
---
## 1. Produktliche Leitlinien
- **Nutzer:** Trainer/Vereinskontext, **Gruppenplanung** keine Pflicht zur individuellen Sportler-Verfolgung; Kontext soll primär aus **Gruppe**, **bereits geplanten/durchgeführten Einheiten**, **Rahmen-/Zielen** und **berechtigtem Übungskorpus** bestehen.
- **Human-in-the-loop:** KI liefert **Vorschläge** (Liste, Reihenfolge, Begründung); schreibende Übernahme in Pläne nur nach **Trainer-Bestätigung** oder expliziter Aktion (analog „Manual First“ in `KI_FEATURES_SPEC.md`).
- **Governance-first:** Nur Übungen, die die API bereits für den Mandanten/Kontext **sichtbar** freigibt, dürfen in Kandidatenlisten landen **vor** Retrieval und **vor** jedem Prompt.
### 1.1 Abgleich: aktueller Code- und Schema-Stand (Stand Review 2026-05-22)
| Thema | Ist im Repo | Konsequenz für dieses Konzept |
|--------|-------------|-------------------------------|
| **OpenRouter / LLM im Backend** | Produktiver Aufruf für ÜbungsSuggest in `openrouter_chat.py`, `exercise_ai.py`; Endpunkte **`POST …/exercises/ai/suggest`** und **`POST …/{id}/ai/regenerate`**; Migration **067** (`ai_prompts`, `summary_ai_generated`). **`db.py`**-Bootstrap nutzt **`display_name`**. | **Übungs-Assistent (P0)** vorhanden; generalisierter Service + **Planungs-KI** folgen. |
| **Übungs-KI laut Spec** | P0: Kurzfassung + SkillVorschläge (`include_summary` / `include_skills`); **kein** Auto-KI beim Speichern (S5 im Umsetzungsplan). | Feinspez: `summary_ai_generated` bei manueller Kurzfassung zurücksetzen; Rate-Limits; Prompt-Admin-UI. |
| **Fähigkeiten-Stammdaten** | Migration **`065_skills_wiki_karate_relevance`:** `skills.karate_relevance` (Text), `skills.relevance_level` (13, optional); dazu weiterhin `description`, `focus_areas`, Kategorien, `skill_level_definitions` (Level 15 je Skill). | Diese Felder sind **expliziter Prompt-Kontext** für Skill-Vorschläge (Disambiguierung Karate vs. universal) siehe §6. |
| **Skill-Scoring & Discovery (ohne LLM)** | Router `skill_profiles.py` + Modul `skill_scoring.py`: u.a. `GET …/skill-profile` für **Rahmenprogramm**, **Trainingsmodul**, **Progressionsgraph**; `POST /skill-profiles/batch-summaries`; **`GET /api/skill-discovery/suggestions`** (Match Bibliotheksartefakte ⇄ `skill_ids`, mit `library_content_visibility_sql`). | Ergänzt §3 **Stufe 3**: deterministische **Skill-Abdeckung / Artefakt-Discovery** ist **bereits vorhanden** und kann später die **Planungs-KI** speisen (Ziel-Skill-Mengen, Vergleich „Profil des Rahmens“) ersetzt aber **nicht** die TopK-Selektion aus dem **Übungskatalog** für eine konkrete Session. |
| **Profil / Planungs-Präferenzen** | `profiles.training_planning_prefs` (JSONB, vgl. `MODULE_VERSIONS``profiles`), Planungsmodul mit u.a. **Vorlagen inkl. Split-Sessions** (`planning`), `training_units` mit **Publish in Rahmen-Slot-Blueprint**. | Zukünftige KI-Planung kann **Prefs** und **Vorlagen-Struktur** als weiche Constraints einbeziehen; Rahmen↔Einheit-Fluss ist produktiv erweitert für KI nur relevant, sobald Planungs-Endpunkte angebunden werden. |
| **Übungsliste API** | Keyset-Pagination u.a. `cursor_updated_at` + Tie-break `id` (`exercises`-Modul laut `MODULE_VERSIONS`). | Retrieval-Pipelines sollten **cursorbasiert** paginieren, nicht „alle IDs auf einmal“ laden. |
**Nächster produktiver Fokus:** Prompt-/AdminUI zur Pflege von `ai_prompts`, **Rate-Limits**, optional **Auto-KI beim Speichern**; danach Übergang zur **Planungs-KI** laut diesem Dokument.
**Architektur-Vorschau (Planungs-KI):** Damit die **kleinere, starre** Übungs-Pipeline nicht zur stillen Vorlage für Planung wird, sind **eigenes Modul**, **stufenweise Outputs mit Validierung** und ein **kompaktes Kontext-DTO** vorgesehen — siehe **`working/AI_PLANNING_KI_MULTISTAGE_FORECAST.md`**.
---
## 2. Kernproblem: Skalierung des Kontextes
Aus einer **großen Übungssammlung** („>1000 Übungen“) können weder alle **Felder** (Ziele, Ablauf, Skills, Varianten …) noch alle **Zeilen** sinnvoll in einen LLM-Prompt.
**Abgrenzung Übungsanlage (aktueller Prioritätspfad):** Hier geht der Prompt typischerweise von **einzelnen** Freitexten (`title`, `goal`, `execution`, …) und einem **Skills-Katalog-Auszug** aus nicht vom gesamten Übungsbestand. Trotzdem gilt: Aktive Skills **paginieren** oder **stufig** laden (Subset + zweite Runde nur für Kurzliste), keine vollständigen Romane aus `skill_level_definitions` für hunderte Fähigkeiten auf einmal.
**Festlegung (Planungs-KI):** Der LLM-Prompt erhält immer nur ein **begrenztes Kontext-Paket** mit:
| Paketteil | Zweck |
|-----------|--------|
| **Auftrag** | z.B. Sektionstyp, Dauerbudget, Schwierigkeit, erlaubte Phasen/Streams |
| **Hard Constraints** | Gruppe, Termin/Zeitraum, Governance-Filter bereits angewendet |
| **Komprimierte Historie** | Letzte *N* Einheiten als **Liste von Übungs-IDs + Kurzlabels** (+ optional Haupt-Skills), keine vollen Fließtexte |
| **Ziele / Rahmen** | Kurztexte aus Rahmen-Slot/Zielblöcken oder Trainer-Prompt |
| **Kandidaten-TopK** | z.B. 30120 Übungen, **je Zeile gekürzt** (Titel, `summary`, 25 Skill-Namen/Stufen); **nie** der gesamte Katalog |
| **Strukturierte Kanten optional** | Kleine Mengen Kanten aus Progressionsgraph: „Nachbarn von zuletzt genutzten Übungen“ |
**ZahlenRichtwerte (überarbeitungsfähig):**
Kandidaten **vor** dem LLM typischerweise **50150** Einträge; im Prompt durch Token-Limit weiter **truncate** oder **zweistufig** (grober Ranking-Schritt ohne LLM, dann finer mit LLM auf Top40).
---
## 3. Pipeline: „Selektion vor dem Prompt“
Die **„optimale“** Auswahl entsteht **nicht**, indem das Modell den Katalog „im Kopf“ hält, sondern über eine **mehrstufige Pipeline**:
### Stufe 1 Harte Filter (deterministisch, DB)
Synchron zur bestehenden Suche/List-API-Logik, z.B.:
- Sichtbarkeit / Verein / `official`Regeln
- Aktivitäts-/Archiv-Status der Übung
- Fokusbereich, Stil, Zielgruppe (wenn Trainings-/Gruppenkontext das vorgibt)
- Ausschluss bereits in **dieser Einheit** fester Übernutzung (optional)
Ergebnis: Menge \(M\) kann noch sehr groß sein.
### Stufe 2 Kontext-Verankerung (deterministisch + Graph)
- **Historie:** aus letzten *N* Gruppeneinheiten extrahierte `exercise_id`s (optional Variant).
- **Progressionsgraph:** ausgehend davon Nachbarn (eingehend/ausgehend begrenzte Tiefe) bereits im Produkt als **unterstützend** modelliert (**CURR013**).
- **Rahmen/Slot-Ziele:** Überlapp mit Skill-Tags oder Stichwortliste (falls formalisiert).
- **Variantenketten:** `prerequisite_variant_id` / `progression_level` nur innerhalb bereits gewählter Übung prüfen oder als Hint an den LLM-Block durchreichen.
Ergebnis: **„AnkerMenge“** + **„GraphNachbarschaft“** → priorisierte Kandidaten.
### Stufe 3 Weiches Ranking / Retrieval (halb-strukturiert)
Mindestens **eine** der folgenden Optionen kombinierbar:
1. **Skill-/Facet-Overlap:** Punktezahl, wenn Übungs-Skills mit Ziel-/Matrix-Schwerpunkten übereinstimmen (bereits Daten in `exercise_skills`).
2. **Diversitäts-/Wiederholungsstrafe:** häufig in letzten Wochen geübte Übungen abwerten.
3. **Textsuche:** PostgreSQL **`tsvector`/Volltext** auf `title`, `summary`, ggf. gekürzte `goal` für Trainer-Stichwort „Koordination Sprung“.
4. **Semantische Suche:** Embeddings + **Ähnlichkeitsuche** auf Kurztexte (siehe §5).
5. **Skill-Discovery über Planungs-Artefakte (bereits implementiert):** `GET /api/skill-discovery/suggestions` matching **Bibliotheksartefakte** (u.a. Rahmenprogramm, Trainingsmodul, Progressionsgraph) gegen gegebene `skill_ids`; `GET …/skill-profile` liefert **gewichtete Fähigkeitsprofile** aus den dort verknüpften Übungen (siehe `SKILL_SCORING_SPEC.md`). Das ist ein **deterministischer** Baustein für „welche Artefakte passen zu diesen Skills?“, **nicht** der Ersatz für **TopK-Übung**-Auswahl in einer konkreten Session dort weiter Stufen 12 + Punkte 14/LLM.
Ergebnis: sortierte Liste, **TopK** für den Prompt.
### Stufe 4 LLM (optional zweistufig)
- **Optional 1:** LLM nur **sortiert/rankted** bereits vorgegebene IDs (Ranking mit kurzer Begründung).
- **Optional 2:** Zwei Calls: erst „welche drei Schwerpunkte“ / „Welche Constraints habe ich übersehen?“, zweiter Call nur mit gekürztem TopK nur wenn UX den Mehraufwand rechtfertigt.
**Ausgabe-Contract:** Zurückkommen dürfen **nur gültige `exercise_id`s** aus der übergebenen Kandidatenliste (Server validiert gegen Set); **Halluzinationsrisiko damit entschärft**.
---
## 4. Antwort auf die konkrete Frage: „Alle Übungen in den Prompt?“
**Nein.** Workflow:
1. **DB + Regeln + Graph + Historie** reduzieren auf **einige Hundert bis wenige tausend** Rohzeilen höchstens **intern** aber
2. in den **LLM-Prompt** gehen nur **TopK kompakte Artefakte** (siehe §2).
Das LLM löst dann **Ranking, Reihenfolge, Timing-Hinweise, Trainer-sprachliche Kurzkommentare** nicht die Frage „gibt es diese Übung überhaupt im System?“.
---
## 5. Vector DB (z.B. Qdrant) wann nötig, wann nicht?
### 5.1 Ziel embeddings
Semantic Retrieval: „wie springt Coordinative Belastung ohne das Wort Koordination im Titel zu stehen.“ Das ist **über** reine Filtersuche und oft **über** stumpfe Volltextsuche erreichbar.
### 5.2 Option A ohne separate Vector DB
- **PostgreSQL + `pgvector`** (Extension): Embeddings-Spalte an `exercises` (oder an „Search Document“-Zeilen), Indices, Abfrage zusammen mit SQL-Filtern in **einer Transaktions-DB**.
- **Größenordnung** einige 10k100k Zeilen für Übungen: für Shinkan **oft ausreichend**.
- Vorteile: ein Betriebspfad, Mandanten-/Governance weiter in SQL lösen; Backup wie heute.
### 5.3 Option B Qdrant (oder anderer Dediz-Vektorstore)
Sinnvoller zeitlicher Punkt oder technische Auslöser:
- sehr hohe Latenz-Anforderung mit **Hybrid-Filter** über viele kombinierte Metadaten in nahezu Echtzeit,
- Entkopplung: Embedding-Pipeline läuft asynchron und soll die **Operational DB** nicht beschweren,
- später **mehrsprachig** oder **mehrere Embedding-Versionen**/Re-Index ohne großen Migrationstress,
- Team bevorzugt **Dedicated** Vector-Ops gegenüber Postgres-Ops.
### 5.4 Empfehlung für diese Codebasis (überarbeitungsfähig)
1. **Phase 1:** Harte Filter + Graph + Historie + **PostgreSQL-Volltext** + TopK; LLM erst auf komprimierten Kandidaten → hoher Nutzen ohne neuen Infrastructure-Typen.
2. **Phase 2:** Bei nachweisbaren „Recall-Lücken“ (Trainer findet nichts Passendes trotz großem Korpus) **`pgvector` in Postgres** evaluieren **vor** zusätzlicher Infrastruktur wie Qdrant.
3. **Phase 3:** Qdrant (o. ä.) wenn Größenordnung, Betrieb oder Produkt-Anforderungen **pgvector** sprengen oder klar eine **embedding-first** Produktstraße geplant wird.
**Fazit:** Eine dedizierte **Vector DB ist nicht zwingende Voraussetzung** für vernünftige Selektion; sie ist eine **Ausbaustufe**, wenn **semantische Lücke** und Skalierung es rechtfertigen. **Selektion** ist immer **„Filter + Ranking + kleines TopK“**, unabhängig vom Speicherort der Vektoren.
---
## 6. Datenpflege für gutes Retrieval (fachlicher Hebel)
RetrievalQualität hängt stärker an **Metadaten** als an der Embedding-Technologie allein:
- verlässliche **Skills** (`exercise_skills`, ggf. KI-geholfen bereits laut Spez beim Übungs-Anlegen); `exercise_skills.ai_suggested` und kanonische Stufen (`required_level` / `target_level` als Slugs) für Nachvollziehbarkeit.
- **`skills`-Stamm:** `description`, **`karate_relevance`**, **`relevance_level` (13)**, **`focus_areas`**, Kategorien/Keywords für **Prompt-Kontext** beim Skill-Mapping bei der Übungsanlage; optional **`skill_level_definitions`** für Stufen 15 **gezielt** in die zweite Prompt-Runde (nur Kurzliste Kandidaten).
- sinnvolle **`summary`**-Felder für Karten/Liste/KI-Pack;
- **Progressionsgraph** dort, wo pädagogische Ketten gefestigt sind;
- konsistente **Fokusbereich/Stil**-Zuordnung.
Das fachliche Konzept sollte entscheiden: **wie viel automatische Pflege vs. Trainer-Pflichtfelder**.
---
## 7. Produkt-/Release-Stufen (Anknüpfung)
Priorität **jetzt**: **Übungsanlage**, danach **Planung**.
| Stufe | Nutzen | Technik-Schwerpunkt |
|-------|--------|---------------------|
| **A0** | **Zentraler KI-Service** (ein Modul/Hilfslayer), Prompts aus `ai_prompts` | OpenRouter oder vereinbarter Provider, Timeouts, `503` ohne Key, Parsing/Validation |
| **A1** | **Übungsanlage** (vgl. `KI_FEATURES_SPEC`): `summary`, Skill-Vorschläge inkl. Stufen/Intensität, optional Textglättung | `POST /api/exercises/ai/suggest`, `POST /api/exercises/{id}/ai/regenerate`; Prompt-Kontext: Skills mit `description`, `karate_relevance`, `relevance_level`, optional `skill_level_definitions` für Kurzliste; DB: `summary_ai_generated`, `exercise_skills.ai_suggested` |
| B | „Übungen für Abschnitt vorschlagen“ | Pipeline §3 Stufen 13 + Prompt mit TopK (Übungsliste **keyset-pagination** beachten) |
| C | Reihenfolge / Zeitslots innerhalb bestehender Sektion | Graph + LLM Ranking |
| D | Ganze Einheit (inkl. Phasen/Streams vereinfacht) | strukturiertes JSON + strikte Schema-Validation gegen bestehende `PUT`-Payloads |
| E | Mehreinheiten / RahmenAlignment | Ziele aus Rahmenprogramm, Serie von Slots; **Skill-Profile** (`…/skill-profile`) als Kontextuelle Verstärker |
Die **SelektionsPipeline §3** bleibt für **Planungs**-KI konsistent und wird parametrierbar erweitert; **§1.1** spiegelt den **aktuellen Implementierungs**-Vorsprung (Skill-Scoring ohne LLM) wider.
---
## 8. Compliance & Datenschutz (Kurzhinweis)
- Datenminimierung: **keine Teilnehmerliste** ohne expliziten Scope; Kontext über **Einheiten-Metadaten** und Übungen.
- **OpenRouter**/Modellwahl: Organisation intern klären (AV/Verarbeitungsvertrag, Datenflüsse außerhalb EU siehe Repo-Compliance-Dokumente).
- **Logging:** Prompts keine unnötigen personenbezogenen Daten; wenn Debug: Retention definieren.
---
## 9. Offene Punkte für die fachliche Verfeinerung
- Gewichtung „**Wiederholung** vs. **Progression** vs. **Motivation**“ (domänenspezifisch).
- Umgang mit **Kombinationsübungen** und **Coach-Stufen B/C** in der Datenübergabe.
- Soll das System **„Lücken“** aus der **Matrix-Auflösung** aktiv quantifizieren oder nur Narrative verwenden?
- Akzeptierte **Übersetzungen**: nur Deutsch oder mehrsprachige Embeddings erforderlich?
---
## 10. Glossar
| Begriff | Bedeutung |
|---------|-----------|
| **TopK** | Feste kleine Obergrenze Übungen pro LLM-Anfrage |
| **Hard filter** | Deterministische SQL-/Policy-Einschränkung vor KI |
| **Kontext-Paket** | Zusammengesetztes, tokenlimitiertes Eingabeobjekt für den Prompt |
| **Retrieval** | algorithmischer Schritt ohne Generierung („wer kommt überhaupt in Frage“) |

View File

@ -0,0 +1,331 @@
# Capability-Katalog Shinkan v1
**Status:** Konzept (verbindliche Zieldefinition; M3 teilweise umgesetzt)
**Stand:** 2026-06-06
**Bezüge:** `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` (Stufe E), `MULTI_TENANCY_RBAC_ARCHITECTURE.md`, `CLUB_MEMBERSHIP_AND_FEATURES.v1.md`, **`MEMBERSHIP_RBAC_DECISIONS_2026-06.md`** (Produktentscheidungen)
---
## 1. Zweck
Dieses Dokument definiert **benannte Capabilities** (Wer darf welche **Funktion** ausführen?) — getrennt von:
- **Governance** (Darf ich *dieses Objekt* lesen/ändern? → `visibility`, `club_id`, `created_by`)
- **Feature-Limits** (Wie viel darf der **Verein**? → `CLUB_MEMBERSHIP_AND_FEATURES.v1.md`)
Capabilities beantworten: *„Darf ein Trainer mit Rolle X die Funktion Y im Verein Z überhaupt nutzen?“*
---
## 2. Namenskonvention
```
{domain}.{action}[.{qualifier}]
```
| Segment | Beispiele |
|---------|-----------|
| `domain` | `exercises`, `media`, `planning`, `org`, `platform` |
| `action` | `read`, `create`, `update`, `delete`, `manage`, `execute` |
| `qualifier` | `ai.suggest`, `join_request`, `inbox.review` |
**CRUD-Mapping:**
| Aktion | Capability-Suffix | Bedeutung |
|--------|-------------------|-----------|
| Lesen (Listen/Detail) | `.read` | Navigation + API-Lesen erlaubt |
| Anlegen | `.create` | POST/INSERT |
| Bearbeiten | `.update` | PUT/PATCH (eigenes + berechtigtes Fremdes) |
| Löschen | `.delete` | DELETE (strenger als update) |
| Verwalten | `.manage` | Org-Funktionen, Freigaben, Mitglieder |
| Ausführen (ohne Persistenz) | `.execute` | z. B. KI-Vorschau, Coach-Lauf |
Objektbezogene Feinheiten (nur Ersteller, nur Vereinsadmin des Objekt-Vereins) bleiben in **Governance** — Capabilities sind das **Tür-Schloss** davor.
---
## 3. Account-Lifecycle (Voraussetzung für Capabilities)
| `account_state` | Bedingung | Typische Capabilities |
|-----------------|-----------|------------------------|
| `anonymous` | Keine Session | nur öffentliche Routen (`/login`, Rechtstexte, `clubs/public-directory`) |
| `unverified` | Session, `email_verified=false` | `account.resend_verification`, `account.logout` |
| `verified_pending_club` | Verifiziert, keine aktive `club_members` | `club.join_request`, `club.creation_request` (M7), `account.settings`**kein** Lesezugriff auf Domänen-Inhalte (siehe Entscheidungs-Doc §1.1) |
| `active_member` | Mind. eine aktive Vereinsmitgliedschaft | Domänen-Capabilities gemäß Vereinsrolle |
| `platform_admin` | `role``admin`, `superadmin` | `platform.*` zusätzlich |
**Regel:** Domänen-Capabilities (`exercises.*`, `planning.*`, …) erfordern mindestens `active_member`, sofern nicht `platform_admin`.
---
## 4. Rollen-Scopes
### 4.1 Portal-Rollen (`profiles.role`)
| Rolle | Scope | Kurz |
|-------|-------|------|
| `user` | Portal | Standard nach Registrierung (Zielbild; heute oft `trainer` Legacy) |
| `trainer` | Portal | Legacy — mittelfristig durch `user` + Vereinsrollen ersetzen |
| `admin` | Portal | Plattform-Admin (Vereine anlegen, erweiterte Ops) |
| `superadmin` | Portal | Vollzugriff Plattform + Superadmin-Werkzeuge |
### 4.2 Vereinsrollen (`club_member_roles.role_code`)
| Rolle | Fachlich |
|-------|----------|
| `club_admin` | Vereinsorganisation, Mitglieder, Struktur |
| `trainer` | Planung, Übungen, Durchführung |
| `content_editor` | Inhalte pflegen (Bibliothek) |
| `division_lead` | Spartenleitung (später division-scope) |
Mehrfachrollen pro Mitgliedschaft sind möglich (OR-Verknüpfung der Capabilities).
### 4.3 Mapping heutiger Helfer → Capabilities
| Heutiger Code (`club_tenancy.py`) | Ziel-Capability-Cluster |
|-----------------------------------|-------------------------|
| `can_manage_club_org` | `org.structure.manage`, `org.members.manage`, `org.inbox.review` |
| `can_plan_in_club` | `planning.*`, `exercises.create/update`, `modules.*`, `framework.*` |
| `is_platform_admin` | `platform.*` (Bypass Mandant, Audit-Pflicht) |
| `is_superadmin` | `platform.superadmin.*` |
---
## 5. Capability-Katalog (v1)
Legende Spalten:
- **Min. Account:** `verified_pending_club` | `active_member` | `platform_admin`
- **Vereinsrollen:** leer = alle aktiven Mitglieder; sonst mindestens eine Rolle
- **Feature-ID:** optionales Kontingent (siehe Club-Membership-Doc); leer = kein Limit
- **Governance:** zusätzliche Objektprüfung ja/nein
### 5.1 Account & Onboarding
| Capability-ID | Min. Account | Vereinsrollen | Feature-ID | Endpoints / UI |
|---------------|--------------|---------------|------------|----------------|
| `account.settings.read` | `unverified` | — | — | `GET /profiles/me`, Einstellungen |
| `account.settings.update` | `unverified` | — | — | `PUT /profiles/{id}` (eigenes Profil) |
| `account.password.change` | `unverified` | — | — | `PUT /api/auth/pin` |
| `account.resend_verification` | `unverified` | — | — | `POST /api/auth/resend-verification` |
| `club.directory.read` | `verified_pending_club` | — | — | `GET /clubs/public-directory` |
| `club.join_request.create` | `verified_pending_club` | — | — | `POST /me/club-join-requests`, Registrierung mit `requested_club_id` |
| `club.join_request.withdraw` | `verified_pending_club` | — | — | `DELETE /me/club-join-requests/{id}` |
| `club.join_request.read_own` | `verified_pending_club` | — | — | `GET /me/club-join-requests` |
### 5.2 Organisation (Verein)
| Capability-ID | Min. Account | Vereinsrollen | Feature-ID | Endpoints / UI |
|---------------|--------------|---------------|------------|----------------|
| `org.club.read` | `active_member` | * | — | `GET /clubs`, `GET /clubs/{id}` (eigene Vereine) |
| `org.club.create` | `platform_admin` | — | — | `POST /clubs` |
| `org.club.update` | `platform_admin` | `club_admin` | — | `PUT /clubs/{id}` |
| `org.club.delete` | `platform_admin` | — | — | `DELETE /clubs/{id}` |
| `org.structure.manage` | `active_member` | `club_admin` | `training_groups` | Sparten, Gruppen CRUD |
| `org.members.read` | `active_member` | `club_admin` | — | `GET /clubs/{id}/members` |
| `org.members.manage` | `active_member` | `club_admin` | `active_members` | POST/PUT/DELETE Mitglieder |
| `org.members.directory` | `active_member` | * | — | `GET /clubs/{id}/members/directory` (ohne E-Mail für Nicht-Admins) |
| `org.join_request.review` | `active_member` | `club_admin` | — | Join-Request accept/reject, Inbox |
| `org.inbox.read` | `active_member` | `club_admin` | — | Posteingang Join + Content-Reports |
### 5.3 Übungen
| Capability-ID | Min. Account | Vereinsrollen | Feature-ID | Governance |
|---------------|--------------|---------------|------------|------------|
| `exercises.read` | `active_member` | * | — | ja (visibility) |
| `exercises.create` | `active_member` | `trainer`, `content_editor`, `club_admin`, `division_lead` | `exercises` | — |
| `exercises.update` | `active_member` | `trainer`, `content_editor`, `club_admin`, `division_lead` | — | ja |
| `exercises.delete` | `active_member` | `club_admin` (+ Ersteller privat) | — | ja |
| `exercises.bulk_metadata` | `active_member` | `content_editor`, `club_admin` | — | ja |
| `exercises.ai.suggest` | `active_member` | `trainer`, `content_editor`, `club_admin` | `ai_calls` | — |
| `exercises.ai.regenerate` | `active_member` | `trainer`, `content_editor`, `club_admin` | `ai_calls` | ja (edit) |
| `exercises.media.read` | `active_member` | * | — | ja |
| `exercises.media.upload` | `active_member` | `trainer`, `content_editor`, `club_admin` | `exercise_media` | ja |
| `exercises.variants.manage` | `active_member` | `trainer`, `content_editor`, `club_admin` | — | ja |
**Representative Endpoints:** `/api/exercises*`, `/api/exercises/ai/*`, Medien-Datei-Download.
### 5.4 Medien-Bibliothek (Archiv)
| Capability-ID | Min. Account | Vereinsrollen | Feature-ID | Governance |
|---------------|--------------|---------------|------------|------------|
| `media.library.read` | `active_member` | * | — | ja |
| `media.library.upload` | `active_member` | `trainer`, `content_editor`, `club_admin` | `exercise_media` | ja |
| `media.library.update` | `active_member` | `trainer`, `content_editor`, `club_admin` | — | ja |
| `media.library.lifecycle` | `active_member` | `trainer`, `club_admin` | — | ja |
| `media.rights.declare` | `active_member` | `trainer`, `club_admin` | — | ja |
| `media.admin.rights_review` | `platform_admin` | — | — | Plattform-Admin Legacy-Review |
### 5.5 Trainingsmodule & Rahmenprogramme
| Capability-ID | Min. Account | Vereinsrollen | Feature-ID | Governance |
|---------------|--------------|---------------|------------|------------|
| `modules.read` | `active_member` | * | — | ja |
| `modules.create` | `active_member` | `trainer`, `content_editor`, `club_admin` | `training_programs` | — |
| `modules.update` | `active_member` | `trainer`, `content_editor`, `club_admin` | — | ja |
| `modules.delete` | `active_member` | `club_admin` (+ Ersteller) | — | ja |
| `framework.read` | `active_member` | * | — | ja |
| `framework.create` | `active_member` | `trainer`, `club_admin` | `training_programs` | — |
| `framework.update` | `active_member` | `trainer`, `club_admin` | — | ja |
| `framework.delete` | `active_member` | `club_admin` (+ Ersteller) | — | ja |
| `plan_templates.read` | `active_member` | * | — | ja |
| `plan_templates.manage` | `active_member` | `trainer`, `club_admin` | — | ja |
### 5.6 Progressionspfade
| Capability-ID | Min. Account | Vereinsrollen | Feature-ID | Governance |
|---------------|--------------|---------------|------------|------------|
| `progression.read` | `active_member` | * | — | ja |
| `progression.manage` | `active_member` | `trainer`, `content_editor`, `club_admin` | — | ja |
### 5.7 Planung & Durchführung
| Capability-ID | Min. Account | Vereinsrollen | Feature-ID | Governance |
|---------------|--------------|---------------|------------|------------|
| `planning.calendar.read` | `active_member` | * | — | ja (Gruppe/Verein) |
| `planning.units.create` | `active_member` | `trainer`, `club_admin`, `division_lead` | `training_units` | ja |
| `planning.units.update` | `active_member` | `trainer`, `club_admin`, `division_lead` | — | ja |
| `planning.units.delete` | `active_member` | `club_admin`, `trainer` (eigene) | — | ja |
| `planning.units.run` | `active_member` | `trainer`, `club_admin`, `division_lead` | — | ja |
| `planning.coach.execute` | `active_member` | `trainer`, `club_admin` | — | ja |
| `planning.ai.suggest` | `active_member` | `trainer`, `club_admin` | `ai_calls` | — |
| `planning.ai.progression_path` | `active_member` | `trainer`, `club_admin` | `ai_calls` | — |
### 5.8 Fähigkeiten & Scoring
| Capability-ID | Min. Account | Vereinsrollen | Feature-ID | Governance |
|---------------|--------------|---------------|------------|------------|
| `skills.catalog.read` | `active_member` | * | — | globaler Katalog |
| `skills.discovery.read` | `active_member` | `trainer`, `content_editor` | — | — |
| `skill_profiles.read` | `active_member` | * | — | ja (Artefakt) |
### 5.9 Governance & Meldungen
| Capability-ID | Min. Account | Vereinsrollen | Feature-ID |
|---------------|--------------|---------------|------------|
| `governance.content_report.create` | `active_member` | * | — |
| `governance.content_report.review` | `active_member` | `club_admin` | — |
| `governance.change_request.*` | `active_member` | `content_editor`, `club_admin` | — |
### 5.10 Plattform (nur Portal-Admin / Superadmin)
| Capability-ID | Min. Account | Portal-Rolle | Feature-ID |
|---------------|--------------|--------------|------------|
| `platform.admin.access` | `platform_admin` | `admin`, `superadmin` | — |
| `platform.users.manage` | `platform_admin` | `superadmin` | — |
| `platform.catalogs.manage` | `platform_admin` | `superadmin` | — |
| `platform.maturity_models.manage` | `platform_admin` | `superadmin` | — |
| `platform.wiki_import.execute` | `platform_admin` | `superadmin` | `wiki_import` |
| `platform.ai_prompts.manage` | `platform_admin` | `superadmin` | — |
| `platform.exercise_enrichment.execute` | `platform_admin` | `superadmin` | `ai_calls` |
| `platform.user_content.moderate` | `platform_admin` | `superadmin` | — |
| `platform.legal_documents.manage` | `platform_admin` | `superadmin` | — |
| `platform.media_storage.manage` | `platform_admin` | `superadmin` | — |
| `platform.club_creation.approve` | `platform_admin` | `superadmin` | — |
*Geplant:* `club.creation_request.submit``verified_pending_club`; Freigabe über `platform.club_creation.approve`.
---
## 6. Standard-Zuordnung Vereinsrolle → Capabilities (v1, fest)
Diese Tabelle ist die **initiale** Grant-Matrix (`club_role_capability_grants`). Später durch Custom Roles ersetzbar — gleiche Capability-IDs.
| Capability-Cluster | `club_admin` | `trainer` | `content_editor` | `division_lead` |
|--------------------|:------------:|:---------:|:----------------:|:---------------:|
| `org.structure.manage` | ✓ | — | — | ✓ (eigene Sparte, später) |
| `org.members.manage` | ✓ | — | — | — |
| `org.join_request.review` | ✓ | — | — | — |
| `exercises.read` | ✓ | ✓ | ✓ | ✓ |
| `exercises.create/update` | ✓ | ✓ | ✓ | ✓ |
| `exercises.delete` | ✓ | — | — | — |
| `exercises.ai.*` | ✓ | ✓ | ✓ | ✓ |
| `media.library.*` | ✓ | ✓ | ✓ | ✓ |
| `modules.*` / `framework.*` | ✓ | ✓ | ✓ | ✓ |
| `planning.*` | ✓ | ✓ | — | ✓ |
| `planning.coach.execute` | ✓ | ✓ | — | ✓ |
| `governance.content_report.review` | ✓ | — | — | — |
---
## 7. API-Vertrag (Ziel)
### 7.1 Effektive Rechte für Frontend
```
GET /api/me/entitlements?club_id={optional}
```
Antwort (Ausschnitt):
```json
{
"account_state": "active_member",
"portal_role": "user",
"club_id": 12,
"club_roles": ["trainer"],
"capabilities": {
"exercises.read": true,
"exercises.ai.suggest": true,
"org.members.manage": false
},
"features": {
"ai_calls": { "allowed": true, "used": 4, "limit": 50, "remaining": 46, "reset_at": "2026-07-01T00:00:00Z" }
}
}
```
Frontend: Navigation und Buttons nur aus dieser Antwort — **keine** duplizierten Rollen-Checks in JSX (Ausnahme: rein kosmetische Labels).
### 7.2 Backend-Enforcement
Zentral (Zielmodul `authorization/capabilities.py` oder Erweiterung `club_tenancy.py`):
```python
assert_capability(tenant, "exercises.ai.suggest", club_id=tenant.effective_club_id)
assert_club_feature(tenant, "ai_calls", club_id=tenant.effective_club_id) # siehe Club-Membership-Doc
# + bestehende Governance auf Objekt-Ebene
```
---
## 8. Implementierungsreihenfolge (Capabilities)
| Phase | Inhalt |
|-------|--------|
| C0 | Account-Gates (`unverified`, `verified_pending_club`) — ohne Capability-DB |
| C1 | `capabilities` + `club_role_capability_grants` seed aus §56 |
| C2 | `GET /api/me/entitlements` + Frontend-Nav |
| C3 | Enforcement: KI-Endpoints, `exercises.create`, `planning.*` |
| C4 | Restliche Router schrittweise; Audit in `ACCESS_LAYER_ENDPOINT_AUDIT.md` |
| C5 | Custom Roles (optional) — gleiche IDs |
---
## 9. Abgrenzung & Drift-Schutz
1. **Neue Nutzerfunktion**`register_capability()` in `rights_registrations/<modul>.py`, dann Endpoint mit `probe_capability`. Namenskonvention hier dokumentieren — **kein** Bulk-Seed in Migrationen.
2. **Kontingent**`register_feature()` im selben Modul; Consume über `consume_club_feature_with_usage`.
3. **Kein** paralleles `if (user.role === 'trainer')` für Sicherheit — nur UX-Fallback.
4. Capability ≠ Feature: `exercises.ai.suggest` (darf ich?) vs. `ai_calls` (wie viel übrig?).
5. Plattform-Admin-Bypass dokumentieren und auditieren (`platform_admin` sieht Mandant, nicht automatisch alle Quotas).
Siehe **`docs/working/RIGHTS_AND_FEATURES_REGISTRY.md`** (Registry-first, ersetzt Katalog-first aus 079).
---
## 10. Referenzen
| Dokument | Inhalt |
|----------|--------|
| `CLUB_MEMBERSHIP_AND_FEATURES.v1.md` | Vereinsabo, Feature-Registry, Kontingente |
| `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` | TenantContext, Governance, Stufe E |
| `MULTI_TENANCY_RBAC_ARCHITECTURE.md` | §4.6 Vereinsabo-Zielbild |
| `ACCESS_LAYER_ENDPOINT_AUDIT.md` | Endpoint-Pflege |
| Mitai `FEATURE_ENFORCEMENT.md` | 4-Phasen-Rollout-Vorbild |
---
**Changelog**
- 2026-06-06: v1 — Initial-Katalog aus Ist-Code (`club_tenancy`, Router-Inventar) + Ziel-Onboarding.

View File

@ -0,0 +1,478 @@
# Vereins-Membership & Feature-System Shinkan v1
**Status:** Konzept + M1M3 teilweise produktiv (siehe Entscheidungs-Doc §2)
**Stand:** 2026-06-06
**Bezüge:** Schwesterprojekt Mitai (`v9c_subscription_system.sql`, `FEATURE_ENFORCEMENT.md`), `CAPABILITY_CATALOG.v1.md`, `MULTI_TENANCY_RBAC_ARCHITECTURE.md` §4.6, `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`, **`MEMBERSHIP_RBAC_DECISIONS_2026-06.md`**
---
## 1. Zweck
Shinkan verkauft und limitiert **nicht Einzelpersonen** (wie Mitai), sondern **Vereine**. Dieses Dokument definiert:
- das **Feature-Registry**-Muster (limitierbare Funktionen),
- das **Vereins-Abo** (`club_plans`, `club_subscriptions`),
- **Kontingente** und Enforcement,
- die **Abbildung von Mitai** und **Vermeidung von Refactoring-Schulden**.
Capabilities (Rollen: *darf ich die Funktion?*) → `CAPABILITY_CATALOG.v1.md`.
---
## 2. Grundprinzip: Zwei Achsen
```mermaid
flowchart TB
subgraph cap [Achse 1 — Capabilities]
CR[club_role_capability_grants]
PR[portal_role_capability_grants]
end
subgraph feat [Achse 2 — Features / Kontingente]
FP[club_plans]
FPL[club_plan_limits]
FS[club_subscriptions]
FU[club_feature_usage]
end
subgraph gov [Achse 3 — Governance]
GV[visibility / club_id / created_by]
end
REQ[HTTP Request] --> ACCT[Account-Lifecycle]
ACCT --> cap
cap --> gov
gov --> feat
feat --> EXEC[Ausführung + increment]
```
| Frage | System | Subjekt |
|-------|--------|---------|
| Darf Trainer X KI nutzen? | Capability `exercises.ai.suggest` | `profile_id` + `club_role` |
| Wie viele KI-Aufrufe hat Verein Y? | Feature `ai_calls` | **`club_id`** |
| Darf ich diese Übung ändern? | Governance | Objekt + Mitgliedschaft |
**Beide Achsen müssen erfüllt sein** (AND), außer dokumentierte Plattform-Ausnahmen.
---
## 3. Mitai-Mapping (was übernehmen, was nicht)
### 3.1 Übernehmen (Pattern)
| Mitai (Person) | Shinkan (Verein) | Anmerkung |
|----------------|------------------|-----------|
| `features` (TEXT-PK, Registry) | `features` (`app='shinkan'`) | Gemeinsames Muster, ggf. später Jinkendo-weit |
| `tiers` | `club_plans` | Produktdefinition |
| `tier_limits` | `club_plan_limits` | Matrix Plan × Feature |
| `user_feature_restrictions` | `club_feature_overrides` | Admin-Override pro Verein |
| `user_feature_usage` | `club_feature_usage` | Verbrauch pro Verein |
| `access_grants` | `club_access_grants` | Trial, Promo, manuelle Freischaltung |
| `check_feature_access()` | `check_club_feature_access()` | Subjekt `club_id` |
| `increment_feature_usage()` | `increment_club_feature_usage()` | Nur bei INSERT / KI-Call |
| 4-Phasen-Rollout | identisch | Log → UI → Hard-Block |
| `GET /api/features/usage` | `GET /api/clubs/{id}/entitlements` | siehe Capability-Doc §7 |
### 3.2 Nicht übernehmen
| Mitai | Shinkan-Grund |
|-------|---------------|
| `profiles.tier` als Haupt-Abo | Verein zahlt, nicht Einzeltrainer |
| `subscriptions` (Shinkan `001`, INT-Features) | Ungenutzt, Schema-Drift |
| `get_effective_tier(profile_id)` für Shinkan-Limits | Ersetzen durch `get_effective_club_plan(club_id)` |
| Profil-zentrierte Enforcement-Hooks allein | Primär `club_id`; Profil nur für Attribution |
### 3.3 Parallelität Jinkendo-Familie (später)
`CENTRAL_SUBSCRIPTION_SYSTEM.md` (Mitai): zentrales Personen-Abo über Apps.
**Zielbild ohne Refactoring:**
```
features.enforcement_subject ∈ { 'club', 'profile', 'portal' }
effektives_limit(feature) = merge(
club_plan_limit(club_id, feature), # Shinkan-Hauptquelle
profile_grant_limit(profile_id, feature) # optional Jinkendo-Bonus
)
```
Merge-Regel (Vorschlag): **Maximum** der erlaubten Kontingente, boolean = OR. Details vor Stripe festlegen.
---
## 4. Ist-Zustand Shinkan (Drift — zuerst bereinigen)
| Artefakt | Problem |
|----------|---------|
| `backend/migrations/001_auth_membership.sql` | `features.id SERIAL`, `tier_limits.tier VARCHAR` |
| `backend/auth.py` `check_feature_access()` | Erwartet Mitai-v9c-Schema (`features.id TEXT`, `tier_id`, `limit_type`, …) |
| Kein Router | Ruft `check_feature_access` auf |
| `profiles.tier` | Existiert, ohne Shinkan-Enforcement |
**Pflicht vor Phase 3 (Enforcement):** Migration `0XX_club_features_v1.sql` — v9c-kompatibles Feature-Schema + Vereins-Tabellen; alte `001`-Feature-Zeilen migrieren oder deprecaten.
---
## 5. Ziel-Schema (v1)
### 5.1 Feature-Registry (app-weit, Mitai-kompatibel)
```sql
-- Konzept — Implementierung als nummerierte Migration
CREATE TABLE features (
id TEXT PRIMARY KEY, -- z.B. 'ai_calls'
app TEXT NOT NULL DEFAULT 'shinkan',
name TEXT NOT NULL,
description TEXT,
category TEXT NOT NULL, -- 'content'|'planning'|'ai'|'org'|'integration'|'platform'
limit_type TEXT NOT NULL DEFAULT 'count', -- 'count' | 'boolean'
reset_period TEXT NOT NULL DEFAULT 'never', -- 'never' | 'daily' | 'monthly'
default_limit INTEGER, -- NULL=∞, 0=aus
enforcement_subject TEXT NOT NULL DEFAULT 'club', -- 'club'|'profile'|'portal'
active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
```
### 5.2 Vereins-Produkte & Abo
```sql
CREATE TABLE club_plans (
id TEXT PRIMARY KEY, -- 'free', 'verein_starter', 'verein_pro'
name TEXT NOT NULL,
description TEXT,
price_monthly_cents INTEGER,
price_yearly_cents INTEGER,
stripe_price_id_monthly TEXT,
stripe_price_id_yearly TEXT,
active BOOLEAN NOT NULL DEFAULT true,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE club_subscriptions (
id SERIAL PRIMARY KEY,
club_id INT NOT NULL REFERENCES clubs(id) ON DELETE CASCADE,
plan_id TEXT NOT NULL REFERENCES club_plans(id),
status TEXT NOT NULL DEFAULT 'active', -- active|trial|past_due|cancelled
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
ends_at TIMESTAMPTZ,
trial_ends_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE (club_id) -- ein aktiver Plan pro Verein (v1)
);
CREATE TABLE club_plan_limits (
id SERIAL PRIMARY KEY,
plan_id TEXT NOT NULL REFERENCES club_plans(id) ON DELETE CASCADE,
feature_id TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE,
limit_value INTEGER, -- NULL=∞, 0=deaktiviert
UNIQUE (plan_id, feature_id)
);
```
### 5.3 Overrides, Grants, Verbrauch
```sql
CREATE TABLE club_feature_overrides (
id SERIAL PRIMARY KEY,
club_id INT NOT NULL REFERENCES clubs(id) ON DELETE CASCADE,
feature_id TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE,
limit_value INTEGER NOT NULL,
reason TEXT,
set_by_profile_id INT REFERENCES profiles(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE (club_id, feature_id)
);
CREATE TABLE club_access_grants (
id SERIAL PRIMARY KEY,
club_id INT NOT NULL REFERENCES clubs(id) ON DELETE CASCADE,
plan_id TEXT REFERENCES club_plans(id),
feature_id TEXT REFERENCES features(id), -- optional Einzel-Feature
grant_limit INTEGER,
starts_at TIMESTAMPTZ NOT NULL,
ends_at TIMESTAMPTZ NOT NULL,
reason TEXT,
created_by_profile_id INT REFERENCES profiles(id) ON DELETE SET NULL
);
CREATE TABLE club_feature_usage (
id SERIAL PRIMARY KEY,
club_id INT NOT NULL REFERENCES clubs(id) ON DELETE CASCADE,
feature_id TEXT NOT NULL REFERENCES features(id) ON DELETE CASCADE,
usage_count INTEGER NOT NULL DEFAULT 0,
reset_at TIMESTAMPTZ,
last_used_at TIMESTAMPTZ,
UNIQUE (club_id, feature_id)
);
-- Optional: Attribution / Fairness / Audit
CREATE TABLE club_feature_usage_events (
id BIGSERIAL PRIMARY KEY,
club_id INT NOT NULL,
feature_id TEXT NOT NULL,
profile_id INT REFERENCES profiles(id) ON DELETE SET NULL,
action TEXT NOT NULL, -- 'ai_suggest', 'exercise_create', ...
created_at TIMESTAMPTZ DEFAULT NOW()
);
```
### 5.4 Capabilities (Rollen — Kurzreferenz)
Siehe `CAPABILITY_CATALOG.v1.md` für IDs. Tabellen:
```sql
CREATE TABLE capabilities (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
domain TEXT NOT NULL,
min_account_state TEXT NOT NULL DEFAULT 'active_member',
linked_feature_id TEXT REFERENCES features(id), -- optional Kontingent
active BOOLEAN NOT NULL DEFAULT true
);
CREATE TABLE club_role_capability_grants (
role_code TEXT NOT NULL, -- club_admin, trainer, ...
capability_id TEXT NOT NULL REFERENCES capabilities(id) ON DELETE CASCADE,
PRIMARY KEY (role_code, capability_id)
);
CREATE TABLE portal_role_capability_grants (
portal_role TEXT NOT NULL, -- admin, superadmin
capability_id TEXT NOT NULL REFERENCES capabilities(id) ON DELETE CASCADE,
PRIMARY KEY (portal_role, capability_id)
);
```
---
## 6. Shinkan Feature-Katalog (Seed v1)
Übernahme aus `001_auth_membership.sql` + Ist-Endpoints, angereichert:
| feature_id | category | limit_type | reset_period | enforcement_subject | Default Free | Beschreibung |
|------------|----------|------------|--------------|---------------------|--------------|--------------|
| `exercises` | content | count | never | club | 100 | Anzahl Übungen im Verein (Bestand) |
| `exercise_media` | content | count | monthly | club | 20 | Medien-Uploads / Monat |
| `training_units` | planning | count | monthly | club | 40 | Geplante/durchgeführte Einheiten |
| `training_programs` | planning | count | never | club | 5 | Module + Rahmenprogramme (kombiniert v1) |
| `training_groups` | org | count | never | club | 10 | Trainingsgruppen |
| `active_members` | org | count | never | club | 25 | Aktive Mitglieder |
| `ai_calls` | ai | count | monthly | club | 0 | KI-Aufrufe (Suggest, Regenerate, Planung) |
| `ai_pipeline` | ai | boolean | never | club | 0 | Erweiterte KI-Pipelines (Batch, später) |
| `wiki_import` | integration | boolean | never | portal | 0 | MediaWiki-Import (Superadmin) |
| `data_export` | integration | boolean | never | club | 0 | Export-Funktionen (wenn eingeführt) |
**Hinweis:** Free-Defaults sind Produktentscheidung — Tabelle dient Implementierung.
### 6.1 Beispiel-Pläne (Seed)
| plan_id | ai_calls/Monat | exercises | active_members |
|---------|----------------|-----------|----------------|
| `free` | 0 | 100 | 25 |
| `verein_starter` | 30 | 500 | 80 |
| `verein_pro` | 200 | NULL (∞) | NULL |
| `pilot` | 100 | NULL | NULL |
Jeder Verein erhält bei Anlage durch Superadmin initial `club_subscriptions.plan_id = 'free'` (oder `pilot`).
---
## 7. Auflösungslogik
### 7.1 Effektiver Vereinsplan
```python
def get_effective_club_plan(cur, club_id: int) -> str:
"""
1. Aktiver club_access_grants mit plan_id (höchste Priorität, Zeitfenster)
2. club_subscriptions.status == 'active' → plan_id
3. Fallback 'free'
"""
```
### 7.2 Feature-Limit (analog Mitai `check_feature_access`)
```python
def check_club_feature_access(
cur,
club_id: int,
feature_id: str,
*,
profile_id: int | None = None, # nur für Logging / optionale Profil-Boni später
) -> dict:
"""
Priorität:
1. club_feature_overrides (club_id, feature_id)
2. club_plan_limits für get_effective_club_plan(club_id)
3. features.default_limit
Auswertung:
- limit_type boolean: limit_value == 1
- limit_type count: used < limit (club_feature_usage, reset beachten)
Returns: { allowed, limit, used, remaining, reason, reset_at }
"""
```
### 7.3 Vollständige Request-Kette
```
1. require_auth
2. assert_account_state(min_state) # unverified / verified_pending_club / active_member
3. get_tenant_context
4. assert_capability(tenant, cap_id) # Rollen-Achse
5. assert_content_governance(...) # nur bei Objekt-Endpoints
6. check_club_feature_access(club_id, feature_id)
7. … Business-Logik …
8. consume_club_feature_with_usage(…) + merge_feature_usage_into_response(payload, usage)
# Standard: zählen, JSON-Log phase=consume, feature_usage in Response
9. optional: club_feature_usage_events (profile_id, action)
```
**Response-Standard (alle Consume-Endpoints):** JSON-Feld `feature_usage` — Map `feature_id → { allowed, used, limit, remaining, reason, … }` wie `GET /me/entitlements`. Frontend: `request()` synchronisiert Entitlements automatisch (`featureUsageSync.js`); UI-Komponenten brauchen keinen Einzelcode.
### 7.4 Wer zählt als Verbrauch?
| Aktion | increment | Subjekt |
|--------|-----------|---------|
| `POST /exercises` (neu) | `exercises` | `club_id` des Objekts oder `effective_club_id` |
| Medien-Upload | `exercise_media` | Verein des Mediums |
| KI Suggest/Regenerate | `ai_calls` | `effective_club_id` |
| Mitglied hinzufügen | `active_members` | Ziel-`club_id` |
| Trainingsgruppe anlegen | `training_groups` | `club_id` |
**Mitai-Regel:** Counter **nicht** bei UPDATE/DELETE erhöhen.
---
## 8. API-Oberfläche
### 8.1 Nutzer / Vereinsadmin
```
GET /api/clubs/{club_id}/entitlements
```
Kombiniert Capabilities + Feature-Kontingente (siehe `CAPABILITY_CATALOG.v1.md` §7.1).
```
GET /api/me/entitlements?club_id=12
```
Bequemer Alias für aktiven Verein.
### 8.2 Superadmin / Plattform
| Endpoint | Zweck |
|----------|-------|
| `GET/PUT /api/admin/club-plans` | Plan-CRUD |
| `GET/PUT /api/admin/club-plan-limits` | Matrix |
| `GET/PUT /api/admin/clubs/{id}/subscription` | Verein-Abo |
| `GET/PUT /api/admin/clubs/{id}/feature-overrides` | Sonderkontingente |
| `POST /api/admin/clubs/{id}/access-grants` | Trial/Promo |
Vorbild UI: Mitai `AdminTierLimitsPage.jsx`, `AdminUserRestrictionsPage.jsx` → Vereins-Kontext.
### 8.3 Geplant: Vereinsgründung
```
POST /api/club-creation-requests # Nutzer (verified_pending_club)
GET /api/admin/club-creation-requests
POST /api/admin/club-creation-requests/{id}/approve # legt club + subscription an
```
---
## 9. Vier-Phasen-Rollout (aus Mitai)
| Phase | Shinkan-Aktivität | Nutzer sichtbar? |
|-------|-------------------|------------------|
| **0** | Schema-Migration, Seed `features` + `club_plans`, Drift `001` bereinigen | Nein |
| **1** | Account-Gates + Capability-Grants (ohne Limits) | Onboarding-Hinweise |
| **2** | `check_club_feature_access`**nur JSON-Log** (`feature_logger` analog Mitai) | Nein |
| **3** | `GET …/entitlements` + UsageBadge im UI | Ja (Kontingent-Anzeige) |
| **4** | HTTP 403 bei Limit + `increment` | Ja (Hard-Block) |
**Reihenfolge innerhalb Phase 4:** zuerst `ai_calls`, dann `exercise_media`, dann Bestands-Limits (`exercises`, `active_members`).
---
## 10. CI / Test-Isolation (Betrieb)
Unabhängig vom Membership-System — **Pflicht** wegen Prod-Vorfälle (`access_layer_it_*@test.local`):
| Regel | Umsetzung |
|-------|-----------|
| Integrationstests nie gegen Prod-DB | Eigene Test-DB oder Job-Postgres in Gitea |
| `ENVIRONMENT=production` + `ALLOW_INTEGRATION_TESTS` | Default `0`, Tests abbrechen |
| Test-Accounts | E-Mail `@test.local` oder `profiles.is_test_account` |
| Cleanup | Fixture-`finally` + Nightly-Job löscht Leichen |
`.gitea/workflows/test.yml`: pytest-backend gegen Deploy-DB **ersetzen** durch isolierte DB (eigenes Epic, parallel zu Membership).
---
## 11. Implementierungs-Roadmap (gesamt)
| Schritt | Deliverable | Membership-relevant |
|---------|-------------|-------------------|
| M0 | CI-Isolation + Prod-Cleanup-Runbook | Nein |
| M1 | Migration Feature-Schema v9c + `club_plans`/`club_subscriptions` (leer nutzbar) | **Ja** |
| M2 | `check_club_feature_access` + Seed Pläne | **Ja** |
| M3 | Account-Lifecycle + Capability-Grants | Capabilities |
| M4 | `GET /me/entitlements` | **Ja** |
| M5 | Enforcement `ai_calls` (Phase 4) | **Ja** |
| M6 | Admin Plan-Matrix UI | **Ja** |
| M7 | `club_creation_requests` | Prozess |
| M8 | Stripe / Rechnung | Später |
**Nach Produktentscheidungen 2026-06-06** (Details `MEMBERSHIP_RBAC_DECISIONS_2026-06.md` §4):
| Phase | Paket | Priorität |
|-------|--------|-----------|
| A | Onboarding-Gates vollständig (`verified_pending_club`) | **Als Nächstes** |
| B | M7 Vereinsgründung beantragen | hoch |
| C | M5 Hard-Block `ai_calls` | danach |
| D | M6 Superadmin-UI | danach |
| E | Systemrolle `co_trainer` + Frontend-Entitlements | v1 Rollen |
| F | Trainer-Member-Budgets (v2) | später |
---
## 12. Offene Produktentscheidungen
Vor M6 festlegen:
1. **Zählen `active_members`:** alle Mitglieder oder nur Rollen mit Planungsrecht?
2. **Soft-Limit vs. Hard-Stop:** Warnung bei 80 % oder sofort 403?
3. **Pilotverein:** eigener Plan `pilot` mit hohen Limits?
4. **KI-Fairness:** nur Vereinslimit oder zusätzlich Max pro Trainer/Monat?
5. **Offizielle Inhalte:** für `verified_pending_club` sichtbar oder gesperrt? → **entschieden: gesperrt** (`MEMBERSHIP_RBAC_DECISIONS_2026-06.md` §1.1)
6. **Portal `admin` vs. `superadmin`:** Wer darf Vereine anlegen? (Ziel: nur `superadmin` für Freigabe)
---
## 13. Referenzen
| Pfad | Inhalt |
|------|--------|
| `c:/dev/mitai-jinkendo/backend/migrations/v9c_subscription_system.sql` | Mitai-Schema-Vorlage |
| `c:/dev/mitai-jinkendo/.claude/docs/architecture/FEATURE_ENFORCEMENT.md` | 4-Phasen-Modell |
| `c:/dev/mitai-jinkendo/.claude/docs/technical/MEMBERSHIP_SYSTEM.md` | Mitai-Hauptdoku |
| `c:/dev/mitai-jinkendo/.claude/docs/technical/CENTRAL_SUBSCRIPTION_SYSTEM.md` | Jinkendo-Familie später |
| `CAPABILITY_CATALOG.v1.md` | Rollen & Capabilities |
| `MULTI_TENANCY_RBAC_ARCHITECTURE.md` §4.6 | Ursprüngliches Vereinsabo-Zielbild |
| `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` | Stufe E/F |
---
**Changelog**
- 2026-06-06: v1 — Mitai-Mapping, Ziel-Schema, Feature-Seed, Auflösungslogik, Rollout.

View File

@ -1,8 +1,8 @@
# Shinkan Jinkendo - Datenbank-Schema (Technisch)
**Version:** 0.5.2
**Stand:** 2026-05-05
**Hinweis:** Produktiver Deploy sollte mindestens bis Migration **037** (RahmenSlotBlueprints in `training_units`; Entfall `training_framework_slot_exercises`) geführt sein — Details siehe `backend/version.py` (`DB_SCHEMA_VERSION`).
**Version:** 0.5.3
**Stand:** 2026-05-07
**Hinweis:** Produktiver Deploy sollte mindestens bis Migration **037** (RahmenSlotBlueprints) und für Medien-Archiv bis **045+** (`media_assets`, …) geführt sein — Details siehe `backend/version.py` (`DB_SCHEMA_VERSION`) und **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**.
---
@ -47,8 +47,7 @@ Dieses Dokument beschreibt die **technische Datenbankstruktur** von Shinkan Jink
| **035** | **2026-05-05** | **Rahmenprogramm:** `training_framework_programs` (+ Ziele, Slots, früher `training_framework_slot_exercises`); **`training_plan_templates.visibility`** (Backfill `club`) — siehe `TRAINING_FRAMEWORK_SPEC.md` | ✅ |
| **036** | **2026-05-05** | **Rahmen nur Bibliothek:** Kopf mit `focus_area_id`, `style_direction_id`, M:N Trainingsarten/Zielgruppen; Entfall `plan_mode`, `group_id`; Slot`training_unit_id` geleert — siehe `036_framework_program_context_only_library.sql` | ✅ |
| **037** | **2026-05-05** | **SlotBlueprint:** `training_units.framework_slot_id` (+ CHECK Blueprint vs. Kalender), `origin_framework_slot_id`; Migration SlotÜbungen → `training_unit_sections`/`training_unit_section_items`; **`DROP training_framework_slot_exercises`** | ✅ |
---
| **040046** | **2026-05** | **Mitgliedschaft/Anträge, Übungs-Governance-Erweiterungen, `media_assets`, `platform_media_storage`, `exercise_media.media_asset_id`, Tags/GIN u.a.** — exakte Nummern: `backend/migrations/`; fachliche Norm Medien: **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`** | ✅ je Umgebung |
## Migration 022: Skills Schema Complete (BREAKING CHANGE)
@ -267,9 +266,11 @@ exercise_skills (
UNIQUE (exercise_id, skill_id) -- Migration 020
)
-- Varianten & Medien
-- Varianten & Medien (028+ Embed/Datei; 045+ optional media_asset_id → media_assets)
exercise_variants (id, exercise_id, name, description, ...)
exercise_media (id, exercise_id, type, url, title, description, ...)
exercise_media (id, exercise_id, media_asset_id NULL FK, embed_url, file_path, …)
media_assets (id, sha256, visibility, club_id, lifecycle_state, copyright_notice, storage_key, …)
platform_media_storage (id, local_relative_root, …)
```
### Trainingsrahmenprogramm Bibliothek (Migrationen **035036**)

View File

@ -1,9 +1,11 @@
# Exercises API Specification
**Version:** 1.4
**Datum:** 2026-04-30
**Version:** 1.6
**Datum:** 2026-05-20
**Status:** Teilweise implementiert (Liste mit Filtern + Varianten + Medienlimits + Progressionsgraphen siehe Code)
**Autor:** Claude Code
**Änderungen v1.6:** Freigabelevel-UI-Hinweis; `exercise_skills` ohne `is_primary` in Requests (Legacy-Feld wird ignoriert/forciert false); Permissions-Bereich an Ist-Code angeglichen; Intensität kanonisch `niedrig|mittel|hoch`
**Änderungen v1.5:** Medien-/Inline-Workflow aktualisiert (Modal-Picker, Drag&Drop UX im Frontend), Klarstellung zu `context` (legacy/optional), Hinweise zu Platzhaltern in Rich-Text-Feldern.
**Änderungen v1.4:** Endpoints **`/exercise-progression-graphs`** inkl. Kanten, **`POST …/edges/sequence`**, **`POST …/edges/delete-batch`** — Detailtabellen siehe **`TRAINING_FRAMEWORK_SPEC.md`** §3.3
**Änderungen v1.3:** `GET /exercises` erweiterte Query-Parameter (`include_variants`, Multi-Filter, `ai_search`-Platzhalter); Dokumentation angepasst
**Änderungen v1.2:** KI-Assistenz Endpoints, Skill-Level-System (benannte Stufen), intensity als low/medium/high
@ -184,11 +186,11 @@ Lightweight-Liste; bei `include_variants=true` zusätzlich z.B.:
"skill_id": 10,
"skill_name": "Distanzgefühl",
"skill_category": "Kumite",
"is_primary": true,
"intensity": "hoch",
"required_level": "grundlagen",
"target_level": "aufbau",
"ai_suggested": false
"ai_suggested": false,
"is_primary": false
}
],
@ -306,7 +308,6 @@ Lightweight-Liste; bei `include_variants=true` zusätzlich z.B.:
"skills": [
{
"skill_id": 10,
"is_primary": true,
"intensity": "hoch",
"required_level": "grundlagen",
"target_level": "aufbau"
@ -336,6 +337,7 @@ Lightweight-Liste; bei `include_variants=true` zusätzlich z.B.:
### `PUT /exercises/{id}`
**Request Body:** Same as POST (all fields optional except id)
**Inline-Hinweis:** Rich-Text-Felder (`summary`, `goal`, `execution`, `preparation`, `trainer_notes` sowie `execution_changes` bei Varianten) unterstützen Platzhalter `{{exerciseMedia:id}}`, die serverseitig auf kanonisches Span-Markup normalisiert werden. Beim erstmaligen Anlegen ohne vorhandene `exercise_media` wird dies mit 400 abgelehnt.
**Response:** `200 OK` (full exercise object)
@ -459,7 +461,7 @@ Lightweight-Liste; bei `include_variants=true` zusätzlich z.B.:
- `media_type` (enum, required) - `image | video | document | sketch`
- `title` (string, optional)
- `description` (string, optional)
- `context` (enum, optional) - `ablauf | detail | trainer_hint`
- `context` (enum, optional, legacy UI-Feld) - `ablauf | detail | trainer_hint`
**Entweder `file` ODER `embed_url` (nicht beides).**
@ -505,11 +507,12 @@ Lightweight-Liste; bei `include_variants=true` zusätzlich z.B.:
{
"title": "Neuer Titel",
"description": "Neue Beschreibung",
"is_primary": true,
"context": "detail"
"is_primary": true
}
```
`context` bleibt backendseitig kompatibel, wird in aktuellen Bearbeitungsflüssen jedoch nicht mehr aktiv zur UI-Zuordnung verwendet (Inline-Platzhalter priorisiert).
**Response:** `200 OK` (media object)
---
@ -575,7 +578,6 @@ Wird beim Klick auf „✨ KI-Vorschlag" im Formular aufgerufen.
"required_level": "grundlagen",
"target_level": "aufbau",
"intensity": "hoch",
"is_primary": true,
"confidence": 0.92
},
{
@ -585,7 +587,6 @@ Wird beim Klick auf „✨ KI-Vorschlag" im Formular aufgerufen.
"required_level": "einsteiger",
"target_level": "grundlagen",
"intensity": "mittel",
"is_primary": false,
"confidence": 0.74
}
]
@ -618,6 +619,38 @@ Trainer muss im Frontend aktiv übernehmen.
## Permissions
**UI-Hinweis:** Das Feld `visibility` heißt in der Oberfläche **Freigabelevel** (`exerciseGovernanceLabels.js`).
### Lesen (`GET /exercises`, `GET /exercises/{id}`)
| `visibility` | Wer darf lesen? |
|--------------|-----------------|
| `official` | Plattform-weit |
| `private` | Ersteller (`created_by`); Plattform-Admin |
| `club` | Aktive Mitglieder des Objekt-`club_id`; Plattform-Admin ohne Mitgliedschaft (Audit-Zugang) |
Implementierung: `library_content_visible_to_profile` / `exercise_visible_to_profile` in `club_tenancy.py`.
### Bearbeiten (`PUT`, Varianten-CRUD, Medien an Übung)
| Bedingung | Wer darf bearbeiten? |
|-----------|----------------------|
| Ersteller | Immer (eigene Übung) |
| Plattform-Admin | Immer |
| `visibility=club` | Zusätzlich **`can_plan_in_club`** im Objekt-Verein: `club_admin`, `trainer`, `content_editor`, `division_lead` |
Implementierung: `_assert_can_edit_exercise` in `exercises.py`. **Varianten** haben kein eigenes Owner-Feld — gleiche Prüfung wie Eltern-Übung.
### Löschen (`DELETE /exercises/{id}`)
| `visibility` | Wer darf löschen? |
|--------------|-------------------|
| `official` | Nur Plattform-Admin |
| `club` | Nur **`club_admin`** im Objekt-Verein |
| `private` | Ersteller; oder Vereins-Admin, der mit dem Ersteller einen gemeinsamen Verein teilt |
Implementierung: `_assert_can_delete_exercise` in `exercises.py`.
### Sichtbarkeits-Workflow
| Von → Nach | Wer darf das? |
@ -635,11 +668,12 @@ Trainer muss im Frontend aktiv übernehmen.
| `club → official` | Club-Admin, Super-Admin |
| `official → club` | Super-Admin |
### Owner-Checks
### Owner-Checks (veraltet — siehe Tabellen oben)
- **Bearbeiten** (PUT): Nur Ersteller oder Club-Admin
- **Löschen** (DELETE): Nur Ersteller oder Super-Admin
- **Lesen** (`private`): Nur Ersteller
Die folgenden Kurzregeln sind durch die Ist-Implementierung ersetzt; nur zur historischen Einordnung:
- ~~Bearbeiten (PUT): Nur Ersteller oder Club-Admin~~ → siehe **Bearbeiten**-Tabelle (`can_plan_in_club`)
- ~~Löschen (DELETE): Nur Ersteller oder Super-Admin~~ → siehe **Löschen**-Tabelle
**403 Fehler-Beispiel:**
```json
@ -893,6 +927,7 @@ Trainer muss im Frontend aktiv übernehmen.
- File size: max 50MB
- Mime types: `image/jpeg, image/png, image/gif, video/mp4, application/pdf`
- Embed platforms: `youtube, instagram, vimeo`
- Inline-Rich-Text: `{{exerciseMedia:id}}` bzw. `data-shinkan-exercise-media="<id>"`; optional `data-shinkan-exercise-media-size="small|medium|full"` für Layout.
### Exercise Block
- `name`: 3-200 chars
@ -900,7 +935,8 @@ Trainer muss im Frontend aktiv übernehmen.
### Exercise Skills
- `required_level`: enum `einsteiger | grundlagen | aufbau | fortgeschritten | experte` (optional/nullable)
- `target_level`: enum gleiche Werte (optional/nullable)
- `intensity`: enum `niedrig | mittel | hoch` (optional/nullable)
- `intensity`: enum **`niedrig | mittel | hoch`** (optional/nullable; Default beim Speichern **`mittel`**)
- `is_primary`: **Legacy** — Spalte existiert in DB, wird bei POST/PUT **nicht ausgewertet** (immer `false` gespeichert); UI liefert/speichert kein Primär-Flag mehr; Scoring ignoriert das Feld
- `target_level` sollte >= `required_level` sein (Warnung, kein Fehler)
### Exercise Block Item
@ -947,6 +983,6 @@ Trainer muss im Frontend aktiv übernehmen.
---
**Version:** 1.0
**Letzte Änderung:** 2026-04-24
**Status:** DRAFT - Awaiting Review
**Version:** 1.5
**Letzte Änderung:** 2026-05-08
**Status:** Living Spec

View File

@ -99,20 +99,21 @@ Exercise Block ──── (N) Block Items ──── (1) Exercise
### 1.3 M:N Beziehungen (Primary/Secondary Pattern)
**Regel:** Alle Katalog-Zuordnungen nutzen M:N mit `is_primary` Flag.
**Regel:** Katalog-Zuordnungen (Fokus, Stil, Zielgruppe, …) nutzen M:N mit optionalem `is_primary`-Flag.
**Betroffene Relationen:**
**Betroffene Relationen (mit `is_primary`):**
- `exercise_focus_areas` (Übung ↔ Fokusbereiche)
- `exercise_styles` (Übung ↔ Trainingsstile)
- `exercise_styles` / `exercise_style_directions` (Übung ↔ Stilrichtungen)
- `exercise_training_types` (Übung ↔ Trainingsstile)
- `exercise_target_groups` (Übung ↔ Zielgruppen)
- `exercise_training_characters` (Übung ↔ Trainingscharaktere)
- `exercise_skills` (Übung ↔ Fähigkeiten)
**Primary/Secondary Semantik:**
**Ausnahme — `exercise_skills`:** Kein Primär-Flag in UI/API mehr; stattdessen **`intensity`** (`niedrig` \| `mittel` \| `hoch`, Default `mittel`). Spalte `is_primary` bleibt Legacy (Backend speichert immer `false`).
**Primary/Secondary Semantik (Katalog-Dimensionen):**
- **Primary:** Hauptzuordnung, entscheidend für Filter/Suche
- **Secondary:** Nebenzuordnung, zusätzlicher Kontext
- **Regel:** Genau EINE Primary-Zuordnung pro Dimension
- **UI:** Primary wird visuell hervorgehoben (z.B. fett, farbig)
- **Regel:** Genau EINE Primary-Zuordnung pro Dimension (wo UI das noch anbietet)
- **UI:** Primary wird visuell hervorgehoben (z. B. fett, farbig) — Fähigkeiten: Intensitäts-Segmente statt Primary
**Legacy-Felder (DEPRECATED):**
- `exercises.focus_area` → Ignorieren, nutze `exercise_focus_areas`

View File

@ -1,9 +1,10 @@
# Frontend Routing & Navigation Specification
**Version:** 1.2
**Datum:** 2026-04-30
**Version:** 1.3
**Datum:** 2026-05-20
**Status:** DRAFT - Awaiting Review
**Autor:** Claude Code
**Änderungen v1.3:** Übungsformular Tab-Navigation unter `/exercises/:id/edit` (Stammdaten … Medien & Mehr); Freigabelevel als UI-Begriff
**Änderungen v1.2:** Übersicht **Übungen**: Tabs Liste \| Progressionsgraphen auf `/exercises`; Progressions-Editor ohne neue Routen (Panel + Formularblock unter `/exercises/:id/edit`)
**Änderungen v1.1:** Übungsvarianten-Bearbeitung nur unter `/exercises/:id/edit` (keine VariantFormPage-Routen)
@ -17,7 +18,7 @@
/exercises → ExercisesListPage — Tabs: **Liste** \| **Progressionsgraphen** (`ExerciseProgressionGraphPanel`)
/exercises/new → ExerciseFormPage (Create)
/exercises/{id} → ExerciseDetailPage (Accordion-Layout)
/exercises/{id}/edit → ExerciseFormPage (Edit inkl. Varianten-Editor inline + Block Progressionsgraph)
/exercises/{id}/edit → ExerciseFormPage (Edit: Registerkarten + Varianten inline + Progressionsgraph)
/exercise-blocks → ExerciseBlocksListPage (Meine Blocks)
/exercise-blocks/new → ExerciseBlockFormPage (Create)
@ -35,6 +36,25 @@
- Pagination: `/exercises?limit=50&offset=100`
- Sortierung: `/exercises?sort=created_at&order=desc`
### 1.2 Übungsformular Registerkarten (`/exercises/new`, `/exercises/:id/edit`)
**Implementierung:** `ExerciseFormPageRoot.jsx` + `ExerciseFormLayout.jsx` (`ExerciseFormTabBar`, `ExerciseFormPanel`).
| Tab-ID | Label | Verfügbarkeit |
|--------|-------|---------------|
| `stammdaten` | Stammdaten | immer |
| `anleitung` | Anleitung | immer |
| `einordnung` | Einordnung | immer |
| `kombination` | Kombination | nur `exercise_kind=combination` |
| `varianten` | Varianten | Edit-Modus; nicht bei Kombination; disabled bei Neuanlage |
| `medien` | Medien & Mehr | Edit-Modus; disabled bei Neuanlage |
**UX-Regeln:**
- Nur ein Panel sichtbar (`activeFormTab`); Navigation über `PageSectionNav`.
- **Freigabelevel** (Feld `visibility`) in Stammdaten — Konstante `EXERCISE_VISIBILITY_FIELD_LABEL`.
- Varianten-Änderungen werden mit **Speichern** in der Aktionsleiste persistiert (`persistPendingVariantChanges`); Button „Variante anlegen“ optional sofort.
- Kein URL-Hash pro Tab (Tab-State nur lokal).
---
## 2. Navigation-Patterns
@ -673,7 +693,7 @@ function App() {
---
**Version:** 1.2
**Letzte Änderung:** 2026-04-30
**Version:** 1.3
**Letzte Änderung:** 2026-05-20
**Status:** REVIEWED - Pending Implementation
**Review-Änderungen:** Progressionsgraphen-UI (Tabs, Formularblock); Exercise Blocks Routes + Navigation (früher)
**Review-Änderungen:** Formular-Registerkarten; Progressionsgraphen-UI (Tabs, Formularblock); Exercise Blocks Routes + Navigation (früher)

View File

@ -7,11 +7,16 @@
**Änderungen v1.1:** Prompts sind nicht hardcoded sie werden aus der DB geladen (AI_PROMPT_SYSTEM_SPEC.md)
**Verwandte Specs:** AI_PROMPT_SYSTEM_SPEC.md (Prompt-DB + Platzhalter), SKILLS_MATRIX_SPEC.md (Fähigkeitsmatrix)
**Übergeordnete Produkt-Vision** (breiter Scope: Zielausbau, bereichsweise vs. Gesamtüberarbeitung, Varianten, Planungs-/Nachbereitungskontext, Admin-Masse):
`functional/AI_EXERCISE_ASSISTANT_VISION.md`
---
## 1. Übersicht
Zwei KI-gestützte Assistenzfunktionen beim Anlegen und Bearbeiten von Übungen:
KI-gestützte Assistenzfunktionen beim Anlegen und Bearbeiten von Übungen (Mindestpaket dieser Spec):
**Hinweis:** Die beiden folgenden Zeilen entsprechen **P0** der Phasierung in **`AI_EXERCISE_ASSISTANT_VISION.md`**; spätere Funkteile sind dort beschrieben.
| Funktion | Ziel |
|---------|------|
@ -155,7 +160,38 @@ KI gibt Vorschläge
Liefert KI-Vorschläge auf Basis von Eingabe-Text, **bevor** die Übung gespeichert wurde.
Wird beim Klick auf „KI-Vorschlag" im Formular aufgerufen.
**Request Body:**
**Required Fields:** mindestens `goal` ODER `execution`
**Optional Skill-Katalogpriorisierung (Stand 068):**
```json
{
"focus_areas_context": [
{ "focus_area_id": 3, "is_primary": true },
{ "focus_area_id": 1, "is_primary": false }
],
"focus_area_hint": "Karate, Kumite…"
}
```
- `focus_areas_context`: IDs aus Stammdatum **Fokusbereiche**; Primär soll zuerst stehen (`is_primary`). Ohne Feld oder leere Liste gilt das DB-Profil **`is_default`** (`ai_skill_retrieval_profiles`).
- `focus_area_hint`: bleibt lesbarer Text für den Prompt (bestehende Prompts).
**Minimal-Beispiel (Mit Fokus für Retrieval):**
```json
{
"title": "Maai - Distanzübung",
"goal": "…",
"execution": "…",
"focus_areas_context": [ { "focus_area_id": 1, "is_primary": true } ]
}
```
**Minimal-Beispiel ( ohne Fokus — nur Texts):**
```json
{
"title": "Maai - Distanzübung",
@ -164,8 +200,6 @@ Wird beim Klick auf „KI-Vorschlag" im Formular aufgerufen.
}
```
**Required Fields:** mindestens `goal` ODER `execution` (je länger, desto besser)
**Response:** `200 OK`
```json
{
@ -182,7 +216,6 @@ Wird beim Klick auf „KI-Vorschlag" im Formular aufgerufen.
"required_level": "grundlagen",
"target_level": "aufbau",
"intensity": "hoch",
"is_primary": true,
"confidence": 0.92
},
{
@ -192,7 +225,6 @@ Wird beim Klick auf „KI-Vorschlag" im Formular aufgerufen.
"required_level": "einsteiger",
"target_level": "grundlagen",
"intensity": "mittel",
"is_primary": false,
"confidence": 0.74
}
]

View File

@ -1,7 +1,7 @@
# Medien-Assets, Archiv & Lebenszyklus (Single Source of Truth)
**Status:** verbindlich für Design, API und DB-Migrationen zum Thema Medien
**Stand:** 2026-05-07 (§11 Inline-Plan ergänzt)
**Stand:** 2026-05-08 (Ist-Changelog Medien bis 0.8.64; §11 Inline umgesetzt und erweitert)
**ersetzt/ergänzt:** operatives Upload-Format/Größen siehe weiterhin `MEDIA_UPLOAD_SPEC.md`
**normative Governance:** Sichtbarkeit & Mandanten wie `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`; bei Abweichung zwischen Dokumenten hat der Zugriffsplan Vorrang, **außer** dieses Dokument präzisiert explizit nur den Medien-Domain.
@ -186,26 +186,33 @@ Das widerspricht **nicht** dem Papierkorb: wer kein Recht hat, ein Asset **globa
| Datum | Änderung |
|-------|----------|
| 2026-05-07 | Erstfassung als Single Source of Truth (verbindlich); Abstimmung mit Stakeholder: Promotion Übung↔Medien, Copyright, Papierkorb 3-stufig, externe Speicher, Embeds getrennt vom Asset-Lifecycle. |
| 2026-05-07 | §11 **Inline-Medien im Fließtext**: Leitplanken (Anker `exercise_media.id`, einheitlicher Render-Pfad, keine zweite Governance); Zeitpunkt der Umsetzung; Drift-Vermeidung ohne jetzigen Vollbau. |
| 2026-05-08 | **0.8.64:** Bearbeiten-UX erweitert: Drag&Drop mit Auto-Scroll in Richtung Rich-Text, Medienliste in Kachel-Grid, Warnhinweis vor Wechsel „Ansehen“ bei ungespeicherten Änderungen + Rückweg „Zurück zur Bearbeitung“, Katalogvorschau ohne separaten Anhangs-Medienstreifen. |
| 2026-05-08 | **0.8.63:** Inline-Medien-Picker als Modal (Mediathek/Upload + separates Embed-Modal), Größenwahl `small|medium|full`, Platzhalter-Caption (`data-shinkan-exercise-media-caption`), Wiederverwendung bereits verknüpfter Assets im Picker, kompakter Medienbereich mit Drag&Drop in Textfelder. |
| 2026-05-08 | **0.8.60 §11:** Inline in Übungstexten (`{{exerciseMedia:id}}` / `data-shinkan-exercise-media`); Server-Normalisierung + Validierung; Client-Sanitize und zentraler Block-Renderer (`ExerciseRichTextBlock`); CREATE ohne bestehende `exercise_media` lehnt Platzhalter ab. |
| 2026-05-07 | **0.8.59:** Dokumentation — Aktiver Verein (Profil/Header/`effective_club_id`) für Plattform-Admin und UI-Dropdown synchron; kein fachliches Archiv-Schema-Change. |
| 2026-05-07 | **0.8.58:** Medien **`official`:** Lifecycle schwerpunktmäßig **Superadmin** (nicht Plattform-Admin); Bearbeitungsdialog Bibliothek für andere Rollen **Lesemodus**; Superadmin-Upload: Vereinskontext folgt aktiv gesetztem Verein / `effective_club_id`. |
| 2026-05-07 | **0.8.470.8.57 (Auszug):** Übung **`official`** nur Superadmin; Vereinsübungen mit File-Assets: **Copyright-Pflicht**; Speicherpfade **`library/`** mit Vereinsordner (Name+c{id}), Medienkind-Unterordner, Governance-Umzug bei Sichtbarkeit; Bibliothek-GET mit Filtern/Tags/Nutzungs-Anzeige; Bulk-Lifecycle/PATCH; Lesemodus/Kacheln; Konflikt **409** bei Upload-Dedupe vs. Papierkorb + UI-Reaktivierung. |
| 2026-05-07 | **0.8.420.8.43:** Papierkorb-Lifecycle API, Retention-Skript; **`POST …/exercises/{id}/media/from-asset`**; Übungs-UI Archiv verknüpfen / Vorschau. |
| 2026-05-07 | Erstfassung als Single Source of Truth (verbindlich); Abstimmung Stakeholder: Promotion Übung↔Medien, Copyright, Papierkorb 3-stufig, externe Speicher, Embeds getrennt vom Asset-Lifecycle. |
| 2026-05-07 | §11 **Inline-Medien im Fließtext**: Leitplanken (Anker `exercise_media.id`, einheitlicher Render-Pfad, keine zweite Governance); Umsetzung **nach** tragfähigem Archiv — siehe **`docs/HANDOVER.md`** §5. |
| 2026-05-07 | **Medienmanager (Basis):** `GET /api/media-assets?lifecycle=…`, `copyright_notice` in Response; `PATCH` Copyright; `GET …/file` für Papierkorb-Zeilen bei Verwaltungsrecht; UI-Route `/media` (Medienbibliothek). |
---
## 11. Inline-Medien im Fließtext (Planung, Leitplanken)
## 11. Inline-Medien im Fließtext (Umsetzung & Leitplanken)
**Status:** nicht implementiert; verbindlich nur als **Richtschnur**, damit später **kein Big-Bang-Refactor** nötig ist.
**Status:** Frontend-Renderer und API-Validierung/Normalisierung umgesetzt (App ≥ 0.8.60); verbindliche Leitplanken unten.
### 11.1 Ziel
- Medien (Player, Bild) sollen **an definierter Stelle** in Feldern wie Ablauf / Ziel / Notizen erscheinen können zusätzlich oder statt reiner Zuordnung zu den Sektionen `ablauf` / `detail` / `trainer_hint`.
- **Keine zweite Sichtbarkeit:** Inline verweist immer auf dieselbe **Übungs-Medium-Zeile** (`exercise_media.id`) bzw. indirekt auf das gleiche Asset wie die Medienliste; **Lesen/Ausliefern** nur nach **bestehender** Übungs- + Medien-Governance (§4.1).
### 11.2 Platzhalter-Konvention (Vorschlag für spätere Umsetzung)
### 11.2 Platzhalter-Konvention (**festgelegt**)
- Beim **Speichern** im Rich-Text: markierter Verweis, z.B.
`data-shinkan-exercise-media="<numerische exercise_media.id>"` auf einem **neutralen** Element (`span`/`figure`), **oder** eine interne Kurzsyntax (`{{exerciseMedia:123}}`), die der Server beim Speichern in eine **kanonische** HTML-Form überführt.
- **Final festlegen** beim Start der Implementierung (ein Format, nicht mehrere parallele).
- **Kanonisches Markup nach Speichern:** `<span data-shinkan-exercise-media="<numerische exercise_media.id>" class="shinkan-inline-media"></span>`.
- **Erweiterung (UI/Lesbarkeit):** optionales Attribut `data-shinkan-exercise-media-size="small|medium|full"` (Default `medium`) und optional `data-shinkan-exercise-media-caption="..."` für sprechende Platzhalter-Chips im Editor.
- **Kurzsyntax (Eingabe/Import):** `{{exerciseMedia:123}}` — Server normalisiert beim Speichern (PUT Übung / Varianten) in das kanonische `span`; **CREATE** ohne bestehende `exercise_media`-Zeilen verwirft Platzhalter mit **400**.
### 11.3 Rendering & Sicherheit
@ -214,14 +221,15 @@ Das widerspricht **nicht** dem Papierkorb: wer kein Recht hat, ein Asset **globa
### 11.4 Koexistenz mit Sektions-Medien
- **Liste + Inline** dürfen dasselbe `exercise_media` referenzieren (ein Player an zwei Stellen) Produktentscheidung: später optional „Duplikat vermeiden“-Hinweis in der UI.
- **Liste + Inline** dürfen dasselbe `exercise_media` referenzieren (ein Player an zwei Stellen).
- Stand 0.8.64: Katalog-/Vorschaupfade priorisieren Inline-Darstellung; ein zusätzlicher Anhangsblock wird dort nicht mehr parallel erzwungen.
- Import/Wiki: vor großen Content-Migrationen **Syntax festlegen**, damit nicht irreversibel „falsches“ HTML importiert wird.
### 11.5 Wann umsetzen (Reihenfolge)
1. **Vorher:** Medien-Archiv, `media_assets`, Upload/Dedupe, Speicherpfad, Basis-Papierkorb (§5) jeweils stabil.
2. **Danach:** Inline implementieren, sobald Trainer-Feedback oder Content-Menge den Bedarf **konkret** bestätigt (typisch nach 12 Beta-Zyklen).
3. **Nicht nötig:** vorher kompletten Block-Editor einführen; **Platzhalter im bestehenden RTE** ist der vorgesehene **schlanke** Einstieg.
1. ~~**Erledigt (Basis):** Medien-Archiv, `media_assets`, Upload/Dedupe, Speicherpfad, Papierkorb, Bibliothek `/media`, Verknüpfung `from-asset`, Governance `official`/Copyright.~~
2. **Umgesetzt (Stand 0.8.64):** Platzhalter/Kurzsyntax + zentraler Frontend-Render-Pfad + Server-Validierung gemäß §11.111.4; Editor-Einstieg „Medium einfügen“ inkl. Modal-Picker (Mediathek/Upload), separates Embed-Modal, Größenwahl und Drag&Drop aus der Medienliste.
3. **Editor:** kein Zwang zum vollen Block-Editor vorab; **Platzhalter im bestehenden RTE** ist der vorgesehene schlanke Einstieg.
### 11.6 Refactor-Vermeidung (jetzt schon)

View File

@ -1,10 +1,8 @@
# Media Upload & Embed Specification
**Version:** 1.1
**Datum:** 2026-04-27
**Status:** DRAFT - Awaiting Review
**Autor:** Claude Code
**Änderungen v1.1:** Rollenbasierte Server-Limits (`EXERCISE_MEDIA_*_MB`)
**Version:** 1.2
**Datum:** 2026-05-07
**Status:** Aktuell für Upload-Limits, MIME, Embed — **zentraler Medien-Ist-Stand** inkl. Archiv, Lifecycle, Pfadkonvention: **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**
> **Zielbild Medien-Archiv, Wiederverwendung, Papierkorb, Copyright, externe Speicherung, später Inline im Fließtext:**
> Verbindliche Single Source of Truth: **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`** (§11 Leitplanken Inline, ohne Umsetzung).

View File

@ -0,0 +1,243 @@
# Membership, RBAC & Kontingente — Produktentscheidungen
**Status:** Verbindlich (Zielbild & Roadmap-Priorisierung)
**Stand:** 2026-06-06
**Bezüge:** `CAPABILITY_CATALOG.v1.md`, `CLUB_MEMBERSHIP_AND_FEATURES.v1.md`, `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`
Dieses Dokument hält **getroffene Produktentscheidungen** fest (Session 2026-06-06) und ergänzt die v1-Konzept-Specs um Umsetzungsrichtung. Technischer Implementierungsstand: Abschnitt 2.
---
## 1. Getroffene Entscheidungen
### 1.1 Onboarding: `verified_pending_club`
Nutzer **ohne aktive Vereinsmitgliedschaft** (E-Mail verifiziert) dürfen **nur**:
| Erlaubt | Nicht erlaubt (Zielbild) |
|---------|---------------------------|
| Konto / Einstellungen | Übungen, Planung, KI, Medien |
| Vereinsverzeichnis lesen | Vereinsinterne Inhalte (`club`), private Fremdinhalte |
| **Beitrittsantrag** an bestehenden Verein | Vollzugriff auf Bibliothek / offizielle Inhalte (Lesen) — **bewusst gesperrt** bis Mitgliedschaft |
| **Vereinsgründung beantragen** (Prozess M7, Superadmin-Freigabe) | |
**Kein** „Bibliothek durchstöbern“ für Bewerber — reduziert Datenexposition und vereinfacht UX („erst Verein, dann Arbeit“).
Technischer Zustand: `account_state = verified_pending_club` (siehe `CAPABILITY_CATALOG.v1.md` §3).
---
### 1.2 Rollenmodell: Risikoarm statt Big-Bang
**Zielbild (langfristig):**
- **Fest:** nur `superadmin` (Plattform) als nicht konfigurierbare Systemrolle.
- **Dynamisch konfigurierbar:** alle Vereinsrollen und deren Capability-Bundles (später `club_custom_roles`).
- Optional: `admin` (Plattform) als abgeschwächter Portal-Admin bleibt vorerst bestehen (Ist-Code).
**Entscheidung v1 (risikoarm):**
| Maßnahme | Jetzt | Später |
|----------|-------|--------|
| Alte Helfer (`can_plan_in_club`, `if (club_admin)` in JSX) | **Behalten** — weiter produktiv | Schrittweise durch `entitlements` ersetzen |
| Neue Endpoints / Features | Nur über **Capability-IDs** + Audit | — |
| Neue Vereinsrollen | Als **Systemrollen** ergänzen (z.B. `co_trainer`) | Custom Roles UI |
| `club_custom_roles` | **Nicht** in v1 | v2 Epic |
**Begründung:** Backend und Frontend haben hunderte Verdrahtungen auf `trainer` / `club_admin` / Plattform-Rollen. Parallelbetrieb Capability-System + Legacy-Helfer ist sicherer als einmaliges Aufbrechen.
**Co-Trainer (geplant als Systemrolle):** weniger Capabilities als `trainer` (z.B. kein `planning.*`, kein `exercises.create`) — Umsetzung nach Onboarding-Gates + Entitlements-Rollout, nicht vorher.
---
### 1.3 Vereins-Kontingente (Membership-Pakete)
**Jetzt:** Schema und Anzeige vorbereiten; **keine** detaillierte Paket-Logik (z.B. „3 Trainer + 10 Co-Trainer“) implementieren.
| Vorbereitet (DB/Module) | Bewusst zurückgestellt |
|-------------------------|-------------------------|
| `features`, `club_plans`, `club_subscriptions` | Eigene Feature-IDs `trainer_seats` / `co_trainer_seats` |
| Bestands-Limits (`exercises`, `training_groups`, `ai_calls`, …) | Zählregel „nur planungsberechtigte Mitglieder“ vs. alle Mitglieder |
| `GET /me/entitlements` Feature-Teil | Stripe / Rechnung (M8) |
**Prinzip:** Neue Kontingent-Typen = neue `features`-Zeile + Plan-Limits + optional Capability-`linked_feature_id` — ohne Schema-Bruch.
---
### 1.4 Trainer-Budget innerhalb Vereins-Kontingent (v2)
**Anforderung:** Vereins-KI-Kontingent liegt beim Verein; **Vereinsadmin** kann pro Trainer ein **Sub-Budget** vergeben (Fairness, „Kontingent-Fresser“).
**Entscheidung:**
- v1: nur **Vereins-Ebene** (`club_plan_limits`, `club_feature_usage`).
- v2: neue Tabellen (Skizze):
```sql
-- Skizze — noch nicht migriert
club_member_feature_budgets (club_id, profile_id, feature_id, limit_value, …)
club_member_feature_usage (club_id, profile_id, feature_id, usage_count, reset_at, …)
```
**Prüf-Kette v2:** Capability → Mitglieds-Budget (falls gesetzt, `profile_id` aus Session) → Vereins-Kontingent.
**Fairness-Modell (offen, Tendenz):** harte Sub-Budgets (Modell A) — Trainer darf sein Budget nicht überschreiten, auch wenn Verein noch Rest hat.
**Roadmap:** Phase 5b / Meilenstein **M9** in `docs/working/RBAC_ENFORCEMENT_ROADMAP.md` — Vereinsadmin-UI zur Verteilung, Entitlements mit persönlichem + Vereins-Rest, Auswertung je Person.
---
### 1.5 Enforcement-Phasen (unverändert, bestätigt)
| Phase | Verhalten | Nutzer sichtbar |
|-------|-----------|-----------------|
| 2 (M2/M3) | JSON-Log, kein Block | Nein (außer Logs) |
| 3 (M4) | `GET /me/entitlements` + Badge | Kontingent-Anzeige |
| 4 (M5+) | HTTP 403 + `increment` | Hard-Block |
Env-Schalter: `ACCOUNT_GATE_ENFORCE` (Default `1`, Endpoint-Helfer), `ACCOUNT_GATE_API_ENFORCE` (Default `1`, API-Middleware Phase A), `CAPABILITY_ENFORCE` / `CLUB_FEATURE_ENFORCE` (Default `0`).
---
## 2. Implementierungsstand (Ist, Codebase)
**DB-Schema:** `20260606083` · App **0.8.199** (`backend/version.py`)
**Roadmap (detailliert):** `docs/working/RBAC_ENFORCEMENT_ROADMAP.md`
### M1 — Feature-Schema v9c ✅
| Deliverable | Status |
|-------------|--------|
| Migration `078_club_features_and_plans.sql` | ✅ |
| Legacy `001` archiviert | ✅ |
| `club_plans`, `club_subscriptions`, Usage-Tabellen | ✅ |
| Seed Features + Pläne (`free`, …) | ✅ |
| `club_features.py`: `check_club_feature_access`, `get_effective_club_plan` | ✅ |
| Backfill Vereine → Plan `free` | ✅ |
### M2 — Feature-Probe (Log only) ✅
| Deliverable | Status |
|-------------|--------|
| `club_feature_logger.py``club-feature-usage.log` | ✅ |
| `probe_club_feature_access()` | ✅ |
| Hooks: KI-Endpoints, `POST /exercises`, Medien-Upload, Planungs-KI | ✅ |
| Consume-Standard + `feature_usage` in Response (`ai_calls`) | ✅ |
| `CLUB_FEATURE_ENFORCE=0` (Default) | ✅ |
### M3 — Account-Lifecycle + Capability-Grants ⚠️ teilweise
| Deliverable | Status | Lücke |
|-------------|--------|-------|
| Migration `079_capabilities.sql` + Seed | ✅ | — |
| `account_lifecycle.py`, `resolve_account_state` | ✅ | — |
| `capabilities.py`, `check_capability`, `probe_capability` | ✅ | — |
| `TenantContext.account_state` | ✅ | — |
| `GET /profiles/me``account_state`, `club_roles` | ✅ | — |
| Account-Gates auf **Schreib-/KI-Endpoints** | ✅ | Lesepfade für Bewerber noch offen |
| `CAPABILITY_ENFORCE=0` (nur Log) | ✅ | — |
| Onboarding UX: nur Bewerbung/Gründung | ✅ | Phase A: API-Middleware + `/onboarding` + reduzierte Nav |
| `club_creation_requests` (M7) | ✅ Basis | Capabilities + Admin-Freigabe |
| Quota-Bypass via Capability-Grants (083) | ✅ | kein paralleles Exemption-Schema |
| Custom Roles / Co-Trainer | ❌ | bewusst v2 |
| Legacy-Helfer entfernt | ❌ | bewusst parallel |
### M4 — Anzeige ✅ teilweise
| Deliverable | Status |
|-------------|--------|
| `GET /api/me/entitlements` | ✅ |
| `EntitlementsContext`, `hasCapability()` | ✅ (UI nutzt noch kaum) |
| `FeatureUsageBadge` | ✅ nur KI im Übungsformular |
| `featureUsageSync` in `request()` | ✅ |
### M5 — Hard-Block + vollständiger Verbrauch ⚠️
| Deliverable | Status |
|-------------|--------|
| `consume_club_feature_with_usage` Standard | ✅ `ai_calls` |
| `CLUB_FEATURE_ENFORCE=1` produktiv | ❌ Default 0 |
| Consume `exercises`, `exercise_media`, … | ❌ |
### M6 — Admin UI Rollen & Rechte ⚠️
| Deliverable | Status |
|-------------|--------|
| `/admin/rights` Capability-Matrix (Portal + Verein) | ✅ |
| Klartext zuerst, Enforcement-Badge | ✅ 2026-06-07 |
| Kontingent-Bypass + Vereinspläne (Seed) | ✅ |
| Neue Pläne / Rollen anlegen (CRUD) | ❌ |
### Bewusst zurückgestellt
| ID | Inhalt |
|----|--------|
| M0 | CI-Isolation / Test-DB |
| M8 | Stripe |
| v2 | Trainer-Budgets, Custom Roles |
---
## 3. Architektur-Zielbild (kompakt)
```
Request
→ require_auth
→ account_state (Gate)
→ TenantContext
→ assert_capability (Rolle / Funktion)
→ check_club_feature_access (Vereins-Kontingent)
→ [v2] member_feature_budget (Trainer-Budget)
→ Governance (Objekt)
```
**Drei Achsen:** Account-Lifecycle · Capabilities · Features (Kontingente). Governance bleibt vierte Prüfung.
---
## 4. Empfohlene Roadmap (nach Entscheidungen)
| Phase | Paket | Warum zuerst |
|-------|--------|--------------|
| **A** | **Onboarding-Gates vollständig** | ✅ umgesetzt (API + Frontend `/onboarding`) |
| **B** | **M7 Vereinsgründung beantragen** | **Als Nächstes** — zweiter Pfad für `verified_pending_club` |
| **C** | **M5 Hard-Block `ai_calls`** | Free-Plan `0` wird real; Badge (M4) liefert Erklärung |
| **D** | **M6 voll** | Pläne-CRUD, Rollen-CRUD | ⚠️ Matrix da |
| **E** | Entitlements im Frontend (`hasCapability`) | Entscheidung 1.2 risikoarm |
| **F** | **M9 Kontingent-Verteilung** — Vereinsadmin vergibt Sub-Budgets pro Person (`profile_id`); Prüfung + Consume personenbezogen; UI Vereinsorga | Entscheidung 1.4, Roadmap Phase 5b |
| **G** | `co_trainer` + Custom Roles (v2) | Entscheidung 1.2 |
M0 parallel, nicht blockierend.
---
## 5. Offene Punkte (vor M6 / v2)
1. Fairness Modell A/B/C für Trainer-Budget (Tendenz: A).
2. Ob `admin` (Portal) langfristig neben `superadmin` bleibt.
3. Ob offizielle Inhalte für Bewerber **nie** lesbar bleiben (aktuell: ja).
---
## 6. Referenzen
| Pfad | Inhalt |
|------|--------|
| `CAPABILITY_CATALOG.v1.md` | Capability-IDs, Account-States |
| `CLUB_MEMBERSHIP_AND_FEATURES.v1.md` | Feature-Registry, Kontingente |
| `backend/club_features.py` | Vereins-Features |
| `backend/capabilities.py` | Capability-Auflösung |
| `backend/account_lifecycle.py` | Account-Gates |
## 7. Superadmin im Verein (FAQ)
Siehe **`docs/working/RBAC_ENFORCEMENT_ROADMAP.md` §4**: Plattform-Admin (`admin`, `superadmin`) erhält **Capability-Bypass** für Vereins-Funktionen ohne `club_admin`-Mitgliedschaft. Mandant über aktiven Verein wählen; Kontingente via Bypass. Einzelne Legacy-Pfade (z.B. Löschen `visibility=club`) sind noch nicht vereinheitlicht — Ziel Phase 3.
---
**Changelog**
- 2026-06-06: Initial — Entscheidungen Onboarding, Rollen-Risiko, Kontingente, Trainer-Budget v2; Ist-Stand M1M3; Roadmap AF.
- 2026-06-06: Phase A — `account_onboarding_gate.py`, Frontend `/onboarding`, reduzierte Navigation.
- 2026-06-07: M4M6 Ist-Stand, Roadmap-Verweis, Superadmin-FAQ; Admin-Matrix UX + Enforcement-Audit.
- 2026-06-08: Roadmap Phase 5b / M9 — Vereinsadmin-Kontingentverteilung pro Person; Enforce Dev verifiziert (0.8.202).

View File

@ -227,7 +227,9 @@ Ziel: **vereinszentrierte** Vertrags- und Limitlogik, analog zur bestehenden Tie
## 8. Verwandtes Dokument
- **`ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`** verbindliche Umsetzungsstufen AF, einheitliche Zugriffsschicht, Scope-Erweiterung (`division`, später Community), Capability-Vorbereitung ohne Custom-Rollen-UI; Vereinsabo explizit zurückgestellt.
- **`CAPABILITY_CATALOG.v1.md`** Rollen, Capability-IDs, Account-Lifecycle, Endpoint-Mapping.
- **`CLUB_MEMBERSHIP_AND_FEATURES.v1.md`** Vereinsabo, Feature-Registry (Mitai-v9c-Pattern), Kontingente.
---
**Letzte Aktualisierung:** 2026-05-05
**Letzte Aktualisierung:** 2026-06-06

View File

@ -0,0 +1,144 @@
# Navigation — Return-Kontext (Rücksprung)
**Stand:** 2026-05-20
**Status:** Spezifikation + Phase 12 umgesetzt
**Ziel:** In der PWA (ohne Browser-Back) zuverlässig an den fachlichen Ausgangspunkt zurückkehren — inkl. sinnvollem Label und optional UI-State.
---
## Problem
Viele Flows navigieren von Kontext A zu Editor/Detail B (z.B. Übungsliste → Modulbearbeitung). Die Zielseite kennt A nicht und bietet nur einen **fest verdrahteten** Zurück-Link (z.B. immer „Modul-Bibliothek“). In der installierten PWA fehlt zusätzlich die Browser-Chrome.
Betroffen u.a.:
- Übungsliste → Modul anlegen/bearbeiten
- Planung → Einheiten-Editor (teilweise gelöst via `planningReturn`)
- Modals mit Speichern + Redirect auf Vollseite
---
## Strategie (Hybrid)
| Mechanismus | Wann |
|-------------|------|
| **Expliziter Return-Kontext** (`appReturn` in Router-State) | Seitenwechsel, bei denen das Ziel einen fachlichen Rücksprung anbieten soll |
| **History-Back** (`navigate(-1)`) | Fallback, wenn kein Kontext gesetzt ist und History-Eintrag existiert |
| **Default-Pfad** | Fallback der Zielseite (z.B. Modul-Bibliothek) |
| **Modal schließen** | Overlays/Peek — kein Routing-Return |
**Nicht** als alleinige Lösung: reines Browser-Back (History durch `replace`, Deep Links, Reload unzuverlässig).
---
## Datenmodell
Router-State-Schlüssel: **`appReturn`**
```javascript
{
v: 1, // Schema-Version
path: '/exercises', // Ziel-URL (inkl. Query, falls nötig)
label: 'Zurück zur Übungsliste', // Anzeige im UI (vollständiger Satz)
kind: 'exerciseList', // optional: Typ für erweiterte Wiederherstellung
payload: { ... } // optional: kind-spezifische Daten
}
```
### `kind`-Werte (erweiterbar)
| kind | payload | path-Ableitung |
|------|---------|----------------|
| `exerciseList` | — | `/exercises` (Filter/Auswahl via sessionStorage) |
| `planningHub` | `buildPlanningHubReturnState(...)` | `planningHubPathFromReturnState(payload)` |
| `trainingModulesList` | — | `/planning/training-modules` |
| `planTemplatesList` | — | `/planning/plan-templates` |
| `frameworkProgramsList` | — | `/planning/framework-programs` |
| `settings` | — | `/settings` |
| `dashboard` | — | `/` |
| `mediaLibrary` | — | `/media` |
| `trainingRun` | `{ unitId }` | `/planning/run/:unitId` |
| `currentLocation` | — | aktuelle Route (z.B. Einheiten-Editor) |
| (frei) | — | `path` direkt gesetzt |
### Legacy-Kompatibilität
Bestehendes Feld **`planningReturn`** (Planung ↔ Einheiten-Editor) wird beim Lesen in `appReturn` **bridged** — keine Big-Bang-Migration nötig.
---
## API (Frontend)
Zentrale Datei: `frontend/src/utils/navReturnContext.js`
| Funktion | Zweck |
|----------|--------|
| `buildNavReturnContext({ path, label, kind?, payload? })` | Kontext-Objekt erzeugen |
| `buildExercisesListReturnContext()` | Standard-Rückkehr Übungsliste |
| `buildPlanningHubReturnContext(hubState)` | Planungs-Hub inkl. Filter-Query |
| `buildTrainingModulesListReturnContext()` | Modul-Bibliothek |
| `readNavReturnFromLocation(location)` | Kontext aus `location.state` (+ Legacy) |
| `resolveNavReturnTarget(location, fallback)` | `{ path, label }` für UI |
| `goNavReturn(navigate, location, fallback?)` | Programmatischer Rücksprung (priorisiert: Kontext → History → Fallback) |
| `navigateWithAppReturn(navigate, to, returnContext, options?)` | Navigation mit gesetztem `appReturn` |
| `preserveAppReturnOnNavigate(navigate, location, to, options?)` | Weiterleiten, bestehenden Kontext behalten (z.B. nach `replace`) |
UI-Komponente: **`PageReturnButton`** — app-typischer Zurück-Schalter (Button mit Pfeil, kein Router-Link).
Links **zum** Ziel: **`NavStateLink`** mit `returnContext` der Quellseite.
### Editor-Aktionen
Auf Vollseiten-Editoren mit **`FormActionBar`** (`placement="bottom"`) oder **`PageFormEditorChrome`**:
- **Kein** separater Zurück-Link/Button oben (wirkt in der App redundant)
- **Abbrechen**`goBack()` / `goNavReturn(...)` (Einsprungspunkt)
- **Speichern & Schließen** → nach erfolgreichem Save ebenfalls `goBack()`
- Sticky Action Bar unten nutzen
**PageReturnButton** nur auf **Leseseiten** ohne Editor-Leiste (z.B. Übungsdetail, Einstellungen-Unterseiten, Trainingsablauf).
---
## Regeln für Entwickler
1. **Jede Navigation** von Kontext A zu Editor B, wo der Nutzer „weitermachen“ soll, setzt `appReturn` (oder nutzt `navigateWithAppReturn`).
2. **Zielseite** zeigt `PageReturnButton` mit sinnvollem **Default-Fallback** (Bibliothek/Hub).
3. **Nach Create + `replace: true`:** Return-Kontext mit `preserveAppReturnOnNavigate` erhalten.
4. **Modals:** Schließen reicht; Redirect nach Speichern = Seiten-Navigation → Return setzen.
5. **Kein Return-Kontext** in `location.state` für interne Bibliothek → Detail → Bearbeiten, wenn Herkunft = offensichtliche Elternliste (Default-Fallback genügt).
6. **UI-State** (Filter, Auswahl): weiter über bestehende Session-Mechanismen (z.B. `exerciseListSessionState`), nicht im Return-Payload duplizieren, außer kind erfordert Query-Reconstruction (Planung).
---
## Umsetzungsstand
### Phase 1 (Pilot)
- [x] Spec + Utility + Tests
- [x] `PageReturnButton` (ersetzt Link-Variante)
- [x] Übungsliste → Modul speichern → Modul-Editor
- [x] Planung: `SaveExercisesAsModuleModal` leitet Return-Kontext weiter
- [x] `TrainingUnitEditPage`: `goBack` über `goNavReturn` (Legacy-bridge)
### Phase 2 (Flows verbinden)
- [x] Listen → Editoren: Übungen, Module, Vorlagen, Rahmenprogramme
- [x] Dashboard → Übung bearbeiten / Trainingsablauf / Einheit bearbeiten
- [x] Einstellungen-Unterseiten (Rechtliches, Systeminfo)
- [x] Trainingsablauf + Coach-Modus (`trainingRun`, Planungs-Fallback)
- [x] Medienbibliothek → verknüpfte Übungen/Einheiten
- [x] `ExercisePeekModal` → Vollseite mit Return
- [x] Editoren: Abbrechen + Speichern & Schließen → Einsprungspunkt
### Optional (später)
- Globaler Zurück-Button in App-Chrome (Mobile)
- Nach Speichern: explizite Aktion „Zurück zum Ausgang“ im Toast
---
## Referenzen
- Bestehend: `frontend/src/utils/planningUnitRoutes.js` (`planningReturn`)
- Session Übungsliste: `frontend/src/utils/exerciseListSessionState.js`
- PWA-Kontext: `docs/FACHLICHE_NUTZERFUNKTIONEN.md`, App-Shell in `App.jsx`

View File

@ -0,0 +1,136 @@
# Parallele Trainingsstreams — Technische Spezifikation (Umsetzung)
**Status:** Umsetzung **Phase 1 (teils)** · **Stand:** 2026-05-14
**Fachgrundlage:** `.claude/docs/functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md`
Dieses Dokument beschreibt die **Umsetzung** auf Basis der **aktuellen Codebasis** (Stand 2026-05-14): **`training_unit_phases` / `training_unit_parallel_streams`** (Migration **063**) und **`training_unit_sections`** mit Phasen-/Stream-Bezug; **`training_unit_section_items`** (Übung/Notiz, optional `planning_method_profile` für Kombinationsübungen, Migration **057**); Rahmen-**Blueprint**-Einheiten mit `framework_slot_id` (**037**); Leitung **`lead_trainer_profile_id`** (**038**); Co-Trainer **`assistant_trainer_profile_ids`** JSONB (**042**); Durchführung und Coaching über **`TrainingUnitRunPage`**, **`TrainingCoachPage`** und **`trainingPlanUtils.js`**.
---
## 1. Ist-Stand (Code, 2026-05-14)
| Bereich | Aktuell |
|---------|---------|
| **Schema** | Migration **063:** `training_unit_phases`, `training_unit_parallel_streams`; Sektionen mit `phase_id` **oder** `parallel_stream_id`. |
| **API** | `GET /api/training-units/:id`**`phases`** (verschachtelt) + flache **`sections`**. `PUT/POST` mit **`phases`** für Breakout-Einheiten (**0.8.138**); höchstens eines von `phases`, `sections`, `exercises` pro Request (Planning-Router). Legacy-PUT mit nur `sections` erzeugt/ergänzt Ganzgruppen-Phase. |
| **Planung (UI)** | Breakout-Panel: Ganzgruppen-/parallele Phasen, Streams; Speichern phasenbasiert (`trainingUnitSectionsForm.js`, `TrainingPlanningPage`). |
| **Durchführung** | `TrainingUnitRunPage.jsx` + `trainingPlanUtils.js` (`sectionsWithPlanLocForDisplay`, `buildPlanRunViewModelFromSections`) — Phasenfolge in „Plan & Ablauf“. |
| **Coaching** | `TrainingCoachPage.jsx` + `flattenPlanTimeline`, Stream-Picks, Rejoin vor Ganzgruppe/nächstem Split (`coachShouldPromptSplitRejoinTransition`), Nachbereitung mit `buildCoachSavePlanPayload`, danach Navigation zu `/planning/run/:id`. |
| **Kombinationsübung** | Unverändert je Item; `planning_method_profile`, Coach-Kombi-Stufe A. |
| **Trainer-Zuweisung** | `lead_trainer_profile_id`, `assistant_trainer_profile_ids` am Einheitskopf; **Stream-**`assigned_trainer_profile_ids` im Schema — UI/Policy noch nicht vollständig (siehe **§8 offen**). |
| **Rahmenprogramm** | Blueprint-`training_units` können dieselbe Phasenstruktur tragen; Kopie aus Slot (`from-framework-slot`, **0.8.138**). |
**Hinweis:** Die frühere Planungsvariante „nur lineare `training_unit_sections` ohne Phasen“ gilt weiter für Alt-Daten; Migration **063** ordnet Bestand einer Default-Ganzgruppenphase zu.
---
## 2. Zielarchitektur (logisch)
```
training_unit (Kalender-Einheit)
├── phase (order, kind: whole_group | parallel, optional Metadaten)
│ ├── [whole_group] → sections[] → items[] (wie heute)
│ └── [parallel] → stream (order, label, optional trainer_ids[])
│ └── sections[] → items[]
```
**Abwärtskompatibilität:** Einheiten **ohne** explizite Phasen/Streams verhalten sich wie heute: **implizit** eine einzige „Gemeinschaftsphase“ mit den vorhandenen Sektionen (Migration: alle bestehenden Sektionen an diese Default-Hülle hängen).
---
## 3. Datenmodell — Optionen
**Ist (063):** Die unten skizzierte **empfohlene** Normalform ist unter den genannten Tabellennamen produktiv; die Abschnitte 3.1/3.2 bleiben zur Einordnung erhalten.
### 3.1 Empfohlen: explizite Phasen + Streams (normalisiert)
Die Tabellen sind **umgesetzt** (Namen final):
| Tabelle | Zweck |
|---------|--------|
| `training_unit_phases` | `training_unit_id`, `order_index`, `phase_kind` (`whole_group` \| `parallel`), optional `title`, `guidance_notes`, optional `planned_duration_min` |
| `training_unit_parallel_streams` | `phase_id` (FK, nur wenn parent parallel), `order_index`, `title`/`label`, optional `notes`, optional `assigned_trainer_profile_ids` JSONB (oder 1:n-Hilfstabelle) |
**Anpassung `training_unit_sections`:** Zusätzliche FK-Spalte(n), z.B.:
- `phase_id` **NULL** und `parallel_stream_id` **NULL****Legacy / Default-Einheitsphase** (Migration setzt Default-Phase); oder
- genau einer von `phase_id` (whole group) oder `parallel_stream_id` gesetzt.
**Constraints:** CHECK: nicht beide gesetzt; bei `phase_kind = parallel` Sektionen nur unter `parallel_stream_id`; bei `whole_group` nur unter `phase_id`.
**Vorteil:** Klare Semantik, Reporting, API-Shape konsistent.
### 3.2 Minimalvariante (nicht ideal fachlich)
Nur **`training_unit_parallel_streams`** + `parallel_stream_id` auf Sektionen; Phasen implizit durch „Marker“-Sektionen oder Konvention. **Nicht empfohlen**, erschwert UI und Erklärbarkeit.
---
## 4. API
- **`GET /api/training-units/:id`** (und Listen-Payloads wo vollständiger Plan nötig): verschachtelte Struktur **Phasen → Streams → sections → items** oder flache `sections` mit ausgefüllten `phase_id` / `parallel_stream_id` (Frontend kann normalisieren).
- **`PUT/PATCH`:** Atomares Ersetzen der Phasen/Streams/Sektionen analog zu bestehendem `_replace_unit_sections`-Muster; **Validierung** der CHECK-Regeln serverseitig.
- **Blueprint / Rahmen:** Blueprint-`training_units` dürfen dieselbe Struktur tragen; `GET` Kalenderliste blendet Blueprints weiter aus (`framework_slot_id IS NOT NULL`).
**Governance / Mandant:** Unverändert über Einheit → `group_id`; keine neuen Mandanten-Entitäten.
---
## 5. Frontend
### 5.1 Planung (`TrainingPlanningPage`)
- Darstellung als **vertikale Phasen**: Gemeinschaftsblöcke + Parallelphase mit **N Spalten** (Streams).
- **Wiederverwendung:** `TrainingUnitSectionsEditor` **pro Stream** und pro Gemeinschaftsphase — analog zur Wiederverwendung **pro Rahmen-Slot** in `TrainingFrameworkProgramEditPage`.
- **Co-Trainer:** UI pro Stream (`assigned_trainer_profile_ids`); Regel zur **Kopfliste** `assistant_trainer_profile_ids` festlegen (z.B. Union aller Stream-Zuweisungen für „Wer ist heute dabei“ + Rückwärtskompatibilität wenn Stream-Felder leer).
### 5.2 Durchführung (`TrainingUnitRunPage`)
- Gemeinschaftsphasen: heutiges **lineares** Verhalten.
- Parallelphase: **Tabs, Akkordeon oder Swipe** zwischen Streams; Fortschritt **pro Stream** (Storage-Key z.B. `${unitId}:${streamId}`).
- Kombi-Items: unverändert `CombinationPlanBracket` / `effectiveComboMethodProfile`.
- Optional später: Filter „nur meine Spur“ anhand Session-Profil vs. Stream-Zuweisung.
### 5.3 Vorlagen (`training_plan_templates`)
- Erweiterung um **dieselbe** Phasen/Streams-Semantik (Kindtabellen oder serialisiertes JSON — Abgleich mit Kopierlogik aus Vorlage in Einheit).
- **Kein** Live-Spiegel: weiterhin Materialisierung beim Anwenden.
---
## 6. Bezug Kombinationsübungen
- **Variante A** (Rotation innerhalb einer Teilstrecke): ein oder mehrere **Items** vom Typ Kombi im jeweiligen Stream; Archetyp und Parameter wie in `TRAINING_MODULES_AND_COMBINATION_EXERCISES_SPEC.md`.
- **Variante B** (synchron Hallenweit): erweiterte **Phasen-** oder **Stream-übergreifende** Metadaten — **nicht** in MVP-Zwang; eigenes Teilpaket nach fachlicher Freigabe (`PARALLEL_TRAINING_STREAMS_CONCEPT.md` §5.2).
---
## 7. Migration und Risiken
1. **Datenmigration:** Alle existierenden `training_unit_sections` einer Einheit einer **Default-Phase** `whole_group` zuordnen.
2. **API-Versionierung:** Clients, die nur flache `sections` erwarten, müssen angepasst werden (oder Server liefert **beides** kurzzeitig — nur wenn nötig).
3. **Performance:** Tiefe Kopien (Rahmen-Slot, Duplikat Einheit) müssen rekursiv Phasen/Streams mitsamt Sektionen/Items kopieren.
4. **Tests:** pytest für PUT/GET mit gemischten Phasen; ggf. Playwright-Smoke für Planung/Run.
---
## 8. Implementierungsphasen (Abgleich)
| Phase | Inhalt | Stand 2026-05-14 |
|-------|--------|------------------|
| **P1** | Schema Phasen + Streams; Migration **063**; GET/PUT verschachtelt; Planungs-UI; Run + Coach phasenbasiert | **Teilweise erledigt** — Run-UI nutzt Phasen-Timeline in der Anzeige; **Stream-Tabs** optional noch zu vereinheitlichen (§5.2) |
| **P2** | Trainer-Zuordnung pro Stream + effektive Anzeige; Vorlagen erweitert | **Offen** |
| **P3** | Synchroner Hallen-Takt / Rotationsmatrix (falls fachlich freigegeben) | **Offen** |
**Offene Punkte (kurz):** siehe **`docs/HANDOVER.md`** Tabelle „Coaching & Breakout“.
## 9. Verwandte Dokumente
| Dokument | Bezug |
|----------|--------|
| `functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md` | Fachziele, Begriffe, Entscheidungsfragen |
| `technical/TRAINING_FRAMEWORK_SPEC.md` | Rahmen-Slot vs. Parallelität |
| `technical/TRAINING_MODULES_AND_COMBINATION_EXERCISES_SPEC.md` | Kombi, `planning_method_profile` |
| `technical/DATABASE_SCHEMA.md`, `backend/migrations/` | DDL-Historie |
| `TrainingPlanningPage.jsx`, `TrainingUnitRunPage.jsx`, `TrainingFrameworkProgramEditPage.jsx` | Planung, Durchführung, Rahmen |
| `frontend/src/utils/trainingPlanUtils.js`, `TrainingCoachPage.jsx` | Phasen-Timeline, Rejoin, Coach-Speichern |

View File

@ -0,0 +1,184 @@
# Gewichtetes Fähigkeiten-Scoring (Phase 3)
**Stand:** 2026-05-20
**Status:** Variante A (regelbasiert) umgesetzt — **v1.3** (Peer-Kontext getrennt + Listen-Filter)
**Modul:** `backend/skill_scoring.py`, Router `backend/routers/skill_profiles.py`
## Ziel
Trainer wählen **Schwerpunkt-Fähigkeiten** und finden passende **Bausteine** für die Trainingsplanung:
- **Trainingsmodule** — wiederverwendbare Übungsfolgen
- **Rahmenprogramme** — Programme mit Zielen und Session-Slots
- **Regressionspfade** (Progressionsgraphen) — Übungsketten
Das Scoring beantwortet: *Wie stark trainiert dieser Baustein eine Fähigkeit?* und *Wie stark ist er im Vergleich zu anderen **sichtbaren** Bausteinen **desselben Typs**?*
## Fachliche Kernregel: Peer-Kontext (nicht vermischen)
| Planungs-Artefakt | Vergleichsgruppe (`universal_percent`) |
|-------------------|----------------------------------------|
| Trainingsmodul | nur andere **sichtbare Module** |
| Rahmenprogramm | nur andere **sichtbare Rahmenprogramme** |
| Regressionspfad | nur andere **sichtbare Pfade** |
**Nicht** verglichen werden:
- Module vs. Rahmenprogramme vs. Pfade (kein Mix)
- Artefakte anderer Vereine, auf die der Nutzer keinen Planungszugriff hat
**Sichtbarkeit:** `library_content_visibility_sql` — private, vereinsinterne und offizielle Inhalte gemäß Mandant/Rolle, analog zu anderen Bibliothekslisten.
## Datenquellen
| Artefakt | Übungen aus |
|----------|-------------|
| Rahmenprogramm (gesamt) | Alle Blueprint-`training_units` der Slots → `training_unit_section_items` |
| Rahmenprogramm (pro Slot) | Blueprint einer Session |
| Trainingsmodul | `training_module_items` (nur `item_type = exercise`) |
| Progressionsgraph | `from_exercise_id` + `to_exercise_id` je Kante (Vorkommen zählt) |
Fähigkeiten je Übung: `exercise_skills``skills` (nur `status = active`).
## Gewichtungsformel (v1.1 / v1.2)
Pro **Übungsvorkommen** (eine Zeile im Ablauf / Modul / Kanten-Endpunkt):
1. **Basis-Minuten** = `planned_duration_min` der Position, sonst Default (Einheit/Modul: 8 Min, Graph: 10 Min).
2. Pro verknüpfte Fähigkeit der Übung:
- `Beitrag = Basis-Minuten × Anzahl Vorkommen × Link-Faktor`
- **Link-Faktor** = Intensität × Stufen-Faktor
### Intensität (Nutzeneinschätzung, UI-Feld)
| Wert | Faktor |
|------|--------|
| niedrig | 0,85 |
| mittel / leer | 1,0 |
| hoch | 1,2 |
### Stufen-Spanne (`required_level` → `target_level`, UI „von/bis“)
Kanonische Slugs: basis … optimierung (15). Fehlen beide: Faktor 1,0.
- **Spanne** = Anzahl Stufen von „von“ bis „bis“ (15)
- **Mittelpunkt** = durchschnittliche Stufe
- Faktor ≈ `(0,92 + 0,04 × Spanne) × (0,95 + 0,025 × Mittelpunkt)` → typisch 0,961,20
### Bewusst nicht im Scoring
| Feld | Grund |
|------|--------|
| `is_primary` | Perspektivabhängig; bleibt in Übungs-UI, fließt nicht ins Profil ein |
| `development_contribution` | Legacy-DB-Feld, in UI nicht gepflegt |
## Aggregierte Metriken
| Feld | Bedeutung |
|------|-----------|
| `weight` / `score` | Absolutes **Trainingsgewicht** (gewichtete Minuten) — über alle Fähigkeiten eines Artefakts vergleichbar |
| `share_percent` | Anteil am `total_weight` **innerhalb dieses Artefakts** (summiert 100 %) — sekundär |
| `by_main_category[]` | Je Unterkategorie `top_skill` (stärkste Fähigkeit nach Gewicht) |
| `universal_percent` | Anteil am **Maximum derselben Fähigkeit im Peer-Kontext** (max. 100 %) |
| `is_club_best_for_skill` | Stärkster sichtbarer Peer für diese Fähigkeit (★ in UI) |
| `club_best` | Referenz-Peer (Titel, Typ, Gewicht) — **Legacy-Name**, fachlich Peer-Best |
### Berechnung `universal_percent`
```
effective_ref = max(max_weight_in_peer_corpus(skill_id), eigenes_gewicht)
universal_percent = min(100, weight / effective_ref × 100)
```
Corpus je Typ: `compute_planning_corpus_by_type()` scannt sichtbare Artefakte getrennt nach `framework_program`, `training_module`, `progression_graph`.
Discovery-Sortierung nutzt **`match_score`** (= Summe absoluter Gewichte der gewählten Fähigkeiten), nicht den Plan-internen Anteil. Discovery verwendet typ-getrennte Referenz (`fw_ref`, `mod_ref`, `graph_ref`).
## API
| Methode | Pfad | Beschreibung |
|---------|------|--------------|
| GET | `/api/training-framework-programs/{id}/skill-profile` | `overall` + `slots[]` mit je `profile`; `reference_scale` (Peer-Kontext Rahmenprogramme) |
| GET | `/api/training-modules/{id}/skill-profile` | `overall`; `reference_scale` (Peer-Kontext Module) |
| GET | `/api/exercise-progression-graphs/{id}/skill-profile` | `overall`; `reference_scale` (Peer-Kontext Pfade) |
| POST | `/api/skill-profiles/batch-summaries` | Kompakte Profile für Listen; Body: `frameworkProgramIds`, `trainingModuleIds`, …; Response: `summaries`, `reference_scale_by_type`, `club_best_by_skill` |
| GET | `/api/skill-discovery/suggestions?skill_ids=1,2,3` | Ranking sichtbarer Artefakte; Query `types`, `limit` |
Zugriff: `get_tenant_context` + `library_content_visibility_sql` wie Parent-Artefakt.
### `reference_scale` / `reference_scale_by_type`
```json
{
"scope": "planning_peer",
"artifact_type": "training_module",
"artifacts_scanned": 12,
"skills_in_corpus": 34,
"description": "Prozent = Anteil am stärksten sichtbaren Eintrag unter Trainingsmodulen …"
}
```
## UI
### Bearbeitung (Vollprofil)
| Ort | Panel | `artifactType` |
|-----|-------|----------------|
| Rahmenprogramm bearbeiten | Fähigkeiten-Schwerpunkte (+ Sessions) | `framework_program` |
| Trainingsmodul bearbeiten | Fähigkeiten im Modul | `training_module` |
| Progressionsgraph | Fähigkeiten entlang des Pfads | `progression_graph` |
Anzeige: Top je Kategorie (Editor) oder alle Fähigkeiten (Modal). Hinweise nennen Peer-Kontext explizit (z. B. „72 % Rahmenpr.“).
### Listen & Filter (UX wie Übungsliste)
| Liste | Filter |
|-------|--------|
| Rahmenprogramme (`/planning/framework-programs`) | Suche, Katalog (Fokus/Trainingsart/Zielgruppe), Session-Dauer, **Fähigkeiten** (`SkillTreeMultiSelect`), Mindest-% im Peer-Kontext, Sortierung nach Stärke |
| Trainingsmodule (`/planning/training-modules`) | Suche, **Fähigkeiten** (+ Min-%, Sortierung) |
- **Filter-Button** mit Badge, entfernbare **Chips**, Einstellungen im **Modal** (`PlanningArtifactFilterModal`)
- KPI-Kacheln: Top-Fähigkeit **je Unterkategorie** mit Score + Peer-%
- Vollprofil-Modal: `SkillProfileFullModal` mit `displayMode=full`
Profil wird nach Speichern neu geladen (`skillProfileTick` in Editoren).
### Discovery
**Fähigkeiten-Seite → Planungs-Vorschläge:** Multi-Select + API `/api/skill-discovery/suggestions` (optional Filter `types`).
## Frontend-Module (Auswahl)
| Pfad | Rolle |
|------|--------|
| `frontend/src/components/planning/PlanningArtifactFilterModal.jsx` | Filter-Modal |
| `frontend/src/components/planning/PlanningSkillFilterSection.jsx` | Fähigkeiten-Block im Modal |
| `frontend/src/utils/planningArtifactFilterChips.js` | Chip-Labels + Entfernen |
| `frontend/src/utils/frameworkProgramListHelpers.js` | Client-Filter Rahmenprogramme |
| `frontend/src/utils/trainingModuleListHelpers.js` | Client-Filter Module |
| `frontend/src/components/skills/SkillProfileCompact.jsx` | KPI-Kacheln in Listen |
| `frontend/src/components/SkillTreeMultiSelect.jsx` | Baumauswahl (Portal-Dropdown in Modals) |
## Grenzen / später
- Kein DB-Cache (`skill_profile_json`) — on-the-fly; bei >50 Artefakten pro Typ serverseitiger Index/Caching
- Entwicklungsziele am Rahmenkopf bleiben Freitext (kein Scoring)
- KI-Zusammenfassung (Variante B) nicht Teil von v1.0
- Trainings**einheiten** (Kalender) optional als nächste Erweiterung
- Filter-Persistenz („Als Standard speichern“) wie bei Übungen — noch nicht für Planungslisten
- Fähigkeiten-Filter im Dialog **Planung → Rahmen übernehmen** — Katalog ja, Skill-Filter optional nachziehen
- API-Feldnamen `club_*` / `skillMinClubPercent` — technische Altlast, semantisch Peer-Kontext
## Tests
- `backend/tests/test_skill_scoring.py` — Multiplikator, Aggregation, Match-Score, Cap 100 %
- **Offen:** dedizierte Tests für `compute_planning_corpus_by_type` (Typ-Trennung)
## Verweise
| Dokument | Inhalt |
|----------|--------|
| `functional/DOMAIN_MODEL.md` | Domänenabschnitt Planungs-Fähigkeiten-Profil |
| `docs/FACHLICHE_NUTZERFUNKTIONEN.md` | Nutzerüberblick Listen/Filter |
| `docs/HANDOVER.md` | Handover-Abschnitt Phase 3 |
| `technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` | Sichtbarkeit / Mandant |

View File

@ -15,6 +15,8 @@
| `DATABASE_SCHEMA.md` | **Nachgeordnete** Übersicht: Migrationshistorie und Tabellenliste; Detail-DDL primär **hier §2§3** + SQL unter `backend/migrations/`. |
| `functional/DOMAIN_MODEL.md` | Fachliche Begriffe; Kurzverweis auf Progressionsgraph ergänzt. |
| `TRAINING_CURRICULUM_AND_GOVERNANCE_CONCEPT.md` | **Was** und **warum** (Bibliothek vs. Instanz, Governance, CURRTabelle). |
| `functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md`, `technical/PARALLEL_TRAINING_STREAMS_SPEC.md` | **Parallele Streams / Breakout innerhalb einer Einheit** — orthogonale Domäne zu **RahmenSlots** (SerienSessions). |
| `technical/SKILL_SCORING_SPEC.md` | **Fähigkeiten-Profil** der RahmenSlots / Module / Pfade; Listen-Filter und PeerVergleich (nur gleicher Artefakttyp). |
**Konsequenz:** Diese Datei bleibt der **technische Arbeitspool** für Rahmenprogramm Stufe 12. Abschnitt **§4** beschreibt explizit den **aktuellen Produktfreigabe-Umfang** und **bekannte Lücken** (damit Trainingsplanung weiter gebaut werden kann ohne falscher Erwartung an „AlternativePakete“ in der UI).

View File

@ -0,0 +1,205 @@
# Trainingsmodule und Kombinationsübungen — Spezifikation (Entwurf)
**Status:** Entwurf zur fachlichen und technischen Abstimmung · **Stand:** 2026-05-12 (Code **0.8.110**, siehe `backend/version.py`)
**Zweck:** Rahmen für Umsetzung, Integration in Planung/Rahmenprogramm und Durchführung im assistierten Training (Coaching-Modus). Dieses Dokument ist **nicht** implementierungsbindend, bis die markierten **offenen Entscheidungen** geschlossen und der Status angehoben wurde.
**Abgleich mit Code (Drift vermeiden):**
- **Kanonische Archetyp-IDs:** fest in `backend/routers/exercises.py` (`COMBINATION_ARCHETYPE_IDS`); fachliche Tabelle und UI-Labels in `frontend/src/constants/combinationArchetypes.js` — die **fachliche Master-Zuordnung** Name↔ID steht in `functional/Shinkan Trainingsmodule Kombinationsuebungen Spezifikation V2.md` §10.2.1. **Administrierbare Archetypen** (DB/UI) gibt es **nicht**; Erweiterungen nur per Code-Release — Fachspez **§10.6**.
- **Planungs-Override:** `planning_method_profile` (Migration **057**) speichert einen **Snapshot**; **Merge** mit Katalogprofil erfolgt im Frontend (`frontend/src/utils/comboPlanningMethodProfile.js` — `effectiveComboMethodProfile`), nicht als serverseitiger Join. Payload-Sanitisierung vor PUT; Backend speichert JSONB zuverlässig (`Json()`).
- **Coaching:** Stufe **A** — Slots, Kandidaten, Archetyp-Hilfstext, **Label** für globale Eckdaten (`describeGlobalComboProfile` in `combinationMethodProfileUi.js`), visuelle Klammer (`CombinationPlanBracket`) in Peek/Run; Stufen **B/C** (archetypgesteuerte Zeitleiste/Takt) **offen** — Fachspez §10.4, **§10.6**, **Anhang A**.
- **Umsetzungsplan:** `working/TRAINING_MODULES_IMPLEMENTATION_PLAN.md` (Phase 2/4 „teilweise“; Pakete **4e4g** für Admin, Vorbelegung, Validierung).
**Verwandte Dokumente:**
| Dokument | Bezug |
|----------|--------|
| `TRAINING_FRAMEWORK_SPEC.md` | Rahmen-Bibliothek, Slot-Blueprint, Kopiersemantik (`from-framework-slot`) |
| `functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md`, `technical/PARALLEL_TRAINING_STREAMS_SPEC.md` | Parallele Teilstrecken **innerhalb einer Einheit**; Kombi-Übungen weiter nutzbar **pro Stream** für Stationsrotation |
| `DATABASE_SCHEMA.md` | Aktueller Stand `training_units`, Sektionen, Items |
| `functional/DOMAIN_MODEL.md` | Domänenbegriffe (bei Bedarf zu erweitern) |
| `EXERCISES_*` (Katalog) | Einzelübungen, Varianten |
| `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` | Sichtbarkeit, Mandant, Rollen bei neuen Bibliotheks-Entitäten |
---
## 1. Zielbild und Abgrenzung
### 1.1 Problem
Die Trainingsplanung unterstützt Einheiten mit Sektionen und einzelnen Übungen (inkl. Notizen) sowie Rahmenprogramme mit Blueprint-Einheiten pro Slot. Es fehlen:
- **Wiederverwendbare Übungsfolgen** („Trainingsmodule“), die sich wie Bausteine in eine Einheit einfügen lassen (ganze Sektion oder Block innerhalb einer Sektion), inkl. kopierbasierter Integration analog zum Rahmen.
- **Strukturierte Kombinationsformen** (z. B. Zirkel mit Stationstausch, Parcour), bei denen **mehrere Einzelübungen** Slots oder Rollen einnehmen und die **Trainingsmethode** den Ablauf (Rotation, parallele Stationen, Zeitmodell) bestimmt.
- Ein durchgängiges Konzept für den **Coaching- bzw. Assistenzmodus**, in dem derselbe Plan je nach Archetyp **unterschiedlich gesteuert** wird (Beispiel Zirkel: Erklärphase vs. parallele Nutzung aller Stationen).
### 1.2 Nicht-Ziele (für erste Ausbaustufe)
- **Individuelles Athleten-Tracking** oder Leistungsmessung pro Person (außerhalb Shinkan-MVP, siehe Produktabgrenzung).
- Automatische **Synchronisation** zwischen Bibliotheksexemplar und bereits geplanten historischen Einheiten (bewusst: **Kopie** statt Live-Spiegel, konsistent mit Rahmen-Konzept).
### 1.3 Zwei Bausteine (fachliche Trennung)
| Baustein | Kurzname | Einordnung | Kurzbeschreibung |
|----------|-----------|------------|------------------|
| **Typ 1** | **Kombinationsübung** | Übungskatalog (Sonderform einer **Übung**) | Eine logische Übung mit **1n Slots**; Slots können einzelne Übungen oder **Pools** auswählbarer Übungen tragen; **Methodenprofil / Archetyp** steuert später den Durchlauf. |
| **Typ 2** | **Trainingsmodul** | Planung / Bibliothek | Gespeicherte, wiederverwendbare **Sequenz** von Elementen (Einzelübungen, optional Kombinationsübungen, Notizen); Einbindung per **Kopie** in konkrete `training_units` oder in Rahmen-Slot-Blueprints. |
**Abgrenzung Rahmenprogramm:** Das Rahmenprogramm strukturiert **mehrere Einheiten** (Slots) auf Programm-Ebene. Ein Trainingsmodul strukturiert typischerweise **Inhalt einer Einheit** oder eines Teils davon, nicht den Wochen-/Periodenrahmen.
---
## 2. Begriffe
| Begriff | Definition |
|---------|------------|
| **Bibliotheksexemplar** | Gespeicherte Vorlage (Modul oder Kombinationsübung-Definition) mit Governance (z. B. global, Verein, privat). |
| **Instanz in der Planung** | In `training_unit_section_items` (und ggf. ergänzende Tabellen) materialisierter Ablauf für einen **konkreten Termin** bzw. eine **geplante Einheit**. |
| **Slot (Typ 1)** | Position innerhalb einer Kombinationsübung; kann genau eine gewählte Übung oder einen **Pool** (mehrere Kandidaten) referenzieren. |
| **Methodenprofil / Archetyp** | Maschinenlesbare Semantik **wie** trainiert wird (Zeit, Rotation, Parallelität), ergänzend zum bestehenden Katalog `training_methods` (Beschreibung **was** für eine Didaktik/Kondition gilt). |
| **Coaching-Modus** | UI- und Zustandslogik zur Durchführung einer geplanten Einheit (Timer, Phasen, Stationen). |
---
## 3. Trainingsmethoden und Archetypen (Typ 1)
### 3.1 Bestehende Basis
Der Katalog `training_methods` (Migration 003) enthält u. a. **Zirkeltraining** (`category` u. a. `zirkel`, `kondition`). Er beschreibt die Methode **inhaltlich**, nicht aber Parameter wie Wechselintervalle oder parallele vs. rotierende Nutzung.
### 3.2 Erweiterung: Archetyp
Jede Kombinationsübung (und optional der Methodendatensatz als Default) erhält ein Feld **`method_archetype`** (Enum/Wertliste). Der Archetyp legt fest, welche **Parameter** am Methodenprofil relevant sind und wie der **Coaching-Modus** den Ablauf interpretiert.
**Vorschlagsliste (erweiterbar, zu verbindlich machen):**
| Archetyp-ID (Vorschlag) | Beschreibung Planungslogik | Coaching (Intent) |
|-------------------------|----------------------------|---------------------|
| `circuit_rotate_time` | n Stationen; Wechsel nach Ablaufzeit, optional globale Rundenanzahl | Rotierender oder gemeinsamer Takt; Umlauf zur nächsten Station |
| `circuit_all_parallel` | n Stationen; **kein** Umlauf als fachlicher Kern, alle Stationen gleichzeitig aktiv | Erklärphase (alle Inhalte vorher), dann **parallel** alle Stationen |
| `sequence_linear` | feste Reihenfolge; Aufbau, keine Kreisrotation | Schrittliste / Timer optional pro Schritt |
| `station_parcour` | Stationsbezogener Pfad, Reihenfolge kann variieren | Navigation / Abhaken eher als ein globaler Umlauf-Takt |
| `pair_superset` | zwei (oder wenige) Blöcke im Wechsel | Partnerlogik, gekoppelte Timer |
| `time_domain_interval` | AMRAP/EMOM-ähnliche Zeitdomäne | Globale Uhr, Runden-/Intervallzähler |
### 3.3 Parameter des Methodenprofils
Zu präzisieren (JSON-Dokument vs. normalisierte Spalten):
- Zeit: `work_seconds`, `rest_seconds`, `transition_seconds`, `rounds`
- Organisation: `station_count`, `rotation_direction`, Flags `explain_all_before_start`, `stations_operate_simultaneously`
- ggf. `intensity_profile` (skalar oder Enum), nur wenn für MVP nötig
**Offen:** Welche Parameter sind **Pflicht pro Archetyp** (Validierung).
---
## 4. Datenmodell (Zielarchitektur, Entwurf)
### 4.1 Typ 2 — Trainingsmodule
**Entwurfstabellen (Namen können bei Implementierung angeglichen werden):**
- `training_modules` — Kopf: Titel, Beschreibung, Metadaten, `visibility`, `club_id`, `created_by`, Timestamps
- optional `training_module_sections` — falls ein Modul mehrere semantische Blöcke abbilden soll
- `training_module_items` — Reihenfolge, Verweis auf:
- Einzelübung (`exercise_id`, `exercise_variant_id`)
- Kombinationsübung (`combination_exercise_id` / `exercise_id` mit `kind=combination`)
- Freitext-Notiz (analog `note` bei Einheiten)
Semantik: **Bibliotheksbaum**, keine Bindung an Kalender oder Gruppe.
### 4.2 Typ 1 — Kombinationsübungen
**Option A (Embedding in `exercises`):** Spalte `exercise_kind` = `simple` | `combination` und Kindtabellen für Slots/Pools.
**Option B (Separate Kopf-Tabelle):** 1:1-Beziehung zwischen `exercises` und `combination_exercises`.
**Slot-Pools:** mindestens M:N **Pool-Kandidat** pro Slot; die **konkret geplante Auswahl** gehört zur **Instanz** (geplante Einheit), nicht zwingend zum Bibliotheksexemplar.
### 4.3 Integration in geplante Einheiten
Heute: `training_unit_section_items` mit `item_type` in (`exercise`, `note`).
**Erweiterungsoptionen (Entscheidung offen):**
1. **Expansion beim Einfügen:** Modul wird in Items „aufgeklappt“; optional `source_module_id` an Items für Herkunft (Lineage-Light).
2. **Block-Item:** neuer `item_type` `module_reference` oder `combination` mit ID und eingebetteter Bearbeitungssemantik (komplexer, aber „Modul als Einheit“ editierbar).
Empfehlung zur Abstimmung: MVP oft mit **Expansion** + optionaler Markierung; später Block-Knoten.
**Rahmenprogramm:** Blueprint-`training_units` pro Slot nutzen dieselbe Sektions-/Item-Struktur — Module müssen **dort** ebenfalls einfügbar sein, wenn Rahmen und konkrete Planung konsistent bleiben sollen.
---
## 5. API (Skizze)
Verbindliche Pfade und Payloads folgen nach Freigabe dieses Dokuments.
| Richtung | Beispielpfad / Funktion | Zweck |
|----------|-------------------------|--------|
| CRUD | `GET/POST/PUT/DELETE …/training-modules` | Bibliothek Trainingsmodule |
| Anwendung | `POST …/training-units/{id}/apply-module` | Modulinhalt in Sektion kopieren (tiefe Kopie) |
| Übungen | Erweiterung `GET/POST/PUT …/exercises` oder Unterressource `…/exercises/{id}/combination` | Kombinationsübung inkl. Slots |
| optional | `POST …/training-units/from-module` | Neue Einheit aus Modul (falls produktrelevant) |
**AuthZ:** analog andere Bibliotheks- und Planungsobjekte; Abgleich mit `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` und Endpoint-Audit.
---
## 6. Frontend
- **Bibliothek:** Verwaltung Trainingsmodule (Liste, Editor, Sortierung, Vorschau).
- **Übungsbereich:** Editor für Kombinationsübungen (Slots, Pools, Methodenprofil/Archetyp).
- **Planungs-UI:** Aktion „Modul einfügen“, Ziel-Sektion und Position; Hinweis **Kopie** und Editierbarkeit pro Termin.
---
## 7. Coaching- / Assistenzmodus (Durchlauf)
### 7.1 Phasenmodell (konzeptionell)
- **Briefing / Erklärung:** insbesondere für `circuit_all_parallel` und Varianten mit `explain_all_before_start`
- **Arbeitsphase(n):** timer- und stationsgetrieben
- **Übergänge:** Pausen, Wechsel, Rundenzähler
### 7.2 Persistenz während Durchführung
**Offen:** Ob ein **`training_session_run`** (Snapshot der aufgelösten Einheit zum Startzeitpunkt) für Nachvollziehbarkeit und Offline-Fähigkeit nötig ist.
### 7.3 Ausbaustufen
1. Read-only **Durchführungsansicht** (Archetyp + Zeiten, keine komplexe State Machine)
2. **Aktiver Modus** mit State Machine und Archetyp-spezifischer UI
3. Optional: Offline/PWA-Verhalten
---
## 8. Umsetzungsphasen (Vorschlag)
| Phase | Inhalt |
|-------|--------|
| **A** | Dieses Dokument verbindlich machen; Archetypen und Parameter final; Governance-Regeln |
| **B** | Typ 2: `training_modules` + API + „Modul in Einheit einfügen“ (Expansion) |
| **C** | Typ 1: Kombinationsübung im Katalog + Slots/Pools + Methodenprofil |
| **D** | Einbindung in Rahmen-Slot-Blueprints (Editor-Flow) |
| **E** | Coaching-Modus gemäß Archetyp |
---
## 9. Offene Entscheidungen (Checkliste)
- [ ] Modul-Einfügung: nur **Expansion** vs. **Block-Knoten** vs. beides
- [ ] Normalisierung vs. JSON für **Methodenprofil-Parameter**
- [ ] Globale vs. vereinsbezogene vs. private **Trainingsmodule** (Governance-Matrix)
- [ ] Pflichtbinding: muss jede Kombinationsübung einen **Default-Archetyp** aus `training_methods` erben dürfen?
- [ ] Coaching: Mindestumfang MVP (nur Ansicht vs. interaktive Timer)
- [ ] Verweise in `DOMAIN_MODEL.md` und `DATABASE_SCHEMA.md` nach Implementierung pflegen
---
## 10. Changelog
| Datum | Änderung |
|-------|----------|
| 2026-05-12 | Erstversion (Entwurf) angelegt |

View File

@ -13,8 +13,11 @@ Fortlaufend gemäß `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` Stufe AC.
| exercises | `PATCH /api/exercises/bulk-metadata` | ja | `get_tenant_context` | ja | Liste: UI-Mehrfachwahl; bis 500 IDs; nur Ersteller oder Plattform-Admin |
| exercises | `GET .../media/{mid}/file` | ja | `get_tenant_context_flexible` | ja (wie Übung lesen) | Datei oder `?ssetoken`; kein anonymes `/media/` ohne ALLOW_PUBLIC_MEDIA_STATIC |
| exercises | übrige geschützte `/api/exercises*` | ja | `get_tenant_context` | ja | PUT Einzelübung: bei Sichtbarkeit `official` Medien-§4.2 (422: Lifecycle/Promotion/Copyright) |
| exercises | POST `/api/exercises/ai/suggest`, POST `/api/exercises/{id}/ai/regenerate` | ja | `get_tenant_context` | nein | Nur Vorschlags-JSON; keine DB-Schreibung; OpenRouter — suggest optional `focus_areas_context` für Retrieval-Profile |
| exercise_progression_graphs | `/api/exercise-progression-graphs*` | ja | `get_tenant_context` | Liste wie Bibliothek; Schreiben Ersteller/Plattform-Admin | Kanten: Lesen wenn Graph lesbar |
| training_planning | alle geschützten Endpoints | ja | `get_tenant_context` | ja | Vorlagen-Liste wie Übungen; POST Vorlage Default club_id |
| dashboard | `GET /api/dashboard/kpis` | ja | `get_tenant_context` | wie `GET /api/exercises` + `GET /api/training-units` | Aggregat für Dashboard-Kurzüberblick (ein Roundtrip) |
| training_modules | `/api/training-modules*` | ja | `get_tenant_context` | ja | Bibliotheks-Module wie Vorlagen/Rahmen; POST Default `club_id` bei `visibility=club` |
| training_framework_programs | alle geschützten Endpoints | ja | `get_tenant_context` | ja | Liste + POST Default club_id |
| admin_users | `GET /api/admin/users` | Plattform | `require_auth` | Admin-Rolle | EXEMPT `check_access_layer_hints.py` |
| platform_media_storage | `GET/PUT /api/admin/platform-media-storage` | Plattform | `require_auth` | GET: `is_platform_admin`; PUT: nur `superadmin` | Relativer Pfad unter `MEDIA_ROOT`; keine Secrets; EXEMPT wie admin_users |
@ -30,18 +33,29 @@ Fortlaufend gemäß `ACCESS_LAYER_AND_GOVERNANCE_PLAN.md` Stufe AC.
| skills | `/api/skills*` | nein (global) | `require_auth` | je Endpoint | EXEMPT |
| maturity_models | Admin-Matrix | nein (global) | `require_auth` | Admin für Schreiben; `GET …/{id}` nur Portal-Admin | EXEMPT |
| matrix_stack_bundle | Export/Import Bundles | Plattform/Test | `require_auth` | Admin | EXEMPT |
| matrix_editor | `/api/admin/matrix-editor/*` (Export/Import Editor-Bundle) | Plattform | `require_auth` | nur `superadmin` | EXEMPT; globale Fähigkeitsmatrix ohne Mandantenkontext |
| import_wiki / import_wiki_admin | Wiki-Import | Werkzeug | `require_auth`/Admin | Admin | EXEMPT |
| ai_skill_retrieval_admin | `/api/admin/ai-skill-retrieval-profiles*` (CRUD) | Plattform | `require_auth` | nur `superadmin`; JSON `config` | EXEMPT wie `admin_users`; kein Vereinsbezug |
| ai_prompts_admin | `/api/admin/ai-prompts*` (Liste, Detail, PUT, Preview, Reset) | Plattform | `require_auth` | nur `superadmin` | EXEMPT; globale `ai_prompts` ohne Mandantenkontext |
| exercise_enrichment_admin | `/api/admin/exercise-enrichment/*` (Kandidaten, Preview, Apply) | Plattform | `require_auth` | nur `superadmin` | EXEMPT; plattformweite Übungsliste + Skill-Schreibung; kein TenantContext |
| admin_user_content | `/api/admin/user-content/*` (Meta, Nutzer-Summary, Items, PATCH, DELETE) | Plattform | `require_auth` | nur `superadmin` | EXEMPT; Moderation nutzerangelegter Inhalte inkl. privat; kein TenantContext |
**Legende:** Router auf der EXEMPT-Liste des Scripts sind globale oder Auth-only-Pfade; sobald ein Router Vereinsdaten oder Bibliotheks-Sichtbarkeit erhält, EXEMPT entfernen und `get_tenant_context` einführen.
**Pflege / Drift:** Änderungen an Mandanten, Governance (`visibility`/`club_id`) oder neuen inhaltsbezogenen Endpoints → eine Zeile in dieser Tabelle anpassen und `PRODUCTION_READINESS_AUDIT_2026-05.md` prüfen.
Letzte Änderung: 2026-05-07 — Upload-Dedupe Papierkorb 409 + `reactivate`; DELETE …/media nur Verknüpfung.
Letzte Änderung: 2026-06-06 — Superadmin `/api/admin/user-content/*` (Nutzer-Inhalte Moderation).
---
### Changelog (Fortführung)
- **2026-05-23:** Superadmin-API `exercise_enrichment_admin` (Batch-Übungs-Anreicherung KI) dokumentiert.
- **2026-05-30:** Superadmin-API `ai_prompts_admin` (`/api/admin/ai-prompts*`) dokumentiert.
- **2026-05-29:** Superadmin-API `ai_skill_retrieval_admin` (Retrieval-Profile) dokumentiert.
- **2026-05-22:** Übungs-KI-Endpunkte (Suggest/Regenerate) dokumentiert.
- **2026-05-13:** Dashboard-KPI-Endpunkt dokumentiert.
- **2026-05-07:** Legacy `GET/PUT /api/profile` auf Session-Profil gehärtet; OpenAPI/Health-Ready Produktionsdefaults; Security-Release-Tests + CI-Schritt `security_release_checks.py` — siehe `PRODUCTION_READINESS_AUDIT_2026-05.md`.
- **2026-05-07 (Phase 3):** CSP SPA (nginx); API `nosniff`-Middleware — siehe `PRODUCTION_READINESS_AUDIT_2026-05.md`.

View File

@ -0,0 +1,67 @@
# Umsetzungsplan KI bei Übungen (stufenweise, Driftschutz)
**Version:** 0.2
**Datum:** 2026-05-29
**Bezüge:** `functional/AI_EXERCISE_ASSISTANT_VISION.md` · **`working/AI_SKILL_RETRIEVAL_PROFILES_SPEC.md`** · `technical/KI_FEATURES_SPEC.md` · `technical/AI_PROMPT_SYSTEM_SPEC.md` · `technical/AI_TRAINING_PLANNING_CONCEPT.md` (§1.1 Ist-Stand)
---
## 1. Drift vermeiden verbindliche Regeln
1. **Spec vor Code:** Request/Response-Felder und Statuscodes an `KI_FEATURES_SPEC.md` ausrichten; Abweichungen zuerst Spec oder dieses Dokument anpassen.
2. **Prompts in der DB:** Keine produktionskritischen Prompt-Langtexte nur im Code; Defaults per **Migration** in `ai_prompts`, Anpassung durch Admins über vorgesehene Oberfläche (später) oder SQL.
3. **Skill-Retrieval-Profile:** Gewichte/Quotes in **`ai_skill_retrieval_profiles.config`** — Spezifikation `working/AI_SKILL_RETRIEVAL_PROFILES_SPEC.md`; kein zweites gleichzeitiges Truth-Repo im Sourcecode außer defensiver Fallback `_FALLBACK_RETRIEVAL_CONFIG` in `exercise_ai.py`.
4. **Stufen-Slugs & Intensität:** Nur **kanonische** Werte wie in `exercises.py` (`basis` … `optimierung`, `niedrig|mittel|hoch`); LLM-Ausgaben **normalisieren**, ungültige `skill_id` verwerfen.
5. **Kein stiller DB-Write:** KI liefert **Vorschläge**; Persistenz nur über bestehende **PUT/POST exercises** inkl. Trainer-Aktion (und optional `summary_ai_generated` / `ai_suggested` wie Spec).
6. **Mandant:** Übungsbezogene KI-Endpunkte nutzen `Depends(get_tenant_context)`; keine Ausnahme ohne Eintrag in `ACCESS_LAYER_ENDPOINT_AUDIT.md`.
7. **Schema:** Neue DB-Objekte nur nummerierte Migration **`backend/migrations/`** (aktuell bis **068**) und `DB_SCHEMA_VERSION` anheben.
---
## 2. Stufen (Releases)
| Stufe | Inhalt | Exit-Kriterium |
|-------|--------|------------------|
| **S0** | Dieses Dokument + Verweise konsistent | Review abgehakt |
| **S1** | Migration `ai_prompts` + Defaults `exercise_summary`, `exercise_skill_suggestions`; `exercises.summary_ai_generated` | Migrierte DB, App startet |
| **S2** | `httpx`-Client OpenRouter; Modul lädt Prompt, ersetzt Platzhalter, parst Antwort | Unit-/Smoke: 503 ohne Key |
| **S3** | `POST /api/exercises/ai/suggest`, `POST /api/exercises/{id}/ai/regenerate` | OpenAPI/Handtest mit Key |
| **S4** | Frontend: KI-Vorschlag, **Änderungsdialog** (Vorschau, Kurzfassung wählbar, Fähigkeiten pro Zeile an-/abwählbar), dann Übernahme ins Formular | Manuelle UX-Prüfung |
| **S4b** | **Skill-Retrieval:** Migration **`ai_skill_retrieval_profiles`**, `focus_areas_context` am **`POST …/ai/suggest`**, `exercise_ai` kontextbezogener Katalog (Gewichte, Caps, Keyword-Patches) | Migration 068 angelegt; Smoke mit Gewaltschutz / ohne Fokus |
| **S5** | (später) Auto-Fallback beim Speichern laut `KI_FEATURES_SPEC` §7 | Feature-Flag / Config |
| **S6** | (später) Zielausbau, Anleitung-only, Varianten, Admin-Masse laut Vision | Separate Epics |
**Aktueller Implementierungsstand:** **S4 + S4b** im Code (`exercise_ai` + Formular übermittelt `focus_areas_context`).
---
## 3. Implementierungs-Checkliste (Technik)
- [ ] `OPENROUTER_API_KEY` / `OPENROUTER_MODEL` in `.env.example` dokumentiert (bereits teils vorhanden prüfen).
- [ ] Fehlerbilder: `400` zu wenig Inhalt, `503` KI nicht konfiguriert, `502` Upstream-Fehler mit kurzer Message.
- [ ] Logging: **keine** vollständigen Prompts mit personenbezogenen Daten in Prod-Logs (optional DEBUG).
- [ ] Optional: Rate-Limit KI-Endpunkte (`slowapi`) nach Bedarf.
- [ ] `MODULE_VERSIONS["exercises"]` / Changelog bei API-Erweiterung setzen.
---
## 4. Changelog dieses Plans
- **2026-05-22:** Initial; S1S4 als erster Umsetzungspfad.
- **2026-05-22:** S1S4 im Code umgesetzt (Migration 067, `exercise_ai` + Router, Übungsformular); S5 weiter offen.
- **2026-05-29:** **S4b:** Migration **068**, `ai_skill_retrieval_profiles`; suggest `focus_areas_context`; Frontend sendet gesetzte Fokusbereiche; Spec `working/AI_SKILL_RETRIEVAL_PROFILES_SPEC.md`.
---
## 5. Umsetzungsstand (Zwischencheckpoint)
**Erledigt (2026-05-22):** Migration **`067_ai_prompts_exercise_assistant`**, **`openrouter_chat`**, **`exercise_ai`**, **`POST /api/exercises/ai/suggest`** und **`POST /api/exercises/{id}/ai/regenerate`**, Formular-Schaltflächen (Kurzfassung / Fähigkeiten / kombiniert).
**Erledigt (2026-05-29):** Migration **`068`** / Profil **`ai_skill_retrieval_profiles`** (Standard + Profil Gewaltschutz wenn `focus_areas.name` vorhanden); **`exercise_ai`** — Score/Kategorie-Zapfen/Text-Overlap/Keyword-Zuschläge; **API:** `ExerciseAiSuggestBody.focus_areas_context`; **Regenerate** nutzt DB-Fokuszeilen.
**Nacharbeit S4 UX:** Übernahmedialog **`ExerciseFormPageRoot`**: keine sofortige Überschreibung; Kurzfassung mit Vergleich + Checkbox; Fähigkeiten mit Neu/Aktualisierung, Checkboxen, „Alle auswählen/abwählen“; **`Escape`** schließt; KI-Schaltflächen blockiert solange Dialog offen.
**Offen nächste Schritte Pflege/Umsetzung:** weitere Retrieval-Profile (z.B. Karate-/Fitness-Schwerpunkt) per SQL später Admin-UI; optionales Feld **`skills.ai_context`** Kurzbeschreibung für KI; automatische KI beim Speichern (**S5**); Prompt-/Profil-Admin-UI ohne SQL; Rate-Limits.
**Bewusst noch nicht (`summary_ai_generated`):** zurücksetzen bei manueller Kurzfassung im UI; Admin-Pflege `ai_skill_retrieval_profiles`.

View File

@ -0,0 +1,124 @@
# Mehrstufige KI für Trainingsplanung Architektur-Vorschau (Anti-Refactoring)
**Version:** 0.1
**Datum:** 2026-05-22
**Status:** Planungs-/Architektur-Arbeitspapier (keine Implementierungspflicht)
**Ziel:** Für die **spätere** Planungs-KI bereits **Schnittstellen und Schichten** vorzeichnen, damit die **kleinere, starre** Übungs-KI nicht zur impliziten Vorlage für einen viel größeren Kopf wird — **ohne** jetzt eine Mitai-artige Workflow-Engine zu bauen.
**Update 2026-06-07:** Progressionsgraph startet **Phase F** (`planning_progression_roadmap.py`) — Roadmap-first, Workflow-lite. Siehe **`PLANNING_PROGRESSION_ROADMAP_SPEC.md`** und **`docs/architecture/PLANNING_KI_ROADMAP.md`**. Gruppenanalyse bleibt in der **Trainingsplanungs-Pipeline** (§3 S0S4), nicht im Graphen.
**Bezüge:** `technical/AI_TRAINING_PLANNING_CONCEPT.md` · `functional/AI_EXERCISE_ASSISTANT_VISION.md` · `technical/SKILL_SCORING_SPEC.md` · `functional/TRAINING_CURRICULUM_AND_GOVERNANCE_CONCEPT.md` (CURR-003) · Schwesterprojekt Mitai: `c:/dev/mitai-jinkendo` (Referenz: `prompt_executor`, `placeholder_resolver`, `workflow_*`**nicht** Pflicht-Port).
---
## 1. Zwei getrennte Produktlinien (bewusst entkoppelt)
| Linie | Rolle | Orchestrator |
|--------|--------|----------------|
| **Übungs-KI** | wenige Eingaben → Kurzfassung / Skills; **starrer** Ablauf (12 Calls), kleines Kontextfenster | z.B. `exercise_ai.py` (heute) |
| **Planungs-KI** | Gruppe, Zeit, Ziele, Historie, Katalogausschnitt, Phasen/Streams → **strukturierte Planelemente** | **eigenes** Modul + **mehrstufig** (siehe §3) |
**Regel:** Shared Library nur auf **niedriger Ebene** (`openrouter_chat`-Art: HTTP, Timeouts, Modellname, Fehler-Mapping) und **gemeinsame Prompt-Tabelle** `ai_prompts`. **Keine** Vermischung der Geschäftslogik „Übung erstellen“ mit „Einheit füllen“, um später keine Abhängigkeiten reißen zu müssen.
---
## 2. Konzeptioneller „Planungs-Graph“ (Daten, nicht zwingend Graph-DB)
Für die Planungs-KI ist ein **Graph als Denkmodell** hilfreich — technisch reicht meist **PostgreSQL + bestehende FKs** (+ optional `exercise_progression_graphs`):
**Knoten-Typen (Auszug):** `training_groups`, `training_units`, `training_unit_sections` / Items, `exercises`, `skills`, `training_framework_programs` / Slots / Goals, ggf. Nachbearbeitungs-/Debrief-Metadaten.
**Kanten-Typen (Auszug):**
- **Zeitliche Folge:** Einheiten einer Gruppe nach `planned_date` / Reihenfolge
- **Inhalt:** Section-Item → `exercise_id` (± Variante)
- **Ziele:** Slot-/Framework-Ziele, Kopf-Notizen, Trainer-Zieltexte
- **Progression:** Kanten aus `exercise_progression_graphs` (optional erweitern um „empfohlene Folge im Gruppenkontext“, bleibt Spekulationsfeld)
- **Skills:** bereits über `exercise_skills`; aggregiert über `skill_scoring`-Pfad
**Wichtig:** Für KI **nicht** einen Riesen-Graphen serialisieren, sondern **Projektionen** („letzte *N* Einheiten“, „Nachbarn im Progressionsgraph zu zuletzt verwendeten Übungen“, „Skill-Gap Heuristik“).
---
## 3. Mehrstufiger Prozess (Pflichtidee für Planungs-KI)
Statt einem Prompt „mach den ganzen Plan“ mehrere **Schritte mit kleinen, validierbaren Outputs**:
| Stufe | Beispiel-Aufgabe | Deterministisch möglich? | Typischer LLM-Einsatz |
|-------|-------------------|--------------------------|------------------------|
| **S0** | Governance + Filter + Historie + Slot-Ziele zusammenstellen | Ja (SQL/API) | Nein |
| **S1** | Kandidaten-Übungen auf TopK schrumpfen (Skills, Volltext, Score, Wiederholungsstrafe) | Teilweise | Optional Ranking |
| **S2** | Reihenfolge je Section / Phase unter Constraints (Aufwärmen, Graphen-Nachbarn) | Teilweise | Ja (auf kleiner Liste) |
| **S3** | Zeiten auf Section/Item vorschlagen oder Plausibilisieren | Teilweise | Ja |
| **S4** | Trainer-sprachliche Kurzbegründung / Alternativen | Nein | Ja |
**Zwischen jeder Stufe:** starkes **Schema / Validierung** (z.B. nur erlaubte `exercise_id`s, nur erlaubte Slot-Struktur zu Phasen/Streams). So bleibt das System auch bei Modell-Fehlern stabil.
---
## 4. Schnittstellen-Vorsorge im Code (ohne Big-Bang)
Minimal-Ausbaustufe später, die Refactoring vermeidet:
1. **`PlanningContextPack` (internes DTO)** — reines Python-`dict`/`dataclass` oder Pydantic: aggregierte, **tokenbewusst gekürzte** Ansicht (Gruppe, nächste Einheit-Ziele, Historie-IDs, TopK-Kandidaten, Constraints).
2. **`planning_ai_steps` als rein **funktionale** Pipeline** — jede Stufe `(context) → context` oder `(context) → partial_suggestion`; keine globale „Prompt-String-Bastelei“ überall im Router.
3. **Prompt-Slugs pro Stufe** in `ai_prompts` (analog Übung), z.B. `planning_rank_section_items`, `planning_explain_sequence`, mit **eigenem** Platzhalter-Katalog (nicht `{{skills_catalog}}` aus Übungen recyclen).
4. **Router** `training_planning.py` (oder neuer `planning_ai.py`): nur **dünne** HTTP-Schicht, ruft Orchestrator.
Optional **später**, wenn nötig: zweite Tabelle `ai_prompt_chains` oder externe Workflow-Definition — **erst** wenn 34 feste Stufen nicht mehr reichen. Mitai-Workflow-Engine dann **bewusste** Option, kein Default.
---
## 5. Kontextfenster und „Kaskade“
**Kerngedanke:** Je Stufe nur **neue** Information hinzufügen, die vorherige Stufen **ersetzen** oder **verdichten**, nicht duplizieren.
Beispiel:
- Stufe A (LLM oder Heuristik): „Priorisierte Skill-Ziele für diese Session“ (kurz)
- Stufe B: Top40 Übungen mit **einer** Zeile pro Übung
- Stufe C: Reihenfolge für 8 IDs + 2-Satz-Begründung
So bleibt dieselbe fachliche Tiefe erreichbar ohne Kontext-Explosion.
---
## 6. Schnittstellen zu bereits vorhandenen Bausteinen
- **`skill_profiles` / `skill-discovery`:** liefern **deterministische** Ziel-/Profil-Signale für S0/S1 (`SKILL_SCORING_SPEC.md`).
- **`training_planning_prefs`:** weiche Constraints (Tone, Dauer, Split-Vorlieben).
- **`exercise_progression_graphs`:** lokale Nachbarschaft um „zuletzt verwendet“.
- **Mitai-Referenz:** Platzhalter-Katalog + Preview-API als **Inspiration** für Admin-UX; Workflow-Graph nur wenn Shinkan **wirklich** viele verzweigte Pipelines braucht.
---
## 7. Was wir **nicht** jetzt tun müssen
- Keine zweite Graph-Datenbank nur für KI.
- Keine Workflow-UI-Kopie aus Mitai.
- Keine Vereinheitlichung der Übungs-KI mit Planungs-KI über einen „Mega-Orchestrator“.
---
## 8. Kurz-Checkliste „Refactoring vermeiden“ vor erster Planungs-KI-Zeile Code
- [ ] Eigenes Modulbaum-„Root“ für Planung (nicht `exercise_ai` erweitern).
- [ ] Prompt-Slugs mit **Planungs-**Präfix und **eigenem** Platzhalter-Set dokumentieren.
- [ ] Outputs pro Stufe **JSON-Schema** oder Pydantic validieren.
- [ ] Kandidatenlisten **immer** serverseitig auf erlaubte IDs begrenzen.
---
## 9. Progressionsgraph vs. Trainingsplanung (2026-06-07)
| Pipeline | Kontext | Orchestrator |
|----------|---------|--------------|
| **Progressionsgraph (F)** | Zieltext, N Steps, Semantic Brief | `planning_progression_roadmap.py` |
| **Trainingsplanung (G, später)** | Gruppe, Historie, Rahmen, Zeit | `planning_ai_steps` + ggf. Mitai Workflow |
---
## 10. Changelog
- **2026-06-07:** Verweis Phase F Roadmap-first; Abgrenzung Graphen/Planung.
- **2026-05-22:** Erstfassung als Vorschau-Dokument für mehrstufige Planungs-KI.

View File

@ -0,0 +1,121 @@
# KI Skill-Retrieval-Profile (`ai_skill_retrieval_profiles`)
**Version:** 0.1
**Datum:** 2026-05-29
**Status:** Umsetzung gestartet (Migration **068**)
**Ziel:** Für `POST /api/exercises/ai/suggest` (Skill-Katalogauszug) **Gewichte und Quoten** steuerbar machen:
- gebunden an **Übungs-Fokusbereich** (`focus_areas.id`),
- ein **Standardprofil** ohne Fokus,
- **optional zusammengeführte** Profile bei mehreren Fokusbereichen,
- **optional Keyword-Übersteuerungen** aus Ziel/Durchführung (z.B. Rollenspiel vs. Befreiung).
**Technische Basis:** Skills mit `skills.main_category_id``skill_main_categories.slug` (`karate` | `allgemeine`) und `skills.category_id``skill_categories.slug` (`kondition`, `selbstverteidigung`, …).
**Bezüge:** `.claude/docs/working/AI_EXERCISE_IMPLEMENTATION_PLAN.md` · `backend/exercise_ai.py`
---
## 1. Datenmodell
### Tabelle `ai_skill_retrieval_profiles`
| Spalte | Typ | Beschreibung |
|--------|-----|--------------|
| `id` | serial | Primärschlüssel |
| `focus_area_id` | int NULL FK → `focus_areas(id)` ON DELETE SET NULL | **`NULL`** nur für Standardeintrag möglich (siehe `is_default`) |
| `is_default` | boolean | Genau **eine** Zeile mit `true` |
| `name` | varchar | Kurzer Name (Admin später) |
| `description` | text | Hinweise für Pflege |
| `active` | boolean | Nur aktive werden geladen |
| `config` | jsonb | Siehe §2 |
**Constraints / Indizes**
- Eindeutig: `(focus_area_id)` WHERE `focus_area_id IS NOT NULL`
- Eindeutig: `(is_default)` WHERE `is_default = true`
---
## 2. JSON-Konfiguration `config.version = 1`
Alle Schlüssel **optional**; fehlende Werte fallen auf **einprogrammierten Fallback** in `exercise_ai.py` zurück (entspricht bisher grob „neutral“).
### 2.1 Gewichtungen (Ranking)
| Schlüssel | Typ | Bedeutung |
|-----------|-----|------------|
| `main_slug_weights` | `object[str, float]` | Multiplikator pro Hauptkategorie-Slug (`karate`, `allgemeine`) |
| `category_slug_weights` | `object[str, float]` | Multiplikator pro `skill_categories.slug` |
Basis-Score (vereinfacht):
`(importance oder 3) × main_w × cat_w × text_overlap_bonus × importance_multiplier`
### 2.2 Kapazitätsbegrenzung (Liste)
`_MAX_SKILLS_CATALOG_LINES` (aktuell **240**) Zeilen Gesamt:
| Schlüssel | Typ | Bedeutung |
|-----------|-----|------------|
| `category_max_share` | `object[str, float]` | Max. Anteil dieser **Unterkategorie** am Endergebnis (01), z.B. `{ "kondition": 0.25 }` |
| `main_min_share` | `object[str, float]` | Mindest-Zielanteil Hauptkategorie beim **Auswahl-Greedy** (weich; Rest nach Score aufgefüllt) |
### 2.3 Text / Token-Sparen
| Schlüssel | Typ | Standard | Bedeutung |
|-----------|-----|----------|------------|
| `description_plain_max_len` | int | 160 | Gekürzte Beschreibung pro Zeile |
| `karate_relevance_max_len` | int | **0** oder 80 | **`0`** = Feld `karate_relevance`/`relevance_level` in der Promptzeile **weglassen** |
### 2.4 Keyword-Overrides (optional)
Liste `keyword_overrides`: jedes Element:
```json
{
"keywords_any": ["befreiung", "haltegriff"],
"case_insensitive": true,
"patch": {
"category_slug_weights": { "selbstverteidigung": 2.5 },
"category_max_share": { "koordination": 0.1 }
}
}
```
Textsuche in verkettetem Korpus **Titel, Ziel, Durchführung, Focus-Hint** (bereits plaintext). Reihenfolge: erst Basis-Profile zusammenmergen, dann **alle treffenden Overrides**`patch`Objekte **flach zusammenführen** (Gewichte multiplikativ übereinander, Caps den strengsten Wert nehmen aktuelle Implementierung im Code dokumentiert).
---
## 3. Mehrere Fokusbereiche auf der Übung
Request-Body: `focus_areas_context: [{ "focus_area_id": n, "is_primary": bool }, …]`
**Aktuelle Merge-Strategie (v1):** Profile laden → **gleichgewichtete Mittelwert-Bildung** der numerischen Gewichte / Caps (implementiert für `main_slug_weights`, `category_slug_weights`, `category_max_share`, `main_min_share`, `*_max_len`). Anschließend **Keyword-Overrides** anwenden.
**Primär-Fokus:** Im Frontend soll die **primäre** Zeile aus `focus_areas_multi` **zuerst** in der Liste stehen; die Merge-Strategie kann später zu „Primär dominate“ erweitert werden.
Ohne Kontext oder ohne Treffer auf aktive Profile: **nur Standardprofil** (`is_default`).
---
## 4. Seed-Daten (Migration)
- **`is_default=true`:** ausgewogene Standard-Gewichte, moderate Caps auf `kondition`/`koordination`, Karate-Relevanz gekürzt.
- **`Gewaltschutz`:** `focus_area_id` per `(SELECT id FROM focus_areas WHERE name = 'Gewaltschutz' LIMIT 1)` — höhere Gewichte für `kognition`, `psychische_faehigkeiten`, `soziale_faehigkeiten`, `selbstverteidigung`; gedrosseltes `kondition`/`koordination`; `karate_relevance_max_len`: 0; Keyword-Patches wie oben können nachgeschärft werden.
Weitere Profile (Karate-Schwerpunkt etc.) später per Admin-SQL oder UI.
---
## 5. API
`ExerciseAiSuggestBody` erweitert um **`focus_areas_context`** (Liste). Feld **`focus_area_hint`** bleibt für den **Prompt-Kontext** (bestehende Prompts).
`POST …/ai/regenerate` nutzt gespeicherte `exercise_focus_areas` zur gleichen Retrieval-Logik wie Suggest.
**Pflege der Profile:** Superadmin ohne Mandantenwahl — **`GET|POST /api/admin/ai-skill-retrieval-profiles`**, **`GET|PUT|DELETE /api/admin/ai-skill-retrieval-profiles/{id}`** (`routers/ai_skill_retrieval_admin.py`); Web-UI Superadmin unter **`/admin/ai-skill-retrieval`**.
## 6. Changelog
- **2026-05-29:** Superadmin-Pflege-Endpoints + UIRoute dokumentiert (`/admin/ai-skill-retrieval`).
- **2026-05-29:** Erstellt; gekoppelt an Migration **068** und erste `exercise_ai`-Integration.

View File

@ -0,0 +1,106 @@
# KombinationsAblaufprofil — Zeitmodell, ArchetypVorgaben, Umsetzung
**Zweck:** Fach-/Technik-Brücke zwischen Wunschbild („kein NutzerJSON“, globale und slotbezogene Eckwerte, ArchetypStrukturen) und bestehendem Speicher **`method_profile` (JSON)** + **`planning_method_profile`** auf Planungszeilen.
**Bezüge:** `.claude/docs/functional/Shinkan Trainingsmodule Kombinationsuebungen Spezifikation V2.md`6.3 / §8.3); Frontend `CombinationMethodProfileEditor`, `combinationMethodProfileUi.js`; ArchetypIDs siehe Backend `COMBINATION_ARCHETYPE_IDS` / Frontend `COMBINATION_ARCHETYPE_OPTIONS`.
---
## 1. Grundprinzipien
| Prinzip | Beschreibung |
|--------|--------------|
| **Kein PflichtJSON für Trainer** | Alle trainertypischen Pflegepfade nur über geführte Felder + ArchetypVorschlagsknöpfe. |
| **JSON bleibt Transport** | Persistenz geschieht weiter in `method_profile` / Kopie in `planning_method_profile`; **kanonische Schlüssel** werden hier und in Codekommentaren festgehalten. |
| **Archetyp = Struktur + Defaults** | Wechsel des Archetyps soll (optional/togglebar) Grundwerte oder typische Relationen vorbelegen können — keine stillen Überschreibungen ohne Hinweis. |
| **`free_method_block` = Maximale Freiheit** | Entspricht „maximaler Konfiguration“: alle relevanten TimingDimensionen über UI, insbesondere **pro Slot**; keine impliziten stationären Constraints. |
---
## 2. Kanonisches ZeitSchema (`timing_schema`)
**Empfohlene Versionierung im Objekt:**
- **`timing_schema: 1`** — sobald neue globale/strukturierte Felder aktiv genutzt werden (Pilot; UI kann ohne Migration starten durch parallele Schlüssel).
### 2.1 Globalebene (`method_profile`)
| Feld (Pilot) | Semantik |
|----------------|----------|
| `timing_schema` | `1` wenn Block unten aktiv |
| `intro_sec` oder bestehend `block_intro_sec` | einmalige Einführung/Demo am Block |
| `rounds` (bzw. bei Intervallen `interval_rounds` — Angleich später) | komplette Durchläufe des Musters |
| *Planned totals* nur **berechnete Anzeige** in UI, optional persistiert z.B. `planned_total_duration_min_hint` später |
Relationen **Zwischen Arbeit und Pause** können als Schnellwahl gesetzt werden (kein eigener PersistErzwingTyp nötig), indem konkrete Sekunden geschrieben werden.
### 2.2 Slots (`slot_profiles_v1`)
Array synchron zu `slot_index`; fehlende Einträge = „nicht gefüllt / aus globalen Eckdaten ableiten wo sinnvoll“.
ObjektShape (Sekunden, ganze Zahlen ≥ 0):
```json
{
"slot_index": 0,
"load_sec": 40,
"consecutive_reps": 1,
"intra_rep_rest_sec": 10,
"transition_after_sec": 15
}
```
| Feld | Bedeutung |
|------|------------|
| `load_sec` | Belastungsdauer „an der Station“. |
| `consecutive_reps` | Wiederholungen pro „Serie“ bzw. ohne Wechsel zu **neuem** Stationsinhalt (oft 1). |
| `rep_series_count` | Anzahl Serien à `consecutive_reps` bei rep/manual; Standard **1**, ArchetypVorgabe möglich (**`ARCHETYPE_DEFAULT_REP_SERIES_COUNT`**). Persistiert für rep/manual ab 1. |
| `intra_rep_rest_sec` | Pause zwischen den FolgeWiederholungen bzw. **zwischen Serien** (nur sinnvoll, wenn `rep_series_count` ≥ 2 im Modus `rep`/`manual`; sonst Wechselzeit `transition_after_sec` nutzen). |
| `transition_after_sec` | Pause / Wechsel **zur nächsten** Station oder zum nächsten logischen Block. |
**Hinweis:** Bestehende Archetyp„flachen“ Schlüssel (`work_seconds`, `transition_seconds`, …) werden schrittweise **nicht zerstört**, sondern Slots ergänzen; Konvergenz (eine Darstellung zu v1) kann Phase 4 sein.
---
## 3. Archetyp → typische Schnellwahl (ÜberblicksMatrix)
| Archetyp | Globale Schnellwahl (Beispiele) | Slots |
|----------|---------------------------------|-------|
| `circuit_rotate_time` | Arbeit; Rotation „≈ Arbeit“ oder „Pause 2/3 Arbeit“ bezogen auf RundPausen/Rotation wo im UI dokumentiert | sinnvoll ab **timing_schema** geführt |
| `time_domain_interval` | Pause = Arbeit; Pause = 2/3 Arbeit (auf `rest_seconds`↔`work_seconds`) | optional |
| `sequence_linear` | Einführung + grobe Sek./Station | **slot_profiles_v1** priorisiert |
| `circuit_all_parallel` | Erklärzeit, gemeinsamer Start | Slots optional |
| `pair_superset` | Wechsel A↔B, Arbeit je Seite (+ später erweiterbar) | 2SlotFokus |
| `free_method_block` | alle globalen Slots optional | **Pfad für maximale Flex** |
| `station_parcour` | Reihenfolge freiFlag bestehend | pro Station Verweilen sinnvoll |
**Pyramidal (später):** neue ArchetypID **`pyramid_interval`** o. ä. oder Flag `pyramid_recovery_rule` mit Regelwerk „Pause abhängig von letzter Belastung“ — **explizit out of scope** bis Regeln feststehen.
---
## 4. UXNormen
- **Trainingsplanung** (`plannerMode`): **keine** RohJSONOberfläche.
- **Übungsformular**: RohJSON nur wenn `allowExpertJson === true` (Default false; später z.B. Superadmin/Dev).
- **CoachingAnsicht**: nur **wirksame** Zahlen aus Snapshot/Katalog (Merge wie in `comboPlanningMethodProfile.js`); **globale** Profilwerte mit **fachlichen Labels** (`describeGlobalComboProfile`), nicht nur Rohschlüsseln.
### 4.1 Stand Umsetzung (App **0.8.110**, Kurz)
- **`slot_profiles_v1`** und Schnellwahlen Zirkel/Intervall im geführten Editor umgesetzt; **`advance_mode`** je Slot (Zeit / ZielWdh. / Coach).
- **Phase 2** dieses Plans (Modal „ArchetypVorlage anwenden?“, nichtdestruktives Merge über alle Slots) — **noch offen** (Fachspez §10.6, Umsetzungsplan Paket **4f**).
---
## 5. Phasen (Implementierung)
| Phase | Inhalt |
|-------|--------|
| **1 (jetzt)** | SlotZeilenUI über `slot_profiles_v1`; SchnellwahlRatios für `circuit_rotate_time` + `time_domain_interval`; `plannerMode` ohne JSON; `allowExpertJson` default false |
| **2** | Beim Archetypwechsel **optionales** Modal „ArchetypVorlage anwenden?“ mit nichtdestruktivem Merge |
| **3** | Geplante **Gesamtzeit** konsistent rechnerisch (Summe Slots × Runden + Global) mit Transparenz in UI |
| **4** | Konsolidierung flacher Schlüssel → **`timing_schema`** v1only im Editor |
| **5** | Pyramide / adaptive Recovery |
---
**Pflege:** Änderungen an Schlüsseln oder Phasen hier und in Anhang A der Fachspez mitziehen.

View File

@ -0,0 +1,68 @@
# Superadmin: Übungs-Anreicherung per KI
Stand: 2026-05-23 · App 0.8.178
## Zweck
Plattform-weites Werkzeug für Superadmins, um Übungen (typisch `draft`, ohne Skills) **batchweise** per KI mit Fähigkeiten anzureichern und kontrolliert auf `in_review` zu setzen.
Verbessert indirekt die Planungs-KI (`POST /api/planning/exercise-suggest`), die gegen Skill-Profile rankt — unvollständige `exercise_skills` führen dort zu Volltext-dominiertem Ranking.
## UI
- Route: `/admin/exercise-enrichment` (nur Superadmin)
- Admin-Menü: „Übungs-Anreicherung“
## API
Prefix: `/api/admin/exercise-enrichment`
| Methode | Pfad | Beschreibung |
|---------|------|--------------|
| GET | `/candidates` | Paginierte Kandidaten (Filter: status, visibility, focus_area, without_skills, with_ai_suggested_skills, include_club, search) |
| POST | `/preview` | Dry-Run — `{ exercise_ids[], modes: { skills, summary }, merge_mode }` |
| POST | `/apply` | `{ items: [{ exercise_id, merged_skills }], merge_mode, set_status }` |
Auth: `require_auth` + `is_superadmin`**kein** `TenantContext` (EXEMPT, siehe ACCESS_LAYER_ENDPOINT_AUDIT.md).
## KI
Wiederverwendet `run_exercise_form_ai_suggestion` → Prompts `exercise_skill_suggestions` (MVP Pflicht), optional `exercise_summary`. Skill-Katalog via `build_contextual_skills_catalog_block` / `ai_skill_retrieval_profiles`.
## Merge-Modi (Skills)
- `additive` (Default): manuelle Skills bleiben; KI ergänzt neue; bestehende `ai_suggested`-Links werden aktualisiert
- `replace_ai_only`: nur `ai_suggested=true` entfernen, dann KI-Set anwenden
- `replace_all`: alle Skills ersetzen (explizit)
## Defaults
- Kandidaten: **Status** primär (Default `draft`); Sichtbarkeit Default **`private`**, wählbar bis „Alle“
- Skill-Merge Default: **`replace_all`** (alle Skills KI-neu, `ai_suggested=true` — unterscheidbar von manuell)
- Nach Apply: `set_status=in_review` (nie automatisch `approved`)
- Batch: keine Gesamtgrenze (bis 10.000 IDs); **Analyze** + explizite Nutzerbestätigung
- **Preview:** max. **3 Übungen/HTTP-Request** (parallel LLM), Frontend chunked — vermeidet Gateway-504 (~60s Fritz!Box)
- **Apply:** HTTP-Chunks à 25 (nur DB, kein LLM)
## Inhalte (modular)
| Modus | Prompt | Apply-Felder |
|-------|--------|--------------|
| Skills | `exercise_skill_suggestions` | `exercise_skills` inkl. Intensität, required/target_level, `ai_suggested` |
| Summary | `exercise_summary` | `summary`, `summary_ai_generated=true` |
| Anleitung | `exercise_instruction_rewrite` | `goal`, `execution`, `preparation`, `trainer_notes` |
## API (ergänzt)
| Methode | Pfad | Beschreibung |
|---------|------|--------------|
| GET | `/candidate-ids` | Alle IDs zum Filter (Select-all) |
| POST | `/analyze` | `{ exercise_ids[], modes }` → Kosten-Schätzung vor Start |
## Keine Migration
Bestehende Spalte `exercise_skills.ai_suggested` reicht; kein Enrichment-Log in MVP.
## Tests
`backend/tests/test_exercise_enrichment_admin.py` — 403, Merge-Logik, Status draft→in_review.

View File

@ -0,0 +1,58 @@
# Rahmenprogramm: Filter, Dauer, Fähigkeiten-Schwerpunkte (Roadmap)
**Stand:** 2026-05-20
**Status:** Phase 1 umgesetzt; Phase 3 v1.0 umgesetzt (regelbasiert); Phase 2 teilweise offen
## Phase 1 (umgesetzt)
### Listen-Anzeige Session-Dauer
- **GET `/api/training-framework-programs`:** `session_duration_min`, `session_duration_max` (aus Blueprint-`training_units.planned_duration_min`), `goal_titles_agg`, ID-Arrays für Katalog-M:N.
- **UI:** Rahmenprogramm-Liste, Trainingsplanung (Einheiten-Liste/Kalender), Import-Dialog (Programm + pro Slot).
### Import-Filter (clientseitig)
- Textsuche (Titel, Beschreibung, Ziele, Katalog-Namen)
- Fokusbereich, Trainingsart, Zielgruppe (Checkboxen, Katalog-API)
- Ziel-Session-Dauer in Minuten (±10 Min Toleranz gegen Min/Max der Slots)
**Grenze:** Entwicklungsziele sind **freie Texte** pro Rahmen (`training_framework_goals.title`), keine kontrollierte Taxonomie → Filter nur Volltext, keine homogene „Ziel-Tags“-Liste.
## Phase 2 (empfohlen, ohne KI)
| Kriterium | Datenquelle heute | Verbesserung |
|-----------|-------------------|--------------|
| Fokusbereich / Stil / Trainingsart / Zielgruppe | M:N am Rahmenkopf | bereits filterbar |
| Entwicklungsziele | Freitext-Ziele | Optional: Ziel-Vorlagen-Katalog oder Tags (Migration) |
| Session-Dauer | `planned_duration_min` pro Slot | erledigt |
| Fähigkeiten-Schwerpunkt | noch nicht | siehe Phase 3 |
**API-Erweiterung (optional):** `GET /api/training-framework-programs?focus_area_id=&training_type_id=&duration_min=` serverseitig — sinnvoll ab >50 Rahmen in der Bibliothek.
## Phase 3 — Fähigkeiten aus Übungen (umgesetzt v1.0)
**Spec:** `.claude/docs/technical/SKILL_SCORING_SPEC.md`
- Gewichtetes Profil: Rahmenprogramm (gesamt + pro Slot), Trainingsmodul, Progressionsgraph
- `GET /api/skill-discovery/suggestions?skill_ids=…` für Bibliotheks-Vorschläge
- UI: Profil-Panels in Editoren + Tab „Planungs-Vorschläge“ auf der Fähigkeiten-Seite
- **Kein** automatisches Überschreiben der Stammdaten-Fokusbereiche
### Variante B — KI-Zusammenfassung (OpenRouter, optional, offen)
1. Input: Titel Rahmen, Ziele (Text), Liste Übungstitel + Dauer + vorhandene Skill-Namen.
2. Prompt: strukturiertes JSON (`suggested_focus_areas[]`, `skill_emphasis[]`, `rationale_de`).
3. Speichern als `ai_context_summary` (Version, Modell, Timestamp) — **nur Vorschlag**, manuelle Bestätigung vor Übernahme in Stammdaten.
**Vorteil:** natürliche Schwerpunkte auch bei unvollständigen Skill-Links.
**Risiko:** Halluzination, Kosten, Datenschutz (Vereinsdaten in Prompt).
### Empfehlung
Zuerst **Variante A** für Listen/Filter und Abgleich mit manuell gesetzten Fokusbereichen; KI nur als **„Vorschlag generieren“-Button** im Rahmen-Editor, wenn Regelwerk und Katalog-Zuordnung zu dünn sind.
## Offene Produktfragen
1. Soll Filter **UND** (alle Kriterien) oder **ODER** (mindestens eines) sein? — Import aktuell **UND**.
2. Rahmen mit **unterschiedlichen** Slot-Dauern: Liste zeigt MinMax; Filter „90 Min“ trifft Range.
3. Sollen homogenisierte **Entwicklungsziel-Tags** ein eigener Katalog werden (Admin), analog `target_groups`?

View File

@ -1,11 +1,14 @@
# Session Handover (Verweis)
**Dieses Dokument ist veraltet.** Der aktuelle Entwicklungsstand und die Handover-Basis stehen hier:
**Aktuelle Handover-Basis (Stand Implementierung + Medien):**
👉 **`docs/HANDOVER.md`** (Projektroot: `c:\Dev\shinkan-jinkendo\docs\HANDOVER.md`)
👉 **`docs/HANDOVER.md`** (Projektroot)
Dort: Fähigkeits-/Reifegrad-Stand (Bindings, Resolve, Export/Import, Matrix-Stack), Verweise auf `.claude/docs` für Anforderungen, und **nächste Priorität Übungen (UI/CRUD/Medien)**.
Dort: **Medien-Archiv/Medienbibliothek (Ist)**, **geplante Inline-Medienverlinkung (§11 Spec)**, Reifegrad/Matrix, Rahmenprogramm, nächste sinnvolle Arbeitspakete für neue Sessions.
**Projektstatus-Kachel:** `.claude/docs/PROJECT_STATUS.md`
**Medien Single Source of Truth:** `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`
---
*Historischer Inhalt aus April 2026 wurde durch `docs/HANDOVER.md` ersetzt.*
*Die historische „Übungen Lücke“-Fassung (2026-04) ist überholt; Medien-UI und Archiv sind seit 0.8.41+ weitgehend umgesetzt — Details `docs/HANDOVER.md` §4.*

View File

@ -0,0 +1,125 @@
# Parallele Trainingsstreams — Ist-Analyse und risikoarmer Umsetzungsplan
**Status:** Stufe A (Analyse/Plan, ohne produktive Umsetzung in jener Session)
**Stand:** 2026-05-14
**Verbindliche fachliche Basis:** `.claude/docs/functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md`, `.claude/docs/technical/PARALLEL_TRAINING_STREAMS_SPEC.md`
Dieses Dokument **persistiert** die strukturierte Prüfung der realen Codebasis (`training_planning.py`, `training_framework_programs.py`, `training_unit_sections`/`items`, Frontend Planung/Run/Coach) und den empfohlenen Implementierungspfad.
---
## 1. Zusammenfassung
- Plan-Inhalt pro Einheit ist heute **eine flache Liste** `training_unit_sections` mit **`UNIQUE (training_unit_id, order_index)`** (Migration 031) und `training_unit_section_items`; zentral: **`_fetch_sections`**, **`_replace_unit_sections`**, **`_hydrate_training_unit_payload`** in `backend/routers/training_planning.py`.
- Parallele Phasen/Streams **passen** zu den Produktregeln (ein Kalendertermin, N Streams, je Miniplan), sind im Schema aber **nicht** abbildbar ohne Erweiterung und **ohne Auflösung** des globalen `order_index`-Modells.
- **Empfehlung:** **Normalisierte** Tabellen `training_unit_phases`, `training_unit_parallel_streams`, erweiterte `training_unit_sections` mit FK auf Phase bzw. Stream, **partielle Unique-Indizes** statt `UNIQUE (training_unit_id, order_index)` für alle Sektionen.
- **Blocker im Code:** u. a. `POST /api/training-units/{id}/apply-training-module` mit **`section_order_index` global pro Einheit** (`_resolve_training_unit_section_id`).
- **Nicht persistiert an anderer Stelle:** Erste Fassung existierte nur als Chat-Antwort; dieses File ist die **kanonische** Arbeitskopie im Repo.
---
## 2. Ist-Analyse (kurz)
### Datenbank
- `training_unit_sections`: u. a. `training_unit_id`, `order_index`, `UNIQUE (training_unit_id, order_index)`.
- `training_unit_section_items`: Übung/Notiz, `planning_method_profile` (Kombi), `source_training_module_id`.
### Backend (`training_planning.py`)
- `_replace_unit_sections`: DELETE aller Sektionen der Einheit + INSERT (vollständiger Ersetzungsbaum).
- `_sections_clone_payload` + `_copy_blueprint_into_scheduled_unit`: tiefe Kopie für `from-framework-slot`.
- `_flatten_exercises_from_sections`: flaches `exercises` am Unit-Payload.
- `apply_training_module_to_training_unit`: Sektion per **`section_order_index`** global.
### Rahmen (`training_framework_programs.py`)
- Blueprint-`training_units` pro Slot; gleiche `_replace_unit_sections`-Semantik.
### Frontend
- Planung: `TrainingPlanningPageRoot.jsx`, `TrainingUnitSectionsEditor`, `buildSectionsPayload` / `normalizeUnitToForm`.
- Run: `TrainingUnitRunPage.jsx` — Fortschritt `sessionStorage` Key `sj_training_run_checked_${unitId}`.
- Coach: `TrainingCoachPage.jsx``flattenPlanTimeline` (linearer Ablauf).
### Tests
- Kaum Abdeckung für Plan-Inhalt; vorhanden u. a. `test_training_unit_assignments.py` (Merge Co-Trainer, ohne DB), `test_training_units_list_keyset.py` (Keyset-Validierung).
---
## 3. Technische Optionen und Empfehlung
| Option | Kurz |
|--------|------|
| A JSONB nur auf `training_units` | Niedriges DDL-Risiko, hohes Drift-/Wartungsrisiko — **nicht empfohlen** |
| B Normalisiert Phasen/Streams | **Empfohlen** — eine Wahrheit, saubere Kopie, Rahmen kompatibel |
| C Nur UI-Konvention ohne DB | Widerspricht Produkt — **abgelehnt** |
---
## 4. Migrations- und Kompatibilitätsstrategie
- Default **`whole_group`Phase** für alle bestehenden Einheiten; alle bisherigen Sektionen erhalten `phase_id`.
- Unique-Regel: **pro Phase** bzw. **pro Stream** `order_index` eindeutig (partielle Unique-Indizes).
- API optional: zusätzlich abgeleitetes flaches `sections` für Übergang — Entscheidung je nach Consumer (praktisch nur dieses Frontend).
---
## 5. API- / Frontend-Hotspots
- `GET`/`PUT` `/api/training-units/{id}`: verschachtelte `phases` / `streams` / `sections` / `items`.
- `POST .../apply-training-module`: Kontext **Phase/Stream + Sektionsindex im Träger**.
- Run/Coach: stream-spezifischer Fortschritt; `flattenPlanTimeline` phase-aware oder pro Stream.
---
## 6. Implementierungspakete (Überblick)
0. Spike DDL + Contract-Doku
1. **Erledigt (2026-05-14):** Migration **063** + `training_planning`: Phasen/Streams-Schema, Backfill whole_group, `GET` mit `phases`, Legacy-`sections`-PUT unverändert (eine whole_group-Phase).
2. PUT mit echten Parallelphasen / Streams, `apply-training-module` mit Stream-Kontext, `from-framework-slot`-Kopie
3. Planung UI
4. Run + Coach
5. Co-Trainer pro Stream
6. MVP+ (Duplizieren, Verschieben, „nur meine Spur“)
---
## 7. Risiken
- Migration Unique-Constraint / bestehende Daten.
- Regression Run/Coach / Dashboard-Joins (meist unkritisch, solange `training_unit_id` auf Sektionen bleibt).
- Rahmen-Blueprints: gleiche Struktur wie Kalender-Einheiten anstreben (oder bewusst zweite Phase nur Kalender).
---
## 8. Offene Produkt-/Technikfragen
- Rahmen-Blueprint parallel im MVP oder erst nach Kalender-Einheit?
- Semantik `exercises`-Flatlist bei Parallelität.
- Merge-Regel `assistant_trainer_profile_ids` Kopf vs. Stream-Zuweisungen.
---
## 9. Verweise
- Fachkonzept: `.claude/docs/functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md`
- Technische Spec (Entwurf): `.claude/docs/technical/PARALLEL_TRAINING_STREAMS_SPEC.md`
- Domänenüberblick: `.claude/docs/functional/DOMAIN_MODEL.md` (Abschnitt Parallele Streams)
- `./PARALLEL_TRAINING_STREAMS_PREREQ_PROMPT.md`**Prompt** für Folgesession (Performance/Wartung/Vorbereitung)
---
## 10. Vorbereitende Arbeiten (Session 2026-05-13)
Ohne produktives Parallel-Feature, nur Risikoabbau und Transparenz:
- **`training_planning.py`:** Lesepfad `_fetch_sections` in SQL-Konstanten + `_fetch_section_items_for_section` / `_hydrate_section_item_combination_slots` strukturiert; `_replace_unit_sections` delegiert an `_insert_one_replacement_section`; `_hydrate_training_unit_payload` dokumentiert.
- **Tests:** `tests/test_training_planning_sections_pure.py` (flatten, ohne DB); `tests/test_training_planning_sections_integration.py` (Roundtrip replace↔fetch bei `TRAINING_PLANNING_INTEGRATION=1`).
- **Frontend:** Kurzkommentare an Planung (`TrainingPlanningPageRoot`, `buildSectionsPayload`), Run, Coach, `flattenPlanTimeline` — Anbindungspunkte für spätere Phase/Stream-Logik.
- **DOMAIN_MODEL:** UNIQUE-Hinweis und „keine Migration ohne Freigabe“.
**Empfohlene nächste Schritte:** Pakete **0** (DDL/Contract festzurren) und **1** (Schema + Migration + hydrate/replace laut Plan Abschnitt 46) in einer dedizierten Feature-Session; danach Paket **2** (PUT/Module/Clone).
---

View File

@ -0,0 +1,41 @@
# Prompt: Vorbereitungs- / Vorarbeit-Session (Performance & Wartung) für „Parallele Trainingsstreams“
**Kontext:** Du arbeitest in **Shinkan Jinkendo** (`c:\Dev\shinkan-jinkendo`). Das Feature **Parallele Trainingsstreams / Breakout** ist **inhaltlich** spezifiziert; eine **Ist-Analyse und ein risikoarmer Umsetzungsplan** liegen **persistiert** in:
- `.claude/docs/working/PARALLEL_TRAINING_STREAMS_ANALYSIS_AND_IMPLEMENTATION_PLAN.md`
- Fachlich: `.claude/docs/functional/PARALLEL_TRAINING_STREAMS_CONCEPT.md`
- Technik-Entwurf: `.claude/docs/technical/PARALLEL_TRAINING_STREAMS_SPEC.md`
**Deine Rolle:** Du hast bereits **Refaktorierungs- und Wartungsaufgaben** mit Fokus **Performance, Lesbarkeit und Testbarkeit** durchgeführt. In **dieser** Session geht es **nicht** darum, das komplette Parallel-Feature zu bauen, sondern um **Vorarbeiten („Prerequisites“)**, die die geplante Komplexitätsauflösung **sicherer und billiger** machen.
## Ziele
1. **Lesepfad Planung vereinheitlichen:** `backend/routers/training_planning.py` ist zentral für `_fetch_sections`, `_replace_unit_sections`, `_hydrate_training_unit_payload`, Klonen, Blueprint-Kopie, `apply-training-module`. Prüfe, ob klar abgegrenzte Hilfsfunktionen (ohne Verhaltensänderung) die **nächste** große Änderung erleichtern — **keine** Feature-Logik für Phasen/Streams hinzufügen, nur Struktur/Tests/Docs wenn nötig.
2. **Test-Lücken schließen (minimal, hoher Nutzen):** Heute fehlen **DB/API-Tests** für kritische Pfade (`_replace_unit_sections` Roundtrip, `from-framework-slot` Struktur-Kopie, optional `apply-training-module`). Ergänze **kleine, deterministische** Tests (pytest mit DB, falls im Projekt üblich), ohne riesige Fixtures.
3. **Frontend-Schneidstellen markieren:** kurze Kommentare oder ein **Working-Doc-Update**, wo `TrainingPlanningPageRoot`, `buildSectionsPayload`, `TrainingUnitRunPage`, `TrainingCoachPage` + `trainingPlanUtils.flattenPlanTimeline` später angebunden werden — **kein** großes UI-Rewrite.
4. **Migrations-Sicherheit:** Dokumentiere in **einem Absatz** im `ANALYSIS`-Dokument oder hier, welche **Unique-Constraints** (`training_unit_sections`: `UNIQUE (training_unit_id, order_index)`) die Parallelität blockieren — **ohne** sie schon zu ändern, außer es ist Teil einer **explizit** freigegebenen ersten Migration.
5. **Performance nur berührensensible Stellen:** Einzelabruf `GET /api/training-units/{id}` wird mit mehr JOINs kommen. Falls du **jetzt** N+1 oder redundante Arbeit in `_fetch_sections` siehst und das **risikoarm** verbesserbar ist, nur mit **Messpunkt/Messvorstellung** (kein unnötiger Micro-Optimismus).
## Leitplanken
- **Stabilität vor Geschwindigkeit:** Keine Änderung, die bestehende Einheiten, Rahmen-Blueprints oder Run-Modus bricht.
- **Keine pauschalen Refactors:** Nur Änderungen mit **klarem** Träger für das Parallel-Feature oder mit **Test-Regression-Schutz**.
- **Regeln:** `.claude/rules/ARCHITECTURE.md`, `CODING_RULES.md`, Zugriffsschicht wo relevant.
## Erwartete Ausgabe
1. Kurze **Liste erledigter Vorarbeiten** (Dateien, was warum).
2. **Empfohlene Reihenfolge** für die **nächste** Session, die Phasen/Streams **implementiert** (verweis auf `PARALLEL_TRAINING_STREAMS_ANALYSIS_AND_IMPLEMENTATION_PLAN.md` Pakete 02).
3. Falls nichts Sinnvolles ohne Feature-Branch riskiert werden kann: **explizit** „keine Code-Änderung“, nur Risiko-Notiz.
## Optionaler Startbefehl
```
Lies zuerst:
.claude/docs/working/PARALLEL_TRAINING_STREAMS_ANALYSIS_AND_IMPLEMENTATION_PLAN.md
dann backend/routers/training_planning.py (Abschnitte um _fetch_sections, _replace_unit_sections).
```

View File

@ -0,0 +1,529 @@
# Planungs-KI: Übungssuche & Kontext für Neu-Anlage
**Version:** 0.2
**Datum:** 2026-05-23
**Status:** P0P2 ✅ · Phase A/B/B2 ✅ · **Phase C1C3 ✅** · **Phase E ✅** (Semantik + Pfad-QA)
**Bezüge:** `AI_PLANNING_KI_MULTISTAGE_FORECAST.md` · `AI_PROMPT_TARGET_ARCHITECTURE.md` · `SKILL_SCORING_SPEC.md` · `TRAINING_FRAMEWORK_SPEC.md` §3 (Progressionsgraph)
---
## 1. Ziel
Trainer in der **Trainingsplanung** sollen Übungen finden oder anlegen können mit natürlichen Anfragen wie:
- „Vertiefung zu Übung XY“
- „Nächste sinnvolle Übung im Progressionsgraph Z“
- „Baut auf der bisherigen Planung auf — Reaktionsschnelligkeit mit Partnern“
- **Preset:** „Schlage mir die nächste Übung vor“
**Suche** (Bibliothek) und **Neu mit KI-Assistent** (Anlage) nutzen dasselbe **`PlanningExerciseContextPack`** — unterschiedliches Ergebnis (Treffer vs. Entwurf).
---
## 2. Architektur (Mehrstufig)
| Stufe | Name | Technik | P0 |
|-------|------|---------|-----|
| **S0** | Kontext-Pack | SQL/API, deterministisch | ✅ |
| **S1a** | Intent strukturieren | LLM `planning_exercise_search_intent` (Szenario-Pipeline) | ✅ P1 |
| **S1b** | Hybrid-Retrieval | Score: Volltext + Graph + Skills + Plan + **Profil** | ✅ |
| **S1b+** | Profil-Vorselektion | `ExerciseMatchProfile` × `PlanningTargetProfile` | ✅ `profile_v1` |
| **S1c** | Rerank + Begründung | Optional LLM `planning_exercise_search_rank` | Regelbasierte `reasons[]` |
| **S2** | Neu-Anlage | Bestehende `suggestExerciseAi` + Pack als Zusatzkontext | Später |
Zwischen jeder Stufe: **nur erlaubte `exercise_id`s** (Governance / Sichtbarkeit).
---
## 3. Intent-Typen
| `intent_hint` | Bedeutung | Retrieval-Gewichtung (P0) |
|---------------|-----------|---------------------------|
| `suggest_next` | Nächste Übung (Default bei leerer/kurzer Query) | Progression + Skill-Overlap + Plan-Kontinuität |
| `progression_next` | Explizit Graph-Folge | Progression hoch |
| `deepen_exercise` | Vertiefung zu Anker-Übung | Skill-Overlap hoch, ähnlicher Fokus |
| `continue_plan_goal` | Auf bisherigen Plan aufbauen | Plan-Kontinuität, Wiederholungsstrafe |
| `free_search` | Freitext / Stichwort | Volltext hoch |
**S1a (später):** Freitext → JSON `{ intent, skill_hints[], requires_partner, level_hint, … }` validiert per Pydantic.
**P0:** `intent_hint` vom Client oder Keyword-Heuristik auf `query`.
---
## 4. PlanningExerciseContextPack (S0)
Serverseitig aus Request + DB (tokenbewusst für spätere LLM-Stufen):
| Feld | Quelle | UI-Chip |
|------|--------|---------|
| `unit_id`, Titel, `group_id`, Gruppenname | `training_units` + `training_groups` | Gruppe · Einheit |
| `section_order_index`, Abschnittstitel | `training_unit_sections` | Abschnitt |
| `planned_exercise_ids[]` | Items der Einheit (Reihenfolge) | „N Übungen im Plan“ |
| `anchor_exercise_id`, Titel | Request oder letzte Übung vor Einfügepunkt | Anker |
| `anchor_skill_ids[]` | `exercise_skills` | (intern) |
| `progression_graph_id` | Request oder **Auto-Match** vom Anker (sichtbarer Graph mit passenden Ausgangskanten) | Graph |
| `progression_graph_name`, `progression_graph_auto_resolved` | Response `context_summary` | Graph (auto) |
| `anchor_exercise_variant_id` | Request / Abschnitt-Item / DB | (intern) |
| `progression_successor_ids[]` | `exercise_progression_edges` ab Anker (variantenbewusst, Migration **034**) | (intern) |
| `progression_successor_variants` | `to_exercise_variant_id` pro Nachfolger | (intern) |
| `group_recent_exercise_ids[]` | Letzte Einheiten derselben Gruppe | Wiederholungsstrafe |
| `framework_slot_notes` | Rahmen-Slot falls `framework_slot_id` | (später) |
**Berechtigung:** `get_tenant_context` + `_assert_training_unit_permission` wie `GET /training-units/{id}`.
---
## 5. Hybrid-Retrieval (S1b, P0)
Kandidaten: sichtbare Übungen (`library_content_visibility_sql`), ohne `archived`, max. ~400 (recent).
**Score** (01, gewichtet nach Intent):
```
score = w_ft * fulltext_rank
+ w_prog * progression_hit
+ w_skill * skill_jaccard(anchor, candidate)
+ w_plan * plan_affinity
+ w_profile * profile_match(exercise, target)
+ w_repeat * (candidate in unit_plan ? -1 : 0)
+ w_group_repeat * (candidate in group_recent ? -0.5 : 0)
```
**`profile_match`** (01): siehe §12§13 — Katalog-Dimensionen + Skill-Gewichte + Skill-Gap.
**`reasons[]`** (regelbasiert, Deutsch): z. B. „Nachfolger im Progressionsgraph“, „Fähigkeiten passen zur Anker-Übung“, „Fokusbereich passend zum Planungsziel“, „Deckt Skill-Lücke im bisherigen Plan“, „Volltext-Treffer“.
---
## 6. API
### `POST /api/planning/exercise-suggest`
**Body:**
```json
{
"unit_id": 123,
"section_order_index": 0,
"phase_order_index": null,
"parallel_stream_order_index": null,
"anchor_exercise_id": 456,
"anchor_exercise_variant_id": 12,
"progression_graph_id": 7,
"query": "Schlage mir die nächste Übung vor",
"intent_hint": "suggest_next",
"limit": 20,
"exercise_kind_any": ["simple"]
}
```
**Response:**
```json
{
"context_summary": {
"unit_title": "…",
"group_name": "…",
"section_title": "Hauptteil",
"planned_count": 4,
"anchor_title": "Partner-Fangspiel"
},
"target_profile_summary": {
"sources": ["framework_catalog", "current_unit_plan", "anchor_exercise"],
"focus_areas": ["Reaktion & Abwehr"],
"top_skills": [{ "skill_id": 12, "name": "Reaktionsgeschwindigkeit", "weight": 1.0 }],
"has_skill_gap": true
},
"retrieval_phase": "profile_v1",
"intent_resolved": "suggest_next",
"hits": [
{
"id": 99,
"title": "…",
"summary": "…",
"score": 0.78,
"reasons": ["Nachfolger im Progressionsgraph", "Fokusbereich passend zum Planungsziel"],
"focus_area": "…"
}
]
}
```
**Modul:** `backend/planning_exercise_suggest.py` · `backend/planning_exercise_profiles.py` · Router `backend/routers/planning_exercise_suggest.py`
---
## 7. Frontend
| Ort | Verhalten |
|-----|-----------|
| `ExercisePickerModal` | Prop `planningContext` → Planungs-API statt reiner `listExercises`; Kontext-Chips; `reasons` unter Treffer |
| `TrainingUnitEditPage` | `planningContext` aus Einheit + Picker-Ziel (Anker = letzte Übung im Abschnitt) |
| **`ExercisesListPageRoot`** | Schalter **„Neu mit KI-Assistent“**: Planungs-KI-Suche (frei, ohne `unit_id`) + Neuanlage im Modal; **„+ Neu“** ausgeblendet |
| Rahmen / Kombi-Formular | analog, sobald `unit_id` / Slot-Blueprint bekannt |
| Übungsliste (ohne KI-Schalter) | weiter Volltext |
**Zweites Suchfeld** im Picker: Query = Volltext + ergänzender Begriff (ODER in P0 als Konkatenation an Backend).
---
## 8. Neu-Anlage (Anbindung, Phase P1)
Wenn `hits` leer oder Trainer wählt „Mit KI anlegen“:
- `planning_context` im Request-Body → `planning_context_json` in Übungs-Prompts (Migration **085**); Pfad-Builder + Picker ✅ **0.8.208**
- Kurzbeschreibung optional leer (freier Vorschlag) oder aus Intent/Skizze
---
## 9. Phasen-Roadmap
| Phase | Inhalt | Status |
|-------|--------|--------|
| **P0** | Context-Pack, Hybrid-Score, API, Picker in Planung | ✅ |
| **P0.1** | `ExerciseMatchProfile` / `PlanningTargetProfile`, `profile_v1` | ✅ |
| **P1** | Szenario-Pipeline + LLM Query-Intent → Erwartungsprofil | ✅ |
| **P2 / B2** | LLM-Rerank bei engem Top-Feld (max. 2 Calls) | ✅ |
| **P3** | Skill-Discovery / Framework-Ziele im Pack | 🔲 |
| **A** | Voll-Library Hybrid-Ranking | ✅ **0.8.177** |
| **B** | Text-Signale guidance/Rahmen-Ziele | ✅ **0.8.181** |
| **C1** | Graph auto-match + variantenbewusste Nachfolger | ✅ **0.8.183** |
| **C2** | Varianten in Trefferliste / Picker | ✅ **0.8.184** |
| **C3** | Graph-Builder (Ziel → Pfad → speichern) | ✅ **0.8.185** |
| **E** | Semantik-Schicht + Pfad-QA (Lücken/Brücken/LLM-QS) | ✅ **0.8.186** |
| **E2** | Pfad-Neuordnung + KI-Lückenfüller | ✅ **0.8.187** |
| **D** | Neu-Anlage: `planning_context` an `suggestExerciseAi` (Migration **085**) | ✅ **0.8.208** |
---
## 10. Changelog
- **2026-05-23:** Phase C1 — Graph auto-match, variantenbewusste Nachfolger (`planning_exercise_progression.py`).
- **2026-05-23:** Phase B2 — Rerank bei engem Top-Feld; Phase B — Text-Signale; Phase A — Voll-Library (siehe §17§19).
- **2026-05-22:** Erstfassung; P0 API + Planungs-Picker.
- **2026-05-22:** P0 implementiert (`planning_exercise_suggest.py`, Router, Picker); unsaved Formular-Plan noch nicht an API (nur persistierte Einheit).
- **2026-05-22:** P0.1 — `planning_exercise_profiles.py`, Profil-Score in Hybrid-Retrieval, `retrieval_phase: profile_v1`, `target_profile_summary`.
- **2026-05-22:** P2 — LLM-Rerank optional (`include_llm_rank`); Client `planned_exercise_ids[]`; Prompt Migration 072.
---
## 11. Bekannte Lücken & Backlog
- **Ungespeicherte Plan-Änderungen:** ✅ Client übergibt `planned_exercise_ids[]` aus Formular (TrainingUnitEditPage).
- **Progressionsgraph-ID:** ✅ Auto-Match vom Anker (**C1**); manuelle Auswahl in UI noch offen.
- **Anker-Variante:** ✅ Client + DB (**C1**); Picker wählt Variante bei Treffer (**C2** — Dropdown + Graph-Vorschlag).
- **Graph-Builder (C3):** Ziel → Pfad vorschlagen → in Graph speichern — ✅ **0.8.185**
- **Varianten-Suche:** Library-Picker nutzt `include_variants`; Planungs-KI rankt primär **Übungsebene** — Varianten-Expansion nur gezielt (**C2**).
- **Enrichment:** Superadmin-Tool für Skills; Datenqualität der Bibliothek entscheidend für Profil-Score.
- **LLM-Intent:** ✅ P1 Szenario-Pipeline + `planning_exercise_search_intent` (Migration 073).
- **Preset + LLM:** ✅ Erwartungs-LLM (074) bei Planungsbezug; Preset ohne Plan = kein Erwartungs-LLM.
---
## 16. Szenario-Pipeline & Query-Erwartungsprofil (P1)
Komplexe Planungsanfragen brauchen **Schritte vor** dem Profil-Match — nicht jede Query ist gleich.
### 16.1 Szenario-Klassen
| `scenario_kind` | Typische Anfrage | LLM Intent? |
|-----------------|------------------|-------------|
| `preset_next` | „Nächste Übung vorschlagen“ (Preset) | Erwartungs-LLM (074) wenn Planungsbezug |
| `progression` | Progressionsgraph / Pfad | Ja (wenn Freitext) |
| `deepen` | Vertiefung Anker | Ja |
| `continue_plan` | Auf bisherigen Plan aufbauen | Ja |
| `additive_constraint` | Plan **+** Zusatz (z. B. Schnellkraft) | Ja |
| `free_search` | Offene Stichwortsuche | Ja |
**Routing:** `planning_exercise_target_pipeline.classify_planning_scenario()``should_run_llm_intent_pipeline()`.
### 16.2 Pipeline (Reihenfolge)
```
S0 Kontext-Pack
→ Heuristik-Intent + Szenario
→ [optional] LLM planning_exercise_search_intent
→ Basis PlanningTargetProfile (Rahmen, Plan, Anker, Gap)
→ Merge Query-Overlay (Katalog-IDs aus Hints)
→ Hybrid-Retrieval + Profil-Score
→ [optional] LLM-Rerank
```
Module: `planning_exercise_target_pipeline.py` · `planning_exercise_intent.py`
### 16.3 API (Erweiterung)
| Request | Default | Bedeutung |
|---------|---------|-----------|
| `include_llm_intent` | `true` | LLM nur wenn Szenario ≠ preset_next und Query nicht leer |
| Response | Bedeutung |
|----------|-----------|
| `scenario_kind` | Szenario-Klasse |
| `query_intent_summary` | intent, llm_applied, rationale, skill_hints_resolved |
| `intent_heuristic` | Heuristik vor LLM |
| `retrieval_phase` | z. B. `profile_v1+query_intent+llm_rank` |
**Prompt 073:** `planning_exercise_search_intent` — Ausgabe JSON mit `skill_hints`, `focus_hints`, `emphasis` (`additive`|`replace`).
---
## 15. LLM-Rerank (P2)
**Request:**
| Feld | Typ | Default | Bedeutung |
|------|-----|---------|-----------|
| `planned_exercise_ids` | `int[]` | — | Optional: Reihenfolge aus Formular (überschreibt DB-Plan) |
| `include_llm_rank` | `bool` | `true` (Client) | Backend gated (B2): Rerank nur bei engem Top-Feld, max. 2 LLM-Calls |
**Response:**
| Feld | Wert |
|------|------|
| `retrieval_phase` | `profile_v1` oder `profile_v1+llm_rank` |
| `llm_rank_applied` | `true` wenn LLM erfolgreich sortiert hat |
| `hits[].llm_rank` | optional: Position nach LLM (1…n) |
**Fallback:** Kein API-Key, inaktiver Prompt oder Parse-Fehler → Hybrid-Reihenfolge unverändert, `llm_rank_applied: false`.
**Prompt:** Migration **072**, Slug `planning_exercise_search_rank` — Kandidaten als JSON mit Titel, summary, goal (Plaintext), skills; Ausgabe `{ ranked_ids, reasons }`.
---
## 12. ExerciseMatchProfile & PlanningTargetProfile (Phase 1)
Ziel: deterministische Vorselektion über **Profil-Dimensionen** statt nur Titel/Jaccard.
### 12.1 ExerciseMatchProfile (pro Übung)
| Feld | Quelle |
|------|--------|
| `focus_area_ids` | `exercise_focus_areas` (Primary = 1.0, sonst 0.85) |
| `style_direction_ids` | `exercise_style_directions` |
| `training_type_ids` | `exercise_training_types` |
| `target_group_ids` | `exercise_target_groups` |
| `skill_weights` | `exercise_skills` × Intensitäts-Multiplikator (`skill_scoring._skill_link_multiplier`) |
Bulk-Lader: `load_exercise_match_profiles_bulk(cur, exercise_ids)`.
### 12.2 PlanningTargetProfile (Planungsziel)
Zusammensetzung aus mehreren Quellen (`sources[]`):
| Quelle | Inhalt |
|--------|--------|
| `framework_catalog` | Fokus/Stil/Trainingsstil/Zielgruppe aus `training_framework_program_*` |
| `framework_slot_skill_profile` | Skill-Profil des Slot-Blueprints (`profile_for_occurrences`) |
| `framework_overall_skill_profile` | Fallback: alle Blueprint-Einheiten des Rahmens |
| `current_unit_plan` | Skill-Profil der bereits eingeplanten Übungen dieser Einheit |
| `anchor_exercise` | Katalog + Skills der Anker-Übung (Intent-abhängig) |
| `skill_gap_vs_plan` | `target_skills plan_skills` (normalisiert, Schwelle > 0.08) |
Builder: `build_planning_target_profile(cur, unit=…, planned_exercise_ids=…, anchor_exercise_id=…, intent=…)`.
Rahmen-Anbindung über `unit.framework_slot_id` oder `origin_framework_slot_id`.
---
## 13. Profil-Score (Formeln)
**Gewichtete Überlappung** (Katalog + Skills):
```
overlap(a, b) = Σ min(a[k], b[k]) / Σ max(a[k], b[k])
```
**Skill-Gap-Abdeckung:**
```
gap_coverage(gap, candidate) = Σ min(gap[k], candidate[k]) / Σ gap[k]
```
**Profil-Score** (intent-gewichtet, Summe Dimensionen = 1.0):
```
profile_score = w_focus * overlap(focus)
+ w_style * overlap(style)
+ w_tt * overlap(training_type)
+ w_tg * overlap(target_group)
+ w_skill * overlap(skill_weights)
+ w_gap * gap_coverage(skill_gap)
```
Intent-Gewichte (Auszug): `deepen_exercise` → Skill hoch; `continue_plan_goal` → Gap hoch; `free_search` → Gap + Skill moderat.
Scorer: `score_exercise_against_target(exercise_profile, target_profile, intent=…) → (score, reasons[])`.
---
## 14. Hybrid + Profil (P0.1)
Im Hybrid-Score kommt **`w_profile * profile_score`** hinzu (Intent-abhängig ~0.150.35). Jaccard auf Anker-Skills bleibt parallel (schneller Anker-Fokus).
**Response-Felder:**
| Feld | Bedeutung |
|------|-----------|
| `retrieval_phase` | `"profile_v1"` — Phase-1 aktiv, kein LLM-Rerank |
| `target_profile_summary` | Lesbare Kurzinfo für UI-Chips (Fokus, Top-Skills, Quellen) |
**Phase 2 (P2 / B2):** siehe §15 und §18 — `include_llm_rank: true` vom Client, Backend entscheidet.
---
## 17. Phase A — Voll-Library-Ranking (0.8.177)
- Kein OR-Profil-Pool (~500 Übungen) mehr.
- Alle sichtbaren Übungen (bis 8000) werden hybrid gescored (`fetch_all_visible_exercise_rows` + `rank_visible_library_hits`).
- API: `full_library_ranked: true`, `retrieval_phase` enthält `+full_library+`.
---
## 18. Phase B / B2 — Text-Signale & Rerank-Gates (0.8.1810.8.182)
**B — Text-Signale (`planning_exercise_text_signals.py`):**
- `section_guidance_notes`, Rahmen-Ziele/Notizen → Skill-/Katalog-Gewichte ohne LLM.
- `requires_partner` aus Intent filtert Kandidaten.
- `retrieval_phase +text_signals`.
**B2 — Rerank bei unklarem Ranking:**
- `hybrid_ranking_ambiguous(hits)` (Top-4-/Top-10-Gap).
- Rerank auch nach Erwartungs-/Intent-LLM, wenn Scores eng beieinander.
- Budget: max. **2** LLM-Calls (Profil + optional Rerank).
---
## 19. Phase C1 — Progressionsgraph im Planungskontext (0.8.183)
**Modul:** `planning_exercise_progression.py`
### Auto-Match Graph
Wenn `progression_graph_id` fehlt und Anker-Übung gesetzt: sichtbarer Graph mit passender `next_exercise`-Kante vom Anker (variantenbewusst). Bevorzugung: variantenspezifische Kanten > Anzahl Kanten.
### Variantenbewusste Nachfolger (Migration 034)
Generische Kante (`from_exercise_variant_id IS NULL`) gilt für jeden Anker; variantenspezifische Kante nur bei passender Anker-Variante.
Treffer: optional `hits[].suggested_variant_id`.
### Request / Response
| Feld | Bedeutung |
|------|-----------|
| `anchor_exercise_variant_id` | Request — Variante der Anker-Übung |
| `progression_graph_name` | Response — Name des (auto-)Graphs |
| `progression_graph_auto_resolved` | Response — Auto-Match aktiv |
---
## 20. Phase C2 — Varianten in Treffern (0.8.184) ✅
- API: `variants[]`, `suggested_variant_name` pro Treffer (Batch aus `exercise_variants`).
- **`ExercisePickerModal`:** Dropdown pro Treffer; Graph-`suggested_variant_id` vorausgewählt; Übernahme setzt `exercise_variant_id`.
- **`hydrateExercisePlanningRow`:** übernimmt `exercise_variant_id` / `suggested_variant_id` in die Planungszeile.
---
## 21. Phase C3 — Graph-Builder (0.8.185) ✅
**API:** `POST /api/planning/progression-path-suggest`
| Feld | Bedeutung |
|------|-----------|
| `query` | Ziel / Entwicklungsrichtung (Freitext, min. 3 Zeichen) |
| `max_steps` | 210, Default 5 |
| `progression_graph_id` | optional — Graph-Kontext für Nachfolger ab Schritt 2 |
| `include_llm_intent` | LLM nur Schritt 1 (Budget) |
**Response:** `steps[]` mit `exercise_id`, `variant_id`, `title`, `reasons`, `variants`; `retrieval_phase: …+path_builder`.
**Algorithmus:** Iterativ Hybrid-Ranking — Schritt 1 aus Zielprofil, Folgeschritte mit Anker = letzte Übung, ohne Duplikate.
**UI:** `ExerciseProgressionPathBuilder` im Progressionsgraph-Panel — Review, Varianten, `POST …/edges/sequence`.
---
## 22. Phase E — Semantik-Schicht + Pfad-QA (0.8.186) ✅
### Semantic Brief (`planning_exercise_semantics.py`)
Parallel zum Katalog-Overlay — **nicht ersetzend**:
| Feld | Bedeutung |
|------|-----------|
| `primary_topic` | z. B. `mae geri` |
| `must_phrases` / `exclude_phrases` | Phrasen-Match in Titel/Ziel/Varianten |
| `development_arc` | einstieg → … → perfektion |
| `semantic_strength` | 01 — steuert dynamisches Blend im Hybrid-Score |
| `retrieval_query` | fokussierte Volltext-Query (nicht ganzer Satz) |
Optional LLM: Prompt `planning_exercise_query_semantics` (Migration **075**).
**Hybrid-Score:** neuer Term `w_semantic * semantic_score` — Profil/Volltext werden bei hoher `semantic_strength` relativ abgeschwächt.
### Pfad-QA (`planning_exercise_path_qa.py`)
Nach Pfad-Bildung:
1. **Lücken-Messung** zwischen benachbarten Schritten (Skill-Jaccard + Semantik zum erwarteten Phasen-Segment)
2. **Brücken-Übungen** bei großen Lücken (zusätzliche Schritte, markiert `is_bridge`)
3. **LLM-QS** (Prompt `planning_exercise_path_qa`): Reihenfolge, Themen-Abdeckung, Empfehlungen
**API-Erweiterung** `progression-path-suggest`: `include_path_qa`, `include_llm_path_qa` · Response: `semantic_brief_summary`, `path_qa`.
**Pfad-Schritte:** Semantic Brief + Entwicklungsphase in **allen** Schritten (nicht nur Schritt 1).
### Phase E2 (0.8.187)
- **LLM-QS → Neuordnung:** `ordered_step_indices` im Prompt `planning_exercise_path_qa` (Migration **076**)
- **KI-Lückenfüller:** `planning_exercise_path_ai_fill.py``is_ai_proposal` wenn Bibliothek keine Brücke liefert
- Request: `include_path_reorder`, `include_ai_gap_fill`
---
## 23. Phase E3 (0.8.203) ✅
- Off-Topic aus Pfad entfernen; `gap_fill_offers` mit `goal_for_ai`; voller KI-Call im UI (kein Pre-Vorschlag)
- Migration **077** `suggested_new_exercises` im Pfad-QS-Prompt
---
## 24. Phase F — Roadmap-first Progressionsgraph (0.8.204217) ✅
**Entscheidung:** Progressionsgraph plant **vom Ziel rückwärts** (Roadmap → Stufenspezifikation → Bibliothek/KI). **Keine Gruppenanalyse** — die gehört zur Trainingsplanung.
**Ist-Stand (vollständig):** `docs/architecture/PLANNING_PROGRESSION_GRAPH_KI.md`
**Spec:** `working/PLANNING_PROGRESSION_ROADMAP_SPEC.md` · **Roadmap:** `docs/architecture/PLANNING_KI_ROADMAP.md`
| Teil | Modul / API |
|------|-------------|
| Pipeline | `planning_progression_roadmap.py` (Workflow-lite) |
| Match | `planning_exercise_path_builder.py``roadmap_first`, `roadmap_override` |
| Skills | `planning_skill_expectations.py` — pro Stufe + Pfad |
| Gap-KI | `planning_exercise_form_context.py`, `planning_exercise_path_ai_fill.py` |
| Persistenz | `planning_roadmap` JSONB (Migration **088**) |
| API | `progression-path-suggest`, `PUT` Graph, `POST …/edges/sequence` |
| Prompts | **078/079/087** — Slugs nur in `ai_prompts` |
| UI | `ExerciseProgressionPathBuilder`, `ExerciseGapFillPrepModal` |
**Graph-Bias:** `progression_graph_id` bevorzugt **bestehende Nachfolger** ab Schritt 2 (Gewicht ~410 %), baut aber **keinen** Pfad aus vorhandenen Knoten — siehe Ist-Doku §5.
**Mitai Workflow-Engine:** bewusst **nicht** jetzt — Pipeline workflow-ready für spätere Anbindung.
---
## 25. Backlog (offen)
Siehe priorisierte Liste in **`docs/architecture/PLANNING_PROGRESSION_GRAPH_KI.md`** §10:
1. UI-Wizard (Progressionsgraph) — separater Chat
2. Graph-Erweiterungsmodus (Start ab Knoten)
3. Trainingsplanung Phase G (Gruppenkontext, `planning_skill_expectations`)
4. Kontext auf allen Pfad-Schritten in der UI
5. Enrichment / Prompt-Feintuning
6. Mitai Workflow-Engine (langfristig)

View File

@ -0,0 +1,209 @@
# Planungs-KI — Progressions-Roadmap (Phase F)
**Version:** 0.1
**Datum:** 2026-06-07
**Status:** VERBINDLICHE ZIELARCHITEKTUR — **F0F9 umgesetzt** (0.8.217)
**Geltungsbereich:** **Progressionsgraph** (`exercise_progression_graphs`) — **ohne** Gruppenanalyse
**Ist-Stand (Module, API, Graph-Verhalten, Persistenz):** `docs/architecture/PLANNING_PROGRESSION_GRAPH_KI.md`
**Bezüge:**
`working/PLANNING_EXERCISE_SUGGEST_CONTEXT.md` · `working/AI_PLANNING_KI_MULTISTAGE_FORECAST.md` · `technical/AI_TRAINING_PLANNING_CONCEPT.md` · `technical/AI_PROMPT_TARGET_ARCHITECTURE.md` · `docs/architecture/PLANNING_KI_ROADMAP.md` · `docs/HANDOVER.md`
---
## 1. Entscheidung (2026-06-07)
### 1.1 Problem
Der Pfad-Builder (Phase C3/E) ist **retrieval-first**: Zieltext → N Übungen aus der Bibliothek → QS nachbessern. Das entspricht nicht der menschlichen Planung (Ziel → Roadmap → Stufenspezifikation → Übung).
### 1.2 Festlegung
| Thema | Entscheidung |
|--------|----------------|
| **Progressionsgraph** | **Roadmap-first** — Phasen A→B→C, dann Bibliothek (D), dann Feinausplanung (E) |
| **Gruppenanalyse** | **Nicht** in der Graphen-Pipeline — erst bei **Trainingsplanung** (Einheit/Rahmen) |
| **Mitai Workflow-Engine** | **Nicht** jetzt portieren — **Workflow-lite** (`PlanningProgressionPipeline`), später workflow-ready |
| **Ein Mega-Prompt** | **Verboten** — validierte Artefakte pro Phase |
### 1.3 Abgrenzung Trainingsplanung
```
Progressionsgraph-Pipeline Trainingsplanungs-Pipeline (später)
───────────────────────── ───────────────────────────────────
Ziel + N Major Steps Gruppe + Historie + Termin + Rahmen
Kein Gruppenkontext Kontext-Pack S0 (AI_PLANNING_KI_MULTISTAGE_FORECAST)
Curriculum / Technikpfad Session-Füllung / Reihenfolge / Zeiten
```
---
## 2. Menschliches Vorbild → Phasen
| Mensch | Phase | Output-Artefakt | LLM |
|--------|-------|-----------------|-----|
| Startpunkt + Zielzustand | **A** Zielanalyse | `goal_analysis` | Optional (klein) |
| Zwischenziele, gewichten, auf N reduzieren | **B** Roadmap | `roadmap` (`micro_objectives[]`, `major_steps[N]`) | Ja |
| Belastung, Übungstyp, Lernziel je Stufe | **C** Stufenspezifikation | `stage_specs[]` | Teilweise |
| Bibliothek / Brücke | **D** Match | `step_matches[]` oder `gaps[]` | Nein (Retrieval) |
| Skizze + Feinplan | **E** Übungsentwurf | bestehend `suggestExerciseAi` | On-demand |
**Phase B** = Kern: 812 `micro_objectives` → Konsolidierung → exakt `max_steps` `major_steps`.
---
## 3. Pipeline-Orchestrator (Workflow-lite)
Modul: **`backend/planning_progression_roadmap.py`**
```python
ctx = ProgressionRoadmapContext(goal_query=..., max_steps=N, semantic_brief=...)
ctx = phase_a_goal_analysis(ctx) # deterministisch + optional LLM
ctx = phase_b_roadmap(ctx) # micro → major
ctx = phase_c_stage_specs(ctx) # je major_step
# Phase D/E: bestehende path_builder / retrieval / ai_fill — speisen von ctx.major_steps
```
Jede Phase: `(ctx) → ctx`, Zwischenergebnisse in API-Response für **Human-in-the-loop** (Roadmap-Review vor Übungs-Match).
**Später:** jede Phase = Workflow-Knoten (Mitai-kompatibel), keine API-Änderung an Artefakten.
---
## 4. JSON-Artefakte (Pydantic)
### 4.1 `goal_analysis` (Phase A)
```json
{
"primary_topic": "Mae Geri",
"start_assumption": "Grundkenntnisse der Standführung, keine Perfektion",
"target_state": "Sicherer, präziser Mae Geri unter Belastung und in Anwendung",
"success_criteria": ["saubere Kammerhaltung", "Hüftführung", "Kime am Zielpunkt"],
"constraints": { "partner_required": false, "equipment": [] }
}
```
### 4.2 `roadmap` (Phase B)
```json
{
"micro_objectives": [
{ "id": "m1", "phase": "grundlage", "title": "Stellung und Kammerhaltung", "weight": 0.9, "depends_on": [] },
{ "id": "m2", "phase": "vertiefung", "title": "Hüft- und Kniekoordination", "weight": 0.85, "depends_on": ["m1"] }
],
"major_steps": [
{
"index": 0,
"phase": "grundlage",
"learning_goal": "Stabile Mae-Geri-Grundstellung",
"consolidates": ["m1"],
"rationale": "Einstieg ohne Perfektionsdruck"
}
],
"consolidation_notes": ["Perfektion mit Anwendung zusammengeführt"]
}
```
### 4.3 `stage_spec` (Phase C, je Major Step)
```json
{
"major_step_index": 2,
"learning_goal": "…",
"load_profile": ["präzision", "koordination"],
"exercise_type": "kihon_einzel",
"success_criteria": ["…"],
"anti_patterns": ["reine Kraftübung ohne Technikbezug"]
}
```
---
## 5. API (schrittweise)
### 5.1 Erweiterung `POST /api/planning/progression-path-suggest`
| Feld (neu) | Default | Bedeutung |
|------------|---------|-----------|
| `roadmap_first` | `false` → später `true` | Roadmap-Pipeline vor Retrieval |
| `include_roadmap_preview` | `true` wenn `roadmap_first` | Artefakte A/B/C in Response |
**Response (neu):**
```json
{
"progression_roadmap": {
"goal_analysis": { },
"roadmap": { },
"stage_specs": [ ],
"pipeline_phase": "roadmap_v1"
},
"steps": [ ]
}
```
**Übergangsphase (0.8.204):** `include_roadmap_preview=true` liefert Roadmap **parallel** zum bestehenden retrieval-first Pfad — UI kann Roadmap reviewen, Schritte bleiben vorerst retrieval-basiert.
**Zielphase (F2):** `roadmap_first=true` — Retrieval pro Major Step aus `stage_specs`, nicht mehr iterativ „beste nächste Übung“.
### 5.2 Prompt-Slugs — nur in `ai_prompts`, nie im Code
**Regel:** Prompt-**Texte** leben ausschließlich in der Tabelle `ai_prompts` (Superadmin bearbeitbar, Vorschau, `openrouter_model` pro Zeile). Python referenziert nur **Slugs** (`PROMPT_SLUG_*` in `planning_progression_roadmap.py`). Kein verstecktes Hardcoding von Templates.
| Slug | Phase | Migration |
|------|-------|-----------|
| `planning_progression_start_target` | Start/Ziel | **087** |
| `planning_progression_goal_analysis` | A | **078** |
| `planning_progression_roadmap` | B | **078** |
| `planning_progression_stage_spec` | C | **079** |
**API:** `include_llm_roadmap` (Default `true`) — lädt Prompts via `load_and_render_ai_prompt`. Bei Fehler/kein OpenRouter: **deterministischer Fallback** (kein stilles Versagen).
**Response:** `prompt_slugs` (genutzte Slugs), `prompt_slug_catalog` (Referenz), `llm_*_applied` Flags.
**Admin:** Templates unter Kategorie `training` pflegen — siehe `AI_PROMPT_SYSTEM_SPEC.md`.
---
## 6. UI-Roadmap
1. **F1:** Roadmap-Box unter Ziel-Eingabe (Major Steps als Karten, editierbar) — vor Übungsliste
2. **F2:** Match-Ergebnis pro Major Step (Bibliothek / Lücke / KI anlegen)
3. **F3:** `roadmap_first` als Default im Graph-Builder
---
## 7. Was bewusst nicht in Phase F
- Gruppen-Historie, Belastungssteuerung der Gruppe
- Mitai `workflow_engine` Port
- Vollautomatisches Speichern ohne Trainer-Review
---
## 8. Implementierungsstände
| ID | Inhalt | Status |
|----|--------|--------|
| **F0** | Spec + Doku + `planning_progression_roadmap.py` Scaffold | ✅ 0.8.204 |
| **F1** | `include_roadmap_preview` in API + deterministische A/B | ✅ 0.8.204 |
| **F2** | LLM Phase A/B/C über `ai_prompts` (078/079), `include_llm_roadmap` | ✅ 0.8.205 |
| **F3** | Retrieval aus `stage_specs` (roadmap_first) | ✅ 0.8.206209 |
| **F4** | UI Roadmap-Review + `roadmap_override` | ✅ 0.8.207 |
| **F5** | Start/Ziel strukturiert + Prompt **087** + Zwei-Schritt-UI | ✅ 0.8.210214 |
| **F6** | Gap-Prep + `planning_context` an Übungs-KI | ✅ 0.8.212214 |
| **F7** | `planning_skill_expectations` | ✅ 0.8.215216 |
| **F8** | Editierbare `stage_specs` in UI | ✅ 0.8.216 |
| **F9** | `planning_roadmap` JSONB (Migration **088**) | ✅ 0.8.217 |
| **G** | Trainingsplanung: eigene Pipeline + Workflow-Engine | 🔲 |
Details: `docs/architecture/PLANNING_PROGRESSION_GRAPH_KI.md`
---
## 9. Changelog
- **2026-05-22:** Ist-Stand F5F9 dokumentiert; Verweis auf `PLANNING_PROGRESSION_GRAPH_KI.md`.
- **2026-06-07:** Erstfassung — Roadmap-first Entscheidung, Abgrenzung Graphen vs. Planung, Workflow-lite.

View File

@ -0,0 +1,101 @@
# Progressionsgraph — Slot-Editor (Phase B + F15)
**Stand:** 2026-05-22 · **Status:** Umgesetzt (F14 + F15 lokal nach 0.8.233)
## Ziel
Ein Progressionsgraph = **ein linearer Hauptpfad** (Roadmap = strukturgebend). Jeder **Major Step** ist ein **Slot** mit:
- **primary** — Hauptübung des Slots (Pfadknoten)
- **siblings** — 0..n Schwestern (gleiche Stufe, `edge_type: sibling`)
KI-Entwürfe und Bibliotheksübungen leben **im selben Slot-Modell**, ohne sofortige Übungsanlage.
## Slot-Zustände (`kind`)
| kind | Bedeutung |
|------|-----------|
| `empty` | Noch keine Übung |
| `library` | `exercise_id` (+ optional `variant_id`) |
| `proposal` | KI-Entwurf (`ai_suggestion`, kein `exercise_id`) |
## Kanten
- `primary(n) → primary(n+1)``next_exercise` (nur befüllte Primärkette, lückenlos verbunden)
- `primary ↔ sibling``sibling` (pro Slot)
Leere Slots in der Roadmap sind erlaubt; Kanten nur zwischen aufeinanderfolgenden befüllten Primär-Slots.
## Editor-Zustand (`ProgressionGraphDraft`)
```ts
{
goalQuery, startSituation, targetState, roadmapNotes, maxSteps,
majorSteps: MajorStep[],
slots: Slot[], // index = major_step_index
pathSkillExpectations?,
lastFindings?, // path_qa-Snapshot
findingsStale?: boolean, // Bewertung veraltet (↔ Artefakt findings_stale)
dirty: boolean,
}
```
**Hydration:** `planning_roadmap` + Kanten → Slots; `slot_contents[]` für Entwürfe; Primärkette aus `next_exercise`.
**Speichern:** Batch-Delete bestehender Pfad-/Schwester-Kanten → `edges/sequence` (Primärkette) → einzelne `sibling`-Kanten → `PUT`/`sequence` mit Artefakt inkl. `slot_contents`, `last_findings`, **`findings_stale`**.
## Findings-Panel
Nutzt `path_qa`:
| Feld | Bedeutung |
|------|-----------|
| `quality_score` | Gesamt = **min(`roadmap_qa`, `assignment_qa`)** |
| `roadmap_qa` | Stufen/Roadmap (LLM `topic_coverage`, …) |
| `assignment_qa` | Slot-Befüllung (`empty_slot_count`, …) |
| `overall_ok`, `issues`, `recommendations`, `gap_fill_offers`, … | wie bisher |
**API:** `POST /api/planning/progression-path-suggest` mit `evaluate_only: true` und `evaluate_steps[]` — QA ohne Re-Match.
**Bewertung veraltet:** Jede Graph-Änderung setzt `findingsStale: true` → Banner im Panel. Nach „Graph bewerten“ → `false`. Persistenz: `planning_roadmap.findings_stale`.
## Match-Flow („Übungen matchen“)
1. **Schritt 1:** `evaluate_only` + volle Pfad-QS (wie „Graph bewerten“)
2. **Schritt 2:** `unified_slot_review: true`**`ProgressionOptimizeCompareModal`**
3. Pro Slot: aktuell vs. beste Bibliothek vs. optional KI-Vorschlag
4. **Vorauswahl:** Bibliothek nur wenn Stufen-Fit ≥ 50 % und besser als Baseline; sonst KI (bei leerem/schwachem Slot)
5. **Übernahme:** nur gewählte Slots speichern — **keine** automatische Nach-Bewertung
## Artefakt-Erweiterung (`GraphPlanningRoadmapArtifact`)
Optional:
- `slot_contents[]``{ major_step_index, primary, siblings[] }`
- `last_findings` — letzter `path_qa`-Snapshot
- **`findings_stale`** — bool, Bewertung bezieht sich nicht mehr auf aktuellen Graph-Stand
## UI (konsolidiert)
- **Eine Oberfläche:** `ExerciseProgressionGraphPanel` embeddet `ProgressionGraphEditor` (Slots + Findings)
- Kein separater Slot-Editor, kein 4-Schritt-KI-Wizard, kein `ProgressionChainEditor` im Panel
- Route `/progression-graphs/:id` → Redirect nach `/exercises` (Deep-Link wählt Graph)
- **Slot-Keys:** stabil `slot-{index}` (nicht Lernziel-Text) — sonst Fokusverlust beim Tippen
## Ersetzt (Legacy, nicht mehr im Panel)
- `ExerciseProgressionPathBuilder` · `ProgressionChainEditor` — Code bleibt vorerst, nicht eingebunden
## Implementierungsreihenfolge
| ID | Inhalt | Status |
|----|--------|--------|
| B.0 | Draft + Laden/Speichern Slots ↔ Kanten | ✅ |
| B.1 | Slot-Karten, Bibliothek + Entwurf | ✅ |
| B.2 | Findings-Panel + `evaluate_only` | ✅ |
| B.3 | Entwürfe im Artefakt + „Übung anlegen“ | ✅ |
| B.4 | Route + Panel vereinfachen | ✅ |
| B.5 | `last_findings` + Phase-C-Vorbereitung | ✅ |
| F15 | Unified Slot-Review, getrennte QS, `findings_stale` | ✅ |
**Ist-Doku:** `docs/architecture/PLANNING_PROGRESSION_GRAPH_KI.md` §8.1 · `docs/HANDOVER.md` §2.8 F15

View File

@ -0,0 +1,43 @@
# Umsetzungsplan: Trainingsmodule & Kombinationsübungen
**Bezug:** `functional/Shinkan Trainingsmodule Kombinationsuebungen Spezifikation V2.md` (Kopf „V3“, inkl. **§10.2.1**, **§10.4 Coaching-Stufen**, **Anhang A** Implementierungsabgleich — Drift-Schutz)
**Technische Entwurfsspezifikation:** `technical/TRAINING_MODULES_AND_COMBINATION_EXERCISES_SPEC.md`
**Stand dieses Dokuments:** 2026-05-20 (Abgleich mit Code, siehe `backend/version.py`)
## Ziele
Umsetzung der MVP-Punkte aus der Fachspezifikation ohne die bestehende Planung zu destabilisieren: schrittweise Migrationen, bestehende Sektions-/Item-Struktur (`training_unit_sections`, `training_unit_section_items`) beibehalten, Kopiersemantik bei Übernahmen.
## Phasenüberblick
| Phase | Inhalt | Status |
|-------|--------|--------|
| **1** | **Trainingsmodule (Bibliothek):** Tabellen `training_modules`, `training_module_items`; REST CRUD mit Governance wie andere Bibliotheken; Übernahme in eine bestehende Einheit per `POST /api/training-units/{id}/apply-training-module` (Anfügen ans Ende eines Abschnitts via `section_order_index`); optionale Lineage-Spalte `source_training_module_id` auf Planungsitems; UI: Liste/Editor unter `/planning/training-modules`, Link von der Planung, Modal „Modul übernehmen“; **Ergänzung 2026-05-20:** Fähigkeiten-Profil + Listen-Filter (Peer-Vergleich nur unter Modulen) — `technical/SKILL_SCORING_SPEC.md` | **umgesetzt (MVP Schritt 1)** |
| **2** | Kombinationsübungen: `exercise_kind`/`combination_*`, Slots, Pools, `method_archetype`, `method_profile` (JSON) | **teilweise** — wie links; zusätzlich **057** `planning_method_profile`; Planungs-Merge Client (`effectiveComboMethodProfile`); Archetypen weiterhin **nur Code-Konstanten** (kein Admin) | **Offen:** Archetyp-Admin-UI; Profil↔Archetyp-Validierung Backend; „alle Slots vorbelegen“ / Presets (siehe Fachspez **§10.6**); Haupt-/Nebenmethoden an Kombi wo Spec es verlangt |
| **3** | Planungsblöcke: Gruppierung, Auflösen, „als Modul speichern“, erweiterter Übernahmemodus (Zwischenposition) | geplant |
| **4** | Coaching: Archetyp-Support | **teilweise:** **Stufe A** — Merge Katalog+Planung; `CombinationPlanBracket` in Peek/Run; globale Profilzahlen mit Labels (`describeGlobalComboProfile`); Stations-/Timing-Zusammenfassung inkl. Wdh.-Hinweise. **Stufe B/C****offen**10.6, Anhang A) |
| **5** | Rahmenprogramm: Modulübernahme UX in Slot-Blueprint-Editor konsolidieren | geplant |
## Coaching — verbindliche Arbeitspakete (gegen Spec-Drift)
| Paket | Spec-Referenz | Kurzinhalt |
|-------|----------------|-----------|
| **4a (Ist/Ziel)** | §10.2.1 | Archetyp-Schlüssel bleiben identisch zu `backend/routers/exercises.py` (`COMBINATION_ARCHETYPE_IDS`) und `frontend/src/constants/combinationArchetypes.js`. |
| **4b** | §10.4 Stufe A | **Erreicht (0.8.110):** Slots + Kandidaten; Archetyp-Hilfstext; wirksames Profil lesend mit **fachlichen Labels**; Klammerdarstellung konsistent (`CombinationPlanBracket`, `comboPlanningMethodProfile.js`). |
| **4c** | §10.4 Stufe B | Entscheidung: virtuelle Substeps vs. persistierte Items; Konsistenz `sectionsToPutPayload`/Ist-Zeit. |
| **4d** | §10.4 Stufe C | Archetyp-spezifische Timer/Wechsel/Abhaken an `method_profile` — nach 4c. |
| **4e** | §10.6 | **Archetyp-Verwaltung:** DB/UI oder Konfiguration statt nur Release — Labels, Defaults, ggf. Vereins-/Rollen-Sichtbarkeit. |
| **4f** | §10.6 · `COMBINATION_TIMING_PROFILE_PLAN.md` | **Massen-Vorbelegung:** ein Klick alle Slot-Zeiten/Anzahlen aus Archetyp/Global; Modal „Archetyp-Vorlage anwenden?“ (Phase 2 des Timing-Plans). |
| **4g** | §10.6 | **Backend-Validierung:** Pflichtfelder/Wertebereiche je `method_archetype`; optional serverseitiger Merge mit Katalog (aktuell nur Client). |
## Phase 1 (technische Notizen)
- **Governance:** `visibility`/`club_id`/`created_by` analog `training_plan_templates`; Listenfilter `library_content_visibility_sql`.
- **Übernahme:** Keine Live-Verknüpfung; Items werden kopiert; `source_training_module_id` dokumentiert Herkunft.
- **Schnittstelle Übernahme:** `section_order_index` entspricht der Reihenfolge der Abschnitte in der gespeicherten Einheit (0-basiert), konsistent zur Planungs-API.
## Pflege nach Merge
- `DATABASE_SCHEMA.md` bei größeren Schema-Erweiterungen ergänzen.
- `ACCESS_LAYER_ENDPOINT_AUDIT.md` bei neuen mandantenbezogenen Endpunkten fortpflegen.
- **Nach jeder Kombi-/Coach-Änderung:** `functional/… Spezifikation V2.md` **Anhang A** und diese Tabelle Phasen 2/4 abstimmen.

View File

@ -58,7 +58,8 @@ return {"message": "Fehler", "success": False}
### 1.4 Mandanten & Zugriffsschicht (Shinkan / ACCESS_LAYER)
**Verbindlicher Rahmen:** `.claude/docs/technical/ACCESS_LAYER_AND_GOVERNANCE_PLAN.md`
**Medien-Assets (Archiv, Papierkorb, Promotion, Copyright, externer Speicher):** `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`
**Medien-Assets (Archiv, Papierkorb, Promotion, Copyright, externer Speicher, geplante Inline-Verlinkung §11):** `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`
**Session-Handover (Ist + nächste Schritte):** `docs/HANDOVER.md`
**Fortlaufendes Inventar:** `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md`
**Definition of Done für neue oder geänderte geschützte APIs**, sobald Daten **Verein**, **Sichtbarkeit** oder **mandantenbezogene Listen** betreffen:

View File

@ -10,7 +10,8 @@
1. Repo-Root: `CLAUDE.md` (Kontext, Links, Pflicht-Dokus)
2. Agent-Übersicht: **`.claude/README.md`** (Baum, wo was liegt)
3. Spez-Index: **`.claude/docs/README.md`**
4. Aufgaben-Tracking: **Gitea** Übersicht lokal: **`.claude/docs/GITEA_ISSUES_INDEX.md`** (regelmäßig refreshen nach Bedarf)
4. **Session-Handover (aktuelle Prioritäten, Medien-Ist):** **`docs/HANDOVER.md`**
5. Aufgaben-Tracking: **Gitea** Übersicht lokal: **`.claude/docs/GITEA_ISSUES_INDEX.md`** (regelmäßig refreshen nach Bedarf)
---

View File

@ -35,6 +35,16 @@ DB_PASSWORD=CHANGE_ME_SECURE_PASSWORD
OPENROUTER_API_KEY=your_api_key_here
OPENROUTER_MODEL=anthropic/claude-sonnet-4
# Vereins-Kontingente hart blockieren (KI-Kosten!). Nur 1, true oder yes aktivieren.
# Nach Änderung: docker compose -f docker-compose.dev-env.yml up -d backend
CLUB_FEATURE_ENFORCE=1
# Standard-OpenRouter-Modell (alle Aufrufe). Optional pro Prompt in ai_prompts.openrouter_model
# ueberschreibbar (Migration 070, Superadmin unter „KI Prompts“).
# Übungs-KI (Docker): ohne Eintrag im compose „environment:“ landet keine .env-Zeile im Container.
# Hier ist SHINKAN_AI_DEBUG in docker-compose*.yml angebunden — 1 = ausführliche WARN-Logs (exercise_ai, openrouter).
# SHINKAN_AI_DEBUG=1
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USER=noreply@jinkendo.de
@ -52,6 +62,8 @@ ENVIRONMENT=production
# Medien (Docker Compose): SHINKAN_MEDIA_HOST = Verzeichnis auf dem Host (Bind-Mount),
# MEDIA_ROOT = gleicher Pfad im Container (muss mit dem Mount-Ziel übereinstimmen — FastAPI).
# Prod: Verzeichnis VOR erstem "docker compose up" anlegen (z. B. sudo mkdir -p …/prod).
# Wenn Docker meldet "chown … operation not permitted": oft NAS/NFS oder Rechte — lokalen Pfad nutzen.
SHINKAN_MEDIA_HOST=/shinkan-media
MEDIA_ROOT=/app/media

View File

@ -18,6 +18,11 @@ jobs:
docker compose -f docker-compose.dev-env.yml build --no-cache
docker compose -f docker-compose.dev-env.yml up -d
sleep 5
curl -sf http://localhost:8098/api/version && echo "✓ DEV API healthy"
if ! curl -sf http://localhost:8098/api/version; then
echo "✗ DEV API nicht erreichbar — Backend-Logs (Migration/Startup):"
docker compose -f docker-compose.dev-env.yml logs backend --tail 120 || true
exit 1
fi
echo "✓ DEV API healthy"
curl -sf http://localhost:3098/api/version && echo "✓ DEV über Frontend-Nginx (wie Browser) healthy"
echo "=== Shinkan DEV Deploy complete ==="

View File

@ -1,24 +1,29 @@
name: Test Suite
# develop: push/PR → Tests gegen Dev (parallel oder vor Deploy Development).
# main: kein push/PR-Trigger — vermeidet doppelten Dev-Lauf beim Merge develop→main;
# Prod-Tests nur via workflow_run nach erfolgreichem Deploy Production.
on:
push:
branches: [main, develop]
branches: [develop]
pull_request:
branches: [main, develop]
branches: [develop]
workflow_run:
workflows: ["Deploy Development", "Deploy Production"]
types: [completed]
jobs:
# Wie Mitai-Jinkendo: pytest im laufenden backend-Container (Python aus Image, gleiche DB wie Deploy).
# Pytest im laufenden backend-Container; ACCESS_LAYER + TRAINING_PLANNING Integration gegen dieselbe PostgreSQL wie Deploy (Schema via Container-Start migriert).
pytest-backend:
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- name: Backend pytest im deployten Container
run: |
set -e
EVENT_NAME="${{ github.event_name }}"
REF_NAME="${{ github.ref_name }}"
BASE_REF="${{ github.base_ref }}"
RUN_WORKFLOW="${{ github.event.workflow_run.name }}"
APP_DIR="/home/lars/docker/shinkan"
COMPOSE_FILE="docker-compose.yml"
@ -28,18 +33,33 @@ jobs:
APP_DIR="/home/lars/docker/shinkan-dev"
COMPOSE_FILE="docker-compose.dev-env.yml"
fi
elif [ "$REF_NAME" = "develop" ]; then
elif [ "$REF_NAME" = "develop" ] || [ "$BASE_REF" = "develop" ]; then
APP_DIR="/home/lars/docker/shinkan-dev"
COMPOSE_FILE="docker-compose.dev-env.yml"
fi
cd "$APP_DIR"
echo "Warte auf stabilen backend-Container …"
for i in $(seq 1 60); do
if docker compose -f "$COMPOSE_FILE" exec -T backend true 2>/dev/null; then
echo "Backend bereit (Versuch $i)"
break
fi
if [ "$i" -eq 60 ]; then
echo "Timeout: backend-Container nicht bereit"
docker compose -f "$COMPOSE_FILE" ps || true
docker compose -f "$COMPOSE_FILE" logs backend --tail 80 || true
exit 1
fi
sleep 5
done
docker compose -f "$COMPOSE_FILE" exec -T backend sh -lc "
pip install -r /app/requirements-dev.txt &&
cd /app &&
ACCESS_LAYER_STRICT=1 python scripts/check_access_layer_hints.py &&
python scripts/security_release_checks.py &&
ACCESS_LAYER_INTEGRATION=1 SKIP_DB_MIGRATE=1 python -m pytest tests -m 'not slow' -ra -vv --tb=short
ACCESS_LAYER_INTEGRATION=1 TRAINING_PLANNING_INTEGRATION=1 SKIP_DB_MIGRATE=1 python -m pytest tests -m 'not slow' -ra -vv --tb=short
"
lint-backend:
@ -88,6 +108,90 @@ jobs:
npm run build
echo "✓ Frontend build OK"
# Phase-0 Lastsmoke: nur k6 — eigener Job (kein Node/Playwright), klare CI-Zuordnung.
k6-health-baseline:
name: k6 /health Baseline
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
env:
E2E_TARGET_URL: https://dev.shinkan.jinkendo.de
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: E2E-Ziel wählen (Dev über Proxy vs. Production)
id: e2e
run: |
EVENT="${{ github.event_name }}"
WF_NAME="${{ github.event.workflow_run.name }}"
DEV_BASE="${{ env.E2E_TARGET_URL }}"
if [ "$EVENT" = "workflow_run" ] && [ "$WF_NAME" = "Deploy Production" ]; then
echo "mode=prod" >> $GITHUB_OUTPUT
echo "base_url=https://shinkan.jinkendo.de" >> $GITHUB_OUTPUT
echo "→ k6 gegen Prod-Basis."
else
echo "mode=dev" >> $GITHUB_OUTPUT
echo "base_url=${DEV_BASE}" >> $GITHUB_OUTPUT
echo "→ k6 gegen Dev (${DEV_BASE})."
fi
- name: Dev /health abwarten
if: ${{ steps.e2e.outputs.mode == 'dev' }}
run: |
BASE="${{ steps.e2e.outputs.base_url }}"
echo "Warte auf $BASE/health …"
for i in $(seq 1 90); do
if curl -sf "$BASE/health" >/dev/null 2>&1; then
echo "Health OK (Versuch $i)"
exit 0
fi
sleep 2
done
echo "Timeout: Dev /health nicht erreichbar — Deploy / DNS / Firewall prüfen."
curl -v "$BASE/health" || true
exit 1
- name: Prod /health abwarten
if: ${{ steps.e2e.outputs.mode == 'prod' }}
run: |
BASE="${{ steps.e2e.outputs.base_url }}"
echo "Warte auf $BASE/health …"
for i in $(seq 1 60); do
if curl -sf "$BASE/health" >/dev/null 2>&1; then
echo "Health OK (Versuch $i)"
exit 0
fi
sleep 5
done
echo "Timeout: Prod /health nicht erreichbar"
curl -v "$BASE/health" || true
exit 1
- name: Install k6
run: |
set -e
K6_VER="v0.55.0"
ARCH=$(uname -m)
case "$ARCH" in
x86_64) K6_ARCH=amd64 ;;
aarch64|arm64) K6_ARCH=arm64 ;;
*) echo "k6: unbekannte Architektur: $ARCH"; exit 1 ;;
esac
echo "Installing k6 ${K6_VER} linux-${K6_ARCH}"
curl -sSL "https://github.com/grafana/k6/releases/download/${K6_VER}/k6-${K6_VER}-linux-${K6_ARCH}.tar.gz" -o /tmp/k6.tgz
tar -xzf /tmp/k6.tgz -C /tmp
sudo mv "/tmp/k6-${K6_VER}-linux-${K6_ARCH}/k6" /usr/local/bin/k6
k6 version
- name: k6 Health-Baseline (parallele /health)
env:
BASE_URL: ${{ steps.e2e.outputs.base_url }}
run: |
set -e
echo "k6 gegen BASE_URL=$BASE_URL"
k6 run scripts/load/k6-health-baseline.js
echo "✓ k6 Health-Baseline passed"
playwright-tests:
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest

View File

@ -11,7 +11,14 @@
> | Lessons Learned | `.claude/rules/LESSONS_LEARNED.md` |
> | Setup-Dokument | `.claude/docs/working/SHINKAN_PROJECT_SETUP.md` |
> | Anforderungen | `.claude/docs/functional/SHINKAN_REQUIREMENTS.md` |
> | Medien-Archiv, Lifecycle, Promotion | `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` |
> | Medien-Archiv, Lifecycle, Inline (Plan §11) | `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` |
> | Fähigkeiten-Scoring (Planungs-Bausteine) | `.claude/docs/technical/SKILL_SCORING_SPEC.md` |
> | Handover / nächste Session | **`docs/HANDOVER.md`** |
> | Fachlicher Nutzerüberblick (Design/Product) | **`docs/FACHLICHE_NUTZERFUNKTIONEN.md`** |
> | Architektur-Zielbild, Refaktor-Roadmap, verbindliche Shinkan-Regeln | **`docs/architecture/README.md`** |
> | Performance-Baseline (Phase 0) | **`docs/architecture/BASELINE_SNAPSHOT.md`** |
> | KI-Prompt-System — Zielarchitektur | `.claude/docs/technical/AI_PROMPT_TARGET_ARCHITECTURE.md` |
> | Planungs-KI Progressionsgraph (Ist-Stand) | **`docs/architecture/PLANNING_PROGRESSION_GRAPH_KI.md`** · Spec **`.claude/docs/working/PLANNING_PROGRESSION_ROADMAP_SPEC.md`** · Roadmap **`docs/architecture/PLANNING_KI_ROADMAP.md`** |
## Projekt-Übersicht
@ -57,7 +64,7 @@ backend/
└── routers/ # Router-Module
auth · profiles · clubs · groups · skills · methods
exercises · exercise_progression_graphs · training_units · training_programs
planning · import_wiki · admin · membership
planning · import_wiki · admin · membership · media_assets
frontend/src/
├── App.jsx # Root, Auth-Gates, Navigation
@ -82,10 +89,12 @@ frontend/src/
**Siehe:** `backend/version.py` (`APP_VERSION`, `DB_SCHEMA_VERSION`, `MODULE_VERSIONS`) und `.claude/docs/PROJECT_STATUS.md`.
Kurz (Stand 2026-05-05): App **0.8.10**, DBSchemaVersion **`20260505037`**; Kern: Übungen, Varianten, Medien, Planung mit Sektionen, **Trainingsrahmen Bibliothek + SlotBlueprint** (036037), Progressionsgraph, Reifegrad/MatrixStack — Details `PROJECT_STATUS.md` und `TRAINING_FRAMEWORK_SPEC.md` §2.
Kurz (Stand 2026-05-14): App- und DB-Version siehe **`backend/version.py`**; Kern: Übungen, Varianten, **Medien-Archiv & Bibliothek (`/media`)**, **Inline-Medien im Rich-Text**, **Inhaltsmeldungen (P-13)** im Posteingang, Mandanten-Sync aktiver Verein, Planung mit **Phasen & parallelen Streams (Breakout, 063)**, **Trainingsrahmen Bibliothek + SlotBlueprint** (036037), Progressionsgraph, Reifegrad/MatrixStack — Details `PROJECT_STATUS.md`, `docs/HANDOVER.md`, Nutzerüberblick **`docs/FACHLICHE_NUTZERFUNKTIONEN.md`**, `PARALLEL_TRAINING_STREAMS_SPEC.md`, `MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` (Abschnitt 11 umgesetzt).
### Log (Auszug)
- 2026-05-20: **Fähigkeiten-Scoring Phase 3** — gewichtete Profile für Module/Rahmen/Pfade; Peer-Vergleich getrennt nach Artefakttyp; Listen-Filter + Discovery — siehe `SKILL_SCORING_SPEC.md`, `docs/HANDOVER.md` §2.6, `FEATURES_DELIVERED_2026-Q2.md` §15.
- 2026-05-07: **Medien** — zentrales Archiv (`media_assets`), Bibliothek-UI, Lifecycle/Papierkorb, `from-asset`, Speicherpfade `library/…`, Governance `official`/Copyright; **0.8.59** aktiver Verein UI/API-Sync — siehe `.claude/docs/library/FEATURES_DELIVERED_2026-Q2.md` §12, `docs/HANDOVER.md`.
- 2026-05-05: Rahmen nur Bibliothek (**036**), SlotAblauf = `training_units` + Sektionen (**037**), `POST /api/training-units/from-framework-slot`, keine `training_framework_slot_exercises` mehr — siehe `DATABASE_SCHEMA.md` / `FEATURES_DELIVERED_2026-Q2.md`.
- 2026-04-27: Übungsvarianten API/UI, Migration 030, Listen-UX-Suche, Admin-Upload-Limits — siehe `PROJECT_STATUS.md` und `docs/library/FEATURES_DELIVERED_2026-Q2.md`.
@ -106,7 +115,7 @@ Kurz (Stand 2026-05-05): App **0.8.10**, DBSchemaVersion **`20260505037`**
- `exercises` - Übungen (Kernobjekt)
- `exercise_variants` - Übungsvarianten
- `exercise_skills` - M:N Übung ↔ Fähigkeit
- `exercise_media` - Medien (Bilder, Videos); Zielbild Archiv & Lifecycle: **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**. **Inline im Fließtext** (Übungstexte): geplant §11 derselben Spec — Anker `exercise_media.id`, einheitlicher Render-Pfad; noch nicht implementiert.
- `exercise_media` - Medien (Bilder, Videos); Zielbild Archiv & Lifecycle: **`MEDIA_ASSETS_AND_ARCHIVE_SPEC.md`**. **Inline im Fließtext** (Übungstexte) ist implementiert (Spec §11): Anker `exercise_media.id`, einheitlicher Render-Pfad.
**Trainingsplanung / Rahmen:**
- `training_plan_templates` + Sektionsvorlagen — Mikrovorlage pro Einheit (Migration **031**)

View File

@ -13,6 +13,7 @@ Shinkan ist eine moderne Web- und Mobile-App für Kampfsport-Trainer und Vereine
- **Kataloge:** Fähigkeiten und Trainingsmethoden strukturiert verwalten
- **Standardisierung:** Vereinsstandards und wiederverwendbare Vorlagen
- **Freigabe:** Gesteuerte Veröffentlichung von Inhalten
- **Medien:** Zentrale **Medienbibliothek** (`/media`), Archiv mit Lifecycle/Papierkorb, Verknüpfung in Übungen — Norm: `.claude/docs/technical/MEDIA_ASSETS_AND_ARCHIVE_SPEC.md` · Einstieg: `docs/HANDOVER.md`
## Nicht in Shinkan

View File

@ -2,14 +2,16 @@ FROM python:3.12-slim
WORKDIR /app
# Install system dependencies
# Install system dependencies (tzdata für zoneinfo/ZoneInfo unter Linux)
RUN apt-get update && apt-get install -y \
postgresql-client \
tzdata \
&& rm -rf /var/lib/apt/lists/*
# Copy requirements and install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
ENV PIP_DEFAULT_TIMEOUT=120
RUN pip install --no-cache-dir --retries 5 -r requirements.txt
# Copy application code
COPY . .

View File

@ -0,0 +1,77 @@
"""
Account-Lifecycle (CAPABILITY_CATALOG.v1.md §3, M3 C0).
Zustände: unverified verified_pending_club active_member; platform_admin separat.
"""
from __future__ import annotations
import os
from typing import TYPE_CHECKING, Optional
from fastapi import HTTPException
from club_tenancy import is_platform_admin
if TYPE_CHECKING:
from tenant_context import TenantContext
_ACCOUNT_STATE_RANK = {
"unverified": 1,
"verified_pending_club": 2,
"active_member": 3,
"platform_admin": 4,
}
def resolve_account_state(
*,
email_verified: bool,
global_role: str,
has_active_membership: bool,
) -> str:
"""Ermittelt account_state für ein Profil."""
if is_platform_admin(global_role):
return "platform_admin"
if not email_verified:
return "unverified"
if not has_active_membership:
return "verified_pending_club"
return "active_member"
def account_state_satisfies(current: str, required: str) -> bool:
"""True wenn current mindestens required ist."""
cur = _ACCOUNT_STATE_RANK.get(current, 0)
req = _ACCOUNT_STATE_RANK.get(required, 99)
if current == "platform_admin":
return True
return cur >= req
def account_gate_enforcement_enabled() -> bool:
"""Account-Gates aktiv (Default an — nur wenige Endpoints in M3)."""
return os.getenv("ACCOUNT_GATE_ENFORCE", "1").strip() == "1"
def assert_min_account_state(
tenant: "TenantContext",
min_state: str,
*,
endpoint: Optional[str] = None,
) -> None:
"""
Prüft Mindest-Account-Status. Wirft 403 wenn ACCOUNT_GATE_ENFORCE=1 (Default).
"""
current = getattr(tenant, "account_state", "active_member")
ok = account_state_satisfies(current, min_state)
if ok:
return
if not account_gate_enforcement_enabled():
return
detail = (
f"Account-Status „{current}“ reicht nicht für diese Aktion "
f"(erforderlich: {min_state})."
)
if endpoint:
detail = f"{detail} ({endpoint})"
raise HTTPException(status_code=403, detail=detail)

View File

@ -0,0 +1,178 @@
"""
API-Gates für Onboarding (Phase A MEMBERSHIP_RBAC_DECISIONS_2026-06.md §1.1).
Blockiert Domänen-APIs für unverified / verified_pending_club vor dem Router.
"""
from __future__ import annotations
import os
import re
from typing import Optional, Tuple
from account_lifecycle import resolve_account_state
from club_tenancy import memberships_with_roles
# Öffentlich ohne Session
PUBLIC_API_PREFIXES = (
"/api/auth/login",
"/api/auth/register",
"/api/auth/forgot-password",
"/api/auth/reset-password",
"/api/auth/verify/",
"/api/legal-documents/",
"/api/clubs/public-directory",
"/api/version",
"/api/health/",
"/health",
)
# Mit Session, unabhängig vom account_state (Logout, Profil lesen, …)
AUTH_INFRA_PREFIXES = (
"/api/auth/logout",
"/api/auth/me",
"/api/auth/status",
"/api/auth/pin",
"/api/auth/resend-verification",
"/api/profiles/me",
"/api/me/entitlements",
)
# Zusätzlich für verified_pending_club (Verein bewerben)
PENDING_CLUB_PREFIXES = (
"/api/me/club-join-requests",
"/api/me/club-creation-requests",
)
_PROFILE_MUTATION_RE = re.compile(r"^/api/profiles/(\d+)$")
def api_onboarding_gate_enabled() -> bool:
"""Produktions-Gate aktiv (ACCOUNT_GATE_API_ENFORCE=0 zum Abschalten)."""
return os.getenv("ACCOUNT_GATE_API_ENFORCE", "1").strip() == "1"
def _middleware_db_lookup_enabled() -> bool:
"""
Middleware-Session-Lookup nur mit echter DB (nicht in pytest TestClient ohne Postgres).
"""
if os.getenv("SKIP_DB_MIGRATE", "").strip().lower() in ("1", "true", "yes"):
return False
if os.getenv("PYTEST_CURRENT_TEST"):
return False
return True
def normalize_api_path(path: str) -> str:
p = (path or "").split("?", 1)[0].strip()
if not p.startswith("/"):
p = "/" + p
if len(p) > 1 and p.endswith("/"):
p = p[:-1]
return p
def is_public_api_path(path: str) -> bool:
p = normalize_api_path(path)
return any(p == pref or p.startswith(pref) for pref in PUBLIC_API_PREFIXES)
def _path_allowed_for_state(path: str, method: str, account_state: str, profile_id: int) -> bool:
p = normalize_api_path(path)
m = (method or "GET").upper()
for pref in AUTH_INFRA_PREFIXES:
if p == pref or p.startswith(pref + "/"):
return True
match = _PROFILE_MUTATION_RE.match(p)
if match and m in ("PUT", "PATCH") and int(match.group(1)) == int(profile_id):
return True
if account_state == "unverified":
return False
if account_state == "verified_pending_club":
for pref in PENDING_CLUB_PREFIXES:
if p == pref or p.startswith(pref + "/"):
return True
return False
return True
def resolve_account_state_for_token(cur, session_row: dict) -> str:
profile_id = int(session_row["profile_id"])
role = (session_row.get("role") or "").lower()
cur.execute(
"SELECT COALESCE(email_verified, false) AS email_verified FROM profiles WHERE id = %s",
(profile_id,),
)
prof = cur.fetchone()
email_verified = bool(prof.get("email_verified")) if prof else False
memberships = memberships_with_roles(cur, profile_id, active_only=True)
has_active = len(memberships) > 0
return resolve_account_state(
email_verified=email_verified,
global_role=role,
has_active_membership=has_active,
)
def check_api_onboarding_gate(
*,
path: str,
method: str,
profile_id: int,
account_state: str,
) -> Tuple[bool, Optional[str]]:
"""
Returns (allowed, reason).
active_member / platform_admin immer erlaubt (Domain).
"""
if not api_onboarding_gate_enabled():
return True, None
if account_state in ("active_member", "platform_admin"):
return True, None
if _path_allowed_for_state(path, method, account_state, profile_id):
return True, None
return False, f"account_state_{account_state}"
def evaluate_request_gate(token: Optional[str], path: str, method: str) -> Tuple[bool, Optional[str], Optional[str]]:
"""
Vollständige Prüfung inkl. Session-Lookup.
Returns: allowed, reason, account_state (für Logging)
"""
if not api_onboarding_gate_enabled() or not _middleware_db_lookup_enabled():
return True, None, None
p = normalize_api_path(path)
if not p.startswith("/api/"):
return True, None, None
if is_public_api_path(p):
return True, None, None
if not token:
return True, None, None
from auth import get_session
from db import get_db, get_cursor
session = get_session(token)
if not session:
return True, None, None
profile_id = int(session["profile_id"])
with get_db() as conn:
cur = get_cursor(conn)
account_state = resolve_account_state_for_token(cur, session)
allowed, reason = check_api_onboarding_gate(
path=p,
method=method,
profile_id=profile_id,
account_state=account_state,
)
return allowed, reason, account_state

View File

@ -0,0 +1,108 @@
"""
Gemeinsame Pydantic-Modelle fuer Uebungs-KI-Kontext (Formularfelder Prompt-Platzhalter).
Keine Imports aus exercise_ai vermeidet Zirkelimporte mit ai_prompt_job / exercise_ai.
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional, Sequence, Tuple
from pydantic import BaseModel, Field
class ExerciseFormAiFocusRow(BaseModel):
"""Fokusbereich fuer Skill-Retrieval (ai_skill_retrieval_profiles)."""
focus_area_id: int = Field(..., ge=1)
is_primary: Optional[bool] = False
class ExerciseFormAiPromptContext(BaseModel):
"""
Inhaltliche Eingabe fuer Uebungs-Prompts (Kurzfassung / Skills / Anleitung).
Wird genutzt von Admin-Prompt-Vorschau und POST /exercises/ai/suggest (via Mapping).
"""
title: Optional[str] = ""
goal: Optional[str] = None
execution: Optional[str] = None
preparation: Optional[str] = None
trainer_notes: Optional[str] = None
focus_hint: Optional[str] = None
focus_areas_context: Optional[List[ExerciseFormAiFocusRow]] = None
planning_context: Optional[Dict[str, Any]] = None
def focus_area_tuples(self) -> Optional[List[Tuple[int, bool]]]:
if not self.focus_areas_context:
return None
return [(int(x.focus_area_id), bool(x.is_primary)) for x in self.focus_areas_context]
def has_instruction_source_text(self) -> bool:
"""Mindestens ein Anleitungsfeld oder Titel fuer instruction_rewrite."""
if (self.title or "").strip():
return True
for val in (self.goal, self.execution, self.preparation, self.trainer_notes):
if val and str(val).strip():
return True
return False
@classmethod
def from_api_suggest(
cls,
*,
title: Optional[str] = None,
goal: Optional[str] = None,
execution: Optional[str] = None,
preparation: Optional[str] = None,
trainer_notes: Optional[str] = None,
focus_area_hint: Optional[str] = None,
focus_areas_context: Optional[Sequence[ExerciseFormAiFocusRow]] = None,
planning_context: Optional[Dict[str, Any]] = None,
) -> ExerciseFormAiPromptContext:
"""Mappt Felder aus POST /exercises/ai/suggest (focus_area_hint → focus_hint)."""
hint = (focus_area_hint or "").strip() or None
return cls(
title=(title or "").strip(),
goal=goal,
execution=execution,
preparation=preparation,
trainer_notes=trainer_notes,
focus_hint=hint,
focus_areas_context=list(focus_areas_context) if focus_areas_context else None,
planning_context=dict(planning_context) if planning_context else None,
)
@classmethod
def from_focus_tuples(
cls,
*,
title: str = "",
goal: Optional[str] = None,
execution: Optional[str] = None,
preparation: Optional[str] = None,
trainer_notes: Optional[str] = None,
focus_hint: Optional[str] = None,
focus_tuples: Optional[Sequence[Tuple[int, bool]]] = None,
) -> ExerciseFormAiPromptContext:
rows = None
if focus_tuples:
rows = [
ExerciseFormAiFocusRow(focus_area_id=int(fid), is_primary=bool(prim))
for fid, prim in focus_tuples
]
return cls(
title=(title or "").strip(),
goal=goal,
execution=execution,
preparation=preparation,
trainer_notes=trainer_notes,
focus_hint=(focus_hint or "").strip() or None,
focus_areas_context=rows,
)
__all__ = [
"ExerciseFormAiFocusRow",
"ExerciseFormAiPromptContext",
]

59
backend/ai_prompt_job.py Normal file
View File

@ -0,0 +1,59 @@
"""
KI-Prompt Jobs: Resolver + oeffentliche Fassade fuer Uebungs-KI-Aufrufe.
Importiert exercise_ai fuer Platzhalter-Builder und OpenRouter-Orchestrierung.
"""
from __future__ import annotations
from typing import Any, Dict
from ai_prompt_context import ExerciseFormAiFocusRow, ExerciseFormAiPromptContext
from exercise_ai import build_exercise_placeholder_variables
def resolve_exercise_form_variables(cur, slug: str, ctx: ExerciseFormAiPromptContext) -> Dict[str, str]:
"""Baut die Mustache-Map fuer exercise_summary / exercise_skill_suggestions."""
return build_exercise_placeholder_variables(
cur,
slug=slug,
title=(ctx.title or "").strip(),
goal=ctx.goal,
execution=ctx.execution,
focus_area_hint=ctx.focus_hint,
focus_areas_context=ctx.focus_area_tuples(),
preparation=ctx.preparation,
trainer_notes=ctx.trainer_notes,
planning_context=ctx.planning_context,
)
def run_exercise_form_ai_suggestion(
cur,
ctx: ExerciseFormAiPromptContext,
*,
want_summary: bool,
want_skills: bool,
want_instructions: bool = False,
) -> Dict[str, Any]:
"""
Fuehrt Uebungs-KI aus (OpenRouter) ein Einstieg fuer Router und kuenftige Jobs.
``ctx`` = Formularinhalt; ``want_*`` = welche Prompt-Slugs angefragt werden.
"""
from exercise_ai import run_exercise_ai_suggestion
return run_exercise_ai_suggestion(
cur,
form_ctx=ctx,
want_summary=want_summary,
want_skills=want_skills,
want_instructions=want_instructions,
)
__all__ = [
"ExerciseFormAiFocusRow",
"ExerciseFormAiPromptContext",
"resolve_exercise_form_variables",
"run_exercise_form_ai_suggestion",
]

View File

@ -0,0 +1,317 @@
"""
Admin-Vorschau: Platzhalter für Planungs-Prompts (Progressionsgraph, Pfad-QS, Suggest).
Nutzt repräsentative Beispieldaten + echte Katalog-Auszüge aus der DB.
"""
from __future__ import annotations
import json
from typing import Any, Dict, List, Mapping, Optional
from pydantic import BaseModel, Field
from planning_exercise_semantics import brief_to_summary_dict, build_semantic_brief
from planning_intent_context import build_planning_intent_context
from planning_prompt_variables import merge_planning_prompt_variables
PLANNING_PROMPT_SLUGS = frozenset(
{
"planning_progression_start_target",
"planning_progression_goal_analysis",
"planning_progression_roadmap",
"planning_progression_stage_spec",
"planning_exercise_query_semantics",
"planning_exercise_path_qa",
"planning_exercise_search_intent",
"planning_exercise_search_rank",
"planning_exercise_expectation_profile",
}
)
class PlanningPromptPreviewInput(BaseModel):
goal_query: str = Field(
default="Mae Geri vom Grundschritt bis zur kontrollierten Kumite-Nähe",
max_length=2000,
)
user_notes: str = Field(default="Fokus Breitensport, ohne Wettkampfdruck.", max_length=2000)
max_steps: int = Field(default=5, ge=2, le=10)
search_query: Optional[str] = Field(default=None, max_length=2000)
planning_catalog_context: Optional[Dict[str, Any]] = Field(default=None)
def is_planning_prompt_slug(slug: str) -> bool:
return (slug or "").strip().lower() in PLANNING_PROMPT_SLUGS
def _compact_json(obj: Any) -> str:
return json.dumps(obj, ensure_ascii=False, separators=(",", ":"))
def _sample_goal_analysis() -> Dict[str, Any]:
return {
"primary_topic": "Mae Geri",
"start_assumption": "Grundstellung und einfache Frontkick-Bewegung bekannt",
"target_state": "Kontrollierter Mae Geri in Kumite-Nähe mit Hüftöffnung",
"success_criteria": [
"Hüfte öffnet vor dem Kick",
"Ballen trifft Zielzone",
"Rückzug ohne Balanceverlust",
],
"constraints": {
"partner_required": False,
"excluded_themes": ["reine Kraft ohne Technikbezug"],
"trainer_notes": "Breitensport, kein Wettkampf",
},
}
def _sample_major_steps(max_steps: int) -> List[Dict[str, Any]]:
phases = ["einstieg", "grundlage", "vertiefung", "anwendung", "perfektion"]
titles = [
"Grundstellung und Mae Geri Einstieg",
"Hüftöffnung und Ballen-Fokus",
"Koordination und Rückzug",
"Anwendung in Partnerübung",
"Qualität unter leichtem Druck",
]
out: List[Dict[str, Any]] = []
for i in range(max_steps):
out.append(
{
"index": i,
"phase": phases[min(i, len(phases) - 1)],
"title": titles[min(i, len(titles) - 1)],
"learning_goal": titles[min(i, len(titles) - 1)],
}
)
return out
def _sample_path_steps() -> List[Dict[str, Any]]:
return [
{
"index": 1,
"exercise_id": 101,
"title": "Mae Geri — Stand und Hüftöffnung",
"goal": "Frontkick mit geöffneter Hüfte aus Grundstellung",
"is_bridge": False,
"is_ai_proposal": False,
"reasons": ["Stufen-Gate: Grundlagen"],
},
{
"index": 2,
"exercise_id": 102,
"title": "Mae Geri — Ballen und Rückzug",
"goal": "Präziser Ballentreffer mit kontrolliertem Rückzug",
"is_bridge": False,
"is_ai_proposal": False,
"reasons": ["Nachfolger im Graph"],
},
]
def _sample_planning_context() -> Dict[str, Any]:
return {
"scope": "progression_path",
"goal_query": "Mae Geri vom Grundschritt bis zur Kumite-Nähe",
"stage_index": 1,
"learning_goal": "Hüftöffnung und Ballen-Fokus",
}
def _sample_target_profile() -> Dict[str, Any]:
return {
"primary_focus": "Kihon",
"training_type": "Breitensport",
"skill_expectations": ["Geri Waza", "Koordination"],
}
def _sample_candidates() -> List[Dict[str, Any]]:
return [
{
"exercise_id": 101,
"title": "Mae Geri — Stand und Hüftöffnung",
"summary": "Frontkick mit Hüftöffnung",
"skill_names": ["Geri Waza"],
"score_hint": 0.82,
},
{
"exercise_id": 102,
"title": "Mae Geri — Ballen und Rückzug",
"summary": "Ballentreffer mit Rückzug",
"skill_names": ["Geri Waza", "Koordination"],
"score_hint": 0.76,
},
]
def _load_catalog_variables(cur) -> Dict[str, str]:
from planning_exercise_intent import (
_load_compact_catalog,
_load_skills_catalog_compact,
)
return {
"skills_catalog_json": _compact_json(_load_skills_catalog_compact(cur)),
"focus_areas_catalog_json": _compact_json(_load_compact_catalog(cur, "focus_areas", "id")),
"training_types_catalog_json": _compact_json(_load_compact_catalog(cur, "training_types", "id")),
"style_directions_catalog_json": _compact_json(_load_compact_catalog(cur, "style_directions", "id")),
"target_groups_catalog_json": _compact_json(_load_compact_catalog(cur, "target_groups", "id")),
}
def _preview_catalog_context(body: PlanningPromptPreviewInput):
from planning_catalog_context import catalog_context_from_mapping
raw = body.planning_catalog_context
if raw:
return catalog_context_from_mapping(raw)
return None
def _merge_catalog_preview(cur, slug: str, base: Dict[str, str], body: PlanningPromptPreviewInput) -> Dict[str, str]:
return merge_planning_prompt_variables(
cur,
base,
catalog=_preview_catalog_context(body),
slug=slug,
)
def resolve_planning_prompt_preview_variables(
cur,
slug: str,
body: PlanningPromptPreviewInput,
) -> Dict[str, str]:
"""Mustache-Variablen für Planungs-Prompt-Vorschau im Admin."""
s = (slug or "").strip().lower()
if s not in PLANNING_PROMPT_SLUGS:
raise ValueError(f"Kein Planungs-Prompt-Slug: {slug!r}")
goal_query = (body.goal_query or "").strip() or "Mae Geri Progression"
search_query = (body.search_query or "").strip() or goal_query
max_steps = int(body.max_steps)
brief = build_semantic_brief(goal_query)
brief_json = _compact_json(brief_to_summary_dict(brief))
goal_analysis = _sample_goal_analysis()
major_steps = _sample_major_steps(max_steps)
intent_ctx = build_planning_intent_context(
goal_query=goal_query,
goal_analysis=goal_analysis,
semantic_brief=brief,
extra_context=(body.user_notes or "").strip() or None,
)
intent_ctx_json = _compact_json(intent_ctx.to_api_dict())
ctx = _sample_planning_context()
target = _sample_target_profile()
catalogs = _load_catalog_variables(cur)
if s == "planning_progression_start_target":
return _merge_catalog_preview(
cur,
s,
{
"goal_query": goal_query,
"semantic_brief_json": brief_json,
"user_notes": (body.user_notes or "").strip(),
},
body,
)
if s == "planning_progression_goal_analysis":
return _merge_catalog_preview(
cur,
s,
{
"goal_query": goal_query,
"semantic_brief_json": brief_json,
},
body,
)
if s == "planning_progression_roadmap":
return _merge_catalog_preview(
cur,
s,
{
"goal_query": goal_query,
"goal_analysis_json": _compact_json(goal_analysis),
"semantic_brief_json": brief_json,
"max_steps": str(max_steps),
},
body,
)
if s == "planning_progression_stage_spec":
return _merge_catalog_preview(
cur,
s,
{
"goal_query": goal_query,
"goal_analysis_json": _compact_json(goal_analysis),
"major_steps_json": _compact_json(major_steps),
"intent_context_json": intent_ctx_json,
"semantic_brief_json": brief_json,
},
body,
)
if s == "planning_exercise_query_semantics":
return {
"search_query": search_query,
"semantic_brief_json": brief_json,
}
if s == "planning_exercise_path_qa":
return _merge_catalog_preview(
cur,
s,
{
"goal_query": goal_query,
"semantic_brief_json": brief_json,
"steps_json": _compact_json(_sample_path_steps()),
"gaps_json": _compact_json([]),
"bridge_inserts_json": _compact_json([]),
},
body,
)
if s == "planning_exercise_search_intent":
return {
"search_query": search_query,
"heuristic_intent": "progression_next",
"scenario_hint": "preset_next",
"planning_context_json": _compact_json(ctx),
"target_profile_json": _compact_json(target),
**catalogs,
}
if s == "planning_exercise_search_rank":
return {
"search_query": search_query,
"intent": "progression_next",
"planning_context_json": _compact_json(ctx),
"target_profile_json": _compact_json(target),
"candidates_json": _compact_json(_sample_candidates()),
"result_limit": "5",
}
if s == "planning_exercise_expectation_profile":
return {
"heuristic_intent": "suggest_next",
"planning_context_json": _compact_json(ctx),
"target_profile_json": _compact_json(target),
**{k: v for k, v in catalogs.items() if k != "style_directions_catalog_json"},
}
raise ValueError(f"Planungs-Prompt-Slug nicht implementiert: {slug!r}")
__all__ = [
"PLANNING_PROMPT_SLUGS",
"PlanningPromptPreviewInput",
"is_planning_prompt_slug",
"resolve_planning_prompt_preview_variables",
]

View File

@ -0,0 +1,125 @@
"""
Gemeinsame KI-Prompt-Laufzeit (Shinkan): DB-Lesezugriff ai_prompts + Kontext-Arten.
Bleibt ohne Import von exercise_ai (kein Zirkel). Domänen wie exercise_ai nutzen
load_ai_prompt_row und die Enum; Platzhalter bauen sie selbst oder über geteilte Builder.
"""
from __future__ import annotations
from enum import Enum
from typing import Any, Dict, Mapping, Optional, Tuple
from prompt_resolver import MustacheRenderResult, render_mustache_template
_PLANNING_AI_SLUGS = frozenset(
{
"planning_exercise_search_rank",
"planning_exercise_search_intent",
"planning_exercise_expectation_profile",
}
)
_EXERCISE_AI_SLUGS = frozenset(
{
"exercise_summary",
"exercise_skill_suggestions",
"exercise_instruction_rewrite",
}
)
class AiPromptContextKind(str, Enum):
"""
Logischer Kontext fuer Platzhalter/Builder erweiterbar fuer Planung/Rahmen
ohne bestehende Slugs zu invalidieren.
"""
PLANNING_EXERCISE_SEARCH = "planning_exercise_search"
EXERCISE_FORM_AI = "exercise_form_ai"
def context_kind_for_slug(slug: str) -> Optional[AiPromptContextKind]:
"""Ordnet einen DB-Slug einer Kontext-Art zu, sofern registriert."""
s = (slug or "").strip().lower()
if s in _PLANNING_AI_SLUGS:
return AiPromptContextKind.PLANNING_EXERCISE_SEARCH
if s in _EXERCISE_AI_SLUGS:
return AiPromptContextKind.EXERCISE_FORM_AI
return None
def load_ai_prompt_row(cur, slug: str, *, active_only: bool = True) -> Optional[Dict[str, Any]]:
"""
Laedt eine Zeile ai_prompts fuer Laufzeit-Orchestrierung.
active_only=True: inaktive Prompts werden wie fehlend behandelt (503 im Aufrufer).
"""
if active_only:
cur.execute(
"""
SELECT slug, display_name, template, output_format, active, openrouter_model
FROM ai_prompts
WHERE slug = %s AND active = true
""",
(slug,),
)
else:
cur.execute(
"""
SELECT slug, display_name, template, output_format, active, openrouter_model
FROM ai_prompts
WHERE slug = %s
""",
(slug,),
)
row = cur.fetchone()
if not row:
return None
d = dict(row)
if active_only and not d.get("active", True):
return None
return d
class AiPromptUnavailableError(LookupError):
"""Kein aktiver Prompt fuer slug (oder Zeile fehlt)."""
def __init__(self, slug: str) -> None:
self.slug = (slug or "").strip()
super().__init__(self.slug)
def render_ai_prompt_template_for_row(
row: Mapping[str, Any],
variables: Mapping[str, str],
) -> MustacheRenderResult:
"""Ersetzt Platzhalter anhand einer bereits geladenen ai_prompts-Zeile (z. B. Admin-Vorschauch, inkl. inaktiv)."""
return render_mustache_template(str(row.get("template") or ""), variables)
def load_and_render_ai_prompt(
cur,
slug: str,
variables: Mapping[str, str],
*,
active_only: bool = True,
) -> Tuple[Dict[str, Any], MustacheRenderResult]:
"""
Laedt einen aktiven Prompt und wendet Mustache-Variablen an.
Wirft AiPromptUnavailableError, wenn die Zeile fehlt oder (bei active_only) inaktiv ist.
"""
row = load_ai_prompt_row(cur, slug, active_only=active_only)
if not row:
raise AiPromptUnavailableError(slug)
rr = render_ai_prompt_template_for_row(row, variables)
return dict(row), rr
__all__ = [
"AiPromptContextKind",
"AiPromptUnavailableError",
"context_kind_for_slug",
"load_ai_prompt_row",
"load_and_render_ai_prompt",
"render_ai_prompt_template_for_row",
]

View File

@ -170,6 +170,10 @@ def get_effective_tier(profile_id: str, conn=None) -> str:
def check_feature_access(profile_id: str, feature_id: str, conn=None) -> dict:
"""
DEPRECATED für Shinkan: Mitai-v9c-Profil-Limits Schema 001 ist archiviert (Migration 078).
Für Vereins-Kontingente: club_features.check_club_feature_access(club_id, feature_id).
Check if a profile has access to a feature.
Access hierarchy:
@ -315,6 +319,8 @@ def _check_impl(profile_id: str, feature_id: str, conn) -> dict:
def increment_feature_usage(profile_id: str, feature_id: str) -> None:
"""
DEPRECATED für Shinkan siehe club_features.increment_club_feature_usage.
Increment usage counter for a feature.
Creates usage record if it doesn't exist, with reset_at based on

285
backend/capabilities.py Normal file
View File

@ -0,0 +1,285 @@
"""
Capability-Auflösung (CAPABILITY_CATALOG.v1.md, M3 C1).
Phase 2: probe_capability JSON-Log, kein Block (CAPABILITY_ENFORCE=0).
Phase 3+: CAPABILITY_ENFORCE=1 HTTP 403 bei fehlender Capability.
"""
from __future__ import annotations
import os
from typing import Any, Dict, List, Optional, TYPE_CHECKING
from fastapi import HTTPException
from account_lifecycle import account_state_satisfies
from club_tenancy import is_platform_admin
from db import get_db, get_cursor
if TYPE_CHECKING:
from tenant_context import TenantContext
def capability_enforcement_enabled() -> bool:
v = os.getenv("CAPABILITY_ENFORCE", "0").strip().lower()
return v in ("1", "true", "yes")
def club_roles_in_club(tenant: "TenantContext", club_id: Optional[int]) -> List[str]:
if club_id is None:
return []
for m in tenant.memberships or []:
if int(m.get("id") or 0) == int(club_id):
roles = m.get("roles") or []
if hasattr(roles, "tolist"):
roles = roles.tolist()
return list(roles)
return []
def check_capability(
cur,
tenant: "TenantContext",
capability_id: str,
*,
club_id: Optional[int] = None,
) -> Dict[str, Any]:
"""
Prüft eine Capability für Tenant + optionalen Vereinskontext.
Returns: allowed, reason, account_state, club_roles, linked_feature_id
"""
account_state = getattr(tenant, "account_state", "active_member")
eff_club = club_id if club_id is not None else tenant.effective_club_id
club_roles = club_roles_in_club(tenant, eff_club) if eff_club is not None else []
cur.execute(
"""
SELECT id, min_account_state, linked_feature_id, active, domain
FROM capabilities
WHERE id = %s
""",
(capability_id,),
)
cap = cur.fetchone()
if not cap or not cap.get("active"):
return {
"allowed": False,
"reason": "capability_not_found",
"account_state": account_state,
"club_roles": club_roles,
"linked_feature_id": None,
}
min_state = cap.get("min_account_state") or "active_member"
if not account_state_satisfies(account_state, min_state):
return {
"allowed": False,
"reason": "account_state_insufficient",
"account_state": account_state,
"club_roles": club_roles,
"linked_feature_id": cap.get("linked_feature_id"),
}
domain = (cap.get("domain") or "").strip().lower()
# Kontingent-Bypass (konfigurierbar per portal_role / profile grants, ohne Plattform-Admin-Pflicht)
if domain == "quota_bypass":
role_lc = (tenant.global_role or "").lower()
cur.execute(
"""
SELECT 1 FROM portal_role_capability_grants
WHERE portal_role = %s AND capability_id = %s
LIMIT 1
""",
(role_lc, capability_id),
)
if cur.fetchone():
return {
"allowed": True,
"reason": "quota_bypass_portal_grant",
"account_state": account_state,
"club_roles": club_roles,
"linked_feature_id": cap.get("linked_feature_id"),
}
cur.execute(
"""
SELECT 1 FROM profile_capability_grants
WHERE profile_id = %s AND capability_id = %s
LIMIT 1
""",
(tenant.profile_id, capability_id),
)
if cur.fetchone():
return {
"allowed": True,
"reason": "quota_bypass_profile_grant",
"account_state": account_state,
"club_roles": club_roles,
"linked_feature_id": cap.get("linked_feature_id"),
}
return {
"allowed": False,
"reason": "quota_bypass_denied",
"account_state": account_state,
"club_roles": club_roles,
"linked_feature_id": cap.get("linked_feature_id"),
}
# Plattform-Capabilities
if domain == "platform" or capability_id.startswith("platform."):
role_lc = (tenant.global_role or "").lower()
if not is_platform_admin(role_lc):
return {
"allowed": False,
"reason": "portal_role_required",
"account_state": account_state,
"club_roles": club_roles,
"linked_feature_id": cap.get("linked_feature_id"),
}
cur.execute(
"""
SELECT 1 FROM portal_role_capability_grants
WHERE portal_role = %s AND capability_id = %s
LIMIT 1
""",
(role_lc, capability_id),
)
if not cur.fetchone():
cur.execute(
"""
SELECT 1 FROM profile_capability_grants
WHERE profile_id = %s AND capability_id = %s
LIMIT 1
""",
(tenant.profile_id, capability_id),
)
if not cur.fetchone():
return {
"allowed": False,
"reason": "portal_capability_denied",
"account_state": account_state,
"club_roles": club_roles,
"linked_feature_id": cap.get("linked_feature_id"),
}
return {
"allowed": True,
"reason": "portal_granted",
"account_state": account_state,
"club_roles": club_roles,
"linked_feature_id": cap.get("linked_feature_id"),
}
# Plattform-Admin-Bypass für Mandanten-Funktionen (Audit-Pflicht, s. Katalog §9)
if is_platform_admin(tenant.global_role):
return {
"allowed": True,
"reason": "platform_admin_bypass",
"account_state": account_state,
"club_roles": club_roles,
"linked_feature_id": cap.get("linked_feature_id"),
}
# Vereins-Capabilities: aktive Mitgliedschaft im Zielverein
if min_state == "active_member":
if eff_club is None:
return {
"allowed": False,
"reason": "no_club_context",
"account_state": account_state,
"club_roles": club_roles,
"linked_feature_id": cap.get("linked_feature_id"),
}
if eff_club not in tenant.club_ids:
return {
"allowed": False,
"reason": "not_club_member",
"account_state": account_state,
"club_roles": club_roles,
"linked_feature_id": cap.get("linked_feature_id"),
}
cur.execute(
"""
SELECT role_code FROM club_role_capability_grants
WHERE capability_id = %s
""",
(capability_id,),
)
required_roles = [r["role_code"] for r in cur.fetchall()]
if required_roles:
if not any(r in required_roles for r in club_roles):
return {
"allowed": False,
"reason": "club_role_denied",
"account_state": account_state,
"club_roles": club_roles,
"linked_feature_id": cap.get("linked_feature_id"),
}
elif min_state == "active_member" and eff_club is not None:
# Offene Capability für alle aktiven Mitglieder — Mitgliedschaft reicht
pass
return {
"allowed": True,
"reason": "granted",
"account_state": account_state,
"club_roles": club_roles,
"linked_feature_id": cap.get("linked_feature_id"),
}
def resolve_capabilities_map(
cur,
tenant: "TenantContext",
*,
club_id: Optional[int] = None,
) -> Dict[str, bool]:
"""Alle aktiven Capabilities → bool (für späteres /me/entitlements)."""
cur.execute("SELECT id FROM capabilities WHERE active = true ORDER BY id")
ids = [r["id"] for r in cur.fetchall()]
out: Dict[str, bool] = {}
for cid in ids:
res = check_capability(cur, tenant, cid, club_id=club_id)
out[cid] = bool(res.get("allowed"))
return out
def probe_capability(
tenant: "TenantContext",
capability_id: str,
*,
action: str,
club_id: Optional[int] = None,
endpoint: Optional[str] = None,
conn=None,
) -> Dict[str, Any]:
"""Phase 2: Capability prüfen + JSON-Log; blockiert nur bei CAPABILITY_ENFORCE=1."""
from capability_logger import log_capability_check
def _run(c):
cur = get_cursor(c)
result = check_capability(cur, tenant, capability_id, club_id=club_id)
log_capability_check(
club_id=club_id if club_id is not None else tenant.effective_club_id,
profile_id=tenant.profile_id,
capability_id=capability_id,
action=action,
result=result,
endpoint=endpoint,
phase="enforce" if capability_enforcement_enabled() else "probe",
)
if capability_enforcement_enabled() and not result.get("allowed"):
raise HTTPException(
status_code=403,
detail=(
f"Keine Berechtigung für {capability_id} "
f"({result.get('reason', 'denied')})."
),
)
return result
if conn is not None:
return _run(conn)
with get_db() as c:
return _run(c)

View File

@ -0,0 +1,94 @@
"""
Audit: Welche Capabilities sind an Endpoints angebunden?
Für Admin-Matrix (Rollen & Rechte) und Roadmap bei neuem probe_capability hier eintragen.
"""
from __future__ import annotations
from typing import Any, Dict
# Endpoints rufen probe_capability auf (Log; Block nur bei CAPABILITY_ENFORCE=1)
WIRED_PROBE = frozenset(
{
"exercises.ai.suggest",
"exercises.ai.regenerate",
"exercises.create",
"exercises.media.upload",
"planning.ai.suggest",
"planning.ai.progression_path",
"club.creation_request.read_own",
"club.creation_request.create",
"club.creation_request.withdraw",
"platform.club_creation.approve",
}
)
# Kontingent-Verbrauch nach Erfolg (consume_club_feature_with_usage)
FEATURE_CONSUME_WIRED = frozenset(
{
"ai_calls",
}
)
def enforcement_status_for_capability(capability_id: str) -> Dict[str, Any]:
"""
Anzeige-Status für Superadmin-Matrix.
level: probe | legacy | platform | open | none
"""
cid = (capability_id or "").strip()
if cid in WIRED_PROBE:
return {
"level": "probe",
"label": "API vorbereitet (Log)",
"detail": "probe_capability am Endpoint; Hard-Block erst mit CAPABILITY_ENFORCE=1",
"implemented": True,
}
if cid.startswith("platform."):
if cid == "platform.admin.access":
return {
"level": "platform",
"label": "Plattform (Router-Guard)",
"detail": "RequireAdmin / Superadmin-Checks",
"implemented": True,
}
if cid in WIRED_PROBE:
pass
return {
"level": "platform",
"label": "Plattform (teilweise)",
"detail": "Meist Router-Guard; Capability-Probe nur wo eingetragen",
"implemented": cid in WIRED_PROBE,
}
if cid.startswith("club."):
return {
"level": "open",
"label": "Onboarding",
"detail": "Account-State / eigene Flows",
"implemented": cid in WIRED_PROBE,
}
# Vereins-Capabilities ohne Probe: Legacy club_tenancy (can_plan_in_club, has_club_role, …)
return {
"level": "legacy",
"label": "Nur Legacy-Rollen",
"detail": "Noch kein probe_capability — prüft can_plan_in_club / club_admin im Code",
"implemented": False,
}
def feature_consume_status(feature_id: str) -> Dict[str, Any]:
fid = (feature_id or "").strip()
if fid in FEATURE_CONSUME_WIRED:
return {
"level": "consume",
"label": "Verbrauch aktiv",
"detail": "consume_club_feature_with_usage + feature_usage in Response",
"implemented": True,
}
return {
"level": "inventory",
"label": "Bestand / Probe",
"detail": "Probe oder Live-Zählung; kein Consume nach Aktion",
"implemented": False,
}

View File

@ -0,0 +1,64 @@
"""
JSON-Log für Capability-Checks (M3 Phase 2 analog club_feature_logger).
"""
from __future__ import annotations
import json
import logging
import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Optional
def _log_dir() -> Path:
custom = (os.getenv("CAPABILITY_LOG_DIR") or "").strip()
if custom:
return Path(custom)
return Path("/app/logs")
capability_logger = logging.getLogger("shinkan.capability_usage")
capability_logger.setLevel(logging.INFO)
capability_logger.propagate = False
if not capability_logger.handlers:
log_dir = _log_dir()
try:
log_dir.mkdir(parents=True, exist_ok=True)
log_file = log_dir / "capability-usage.log"
file_handler = logging.FileHandler(log_file, encoding="utf-8")
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(logging.Formatter("%(message)s"))
capability_logger.addHandler(file_handler)
except OSError:
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(logging.Formatter("[capability-usage] %(message)s"))
capability_logger.addHandler(stream_handler)
def log_capability_check(
*,
club_id: Optional[int],
profile_id: Optional[int],
capability_id: str,
action: str,
result: Dict[str, Any],
endpoint: Optional[str] = None,
phase: str = "probe",
) -> None:
entry = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"club_id": club_id,
"profile_id": profile_id,
"capability": capability_id,
"action": action,
"endpoint": endpoint,
"phase": phase,
"allowed": result.get("allowed", True),
"reason": result.get("reason", "unknown"),
"account_state": result.get("account_state"),
"club_roles": result.get("club_roles"),
"enforcement": os.getenv("CAPABILITY_ENFORCE", "0") == "1",
}
capability_logger.info(json.dumps(entry, ensure_ascii=False))

View File

@ -0,0 +1,432 @@
"""
Katalog-Prompt-Slots Slot-Typ-Vokabular + Werte pro Stammdaten-Zeile (H2).
Prompts in ai_prompts referenzieren Platzhalter wie {{focus_area_hints_on_progression}}.
Inhalte liegen in catalog_prompt_slots (Admin-editierbar), nicht im Code pro Eintrag.
"""
from __future__ import annotations
import json
from dataclasses import dataclass
from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple
from planning_catalog_context import (
ProgressionPlanningCatalogContext,
PlanningCatalogContextItem,
catalog_context_has_items,
)
from catalog_slot_fallbacks import merge_stored_slots_with_fallbacks
# ---------------------------------------------------------------------------
# Dimensionen (Prioritätsreihenfolge)
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class CatalogKindConfig:
kind: str
table: str
context_attr: str
label_de: str
CATALOG_KINDS: Tuple[CatalogKindConfig, ...] = (
CatalogKindConfig("focus_area", "focus_areas", "focus_areas", "Primärfokus"),
CatalogKindConfig("training_type", "training_types", "training_types", "Trainingsstil"),
CatalogKindConfig("target_group", "target_groups", "target_groups", "Zielgruppe"),
CatalogKindConfig("style_direction", "style_directions", "style_directions", "Stilrichtung"),
)
_KIND_BY_NAME = {c.kind: c for c in CATALOG_KINDS}
# ---------------------------------------------------------------------------
# Slot-Typen (Vokabular — erweiterbar via catalog_prompt_slot_types)
# ---------------------------------------------------------------------------
SLOT_KEYS: Tuple[str, ...] = (
"description",
"hints_on_progression",
"hints_on_exercise",
"hints_on_path_qa",
"anti_patterns",
"rematch_guard",
)
LLM_SLOT_KEYS: Tuple[str, ...] = tuple(k for k in SLOT_KEYS if k != "rematch_guard")
GUIDANCE_BLOCK_SLOTS: Tuple[str, ...] = (
"description",
"hints_on_progression",
"hints_on_path_qa",
"anti_patterns",
)
GUIDANCE_PROFILE_BY_SLUG: Dict[str, Tuple[str, ...]] = {
"planning_exercise_path_qa": ("description", "hints_on_path_qa", "anti_patterns"),
"planning_progression_roadmap": ("description", "hints_on_progression", "anti_patterns"),
"planning_progression_stage_spec": ("hints_on_progression", "anti_patterns", "description"),
"planning_progression_goal_analysis": ("description", "hints_on_progression"),
"planning_progression_start_target": ("description",),
}
def placeholder_key(catalog_kind: str, slot_key: str) -> str:
return f"{catalog_kind}_{slot_key}"
def all_placeholder_keys() -> List[str]:
keys: List[str] = []
for cfg in CATALOG_KINDS:
for slot in SLOT_KEYS:
keys.append(placeholder_key(cfg.kind, slot))
keys.extend(["catalog_guidance_block", "catalog_context_json", "has_catalog_guidance"])
return keys
def empty_catalog_variables() -> Dict[str, str]:
out = {k: "" for k in all_placeholder_keys()}
return out
# ---------------------------------------------------------------------------
# Katalog-Kontext → aktiver Eintrag
# ---------------------------------------------------------------------------
def pick_active_catalog_item(
items: Sequence[PlanningCatalogContextItem],
) -> Optional[PlanningCatalogContextItem]:
if not items:
return None
primaries = [i for i in items if i.is_primary]
if primaries:
return primaries[0]
if len(items) == 1:
return items[0]
return max(items, key=lambda i: (float(i.weight), -int(i.id)))
def _load_catalog_row(cur, table: str, item_id: int) -> Optional[Dict[str, Any]]:
cur.execute(
f"""
SELECT id, name, description
FROM {table}
WHERE id = %s
""",
(int(item_id),),
)
row = cur.fetchone()
if not row:
return None
return {
"id": int(row["id"]),
"name": str(row.get("name") or "").strip(),
"description": str(row.get("description") or "").strip(),
}
def _load_slots_for_entry(cur, catalog_kind: str, catalog_id: int) -> Dict[str, str]:
cur.execute(
"""
SELECT slot_key, content
FROM catalog_prompt_slots
WHERE catalog_kind = %s AND catalog_id = %s
""",
(catalog_kind, int(catalog_id)),
)
out: Dict[str, str] = {}
for row in cur.fetchall():
key = str(row.get("slot_key") or "").strip()
if key:
out[key] = str(row.get("content") or "").strip()
return out
def _slot_types_table_ready(cur) -> bool:
cur.execute("SELECT to_regclass(%s)::text AS t", ("public.catalog_prompt_slot_types",))
row = cur.fetchone()
if not row:
return False
val = row.get("t") if isinstance(row, dict) else row[0]
return val is not None and str(val).strip() != ""
def list_slot_type_definitions(cur) -> List[Dict[str, Any]]:
if not _slot_types_table_ready(cur):
return _fallback_slot_type_rows()
cur.execute(
"""
SELECT slot_key, display_name, description, applicable_kinds, sort_order, for_llm, for_code
FROM catalog_prompt_slot_types
ORDER BY sort_order ASC NULLS LAST, slot_key ASC
"""
)
rows = []
for row in cur.fetchall():
d = dict(row)
kinds = d.get("applicable_kinds")
if isinstance(kinds, str):
kinds = [k.strip() for k in kinds.strip("{}").split(",") if k.strip()]
d["applicable_kinds"] = list(kinds or [])
rows.append(d)
return rows
def _fallback_slot_type_rows() -> List[Dict[str, Any]]:
labels = {
"description": "Allgemeine Beschreibung",
"hints_on_progression": "Hinweise Progressionsgraph",
"hints_on_exercise": "Hinweise Übungsanlage",
"hints_on_path_qa": "Hinweise Pfad-QS",
"anti_patterns": "Anti-Patterns (Fehlbewertung vermeiden)",
"rematch_guard": "Rematch-Guard (Code)",
}
kinds = [c.kind for c in CATALOG_KINDS]
rows = []
for i, key in enumerate(SLOT_KEYS):
rows.append(
{
"slot_key": key,
"display_name": labels.get(key, key),
"description": "",
"applicable_kinds": kinds,
"sort_order": (i + 1) * 10,
"for_llm": key != "rematch_guard",
"for_code": key == "rematch_guard",
}
)
return rows
def _resolve_entry_slot_values(
stored: Mapping[str, str],
row: Mapping[str, Any],
catalog_kind: str,
) -> Dict[str, str]:
"""DB → Namens-Fallback → Stammdaten-Beschreibung (nur description)."""
return merge_stored_slots_with_fallbacks(
stored,
catalog_kind=catalog_kind,
name=str(row.get("name") or ""),
stammdaten_description=str(row.get("description") or ""),
)
def get_catalog_entry_slots(cur, catalog_kind: str, catalog_id: int) -> Dict[str, Any]:
cfg = _KIND_BY_NAME.get((catalog_kind or "").strip())
if not cfg:
raise ValueError(f"Unbekannter catalog_kind: {catalog_kind!r}")
row = _load_catalog_row(cur, cfg.table, catalog_id)
if not row:
raise LookupError("Katalog-Eintrag nicht gefunden")
stored = _load_slots_for_entry(cur, cfg.kind, catalog_id)
merged = _resolve_entry_slot_values(stored, row, cfg.kind)
return {
"catalog_kind": cfg.kind,
"catalog_id": int(catalog_id),
"name": row["name"],
"slots": merged,
"stored_slots": {k: stored.get(k, "") for k in SLOT_KEYS},
}
def upsert_catalog_entry_slots(
cur,
catalog_kind: str,
catalog_id: int,
slots: Mapping[str, Any],
) -> Dict[str, Any]:
cfg = _KIND_BY_NAME.get((catalog_kind or "").strip())
if not cfg:
raise ValueError(f"Unbekannter catalog_kind: {catalog_kind!r}")
row = _load_catalog_row(cur, cfg.table, catalog_id)
if not row:
raise LookupError("Katalog-Eintrag nicht gefunden")
for slot_key, raw in (slots or {}).items():
sk = str(slot_key or "").strip()
if sk not in SLOT_KEYS:
continue
content = str(raw or "").strip()
if not content:
cur.execute(
"""
DELETE FROM catalog_prompt_slots
WHERE catalog_kind = %s AND catalog_id = %s AND slot_key = %s
""",
(cfg.kind, int(catalog_id), sk),
)
continue
cur.execute(
"""
INSERT INTO catalog_prompt_slots (catalog_kind, catalog_id, slot_key, content, updated_at)
VALUES (%s, %s, %s, %s, NOW())
ON CONFLICT (catalog_kind, catalog_id, slot_key)
DO UPDATE SET content = EXCLUDED.content, updated_at = NOW()
""",
(cfg.kind, int(catalog_id), sk, content),
)
return get_catalog_entry_slots(cur, cfg.kind, catalog_id)
def _render_dimension_section(
label_de: str,
name: str,
slot_values: Mapping[str, str],
*,
slot_keys: Sequence[str],
) -> Optional[str]:
parts: List[str] = [f"### {label_de}{name}"]
labels = {
"description": "Beschreibung",
"hints_on_progression": "Progressions-Hinweise",
"hints_on_path_qa": "QS-Hinweise",
"hints_on_exercise": "Übungsanlage",
"anti_patterns": "Vermeiden",
}
added = False
for sk in slot_keys:
text = str(slot_values.get(sk) or "").strip()
if not text:
continue
added = True
if sk == "description":
parts.append(text)
else:
parts.append(f"{labels.get(sk, sk)}: {text}")
if not added:
return None
return "\n".join(parts)
def _compose_guidance_block(
sections: List[str],
) -> str:
if not sections:
return ""
return "## Katalog-Kontext (Didaktik & Bewertung)\n\n" + "\n\n".join(sections)
def resolve_catalog_prompt_variables(
cur,
catalog: Optional[ProgressionPlanningCatalogContext],
*,
slug: Optional[str] = None,
) -> Dict[str, Any]:
"""
Liefert Mustache-Strings + Metadaten.
Returns dict mit allen {{kind_slot}} Keys, catalog_guidance_block, catalog_context_json,
has_catalog_guidance (bool), active_slots (list).
"""
variables = empty_catalog_variables()
meta: Dict[str, Any] = {
"active_slots": [],
"audit": {},
}
if cur is None or not catalog_context_has_items(catalog):
variables["catalog_context_json"] = ""
return {**variables, **meta}
profile = GUIDANCE_PROFILE_BY_SLUG.get((slug or "").strip().lower(), GUIDANCE_BLOCK_SLOTS)
sections: List[str] = []
audit: Dict[str, Any] = {}
has_any = False
active_slots: List[str] = []
for cfg in CATALOG_KINDS:
items = getattr(catalog, cfg.context_attr, None) or []
active = pick_active_catalog_item(items)
if not active:
continue
row = _load_catalog_row(cur, cfg.table, active.id)
if not row:
continue
stored = _load_slots_for_entry(cur, cfg.kind, row["id"]) if _slot_types_table_ready(cur) else {}
slot_values = _resolve_entry_slot_values(stored, row, cfg.kind)
for sk in SLOT_KEYS:
pk = placeholder_key(cfg.kind, sk)
text = slot_values.get(sk, "")
variables[pk] = text
if text.strip() and sk in LLM_SLOT_KEYS:
has_any = True
active_slots.append(pk)
audit[cfg.context_attr] = {
"catalog_kind": cfg.kind,
"id": row["id"],
"name": row["name"],
"is_primary": bool(active.is_primary),
"weight": float(active.weight),
"filled_slots": [k for k in LLM_SLOT_KEYS if slot_values.get(k, "").strip()],
"stored_slots": [k for k in SLOT_KEYS if (stored.get(k) or "").strip()],
}
section = _render_dimension_section(cfg.label_de, row["name"], slot_values, slot_keys=profile)
if section:
sections.append(section)
variables["catalog_guidance_block"] = _compose_guidance_block(sections)
ctx_json = json.dumps(audit, ensure_ascii=False, separators=(",", ":"))
variables["catalog_context_json"] = f"Katalog-Audit: {ctx_json}" if audit else ""
variables["has_catalog_guidance"] = "true" if has_any else ""
return {
**variables,
"active_slots": active_slots,
"audit": audit,
}
def get_rematch_guard_for_catalog(
cur,
catalog: Optional[ProgressionPlanningCatalogContext],
) -> Optional[str]:
"""Erste passende rematch_guard entlang der Dimensions-Priorität."""
if cur is None or not catalog_context_has_items(catalog):
return None
for cfg in CATALOG_KINDS:
items = getattr(catalog, cfg.context_attr, None) or []
active = pick_active_catalog_item(items)
if not active:
continue
stored = _load_slots_for_entry(cur, cfg.kind, active.id)
row = _load_catalog_row(cur, cfg.table, active.id)
if not row:
continue
slot_values = _resolve_entry_slot_values(stored, row, cfg.kind)
guard = (slot_values.get("rematch_guard") or "").strip()
if guard:
return guard
return None
# Abwärtskompatibilität H1-API
def build_catalog_guidance_for_prompt(
cur,
catalog: Optional[ProgressionPlanningCatalogContext],
*,
slug: Optional[str] = None,
) -> Dict[str, Any]:
resolved = resolve_catalog_prompt_variables(cur, catalog, slug=slug)
return {
"catalog_guidance_block": resolved.get("catalog_guidance_block", ""),
"catalog_context_json": resolved.get("catalog_context_json", ""),
"has_catalog_guidance": resolved.get("has_catalog_guidance") == "true",
"snippet_keys": list(resolved.get("active_slots") or []),
"variables": {k: str(resolved.get(k) or "") for k in all_placeholder_keys()},
}
__all__ = [
"CATALOG_KINDS",
"GUIDANCE_PROFILE_BY_SLUG",
"SLOT_KEYS",
"build_catalog_guidance_for_prompt",
"empty_catalog_variables",
"get_catalog_entry_slots",
"get_rematch_guard_for_catalog",
"list_slot_type_definitions",
"pick_active_catalog_item",
"placeholder_key",
"all_placeholder_keys",
"resolve_catalog_prompt_variables",
"upsert_catalog_entry_slots",
]

View File

@ -0,0 +1,284 @@
"""
Namensbasierte Fallback-Slots bis Admin/DB befüllt sind (H1-Registry-Inhalt).
DB-Werte in catalog_prompt_slots haben immer Vorrang. Fallbacks füllen nur leere Slot-Keys.
"""
from __future__ import annotations
import re
import unicodedata
from typing import Dict, Mapping, Optional, Sequence, Tuple
_UMLAUT_MAP = str.maketrans({"ä": "ae", "ö": "oe", "ü": "ue", "ß": "ss", "Ä": "ae", "Ö": "oe", "Ü": "ue"})
SlotPack = Dict[str, str]
# (catalog_kind, name_pattern_lower) — erste passende Regel gewinnt; * = Default pro Kind
_FALLBACK_RULES: Tuple[Tuple[str, str, SlotPack], ...] = (
# --- focus_area ---
(
"focus_area",
"gewaltschutz",
{
"description": (
"Planung zielt auf Prävention, Deeskalation, Grenzen und sichere Übungsformen — "
"nicht auf Wettkampf-Perfektion oder Technik-Show."
),
"hints_on_progression": (
"Phasen: Wahrnehmung → Grenzen → Deeskalation → sichere Übungsformen; "
"keine Kumite-Perfektionsstufen erzwingen."
),
"hints_on_exercise": (
"Übungen mit Rollen, Kommunikation, Ausweichen; keine rein technischen Kick-Fokus-Inseln ohne Bezug."
),
"hints_on_path_qa": (
"Gute Pfade bauen Sicherheit, Kommunikation und Alternativen auf; "
"„Lücken“ sind fehlende Deeskalations- oder Rollenspiel-Stufen, nicht fehlende Kick-Varianten."
),
"anti_patterns": "Nicht nach Kumite-Tiefe, Explosivität oder Wettkampf-Belastung bewerten.",
},
),
(
"focus_area",
"selbstverteidigung",
{
"description": (
"Praktische Selbstverteidigung: realistische Szenarien, Sicherheit und "
"anwendungsnahe Progression — nicht Show-Technik oder Wettkampf-Kata."
),
"hints_on_progression": (
"Von Wahrnehmung und Distanz zu einfachen Abwehrmustern und kontrollierter Anwendung."
),
"hints_on_exercise": "Partnerübungen mit klaren Sicherheitsregeln; Szenario-Bezug wichtiger als Stil-Show.",
"hints_on_path_qa": (
"Lücken bei Szenario- oder Sicherheitsstufen sind relevant; "
"fehlende Kick-Varianten oder Wettkampftiefe sind kein Mangel."
),
"anti_patterns": "Keine Wettkampf- oder Kata-Perfektion als QS-Maßstab.",
},
),
(
"focus_area",
"fitness",
{
"description": (
"Fitness- und Konditionsorientierung mit sicherer Belastungssteuerung; "
"Technikbezug nur wo fachlich sinnvoll."
),
"hints_on_progression": "Progression von niedriger zu moderater Belastung; klare Pausen und Technikhygiene.",
"hints_on_path_qa": (
"Keine Wettkampf-Spezialisierung als Pflicht-Kriterium; "
"Belastungssteigerung ohne Technikbezug abwerten."
),
"anti_patterns": "Keine Kumite-Perfektion oder Wettkampf-Kombinationen als QS-Maßstab verlangen.",
},
),
(
"focus_area",
"karate",
{
"description": (
"Technik-Curriculum im Karate-Kontext: aufeinander aufbauende Kihon-Progression "
"mit klaren Qualitätsankern (Stand, Hüfte, Kime)."
),
"hints_on_progression": (
"Typische Phasen: Einstieg → Grundlagen → Koordination/Kraft → Anwendung → optional Vertiefung; "
"Grundlagen vor Perfektion."
),
"hints_on_exercise": (
"Kihon und Partnerübungen mit Technikbezug; reine Kraft-/Ausdauer-Inseln nur mit klarer Begründung."
),
"hints_on_path_qa": (
"Kohärente Progression Grundlagen → Anwendung → Vertiefung; "
"Übergänge ohne Sprünge; themenfremde Kraft-/Ausdauer-Inseln abwerten."
),
"anti_patterns": "Keine pauschale Perfektions-Stufe verlangen, wenn Kontext Breitensport ist.",
},
),
(
"focus_area",
"*",
{
"description": "Technik- oder Themen-Curriculum mit didaktisch aufeinander aufbauenden Stufen.",
"hints_on_progression": "Grundlagen vor Anwendung; moderate Sprünge zwischen Stufen vermeiden.",
"hints_on_path_qa": (
"Kohärente Progression zum Anfrage-Thema; "
"Lücken sind fehlende Zwischenstufen im Lernpfad, nicht fehlende Nebenthemen."
),
"hints_on_exercise": "Übungen mit klarem Bezug zum Pfad-Thema und zur Stufe.",
},
),
# --- training_type ---
(
"training_type",
"breitensport",
{
"description": (
"Partizipation, Verständlichkeit, Freude am Bewegen; weniger maximale Spezialisierung."
),
"hints_on_progression": "Moderater Schwierigkeitsanstieg; Perfektionsphasen optional.",
"hints_on_path_qa": (
"Hohe OK-Rate bei moderatem Schwierigkeitsanstieg; "
"„Perfektion“-Stufen nur optional, nicht als Pflicht-Lücke."
),
"rematch_guard": "Keine leeren Slots erzwingen, nur um eine Leistungs-Perfektionsstufe zu füllen.",
},
),
(
"training_type",
"leistungssport",
{
"description": "Leistungsorientiertes Training mit höherer Anspruchskurve und Spezialisierung.",
"hints_on_progression": "Belastungs- und Kombinationsprogressionen sind erwünscht.",
"hints_on_path_qa": (
"Höhere Anspruchskurven sind passend; Lücken in Spezialisierung können echte Hinweise sein."
),
},
),
(
"training_type",
"wettkampf",
{
"description": (
"Wettkampforientiertes Training mit höherer Anspruchskurve und belastungsnahen Phasen."
),
"hints_on_progression": "Anwendungs- und Druckphasen zeitig einplanen.",
"hints_on_path_qa": (
"Spezialisierung, Kombination und Belastung unter Druck sind relevant; "
"Lücken in Anwendungs- oder Perfektionsphasen können echte Hinweise sein."
),
},
),
(
"training_type",
"*",
{
"hints_on_path_qa": "Didaktische Kohärenz wichtiger als maximale Spezialisierung — Kontext beachten.",
},
),
# --- target_group ---
(
"target_group",
"kinder",
{
"description": (
"Kinder: kurze Einheiten, spielerische Einstiege, Sicherheit und altersgerechte Komplexität."
),
"hints_on_progression": "Spielerische Einstiege; kurze Abschnitte; Sicherheit vor Perfektion.",
"hints_on_path_qa": (
"Didaktik ohne Überforderung; klare Regeln und Sicherheit vor Perfektion; "
"Lücken bei Spiel-/Rollenelementen wichtiger als Wettkampftiefe."
),
"anti_patterns": "Keine Erwachsenen-Wettkampf-Perfektion als QS-Maßstab.",
},
),
(
"target_group",
"leistungssportler",
{
"description": "Leistungsgruppe: höhere Anspruchskurven und Spezialisierung sind fachlich passend.",
"hints_on_progression": "Anspruchskurve und Spezialisierung dürfen steiler sein.",
"hints_on_path_qa": (
"Höhere Anspruchskurven, Belastungs- und Kombinationsprogressionen sind relevant; "
"Lücken in Spezialisierung können echte Hinweise sein."
),
},
),
(
"target_group",
"breitensportler",
{
"description": "Breitensport: Partizipation und Verständlichkeit vor maximaler Spezialisierung.",
"hints_on_path_qa": (
"Moderate Progression; Perfektions-Lücken sind selten echte Mängel."
),
"anti_patterns": "Keine Leistungssport-Perfektion als Pflicht-Kriterium.",
},
),
(
"target_group",
"*",
{
"hints_on_path_qa": "Zielgruppe im Tempo und in der Komplexität berücksichtigen.",
},
),
# --- style_direction ---
(
"style_direction",
"shotokan",
{
"description": (
"Shotokan-Linie: klare Kihon-Struktur, Hüft- und Standarbeit als wiederkehrende Qualitätsanker."
),
"hints_on_progression": "Nuancen in Stellung und Hüfttechnik; kein neuer Planungstyp.",
"hints_on_path_qa": "Konsistenz von Stand, Hüfte und Kime entlang des Pfads bewerten.",
},
),
(
"style_direction",
"*",
{
"hints_on_progression": (
"Stil-spezifische Nuancen (Stand, Hüfte, Rhythmus) einbeziehen — ohne Stilwechsel zu erzwingen."
),
},
),
)
def normalize_catalog_name_key(name: str) -> str:
s = unicodedata.normalize("NFKD", (name or "").translate(_UMLAUT_MAP))
s = s.encode("ascii", "ignore").decode("ascii").lower()
s = re.sub(r"[^a-z0-9]+", "_", s).strip("_")
return s or "unknown"
def get_fallback_slots_for_entry(catalog_kind: str, name: str) -> SlotPack:
kind = (catalog_kind or "").strip().lower()
norm = normalize_catalog_name_key(name)
default: SlotPack = {}
for rule_kind, pattern, pack in _FALLBACK_RULES:
if rule_kind != kind:
continue
if pattern == "*":
default = dict(pack)
continue
if pattern in norm or norm.startswith(pattern) or pattern in (name or "").lower():
return dict(pack)
return default
def merge_stored_slots_with_fallbacks(
stored: Mapping[str, str],
*,
catalog_kind: str,
name: str,
stammdaten_description: str = "",
) -> Dict[str, str]:
"""DB + Stammdaten-Beschreibung + Namens-Fallback."""
fallbacks = get_fallback_slots_for_entry(catalog_kind, name)
out: Dict[str, str] = {}
for key in (
"description",
"hints_on_progression",
"hints_on_exercise",
"hints_on_path_qa",
"anti_patterns",
"rematch_guard",
):
if key == "description":
out[key] = (
(stored.get(key) or "").strip()
or (fallbacks.get(key) or "").strip()
or (stammdaten_description or "").strip()
)
else:
out[key] = (stored.get(key) or "").strip() or (fallbacks.get(key) or "").strip()
return out
__all__ = [
"get_fallback_slots_for_entry",
"merge_stored_slots_with_fallbacks",
"normalize_catalog_name_key",
]

View File

@ -0,0 +1,74 @@
"""
JSON-Log für Vereins-Feature-Zugriffe (Phase 2: nur Monitoring, kein Block).
Spez: CLUB_MEMBERSHIP_AND_FEATURES.v1.md §9 Phase 2 analog Mitai feature_logger.py.
"""
from __future__ import annotations
import json
import logging
import os
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Optional
def _log_dir() -> Path:
custom = (os.getenv("CLUB_FEATURE_LOG_DIR") or "").strip()
if custom:
return Path(custom)
return Path("/app/logs")
feature_usage_logger = logging.getLogger("shinkan.club_feature_usage")
feature_usage_logger.setLevel(logging.INFO)
feature_usage_logger.propagate = False
if not feature_usage_logger.handlers:
log_dir = _log_dir()
try:
log_dir.mkdir(parents=True, exist_ok=True)
log_file = log_dir / "club-feature-usage.log"
file_handler = logging.FileHandler(log_file, encoding="utf-8")
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(logging.Formatter("%(message)s"))
feature_usage_logger.addHandler(file_handler)
except OSError:
# Dev ohne /app/logs: Fallback stderr
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(logging.Formatter("[club-feature-usage] %(message)s"))
feature_usage_logger.addHandler(stream_handler)
def log_club_feature_usage(
*,
club_id: Optional[int],
profile_id: Optional[int],
feature_id: str,
action: str,
access: Dict[str, Any],
endpoint: Optional[str] = None,
phase: str = "probe",
) -> None:
"""
Strukturiertes JSON-Log eines Feature-Checks.
phase: probe (Phase 2, non-blocking) | enforce (Phase 4, nach Block-Entscheid)
"""
entry = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"club_id": club_id,
"profile_id": profile_id,
"feature": feature_id,
"action": action,
"endpoint": endpoint,
"phase": phase,
"plan_id": access.get("plan_id"),
"used": access.get("used", 0),
"limit": access.get("limit"),
"remaining": access.get("remaining"),
"allowed": access.get("allowed", True),
"reason": access.get("reason", "unknown"),
"enforcement": os.getenv("CLUB_FEATURE_ENFORCE", "0") == "1",
}
feature_usage_logger.info(json.dumps(entry, ensure_ascii=False))

713
backend/club_features.py Normal file
View File

@ -0,0 +1,713 @@
"""
Vereinsbezogene Feature-Limits (Mitai-v9c-Pattern, Subjekt club_id).
Spez: .claude/docs/technical/CLUB_MEMBERSHIP_AND_FEATURES.v1.md
Phase 2 (M2): probe_club_feature_access JSON-Log, kein HTTP-Block.
Phase 4 (M5+): CLUB_FEATURE_ENFORCE=1 HTTP 403 + increment.
Verbrauch-Standard für Router:
probe_club_feature_access Business-Logik consume_club_feature_with_usage merge_feature_usage_into_response
Legacy profil-zentriert: auth.check_feature_access (001 / Mitai-Überbleibsel) nicht für Shinkan-Limits nutzen.
"""
from __future__ import annotations
import os
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, Optional, TYPE_CHECKING
from fastapi import HTTPException
from db import get_db, get_cursor
if TYPE_CHECKING:
from tenant_context import TenantContext
# Bestands-Features: Verbrauch = Live-Zählung in DB (nicht club_feature_usage)
_INVENTORY_FEATURES = frozenset(
{"exercises", "training_groups", "active_members", "training_programs"}
)
def _calculate_next_reset(reset_period: str, *, now: Optional[datetime] = None) -> Optional[datetime]:
"""Nächster Reset-Zeitpunkt; None bei 'never'."""
ref = now or datetime.now(timezone.utc)
if reset_period == "never":
return None
if reset_period == "daily":
tomorrow = ref.date() + timedelta(days=1)
return datetime.combine(tomorrow, datetime.min.time(), tzinfo=timezone.utc)
if reset_period == "monthly":
if ref.month == 12:
return datetime(ref.year + 1, 1, 1, tzinfo=timezone.utc)
return datetime(ref.year, ref.month + 1, 1, tzinfo=timezone.utc)
return None
def _normalize_limit(raw: Any) -> Optional[int]:
"""NULL = unbegrenzt; -1 (Legacy 001) wird als unbegrenzt behandelt."""
if raw is None:
return None
try:
v = int(raw)
except (TypeError, ValueError):
return None
if v < 0:
return None
return v
def get_effective_club_plan(cur, club_id: int) -> str:
"""
Effektiver Plan für einen Verein.
1. Aktiver club_access_grants mit plan_id (Zeitfenster, neueste ends_at)
2. club_subscriptions.status = 'active' plan_id
3. Fallback 'free'
"""
cur.execute(
"""
SELECT plan_id
FROM club_access_grants
WHERE club_id = %s
AND plan_id IS NOT NULL
AND starts_at <= NOW()
AND ends_at > NOW()
ORDER BY ends_at DESC
LIMIT 1
""",
(club_id,),
)
grant = cur.fetchone()
if grant and grant.get("plan_id"):
return str(grant["plan_id"])
cur.execute(
"""
SELECT plan_id
FROM club_subscriptions
WHERE club_id = %s AND status = 'active'
LIMIT 1
""",
(club_id,),
)
sub = cur.fetchone()
if sub and sub.get("plan_id"):
return str(sub["plan_id"])
return "free"
def _resolve_club_limit(cur, club_id: int, feature_id: str, feature_row: dict) -> Optional[int]:
"""Limit-Wert: Override > Plan > Feature-Default."""
cur.execute(
"""
SELECT limit_value
FROM club_feature_overrides
WHERE club_id = %s AND feature_id = %s
""",
(club_id, feature_id),
)
override = cur.fetchone()
if override is not None:
return _normalize_limit(override.get("limit_value"))
plan_id = get_effective_club_plan(cur, club_id)
cur.execute(
"""
SELECT limit_value
FROM club_plan_limits
WHERE plan_id = %s AND feature_id = %s
""",
(plan_id, feature_id),
)
plan_lim = cur.fetchone()
if plan_lim is not None:
return _normalize_limit(plan_lim.get("limit_value"))
return _normalize_limit(feature_row.get("default_limit"))
def _live_inventory_count(cur, club_id: int, feature_id: str) -> Optional[int]:
"""Aktueller Bestand für reset_period=never Features."""
if feature_id == "exercises":
cur.execute(
"""
SELECT COUNT(*)::int AS c
FROM exercises
WHERE club_id = %s AND status != 'archived'
""",
(club_id,),
)
elif feature_id == "training_groups":
cur.execute(
"SELECT COUNT(*)::int AS c FROM training_groups WHERE club_id = %s",
(club_id,),
)
elif feature_id == "active_members":
cur.execute(
"""
SELECT COUNT(*)::int AS c
FROM club_members
WHERE club_id = %s AND status = 'active'
""",
(club_id,),
)
elif feature_id == "training_programs":
cur.execute(
"""
SELECT COUNT(*)::int AS c FROM (
SELECT id FROM training_framework_programs WHERE club_id = %s
UNION ALL
SELECT id FROM training_modules WHERE club_id = %s
) t
""",
(club_id, club_id),
)
else:
return None
row = cur.fetchone()
return int(row["c"] or 0) if row else 0
def resolve_club_id_for_probe(
tenant: "TenantContext",
*,
object_club_id: Optional[int] = None,
) -> Optional[int]:
"""Verein für Feature-Probe: explizites Objekt > effective_club_id."""
if object_club_id is not None:
return int(object_club_id)
eff = getattr(tenant, "effective_club_id", None)
return int(eff) if eff is not None else None
def _maybe_reset_usage(cur, conn, club_id: int, feature_id: str, feature_row: dict, usage_row: Optional[dict]) -> int:
"""Setzt Zähler zurück wenn reset_at überschritten; gibt aktuellen used zurück."""
used = int(usage_row.get("usage_count") or 0) if usage_row else 0
reset_at = usage_row.get("reset_at") if usage_row else None
period = (feature_row.get("reset_period") or "never").strip().lower()
if not usage_row or not reset_at or period == "never":
return used
now = datetime.now(timezone.utc)
ra = reset_at
if hasattr(ra, "tzinfo") and ra.tzinfo is None:
ra = ra.replace(tzinfo=timezone.utc)
if ra and now > ra:
next_reset = _calculate_next_reset(period, now=now)
cur.execute(
"""
UPDATE club_feature_usage
SET usage_count = 0, reset_at = %s, updated_at = NOW()
WHERE club_id = %s AND feature_id = %s
""",
(next_reset, club_id, feature_id),
)
conn.commit()
return 0
return used
def check_club_feature_access(
club_id: int,
feature_id: str,
*,
conn=None,
) -> Dict[str, Any]:
"""
Prüft Vereins-Kontingent für ein Feature.
Returns:
allowed, limit, used, remaining, reason, plan_id, reset_at (optional)
"""
if conn is not None:
return _check_club_impl(club_id, feature_id, conn)
with get_db() as c:
return _check_club_impl(club_id, feature_id, c)
def _check_club_impl(club_id: int, feature_id: str, conn) -> Dict[str, Any]:
cur = get_cursor(conn)
cur.execute(
"""
SELECT id, limit_type, reset_period, default_limit, active, enforcement_subject
FROM features
WHERE id = %s AND app = 'shinkan'
""",
(feature_id,),
)
feature = cur.fetchone()
if not feature or not feature.get("active"):
return {
"allowed": False,
"limit": None,
"used": 0,
"remaining": None,
"reason": "feature_not_found",
"plan_id": get_effective_club_plan(cur, club_id),
}
plan_id = get_effective_club_plan(cur, club_id)
limit = _resolve_club_limit(cur, club_id, feature_id, feature)
limit_type = (feature.get("limit_type") or "count").strip().lower()
if limit_type == "boolean":
allowed = limit == 1
return {
"allowed": allowed,
"limit": limit,
"used": 0,
"remaining": None,
"reason": "enabled" if allowed else "feature_disabled",
"plan_id": plan_id,
}
cur.execute(
"""
SELECT usage_count, reset_at
FROM club_feature_usage
WHERE club_id = %s AND feature_id = %s
""",
(club_id, feature_id),
)
usage = cur.fetchone()
used = _maybe_reset_usage(cur, conn, club_id, feature_id, feature, usage)
period = (feature.get("reset_period") or "never").strip().lower()
if period == "never" and feature_id in _INVENTORY_FEATURES:
inv = _live_inventory_count(cur, club_id, feature_id)
if inv is not None:
used = inv
if limit is None:
return {
"allowed": True,
"limit": None,
"used": used,
"remaining": None,
"reason": "unlimited",
"plan_id": plan_id,
"reset_at": usage.get("reset_at") if usage else None,
}
if limit == 0:
return {
"allowed": False,
"limit": 0,
"used": used,
"remaining": 0,
"reason": "feature_disabled",
"plan_id": plan_id,
"reset_at": usage.get("reset_at") if usage else None,
}
allowed = used < limit
return {
"allowed": allowed,
"limit": limit,
"used": used,
"remaining": max(0, limit - used),
"reason": "within_limit" if allowed else "limit_exceeded",
"plan_id": plan_id,
"reset_at": usage.get("reset_at") if usage else None,
}
def club_feature_enforcement_enabled() -> bool:
"""Phase 4: Hard-Block aktiv (Env CLUB_FEATURE_ENFORCE=1|true|yes)."""
v = os.getenv("CLUB_FEATURE_ENFORCE", "0").strip().lower()
return v in ("1", "true", "yes")
def probe_club_feature_access(
*,
feature_id: str,
action: str,
club_id: Optional[int] = None,
profile_id: Optional[int] = None,
portal_role: Optional[str] = None,
endpoint: Optional[str] = None,
tenant: Optional["TenantContext"] = None,
conn=None,
) -> Dict[str, Any]:
"""
Phase 2: Prüft Vereins-Kontingent, schreibt JSON-Log, blockiert standardmäßig nicht.
Bei CLUB_FEATURE_ENFORCE=1: HTTP 403 wenn nicht allowed.
"""
from club_feature_logger import log_club_feature_usage
if club_id is None:
access = {
"allowed": not club_feature_enforcement_enabled(),
"limit": None,
"used": 0,
"remaining": None,
"reason": "no_club_context",
"plan_id": None,
}
log_club_feature_usage(
club_id=None,
profile_id=profile_id,
feature_id=feature_id,
action=action,
access=access,
endpoint=endpoint,
phase="enforce" if club_feature_enforcement_enabled() else "probe",
)
if club_feature_enforcement_enabled() and not access.get("allowed"):
raise HTTPException(
status_code=403,
detail=(
f"Kein Vereinskontext für {feature_id}"
"aktiven Verein wählen (X-Active-Club-Id)."
),
)
return access
def _resolve_access(connection):
from club_quota_bypass import is_club_feature_quota_bypassed, quota_bypass_access
cur = get_cursor(connection)
if is_club_feature_quota_bypassed(
cur,
profile_id=profile_id,
portal_role=portal_role,
feature_id=feature_id,
tenant=tenant,
):
plan_id = get_effective_club_plan(cur, int(club_id))
return quota_bypass_access(
feature_id=feature_id,
club_id=int(club_id),
plan_id=plan_id,
)
return check_club_feature_access(club_id, feature_id, conn=connection)
if conn is not None:
access = _resolve_access(conn)
else:
with get_db() as c:
access = _resolve_access(c)
log_club_feature_usage(
club_id=club_id,
profile_id=profile_id,
feature_id=feature_id,
action=action,
access=access,
endpoint=endpoint,
phase="enforce" if club_feature_enforcement_enabled() else "probe",
)
if club_feature_enforcement_enabled() and not access.get("allowed"):
limit = access.get("limit")
used = access.get("used", 0)
detail = (
f"Kontingent überschritten für {feature_id} "
f"({used}/{limit if limit is not None else ''}). "
f"Grund: {access.get('reason', 'limit_exceeded')}."
)
raise HTTPException(status_code=403, detail=detail)
return access
def consume_club_feature(
*,
feature_id: str,
club_id: Optional[int],
profile_id: Optional[int] = None,
portal_role: Optional[str] = None,
action: Optional[str] = None,
amount: int = 1,
conn=None,
) -> None:
"""
Phase 4 (M5): Zähler nach erfolgreichem Verbrauch erhöhen.
Nur wenn club_id gesetzt (Vereins-Kontingent); amount = Anzahl LLM/API-Verbrauchseinheiten.
Plattform-Ausnahmen (superadmin, konfigurierte Rollen/Profile) werden nicht gezählt.
"""
if club_id is None:
return
def _is_exempt(connection) -> bool:
from club_quota_bypass import is_club_feature_quota_bypassed
cur = get_cursor(connection)
return is_club_feature_quota_bypassed(
cur,
profile_id=profile_id,
portal_role=portal_role,
feature_id=feature_id,
)
if conn is not None:
if _is_exempt(conn):
return
else:
with get_db() as c:
if _is_exempt(c):
return
try:
n = int(amount)
except (TypeError, ValueError):
n = 1
if n < 1:
return
for _ in range(n):
increment_club_feature_usage(
int(club_id),
feature_id,
profile_id=profile_id,
action=action,
conn=conn,
)
def _log_consume(connection) -> None:
from club_feature_logger import log_club_feature_usage
access = check_club_feature_access(int(club_id), feature_id, conn=connection)
log_club_feature_usage(
club_id=int(club_id),
profile_id=profile_id,
feature_id=feature_id,
action=action or "consume",
access=access,
phase="consume",
)
if conn is not None:
_log_consume(conn)
else:
with get_db() as c:
_log_consume(c)
def consume_club_feature_with_usage(
*,
feature_id: str,
club_id: Optional[int],
profile_id: Optional[int] = None,
portal_role: Optional[str] = None,
action: Optional[str] = None,
amount: int = 1,
cur,
tenant: Optional["TenantContext"] = None,
conn=None,
) -> Optional[Dict[str, Dict[str, Any]]]:
"""
Standard nach erfolgreichem Verbrauch: zählen, protokollieren, Snapshot für Response.
Alle Endpoints mit Vereins-Kontingent-Verbrauch nutzen diese Funktion und
``merge_feature_usage_into_response`` kein duplizierter Einzelcode pro Route.
"""
consume_club_feature(
feature_id=feature_id,
club_id=club_id,
profile_id=profile_id,
portal_role=portal_role,
action=action,
amount=amount,
conn=conn,
)
if club_id is None:
return None
return {
feature_id: club_feature_usage_for_api(
cur,
club_id=int(club_id),
feature_id=feature_id,
profile_id=profile_id,
portal_role=portal_role,
tenant=tenant,
conn=conn,
),
}
def merge_feature_usage_into_response(
payload: Any,
feature_usage: Optional[Dict[str, Dict[str, Any]]],
) -> Any:
"""Standard-Einbettung ``feature_usage`` in JSON-Responses."""
if not feature_usage or not isinstance(payload, dict):
return payload
return {**payload, "feature_usage": feature_usage}
def club_feature_usage_for_api(
cur,
*,
club_id: int,
feature_id: str,
profile_id: Optional[int] = None,
portal_role: Optional[str] = None,
tenant: Optional["TenantContext"] = None,
conn=None,
) -> Dict[str, Any]:
"""Feature-Zustand wie GET /me/entitlements → features[feature_id] (nach Verbrauch)."""
from club_quota_bypass import is_club_feature_quota_bypassed, quota_bypass_access
db_conn = conn if conn is not None else cur.connection
access = check_club_feature_access(int(club_id), feature_id, conn=db_conn)
plan_id = access.get("plan_id") or get_effective_club_plan(cur, int(club_id))
if is_club_feature_quota_bypassed(
cur,
profile_id=profile_id,
portal_role=portal_role,
feature_id=feature_id,
tenant=tenant,
):
ex = quota_bypass_access(
feature_id=feature_id,
club_id=int(club_id),
plan_id=plan_id,
)
reset_at = access.get("reset_at")
return {
"allowed": True,
"used": access.get("used"),
"limit": None,
"remaining": None,
"reason": ex.get("reason"),
"platform_exempt": True,
"reset_at": reset_at.isoformat() if hasattr(reset_at, "isoformat") else reset_at,
}
return {
"allowed": access.get("allowed"),
"used": access.get("used"),
"limit": access.get("limit"),
"remaining": access.get("remaining"),
"reason": access.get("reason"),
"platform_exempt": False,
"reset_at": access.get("reset_at").isoformat()
if access.get("reset_at") is not None and hasattr(access.get("reset_at"), "isoformat")
else access.get("reset_at"),
}
def increment_club_feature_usage(
club_id: int,
feature_id: str,
*,
profile_id: Optional[int] = None,
action: Optional[str] = None,
conn=None,
) -> None:
"""Erhöht Vereins-Zähler (nur bei neuem Verbrauch / INSERT-Pfad aufrufen)."""
def _run(c):
cur = get_cursor(c)
cur.execute(
"""
SELECT reset_period, limit_type
FROM features
WHERE id = %s AND app = 'shinkan' AND active = true
""",
(feature_id,),
)
feature = cur.fetchone()
if not feature:
return
if (feature.get("limit_type") or "count").strip().lower() == "boolean":
return
period = (feature.get("reset_period") or "never").strip().lower()
next_reset = _calculate_next_reset(period)
cur.execute(
"""
INSERT INTO club_feature_usage (club_id, feature_id, usage_count, reset_at, last_used_at)
VALUES (%s, %s, 1, %s, NOW())
ON CONFLICT (club_id, feature_id)
DO UPDATE SET
usage_count = club_feature_usage.usage_count + 1,
last_used_at = NOW(),
updated_at = NOW()
""",
(club_id, feature_id, next_reset),
)
if profile_id is not None or action:
cur.execute(
"""
INSERT INTO club_feature_usage_events (club_id, feature_id, profile_id, action)
VALUES (%s, %s, %s, %s)
""",
(club_id, feature_id, profile_id, action or feature_id),
)
if conn is not None:
_run(conn)
else:
with get_db() as c:
_run(c)
def list_club_entitlements(cur, club_id: int, *, conn=None) -> Dict[str, Any]:
"""Alle aktiven Shinkan-Features mit effektivem Limit und Verbrauch (Liste, intern)."""
db_conn = conn if conn is not None else cur.connection
plan_id = get_effective_club_plan(cur, club_id)
cur.execute(
"""
SELECT id, name, category, limit_type, reset_period
FROM features
WHERE app = 'shinkan' AND active = true
ORDER BY category, id
"""
)
rows = cur.fetchall()
features_out = []
for row in rows:
fid = row["id"]
access = _check_club_impl(club_id, fid, db_conn)
features_out.append(
{
"id": fid,
"name": row.get("name"),
"category": row.get("category"),
"limit_type": row.get("limit_type"),
"reset_period": row.get("reset_period"),
"allowed": access.get("allowed"),
"limit": access.get("limit"),
"used": access.get("used"),
"remaining": access.get("remaining"),
"reason": access.get("reason"),
"reset_at": access.get("reset_at"),
}
)
return {"club_id": club_id, "plan_id": plan_id, "features": features_out}
def club_features_map(cur, club_id: int, *, conn=None) -> Dict[str, Any]:
"""Feature-Kontingente als Dict feature_id → Zustand (für /me/entitlements)."""
raw = list_club_entitlements(cur, club_id, conn=conn)
features_dict: Dict[str, Any] = {}
for row in raw.get("features") or []:
fid = row["id"]
features_dict[fid] = {
"name": row.get("name"),
"category": row.get("category"),
"limit_type": row.get("limit_type"),
"reset_period": row.get("reset_period"),
"allowed": row.get("allowed"),
"limit": row.get("limit"),
"used": row.get("used"),
"remaining": row.get("remaining"),
"reason": row.get("reason"),
"reset_at": row.get("reset_at"),
}
return {
"club_id": raw.get("club_id"),
"plan_id": raw.get("plan_id"),
"features": features_dict,
}

View File

@ -0,0 +1,180 @@
"""
Vereins-Kontingent-Bypass über das Capability-System (kein Parallel-Rechtemodell).
Capabilities:
- platform.club_quota.bypass alle Vereins-Features (Portal-Admin, Grant via portal_role)
- platform.club_quota.bypass.{feature_id} ein Feature (domain quota_bypass, auch für Nicht-Admins per Grant)
"""
from __future__ import annotations
from typing import Any, Dict, List, Optional, TYPE_CHECKING
if TYPE_CHECKING:
from tenant_context import TenantContext
QUOTA_BYPASS_ALL = "platform.club_quota.bypass"
QUOTA_BYPASS_FEATURE_PREFIX = "platform.club_quota.bypass."
def quota_bypass_capability_id_for_feature(feature_id: str) -> str:
return f"{QUOTA_BYPASS_FEATURE_PREFIX}{feature_id}"
def ensure_quota_bypass_capability(cur, feature_id: str) -> str:
"""Legt feature-spezifische Bypass-Capability an falls nötig."""
cap_id = quota_bypass_capability_id_for_feature(feature_id)
cur.execute(
"""
INSERT INTO capabilities (id, name, domain, min_account_state, linked_feature_id)
VALUES (%s, %s, 'quota_bypass', 'active_member', %s)
ON CONFLICT (id) DO NOTHING
""",
(cap_id, f"Vereins-Kontingent umgehen: {feature_id}", feature_id),
)
return cap_id
def _bypass_capability_ids(cur, feature_id: str) -> List[str]:
ids: List[str] = [QUOTA_BYPASS_ALL, quota_bypass_capability_id_for_feature(feature_id)]
cur.execute(
"""
SELECT id FROM capabilities
WHERE active = true
AND domain = 'quota_bypass'
AND linked_feature_id = %s
AND id <> %s
""",
(feature_id, quota_bypass_capability_id_for_feature(feature_id)),
)
for row in cur.fetchall():
cid = row.get("id")
if cid and cid not in ids:
ids.append(str(cid))
return ids
def _portal_role_has_grant(cur, portal_role: str, capability_id: str) -> bool:
role = (portal_role or "").strip().lower()
if not role:
return False
cur.execute(
"""
SELECT 1 FROM portal_role_capability_grants
WHERE portal_role = %s AND capability_id = %s
LIMIT 1
""",
(role, capability_id),
)
return cur.fetchone() is not None
def _profile_has_grant(cur, profile_id: int, capability_id: str) -> bool:
cur.execute(
"""
SELECT 1 FROM profile_capability_grants
WHERE profile_id = %s AND capability_id = %s
LIMIT 1
""",
(int(profile_id), capability_id),
)
return cur.fetchone() is not None
def is_club_feature_quota_bypassed(
cur,
*,
profile_id: Optional[int],
portal_role: Optional[str],
feature_id: str,
tenant: Optional["TenantContext"] = None,
) -> bool:
"""
True wenn ein konfigurierter Capability-Grant das Vereins-Kontingent für feature_id umgeht.
"""
if tenant is not None:
from capabilities import check_capability
for cap_id in _bypass_capability_ids(cur, feature_id):
if check_capability(cur, tenant, cap_id).get("allowed"):
return True
return False
for cap_id in _bypass_capability_ids(cur, feature_id):
if _portal_role_has_grant(cur, portal_role or "", cap_id):
return True
if profile_id is not None and _profile_has_grant(cur, int(profile_id), cap_id):
return True
return False
def quota_bypass_access(
*,
feature_id: str,
club_id: Optional[int] = None,
plan_id: Optional[str] = None,
capability_id: Optional[str] = None,
) -> Dict[str, Any]:
return {
"allowed": True,
"limit": None,
"used": 0,
"remaining": None,
"reason": "capability_quota_bypass",
"platform_exempt": True,
"quota_bypass_capability": capability_id,
"plan_id": plan_id,
"club_id": club_id,
"feature_id": feature_id,
}
def list_quota_bypass_grants(cur) -> Dict[str, Any]:
"""Admin: alle Grants zu Kontingent-Bypass-Capabilities."""
cur.execute(
"""
SELECT g.portal_role, g.capability_id, c.name AS capability_name,
c.linked_feature_id, c.domain
FROM portal_role_capability_grants g
INNER JOIN capabilities c ON c.id = g.capability_id
WHERE g.capability_id = %s
OR g.capability_id LIKE %s
OR c.domain = 'quota_bypass'
ORDER BY g.portal_role, g.capability_id
""",
(QUOTA_BYPASS_ALL, f"{QUOTA_BYPASS_FEATURE_PREFIX}%"),
)
portal_grants = [dict(r) for r in cur.fetchall()]
cur.execute(
"""
SELECT g.profile_id, p.email, p.name AS profile_name,
g.capability_id, c.name AS capability_name, c.linked_feature_id,
g.reason, g.granted_by_profile_id, g.created_at
FROM profile_capability_grants g
INNER JOIN profiles p ON p.id = g.profile_id
INNER JOIN capabilities c ON c.id = g.capability_id
WHERE g.capability_id = %s
OR g.capability_id LIKE %s
OR c.domain = 'quota_bypass'
ORDER BY g.profile_id, g.capability_id
""",
(QUOTA_BYPASS_ALL, f"{QUOTA_BYPASS_FEATURE_PREFIX}%"),
)
profile_grants = [dict(r) for r in cur.fetchall()]
cur.execute(
"""
SELECT id, name, domain, linked_feature_id
FROM capabilities
WHERE id = %s OR id LIKE %s OR domain = 'quota_bypass'
ORDER BY id
""",
(QUOTA_BYPASS_ALL, f"{QUOTA_BYPASS_FEATURE_PREFIX}%"),
)
capabilities = [dict(r) for r in cur.fetchall()]
return {
"capabilities": capabilities,
"portal_role_grants": portal_grants,
"profile_grants": profile_grants,
}

View File

@ -3,7 +3,7 @@ Vereins-Mandanten: Mitgliedschaften, aktiver Vereinskontext, einfache Berechtigu
Siehe .claude/docs/technical/MULTI_TENANCY_RBAC_ARCHITECTURE.md
"""
from typing import Any, Dict, List, Optional, Set
from typing import Any, Dict, List, Mapping, Optional, Set, Union
from fastapi import HTTPException
@ -91,7 +91,12 @@ def can_manage_club_org(cur, profile_id: int, club_id: int, global_role: Optiona
def can_plan_in_club(cur, profile_id: int, club_id: int, global_role: Optional[str]) -> bool:
"""Trainingsgruppen anlegen / planen: Admin-Rollen im Verein oder Plattform."""
"""Trainingsgruppe anlegen u.Ä.; Vereins-rollentrainer, Content-Editor, Spartenleitung …
Hinweis: ``content_editor`` ist derzeit zusammen mit ``trainer``/``division_lead`` in diesem
gemeinsamen Strang gebündelt u.a. Vereinsübungen bearbeiten (s. exercises) und
Trainingsgruppen unter ``clubs``. Es gibt noch keine eigene Nur-Content-Guard pro Endpunkt.
"""
if is_platform_admin(global_role):
return True
return has_club_role(
@ -150,6 +155,165 @@ def club_ids_for_profile_with_roles(cur, profile_id: int, *role_codes: str) -> S
_GOVERNANCE_VISIBILITY = frozenset({"private", "club", "official"})
def _library_governance_triplet(
row: Mapping[str, Any],
) -> tuple[str, Optional[int], Optional[int]]:
"""visibility, club_id, created_by als normalisierte Werte für Bibliotheks-/Planungsartefakte."""
vis = str(row.get("visibility") or "private").strip().lower()
if vis not in _GOVERNANCE_VISIBILITY:
vis = "private"
cid_raw = row.get("club_id")
try:
ex_cid = int(cid_raw) if cid_raw is not None else None
except (TypeError, ValueError):
ex_cid = None
cr_raw = row.get("created_by")
try:
creator = int(cr_raw) if cr_raw is not None else None
except (TypeError, ValueError):
creator = None
return vis, ex_cid, creator
def assert_library_content_editable(
cur,
profile_id: int,
role: Optional[str],
row: Union[Dict[str, Any], Mapping[str, Any]],
) -> None:
"""Inhalt bearbeiten: wie Übungen — Ersteller, Plattform-Admin oder Planungsberechtigter im Verein."""
pid = int(profile_id)
ex_vis, ex_cid, creator = _library_governance_triplet(row)
if creator is not None and creator == pid:
return
if is_platform_admin(role):
return
if ex_vis == "club" and ex_cid is not None and can_plan_in_club(cur, pid, ex_cid, role):
return
raise HTTPException(status_code=403, detail="Keine Berechtigung zum Bearbeiten dieses Inhalts")
def assert_library_content_deletable(
cur,
profile_id: int,
role: Optional[str],
row: Union[Dict[str, Any], Mapping[str, Any]],
) -> None:
"""Löschen: wie Übungen — privat Eigentümer/Vereins-Admin-Kontext, Verein nur Vereinsadmin, offiziell nur Plattform."""
pid = int(profile_id)
if is_platform_admin(role):
return
vis, cid, creator = _library_governance_triplet(row)
try:
creator_int = int(creator) if creator is not None else None
except (TypeError, ValueError):
creator_int = None
if vis == "official":
raise HTTPException(
status_code=403,
detail="Offizielle Inhalte dürfen nur von Plattform-Admins gelöscht werden.",
)
if vis == "club":
try:
ex_club = int(cid) if cid is not None else None
except (TypeError, ValueError):
ex_club = None
if ex_club is None:
raise HTTPException(status_code=400, detail="Vereinsinhalt ohne gültige Vereinszuordnung")
if not has_club_role(cur, pid, ex_club, "club_admin"):
raise HTTPException(
status_code=403,
detail="Nur Vereins-Admins dürfen Vereins-Inhalte löschen.",
)
return
if creator_int is not None and creator_int == pid:
return
if creator_int is not None and club_admin_shares_club_with_creator(cur, pid, creator_int):
return
raise HTTPException(status_code=403, detail="Keine Berechtigung zum Löschen dieses Inhalts")
def assert_library_content_governance_transition(
cur,
profile_id: int,
role: Optional[str],
prev_row: Union[Dict[str, Any], Mapping[str, Any]],
next_visibility: str,
next_club_id: Optional[int],
) -> None:
"""
Zusätzliche Regeln beim Ändern von visibility/club_id (Zielzustand vor assert_valid_governance_visibility prüfen).
- Abwahl official: nur Plattform-Admin.
- private club: nur Ersteller (oder Plattform-Admin).
- club private: Ersteller, Vereinsadmin im bisherigen Verein oder Plattform-Admin.
- club club mit Wechsel club_id: Vereinsadmin im alten oder neuen Verein oder Plattform-Admin.
"""
nv = str(next_visibility or "").strip().lower()
if nv not in _GOVERNANCE_VISIBILITY:
raise HTTPException(status_code=400, detail="Ungültige visibility")
old_vis, old_cid, creator = _library_governance_triplet(prev_row)
new_cid: Optional[int]
try:
new_cid = int(next_club_id) if next_club_id is not None else None
except (TypeError, ValueError):
new_cid = None
pid = int(profile_id)
try:
creator_int = int(creator) if creator is not None else None
except (TypeError, ValueError):
creator_int = None
if old_vis == nv and (nv != "club" or old_cid == new_cid):
return
if old_vis == "official" and nv != "official":
if not is_platform_admin(role):
raise HTTPException(
status_code=403,
detail="Nur Plattform-Admins dürfen offizielle Inhalte auf Verein oder privat setzen.",
)
if nv == "official":
return
if old_vis == "private" and nv == "club":
if creator_int is not None and creator_int != pid and not is_platform_admin(role):
raise HTTPException(
status_code=403,
detail="Nur der Ersteller darf private Inhalte für den Verein freigeben.",
)
return
if old_vis == "club" and nv == "private":
if is_platform_admin(role):
return
if creator_int is not None and creator_int == pid:
return
if old_cid is not None and has_club_role(cur, pid, old_cid, "club_admin"):
return
raise HTTPException(
status_code=403,
detail="Nur Ersteller, Vereins-Admins oder Plattform-Admins dürfen Vereins-Inhalte auf privat setzen.",
)
if old_vis == "club" and nv == "club" and old_cid != new_cid:
if is_platform_admin(role):
return
ok_old = old_cid is not None and has_club_role(cur, pid, old_cid, "club_admin")
ok_new = new_cid is not None and has_club_role(cur, pid, new_cid, "club_admin")
if ok_old or ok_new:
return
raise HTTPException(
status_code=403,
detail="Nur Vereins-Admins oder Plattform-Admins dürfen die Vereinszuordnung ändern.",
)
def assert_valid_governance_visibility(
cur,
profile_id: int,
@ -157,13 +321,13 @@ def assert_valid_governance_visibility(
visibility: str,
club_id: Optional[int],
) -> None:
"""Pflicht club_id bei visibility=club; Mitgliedschaft außer Plattform-Admin; official nur Plattform-Admin."""
"""Pflicht club_id bei visibility=club; Mitgliedschaft außer Plattform-Admin; official nur Superadmin."""
if visibility not in _GOVERNANCE_VISIBILITY:
raise HTTPException(status_code=400, detail="Ungültige visibility")
if visibility == "official" and not is_platform_admin(role):
if visibility == "official" and not is_superadmin(role):
raise HTTPException(
status_code=403,
detail="Nur Plattform-Admins dürfen offizielle Inhalte setzen",
detail="Nur Superadmins dürfen offizielle Inhalte setzen",
)
if visibility == "club":
if club_id is None:
@ -198,25 +362,45 @@ def exercise_visible_to_profile(
created_by: Optional[int],
global_role: Optional[str],
) -> bool:
"""Leserechte einer Übung. Für neue Codepfade lieber `library_content_visible_to_profile` verwenden."""
if is_platform_admin(global_role):
"""
Leserechte einer Übung (und analoger Bibliotheksobjekte).
Vereinsbezogene Inhalte (visibility club): aktiv nur mit **aktiver** Mitgliedschaft in diesem Verein.
Mitgliedschaft mit status **inactive** sperrt auch für Plattform-/Super-Admins solange eine
Mitgliedschaft existiert.
Ist man kein Mitglied dieses Vereins, behalten Plattform-Admins den bisherigen Audit-Zugang
zum Vereinskontext ohne eigene Mitgliedschaft.
"""
vis = (visibility or "").strip().lower()
plat = is_platform_admin(global_role)
pid = int(profile_id)
if vis == "official":
return True
if visibility == "official":
if created_by is not None and int(created_by) == pid:
return True
if created_by is not None and created_by == profile_id:
return True
if visibility == "private":
if vis == "private":
return plat
if vis != "club":
return False
if visibility == "club":
if exercise_club_id is None:
return False
try:
ecid = int(exercise_club_id)
except (TypeError, ValueError):
return False
cur.execute(
"""
SELECT 1 FROM club_members
WHERE profile_id = %s AND club_id = %s AND status = 'active'
SELECT cm.status
FROM club_members cm
WHERE cm.profile_id = %s AND cm.club_id = %s
LIMIT 1
""",
(profile_id, exercise_club_id),
(pid, ecid),
)
return cur.fetchone() is not None
return False
row = cur.fetchone()
if row is None:
return plat
st_raw = row["status"] if isinstance(row, dict) else row[0]
return str(st_raw or "active").strip().lower() == "active"

View File

@ -180,12 +180,17 @@ def init_db():
cur.execute("SELECT COUNT(*) as count FROM ai_prompts WHERE slug='pipeline'")
if cur.fetchone()['count'] == 0:
cur.execute("""
INSERT INTO ai_prompts (slug, name, description, template, active, sort_order)
INSERT INTO ai_prompts (
slug, display_name, description, template,
category, output_format, active, sort_order
)
VALUES (
'pipeline',
'Mehrstufige Gesamtanalyse',
'Master-Schalter für die gesamte Pipeline. Deaktiviere diese Analyse, um die Pipeline komplett zu verstecken.',
'Master-Schalter fuer die gesamte Pipeline. Deaktiviere diese Zeile um die Pipeline zu verstecken.',
'PIPELINE_MASTER',
'admin',
'text',
true,
-10
)

113
backend/entitlements.py Normal file
View File

@ -0,0 +1,113 @@
"""
Zusammenstellung effektiver Rechte für GET /api/me/entitlements (M4).
Spez: CAPABILITY_CATALOG.v1.md §7.1, CLUB_MEMBERSHIP_AND_FEATURES.v1.md §8.1
"""
from __future__ import annotations
from datetime import datetime
from typing import Any, Dict, Optional, TYPE_CHECKING
from fastapi import HTTPException
from capabilities import club_roles_in_club, resolve_capabilities_map
from club_quota_bypass import is_club_feature_quota_bypassed, quota_bypass_access
from club_features import club_features_map
from club_tenancy import is_platform_admin
from tenant_context import _club_exists
if TYPE_CHECKING:
from tenant_context import TenantContext
def _serialize_reset_at(value: Any) -> Optional[str]:
if value is None:
return None
if isinstance(value, datetime):
if value.tzinfo is None:
return value.replace(tzinfo=None).isoformat() + "Z"
return value.isoformat()
return str(value)
def _resolve_target_club_id(
cur,
tenant: "TenantContext",
club_id: Optional[int],
) -> Optional[int]:
"""Effektiver Verein für Entitlements (Query > Tenant)."""
target = int(club_id) if club_id is not None else tenant.effective_club_id
if target is None:
return None
if is_platform_admin(tenant.global_role):
if not _club_exists(cur, target):
raise HTTPException(status_code=400, detail="Verein nicht gefunden")
return target
if target not in tenant.club_ids:
raise HTTPException(status_code=403, detail="Keine Mitgliedschaft in diesem Verein")
return target
def build_me_entitlements(
cur,
tenant: "TenantContext",
*,
club_id: Optional[int] = None,
) -> Dict[str, Any]:
"""
Kombiniert Account-Status, Capabilities und Feature-Kontingente.
"""
target_club = _resolve_target_club_id(cur, tenant, club_id)
club_roles = club_roles_in_club(tenant, target_club) if target_club is not None else []
capabilities = resolve_capabilities_map(cur, tenant, club_id=target_club)
features: Dict[str, Any] = {}
plan_id = None
if target_club is not None:
raw = club_features_map(cur, target_club)
plan_id = raw.get("plan_id")
for fid, row in (raw.get("features") or {}).items():
if is_club_feature_quota_bypassed(
cur,
profile_id=tenant.profile_id,
portal_role=tenant.global_role,
feature_id=fid,
tenant=tenant,
):
ex = quota_bypass_access(
feature_id=fid,
club_id=target_club,
plan_id=plan_id,
)
features[fid] = {
"allowed": True,
"used": row.get("used"),
"limit": None,
"remaining": None,
"reset_at": _serialize_reset_at(row.get("reset_at")),
"reason": ex.get("reason"),
"platform_exempt": True,
}
else:
features[fid] = {
"allowed": row.get("allowed"),
"used": row.get("used"),
"limit": row.get("limit"),
"remaining": row.get("remaining"),
"reset_at": _serialize_reset_at(row.get("reset_at")),
"reason": row.get("reason"),
"platform_exempt": False,
}
return {
"account_state": tenant.account_state,
"portal_role": tenant.global_role,
"club_id": target_club,
"plan_id": plan_id,
"club_roles": club_roles,
"capabilities": capabilities,
"features": features,
}

1122
backend/exercise_ai.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,536 @@
"""
Superadmin-Werkzeug: Übungs-Anreicherung per KI (Skills + optional Metadaten).
Wiederverwendet run_exercise_form_ai_suggestion / exercise_ai keine neue OpenRouter-Pipeline.
"""
from __future__ import annotations
from typing import Any, Dict, List, Literal, Optional
from ai_prompt_context import ExerciseFormAiPromptContext
from ai_prompt_job import run_exercise_form_ai_suggestion
from exercise_ai import strip_html_to_plain
from exercise_rich_text import normalize_inline_exercise_media_markup
from routers.exercises import (
enrich_exercise_detail,
normalize_exercise_skill_intensity,
normalize_exercise_skill_level,
)
SkillMergeMode = Literal["additive", "replace_ai_only", "replace_all"]
SKILL_MERGE_MODES = frozenset({"additive", "replace_ai_only", "replace_all"})
DEFAULT_SET_STATUS = "in_review"
# Max. IDs pro Apply-HTTP-Anfrage (kein LLM).
MAX_BATCH_EXERCISES = 50
# Preview: pro Request nur wenige Übungen — sonst Gateway-504 (Fritz!Box o.ä. ~60s).
MAX_PREVIEW_BATCH_EXERCISES = 3
_INSTRUCTION_FIELDS = ("goal", "execution", "preparation", "trainer_notes")
_SKILL_COMPARE_KEYS = ("intensity", "required_level", "target_level", "is_primary")
def _focus_areas_ai_ctx_from_detail(exercise: Dict[str, Any]) -> list[tuple[int, bool]]:
rows: list[tuple[int, bool]] = []
for row in exercise.get("focus_areas") or []:
if not isinstance(row, dict):
continue
try:
fid = int(row.get("focus_area_id"))
except (TypeError, ValueError):
continue
if fid < 1:
continue
rows.append((fid, bool(row.get("is_primary"))))
rows.sort(key=lambda x: (not x[1], x[0]))
return rows
def _focus_area_hint_from_detail(exercise: Dict[str, Any]) -> str:
parts: List[str] = []
for row in exercise.get("focus_areas") or []:
if isinstance(row, dict):
nm = (row.get("name") or "").strip()
if nm:
parts.append(nm)
txt = ", ".join(parts).strip()
if len(txt) > 900:
return txt[:899] + ""
return txt
def build_form_context_from_exercise(exercise: Dict[str, Any]) -> ExerciseFormAiPromptContext:
focus = _focus_area_hint_from_detail(exercise)
fctx = _focus_areas_ai_ctx_from_detail(exercise)
return ExerciseFormAiPromptContext.from_focus_tuples(
title=str(exercise.get("title") or "").strip(),
goal=exercise.get("goal"),
execution=exercise.get("execution"),
preparation=exercise.get("preparation"),
trainer_notes=exercise.get("trainer_notes"),
focus_hint=focus or None,
focus_tuples=fctx or None,
)
def validate_exercise_for_enrichment(
exercise: Dict[str, Any],
*,
want_skills: bool = False,
want_summary: bool = False,
want_instructions: bool = False,
) -> Optional[str]:
title = str(exercise.get("title") or "").strip()
if not title:
return "Titel fehlt"
ctx = build_form_context_from_exercise(exercise)
g_plain = strip_html_to_plain(exercise.get("goal"))
e_plain = strip_html_to_plain(exercise.get("execution"))
if want_skills or want_summary:
if not (g_plain.strip() or e_plain.strip()):
return "Mindestens Ziel oder Durchführung muss Inhalt liefern (für Skills/Kurzfassung)"
if want_instructions and not ctx.has_instruction_source_text():
return "Für Anleitungs-Überarbeitung fehlt Ausgangstext (Titel oder Anleitungsfeld)"
if not (want_skills or want_summary or want_instructions):
return "Kein Anreicherungsmodus aktiv"
return None
def _normalize_skill_row(raw: Dict[str, Any], *, ai_suggested: bool) -> Dict[str, Any]:
return {
"skill_id": int(raw["skill_id"]),
"skill_name": (raw.get("skill_name") or "").strip() or f"Skill #{raw['skill_id']}",
"skill_category": raw.get("skill_category"),
"is_primary": bool(raw.get("is_primary")),
"intensity": normalize_exercise_skill_intensity(raw.get("intensity")),
"required_level": normalize_exercise_skill_level(raw.get("required_level")),
"target_level": normalize_exercise_skill_level(raw.get("target_level")),
"ai_suggested": ai_suggested,
}
def _skill_meta_differs(a: Dict[str, Any], b: Dict[str, Any]) -> bool:
for k in _SKILL_COMPARE_KEYS:
av = a.get(k)
bv = b.get(k)
if k in ("required_level", "target_level"):
av = normalize_exercise_skill_level(av)
bv = normalize_exercise_skill_level(bv)
elif k == "intensity":
av = normalize_exercise_skill_intensity(av)
bv = normalize_exercise_skill_intensity(bv)
elif k == "is_primary":
av = bool(av)
bv = bool(bv)
if av != bv:
return True
return False
def merge_skills(
existing: List[Dict[str, Any]],
suggested: List[Dict[str, Any]],
mode: SkillMergeMode,
) -> List[Dict[str, Any]]:
"""Merge-Modi: additive | replace_ai_only | replace_all (alle KI-Skills mit ai_suggested=true)."""
existing_norm = [_normalize_skill_row(s, ai_suggested=bool(s.get("ai_suggested"))) for s in existing]
suggested_norm = [_normalize_skill_row(s, ai_suggested=True) for s in suggested]
suggested_by_id = {int(s["skill_id"]): s for s in suggested_norm}
if mode == "replace_all":
return list(suggested_norm)
if mode == "replace_ai_only":
manual = [s for s in existing_norm if not s.get("ai_suggested")]
manual_ids = {int(s["skill_id"]) for s in manual}
result = list(manual)
for s in suggested_norm:
sid = int(s["skill_id"])
if sid in manual_ids:
continue
result.append(s)
return result
# additive
result: List[Dict[str, Any]] = []
seen: set[int] = set()
for s in existing_norm:
sid = int(s["skill_id"])
seen.add(sid)
if sid in suggested_by_id and s.get("ai_suggested"):
merged = {**s, **suggested_by_id[sid], "ai_suggested": True}
result.append(merged)
else:
result.append(dict(s))
for s in suggested_norm:
sid = int(s["skill_id"])
if sid not in seen:
result.append(s)
seen.add(sid)
return result
def compute_skill_diff(
before: List[Dict[str, Any]],
after: List[Dict[str, Any]],
) -> Dict[str, Any]:
before_ids = {int(s["skill_id"]): s for s in before}
after_ids = {int(s["skill_id"]): s for s in after}
added = [after_ids[i] for i in sorted(after_ids) if i not in before_ids]
removed = [before_ids[i] for i in sorted(before_ids) if i not in after_ids]
changed: List[Dict[str, Any]] = []
for sid in before_ids:
if sid in after_ids and _skill_meta_differs(before_ids[sid], after_ids[sid]):
changed.append(
{
"skill_id": sid,
"skill_name": after_ids[sid].get("skill_name") or before_ids[sid].get("skill_name"),
"before": before_ids[sid],
"after": after_ids[sid],
}
)
kept = [
before_ids[i]
for i in sorted(before_ids)
if i in after_ids and i not in {c["skill_id"] for c in changed}
]
return {"added": added, "removed": removed, "changed": changed, "kept": kept}
def _skills_from_ai_payload(payload: Dict[str, Any]) -> List[Dict[str, Any]]:
rows = payload.get("skills")
if not isinstance(rows, list):
return []
return [_normalize_skill_row(r, ai_suggested=True) for r in rows if isinstance(r, dict) and r.get("skill_id")]
def _summary_from_ai_payload(payload: Dict[str, Any]) -> Optional[str]:
block = payload.get("summary")
if isinstance(block, dict):
text = (block.get("text") or "").strip()
return text or None
if isinstance(block, str) and block.strip():
return block.strip()
return None
def _instructions_from_ai_payload(payload: Dict[str, Any]) -> Dict[str, str]:
block = payload.get("instructions")
if not isinstance(block, dict):
return {}
fields = block.get("fields")
if not isinstance(fields, dict):
return {}
out: Dict[str, str] = {}
for key in _INSTRUCTION_FIELDS:
val = fields.get(key)
if val is not None and str(val).strip():
out[key] = str(val).strip()
return out
def _instruction_snapshot(exercise: Dict[str, Any]) -> Dict[str, str]:
out: Dict[str, str] = {}
for key in _INSTRUCTION_FIELDS:
raw = exercise.get(key)
plain = strip_html_to_plain(raw, max_len=400) if raw else ""
if plain.strip():
out[key] = plain.strip()
return out
def compute_instruction_diff(
before: Dict[str, str],
after: Dict[str, str],
) -> Dict[str, Any]:
changed: List[Dict[str, Any]] = []
added: List[str] = []
for key in _INSTRUCTION_FIELDS:
b = (before.get(key) or "").strip()
a = (after.get(key) or "").strip()
if not a:
continue
if not b:
added.append(key)
elif b != strip_html_to_plain(a, max_len=400).strip() and b != a:
changed.append({"field": key, "before_plain": b, "after_html": a})
return {"changed_fields": changed, "added_fields": added}
def preview_exercise_enrichment(
cur,
exercise_id: int,
*,
want_skills: bool = True,
want_summary: bool = False,
want_instructions: bool = False,
merge_mode: SkillMergeMode = "additive",
) -> Dict[str, Any]:
exercise = enrich_exercise_detail(exercise_id, cur)
if not exercise:
return {"exercise_id": exercise_id, "ok": False, "error": "Übung nicht gefunden"}
skip_reason = validate_exercise_for_enrichment(
exercise,
want_skills=want_skills,
want_summary=want_summary,
want_instructions=want_instructions,
)
if skip_reason:
return {
"exercise_id": exercise_id,
"ok": False,
"skipped": True,
"error": skip_reason,
"title": exercise.get("title"),
"status": exercise.get("status"),
}
existing = exercise.get("skills") or []
suggested: List[Dict[str, Any]] = []
ai_meta: Dict[str, Any] = {}
payload: Dict[str, Any] = {}
suggested_summary: Optional[str] = None
suggested_instructions: Dict[str, str] = {}
if want_skills or want_summary or want_instructions:
ctx = build_form_context_from_exercise(exercise)
payload = run_exercise_form_ai_suggestion(
cur,
ctx,
want_summary=want_summary,
want_skills=want_skills,
want_instructions=want_instructions,
)
if want_skills:
suggested = _skills_from_ai_payload(payload)
if want_summary:
suggested_summary = _summary_from_ai_payload(payload)
if want_instructions:
suggested_instructions = _instructions_from_ai_payload(payload)
ai_meta = {
"models": payload.get("models_by_slug") or {},
"llm_calls": sum([want_skills, want_summary, want_instructions]),
}
merged = merge_skills(existing, suggested, merge_mode) if want_skills else list(existing)
diff = compute_skill_diff(existing, merged) if want_skills else None
existing_summary = (exercise.get("summary") or "").strip() or None
instr_before = _instruction_snapshot(exercise)
instr_after_plain = {
k: strip_html_to_plain(v, max_len=400) for k, v in suggested_instructions.items()
}
instruction_diff = (
compute_instruction_diff(instr_before, instr_after_plain) if want_instructions else None
)
return {
"exercise_id": exercise_id,
"ok": True,
"title": exercise.get("title"),
"status": exercise.get("status"),
"visibility": exercise.get("visibility"),
"primary_focus_name": _primary_focus_from_exercise(exercise),
"existing_skills": existing,
"suggested_skills": suggested,
"merged_skills": merged,
"diff": diff,
"existing_summary": existing_summary,
"suggested_summary": suggested_summary,
"existing_instructions": instr_before,
"suggested_instructions": suggested_instructions,
"instruction_diff": instruction_diff,
"ai_meta": ai_meta,
}
def _primary_focus_from_exercise(exercise: Dict[str, Any]) -> Optional[str]:
for row in exercise.get("focus_areas") or []:
if isinstance(row, dict) and row.get("is_primary"):
return (row.get("name") or "").strip() or None
for row in exercise.get("focus_areas") or []:
if isinstance(row, dict):
nm = (row.get("name") or "").strip()
if nm:
return nm
return None
def persist_merged_skills(cur, exercise_id: int, merged: List[Dict[str, Any]], merge_mode: SkillMergeMode) -> None:
if merge_mode == "replace_all":
cur.execute("DELETE FROM exercise_skills WHERE exercise_id = %s", (exercise_id,))
elif merge_mode == "replace_ai_only":
cur.execute(
"DELETE FROM exercise_skills WHERE exercise_id = %s AND ai_suggested = true",
(exercise_id,),
)
for sk in merged:
cur.execute(
"""
INSERT INTO exercise_skills
(exercise_id, skill_id, is_primary, intensity, required_level, target_level, ai_suggested)
VALUES (%s, %s, %s, %s, %s, %s, %s)
ON CONFLICT (exercise_id, skill_id) DO UPDATE SET
intensity = CASE
WHEN exercise_skills.ai_suggested = false AND %s = 'additive'
THEN exercise_skills.intensity ELSE EXCLUDED.intensity END,
required_level = CASE
WHEN exercise_skills.ai_suggested = false AND %s = 'additive'
THEN exercise_skills.required_level ELSE EXCLUDED.required_level END,
target_level = CASE
WHEN exercise_skills.ai_suggested = false AND %s = 'additive'
THEN exercise_skills.target_level ELSE EXCLUDED.target_level END,
is_primary = CASE
WHEN exercise_skills.ai_suggested = false AND %s = 'additive'
THEN exercise_skills.is_primary ELSE EXCLUDED.is_primary END,
ai_suggested = CASE
WHEN exercise_skills.ai_suggested = false AND %s = 'additive'
THEN exercise_skills.ai_suggested ELSE EXCLUDED.ai_suggested END
""",
(
exercise_id,
int(sk["skill_id"]),
bool(sk.get("is_primary")),
normalize_exercise_skill_intensity(sk.get("intensity")),
normalize_exercise_skill_level(sk.get("required_level")),
normalize_exercise_skill_level(sk.get("target_level")),
bool(sk.get("ai_suggested")),
merge_mode,
merge_mode,
merge_mode,
merge_mode,
merge_mode,
),
)
def _normalize_instruction_fields(fields: Optional[Dict[str, Any]]) -> Dict[str, str]:
if not fields:
return {}
out: Dict[str, str] = {}
for key in _INSTRUCTION_FIELDS:
if key not in fields:
continue
raw = fields.get(key)
if raw is None or not str(raw).strip():
continue
out[key] = normalize_inline_exercise_media_markup(str(raw).strip())
return out
def apply_exercise_enrichment(
cur,
exercise_id: int,
*,
merged_skills: Optional[List[Dict[str, Any]]] = None,
merge_mode: SkillMergeMode = "additive",
set_status: Optional[str] = DEFAULT_SET_STATUS,
apply_skills: bool = False,
summary_text: Optional[str] = None,
apply_summary: bool = False,
instruction_fields: Optional[Dict[str, Any]] = None,
apply_instructions: bool = False,
) -> Dict[str, Any]:
exercise = enrich_exercise_detail(exercise_id, cur)
if not exercise:
return {"exercise_id": exercise_id, "ok": False, "error": "Übung nicht gefunden"}
skip_reason = validate_exercise_for_enrichment(
exercise,
want_skills=apply_skills,
want_summary=apply_summary,
want_instructions=apply_instructions,
)
if skip_reason:
return {
"exercise_id": exercise_id,
"ok": False,
"skipped": True,
"error": skip_reason,
}
skills_list = merged_skills or []
if apply_skills:
if not skills_list and merge_mode != "replace_all":
return {
"exercise_id": exercise_id,
"ok": False,
"error": "Keine Skills zum Anwenden",
}
persist_merged_skills(cur, exercise_id, skills_list, merge_mode)
sets: List[str] = []
vals: List[Any] = []
if apply_summary and summary_text is not None:
text = str(summary_text).strip()
if text:
sets.extend(["summary = %s", "summary_ai_generated = true"])
vals.append(text[:220])
if apply_instructions:
norm = _normalize_instruction_fields(instruction_fields)
for key, val in norm.items():
sets.append(f"{key} = %s")
vals.append(val)
new_status = (set_status or "").strip().lower() or None
if new_status:
if new_status == "approved":
return {
"exercise_id": exercise_id,
"ok": False,
"error": "Automatisches Freigeben (approved) ist nicht erlaubt",
}
if new_status not in ("draft", "in_review", "archived"):
return {"exercise_id": exercise_id, "ok": False, "error": "Ungültiger Ziel-Status"}
sets.append("status = %s")
vals.append(new_status)
if sets:
sets.append("updated_at = NOW()")
vals.append(exercise_id)
cur.execute(
f"UPDATE exercises SET {', '.join(sets)} WHERE id = %s",
tuple(vals),
)
elif not apply_skills:
return {"exercise_id": exercise_id, "ok": False, "error": "Nichts anzuwenden"}
return {
"exercise_id": exercise_id,
"ok": True,
"status": new_status or exercise.get("status"),
"skills_applied": len(skills_list) if apply_skills else 0,
"summary_applied": apply_summary and bool(summary_text and str(summary_text).strip()),
"instructions_applied": apply_instructions and bool(_normalize_instruction_fields(instruction_fields)),
}
def estimate_llm_calls(
*,
exercise_count: int,
want_skills: bool,
want_summary: bool,
want_instructions: bool = False,
) -> Dict[str, Any]:
per_skills = exercise_count if want_skills else 0
per_summary = exercise_count if want_summary else 0
per_instructions = exercise_count if want_instructions else 0
total = per_skills + per_summary + per_instructions
return {
"total": total,
"per_exercise": sum([want_skills, want_summary, want_instructions]),
"skills": per_skills,
"summary": per_summary,
"instructions": per_instructions,
}

View File

@ -0,0 +1,101 @@
"""
Übungs-Fließtext: Inline-Verweise auf exercise_media.id (MEDIA_ASSETS_AND_ARCHIVE_SPEC §11).
- Kurzsyntax beim Speichern kanonisches <span data-shinkan-exercise-media="">.
- Verweise müssen für die betreffende Übung gültige exercise_media-Zeilen sein.
"""
from __future__ import annotations
import re
from typing import FrozenSet, Optional, Set
from fastapi import HTTPException
# {{exerciseMedia:123}} (Whitespace tolerant, case-insensitive Schlüssel)
_BRACE_PATTERN = re.compile(r"\{\{\s*exerciseMedia\s*:\s*(\d+)\s*\}\}", re.IGNORECASE)
# bereits gespeichertes Markup (einfache Anführungszeichen-varianten durch Regex abgedeckt)
_DATA_ATTR_PATTERN = re.compile(r'data-shinkan-exercise-media\s*=\s*["\']?(\d+)["\']?', re.IGNORECASE)
RICH_HTML_EXERCISE_FIELDS: FrozenSet[str] = frozenset(
{"summary", "goal", "execution", "preparation", "trainer_notes"}
)
def normalize_inline_exercise_media_markup(html: Optional[str]) -> Optional[str]:
"""Wandelt {{exerciseMedia:id}} in kanonisches span mit data-shinkan-exercise-media."""
if html is None:
return None
if not isinstance(html, str):
html = str(html)
stripped = html.strip()
if not stripped:
return html
def _repl(match: re.Match) -> str:
mid = int(match.group(1))
return (
f'<span data-shinkan-exercise-media="{mid}" data-shinkan-exercise-media-size="medium" '
f'class="shinkan-inline-media"></span>'
)
return _BRACE_PATTERN.sub(_repl, html)
def collect_inline_exercise_media_ids(html: Optional[str]) -> Set[int]:
"""Sammelt alle referenzierten exercise_media.ids aus Kurzsyntax und kanonischem Span."""
if html is None or not isinstance(html, str):
return set()
if not html.strip():
return set()
ids: Set[int] = set()
ids.update(int(m) for m in _BRACE_PATTERN.findall(html))
ids.update(int(m) for m in _DATA_ATTR_PATTERN.findall(html))
return ids
def assert_no_inline_media_references_on_create(ids: Set[int]) -> None:
"""Neue Übung hat noch keine exercise_media-Zeilen — Platzhalter verboten."""
if not ids:
return
raise HTTPException(
status_code=400,
detail={
"code": "INLINE_EXERCISE_MEDIA_ON_CREATE",
"message": (
"Medienverweise im Fließtext sind beim ersten Anlegen der Übung nicht möglich. "
"Bitte Übung ohne Platzhalter speichern, Medien hochladen oder verknüpfen "
"und die Verweise dann bearbeiten ({{exerciseMedia:id}} oder „Medium einfügen“)."
),
"invalid_exercise_media_ids": sorted(ids),
},
)
def validate_inline_exercise_media_ids_for_exercise(cur, exercise_id: int, ids: Set[int]) -> None:
"""Prüft, dass jede genannte exercise_media.id zu dieser Übung gehört."""
if not ids:
return
sid = sorted(ids)
ph = ",".join(["%s"] * len(sid))
cur.execute(
f"SELECT id FROM exercise_media WHERE exercise_id = %s AND id IN ({ph})",
(exercise_id, *sid),
)
found = set()
for row in cur.fetchall():
rid = row["id"] if isinstance(row, dict) else row[0]
found.add(int(rid))
missing = ids - found
if missing:
raise HTTPException(
status_code=400,
detail={
"code": "INLINE_EXERCISE_MEDIA_INVALID",
"message": "Ein oder mehrere eingebettete Medien-Verweise gehören nicht zu dieser Übung.",
"invalid_exercise_media_ids": sorted(missing),
},
)

View File

@ -0,0 +1,16 @@
"""Hilfen für direkte Python-Aufrufe von FastAPI-Route-Handlern (ohne Request-Kontext)."""
from __future__ import annotations
from typing import Any
def unwrap_query_default(value: Any) -> Any:
"""
Parameter mit Annotation ``= Query(default=)`` sind im Funktionskörper ``fastapi.params.Query``-Instanzen,
solange FastAPI sie nicht durch echte Werte ersetzt hat (interne Aufrufe, Aggregat-Endpunkte).
"""
try:
from fastapi.params import Query
except ImportError:
return value
return value.default if isinstance(value, Query) else value

View File

@ -52,6 +52,28 @@ else:
print(f"[FAIL] Migration-Laufzeitfehler: {e}")
sys.exit(1)
# Registry-first: Module → DB (nur registrierte Rechte/Kontingente in Admin-Matrix)
if os.getenv("SKIP_DB_MIGRATE", "").strip().lower() not in ("1", "true", "yes"):
try:
from rights_registry import sync_rights_registry_to_db
counts = sync_rights_registry_to_db()
print(
f"[OK] Rights registry sync: {counts['capabilities']} capabilities, "
f"{counts['features']} features"
)
except Exception as e:
print(f"[FAIL] Rights registry sync: {e}")
sys.exit(1)
from club_features import club_feature_enforcement_enabled
_cfe = os.getenv("CLUB_FEATURE_ENFORCE", "0")
print(
f"[OK] CLUB_FEATURE_ENFORCE raw={_cfe!r} "
f"active={club_feature_enforcement_enabled()}"
)
from routers.auth import limiter as auth_rate_limiter
# OpenAPI: in Produktion standardmäßig aus (Schema nicht öffentlich). Notfall: PUBLIC_OPENAPI=1
@ -82,11 +104,39 @@ app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "X-Auth-Token", "X-Active-Club-Id"],
)
@app.middleware("http")
async def account_onboarding_api_gate(request: Request, call_next):
"""
Phase A: Domänen-APIs für unverified / verified_pending_club sperren.
Siehe account_onboarding_gate.py und MEMBERSHIP_RBAC_DECISIONS_2026-06.md §1.1
"""
from account_onboarding_gate import evaluate_request_gate
token = request.headers.get("x-auth-token") or request.headers.get("X-Auth-Token")
allowed, reason, _state = evaluate_request_gate(
token,
request.url.path,
request.method,
)
if not allowed:
return JSONResponse(
status_code=403,
content={
"detail": (
"Zugriff erst nach E-Mail-Bestätigung und Vereinsmitgliedschaft möglich. "
"Du kannst einen Beitrittsantrag stellen oder dein Konto in den Einstellungen verwalten."
),
"reason": reason,
},
)
return await call_next(request)
@app.middleware("http")
async def add_api_security_headers(request: Request, call_next):
"""Konsistente Basis-Header auch für rein JSON-Responses (MIME-Sniffing)."""
@ -193,7 +243,7 @@ def read_root():
return out
# Register routers
from routers import auth, profiles, exercises, exercise_progression_graphs, clubs, club_memberships, club_join_requests, admin_users, platform_media_storage, media_assets, skills, training_planning, training_framework_programs, catalogs, maturity_models, matrix_stack_bundle, import_wiki, import_wiki_admin
from routers import auth, profiles, exercises, exercise_progression_graphs, clubs, club_memberships, club_join_requests, club_creation_requests, admin_users, admin_user_content, admin_rights, me_entitlements, platform_media_storage, media_assets, skills, skill_profiles, training_planning, planning_exercise_suggest, dashboard, training_modules, training_framework_programs, catalogs, catalog_prompt_slots, maturity_models, matrix_stack_bundle, matrix_editor, import_wiki, import_wiki_admin, legal_documents, content_reports, ai_prompts_admin, ai_skill_retrieval_admin, exercise_enrichment_admin
app.include_router(auth.router)
app.include_router(profiles.router)
@ -202,17 +252,34 @@ app.include_router(exercise_progression_graphs.router)
app.include_router(clubs.router)
app.include_router(club_memberships.router)
app.include_router(club_join_requests.router)
app.include_router(club_creation_requests.router)
app.include_router(admin_users.router)
app.include_router(admin_user_content.router)
app.include_router(admin_rights.router)
app.include_router(me_entitlements.router)
app.include_router(platform_media_storage.router)
app.include_router(media_assets.router)
app.include_router(media_assets.admin_rights_router)
app.include_router(media_assets.admin_legal_hold_router)
app.include_router(skills.router)
app.include_router(skill_profiles.router)
app.include_router(training_planning.router)
app.include_router(planning_exercise_suggest.router)
app.include_router(dashboard.router)
app.include_router(training_modules.router)
app.include_router(training_framework_programs.router)
app.include_router(catalogs.router)
app.include_router(catalog_prompt_slots.router)
app.include_router(maturity_models.router)
app.include_router(matrix_stack_bundle.router)
app.include_router(matrix_editor.router)
app.include_router(import_wiki.router)
app.include_router(import_wiki_admin.router)
app.include_router(legal_documents.router)
app.include_router(content_reports.router)
app.include_router(ai_prompts_admin.router)
app.include_router(ai_skill_retrieval_admin.router)
app.include_router(exercise_enrichment_admin.router)
# Lokale Übungs-Medien: standardmäßig nur über geschützten API-Pfad
# GET /api/exercises/{id}/media/{mid}/file (?ssetoken für <img>/<video>).

247
backend/media_legal_hold.py Normal file
View File

@ -0,0 +1,247 @@
"""P-11: Legal-Hold-Services fuer Medien-Assets.
Sofortsperrung bei Rechtsverletzungen (Compliance-Paket P-11).
Klare Trennung:
- P-06: Rechteerklaerungs-/Deklarationsstatus (rights_status='declared'/'legacy_unreviewed')
- P-11: Legal-Hold-Sperre (legal_hold_active + Metadaten)
- P-03: Normaler Papierkorb-Lifecycle (lifecycle_state)
- P-13: Meldeverfahren (spaeter; nutzt denselben set_legal_hold-Service)
Berechtigungen:
- Setzen und Aufheben: ausschliesslich Superadmin.
- Lesezugriff auf Legal-Hold-Status: Superadmin und Plattform-Admin.
"""
from __future__ import annotations
from typing import Any, Optional
from fastapi import HTTPException
from club_tenancy import is_superadmin
from media_rights import write_audit_log_entry
LEGAL_HOLD_REASON_CODES = {
"rights_dispute",
"consent_withdrawn",
"privacy_complaint",
"copyright_complaint",
"youth_protection",
"illegal_content",
"other",
}
def assert_superadmin_for_legal_hold(role: Optional[str]) -> None:
if not is_superadmin(role):
raise HTTPException(
status_code=403,
detail="Legal-Hold-Aktionen sind nur fuer Superadmins verfuegbar.",
)
def is_media_available_for_normal_use(asset: dict) -> bool:
"""True wenn das Medium fuer normale Nutzerpfade verfuegbar ist.
Ein Medium ist NICHT verfuegbar wenn legal_hold_active = True,
unabhaengig vom lifecycle_state.
"""
return not bool(asset.get("legal_hold_active"))
def assert_not_under_legal_hold(asset: dict) -> None:
"""Wirft HTTPException 403 wenn das Medium unter Legal Hold steht."""
if bool(asset.get("legal_hold_active")):
raise HTTPException(
status_code=403,
detail={
"code": "LEGAL_HOLD_ACTIVE",
"message": (
"Dieses Medium ist durch einen Administrator sofort gesperrt und "
"kann nicht verwendet werden."
),
"asset_id": asset.get("id"),
},
)
def set_legal_hold(
cur: Any,
conn: Any,
asset_id: int,
acting_profile_id: int,
reason_code: str,
reason_note: Optional[str],
) -> dict:
"""Setzt Legal Hold auf ein Medium. Nur Superadmin.
Wirkung:
- legal_hold_active = TRUE
- legal_hold_reason_code, legal_hold_reason_note, legal_hold_set_by_profile_id, legal_hold_set_at
- rights_status = 'blocked' (Spiegel fuer schnelle Checks)
- Audit-Log-Eintrag 'legal_hold_set'
Gibt das aktualisierte Asset-Dict zurueck.
"""
if reason_code not in LEGAL_HOLD_REASON_CODES:
raise HTTPException(
status_code=400,
detail=f"Ungueltiger reason_code. Erlaubt: {sorted(LEGAL_HOLD_REASON_CODES)}",
)
if not reason_note or not reason_note.strip():
raise HTTPException(
status_code=400,
detail="reason_note (Begruendung) ist Pflicht beim Setzen eines Legal Holds.",
)
cur.execute(
"""SELECT id, legal_hold_active, rights_status, lifecycle_state, visibility
FROM media_assets WHERE id = %s""",
(asset_id,),
)
from db import r2d
row = cur.fetchone()
if not row:
raise HTTPException(status_code=404, detail="Medium nicht gefunden")
asset = r2d(row)
if bool(asset.get("legal_hold_active")):
raise HTTPException(
status_code=409,
detail="Dieses Medium befindet sich bereits unter Legal Hold.",
)
old_rights_status = asset.get("rights_status") or "legacy_unreviewed"
cur.execute(
"""UPDATE media_assets
SET legal_hold_active = TRUE,
legal_hold_reason_code = %s,
legal_hold_reason_note = %s,
legal_hold_set_by_profile_id = %s,
legal_hold_set_at = NOW(),
legal_hold_released_by_profile_id = NULL,
legal_hold_released_at = NULL,
legal_hold_release_note = NULL,
rights_status = 'blocked',
updated_at = NOW()
WHERE id = %s
RETURNING id, legal_hold_active, legal_hold_reason_code, legal_hold_reason_note,
legal_hold_set_by_profile_id, legal_hold_set_at, rights_status,
lifecycle_state, visibility""",
(reason_code, reason_note.strip()[:2000], acting_profile_id, asset_id),
)
updated_row = cur.fetchone()
updated = r2d(updated_row)
write_audit_log_entry(
cur,
asset_id=asset_id,
acting_profile_id=acting_profile_id,
event_type="legal_hold_set",
old_values={"rights_status": old_rights_status, "legal_hold_active": False},
new_values={
"rights_status": "blocked",
"legal_hold_active": True,
"reason_code": reason_code,
"reason_note": reason_note.strip()[:2000] if reason_note else None,
},
)
conn.commit()
return updated
def release_legal_hold(
cur: Any,
conn: Any,
asset_id: int,
acting_profile_id: int,
release_note: Optional[str],
) -> dict:
"""Hebt Legal Hold auf. Nur Superadmin.
Wirkung:
- legal_hold_active = FALSE
- legal_hold_released_by_profile_id, legal_hold_released_at, legal_hold_release_note
- rights_status: zurueck auf 'declared' wenn vorher declared war, sonst 'legacy_unreviewed'
(Entscheidungslogik: war vor dem Hold ein Deklarations-Eintrag vorhanden?)
- Audit-Log-Eintrag 'legal_hold_released'
Gibt das aktualisierte Asset-Dict zurueck.
"""
if not release_note or not release_note.strip():
raise HTTPException(
status_code=400,
detail="release_note (Freigabe-Begruendung) ist Pflicht.",
)
cur.execute(
"""SELECT id, legal_hold_active, rights_status, lifecycle_state, visibility,
legal_hold_reason_code, legal_hold_reason_note
FROM media_assets WHERE id = %s""",
(asset_id,),
)
from db import r2d
row = cur.fetchone()
if not row:
raise HTTPException(status_code=404, detail="Medium nicht gefunden")
asset = r2d(row)
if not bool(asset.get("legal_hold_active")):
raise HTTPException(
status_code=409,
detail="Dieses Medium befindet sich nicht unter Legal Hold.",
)
# Bestimme den rights_status nach Freigabe:
# Gibt es eine gueltige Deklaration? → 'declared', sonst 'legacy_unreviewed'
cur.execute(
"""SELECT COUNT(*) AS cnt FROM media_asset_rights_declarations
WHERE media_asset_id = %s
AND action_type NOT IN ('correction')
AND rights_holder_confirmed = TRUE""",
(asset_id,),
)
decl_row = cur.fetchone()
decl_count = int(decl_row[0] if not hasattr(decl_row, "keys") else decl_row["cnt"])
restored_rights_status = "declared" if decl_count > 0 else "legacy_unreviewed"
cur.execute(
"""UPDATE media_assets
SET legal_hold_active = FALSE,
legal_hold_released_by_profile_id = %s,
legal_hold_released_at = NOW(),
legal_hold_release_note = %s,
rights_status = %s,
updated_at = NOW()
WHERE id = %s
RETURNING id, legal_hold_active, rights_status, lifecycle_state, visibility,
legal_hold_reason_code, legal_hold_reason_note,
legal_hold_set_by_profile_id, legal_hold_set_at,
legal_hold_released_by_profile_id, legal_hold_released_at,
legal_hold_release_note""",
(acting_profile_id, release_note.strip()[:2000], restored_rights_status, asset_id),
)
updated_row = cur.fetchone()
updated = r2d(updated_row)
write_audit_log_entry(
cur,
asset_id=asset_id,
acting_profile_id=acting_profile_id,
event_type="legal_hold_released",
old_values={
"rights_status": "blocked",
"legal_hold_active": True,
"reason_code": asset.get("legal_hold_reason_code"),
},
new_values={
"rights_status": restored_rights_status,
"legal_hold_active": False,
"release_note": release_note.strip()[:2000] if release_note else None,
},
)
conn.commit()
return updated

View File

@ -21,26 +21,33 @@ LC_TRASH_SOFT = "trash_soft"
LC_TRASH_HIDDEN = "trash_hidden"
SOFT_TO_HIDDEN_DAYS = max(1, int(os.getenv("MEDIA_TRASH_SOFT_TO_HIDDEN_DAYS", "30")))
HIDDEN_TO_PURGE_DAYS = max(1, int(os.getenv("MEDIA_TRASH_HIDDEN_TO_PURGE_DAYS", "90")))
# P-03b: Default gemaess fachlichem Loeschkonzept (Audit 2026-05-09): 30+30 Tage.
HIDDEN_TO_PURGE_DAYS = max(1, int(os.getenv("MEDIA_TRASH_HIDDEN_TO_PURGE_DAYS", "30")))
def assert_can_manage_media_asset_lifecycle(cur: Any, tenant: Any, asset: dict) -> None:
"""
Papierkorb Stufe 2 / Recovery / Reaktivierung nicht für trash_soft (siehe assert_can_trash_soft).
§5.2: official nur Plattform-Admin; club Vereinsorga; privat nur Uploader.
§5.2: official nur Superadmin; club Vereinsorga; privat nur Uploader (Plattform-Admin sonst wie bisher).
"""
profile_id = tenant.profile_id
role = (tenant.global_role or "").strip().lower()
vis = (asset.get("visibility") or "private").strip().lower()
if vis == "official":
if not is_superadmin(role):
raise HTTPException(
status_code=403,
detail="Offizielle Medien dürfen nur von Superadmins geändert oder gelöscht werden",
)
return
if is_platform_admin(role):
return
vis = (asset.get("visibility") or "private").strip().lower()
uid = asset.get("uploaded_by_profile_id")
if vis == "private":
if uid is not None and int(uid) == int(profile_id):
return
raise HTTPException(status_code=403, detail="Keine Berechtigung für dieses Medium")
if vis == "official":
raise HTTPException(status_code=403, detail="Nur Plattform-Admin")
if vis == "club":
cid = asset.get("club_id")
if cid is None:
@ -54,15 +61,20 @@ def assert_can_manage_media_asset_lifecycle(cur: Any, tenant: Any, asset: dict)
def assert_can_trash_soft(cur: Any, tenant: Any, asset: dict) -> None:
"""
Aktiv Papierkorb (Stufe 1). Trainer: nur eigene private Uploads.
Vereinsmedien: Vereinsorga; official: Plattform-Admin; Superadmin: immer.
Vereinsmedien: Vereinsorga; official: nur Superadmin; Plattform-Admin: sonst wie Verein/privat.
"""
role_raw = tenant.global_role
role = (role_raw or "").strip().lower()
if is_superadmin(role):
return
vis = (asset.get("visibility") or "private").strip().lower()
if vis == "official":
raise HTTPException(
status_code=403,
detail="Offizielle Medien dürfen nur von Superadmins in den Papierkorb gelegt werden",
)
if is_platform_admin(role):
return
vis = (asset.get("visibility") or "private").strip().lower()
uid = asset.get("uploaded_by_profile_id")
cid = asset.get("club_id")
pid = int(tenant.profile_id)
@ -80,8 +92,6 @@ def assert_can_trash_soft(cur: Any, tenant: Any, asset: dict) -> None:
status_code=403,
detail="Nur Vereinsorganisation darf Vereinsmedien in den Papierkorb legen",
)
if vis == "official":
raise HTTPException(status_code=403, detail="Nur Plattform-Admin")
raise HTTPException(status_code=403, detail="Keine Berechtigung für dieses Medium")
@ -269,6 +279,10 @@ def run_retention_pass(cur: Any, conn: Any) -> dict:
"""
Automatik: trash_soft älter als SOFT_TO_HIDDEN_DAYS trash_hidden;
trash_hidden mit purge_after_at in der Vergangenheit purge.
P-11: Medien unter aktivem Legal Hold werden NICHT gerpurged (legal_hold_active = TRUE).
Die Retention verschiebt sie auch nicht automatisch von trash_soft nach trash_hidden
Legal-Hold-Status hat Vorrang vor dem Papierkorb-Lifecycle.
"""
cutoff_soft = datetime.now(timezone.utc) - timedelta(days=SOFT_TO_HIDDEN_DAYS)
cur.execute(
@ -276,6 +290,7 @@ def run_retention_pass(cur: Any, conn: Any) -> dict:
SET lifecycle_state = %s, trash_hidden_at = NOW(), updated_at = NOW(),
purge_after_at = NOW() + (%s * INTERVAL '1 day')
WHERE lifecycle_state = %s AND trash_soft_at IS NOT NULL AND trash_soft_at <= %s
AND (legal_hold_active = FALSE OR legal_hold_active IS NULL)
RETURNING id""",
(LC_TRASH_HIDDEN, HIDDEN_TO_PURGE_DAYS, LC_TRASH_SOFT, cutoff_soft),
)
@ -284,7 +299,8 @@ def run_retention_pass(cur: Any, conn: Any) -> dict:
cur.execute(
"""SELECT id FROM media_assets
WHERE lifecycle_state = %s AND purge_after_at IS NOT NULL AND purge_after_at <= NOW()""",
WHERE lifecycle_state = %s AND purge_after_at IS NOT NULL AND purge_after_at <= NOW()
AND (legal_hold_active = FALSE OR legal_hold_active IS NULL)""",
(LC_TRASH_HIDDEN,),
)
purge_ids = [r2d(r)["id"] for r in cur.fetchall()]

412
backend/media_rights.py Normal file
View File

@ -0,0 +1,412 @@
"""P-06: Zentrale Rechte-Policy fuer Medien-Uploads und Promotionen.
Konservative Erstannahmen (p06-v1-conservative):
- Alle Uploads (inkl. private) erfordern vollstaendige Erklaerung
- Personenfragen bei allen Sichtbarkeiten Pflicht zu beantworten
- Promotion zu hoeherem Niveau erfordert neue Erklaerung
- Altmedien ('legacy_unreviewed') duerfen nicht promoted werden
VORLAEUTIG: Juristische Validierung der Felder und Texte steht aus.
"""
from __future__ import annotations
import json as _json
from typing import Any, Optional
from fastapi import HTTPException
DECLARATION_VERSION = "p06-v1-conservative"
# Sichtbarkeits-Hierarchie: private(1) < club(2) < official(3)
VISIBILITY_LEVELS: dict[str, int] = {
"private": 1,
"club": 2,
"official": 3,
}
def visibility_level(vis: str) -> int:
return VISIBILITY_LEVELS.get((vis or "").strip().lower(), 0)
def rights_covers_target(declared_for: Optional[str], target_vis: str) -> bool:
"""True wenn die vorhandene Erklaerung die Ziel-Sichtbarkeit abdeckt."""
if not declared_for:
return False
return visibility_level(declared_for) >= visibility_level(target_vis)
# --------------------------------------------------------------------------
# Validierung einer eingehenden Erklaerung
# --------------------------------------------------------------------------
def validate_rights_declaration(decl: dict[str, Any], target_visibility: str) -> None:
"""Pruefen ob alle Pflichtfelder der konservativen Erstannahme vorliegen.
Wirft HTTPException 400 mit maschinenlesbarem code bei Verstoss.
Gilt fuer alle Sichtbarkeiten (private/club/official) identisch.
"""
# 1. rights_holder_confirmed ist immer Pflicht
if not decl.get("rights_holder_confirmed"):
raise HTTPException(
status_code=400,
detail={
"code": "RIGHTS_DECLARATION_REQUIRED",
"message": (
"Bitte bestaetigen, dass du die erforderlichen Rechte an diesem Medium besitzt."
),
},
)
# 2. contains_identifiable_persons muss explizit beantwortet sein
if decl.get("contains_identifiable_persons") is None:
raise HTTPException(
status_code=400,
detail={
"code": "RIGHTS_DECLARATION_REQUIRED",
"message": "Bitte angeben, ob erkennbare Personen abgebildet sind.",
},
)
# 3. Wenn Personen vorhanden: Einwilligung Pflicht
if decl.get("contains_identifiable_persons") is True:
if not decl.get("person_consent_confirmed"):
raise HTTPException(
status_code=400,
detail={
"code": "PERSON_CONSENT_REQUIRED",
"message": (
"Bitte bestaetigen, dass die Einwilligungen aller erkennbaren Personen vorliegen."
),
},
)
# 4. contains_minors muss explizit beantwortet sein
if decl.get("contains_minors") is None:
raise HTTPException(
status_code=400,
detail={
"code": "RIGHTS_DECLARATION_REQUIRED",
"message": "Bitte angeben, ob Minderjaehrige abgebildet sind.",
},
)
# 5. Wenn Minderjaehrige: Elterneinwilligung Pflicht
if decl.get("contains_minors") is True:
if not decl.get("parental_consent_confirmed"):
raise HTTPException(
status_code=400,
detail={
"code": "PARENTAL_CONSENT_REQUIRED",
"message": (
"Bitte bestaetigen, dass die Einwilligungen der Sorgeberechtigten vorliegen."
),
},
)
# 6. contains_music muss explizit beantwortet sein
if decl.get("contains_music") is None:
raise HTTPException(
status_code=400,
detail={
"code": "RIGHTS_DECLARATION_REQUIRED",
"message": "Bitte angeben, ob das Medium Musik enthaelt.",
},
)
# 7. Wenn Musik: Musikrechte Pflicht
if decl.get("contains_music") is True:
if not decl.get("music_rights_confirmed"):
raise HTTPException(
status_code=400,
detail={
"code": "MUSIC_RIGHTS_REQUIRED",
"message": (
"Bitte bestaetigen, dass die erforderlichen Musikrechte vorliegen."
),
},
)
# 8. contains_third_party_content muss explizit beantwortet sein
if decl.get("contains_third_party_content") is None:
raise HTTPException(
status_code=400,
detail={
"code": "RIGHTS_DECLARATION_REQUIRED",
"message": "Bitte angeben, ob fremde geschuetzte Inhalte (Logos, Grafiken etc.) enthalten sind.",
},
)
# 9. Wenn Fremdmaterial: Rechte Pflicht
if decl.get("contains_third_party_content") is True:
if not decl.get("third_party_rights_confirmed"):
raise HTTPException(
status_code=400,
detail={
"code": "THIRD_PARTY_RIGHTS_REQUIRED",
"message": (
"Bitte bestaetigen, dass die Rechte an allen enthaltenen Fremdmaterialien vorliegen."
),
},
)
# --------------------------------------------------------------------------
# Pruefen ob vorhandene Erklaerung Zielsichtbarkeit abdeckt
# --------------------------------------------------------------------------
def check_rights_coverage(cur: Any, asset_id: int, target_visibility: str) -> str:
"""Status der Rechteabdeckung fuer ein Asset und eine Zielsichtbarkeit.
Returns:
'ok' - vorhandene Erklaerung reicht aus
'legacy' - Altmedium ohne Erklaerung (legacy_unreviewed)
'blocked' - durch Admin gesperrt
'no_declaration' - neues Medium ohne Erklaerung (sollte nicht vorkommen)
Hinweis: Eine P-06-Erklaerung beschreibt den Inhalt (Rechteinhaber, Personen, Musik etc.)
und ist sichtbarkeitsunabhaengig. rights_status='declared' gilt daher fuer alle
Sichtbarkeits-Stufen ohne Levelvergleich.
"""
cur.execute(
"SELECT rights_status FROM media_assets WHERE id = %s",
(asset_id,),
)
row = cur.fetchone()
if not row:
return "no_declaration"
rs = (row[0] if not hasattr(row, "keys") else row["rights_status"] or "").strip().lower()
if rs == "blocked":
return "blocked"
if rs == "legacy_unreviewed":
return "legacy"
if rs == "declared":
return "ok"
return "no_declaration"
def assert_rights_for_promotion(cur: Any, asset_id: int, target_visibility: str) -> None:
"""Wirft HTTPException wenn das Asset keine gueltige Erklaerung fuer target_visibility hat."""
status = check_rights_coverage(cur, asset_id, target_visibility)
if status == "ok":
return
if status == "legacy":
raise HTTPException(
status_code=400,
detail={
"code": "LEGACY_REDECLARATION_REQUIRED",
"message": (
"Dieses Medium wurde vor Einfuehrung der Einwilligungspflicht hochgeladen. "
"Bitte eine Rechterklaerung nachreichen, bevor die Sichtbarkeit erhoeht wird."
),
"asset_id": asset_id,
},
)
if status == "blocked":
raise HTTPException(
status_code=403,
detail={
"code": "RIGHTS_BLOCKED",
"message": "Dieses Medium ist durch einen Administrator gesperrt.",
"asset_id": asset_id,
},
)
# no_declaration (neues Medium ohne Erklaerung)
raise HTTPException(
status_code=400,
detail={
"code": "RIGHTS_DECLARATION_REQUIRED",
"message": "Fuer dieses Medium liegt keine Rechterklaerung vor.",
"asset_id": asset_id,
},
)
def assert_rights_for_exercise_link(cur: Any, asset_id: int, exercise_visibility: str) -> None:
"""Pruefen ob das Asset in eine Uebung mit dieser Sichtbarkeit eingebunden werden darf."""
status = check_rights_coverage(cur, asset_id, exercise_visibility)
if status == "ok":
return
if status == "legacy" and exercise_visibility == "private":
# Altmedien duerfen in private Uebungen eingebunden bleiben (kein Upgrade-Risiko)
return
if status == "legacy":
raise HTTPException(
status_code=400,
detail={
"code": "LEGACY_REDECLARATION_REQUIRED",
"message": (
"Das gewahlte Archiv-Medium hat noch keine Rechterklaerung nach neuem Standard. "
"Bitte zuerst eine Erklaerung fuer dieses Medium abgeben."
),
"asset_id": asset_id,
},
)
if status == "insufficient":
raise HTTPException(
status_code=400,
detail={
"code": "RIGHTS_SCOPE_INSUFFICIENT",
"message": (
f"Das Archiv-Medium hat keine Erklaerung fuer Sichtbarkeit '{exercise_visibility}'. "
"Bitte zuerst eine neue Erklaerung fuer dieses Medium abgeben."
),
"asset_id": asset_id,
},
)
if status == "blocked":
raise HTTPException(
status_code=403,
detail={
"code": "RIGHTS_BLOCKED",
"message": "Dieses Medium ist gesperrt und kann nicht verwendet werden.",
"asset_id": asset_id,
},
)
# --------------------------------------------------------------------------
# Declaration-Log schreiben + Schnellfelder aktualisieren
# --------------------------------------------------------------------------
def _clean_context(val: Any) -> Optional[str]:
"""Leere Strings → None, sonst auf 2000 Zeichen kuerzen."""
s = (val or "").strip()
return s[:2000] if s else None
def write_rights_declaration(
cur: Any,
asset_id: int,
profile_id: int,
action_type: str,
target_visibility: str,
decl: dict[str, Any],
) -> int:
"""Schreibt einen neuen Eintrag in media_asset_rights_declarations (append-only).
Returns: id des neuen Eintrags
"""
cur.execute(
"""INSERT INTO media_asset_rights_declarations (
media_asset_id, declared_by_profile_id, action_type, target_visibility,
declaration_version,
rights_holder_confirmed,
contains_identifiable_persons, person_consent_confirmed, person_consent_context,
contains_minors, parental_consent_confirmed, parental_consent_context,
contains_music, music_rights_confirmed, music_rights_context,
contains_third_party_content, third_party_rights_confirmed, third_party_rights_context
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
RETURNING id""",
(
asset_id,
profile_id,
action_type,
target_visibility,
DECLARATION_VERSION,
bool(decl.get("rights_holder_confirmed")),
decl.get("contains_identifiable_persons"),
decl.get("person_consent_confirmed"),
_clean_context(decl.get("person_consent_context")),
decl.get("contains_minors"),
decl.get("parental_consent_confirmed"),
_clean_context(decl.get("parental_consent_context")),
decl.get("contains_music"),
decl.get("music_rights_confirmed"),
_clean_context(decl.get("music_rights_context")),
decl.get("contains_third_party_content"),
decl.get("third_party_rights_confirmed"),
_clean_context(decl.get("third_party_rights_context")),
),
)
row = cur.fetchone()
if hasattr(row, "keys"):
return int(row["id"])
return int(row[0])
def write_audit_log_entry(
cur: Any,
asset_id: int,
acting_profile_id: Optional[int],
event_type: str,
old_values: dict,
new_values: dict,
) -> None:
"""Schreibt einen Eintrag in media_asset_audit_log (append-only)."""
cur.execute(
"""INSERT INTO media_asset_audit_log
(media_asset_id, acting_profile_id, event_type, old_values, new_values)
VALUES (%s, %s, %s, %s, %s)""",
(
asset_id,
acting_profile_id,
event_type,
_json.dumps(old_values, default=str),
_json.dumps(new_values, default=str),
),
)
def write_rights_correction_declaration(
cur: Any,
asset_id: int,
profile_id: int,
target_visibility: str,
decl: dict[str, Any],
correction_note: Optional[str],
) -> int:
"""Schreibt eine Korrektur-Deklaration (action_type='correction', append-only)."""
cur.execute(
"""INSERT INTO media_asset_rights_declarations (
media_asset_id, declared_by_profile_id, action_type, target_visibility,
declaration_version,
rights_holder_confirmed,
contains_identifiable_persons, person_consent_confirmed, person_consent_context,
contains_minors, parental_consent_confirmed, parental_consent_context,
contains_music, music_rights_confirmed, music_rights_context,
contains_third_party_content, third_party_rights_confirmed, third_party_rights_context,
correction_note
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
RETURNING id""",
(
asset_id,
profile_id,
"correction",
target_visibility,
DECLARATION_VERSION,
bool(decl.get("rights_holder_confirmed")),
decl.get("contains_identifiable_persons"),
decl.get("person_consent_confirmed"),
_clean_context(decl.get("person_consent_context")),
decl.get("contains_minors"),
decl.get("parental_consent_confirmed"),
_clean_context(decl.get("parental_consent_context")),
decl.get("contains_music"),
decl.get("music_rights_confirmed"),
_clean_context(decl.get("music_rights_context")),
decl.get("contains_third_party_content"),
decl.get("third_party_rights_confirmed"),
_clean_context(decl.get("third_party_rights_context")),
_clean_context(correction_note),
),
)
row = cur.fetchone()
if hasattr(row, "keys"):
return int(row["id"])
return int(row[0])
def update_rights_quick_fields(cur: Any, asset_id: int, target_visibility: str) -> None:
"""Setzt die Schnellfelder in media_assets nach erfolgreicher Deklaration."""
cur.execute(
"""UPDATE media_assets
SET rights_status = 'declared',
rights_declared_for_visibility = %s,
rights_declared_at = NOW(),
updated_at = NOW()
WHERE id = %s""",
(target_visibility, asset_id),
)

View File

@ -3,9 +3,23 @@ from __future__ import annotations
import os
import re
import shutil
import unicodedata
from pathlib import Path
from typing import Any, Optional
_CLUB_SLUG_TRANS = str.maketrans(
{
"ä": "ae",
"ö": "oe",
"ü": "ue",
"ß": "ss",
"Ä": "ae",
"Ö": "oe",
"Ü": "ue",
}
)
def _default_media_root() -> Path:
return Path(os.getenv("MEDIA_ROOT", str(Path(__file__).resolve().parent / "media")))
@ -44,6 +58,70 @@ def get_effective_media_root(cur: Any) -> Path:
return (base / rel).resolve()
def library_media_kind_dir(mime_type: Optional[str], ext: str) -> str:
"""
Unterordner unter library/* an API-Filter media_kind angelehnt (image/video/pdf/other).
Bei fehlendem MIME wird anhand der Dateiendung geraten (letzte Instanz: other).
"""
m = (mime_type or "").strip().lower()
x = (ext or "").strip().lower()
if x and not x.startswith("."):
x = "." + x
if m.startswith("image/"):
return "image"
if m.startswith("video/"):
return "video"
if m == "application/pdf" or (m and "pdf" in m):
return "pdf"
if x in (
".jpg",
".jpeg",
".png",
".gif",
".webp",
".heic",
".heif",
".bmp",
".svg",
".avif",
):
return "image"
if x in (".mp4", ".mov", ".webm", ".mkv", ".avi", ".m4v"):
return "video"
if x == ".pdf":
return "pdf"
return "other"
def library_club_path_segment(club_id: int, club_name: Optional[str]) -> str:
"""
Ein Verzeichnissegment pro Verein: aus Anzeigenamen abgeleitet + -c{id} für Eindeutigkeit
(Umbenennung, Kollisionen nach Slugify). Nur [a-z0-9-], keine Pfad-Sonderzeichen.
Fehlt der Name / Slug leer verein-c{id}.
"""
cid = int(club_id)
if cid < 1:
raise ValueError("club_id muss eine positive Ganzzahl sein")
raw = unicodedata.normalize("NFKC", (club_name or "").strip()).translate(_CLUB_SLUG_TRANS).lower()
raw = re.sub(r"[^a-z0-9]+", "-", raw)
raw = raw.strip("-")
if len(raw) > 48:
raw = raw[:48].rstrip("-")
if not raw:
base = f"verein-c{cid}"
else:
base = f"{raw}-c{cid}"
if not re.fullmatch(r"[a-z0-9]+(?:-[a-z0-9]+)*", base):
raise ValueError("Ungültiger abgeleiteter Vereins-Ordnername")
return base
def path_under_media_root(media_root: Path, storage_key: str) -> Optional[Path]:
"""Gibt absoluten Pfad zurück oder None bei Path-Traversal."""
key = (storage_key or "").strip().replace("\\", "/").lstrip("/")
@ -55,3 +133,97 @@ def path_under_media_root(media_root: Path, storage_key: str) -> Optional[Path]:
except ValueError:
return None
return p
def library_storage_key(
visibility: str,
club_id: Optional[int],
sha256_hex: str,
ext: str,
*,
uploader_profile_id: Optional[int] = None,
mime_type: Optional[str] = None,
club_name: Optional[str] = None,
) -> str:
"""
Relativer Speicherpfad unter MEDIA_ROOT für lokale media_assets.
- official library/official/{kind}/{sha256}{ext}
- club (vereinsgeteilt) library/{vereins-segment}/{kind}/{sha256}{ext}
- private dieselbe Ordnerlogik wie Verein: library/{vereins-segment}/{kind}/{sha}.u{profile}{ext}
Dateiname bei private: {sha}.u{profile_id}{ext} (nicht Unterordner u{}), damit Ordnerstruktur wie bei Verein.
Vereins-Segment: aus club_name abgeleitet + -c{club_id} siehe library_club_path_segment.
kind {image, video, pdf, other} siehe library_media_kind_dir.
Kein Ordnername private auf der Platte. Private Dateien unterscheiden sich nur im Dateinamen (.u{Profil} vor Endung).
"""
vis = (visibility or "private").strip().lower()
if vis not in ("private", "club", "official"):
raise ValueError(f"Ungültige Sichtbarkeit für Speicherpfad: {visibility!r}")
sha = (sha256_hex or "").strip().lower()
if len(sha) != 64 or any(c not in "0123456789abcdef" for c in sha):
raise ValueError("sha256_hex muss 64 Hex-Zeichen sein")
e = (ext or "").strip()
if not e:
e = ".bin"
if not e.startswith("."):
e = "." + e
if ".." in e or "/" in e or "\\" in e or "\x00" in e:
raise ValueError("Ungültige Dateiendung")
kind = library_media_kind_dir(mime_type, e)
e = e[:16]
club_blob = f"{kind}/{sha}{e}"
if vis == "official":
return f"library/official/{club_blob}"
if club_id is None:
raise ValueError("Verein (club_id) ist für diese Sichtbarkeit auf der Platte erforderlich")
cid = int(club_id)
if cid < 1:
raise ValueError("club_id muss eine positive Ganzzahl sein")
club_seg = library_club_path_segment(cid, club_name)
if vis == "club":
return f"library/{club_seg}/{club_blob}"
if uploader_profile_id is None:
raise ValueError("uploader_profile_id ist für private Archiv-Medien erforderlich")
up = int(uploader_profile_id)
if up < 1:
raise ValueError("uploader_profile_id muss positiv sein")
priv_name = f"{sha}.u{up}{e}"
return f"library/{club_seg}/{kind}/{priv_name}"
def relocate_local_media_file(media_root: Path, old_storage_key: str, new_storage_key: str) -> None:
"""
Physisches Verschieben bei geändertem library_*-Pfad (z. B. nach PATCH visibility/club_id).
- Kein Op, wenn Quelle fehlt aber Ziel bereits existiert (idempotent).
- Erwartet storage_backend=local; Aufrufer prüft das.
"""
if (old_storage_key or "").replace("\\", "/").lstrip("/") == (new_storage_key or "").replace(
"\\", "/"
).lstrip("/"):
return
old_p = path_under_media_root(media_root, old_storage_key)
new_p = path_under_media_root(media_root, new_storage_key)
if old_p is None or new_p is None:
raise ValueError("Ungültiger Speicherpfad (Path-Traversal)")
if new_p.is_file():
if not old_p.is_file():
return
if old_p.resolve() == new_p.resolve():
return
raise FileExistsError(f"Zieldatei existiert bereits: {new_storage_key}")
if not old_p.is_file():
raise FileNotFoundError(f"Medien-Quelldatei nicht gefunden: {old_storage_key}")
new_p.parent.mkdir(parents=True, exist_ok=True)
shutil.move(str(old_p), str(new_p))

View File

@ -0,0 +1,37 @@
-- Migration 047: Admin-konfigurierbare Rechtstexte
-- Tabellen: legal_documents (versioniert), legal_document_audit (Änderungslog)
-- document_type: impressum | privacy_policy | terms_of_use | media_policy
-- status: draft | published | archived
-- Partial unique index: nur genau ein published-Dokument pro document_type erlaubt.
CREATE TABLE IF NOT EXISTS legal_documents (
id SERIAL PRIMARY KEY,
document_type VARCHAR(50) NOT NULL
CHECK (document_type IN ('impressum', 'privacy_policy', 'terms_of_use', 'media_policy')),
version INT NOT NULL DEFAULT 1,
title VARCHAR(255) NOT NULL,
content_sections JSONB NOT NULL DEFAULT '[]',
status VARCHAR(20) NOT NULL DEFAULT 'draft'
CHECK (status IN ('draft', 'published', 'archived')),
change_note TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
created_by_profile_id INT REFERENCES profiles(id) ON DELETE SET NULL,
published_by_profile_id INT REFERENCES profiles(id) ON DELETE SET NULL,
published_at TIMESTAMP
);
-- Sicherstellt: pro document_type maximal ein published-Datensatz
CREATE UNIQUE INDEX IF NOT EXISTS legal_documents_unique_published
ON legal_documents (document_type)
WHERE status = 'published';
CREATE TABLE IF NOT EXISTS legal_document_audit (
id SERIAL PRIMARY KEY,
legal_document_id INT NOT NULL REFERENCES legal_documents(id) ON DELETE CASCADE,
action VARCHAR(50) NOT NULL,
changed_by_profile_id INT REFERENCES profiles(id) ON DELETE SET NULL,
change_note TEXT,
previous_status VARCHAR(20),
created_at TIMESTAMP DEFAULT NOW()
);

View File

@ -0,0 +1,75 @@
-- Migration 048: P-06 Upload-Einwilligungsdialog
-- Append-only Deklarations-Log + Schnellfelder in media_assets
-- Alle bestehenden Medien erhalten rights_status = 'legacy_unreviewed'
-- Deklarations-Log (append-only, wird nie geaendert oder geloescht)
CREATE TABLE IF NOT EXISTS media_asset_rights_declarations (
id SERIAL PRIMARY KEY,
media_asset_id INT NOT NULL REFERENCES media_assets(id) ON DELETE CASCADE,
declared_by_profile_id INT REFERENCES profiles(id) ON DELETE SET NULL,
declared_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
-- Kontext der Erklaerung
action_type VARCHAR(50) NOT NULL
CHECK (action_type IN (
'upload', -- Erstupload
'promote_club', -- Promotion zu club
'promote_official', -- Promotion zu official
're_declaration', -- Freiwillige Nacherklaerung
'legacy_re_declaration' -- Altmedium: erste Erklaerung nachgereicht
)),
target_visibility VARCHAR(32) NOT NULL
CHECK (target_visibility IN ('private', 'club', 'official')),
-- Textversion der Erklaerung; 'p06-v1-conservative' = konservative Erstannahmen
-- VORLAEUTIG: Texte noch nicht juristisch geprueft
declaration_version VARCHAR(40) NOT NULL DEFAULT 'p06-v1-conservative',
-- Pflichtfeld (alle Sichtbarkeiten, alle Aktionen)
rights_holder_confirmed BOOLEAN NOT NULL,
-- Personen (konservative Annahme: immer abgefragt, auch bei 'private')
contains_identifiable_persons BOOLEAN,
person_consent_confirmed BOOLEAN, -- Pflicht wenn contains_identifiable_persons = true
-- Minderjaehrige
contains_minors BOOLEAN,
parental_consent_confirmed BOOLEAN, -- Pflicht wenn contains_minors = true
-- Drittmaterial
contains_music BOOLEAN,
music_rights_confirmed BOOLEAN, -- Pflicht wenn contains_music = true
contains_third_party_content BOOLEAN,
third_party_rights_confirmed BOOLEAN, -- Pflicht wenn contains_third_party_content = true
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_mard_asset
ON media_asset_rights_declarations (media_asset_id);
CREATE INDEX IF NOT EXISTS idx_mard_profile
ON media_asset_rights_declarations (declared_by_profile_id);
CREATE INDEX IF NOT EXISTS idx_mard_action_type
ON media_asset_rights_declarations (action_type);
-- Schnellfelder in media_assets (kein Ersatz fuer den Log, nur fuer effiziente Abfragen)
ALTER TABLE media_assets
ADD COLUMN IF NOT EXISTS rights_status VARCHAR(32)
NOT NULL DEFAULT 'legacy_unreviewed'
CHECK (rights_status IN ('legacy_unreviewed', 'declared', 'blocked')),
ADD COLUMN IF NOT EXISTS rights_declared_for_visibility VARCHAR(32)
CHECK (rights_declared_for_visibility IN ('private', 'club', 'official')),
ADD COLUMN IF NOT EXISTS rights_declared_at TIMESTAMPTZ;
-- Bestehende Medien: explicit legacy_unreviewed setzen (redundant zum DEFAULT, zur Klarheit)
UPDATE media_assets
SET rights_status = 'legacy_unreviewed'
WHERE rights_status = 'legacy_unreviewed'; -- no-op, setzt Default explizit
COMMENT ON TABLE media_asset_rights_declarations IS
'P-06: Append-only Erklaerungslog fuer Upload-Einwilligungen. '
'Eintraege werden nie geaendert. Juristische Validierung der Felder und Texte steht aus.';
COMMENT ON COLUMN media_assets.rights_status IS
'P-06: legacy_unreviewed = Altbestand ohne P-06-Erklaerung; '
'declared = gueltige Erklaerung fuer rights_declared_for_visibility; '
'blocked = durch Admin gesperrt (P-11-Schnittstelle).';

View File

@ -0,0 +1,18 @@
-- Migration 049: P-06 Erweiterung Einwilligungskontext-Felder und Copyright im Upload-Dialog
-- Optionale Freitextfelder fuer den Kontext der Einwilligung (z.B. "Schriftliche Einwilligung
-- vom 2026-05-01 liegt vor") sowie copyright_notice direkt beim Upload erfassbar.
ALTER TABLE media_asset_rights_declarations
ADD COLUMN IF NOT EXISTS person_consent_context TEXT,
ADD COLUMN IF NOT EXISTS parental_consent_context TEXT,
ADD COLUMN IF NOT EXISTS music_rights_context TEXT,
ADD COLUMN IF NOT EXISTS third_party_rights_context TEXT;
COMMENT ON COLUMN media_asset_rights_declarations.person_consent_context IS
'Optionaler Freitext: In welchem Zusammenhang liegt die Einwilligung der abgebildeten Personen vor?';
COMMENT ON COLUMN media_asset_rights_declarations.parental_consent_context IS
'Optionaler Freitext: In welchem Zusammenhang liegt die Einwilligung der Sorgeberechtigten vor?';
COMMENT ON COLUMN media_asset_rights_declarations.music_rights_context IS
'Optionaler Freitext: Welche Lizenz / GEMA-Regelung liegt fuer die Musik vor?';
COMMENT ON COLUMN media_asset_rights_declarations.third_party_rights_context IS
'Optionaler Freitext: Auf welcher Grundlage duerfen die enthaltenen Fremdinhalte verwendet werden?';

View File

@ -0,0 +1,47 @@
-- Migration 050: Medien-Volljournal Audit-Log für alle Änderungen + Korrektur-Deklarationen
-- Vollständiger Audit-Log: Sichtbarkeitsänderungen, Copyright, Metadaten, Lifecycle
CREATE TABLE IF NOT EXISTS media_asset_audit_log (
id SERIAL PRIMARY KEY,
media_asset_id INT NOT NULL REFERENCES media_assets(id) ON DELETE CASCADE,
acting_profile_id INT REFERENCES profiles(id) ON DELETE SET NULL,
occurred_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
event_type VARCHAR(50) NOT NULL
CHECK (event_type IN (
'visibility_change',
'copyright_change',
'metadata_change',
'lifecycle_change'
)),
old_values JSONB,
new_values JSONB
);
CREATE INDEX IF NOT EXISTS idx_maal_asset ON media_asset_audit_log (media_asset_id);
CREATE INDEX IF NOT EXISTS idx_maal_asset_occurred ON media_asset_audit_log (media_asset_id, occurred_at);
COMMENT ON TABLE media_asset_audit_log IS
'Append-only Protokoll aller Aenderungen an Medien-Assets (Sichtbarkeit, Copyright, Metadaten, Lifecycle). '
'Wird nie aktualisiert oder geloescht (ausser ON DELETE CASCADE des Assets).';
-- Korrektur-Notiz für nachtraegliche Deklarations-Korrekturen
ALTER TABLE media_asset_rights_declarations
ADD COLUMN IF NOT EXISTS correction_note TEXT;
COMMENT ON COLUMN media_asset_rights_declarations.correction_note IS
'Optionale Begruendung fuer action_type=correction: Warum wurde die Erklaerung korrigiert?';
-- ''correction'' action_type hinzufuegen (bestehende CHECK-Constraint ersetzen)
ALTER TABLE media_asset_rights_declarations
DROP CONSTRAINT IF EXISTS media_asset_rights_declarations_action_type_check;
ALTER TABLE media_asset_rights_declarations
ADD CONSTRAINT media_asset_rights_declarations_action_type_check
CHECK (action_type IN (
'upload',
'promote_club',
'promote_official',
're_declaration',
'legacy_re_declaration',
'correction'
));

View File

@ -0,0 +1,73 @@
-- Migration 051: P-11 Legal-Hold Lifecycle-Status
-- Sofortsperrung fuer rechtlich problematische Medien (Compliance-Paket P-11)
--
-- Architekturentscheidung:
-- rights_status='blocked' bleibt als Spiegel-Schnellstatus (P-06-Kompatibilitaet).
-- Primaere Wahrheit: legal_hold_active + dedizierte Metadaten-Felder in media_assets.
-- Dies ermoeglicht klare Trennung: P-06 Deklarationsstatus / P-11 Legal Hold / P-03 Lifecycle.
-- P-13 kann spaeter denselben set_legal_hold-Service nutzen.
ALTER TABLE media_assets
ADD COLUMN IF NOT EXISTS legal_hold_active BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS legal_hold_reason_code VARCHAR(50)
CHECK (legal_hold_reason_code IN (
'rights_dispute',
'consent_withdrawn',
'privacy_complaint',
'copyright_complaint',
'youth_protection',
'illegal_content',
'other'
)),
ADD COLUMN IF NOT EXISTS legal_hold_reason_note TEXT,
ADD COLUMN IF NOT EXISTS legal_hold_set_by_profile_id INT REFERENCES profiles(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS legal_hold_set_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS legal_hold_released_by_profile_id INT REFERENCES profiles(id) ON DELETE SET NULL,
ADD COLUMN IF NOT EXISTS legal_hold_released_at TIMESTAMPTZ,
ADD COLUMN IF NOT EXISTS legal_hold_release_note TEXT;
COMMENT ON COLUMN media_assets.legal_hold_active IS
'P-11: TRUE = Medium unter Legal Hold; sofortige Sperrung fuer alle normalen Nutzerpfade. '
'Retention-Job darf dieses Medium nicht purgen. '
'rights_status wird bei Aktivierung auf ''blocked'' gespiegelt.';
COMMENT ON COLUMN media_assets.legal_hold_reason_code IS
'P-11: Kategorie des Legal Holds. Pflicht beim Setzen.';
COMMENT ON COLUMN media_assets.legal_hold_reason_note IS
'P-11: Freitext-Begruendung fuer den Legal Hold.';
COMMENT ON COLUMN media_assets.legal_hold_set_by_profile_id IS
'P-11: Profil das den Legal Hold gesetzt hat (Superadmin).';
COMMENT ON COLUMN media_assets.legal_hold_set_at IS
'P-11: Zeitpunkt der Legal-Hold-Aktivierung.';
COMMENT ON COLUMN media_assets.legal_hold_released_by_profile_id IS
'P-11: Profil das den Legal Hold aufgehoben hat.';
COMMENT ON COLUMN media_assets.legal_hold_released_at IS
'P-11: Zeitpunkt der Legal-Hold-Freigabe.';
COMMENT ON COLUMN media_assets.legal_hold_release_note IS
'P-11: Begruendung fuer die Aufhebung des Legal Holds.';
-- Index fuer Admin-Liste aktiver Legal Holds
CREATE INDEX IF NOT EXISTS idx_media_assets_legal_hold_active
ON media_assets (legal_hold_active)
WHERE legal_hold_active = TRUE;
-- Neue event_types fuer media_asset_audit_log
ALTER TABLE media_asset_audit_log
DROP CONSTRAINT IF EXISTS media_asset_audit_log_event_type_check;
ALTER TABLE media_asset_audit_log
ADD CONSTRAINT media_asset_audit_log_event_type_check
CHECK (event_type IN (
'visibility_change',
'copyright_change',
'metadata_change',
'lifecycle_change',
'legal_hold_set',
'legal_hold_released'
));

View File

@ -0,0 +1,73 @@
-- P-13: Content-Melde-Backend
-- Meldungen rechtswidriger Inhalte (DSA-konformes Meldeverfahren, KRIT-03)
--
-- Architektur: Diese Tabelle traegt alle fachlichen Report-Daten.
-- Die bestehende Admin-Inbox (InboxPage.jsx, GET /api/me/inbox/join-requests)
-- wird um einen zweiten Abschnitt erweitert, der Content-Reports anzeigt.
-- Keine separate Admin-Queue, keine generische inbox_items-Tabelle.
CREATE TABLE IF NOT EXISTS content_reports (
id SERIAL PRIMARY KEY,
-- Ziel der Meldung (erweiterbar auf weitere Typen)
target_type VARCHAR(20) NOT NULL DEFAULT 'media_asset'
CHECK (target_type IN ('media_asset', 'exercise')),
target_id INTEGER NOT NULL,
-- Meldungsinhalt
report_reason VARCHAR(50) NOT NULL
CHECK (report_reason IN (
'copyright',
'image_rights',
'privacy',
'minors',
'illegal_content',
'youth_protection',
'offensive_content',
'other'
)),
report_description TEXT NOT NULL,
-- Meldende Person (Name + E-Mail Pflicht; Profil optional bei eingeloggten Nutzern)
reporter_name VARCHAR(200) NOT NULL,
reporter_email VARCHAR(200) NOT NULL,
reporter_profile_id INTEGER REFERENCES profiles(id) ON DELETE SET NULL,
-- Gutglaubenserklärung (Pflicht)
good_faith_confirmed BOOLEAN NOT NULL DEFAULT FALSE,
-- Automatische Priorisierung (high fuer minors/youth_protection/illegal_content)
priority VARCHAR(20) NOT NULL DEFAULT 'normal'
CHECK (priority IN ('high', 'normal')),
-- Workflow-Status
status VARCHAR(30) NOT NULL DEFAULT 'submitted'
CHECK (status IN (
'submitted',
'under_review',
'resolved_no_action',
'resolved_legal_hold',
'rejected_invalid'
)),
-- Bearbeitung
assigned_to_profile_id INTEGER REFERENCES profiles(id) ON DELETE SET NULL,
reviewed_by_profile_id INTEGER REFERENCES profiles(id) ON DELETE SET NULL,
reviewed_at TIMESTAMP,
resolution_note TEXT,
-- Zeitstempel
submitted_at TIMESTAMP DEFAULT NOW(),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Indices fuer Admin-Liste
CREATE INDEX IF NOT EXISTS idx_content_reports_status_created
ON content_reports (status, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_content_reports_target
ON content_reports (target_type, target_id);
CREATE INDEX IF NOT EXISTS idx_content_reports_priority
ON content_reports (priority, status, created_at DESC);

View File

@ -0,0 +1,21 @@
-- Migration 053: Audit-Log event_type-Constraint korrigieren + 'content_report_filed' hinzufuegen
--
-- Hintergrund: Die Tabelle media_asset_audit_log wurde per CREATE TABLE IF NOT EXISTS angelegt.
-- Da die Tabelle bereits existierte, wurde die CHECK-Constraint aus Migration 050 nie angewendet.
-- In der DB existieren Zeilen mit legal_hold_set und legal_hold_released (aus P-11).
-- Diese Migration setzt die Constraint erstmalig mit allen gueltigen Werten.
ALTER TABLE media_asset_audit_log
DROP CONSTRAINT IF EXISTS media_asset_audit_log_event_type_check;
ALTER TABLE media_asset_audit_log
ADD CONSTRAINT media_asset_audit_log_event_type_check
CHECK (event_type IN (
'visibility_change',
'copyright_change',
'metadata_change',
'lifecycle_change',
'legal_hold_set',
'legal_hold_released',
'content_report_filed'
));

View File

@ -0,0 +1,60 @@
-- Migration 054: Trainingsmodule (Bibliothek / Planung) — Phase 1 MVP
-- Fachgrundlage: functional/Shinkan Trainingsmodule Kombinationsuebungen Spezifikation V2.md
CREATE TABLE IF NOT EXISTS training_modules (
id SERIAL PRIMARY KEY,
club_id INT REFERENCES clubs(id) ON DELETE SET NULL,
created_by INT REFERENCES profiles(id) ON DELETE SET NULL,
title VARCHAR(200) NOT NULL,
summary TEXT,
goal TEXT,
recommended_duration_min INT,
target_group_notes TEXT,
deployment_context_notes TEXT,
primary_method_id INT REFERENCES training_methods(id) ON DELETE SET NULL,
visibility VARCHAR(50) NOT NULL DEFAULT 'club'
CHECK (visibility IN ('private', 'club', 'official')),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_training_modules_club ON training_modules(club_id);
CREATE INDEX IF NOT EXISTS idx_training_modules_creator ON training_modules(created_by);
CREATE INDEX IF NOT EXISTS idx_training_modules_visibility ON training_modules(visibility);
CREATE INDEX IF NOT EXISTS idx_training_modules_method ON training_modules(primary_method_id)
WHERE primary_method_id IS NOT NULL;
DROP TRIGGER IF EXISTS training_modules_update ON training_modules;
CREATE TRIGGER training_modules_update
BEFORE UPDATE ON training_modules
FOR EACH ROW EXECUTE FUNCTION update_timestamp();
CREATE TABLE IF NOT EXISTS training_module_items (
id SERIAL PRIMARY KEY,
module_id INT NOT NULL REFERENCES training_modules(id) ON DELETE CASCADE,
order_index INT NOT NULL,
item_type VARCHAR(20) NOT NULL CHECK (item_type IN ('exercise', 'note')),
exercise_id INT REFERENCES exercises(id) ON DELETE SET NULL,
exercise_variant_id INT REFERENCES exercise_variants(id) ON DELETE SET NULL,
planned_duration_min INT,
notes TEXT,
note_body TEXT,
UNIQUE (module_id, order_index),
CHECK (
(item_type = 'exercise' AND exercise_id IS NOT NULL AND note_body IS NULL)
OR
(item_type = 'note' AND exercise_id IS NULL)
)
);
CREATE INDEX IF NOT EXISTS idx_training_module_items_module ON training_module_items(module_id);
CREATE INDEX IF NOT EXISTS idx_training_module_items_exercise ON training_module_items(exercise_id)
WHERE exercise_id IS NOT NULL;
-- Herkunft bei Übernahme aus Modul-Bibliothek (Kopie, keine Live-Verknüpfung)
ALTER TABLE training_unit_section_items
ADD COLUMN IF NOT EXISTS source_training_module_id INT REFERENCES training_modules(id) ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS idx_training_unit_section_items_source_module
ON training_unit_section_items(source_training_module_id)
WHERE source_training_module_id IS NOT NULL;

View File

@ -0,0 +1,3 @@
-- Persönliche Planungs-UI-Präferenzen (JSONB, selbst vom Nutzer setzbar)
ALTER TABLE profiles
ADD COLUMN IF NOT EXISTS training_planning_prefs JSONB NOT NULL DEFAULT '{}'::jsonb;

View File

@ -0,0 +1,33 @@
-- Migration 056: Kombinationsübungen (Phase 2 MVP) — Slots + Pool-Kandidaten
-- Fachgrundlage: functional/Shinkan Trainingsmodule Kombinationsuebungen Spezifikation V2.md §6
ALTER TABLE exercises
ADD COLUMN IF NOT EXISTS exercise_kind VARCHAR(20) NOT NULL DEFAULT 'simple'
CHECK (exercise_kind IN ('simple', 'combination')),
ADD COLUMN IF NOT EXISTS method_archetype VARCHAR(80),
ADD COLUMN IF NOT EXISTS method_profile JSONB NOT NULL DEFAULT '{}'::jsonb;
CREATE INDEX IF NOT EXISTS idx_exercises_exercise_kind ON exercises(exercise_kind);
CREATE INDEX IF NOT EXISTS idx_exercises_method_archetype ON exercises(method_archetype)
WHERE method_archetype IS NOT NULL;
CREATE TABLE IF NOT EXISTS combination_exercise_slots (
id SERIAL PRIMARY KEY,
exercise_id INT NOT NULL REFERENCES exercises(id) ON DELETE CASCADE,
slot_index INT NOT NULL,
title VARCHAR(200),
UNIQUE (exercise_id, slot_index)
);
CREATE INDEX IF NOT EXISTS idx_combination_exercise_slots_exercise ON combination_exercise_slots(exercise_id);
CREATE TABLE IF NOT EXISTS combination_slot_candidates (
id SERIAL PRIMARY KEY,
slot_id INT NOT NULL REFERENCES combination_exercise_slots(id) ON DELETE CASCADE,
candidate_exercise_id INT NOT NULL REFERENCES exercises(id) ON DELETE CASCADE,
sort_order INT NOT NULL DEFAULT 0,
UNIQUE (slot_id, candidate_exercise_id)
);
CREATE INDEX IF NOT EXISTS idx_combination_slot_candidates_slot ON combination_slot_candidates(slot_id);
CREATE INDEX IF NOT EXISTS idx_combination_slot_candidates_exercise ON combination_slot_candidates(candidate_exercise_id);

View File

@ -0,0 +1,8 @@
-- 057: Terminspezifisches Ablaufprofil fuer Kombinationsuebungen in der Planung
-- NULL = method_profile vom Katalog (exercises) verwenden; sonst dieser JSONB-Stand gilt fuer diese Platzierung.
ALTER TABLE training_unit_section_items
ADD COLUMN IF NOT EXISTS planning_method_profile JSONB NULL;
COMMENT ON COLUMN training_unit_section_items.planning_method_profile IS
'Snapshots des Ablaufprofils fuer diese Einheit/Zeile; NULL = exercises.method_profile.';

View File

@ -0,0 +1,7 @@
-- Unterstützung für GET /api/exercises: ORDER BY e.updated_at DESC
-- und häufiger Pfad created_by_me (= e.created_by = Profil) mit derselben Sortierung.
-- Hinweis: idx_exercises_created_at (014) betrifft created_at, nicht updated_at.
CREATE INDEX IF NOT EXISTS idx_exercises_updated_at_desc ON exercises (updated_at DESC);
CREATE INDEX IF NOT EXISTS idx_exercises_created_by_updated_at_desc ON exercises (created_by, updated_at DESC);

View File

@ -0,0 +1,7 @@
-- GET /api/training-units: Liste nutzt immer tu.framework_slot_id IS NULL (keine Rahmen-Blueprints)
-- und sortiert nach planned_date, planned_time_start (ASC/DESC mit NULLS LAST).
-- Teilindex verkleinert die Menge und unterstützt die Sortierung.
CREATE INDEX IF NOT EXISTS idx_training_units_scheduled_order
ON training_units (planned_date DESC, planned_time_start DESC NULLS LAST)
WHERE framework_slot_id IS NULL;

View File

@ -0,0 +1,33 @@
-- Migration 060: Übungslisten bei großem Bestand (Ziel: Tausende Übungen, viele Filterkombinationen).
-- Ergänzt 058 (globale Sortierung / created_by): kleinere Partial-Indizes für häufige
-- Sichtbarkeits-Pfade der Bibliothek sowie Junction-Indizes für die List-Subqueries
-- (primary_focus_name / JSON-Aggregate mit is_primary).
--
-- Bereits vorhanden und sinnvoll: UNIQUE(exercise_id, …) auf den M:N-Tabellen für EXISTS-Joins;
-- GIN auf exercises.search_vector (014); idx_exercises_exercise_kind (056).
-- Official: OR-Zweig der Bibliothek — kompakter als Full-Table-Scan bei BitmapOr mit anderen Partial-Indizes
CREATE INDEX IF NOT EXISTS idx_exercises_list_official_updated
ON exercises (updated_at DESC)
WHERE visibility = 'official'
AND COALESCE(status, '') <> 'archived';
-- Club: häufig club_id + Sortierung nach updated_at (Mandanten-Bibliothek)
CREATE INDEX IF NOT EXISTS idx_exercises_list_club_updated
ON exercises (club_id, updated_at DESC)
WHERE visibility = 'club'
AND club_id IS NOT NULL
AND COALESCE(status, '') <> 'archived';
-- List-SELECT: Subqueries / json_agg sortieren zuerst nach is_primary (siehe exercises.py)
CREATE INDEX IF NOT EXISTS idx_exercise_focus_areas_exercise_primary
ON exercise_focus_areas (exercise_id, is_primary DESC NULLS LAST, focus_area_id);
CREATE INDEX IF NOT EXISTS idx_exercise_style_directions_exercise_primary
ON exercise_style_directions (exercise_id, is_primary DESC NULLS LAST, style_direction_id);
CREATE INDEX IF NOT EXISTS idx_exercise_training_types_exercise_primary
ON exercise_training_types (exercise_id, is_primary DESC NULLS LAST, training_type_id);
CREATE INDEX IF NOT EXISTS idx_exercise_target_groups_exercise_primary
ON exercise_target_groups (exercise_id, is_primary DESC NULLS LAST, target_group_id);

View File

@ -0,0 +1,22 @@
-- GET /api/training-units: Keyset über (planned_date, planned_time_start NULLS LAST per Sort, id)
-- Ersetzt den reinen Datum/Uhrzeit-Teilindex 059 durch zwei Richtungen mit Tie-Break id.
DROP INDEX IF EXISTS idx_training_units_scheduled_order;
CREATE INDEX IF NOT EXISTS idx_training_units_list_keyset_desc
ON training_units (
planned_date DESC,
(planned_time_start IS NULL) ASC,
planned_time_start DESC NULLS LAST,
id DESC
)
WHERE framework_slot_id IS NULL;
CREATE INDEX IF NOT EXISTS idx_training_units_list_keyset_asc
ON training_units (
planned_date ASC,
(planned_time_start IS NULL) ASC,
planned_time_start ASC NULLS LAST,
id ASC
)
WHERE framework_slot_id IS NULL;

View File

@ -0,0 +1,41 @@
-- list_exercises mit skill_min_level / skill_max_level: EXISTS auf exercise_skills mit numerischem Stufen-Rang.
-- Ausdruck muss mit backend/routers/exercises.py _EXERCISE_SKILL_LEVEL_RANK_SQL (Alias „es“) übereinstimmen.
CREATE INDEX IF NOT EXISTS idx_exercise_skills_exercise_level_rank
ON exercise_skills (
exercise_id,
(CASE COALESCE(
NULLIF(TRIM(LOWER(target_level::text)), ''),
NULLIF(TRIM(LOWER(required_level::text)), '')
)
WHEN 'basis' THEN 1
WHEN 'grundlagen' THEN 2
WHEN 'aufbau' THEN 3
WHEN 'fortgeschritten' THEN 4
WHEN 'optimierung' THEN 5
WHEN 'einsteiger' THEN 1
WHEN 'experte' THEN 5
WHEN '1' THEN 1
WHEN '2' THEN 2
WHEN '3' THEN 3
WHEN '4' THEN 4
WHEN '5' THEN 5
ELSE NULL END)
)
WHERE (CASE COALESCE(
NULLIF(TRIM(LOWER(target_level::text)), ''),
NULLIF(TRIM(LOWER(required_level::text)), '')
)
WHEN 'basis' THEN 1
WHEN 'grundlagen' THEN 2
WHEN 'aufbau' THEN 3
WHEN 'fortgeschritten' THEN 4
WHEN 'optimierung' THEN 5
WHEN 'einsteiger' THEN 1
WHEN 'experte' THEN 5
WHEN '1' THEN 1
WHEN '2' THEN 2
WHEN '3' THEN 3
WHEN '4' THEN 4
WHEN '5' THEN 5
ELSE NULL END) IS NOT NULL;

View File

@ -0,0 +1,85 @@
-- Migration 063: Phasen und parallele Streams pro Trainingseinheit (Grundlage Breakout).
-- Bestehende Sektionen werden einer Default-whole_group-Phase zugeordnet.
-- UNIQUE (training_unit_id, order_index) auf Sektionen entfällt zugunsten
-- eindeutiger order_index je Phase bzw. je parallel_stream.
-- ── Phasen ───────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS training_unit_phases (
id SERIAL PRIMARY KEY,
training_unit_id INT NOT NULL REFERENCES training_units(id) ON DELETE CASCADE,
order_index INT NOT NULL,
phase_kind VARCHAR(20) NOT NULL CHECK (phase_kind IN ('whole_group', 'parallel')),
title VARCHAR(200),
guidance_notes TEXT,
UNIQUE (training_unit_id, order_index)
);
CREATE INDEX IF NOT EXISTS idx_training_unit_phases_unit ON training_unit_phases(training_unit_id);
-- ── Streams innerhalb einer Parallelphase ──────────────────────────────────
CREATE TABLE IF NOT EXISTS training_unit_parallel_streams (
id SERIAL PRIMARY KEY,
phase_id INT NOT NULL REFERENCES training_unit_phases(id) ON DELETE CASCADE,
order_index INT NOT NULL,
title VARCHAR(200),
notes TEXT,
assigned_trainer_profile_ids JSONB,
UNIQUE (phase_id, order_index)
);
CREATE INDEX IF NOT EXISTS idx_training_unit_parallel_streams_phase
ON training_unit_parallel_streams(phase_id);
COMMENT ON COLUMN training_unit_parallel_streams.assigned_trainer_profile_ids IS
'Optionale Co-Trainer-IDs (JSON-Array von Profil-IDs) für diese Teilstrecke; MVP+';
-- ── Sektionen: Zuordnung zu Phase (gemeinsam) oder Stream (parallel) ─────
ALTER TABLE training_unit_sections
ADD COLUMN IF NOT EXISTS phase_id INT REFERENCES training_unit_phases(id) ON DELETE CASCADE,
ADD COLUMN IF NOT EXISTS parallel_stream_id INT REFERENCES training_unit_parallel_streams(id) ON DELETE CASCADE;
-- Backfill: je Einheit mit Sektionen eine whole_group-Phase, alle Sektionen dorthin
INSERT INTO training_unit_phases (training_unit_id, order_index, phase_kind, title)
SELECT tu.id, 0, 'whole_group', NULL
FROM training_units tu
WHERE EXISTS (SELECT 1 FROM training_unit_sections s WHERE s.training_unit_id = tu.id)
AND NOT EXISTS (
SELECT 1 FROM training_unit_phases p
WHERE p.training_unit_id = tu.id AND p.order_index = 0 AND p.phase_kind = 'whole_group'
);
UPDATE training_unit_sections tus
SET phase_id = p.id
FROM training_unit_phases p
WHERE tus.phase_id IS NULL
AND p.training_unit_id = tus.training_unit_id
AND p.order_index = 0
AND p.phase_kind = 'whole_group';
-- Alte globale Reihenfolge-Eindeutigkeit pro Einheit entfernen
ALTER TABLE training_unit_sections
DROP CONSTRAINT IF EXISTS training_unit_sections_training_unit_id_order_index_key;
-- Genau eine Zielspalte gesetzt: gemeinsame Phase ODER paralleler Stream
ALTER TABLE training_unit_sections
DROP CONSTRAINT IF EXISTS training_unit_sections_phase_or_stream_chk;
ALTER TABLE training_unit_sections
ADD CONSTRAINT training_unit_sections_phase_or_stream_chk CHECK (
(phase_id IS NOT NULL AND parallel_stream_id IS NULL)
OR (phase_id IS NULL AND parallel_stream_id IS NOT NULL)
);
CREATE UNIQUE INDEX IF NOT EXISTS uq_training_unit_sections_phase_order
ON training_unit_sections (phase_id, order_index)
WHERE phase_id IS NOT NULL;
CREATE UNIQUE INDEX IF NOT EXISTS uq_training_unit_sections_stream_order
ON training_unit_sections (parallel_stream_id, order_index)
WHERE parallel_stream_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_training_unit_sections_phase
ON training_unit_sections(phase_id) WHERE phase_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_training_unit_sections_parallel_stream
ON training_unit_sections(parallel_stream_id) WHERE parallel_stream_id IS NOT NULL;

View File

@ -0,0 +1,8 @@
-- Vorlagen: Phasen/Parallel-Streams wie im Einheiten-Editor (planLoc-Abbild)
ALTER TABLE training_plan_template_sections
ADD COLUMN IF NOT EXISTS phase_kind VARCHAR(20) NOT NULL DEFAULT 'whole_group',
ADD COLUMN IF NOT EXISTS phase_order_index INT NOT NULL DEFAULT 0,
ADD COLUMN IF NOT EXISTS parallel_stream_order_index INT NULL;
COMMENT ON COLUMN training_plan_template_sections.parallel_stream_order_index IS
'NULL = Ganzgruppen-Abschnitt; 0..n = Stream innerhalb paralleler Phase';

View File

@ -0,0 +1,11 @@
-- Migration 065: Wiki-spezifische Felder fuer Fähigkeiten (KarateRelevanz, RelevanzLevel)
-- SMW karatetrainer.net; Import mappt in strukturierte Spalten statt nur Freitext in description
ALTER TABLE skills
ADD COLUMN IF NOT EXISTS karate_relevance TEXT;
ALTER TABLE skills
ADD COLUMN IF NOT EXISTS relevance_level SMALLINT CHECK (relevance_level IS NULL OR relevance_level BETWEEN 1 AND 3);
COMMENT ON COLUMN skills.karate_relevance IS 'Wiki Karate-Relevanz (Plaintext aus SMW Property KarateRelevanz)';
COMMENT ON COLUMN skills.relevance_level IS 'Wiki-RelevanzLevel 13 (Semantic MediaWiki)';

View File

@ -0,0 +1,36 @@
-- Geplante Gesamt- und Abschnittsdauer; Rahmenprogramm: Fokus/Stil als M:N (wie Trainingsarten/Zielgruppen)
ALTER TABLE training_units
ADD COLUMN IF NOT EXISTS planned_duration_min INT;
ALTER TABLE training_unit_sections
ADD COLUMN IF NOT EXISTS planned_duration_min INT;
ALTER TABLE training_plan_template_sections
ADD COLUMN IF NOT EXISTS planned_duration_min INT;
CREATE TABLE IF NOT EXISTS training_framework_program_focus_areas (
framework_program_id INT NOT NULL REFERENCES training_framework_programs(id) ON DELETE CASCADE,
focus_area_id INT NOT NULL REFERENCES focus_areas(id) ON DELETE CASCADE,
PRIMARY KEY (framework_program_id, focus_area_id)
);
CREATE INDEX IF NOT EXISTS idx_tfpfa_focus ON training_framework_program_focus_areas(focus_area_id);
CREATE TABLE IF NOT EXISTS training_framework_program_style_directions (
framework_program_id INT NOT NULL REFERENCES training_framework_programs(id) ON DELETE CASCADE,
style_direction_id INT NOT NULL REFERENCES style_directions(id) ON DELETE CASCADE,
PRIMARY KEY (framework_program_id, style_direction_id)
);
CREATE INDEX IF NOT EXISTS idx_tfpsd_style ON training_framework_program_style_directions(style_direction_id);
INSERT INTO training_framework_program_focus_areas (framework_program_id, focus_area_id)
SELECT id, focus_area_id FROM training_framework_programs
WHERE focus_area_id IS NOT NULL
ON CONFLICT DO NOTHING;
INSERT INTO training_framework_program_style_directions (framework_program_id, style_direction_id)
SELECT id, style_direction_id FROM training_framework_programs
WHERE style_direction_id IS NOT NULL
ON CONFLICT DO NOTHING;

View File

@ -0,0 +1,141 @@
-- Migration 067: Konfigurierbare KI-Prompts + Tracking-Feld fuer Uebungs-Zusammenfassung
-- Datum: 2026-05-22
-- Spec: technical/KI_FEATURES_SPEC.md, AI_PROMPT_SYSTEM_SPEC.md
-- ============================================================================
-- AI PROMPTS
-- ============================================================================
CREATE TABLE IF NOT EXISTS ai_prompts (
id SERIAL PRIMARY KEY,
slug VARCHAR(100) NOT NULL UNIQUE,
display_name VARCHAR(200) NOT NULL,
description TEXT,
template TEXT NOT NULL,
category VARCHAR(50) DEFAULT 'exercise'
CHECK (category IN ('exercise', 'training', 'matrix', 'import', 'admin')),
output_format VARCHAR(10) DEFAULT 'text'
CHECK (output_format IN ('text', 'json')),
output_schema JSONB,
is_system_default BOOLEAN DEFAULT false,
default_template TEXT,
active BOOLEAN DEFAULT true,
sort_order INT DEFAULT 0,
created_by INT REFERENCES profiles(id) ON DELETE SET NULL,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_ai_prompts_slug ON ai_prompts(slug);
CREATE INDEX IF NOT EXISTS idx_ai_prompts_category ON ai_prompts(category);
CREATE INDEX IF NOT EXISTS idx_ai_prompts_active ON ai_prompts(active, sort_order);
DROP TRIGGER IF EXISTS ai_prompts_update ON ai_prompts;
CREATE TRIGGER ai_prompts_update
BEFORE UPDATE ON ai_prompts
FOR EACH ROW EXECUTE FUNCTION update_timestamp();
-- ============================================================================
-- TRACKING SUMMARY (KI)
-- ============================================================================
ALTER TABLE exercises ADD COLUMN IF NOT EXISTS summary_ai_generated BOOLEAN DEFAULT false;
COMMENT ON COLUMN exercises.summary_ai_generated IS 'TRUE wenn Kurzbeschreibung zuletzt von KI vorgeschlagen und uebernommen (UI setzt bei manueller Aenderung false)';
-- ============================================================================
-- SEED PROMPTS (idempotent)
-- ============================================================================
INSERT INTO ai_prompts (
slug, display_name, description, template,
category, output_format, is_system_default, default_template, active, sort_order
)
SELECT
'pipeline',
'Mehrstufige Gesamtanalyse',
'Master-Schalter fuer die Pipeline-Anzeige.',
'PIPELINE_MASTER',
'admin',
'text',
false,
'PIPELINE_MASTER',
true,
-10
WHERE NOT EXISTS (SELECT 1 FROM ai_prompts WHERE slug = 'pipeline');
INSERT INTO ai_prompts (
slug, display_name, description, template,
category, output_format, is_system_default, default_template, active, sort_order
)
SELECT
'exercise_summary',
'Uebungs-Zusammenfassung',
'Erzeugt eine kurze Kurzbeschreibung fuer Listen/Galerie.',
$s$Du bist Assistent fuer Kampfsport-Trainer.
Erstelle eine kurze Kurzbeschreibung fuer Listen und Trainingsplaene.
Anforderungen:
- Hochstens etwa 200 Zeichen (bei Bedarf gekuerzt fuer Mobile)
- Kern: Welche Trainingsqualitaeten? Wie fuehrt man die Uebung kurz aus?
- Sachlich, auf Deutsch
Uebung: {{exercise_title}}
Fokuskontext: {{exercise_focus_area}}
Ziel (Fliesstext, kann HTML sein): {{exercise_goal}}
Durchfuehrung (Fliesstext, kann HTML sein): {{exercise_execution}}
Antworte NUR mit der Kurzbeschreibung als einfachen Text (keine Markdown-Codeblocks, keine Anfuehrungszeichen um den ganzen Text).$s$,
'exercise',
'text',
true,
NULL,
true,
1
WHERE NOT EXISTS (SELECT 1 FROM ai_prompts WHERE slug = 'exercise_summary');
INSERT INTO ai_prompts (
slug, display_name, description, template,
category, output_format, is_system_default, default_template, active, sort_order
)
SELECT
'exercise_skill_suggestions',
'Faehigkeiten-Empfehlungen',
'Schlaegt passende Skills mit Stufen/Intensitaet vor (JSON-Ausgabe-Prompt).',
$j$Du bist Assistent fuer Kampfsport-Trainer.
Ordne diese Uebung dem globalen Skill-Katalog zu.
Daten zur Uebung:
Titel: {{exercise_title}}
Fokuskontext (optional): {{exercise_focus_area}}
Ziel (gekuerzt_plain): {{exercise_goal}}
Durchfuehrung (gekuerzt_plain): {{exercise_execution}}
Verfuegbare Faehigkeiten (Auswahl NUR ueber diese IDs keine anderen IDs verwenden):
{{skills_catalog}}
Waehle hoechstens 5 passende Skills. Für jede Faehigkeit:
- skill_id: ganze Zahl aus der Liste
- required_level: eines von basis, grundlagen, aufbau, fortgeschritten, optimierung
- target_level: derselbe Wertvorrat
- intensity: eines von niedrig, mittel, hoch
- is_primary (optional): true fuer die Hauptfaehigkeit der Uebung, sondern false/weglassen
Antworte NUR mit einem JSON-Array ohne Erklaertext, keine Markdown-Fences.
Beispielformat:
[{"skill_id": 1, "required_level": "grundlagen", "target_level": "aufbau", "intensity": "hoch", "is_primary": true}]
Wenn nichts gut passt, antworte mit [].$j$,
'exercise',
'json',
true,
NULL,
true,
2
WHERE NOT EXISTS (SELECT 1 FROM ai_prompts WHERE slug = 'exercise_skill_suggestions');

View File

@ -0,0 +1,125 @@
-- Migration 068: KI Skill-Retrieval-Profile pro Fokusbereich (+ Standardprofil)
-- Purpose: Gewichtungen/Quota fuer exercise_ai Skill-Katalog (OpenRouter Kontext)
CREATE TABLE IF NOT EXISTS ai_skill_retrieval_profiles (
id SERIAL PRIMARY KEY,
focus_area_id INT REFERENCES focus_areas(id) ON DELETE CASCADE,
is_default BOOLEAN NOT NULL DEFAULT FALSE,
name VARCHAR(200) NOT NULL,
description TEXT,
active BOOLEAN NOT NULL DEFAULT TRUE,
config JSONB NOT NULL DEFAULT '{}'::jsonb,
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE UNIQUE INDEX IF NOT EXISTS ux_ai_skill_retrieval_profile_focus_area
ON ai_skill_retrieval_profiles (focus_area_id)
WHERE focus_area_id IS NOT NULL AND active = TRUE;
CREATE UNIQUE INDEX IF NOT EXISTS ux_ai_skill_retrieval_profile_default_only
ON ai_skill_retrieval_profiles (is_default)
WHERE is_default IS TRUE AND active = TRUE;
COMMENT ON TABLE ai_skill_retrieval_profiles IS
'Gewichte/Quota fuer Skill-Katalog in exercise_ai; optional gebunden an focus_areas, genau eine is_default=TRUE';
INSERT INTO ai_skill_retrieval_profiles (focus_area_id, is_default, name, description, active, config)
VALUES (
NULL,
TRUE,
'Standard',
'Kein/Undefinierter Fokusbereich: neutrale Gewichte mit sanften Caps auf sehr breite Unterkategorien.',
TRUE,
'{
"version": 1,
"importance_multiplier": 1,
"text_overlap_bonus": 2,
"main_slug_weights": { "karate": 1, "allgemeine": 1 },
"category_slug_weights": {},
"category_max_share": {
"kondition": 0.38,
"koordination": 0.35
},
"main_min_share": {},
"description_plain_max_len": 160,
"karate_relevance_max_len": 72,
"keyword_overrides": [
{
"keywords_any": ["rollenspiel", "szenario", "deesk", "diskussion"],
"case_insensitive": true,
"patch": {
"category_slug_weights": {
"psychische_faehigkeiten": 1.65,
"soziale_faehigkeiten": 1.65,
"kognition": 1.4
},
"category_max_share": {
"kondition": 0.08,
"koordination": 0.1
}
}
},
{
"keywords_any": ["befreiung", "haltegriff", "greifer", "umklammer"],
"case_insensitive": true,
"patch": {
"category_slug_weights": {
"selbstverteidigung": 2.2,
"koordination": 0.9
},
"main_slug_weights": { "karate": 1.35 }
}
}
]
}'::jsonb
);
INSERT INTO ai_skill_retrieval_profiles (focus_area_id, is_default, name, description, active, config)
SELECT
fa.id,
FALSE,
'Gewaltschutz',
'Kaum klassische Sportfaehigkeit; Gewicht auf Deeskalation, Kognition/Soziales; SV-Schwerpunkt per Keywords verstaerken.',
TRUE,
'{
"version": 1,
"importance_multiplier": 1,
"text_overlap_bonus": 2.25,
"main_slug_weights": { "karate": 1.08, "allgemeine": 1.06 },
"category_slug_weights": {
"kognition": 1.72,
"psychische_faehigkeiten": 1.78,
"soziale_faehigkeiten": 1.78,
"selbstverteidigung": 1.82,
"kondition": 0.32,
"koordination": 0.4
},
"category_max_share": {
"kondition": 0.12,
"koordination": 0.16
},
"main_min_share": {},
"description_plain_max_len": 150,
"karate_relevance_max_len": 0,
"keyword_overrides": [
{
"keywords_any": ["befreiung", "haltegriff", "greifer"],
"case_insensitive": true,
"patch": {
"category_slug_weights": {
"selbstverteidigung": 3.25,
"koordination": 1.08
},
"main_slug_weights": { "karate": 1.5 }
}
}
]
}'::jsonb
FROM focus_areas fa
WHERE fa.name = 'Gewaltschutz'
AND (fa.status IS NULL OR fa.status = 'active')
AND NOT EXISTS (
SELECT 1 FROM ai_skill_retrieval_profiles p
WHERE p.focus_area_id = fa.id AND p.active = TRUE
)
LIMIT 1;

View File

@ -0,0 +1,10 @@
-- Migration 069: ai_prompts default_template fuer Ruecksetzen & Transparenz
-- Setzt fuer bestehende System-Prompt-Zeilen default_template aus dem aktuellen template,
-- sofern noch kein Referenzinhalt gespeichert war (Migration 067 hatte NULL fuer exercise_*).
UPDATE ai_prompts
SET default_template = template,
updated_at = NOW()
WHERE default_template IS NULL
AND template IS NOT NULL
AND LENGTH(TRIM(template)) > 0;

View File

@ -0,0 +1,7 @@
-- Migration 070: optionales OpenRouter-Modell pro Prompt-Zeile
-- Leer/NULL → Umgebungsvariable OPENROUTER_MODEL (wie bisher).
ALTER TABLE ai_prompts ADD COLUMN IF NOT EXISTS openrouter_model VARCHAR(200);
COMMENT ON COLUMN ai_prompts.openrouter_model IS
'Optional: OpenRouter model id (z.B. anthropic/claude-3.5-haiku); NULL = OPENROUTER_MODEL aus Env';

Some files were not shown because too many files have changed in this diff Show More