shinkan-jinkendo/.claude/docs/technical/SKILL_SCORING_SPEC.md

185 lines
8.4 KiB
Markdown
Raw Permalink Normal View History

# 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 (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 |
## 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 |