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