2026-05-20 16:42:25 +02:00
# Gewichtetes Fähigkeiten-Scoring (Phase 3)
**Stand:** 2026-05-20
2026-05-21 12:35:45 +02:00
**Status:** Variante A (regelbasiert) umgesetzt — **v1.3** (Peer-Kontext getrennt + Listen-Filter)
**Modul:** `backend/skill_scoring.py` , Router `backend/routers/skill_profiles.py`
2026-05-20 16:42:25 +02:00
## Ziel
2026-05-21 12:35:45 +02:00
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.
2026-05-20 16:42:25 +02:00
## 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` ).
2026-05-21 12:35:45 +02:00
## Gewichtungsformel (v1.1 / v1.2)
2026-05-20 16:42:25 +02:00
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:
2026-05-21 08:24:23 +02:00
- `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 (1– 5). Fehlen beide: Faktor 1,0.
- **Spanne** = Anzahl Stufen von „von“ bis „bis“ (1– 5)
- **Mittelpunkt** = durchschnittliche Stufe
- Faktor ≈ `(0,92 + 0,04 × Spanne) × (0,95 + 0,025 × Mittelpunkt)` → typisch 0,96– 1,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 |
2026-05-20 16:42:25 +02:00
2026-05-21 12:35:45 +02:00
## 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`
2026-05-20 16:42:25 +02:00
2026-05-21 12:35:45 +02:00
```
effective_ref = max(max_weight_in_peer_corpus(skill_id), eigenes_gewicht)
universal_percent = min(100, weight / effective_ref × 100)
```
2026-05-21 08:37:58 +02:00
2026-05-21 12:35:45 +02:00
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` ).
2026-05-20 16:42:25 +02:00
## API
| Methode | Pfad | Beschreibung |
|---------|------|--------------|
2026-05-21 12:35:45 +02:00
| 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` |
2026-05-20 16:42:25 +02:00
| GET | `/api/skill-discovery/suggestions?skill_ids=1,2,3` | Ranking sichtbarer Artefakte; Query `types` , `limit` |
2026-05-21 12:35:45 +02:00
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 …"
}
```
2026-05-20 16:42:25 +02:00
## UI
2026-05-21 12:35:45 +02:00
### Bearbeitung (Vollprofil)
2026-05-20 16:42:25 +02:00
2026-05-21 12:35:45 +02:00
| 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) |
2026-05-20 16:42:25 +02:00
## Grenzen / später
2026-05-21 12:35:45 +02:00
- Kein DB-Cache (`skill_profile_json`) — on-the-fly; bei >50 Artefakten pro Typ serverseitiger Index/Caching
2026-05-20 16:42:25 +02:00
- Entwicklungsziele am Rahmenkopf bleiben Freitext (kein Scoring)
2026-05-21 12:35:45 +02:00
- KI-Zusammenfassung (Variante B) nicht Teil von v1.0
2026-05-20 16:42:25 +02:00
- Trainings**einheiten** (Kalender) optional als nächste Erweiterung
2026-05-21 12:35:45 +02:00
- 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
2026-05-20 16:42:25 +02:00
## Tests
2026-05-21 12:35:45 +02:00
- `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 |