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
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.
This commit is contained in:
parent
6ab2f20f08
commit
f3710ac0a1
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
**Plattform-Rechtstexte (P-01, 0.8.95–0.8.96):** Admin-Editor mit **Abschnitts- und Vollvorschau** (Markdown); fortlaufende Abschnittsnummerierung in der Anzeige/PDF (Darstellung, nicht DB-persistent).
|
||||
|
||||
**Parallel weiter relevant:** **Trainingsplan Phasen & Streams** (Migration **063**, Coach + Planung **0.8.137–0.8.140**; Handover **`docs/HANDOVER.md`** §3); **Trainingsrahmenprogramm** (036–037), **Progressionsgraph** (032–034) — siehe **`TRAINING_FRAMEWORK_SPEC.md`**.
|
||||
**Parallel weiter relevant:** **Trainingsplan Phasen & Streams** (Migration **063**, Coach + Planung **0.8.137–0.8.140**; Handover **`docs/HANDOVER.md`** §3); **Trainingsrahmenprogramm** (036–037), **Progressionsgraph** (032–034) — siehe **`TRAINING_FRAMEWORK_SPEC.md`**. **Planungs-KI Progressionsgraph** (Roadmap-first, Auto-Optimierung, Katalog-Kontext **0.8.233**): 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)
|
||||
|
||||
|
|
|
|||
|
|
@ -465,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. 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`**.
|
||||
|
||||
### Trainingsrahmen‑Vorlage (Rahmenprogramm, CURR‑002 Stufe 2 / CURR‑009)
|
||||
|
||||
**Abgrenzung:** Eine **einzeilige** Trainingsplan‑Mikrovorlage (`training_plan_template`) strukturiert **eine** Einheit; das **Rahmenprogramm** ist eine **eigene Bibliotheksentität** mit **sortierten Session‑Slots**, **mindestens einem** formulierten **Entwicklungsziel** (Zielliste, **CURR‑011**) und einem **vollständigen Ablauf** pro Slot (**`training_unit_sections` + `training_unit_section_items`** wie bei geplanten Einheiten — **CURR‑010** inhaltlich, technisch seit **037** identisch zur Planungsstruktur). Der persistierte **Progressionsgraph** zwischen Übungen bleibt **optional** (**CURR‑013**).
|
||||
|
|
|
|||
|
|
@ -45,3 +45,34 @@ def test_normalize_planning_roadmap_with_catalog_context():
|
|||
}
|
||||
)
|
||||
assert out["planning_catalog_context"]["focus_areas"][0]["id"] == 4
|
||||
|
||||
|
||||
def test_multistage_qa_splits_llm_highlights_from_fix_hints():
|
||||
from planning_path_qa_pipeline import run_multistage_path_qa
|
||||
|
||||
result = run_multistage_path_qa(
|
||||
off_topic_steps=[],
|
||||
stripped_off_topic=[
|
||||
{
|
||||
"issue": "roadmap_unfilled",
|
||||
"step_index": 1,
|
||||
"reasons": ["Keine passende Übung"],
|
||||
}
|
||||
],
|
||||
gaps=[],
|
||||
llm_qa={
|
||||
"overall_ok": True,
|
||||
"quality_score": 0.88,
|
||||
"recommendations": [
|
||||
"Gute didaktische Progression",
|
||||
"Optional: Vertiefung Koordination",
|
||||
],
|
||||
},
|
||||
llm_applied=True,
|
||||
)
|
||||
hints = result["optimization_hints"]
|
||||
llm_hints = [h for h in hints if h.get("issue") == "llm_recommendation"]
|
||||
fix_hints = [h for h in hints if h.get("issue") != "llm_recommendation"]
|
||||
assert len(llm_hints) >= 2
|
||||
assert any(h.get("issue") == "roadmap_unfilled" for h in fix_hints)
|
||||
assert result["qa_tiers"][2]["recommendations"][0].startswith("Gute didaktische")
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Shinkan Jinkendo – Entwicklungsstand & Handover
|
||||
|
||||
**Stand:** 2026-06-07
|
||||
**App-Version / DB-Schema:** App **`0.8.208`** (Planungs-KI Phase D); DB **`20260606086`** — maßgeblich **`backend/version.py`**.
|
||||
**Stand:** 2026-05-22
|
||||
**App-Version / DB-Schema:** App **`0.8.233`** (Planungs-KI F11–F14, Katalog-Kontext); DB siehe **`backend/version.py`** (`DB_SCHEMA_VERSION`, Migration **088**).
|
||||
|
||||
Diese Datei ist die **Einstiegs-Doku für neue Chat-Sessions**: Anforderungen im Detail stehen in `.claude/docs/` (siehe unten); hier der **implementierte Stand**, **Medien-Meilenstein** und **sinnvolle nächste Schritte**.
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ Das Schema ist gegenüber dem Code zurück: Migration **`022_skills_schema_compl
|
|||
- **Varianten:** Speichern in der **Aktionsleiste** persistiert zuerst geänderte Varianten (`persistPendingVariantChanges`), dann Übungs-Stammdaten; „Variante anlegen“ als `type="button"` ohne verschachteltes Formular (`createVariantFromDraft`)
|
||||
- **Governance (Übungen):** Owner = `created_by`; Bearbeiten = Ersteller, Plattform-Admin oder `can_plan_in_club` bei `visibility=club`; Löschen `club` = nur `club_admin`; Details **`FEATURES_DELIVERED_2026-Q2.md`** §16, **`EXERCISES_API_SPEC.md`** Permissions
|
||||
|
||||
### 2.8 KI Assistenz Übungen & Planungs-KI (Stand **0.8.217**)
|
||||
### 2.8 KI Assistenz Übungen & Planungs-KI (Stand **0.8.233**)
|
||||
|
||||
**Zentrale Ist-Doku (Progressionsgraph-KI):** **`docs/architecture/PLANNING_PROGRESSION_GRAPH_KI.md`** — bei Drift zuerst dort pflegen.
|
||||
|
||||
|
|
@ -108,20 +108,31 @@ Das Schema ist gegenüber dem Code zurück: Migration **`022_skills_schema_compl
|
|||
| **F7** | `planning_skill_expectations` — Retrieval + UI + Gap | ✅ **0.8.215–216** |
|
||||
| **F8** | Editierbare `stage_specs` (Belastung, Erfolgskriterien) | ✅ **0.8.216** |
|
||||
| **F9** | `planning_roadmap` JSONB am Graph (Migration **088**) | ✅ **0.8.217** |
|
||||
| **F10** | Stufen-Lernziel-Gate, kein blindes Rank-Fallback | ✅ **0.8.218** |
|
||||
| **F11** | Auto-Rematch + Stufen-Spec-Refine + mehrstufige QS | ✅ **0.8.225–0.8.230** |
|
||||
| **F12** | Post-Match-Gate, LLM-QA nach Rematch, Gap-Timing, `roadmap_unfilled`-Sync | ✅ **0.8.231–0.8.232** |
|
||||
| **F13** | **`planning_catalog_context`** (Fokus/Stil/TT/ZG) im Match + Graph-Artefakt | ✅ **0.8.233** |
|
||||
| **F14** | **`ProgressionGraphEditor`** — Slot-UI + Planungskontext-Dropdowns | ✅ **0.8.233** |
|
||||
|
||||
**Architektur (verbindlich):** Progressionsgraph = **Roadmap-first**, **keine Gruppenanalyse**. Bestehender Graph = **leichter Nachfolger-Bias** ab Schritt 2, **kein** automatisches Erweitern ab letztem Knoten (siehe Ist-Doku §5). Trainingsplanung = **eigene Pipeline** (Phase G), wiederverwendet `planning_skill_expectations`.
|
||||
**Architektur (verbindlich):** Drei Schichten — (1) **Katalog-Dimensionen** (DB, jetzt im Match verdrahtet), (2) **Technik-Disambiguierung** (Code, nur bei `topic_type=technique`), (3) **Didaktik** (Roadmap + LLM-QS, nicht im Vokabular). Progressionsgraph = **Roadmap-first**, **keine Gruppenanalyse**. Bestehender Graph = **leichter Nachfolger-Bias** ab Schritt 2. Trainingsplanung = **eigene Pipeline** (Phase G) — Wiederverwendung der Bausteine, siehe Ist-Doku §16.
|
||||
|
||||
**Backend-Kern:** `planning_progression_roadmap.py`, `planning_exercise_path_builder.py`, `planning_skill_expectations.py`, `planning_exercise_form_context.py`, `planning_exercise_path_ai_fill.py`, `progression_graph_planning_artifact.py`
|
||||
**Validierung (Mae Geri, Härtetest):** Pfad-QS vor Optimierung ~65 % → nach Trainer-Roadmap + KI-Gap-Fill **~88 % OK**. Workbench ist **universell** gedacht; Mae Geri war Referenzfall, kein Sonder-Patch.
|
||||
|
||||
**API:** `POST /api/planning/progression-path-suggest` · `PUT /api/exercise-progression-graphs/:id` (`planning_roadmap`) · `POST …/edges/sequence`
|
||||
**Backend-Kern:** `planning_progression_roadmap.py`, `planning_exercise_path_builder.py`, `planning_catalog_context.py`, `planning_path_rematch.py`, `planning_path_refine_stage.py`, `planning_path_qa_pipeline.py`, `planning_skill_expectations.py`, `planning_exercise_form_context.py`, `planning_exercise_path_ai_fill.py`, `progression_graph_planning_artifact.py`
|
||||
|
||||
**Frontend:** `ExerciseProgressionPathBuilder`, `ExerciseGapFillPrepModal`, `planningContextForExerciseAi.js`
|
||||
**API:** `POST /api/planning/progression-path-suggest` · `PUT /api/exercise-progression-graphs/:id` (`planning_roadmap`, `planning_catalog_context`) · `POST …/edges/sequence`
|
||||
|
||||
**Frontend:** **`ProgressionGraphEditor`** (primäre Workbench), `ExerciseProgressionPathBuilder`, `ExerciseGapFillPrepModal`, `progressionGraphDraft.js`, `planningContextForExerciseAi.js`
|
||||
|
||||
**Offen (priorisiert):**
|
||||
1. UI-Wizard (Scroll-Monolith → 4 Schritte) — **separater UI-Chat**
|
||||
2. Graph-Erweiterungsmodus (Start ab Knoten)
|
||||
3. Trainingsplanung Phase G (Gruppenkontext)
|
||||
4. Kontext-Anzeige auf allen Pfad-Schritten
|
||||
1. Dev-Regression: Gewaltschutz / Breitensport / Kinder (nicht nur Mae Geri)
|
||||
2. **PathBuilder-Parität** — gleiche Katalog-Dropdowns wie GraphEditor
|
||||
3. QS-UI — positive LLM-Hinweise als Highlights
|
||||
4. UI-Wizard (4 Schritte: Ziel → Roadmap → Match → Lücken)
|
||||
5. Graph-Erweiterungsmodus (Start ab Knoten)
|
||||
6. Phase D′ — Auto KI-Gap-Fill bei persistent leeren Slots
|
||||
7. **Trainingsplanung Phase G** — Gruppenkontext-Pack, Scopes `training_section` / `framework_slot` (Ist-Doku §16)
|
||||
8. Technik-Katalog konfigurierbar (Backlog)
|
||||
|
||||
#### Übungs-KI Formular / Schnellanlage (Stand **0.8.171**)
|
||||
|
||||
|
|
@ -256,11 +267,14 @@ Das Schema ist gegenüber dem Code zurück: Migration **`022_skills_schema_compl
|
|||
|
||||
### Planungs-KI (priorisiert)
|
||||
|
||||
1. **Phase F2:** LLM für Roadmap (Prompts **078**) + `roadmap_first` Retrieval aus `stage_specs`.
|
||||
2. **Phase F4:** Roadmap-Review UI (Major Steps editierbar vor Übungs-Match).
|
||||
3. **Enrichment:** Skills/Tags pro Technik (Feinauflösung statt nur Geri Waza).
|
||||
4. **D — Neu-Anlage:** `planning_context_json` an `POST /api/exercises/ai/suggest`.
|
||||
5. **Trainingsplanung G:** Kontext-Pack Gruppe/Historie — eigene Pipeline (`AI_PLANNING_KI_MULTISTAGE_FORECAST`); Mitai Workflow-Engine erst danach.
|
||||
1. **Dev-Regression:** Katalog-Match für Gewaltschutz, Breitensport, Kinder — nicht nur Mae-Geri-Härtetest.
|
||||
2. **PathBuilder-Parität:** `planning_catalog_context`-Dropdowns auch in `ExerciseProgressionPathBuilder`.
|
||||
3. **QS-UI:** positive LLM-Empfehlungen als Highlights statt nur Optimierungspotenziale.
|
||||
4. **UI-Wizard:** 4 Schritte (Ziel & Katalog → Roadmap → Match → Lücken); Backend-Pipeline unverändert.
|
||||
5. **Phase D′:** automatisches KI-Gap-Fill bei persistent `roadmap_unfilled`.
|
||||
6. **Trainingsplanung G0–G4:** Katalog in Einheits-Editor, Scopes `training_section`/`framework_slot`, Abschnitts-QS, Gruppenkontext-Pack — Details **`PLANNING_PROGRESSION_GRAPH_KI.md`** §16, **`PLANNING_KI_ROADMAP.md`**.
|
||||
7. **Technik-Katalog externalisieren** (Backlog): `concept_groups` konfigurierbar statt Code-Tuples.
|
||||
8. **Mitai Workflow-Engine** erst nach stabiler Phase G.
|
||||
|
||||
### Allgemein
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Planungs-KI — Produkt-Roadmap
|
||||
|
||||
**Stand:** 2026-05-22
|
||||
**App-Version:** **0.8.217** — maßgeblich `backend/version.py`
|
||||
**App-Version:** **0.8.233** — maßgeblich `backend/version.py`
|
||||
|
||||
Diese Roadmap ergänzt die **Architektur-Refaktor-Roadmap** (`UMSETZUNGSPLAN_ROADMAP.md`) und gilt **nur für KI-gestützte Trainingsplanungsunterstützung**.
|
||||
|
||||
|
|
@ -13,9 +13,10 @@ Diese Roadmap ergänzt die **Architektur-Refaktor-Roadmap** (`UMSETZUNGSPLAN_ROA
|
|||
## Strategische Entscheidung (verbindlich)
|
||||
|
||||
1. **Progressionsgraph:** Planung **vom Ziel rückwärts** (Roadmap-first), nicht Bibliothek-first.
|
||||
2. **Keine Gruppenanalyse** im Graphen — Kontext = Zieltext, Thema, Schrittanzahl, optional Graph-Kanten.
|
||||
3. **Trainingsplanung** (Einheit, Rahmen, Abschnitt): eigene Pipeline später, **mit** Gruppenkontext — siehe `AI_PLANNING_KI_MULTISTAGE_FORECAST.md` S0–S4.
|
||||
4. **Orchestrierung:** Workflow-**lite** jetzt (`planning_progression_roadmap.py`); Mitai Workflow-Engine **später**, wenn 2–3 Pipelines stabil sind.
|
||||
2. **Keine Gruppenanalyse** im Graphen — Kontext = Zieltext, Katalog-Dimensionen, Start/Ziel, Roadmap, optional Graph-Kanten.
|
||||
3. **Drei Schichten statt monolithischem Vokabular:** Katalog (DB) · Technik-Disambiguierung (Code, nur bei Technik-Themen) · Didaktik (Roadmap + LLM-QS).
|
||||
4. **Trainingsplanung** (Einheit, Rahmen, Abschnitt): eigene Pipeline (Phase G), **mit** Gruppenkontext — siehe `AI_PLANNING_KI_MULTISTAGE_FORECAST.md` S0–S4 und Ist-Doku §16.
|
||||
5. **Orchestrierung:** Workflow-**lite** jetzt (`planning_progression_roadmap.py`, `planning_exercise_path_builder.py`); Mitai Workflow-Engine **später**, wenn Phase G stabil ist.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -27,84 +28,138 @@ Diese Roadmap ergänzt die **Architektur-Refaktor-Roadmap** (`UMSETZUNGSPLAN_ROA
|
|||
| A–C2 | Übungssuche | Voll-Library, Graph, Varianten | ✅ |
|
||||
| C3 | Progressionsgraph | Pfad-Builder (retrieval-first) | ✅ |
|
||||
| E–E3 | Progressionsgraph | Semantik, QA, Lücken-Angebote | ✅ |
|
||||
| **F0–F4** | Progressionsgraph | Roadmap-Pipeline, LLM, roadmap-first, UI Review | ✅ **0.8.204–209** |
|
||||
| **F0–F4** | Progressionsgraph | Roadmap-Pipeline, LLM, roadmap_first, UI Review | ✅ **0.8.204–209** |
|
||||
| **F5–F9** | Progressionsgraph | Start/Ziel, Gap-Prep, Skill-Expectations, Persistenz | ✅ **0.8.210–217** |
|
||||
| **F10** | Progressionsgraph | Stufen-Lernziel-Gate, kein blindes Rank-Fallback | ✅ **0.8.218** |
|
||||
| **F11–F12** | Progressionsgraph | Auto-Rematch, Spec-Refine, QS-Pipeline-Timing | ✅ **0.8.225–0.8.232** |
|
||||
| **F13–F14** | Progressionsgraph | Katalog-Kontext + GraphEditor-Workbench | ✅ **0.8.233** |
|
||||
| D | Übungs-Neuanlage | `planning_context` an `suggestExerciseAi` | ✅ **0.8.208** |
|
||||
| **UX** | Progressionsgraph | Wizard/Stepper statt Scroll-UI | 🔲 |
|
||||
| G | Trainingsplanung | Kontext-Pack Gruppe/Historie, S0–S4 | 🔲 |
|
||||
| H | Plattform | Mitai-Workflow-Engine (optional) | 🔲 Backlog |
|
||||
| **UX** | Progressionsgraph | Wizard/Stepper; PathBuilder-Parität Katalog | 🔲 |
|
||||
| **D′** | Progressionsgraph | Auto KI-Gap-Fill bei persistent leeren Slots | 🔲 Backlog |
|
||||
| **G** | Trainingsplanung | Kontext-Pack Gruppe/Historie, G0–G4 | 🔲 |
|
||||
| **H** | Plattform | Technik-Katalog konfigurierbar; Mitai-Workflow | 🔲 Backlog |
|
||||
|
||||
---
|
||||
|
||||
## Phase F — Progressions-Roadmap (aktiver Fokus)
|
||||
## Phase F — Progressions-Roadmap (abgeschlossen bis F14)
|
||||
|
||||
### F0 — Foundation (0.8.204)
|
||||
Details und Module: **`PLANNING_PROGRESSION_GRAPH_KI.md`**.
|
||||
|
||||
- [x] Spec `PLANNING_PROGRESSION_ROADMAP_SPEC.md`
|
||||
- [x] Modul `planning_progression_roadmap.py` (Pydantic, Pipeline-Skeleton)
|
||||
- [x] Migration **078** Prompt-Slugs (Zielanalyse, Roadmap)
|
||||
- [x] API: `include_roadmap_preview` auf `progression-path-suggest`
|
||||
- [x] Doku: HANDOVER, PLANNING_EXERCISE_SUGGEST_CONTEXT, MULTISTAGE_FORECAST
|
||||
### F0–F9 — (Kurz, siehe Ist-Doku)
|
||||
|
||||
### F1 — Deterministische Roadmap
|
||||
- [x] F0 Foundation (0.8.204) — Spec, Pipeline-Skeleton, Prompts 078
|
||||
- [x] F1 Deterministische Roadmap — Phase A/B/C heuristisch
|
||||
- [x] F2 LLM Roadmap (0.8.205) — Prompts 078/079
|
||||
- [x] F3 roadmap-first (0.8.206) — Match pro `stage_spec`, `roadmap_unfilled`
|
||||
- [x] F4 UI Review (0.8.207) — `roadmap_override`, Major Steps editierbar
|
||||
- [x] F5 Start/Ziel (0.8.210–214) — Prompt **087**, Zwei-Schritt-UI
|
||||
- [x] F6 Gap-KI-Kontext (0.8.212–214) — `ExerciseGapFillPrepModal`
|
||||
- [x] F7 Fähigkeiten-Scoring (0.8.215–216) — `planning_skill_expectations`
|
||||
- [x] F8 Stufen-Details UI (0.8.216) — editierbare `stage_specs`
|
||||
- [x] F9 Persistenz (0.8.217) — Migration **088** `planning_roadmap` JSONB
|
||||
|
||||
- [x] Phase A aus Semantic Brief
|
||||
- [x] Phase B: `micro_objectives` aus `development_arc` + Konsolidierung auf N
|
||||
- [x] Phase C: heuristische `stage_specs`
|
||||
- [ ] pytest für Konsolidierung
|
||||
### F10 — Stufen-Qualität (0.8.218)
|
||||
|
||||
### F2 — LLM Roadmap (0.8.205)
|
||||
- [x] Stufen-Lernziel-Gate — kein Rank-Fallback ohne Pass
|
||||
- [x] Anti-Pattern-Sanitizer, `stage_mismatch` → leerer Slot + Gap
|
||||
|
||||
- [x] Prompts **078/079** in `ai_prompts` — Code nur Slugs (`PROMPT_SLUG_*`)
|
||||
- [x] `include_llm_roadmap` + `load_and_render_ai_prompt` + JSON-Validierung
|
||||
- [x] Deterministischer Fallback wenn Prompt/OpenRouter fehlt
|
||||
- [ ] Response/UI: genutzte `prompt_slugs` sichtbar machen (Admin-Hinweis)
|
||||
### F11 — Auto-Optimierung (0.8.225–0.8.230)
|
||||
|
||||
### F3 — roadmap-first (0.8.206)
|
||||
- [x] `planning_path_rematch.py` — Rematch-Schleife für `rematch_slot` / `roadmap_unfilled`
|
||||
- [x] `planning_path_refine_stage.py` — Spec-Schärfung aus QS
|
||||
- [x] `planning_path_qa_pipeline.py` — mehrstufige QS
|
||||
|
||||
- [x] Retrieval pro `major_step` + `stage_spec` statt iterativem Pfad-Bau
|
||||
- [x] Gap-Angebote für unbesetzte Roadmap-Stufen (`roadmap_unfilled`)
|
||||
- [x] QA/Lücken an Roadmap gekoppelt (`roadmap_first_lite`: keine Brücken/Reorder zwischen Major Steps)
|
||||
### F12 — Pipeline-Timing & Sync (0.8.231–0.8.232)
|
||||
|
||||
### F4 — UI (0.8.207)
|
||||
- [x] Post-Match-Gate vor Rematch-Akzeptanz
|
||||
- [x] LLM Pfad-QS **nach** Rematch
|
||||
- [x] Gap-Offers vor `path_qa`-Summary
|
||||
- [x] Frontend: `applyMatchStepsToSlots` sync per `majorStepIndex`
|
||||
|
||||
- [x] Roadmap-Review im `ExerciseProgressionPathBuilder`
|
||||
- [x] Major Steps editierbar (Phase, Lernziel, Reihenfolge) vor Übungs-Match
|
||||
- [x] API `roadmap_only` + `roadmap_override`
|
||||
### F13 — Katalog-Kontext (0.8.233)
|
||||
|
||||
### F5 — Start/Ziel (0.8.210–214)
|
||||
- [x] `planning_catalog_context.py` — Fokus, Stil, Trainingsstil, Zielgruppe
|
||||
- [x] Merge in `PlanningTargetProfile` + Text-Signale
|
||||
- [x] Persistenz im Graph-Artefakt
|
||||
- [x] Technik-Gates nur bei `topic_type == "technique"`
|
||||
|
||||
- [x] Strukturierte Felder `start_situation`, `target_state`, `roadmap_notes`
|
||||
- [x] Prompt **087** `planning_progression_start_target`
|
||||
- [x] Priorität: Trainer > KI > Regex (`resolve_roadmap_structured_input`)
|
||||
- [x] Zwei-Schritt-UI: „Start/Ziel analysieren“ / „Roadmap vorschlagen“
|
||||
### F14 — GraphEditor Workbench (0.8.233)
|
||||
|
||||
### F6 — Gap-KI-Kontext (0.8.212–214)
|
||||
- [x] `ProgressionGraphEditor` — primäre UI für Roadmap + Match + Lücken
|
||||
- [x] Vier Planungskontext-Dropdowns im Editor
|
||||
- [x] `progressionGraphDraft.js` — Artefakt + API-Payload
|
||||
|
||||
- [x] `ExerciseGapFillPrepModal` vor KI-Call
|
||||
- [x] `planning_exercise_form_context.py` — Gap-Snapshot, `context_preview`
|
||||
- [x] Migration **085** — `planning_context` in Übungs-Prompts
|
||||
### Validierung (Referenz Mae Geri, 2026-05)
|
||||
|
||||
### F7 — Fähigkeiten-Scoring (0.8.215–216)
|
||||
| Phase | Pfad-QS | Ergebnis |
|
||||
|-------|---------|----------|
|
||||
| Vor Roadmap/KI | ~65 % | Lücken, falsche Reihenfolge, Off-Topic |
|
||||
| Nach Trainer-Roadmap + KI-Gap-Fill | **~88 % OK** | Vollständige Abdeckung; positive LLM-Hinweise |
|
||||
|
||||
- [x] `planning_skill_expectations.py` (Scopes: `progression_stage`, `progression_path`)
|
||||
- [x] Pro-Stufe-Retrieval + `path_skill_expectations` + UI-Tags
|
||||
- [x] `expected_skills` in Gap-Fill
|
||||
**Fazit:** Workbench + Katalog + Roadmap sind universell; Technik-Hardcoding allein reicht für Didaktik nicht.
|
||||
|
||||
### F8 — Stufen-Details UI (0.8.216)
|
||||
---
|
||||
|
||||
- [x] Editierbare `stage_specs` in `roadmap_override` (Belastung, Erfolgskriterien, Vermeiden)
|
||||
## UX — UI-Überarbeitung (offen)
|
||||
|
||||
### F9 — Persistenz (0.8.217)
|
||||
- [ ] Wizard mit 4 Schritten (Ziel & Katalog → Roadmap → Match → Lücken)
|
||||
- [ ] Progressive disclosure — Details in Panels
|
||||
- [ ] PathBuilder-Parität: gleiche Katalog-Dropdowns wie GraphEditor
|
||||
- [ ] QS-UI: positive LLM-Hinweise als Highlights
|
||||
- Briefing: `PLANNING_PROGRESSION_GRAPH_KI.md` §12
|
||||
|
||||
- [x] Migration **088** — `planning_roadmap` JSONB am Graph
|
||||
- [x] Laden/Speichern über `GET/PUT` Graph + Sequenz-Endpoint
|
||||
---
|
||||
|
||||
### UX — UI-Überarbeitung (offen)
|
||||
## Phase D′ — Auto Gap-Fill (Backlog)
|
||||
|
||||
- [ ] Wizard mit 4 Schritten (Ziel → Roadmap → Match → Lücken)
|
||||
- [ ] Progressive disclosure — Details in Panels, nicht alles gleichzeitig
|
||||
- [ ] Briefing: `PLANNING_PROGRESSION_GRAPH_KI.md` §10
|
||||
- [ ] Bei persistent `roadmap_unfilled` automatisch KI-Vorschlag vorbereiten (ohne manuelles Modal)
|
||||
- [ ] Governance: Trainer bestätigt vor Persistenz
|
||||
|
||||
---
|
||||
|
||||
## Phase G — Trainingsplanung (komplexere Domäne)
|
||||
|
||||
**Ziel:** Einheiten, Rahmen-Slots, Abschnitte und parallele Streams KI-gestützt planen — **ohne** zweite Retrieval-Welt.
|
||||
|
||||
### Wiederverwendung aus Progressionsgraph
|
||||
|
||||
| Baustein | Progressionsgraph | Trainingsplanung |
|
||||
|----------|-------------------|------------------|
|
||||
| `PlanningTargetProfile` | Curriculum-Query + Katalog | Einheit + Abschnitt + Slot + Katalog + Historie |
|
||||
| `planning_catalog_context` | Am Graph gespeichert | Pro Einheit / Slot / Voreinstellung |
|
||||
| `planning_skill_expectations` | `progression_stage`, `progression_path` | **`training_section`**, **`framework_slot`** |
|
||||
| `planning_exercise_retrieval` | Roadmap-Stufen-Match | `suggest_planning_exercises` — **bereits produktiv** |
|
||||
| `planning_path_qa_pipeline` | Curriculum-QS | Abschnitts-QS (Kohärenz, Streams) |
|
||||
| `planning_exercise_form_context` | Pfad-Lücken | Abschnitts-/Slot-Lücken |
|
||||
| Roadmap-Pipeline | Major Steps über Wochen | **Nicht 1:1** — Phasen/Streams + Vorlagen |
|
||||
|
||||
### Was Phase G neu braucht
|
||||
|
||||
- Gruppen-/Historie-Kontext-Pack (`AI_PLANNING_KI_MULTISTAGE_FORECAST` S0–S4)
|
||||
- Abschnitts-Didaktik — Dauer, Parallel-Streams, Coaching
|
||||
- Rahmen-Blueprint-Anbindung (`training_framework_programs`, Slot-Blueprints)
|
||||
- Eigene Orchestrierung pro Einheit (kein Curriculum über N Wochen)
|
||||
|
||||
### Integrations-Reihenfolge G0–G4
|
||||
|
||||
| Schritt | Inhalt | Abhängigkeit |
|
||||
|---------|--------|--------------|
|
||||
| **G0** | Katalog in Einheits-Editor → bestehende Suggest-Pipeline | F13 ✅ |
|
||||
| **G1** | Scope `training_section` + Skill-Erwartungen aktiv | F7 ✅ |
|
||||
| **G2** | Abschnitts-QS (Hint-Struktur wie Graph) | F11–F12 ✅ |
|
||||
| **G3** | Framework-Slot + Gap-Fill | G0, G1 |
|
||||
| **G4** | Gruppenkontext-Pack | G0–G3 |
|
||||
|
||||
**Nicht:** Roadmap-first-Loop 1:1 auf Trainingseinheit mappen.
|
||||
|
||||
Details: **`PLANNING_PROGRESSION_GRAPH_KI.md`** §16 · Domäne **`DOMAIN_MODEL.md`** §1–2.
|
||||
|
||||
---
|
||||
|
||||
## Phase H — Plattform (Backlog)
|
||||
|
||||
- [ ] Technik-Disambiguierung konfigurierbar (DB statt `_GERI_TECHNIQUES` in Code)
|
||||
- [ ] Mitai Workflow-Engine — erst wenn G0–G4 stabil
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -112,10 +167,10 @@ Diese Roadmap ergänzt die **Architektur-Refaktor-Roadmap** (`UMSETZUNGSPLAN_ROA
|
|||
|
||||
| Von | Nach | Hinweis |
|
||||
|-----|------|---------|
|
||||
| F2 | Enrichment / Skills | Bessere Roadmap bei technikspezifischen Skills |
|
||||
| F3 | F2 | LLM-Roadmap oder stabile heuristische B |
|
||||
| G | F4 | Trainingsplanung kann Roadmap aus Graph referenzieren |
|
||||
| H | G + F4 | Workflow-Engine lohnt bei verzweigten Planungsflows |
|
||||
| F13 | G0 | Katalog-Kontext in Einheitsplanung |
|
||||
| F7, F11 | G1, G2 | Skill-Expectations + QS-Muster |
|
||||
| F4, F9 | G3 | Graph-Roadmap kann Rahmen referenzieren |
|
||||
| G | H | Workflow-Engine lohnt bei verzweigten Planungsflows |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# Progressionsgraph — KI-Planung (Ist-Stand)
|
||||
|
||||
**Stand:** 2026-05-22 (Dokumentation) · **App-Version:** **0.8.218** · **DB:** Migration **088**
|
||||
**Maßgeblich für Code:** `backend/version.py` (`APP_VERSION`, `DB_SCHEMA_VERSION`)
|
||||
**Stand:** 2026-05-22 (Dokumentation) · **App-Version:** **0.8.233** · **DB:** Migration **088**
|
||||
**Maßgeblich für Code:** `backend/version.py` (`APP_VERSION`, `DB_SCHEMA_VERSION`, `MODULE_VERSIONS.planning_exercise_suggest`)
|
||||
|
||||
> **Diese Datei ist die zentrale Referenz** für die KI-gestützte Planung im Progressionsgraph.
|
||||
> Ältere Abschnitte in `HANDOVER.md` §2.8 und `PLANNING_KI_ROADMAP.md` verweisen hierher.
|
||||
|
|
@ -30,19 +30,20 @@
|
|||
|
||||
## 2. Trainer-Workflow (UI)
|
||||
|
||||
Aktuell in `ExerciseProgressionPathBuilder.jsx` (eingebettet in `ExerciseProgressionGraphPanel.jsx`):
|
||||
**Primär:** `ProgressionGraphEditor.jsx` (integrierter Slot-Editor, Phase B).
|
||||
**Legacy/Parallel:** `ExerciseProgressionPathBuilder.jsx` (Scroll-Monolith — gleiche API, Katalog-Kontext-Dropdowns dort noch nachziehen).
|
||||
|
||||
```
|
||||
① Ziel eingeben (+ optional Start/Ziel-Felder manuell)
|
||||
② „Start/Ziel analysieren“ (optional, start_target_only)
|
||||
③ „Roadmap vorschlagen“ (roadmap_only, LLM-Roadmap)
|
||||
① Ziel eingeben (+ Planungskontext: Primärfokus, Stil, Trainingsstil, Zielgruppe)
|
||||
② Optional: Start/Ziel-Felder manuell oder „Start/Ziel analysieren“
|
||||
③ „Roadmap generieren“ (roadmap_only, LLM-Roadmap)
|
||||
④ Roadmap bearbeiten (Major Steps + Stufen-Details)
|
||||
⑤ „Übungen matchen“ (roadmap_first + roadmap_override)
|
||||
⑥ Lücken mit KI schließen (gap_fill_offers + Vorbereitungs-Dialog)
|
||||
⑦ „Pfad in Graph speichern“ (Sequenz-Kanten)
|
||||
⑤ „Übungen matchen“ (roadmap_first + roadmap_override + Auto-QS/Rematch)
|
||||
⑥ Lücken: KI-Angebote → „KI anlegen“ (Gap-Prep-Modal) → in Slot
|
||||
⑦ „Graph speichern“ (planning_roadmap + optional Kanten-Sequenz)
|
||||
```
|
||||
|
||||
**Bekannte UX-Schuld:** Alle Schritte liegen auf **einer langen Scroll-Seite** — Überarbeitung als Wizard/Stepper ist geplant (separater UI-Chat). Briefing-Vorlage siehe unten §10.
|
||||
**Bekannte UX-Schuld:** PathBuilder = lange Scroll-Seite; GraphEditor = kompakter, aber noch kein Wizard. Stepper geplant (separater UI-Chat). Briefing §12.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -51,6 +52,7 @@ Aktuell in `ExerciseProgressionPathBuilder.jsx` (eingebettet in `ExerciseProgres
|
|||
```mermaid
|
||||
flowchart TB
|
||||
subgraph ui [Frontend]
|
||||
PGE[ProgressionGraphEditor]
|
||||
EPB[ExerciseProgressionPathBuilder]
|
||||
GFM[ExerciseGapFillPrepModal]
|
||||
PCtx[planningContextForExerciseAi.js]
|
||||
|
|
@ -71,6 +73,10 @@ flowchart TB
|
|||
|
||||
subgraph match [Match + QA]
|
||||
PB[planning_exercise_path_builder.py]
|
||||
PCC[planning_catalog_context.py]
|
||||
REM[planning_path_rematch.py]
|
||||
REF[planning_path_refine_stage.py]
|
||||
QAP[planning_path_qa_pipeline.py]
|
||||
RET[planning_exercise_retrieval.py]
|
||||
PG[planning_exercise_progression.py]
|
||||
SEM[planning_exercise_semantics.py]
|
||||
|
|
@ -88,11 +94,16 @@ flowchart TB
|
|||
end
|
||||
|
||||
EPB --> PPS
|
||||
EPB --> SEQ
|
||||
EPB --> PUT
|
||||
PGE --> PPS
|
||||
PGE --> SEQ
|
||||
PGE --> PUT
|
||||
GFM --> EAI
|
||||
PPS --> PR
|
||||
PPS --> PB
|
||||
PB --> PCC
|
||||
PB --> REM
|
||||
PB --> REF
|
||||
PB --> QAP
|
||||
PB --> RET
|
||||
PB --> PG
|
||||
PB --> PSE
|
||||
|
|
@ -108,12 +119,18 @@ flowchart TB
|
|||
| Modul | Aufgabe |
|
||||
|--------|---------|
|
||||
| `planning_progression_roadmap.py` | Phasen A–C: Zielanalyse, Roadmap, `stage_specs`; Start/Ziel-Auflösung (Trainer > KI > Regex) |
|
||||
| `planning_exercise_path_builder.py` | `suggest_progression_path`: roadmap_first Match, QA, Gap-Offers |
|
||||
| `planning_exercise_path_builder.py` | `suggest_progression_path`: roadmap_first Match, Auto-QS, Rematch, Gap-Offers |
|
||||
| `planning_catalog_context.py` | **Expliziter Katalog-Kontext** (Fokus, Stil, Trainingsstil, Zielgruppe) → `PlanningTargetProfile` |
|
||||
| `planning_path_rematch.py` | Auto-Rematch betroffener Slots (`max_rematch_rounds`) |
|
||||
| `planning_path_refine_stage.py` | Stufen-Spec-Verfeinerung bei `stage_mismatch` (Phase C) |
|
||||
| `planning_path_qa_pipeline.py` | Mehrstufige QS → `optimization_hints` |
|
||||
| `planning_exercise_progression.py` | Graph auflösen, Nachfolger-Kanten für Retrieval-Bias |
|
||||
| `planning_skill_expectations.py` | Skill-Erwartungen pro Scope (`progression_stage`, `progression_path`, später `training_section`) |
|
||||
| `planning_exercise_form_context.py` | `planning_context` / Gap-Snapshot für Übungs-KI |
|
||||
| `planning_exercise_path_ai_fill.py` | Gap-Fill-Angebote, `goal_for_ai`, `context_preview` |
|
||||
| `progression_graph_planning_artifact.py` | Validierung `planning_roadmap` JSON (Schema v1, max. 64 KB) |
|
||||
| `planning_exercise_profiles.py` | **Katalog-Scoring** (Fokus/Stil/TT/ZG/Skills) — gemeinsam mit Einheitsplanung |
|
||||
| `planning_exercise_target_pipeline.py` | Query-Intent-Pipeline — Progressionsgraph nutzt `query_only`-Modus + Katalog-Overlay |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -131,10 +148,14 @@ flowchart TB
|
|||
| `start_target_only` | bool | Nur Start/Ziel-Analyse |
|
||||
| `roadmap_override` | object | Trainer-bearbeitete `major_steps` + `stage_specs` |
|
||||
| `start_situation`, `target_state`, `roadmap_notes` | string? | Strukturierte Eingabe (Priorität vor KI) |
|
||||
| `planning_catalog_context` | object? | Primärfokus, Stilrichtung, Trainingsstil, Zielgruppe (IDs + `is_primary`) |
|
||||
| `include_llm_start_target` | bool | LLM-Extraktion Start/Ziel (Prompt **087**) |
|
||||
| `include_llm_roadmap` | bool | LLM Roadmap (Prompts **078/079**) |
|
||||
| `include_llm_intent` | bool | LLM Intent für Semantic Brief (Roadmap-Vorschlag: **true** seit 0.8.217) |
|
||||
| `include_path_qa`, `include_ai_gap_fill` | bool | QS, Lücken-Angebote |
|
||||
| `include_llm_intent` | bool | LLM Intent für Semantic Brief |
|
||||
| `auto_rematch_after_qa` | bool | Auto-Rematch nach QS (Default **true**) |
|
||||
| `auto_refine_stage_spec` | bool | Stufen-Spec bei `stage_mismatch` schärfen (Default **true**) |
|
||||
| `max_rematch_rounds` | int | Rematch-Runden 0–4 (Default **3**) |
|
||||
| `include_path_qa`, `include_llm_path_qa`, `include_ai_gap_fill` | bool | QS, LLM-Ganzpfad, Lücken-Angebote |
|
||||
|
||||
### 4.2 Wichtige Response-Felder
|
||||
|
||||
|
|
@ -144,7 +165,27 @@ flowchart TB
|
|||
| `steps[]` | Gematchte Übungen; pro Schritt u. a. `roadmap_*`, `skill_expectations` |
|
||||
| `path_skill_expectations` | Pfadweite Skill-Erwartungen |
|
||||
| `gap_fill_offers[]` | Lücken mit `context_preview`, `goal_for_ai` |
|
||||
| `path_qa` | QS inkl. `roadmap_qa_mode: roadmap_first_lite` |
|
||||
| `path_qa` | QS inkl. `qa_tiers`, `optimization_hints`, `rematch_log`, `refine_log` |
|
||||
| `target_profile_summary` | Erwartungsprofil inkl. Katalog-Dimensionen (nach Match) |
|
||||
| `match_summary` | `library_matches`, `gap_fill_offer_count`, `roadmap_unfilled_count` |
|
||||
|
||||
---
|
||||
|
||||
## 4.4 Planungskontext — Katalog vs. Technik-Vokabular
|
||||
|
||||
Shinkan unterscheidet **drei Schichten** (kein monolithisches „Vokabular“):
|
||||
|
||||
| Schicht | Was | Wo | Beispiel |
|
||||
|---------|-----|-----|----------|
|
||||
| **Katalog-Dimensionen** | Was für Training? | DB: `focus_areas`, `style_directions`, `training_types`, `target_groups`, `skills` | Gewaltschutz, Breitensport, Shotokan |
|
||||
| **Disambiguierung (Technik)** | Verwechslungs-Nachbarn | Code: `planning_exercise_semantics.py` (`_GERI_TECHNIQUES`, …) | Mae Geri ≠ Mawashi Geri |
|
||||
| **Didaktik / Kausalität** | Reihenfolge, Lernphasen | Roadmap + LLM Pfad-QS | Grundlagen vor Geschwindigkeit |
|
||||
|
||||
**Seit 0.8.233:** `planning_catalog_context` im Request und im Graph-Artefakt (`planning_catalog_context` JSON). Fließt in `PlanningTargetProfile` → Hybrid-Retrieval (`score_exercise_against_target`: „Fokusbereich passend“, …). Zusätzlich additive Text-Signale aus Anfrage + Start/Ziel + Notizen (`planning_exercise_text_signals`).
|
||||
|
||||
**Technik-Gates** (`technique_scope`, Geschwister-Ausschluss) nur bei `topic_type == "technique"` — Fokus-Pfade (Gewaltschutz, Fitness, …) werden nicht wie Mae-Geri-Pfade behandelt.
|
||||
|
||||
Fallback: fehlt `planning_catalog_context` im Request, wird aus gespeichertem `planning_roadmap` am Graph geladen.
|
||||
|
||||
### 4.3 Prompt-Slugs (nur in `ai_prompts`, nie Hardcoding)
|
||||
|
||||
|
|
@ -157,17 +198,32 @@ flowchart TB
|
|||
|
||||
---
|
||||
|
||||
## 5. Roadmap-Match — Stufen-Qualität (0.8.218)
|
||||
## 5. Roadmap-Match — Stufen-Qualität (0.8.218–0.8.233)
|
||||
|
||||
Pro Major Step gilt:
|
||||
|
||||
1. **Stufen-Brief** — `semantic_brief_for_stage()` ergänzt `must_phrases` um das Stufen-Lernziel.
|
||||
2. **Stufen-Gate** — `exercise_passes_stage_learning_goal_gate()` prüft Lernziel-Text in Titel/Summary/Ziel oder Mindest-`semantic_score`.
|
||||
3. **Kein Fallback** — Bei `roadmap_first` wird **nicht** auf die globale `goal_query` zurückgefallen; passt keine Übung → **Lücke** (`roadmap_unfilled`) statt themenfremder Übung.
|
||||
4. **Retrieval** — Bonus/Strafe im Hybrid-Score je nach Stufen-Passung.
|
||||
5. **QS** — `detect_off_topic_steps` erkennt `stage_mismatch` anhand `roadmap_learning_goal`.
|
||||
1. **Stufen-Brief** — `build_stage_match_brief()` aus Lernziel, `anti_patterns`, Erfolgskriterien, Pfad-Kontext.
|
||||
2. **Stufen-Gate** — `exercise_passes_stage_fit()` / `exercise_passes_stage_learning_goal_gate()` auf vollem Übungstext.
|
||||
3. **Kein blindes Rank-Fallback** — ohne Gate-Passung → `roadmap_unfilled`, nicht themenfremde Übung.
|
||||
4. **Post-Match-Gate** — `_roadmap_step_passes_post_match_gate()` = gleiche QS wie `detect_off_topic_steps` (kein Rematch-Treffer, der sofort wieder `stage_mismatch` wäre).
|
||||
5. **Retrieval** — Hybrid-Score: Volltext + Semantik + **Profil/Katalog** + Skill-Erwartungen + optional Graph-Bias.
|
||||
6. **Auto-Optimierung (ein Match-Lauf):**
|
||||
- **Phase B:** Rematch-Schleife (`planning_path_rematch.py`) für `rematch_slot` / `roadmap_unfilled`
|
||||
- **Phase C:** `planning_path_refine_stage.py` — `anti_patterns` / Erfolgskriterien aus QS
|
||||
- Purge persistent `stage_mismatch` → Slot leeren + KI-Gap
|
||||
- LLM Pfad-QS **nach** Rematch auf finalem Pfad
|
||||
- Gap-Offers für alle leeren Slots **vor** `path_qa`-Summary
|
||||
|
||||
Tests: `test_planning_roadmap_stage_match.py`
|
||||
Tests: `test_planning_roadmap_stage_match.py`, `test_planning_path_rematch.py`, `test_planning_path_refine_stage.py`, `test_planning_catalog_context.py`
|
||||
|
||||
### Referenz-Validierung (Mae Geri, 2026-05)
|
||||
|
||||
| Phase | Pfad-QS | Ergebnis |
|
||||
|-------|---------|----------|
|
||||
| Vor Roadmap/KI-Anpassung | ~65 % | Strukturelle Lücken (Grundlagen, Reihenfolge, Zielgenauigkeit) |
|
||||
| Nach Trainer-Roadmap + KI-Angebote in leeren Slots | **~88 % OK** | Vollständige Curriculum-Abdeckung; positive LLM-Empfehlungen |
|
||||
|
||||
**Lesson:** Workbench + Katalog-Kontext + Roadmap sind der Hebel; Technik-Hardcoding allein reicht nicht für Didaktik.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -209,7 +265,15 @@ Gespeichert am **Graph-Container** (`exercise_progression_graphs`), Schema v1:
|
|||
"roadmap_notes": "…",
|
||||
"max_steps": 5,
|
||||
"progression_roadmap": { },
|
||||
"path_skill_expectations": { }
|
||||
"path_skill_expectations": { },
|
||||
"planning_catalog_context": {
|
||||
"focus_areas": [{ "id": 1, "is_primary": true }],
|
||||
"style_directions": [],
|
||||
"training_types": [{ "id": 2, "is_primary": true }],
|
||||
"target_groups": []
|
||||
},
|
||||
"slot_contents": [ ],
|
||||
"last_findings": { }
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -307,26 +371,73 @@ Kontext-Helfer: `frontend/src/utils/planningContextForExerciseAi.js`
|
|||
| F7 | `planning_skill_expectations` (Retrieval + UI + Gap) | ✅ | 0.8.215–216 |
|
||||
| F8 | Editierbare `stage_specs` in UI | ✅ | 0.8.216 |
|
||||
| F9 | `planning_roadmap` Persistenz (Migration **088**) | ✅ | 0.8.217 |
|
||||
| F10 | Stufen-Lernziel-Gate beim Match (kein goal_query-Fallback) | ✅ | 0.8.218 |
|
||||
| **G** | Trainingsplanung eigene Pipeline + Graph-Referenz | 🔲 | — |
|
||||
| **UX** | Wizard/Stepper statt Scroll-Monolith | 🔲 | separater Chat |
|
||||
| F10 | Stufen-Lernziel-Gate + kein goal_query-Fallback | ✅ | 0.8.218 |
|
||||
| **F11** | Auto-Rematch + Stufen-Spec-Refine + mehrstufige QS | ✅ | 0.8.225–0.8.230 |
|
||||
| **F12** | Post-Match-Gate, LLM-QA nach Rematch, Gap-Timing, `roadmap_unfilled`-Sync | ✅ | 0.8.231–0.8.232 |
|
||||
| **F13** | **Katalog-Kontext** (`planning_catalog_context`) im Match + Graph-Artefakt | ✅ | **0.8.233** |
|
||||
| **F14** | `ProgressionGraphEditor` Slot-UI + Planungskontext-Dropdowns | ✅ | 0.8.233 |
|
||||
| **G** | Trainingsplanung: eigene Pipeline + Wiederverwendung Bausteine (§16) | 🔲 | — |
|
||||
| **UX** | Wizard/Stepper; PathBuilder-Parität Katalog | 🔲 | — |
|
||||
| **H** | Technik-Disambiguierung konfigurierbar (DB statt Code-Tuples) | 🔲 | Backlog |
|
||||
| **D′** | Auto Gap-Fill (KI generiert bei persistent `roadmap_unfilled`) | 🔲 | Backlog |
|
||||
|
||||
---
|
||||
|
||||
## 12. Offenes Backlog (priorisiert)
|
||||
|
||||
1. **UI-Überarbeitung** — Wizard mit 4 Schritten, progressive disclosure (Briefing unten)
|
||||
2. **Graph-Erweiterungsmodus** — Start ab gewähltem Knoten / letzter Sequenz
|
||||
3. **Kontext auf allen Pfad-Schritten** in UI (nicht nur Lücken)
|
||||
4. **Trainingsplanung Phase G** — Pipeline mit Gruppenkontext, Wiederverwendung `planning_skill_expectations`
|
||||
5. Enrichment / Prompt-Feintuning
|
||||
6. Mitai Workflow-Engine (langfristig)
|
||||
1. **Dev-Regression:** Gewaltschutz + Breitensport + Kinder (ohne Mae Geri) — Katalog-Match verifizieren
|
||||
2. **PathBuilder-Parität** — gleiche `planning_catalog_context`-Dropdowns in `ExerciseProgressionPathBuilder`
|
||||
3. **QS-UI** — positive LLM-Hinweise als „Highlights“, nicht als „Optimierungspotenziale“
|
||||
4. **UI-Wizard** — 4 Schritte (Ziel → Roadmap → Match → Lücken); Backend unverändert
|
||||
5. **Graph-Erweiterungsmodus** — Start ab gewähltem Knoten / letzter Sequenz
|
||||
6. **Phase D′** — automatisches KI-Gap-Fill bei persistent leeren Slots
|
||||
7. **Trainingsplanung Phase G** — siehe §16
|
||||
8. **Technik-Katalog externalisieren** — konfigurierbare `concept_groups` (Backlog)
|
||||
9. Graph-Metadaten: Primärfokus/Stil als Spalten (Reporting)
|
||||
10. Mitai Workflow-Engine (langfristig)
|
||||
|
||||
### Briefing-Vorlage UI-Chat (Copy-Paste)
|
||||
### Briefing-Vorlage UI-Chat
|
||||
|
||||
Siehe Nutzer-Chat 2026-05-22 oder `HANDOVER.md` §2.8 — Abschnitt „UI-Überarbeitung“.
|
||||
Kern: Wizard ① Ziel & Planungskontext → ② Roadmap → ③ Match → ④ Lücken & Speichern; Backend-Pipeline unverändert lassen.
|
||||
|
||||
Kern: Wizard ① Ziel & Start/Ziel → ② Roadmap → ③ Match → ④ Lücken & Speichern; Backend-Pipeline unverändert lassen.
|
||||
---
|
||||
|
||||
## 16. Wiederverwendung in der Trainingsplanung (Phase G)
|
||||
|
||||
Die **komplexere Trainingsplanung** (Einheit, Rahmen-Slot, Abschnitt, parallele Streams) soll **keine zweite Retrieval-Welt** bauen, sondern bestehende Module mit **anderem Kontext-Pack** nutzen.
|
||||
|
||||
### 16.1 Was Progressionsgraph liefert (Workbench-Muster)
|
||||
|
||||
| Baustein | Progressionsgraph | Trainingsplanung (Ziel) |
|
||||
|----------|-------------------|-------------------------|
|
||||
| `PlanningTargetProfile` | Query + Katalog + Skills | Einheit + Abschnitt + Slot + Katalog + Historie |
|
||||
| `planning_catalog_context` | Am Graph gespeichert | Pro Einheit / Slot / Trainer-Voreinstellung |
|
||||
| `planning_skill_expectations` | `progression_stage` / `progression_path` | **`training_section`**, **`framework_slot`** |
|
||||
| `planning_exercise_retrieval` | Roadmap-Stufen-Match | Abschnitts-Suche (`suggest_planning_exercises`) — **produktiv** |
|
||||
| `planning_path_qa_pipeline` | Curriculum-QS | Abschnitts-QS (Kohärenz, Streams) |
|
||||
| `planning_intent_context` | Pfad-Ausschlüsse → Stufen | Abschnitts-Guidance → Brief |
|
||||
| `planning_exercise_form_context` | Pfad-Lücken | Abschnitts-/Slot-Lücken |
|
||||
| Roadmap-Pipeline | Curriculum Major Steps | **Nicht 1:1** — Phasen/Streams + Vorlagen |
|
||||
| Technik-Disambiguierung | bei `topic_type=technique` | nur bei explizitem Technik-Abschnitt |
|
||||
|
||||
### 16.2 Was Phase G neu braucht
|
||||
|
||||
- **Gruppen-/Historie-Kontext-Pack** (`AI_PLANNING_KI_MULTISTAGE_FORECAST` S0–S4)
|
||||
- **Abschnitts-Didaktik** — Dauer, Parallel-Streams, Coaching (`training_unit_phases`)
|
||||
- **Rahmen-Blueprint** — bereits `training_framework_programs` / Slot-Blueprints
|
||||
- **Eigene Orchestrierung** pro Einheit — kein Curriculum über N Wochen
|
||||
|
||||
### 16.3 Integrations-Reihenfolge (Phase G)
|
||||
|
||||
1. **G0** — Katalog in Einheits-Editor → bestehende Suggest-Pipeline
|
||||
2. **G1** — Scope `training_section` + Skill-Erwartungen aktiv
|
||||
3. **G2** — Abschnitts-QS (Hint-Struktur wie Graph)
|
||||
4. **G3** — Framework-Slot + Gap-Fill
|
||||
5. **G4** — Gruppenkontext-Pack
|
||||
|
||||
**Nicht:** Roadmap-first-Loop 1:1 auf Trainingseinheit mappen.
|
||||
|
||||
Domänenbezug: **`DOMAIN_MODEL.md`** §1–2 (Katalog-Dimensionen).
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -342,6 +453,10 @@ Kern: Wizard ① Ziel & Start/Ziel → ② Roadmap → ③ Match → ④ Lücken
|
|||
| `test_planning_exercise_form_context.py` | `planning_context`, Gap-Snapshot |
|
||||
| `test_progression_graph_planning_artifact.py` | JSONB-Artefakt-Validierung |
|
||||
| `test_planning_exercise_progression.py` | Graph-Auflösung, Nachfolger |
|
||||
| `test_planning_path_rematch.py` | Auto-Rematch, unfilled-Platzhalter |
|
||||
| `test_planning_path_refine_stage.py` | Stufen-Spec-Refine |
|
||||
| `test_planning_stage_anti_patterns.py` | Anti-Pattern-Sanitizer, Stufen-Gate |
|
||||
| `test_planning_catalog_context.py` | Katalog-Kontext → Target-Profil |
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -366,3 +481,4 @@ Kern: Wizard ① Ziel & Start/Ziel → ② Roadmap → ③ Match → ④ Lücken
|
|||
| Datum | Änderung |
|
||||
|-------|----------|
|
||||
| 2026-05-22 | Erstfassung Ist-Stand 0.8.217 — zentrale Referenz nach F5–F9 |
|
||||
| 2026-05-22 | F11–F14: Auto-Optimierung, Katalog-Kontext, GraphEditor, Mae-Geri-Validierung, Phase-G-Wiederverwendung §16 |
|
||||
|
|
|
|||
|
|
@ -7,6 +7,16 @@ import api from '../utils/api'
|
|||
import ExerciseAiQuickCreateModal from './exercises/ExerciseAiQuickCreateModal'
|
||||
import ExerciseGapFillPrepModal from './exercises/ExerciseGapFillPrepModal'
|
||||
import ExerciseAiSuggestPreviewModal from './ExerciseAiSuggestPreviewModal'
|
||||
import PlanningCatalogContextFields from './PlanningCatalogContextFields'
|
||||
import {
|
||||
EMPTY_PLANNING_CATALOG_CONTEXT,
|
||||
parsePlanningCatalogContextFromArtifact,
|
||||
planningCatalogContextToApi,
|
||||
pathQaQualityPercent,
|
||||
pathQaShowsStrongResult,
|
||||
setCatalogSelectItems,
|
||||
splitPathQaHints,
|
||||
} from '../utils/progressionGraphDraft'
|
||||
import {
|
||||
aiPreviewToQuickCreateDraft,
|
||||
buildQuickCreateAiPreview,
|
||||
|
|
@ -449,9 +459,13 @@ function buildPlanningRoadmapArtifactSnapshot({
|
|||
maxSteps,
|
||||
progressionRoadmap,
|
||||
pathSkillExpectations,
|
||||
planningCatalogContext,
|
||||
}) {
|
||||
const q = (goalQuery || '').trim()
|
||||
if (!q && !progressionRoadmap) return null
|
||||
const catalogPayload = planningCatalogContextToApi(
|
||||
planningCatalogContext || EMPTY_PLANNING_CATALOG_CONTEXT,
|
||||
)
|
||||
return {
|
||||
schema_version: PLANNING_ARTIFACT_SCHEMA,
|
||||
goal_query: q,
|
||||
|
|
@ -461,6 +475,9 @@ function buildPlanningRoadmapArtifactSnapshot({
|
|||
max_steps: Number(maxSteps) || 5,
|
||||
progression_roadmap: progressionRoadmap || null,
|
||||
path_skill_expectations: pathSkillExpectations || null,
|
||||
...(catalogPayload.planning_catalog_context
|
||||
? { planning_catalog_context: catalogPayload.planning_catalog_context }
|
||||
: {}),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -544,6 +561,12 @@ export default function ExerciseProgressionPathBuilder({
|
|||
const [startTargetAnalyzed, setStartTargetAnalyzed] = useState(false)
|
||||
const loading = loadingRoadmap || loadingStartTarget || loadingMatch
|
||||
const [focusAreas, setFocusAreas] = useState([])
|
||||
const [styleDirections, setStyleDirections] = useState([])
|
||||
const [trainingTypes, setTrainingTypes] = useState([])
|
||||
const [targetGroups, setTargetGroups] = useState([])
|
||||
const [planningCatalogContext, setPlanningCatalogContext] = useState(() => ({
|
||||
...EMPTY_PLANNING_CATALOG_CONTEXT,
|
||||
}))
|
||||
const [skillsCatalog, setSkillsCatalog] = useState([])
|
||||
const [generatingOfferId, setGeneratingOfferId] = useState(null)
|
||||
|
||||
|
|
@ -571,6 +594,22 @@ export default function ExerciseProgressionPathBuilder({
|
|||
[editableMajorSteps, pathSteps],
|
||||
)
|
||||
|
||||
const catalogApiPayload = useMemo(
|
||||
() => planningCatalogContextToApi(planningCatalogContext),
|
||||
[planningCatalogContext],
|
||||
)
|
||||
|
||||
const pathQaSplit = useMemo(() => splitPathQaHints(pathQa), [pathQa])
|
||||
const pathQaHighlights = pathQaSplit.highlightTexts
|
||||
const pathQaFixHints = pathQaSplit.fixHints
|
||||
|
||||
const patchCatalogDimension = useCallback((key, value) => {
|
||||
setPlanningCatalogContext((prev) => ({
|
||||
...prev,
|
||||
[key]: setCatalogSelectItems(prev?.[key], value),
|
||||
}))
|
||||
}, [])
|
||||
|
||||
const buildPlanningArtifact = useCallback(
|
||||
() =>
|
||||
buildPlanningRoadmapArtifactSnapshot({
|
||||
|
|
@ -581,6 +620,7 @@ export default function ExerciseProgressionPathBuilder({
|
|||
maxSteps,
|
||||
progressionRoadmap,
|
||||
pathSkillExpectations,
|
||||
planningCatalogContext,
|
||||
}),
|
||||
[
|
||||
goalQuery,
|
||||
|
|
@ -590,6 +630,7 @@ export default function ExerciseProgressionPathBuilder({
|
|||
maxSteps,
|
||||
progressionRoadmap,
|
||||
pathSkillExpectations,
|
||||
planningCatalogContext,
|
||||
],
|
||||
)
|
||||
|
||||
|
|
@ -634,6 +675,9 @@ export default function ExerciseProgressionPathBuilder({
|
|||
if (art.roadmap_notes) setRoadmapNotes(String(art.roadmap_notes))
|
||||
if (art.max_steps) setMaxSteps(Number(art.max_steps))
|
||||
if (art.path_skill_expectations) setPathSkillExpectations(art.path_skill_expectations)
|
||||
if (art.planning_catalog_context) {
|
||||
setPlanningCatalogContext(parsePlanningCatalogContextFromArtifact(art))
|
||||
}
|
||||
if (art.progression_roadmap) {
|
||||
setProgressionRoadmap(art.progression_roadmap)
|
||||
const majors = mapMajorStepsFromApi(art.progression_roadmap)
|
||||
|
|
@ -670,16 +714,25 @@ export default function ExerciseProgressionPathBuilder({
|
|||
let cancelled = false
|
||||
Promise.all([
|
||||
api.listFocusAreas({ status: 'active' }),
|
||||
api.listStyleDirections({ status: 'active' }),
|
||||
api.listTrainingTypes({ status: 'active' }),
|
||||
api.listTargetGroups({ status: 'active' }),
|
||||
api.listSkillsCatalog({ status: 'active' }),
|
||||
])
|
||||
.then(([fa, sk]) => {
|
||||
.then(([fa, sd, tt, tg, sk]) => {
|
||||
if (cancelled) return
|
||||
setFocusAreas(Array.isArray(fa) ? fa : [])
|
||||
setStyleDirections(Array.isArray(sd) ? sd : [])
|
||||
setTrainingTypes(Array.isArray(tt) ? tt : [])
|
||||
setTargetGroups(Array.isArray(tg) ? tg : [])
|
||||
setSkillsCatalog(Array.isArray(sk) ? sk : [])
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) {
|
||||
setFocusAreas([])
|
||||
setStyleDirections([])
|
||||
setTrainingTypes([])
|
||||
setTargetGroups([])
|
||||
setSkillsCatalog([])
|
||||
}
|
||||
})
|
||||
|
|
@ -1095,6 +1148,7 @@ export default function ExerciseProgressionPathBuilder({
|
|||
start_target_only: true,
|
||||
progression_graph_id: Number(graphId),
|
||||
...roadmapStructuredPayload(startSituation, targetState, roadmapNotes),
|
||||
...catalogApiPayload,
|
||||
})
|
||||
applyStartTargetResponse(res)
|
||||
} catch (e) {
|
||||
|
|
@ -1133,6 +1187,7 @@ export default function ExerciseProgressionPathBuilder({
|
|||
roadmap_only: true,
|
||||
progression_graph_id: Number(graphId),
|
||||
...roadmapStructuredPayload(startSituation, targetState, roadmapNotes),
|
||||
...catalogApiPayload,
|
||||
})
|
||||
const majors = mapMajorStepsFromApi(res?.progression_roadmap)
|
||||
if (majors.length < 2) {
|
||||
|
|
@ -1204,6 +1259,7 @@ export default function ExerciseProgressionPathBuilder({
|
|||
roadmap_override: override,
|
||||
progression_graph_id: Number(graphId),
|
||||
...roadmapStructuredPayload(startSituation, targetState, roadmapNotes),
|
||||
...catalogApiPayload,
|
||||
})
|
||||
applyPathMatchResponse(res, q)
|
||||
setMaxSteps(validSteps.length)
|
||||
|
|
@ -1406,6 +1462,16 @@ export default function ExerciseProgressionPathBuilder({
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<PlanningCatalogContextFields
|
||||
catalogCtx={planningCatalogContext}
|
||||
onPatchDimension={patchCatalogDimension}
|
||||
focusAreas={focusAreas}
|
||||
styleDirections={styleDirections}
|
||||
trainingTypes={trainingTypes}
|
||||
targetGroups={targetGroups}
|
||||
disabled={disabled || loading || saving}
|
||||
helperText="Planungskontext steuert Bibliotheks-Matching — wird mit dem Graph gespeichert."
|
||||
/>
|
||||
<p style={{ fontSize: '11px', color: 'var(--text3)', margin: '6px 0 0', lineHeight: 1.4 }}>
|
||||
Optional zuerst „Start/Ziel analysieren“, anpassen, dann Roadmap-Stufen. Sind Start und Ziel leer,
|
||||
geschieht die Analyse beim Roadmap-Vorschlag automatisch mit. Manuelle Eingaben haben immer Vorrang.
|
||||
|
|
@ -1826,11 +1892,40 @@ export default function ExerciseProgressionPathBuilder({
|
|||
>
|
||||
<strong>
|
||||
Pfad-QS: {pathQa.overall_ok ? 'OK' : 'Hinweise'}
|
||||
{pathQa.quality_score != null ? ` (${Math.round(Number(pathQa.quality_score) * 100)} %)` : ''}
|
||||
{pathQaQualityPercent(pathQa) != null ? ` (${pathQaQualityPercent(pathQa)} %)` : ''}
|
||||
</strong>
|
||||
{pathQaShowsStrongResult(pathQa) ? (
|
||||
<p style={{ margin: '6px 0 0', color: 'var(--accent-dark)', fontWeight: 600 }}>
|
||||
Starker Pfad — KI-Highlights können Feinschliff oder optionale Vertiefung sein.
|
||||
</p>
|
||||
) : null}
|
||||
{pathQa.topic_coverage ? (
|
||||
<p style={{ margin: '6px 0 0', color: 'var(--text2)' }}>{pathQa.topic_coverage}</p>
|
||||
) : null}
|
||||
{pathQaHighlights.length > 0 ? (
|
||||
<>
|
||||
<p style={{ margin: '8px 0 4px', fontWeight: 600, color: 'var(--accent-dark)' }}>
|
||||
KI-Highlights ({pathQaHighlights.length})
|
||||
</p>
|
||||
<ul style={{ margin: 0, padding: 0, listStyle: 'none', display: 'flex', flexDirection: 'column', gap: '6px' }}>
|
||||
{pathQaHighlights.map((item, i) => (
|
||||
<li
|
||||
key={`hl-${i}-${item.text.slice(0, 24)}`}
|
||||
style={{
|
||||
padding: '8px 10px',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid color-mix(in srgb, var(--accent) 35%, var(--border))',
|
||||
background: 'color-mix(in srgb, var(--accent) 10%, var(--surface2))',
|
||||
fontSize: '11px',
|
||||
color: 'var(--text1)',
|
||||
}}
|
||||
>
|
||||
{item.text}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
) : null}
|
||||
{Array.isArray(pathQa.issues) && pathQa.issues.length > 0 ? (
|
||||
<ul style={{ margin: '6px 0 0', paddingLeft: '16px', color: 'var(--text2)' }}>
|
||||
{pathQa.issues.slice(0, 4).map((issue) => (
|
||||
|
|
@ -1838,6 +1933,21 @@ export default function ExerciseProgressionPathBuilder({
|
|||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
{pathQaFixHints.length > 0 ? (
|
||||
<>
|
||||
<p style={{ margin: '8px 0 4px', fontWeight: 600, color: 'var(--text2)' }}>
|
||||
Handlungsbedarf ({pathQaFixHints.length})
|
||||
</p>
|
||||
<ul style={{ margin: 0, paddingLeft: '16px', color: 'var(--text2)' }}>
|
||||
{pathQaFixHints.slice(0, 6).map((hint, i) => (
|
||||
<li key={`fix-${i}-${hint.issue}-${hint.step_index ?? 'x'}`}>
|
||||
{hint.title ? `${hint.title}: ` : ''}
|
||||
{hint.reason || hint.issue}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
) : null}
|
||||
{Number(pathQa.bridge_insert_count) > 0 ? (
|
||||
<p style={{ margin: '6px 0 0', color: 'var(--accent-dark)' }}>
|
||||
{pathQa.bridge_insert_count} Brücken-Übung(en) aus der Bibliothek eingefügt.
|
||||
|
|
|
|||
99
frontend/src/components/PlanningCatalogContextFields.jsx
Normal file
99
frontend/src/components/PlanningCatalogContextFields.jsx
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* Planungskontext — Katalog-Dimensionen für Progressionsgraph-Matching.
|
||||
*/
|
||||
import React from 'react'
|
||||
import { getCatalogSelectId } from '../utils/progressionGraphDraft'
|
||||
|
||||
export default function PlanningCatalogContextFields({
|
||||
catalogCtx,
|
||||
onPatchDimension,
|
||||
focusAreas = [],
|
||||
styleDirections = [],
|
||||
trainingTypes = [],
|
||||
targetGroups = [],
|
||||
disabled = false,
|
||||
helperText = 'Planungskontext steuert Bibliotheks-Matching (Fokusbereich, Stil, Trainingsstil, Zielgruppe) — unabhängig von Technik-Pfaden.',
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))',
|
||||
gap: '10px',
|
||||
marginTop: '10px',
|
||||
}}
|
||||
>
|
||||
<div className="form-row" style={{ marginBottom: 0 }}>
|
||||
<label className="form-label">Primärfokus</label>
|
||||
<select
|
||||
className="form-input"
|
||||
disabled={disabled}
|
||||
value={getCatalogSelectId(catalogCtx?.focusAreas)}
|
||||
onChange={(e) => onPatchDimension('focusAreas', e.target.value)}
|
||||
>
|
||||
<option value="">— optional —</option>
|
||||
{(focusAreas || []).map((fa) => (
|
||||
<option key={fa.id} value={String(fa.id)}>
|
||||
{fa.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-row" style={{ marginBottom: 0 }}>
|
||||
<label className="form-label">Stilrichtung</label>
|
||||
<select
|
||||
className="form-input"
|
||||
disabled={disabled}
|
||||
value={getCatalogSelectId(catalogCtx?.styleDirections)}
|
||||
onChange={(e) => onPatchDimension('styleDirections', e.target.value)}
|
||||
>
|
||||
<option value="">— optional —</option>
|
||||
{(styleDirections || []).map((row) => (
|
||||
<option key={row.id} value={String(row.id)}>
|
||||
{row.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-row" style={{ marginBottom: 0 }}>
|
||||
<label className="form-label">Trainingsstil</label>
|
||||
<select
|
||||
className="form-input"
|
||||
disabled={disabled}
|
||||
value={getCatalogSelectId(catalogCtx?.trainingTypes)}
|
||||
onChange={(e) => onPatchDimension('trainingTypes', e.target.value)}
|
||||
>
|
||||
<option value="">— optional —</option>
|
||||
{(trainingTypes || []).map((row) => (
|
||||
<option key={row.id} value={String(row.id)}>
|
||||
{row.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-row" style={{ marginBottom: 0 }}>
|
||||
<label className="form-label">Zielgruppe</label>
|
||||
<select
|
||||
className="form-input"
|
||||
disabled={disabled}
|
||||
value={getCatalogSelectId(catalogCtx?.targetGroups)}
|
||||
onChange={(e) => onPatchDimension('targetGroups', e.target.value)}
|
||||
>
|
||||
<option value="">— optional —</option>
|
||||
{(targetGroups || []).map((row) => (
|
||||
<option key={row.id} value={String(row.id)}>
|
||||
{row.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{helperText ? (
|
||||
<p style={{ fontSize: '11px', color: 'var(--text3)', margin: '8px 0 0', lineHeight: 1.4 }}>
|
||||
{helperText}
|
||||
</p>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -10,8 +10,11 @@ import {
|
|||
formatRematchLogEntry,
|
||||
formatRefineLogEntry,
|
||||
hasRematchSlotHints,
|
||||
pathQaQualityPercent,
|
||||
pathQaShowsStrongResult,
|
||||
resolveHintSlotIndex,
|
||||
resolveOfferSlotIndex,
|
||||
splitPathQaHints,
|
||||
} from '../utils/progressionGraphDraft'
|
||||
|
||||
function severityStyle(pathQa) {
|
||||
|
|
@ -164,10 +167,15 @@ export default function ProgressionFindingsPanel({
|
|||
aiBusy = false,
|
||||
evaluateDisabled = false,
|
||||
}) {
|
||||
const optimizationHints = Array.isArray(pathQa?.optimization_hints) ? pathQa.optimization_hints : []
|
||||
const { fixHints: optimizationHints, highlightTexts } = useMemo(
|
||||
() => splitPathQaHints(pathQa),
|
||||
[pathQa],
|
||||
)
|
||||
const rematchLog = Array.isArray(pathQa?.rematch_log) ? pathQa.rematch_log : []
|
||||
const refineLog = Array.isArray(pathQa?.refine_log) ? pathQa.refine_log : []
|
||||
const showRematchAction = hasRematchSlotHints(pathQa) && typeof onRematchSlots === 'function'
|
||||
const qualityPct = pathQaQualityPercent(pathQa)
|
||||
const strongResult = pathQaShowsStrongResult(pathQa)
|
||||
|
||||
return (
|
||||
<div className="card" style={{ position: 'sticky', top: '12px' }}>
|
||||
|
|
@ -204,13 +212,50 @@ export default function ProgressionFindingsPanel({
|
|||
>
|
||||
<strong>
|
||||
Pfad-QS: {pathQa.overall_ok ? 'OK' : 'Hinweise'}
|
||||
{pathQa.quality_score != null
|
||||
? ` (${Math.round(Number(pathQa.quality_score) * 100)} %)`
|
||||
: ''}
|
||||
{qualityPct != null ? ` (${qualityPct} %)` : ''}
|
||||
</strong>
|
||||
{strongResult ? (
|
||||
<p style={{ margin: '6px 0 0', color: 'var(--accent-dark)', fontWeight: 600 }}>
|
||||
Starker Pfad — KI-Empfehlungen unten können Feinschliff oder optionale Vertiefung sein.
|
||||
</p>
|
||||
) : null}
|
||||
{pathQa.topic_coverage ? (
|
||||
<p style={{ margin: '6px 0 0', color: 'var(--text2)' }}>{pathQa.topic_coverage}</p>
|
||||
) : null}
|
||||
{highlightTexts.length > 0 ? (
|
||||
<>
|
||||
<p style={{ margin: '8px 0 4px', fontWeight: 600, color: 'var(--accent-dark)' }}>
|
||||
KI-Highlights ({highlightTexts.length})
|
||||
</p>
|
||||
<ul
|
||||
style={{
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
listStyle: 'none',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '6px',
|
||||
}}
|
||||
>
|
||||
{highlightTexts.map((item, i) => (
|
||||
<li
|
||||
key={`hl-${i}-${item.text.slice(0, 24)}`}
|
||||
style={{
|
||||
padding: '8px 10px',
|
||||
borderRadius: '6px',
|
||||
border: '1px solid color-mix(in srgb, var(--accent) 35%, var(--border))',
|
||||
background: 'color-mix(in srgb, var(--accent) 10%, var(--surface2))',
|
||||
fontSize: '11px',
|
||||
color: 'var(--text1)',
|
||||
lineHeight: 1.45,
|
||||
}}
|
||||
>
|
||||
{item.text}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
) : null}
|
||||
{Array.isArray(pathQa.issues) && pathQa.issues.length > 0 ? (
|
||||
<ul style={{ margin: '6px 0 0', paddingLeft: '16px', color: 'var(--text2)' }}>
|
||||
{pathQa.issues.map((issue) => (
|
||||
|
|
@ -218,7 +263,9 @@ export default function ProgressionFindingsPanel({
|
|||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
{Array.isArray(pathQa.recommendations) && pathQa.recommendations.length > 0 ? (
|
||||
{Array.isArray(pathQa.recommendations) &&
|
||||
pathQa.recommendations.length > 0 &&
|
||||
highlightTexts.length === 0 ? (
|
||||
<>
|
||||
<p style={{ margin: '8px 0 4px', fontWeight: 600, color: 'var(--text2)' }}>Empfehlungen</p>
|
||||
<ul style={{ margin: 0, paddingLeft: '16px', color: 'var(--text2)' }}>
|
||||
|
|
@ -265,7 +312,7 @@ export default function ProgressionFindingsPanel({
|
|||
{optimizationHints.length > 0 ? (
|
||||
<>
|
||||
<p style={{ margin: '8px 0 4px', fontWeight: 600, color: 'var(--text2)' }}>
|
||||
Optimierungspotenziale ({optimizationHints.length})
|
||||
Handlungsbedarf ({optimizationHints.length})
|
||||
</p>
|
||||
<ul
|
||||
style={{
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import ExercisePickerModal from './ExercisePickerModal'
|
|||
import ExerciseGapFillPrepModal from './exercises/ExerciseGapFillPrepModal'
|
||||
import ProgressionSlotCard from './ProgressionSlotCard'
|
||||
import ProgressionFindingsPanel from './ProgressionFindingsPanel'
|
||||
import PlanningCatalogContextFields from './PlanningCatalogContextFields'
|
||||
import {
|
||||
aiPreviewToQuickCreateDraft,
|
||||
buildQuickCreateAiPreview,
|
||||
|
|
@ -46,7 +47,6 @@ import {
|
|||
syncProgressionRoadmapFromSlots,
|
||||
syncSlotPhasesFromRoadmap,
|
||||
EMPTY_PLANNING_CATALOG_CONTEXT,
|
||||
getCatalogSelectId,
|
||||
planningCatalogContextToApi,
|
||||
setCatalogSelectItems,
|
||||
} from '../utils/progressionGraphDraft'
|
||||
|
|
@ -909,83 +909,16 @@ export default function ProgressionGraphEditor({ graphId, embedded = false, onSa
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))',
|
||||
gap: '10px',
|
||||
marginTop: '10px',
|
||||
}}
|
||||
>
|
||||
<div className="form-row" style={{ marginBottom: 0 }}>
|
||||
<label className="form-label">Primärfokus</label>
|
||||
<select
|
||||
className="form-input"
|
||||
disabled={busy}
|
||||
value={getCatalogSelectId(catalogCtx.focusAreas)}
|
||||
onChange={(e) => patchCatalogDimension('focusAreas', e.target.value)}
|
||||
>
|
||||
<option value="">— optional —</option>
|
||||
{(focusAreas || []).map((fa) => (
|
||||
<option key={fa.id} value={String(fa.id)}>
|
||||
{fa.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-row" style={{ marginBottom: 0 }}>
|
||||
<label className="form-label">Stilrichtung</label>
|
||||
<select
|
||||
className="form-input"
|
||||
disabled={busy}
|
||||
value={getCatalogSelectId(catalogCtx.styleDirections)}
|
||||
onChange={(e) => patchCatalogDimension('styleDirections', e.target.value)}
|
||||
>
|
||||
<option value="">— optional —</option>
|
||||
{(styleDirections || []).map((row) => (
|
||||
<option key={row.id} value={String(row.id)}>
|
||||
{row.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-row" style={{ marginBottom: 0 }}>
|
||||
<label className="form-label">Trainingsstil</label>
|
||||
<select
|
||||
className="form-input"
|
||||
disabled={busy}
|
||||
value={getCatalogSelectId(catalogCtx.trainingTypes)}
|
||||
onChange={(e) => patchCatalogDimension('trainingTypes', e.target.value)}
|
||||
>
|
||||
<option value="">— optional —</option>
|
||||
{(trainingTypes || []).map((row) => (
|
||||
<option key={row.id} value={String(row.id)}>
|
||||
{row.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="form-row" style={{ marginBottom: 0 }}>
|
||||
<label className="form-label">Zielgruppe</label>
|
||||
<select
|
||||
className="form-input"
|
||||
disabled={busy}
|
||||
value={getCatalogSelectId(catalogCtx.targetGroups)}
|
||||
onChange={(e) => patchCatalogDimension('targetGroups', e.target.value)}
|
||||
>
|
||||
<option value="">— optional —</option>
|
||||
{(targetGroups || []).map((row) => (
|
||||
<option key={row.id} value={String(row.id)}>
|
||||
{row.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p style={{ fontSize: '11px', color: 'var(--text3)', margin: '8px 0 0', lineHeight: 1.4 }}>
|
||||
Planungskontext steuert Bibliotheks-Matching (Fokusbereich, Stil, Trainingsstil, Zielgruppe) — unabhängig
|
||||
von Technik-Pfaden wie Mae Geri. Wird mit dem Graph gespeichert.
|
||||
</p>
|
||||
<PlanningCatalogContextFields
|
||||
catalogCtx={catalogCtx}
|
||||
onPatchDimension={patchCatalogDimension}
|
||||
focusAreas={focusAreas}
|
||||
styleDirections={styleDirections}
|
||||
trainingTypes={trainingTypes}
|
||||
targetGroups={targetGroups}
|
||||
disabled={busy}
|
||||
helperText="Planungskontext steuert Bibliotheks-Matching (Fokusbereich, Stil, Trainingsstil, Zielgruppe) — unabhängig von Technik-Pfaden wie Mae Geri. Wird mit dem Graph gespeichert."
|
||||
/>
|
||||
<p style={{ fontSize: '11px', color: 'var(--text3)', margin: '8px 0 0', lineHeight: 1.4 }}>
|
||||
Optional zuerst „Start/Ziel analysieren“, anpassen, dann Roadmap-Stufen. Sind Start und Ziel leer,
|
||||
geschieht die Analyse beim Roadmap-Vorschlag automatisch. Manuelle Eingaben haben Vorrang.
|
||||
|
|
|
|||
|
|
@ -141,6 +141,44 @@ export function optimizationHintActionLabel(action) {
|
|||
return OPTIMIZATION_ACTION_LABELS[action] || action || 'Hinweis'
|
||||
}
|
||||
|
||||
/** LLM-Empfehlungen von technischen Fix-Hinweisen trennen (QS-UI). */
|
||||
export function splitPathQaHints(pathQa) {
|
||||
const hints = Array.isArray(pathQa?.optimization_hints) ? pathQa.optimization_hints : []
|
||||
const fixHints = hints.filter((h) => String(h?.issue || '') !== 'llm_recommendation')
|
||||
const highlightHints = hints.filter((h) => String(h?.issue || '') === 'llm_recommendation')
|
||||
const recommendations = Array.isArray(pathQa?.recommendations) ? pathQa.recommendations : []
|
||||
const highlightTexts = []
|
||||
const seen = new Set()
|
||||
for (const rec of recommendations) {
|
||||
const text = String(rec || '').trim()
|
||||
const key = text.toLowerCase()
|
||||
if (text && !seen.has(key)) {
|
||||
seen.add(key)
|
||||
highlightTexts.push({ text, source: 'recommendation' })
|
||||
}
|
||||
}
|
||||
for (const hint of highlightHints) {
|
||||
const text = String(hint.reason || hint.title || '').trim()
|
||||
const key = text.toLowerCase()
|
||||
if (text && !seen.has(key)) {
|
||||
seen.add(key)
|
||||
highlightTexts.push({ text, source: 'hint', hint })
|
||||
}
|
||||
}
|
||||
return { fixHints, highlightTexts }
|
||||
}
|
||||
|
||||
export function pathQaQualityPercent(pathQa) {
|
||||
if (pathQa?.quality_score == null || !Number.isFinite(Number(pathQa.quality_score))) return null
|
||||
return Math.round(Number(pathQa.quality_score) * 100)
|
||||
}
|
||||
|
||||
export function pathQaShowsStrongResult(pathQa) {
|
||||
const pct = pathQaQualityPercent(pathQa)
|
||||
if (pathQa?.overall_ok && pct != null && pct >= 85) return true
|
||||
return Boolean(pathQa?.overall_ok && pct != null && pct >= 80 && !(pathQa?.issues || []).length)
|
||||
}
|
||||
|
||||
/** Slot-Index aus optimization_hint (roadmap_major_step_index oder step_index). */
|
||||
export function resolveHintSlotIndex(hint, draft = null) {
|
||||
if (!hint || typeof hint !== 'object') return null
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user