All checks were successful
Deploy Development / deploy (push) Successful in 41s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 34s
Test Suite / playwright-tests (push) Successful in 1m16s
- Introduced `planning_catalog_context` to `ProgressionPathSuggestRequest` for improved handling of catalog-related data during path suggestions. - Implemented `_resolve_planning_catalog_context` to retrieve and validate the planning catalog context, enhancing the robustness of the suggestion process. - Updated `_build_path_target_profile` to incorporate catalog context, enriching target profiles with relevant planning data. - Enhanced frontend components in `ProgressionGraphEditor` to manage and display planning catalog context, including new selection options for focus areas, style directions, training types, and target groups. - Added utility functions for parsing and transforming planning catalog context data for API interactions. - Bumped version to 0.8.233 to reflect the new features and improvements.
148 lines
4.8 KiB
Python
148 lines
4.8 KiB
Python
"""
|
|
Katalog-Kontext für Progressionsgraph-Planung — Fokusbereich, Stil, Trainingsstil, Zielgruppe.
|
|
|
|
Explizite Trainer-Auswahl ergänzt Freitext/LLM; ersetzt kein Roadmap-Didaktik-Modell.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import Any, Dict, List, Mapping, Optional, Sequence
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from planning_exercise_profiles import PlanningTargetProfile, _normalize_weight_map
|
|
from planning_exercise_target_pipeline import (
|
|
SCENARIO_FREE_SEARCH,
|
|
merge_query_overlay_into_target,
|
|
)
|
|
from planning_exercise_text_signals import resolve_planning_text_to_catalog_weights
|
|
|
|
|
|
class PlanningCatalogContextItem(BaseModel):
|
|
id: int = Field(..., ge=1)
|
|
is_primary: bool = False
|
|
weight: float = Field(default=1.0, ge=0.1, le=1.0)
|
|
|
|
|
|
class ProgressionPlanningCatalogContext(BaseModel):
|
|
focus_areas: List[PlanningCatalogContextItem] = Field(default_factory=list)
|
|
style_directions: List[PlanningCatalogContextItem] = Field(default_factory=list)
|
|
training_types: List[PlanningCatalogContextItem] = Field(default_factory=list)
|
|
target_groups: List[PlanningCatalogContextItem] = Field(default_factory=list)
|
|
|
|
|
|
def catalog_context_has_items(catalog: Optional[ProgressionPlanningCatalogContext]) -> bool:
|
|
if catalog is None:
|
|
return False
|
|
return bool(
|
|
catalog.focus_areas
|
|
or catalog.style_directions
|
|
or catalog.training_types
|
|
or catalog.target_groups
|
|
)
|
|
|
|
|
|
def catalog_items_to_weight_map(
|
|
items: Sequence[PlanningCatalogContextItem],
|
|
*,
|
|
primary_weight: float = 0.95,
|
|
secondary_weight: float = 0.78,
|
|
) -> Dict[int, float]:
|
|
out: Dict[int, float] = {}
|
|
for item in items or []:
|
|
base = primary_weight if item.is_primary else secondary_weight
|
|
w = base * float(item.weight)
|
|
iid = int(item.id)
|
|
out[iid] = max(out.get(iid, 0.0), w)
|
|
return _normalize_weight_map(out) if out else out
|
|
|
|
|
|
def merge_catalog_context_into_target(
|
|
target: PlanningTargetProfile,
|
|
catalog: Optional[ProgressionPlanningCatalogContext],
|
|
*,
|
|
emphasis: str = "replace",
|
|
) -> PlanningTargetProfile:
|
|
"""Trainer-Katalog-Kontext ins Erwartungsprofil — beeinflusst Retrieval-Scoring."""
|
|
if not catalog_context_has_items(catalog):
|
|
return target
|
|
|
|
focus = catalog_items_to_weight_map(catalog.focus_areas)
|
|
style = catalog_items_to_weight_map(catalog.style_directions, primary_weight=0.9, secondary_weight=0.72)
|
|
tt = catalog_items_to_weight_map(catalog.training_types, primary_weight=0.9, secondary_weight=0.72)
|
|
tg = catalog_items_to_weight_map(catalog.target_groups, primary_weight=0.88, secondary_weight=0.7)
|
|
|
|
merged = merge_query_overlay_into_target(
|
|
target,
|
|
focus=focus,
|
|
style=style,
|
|
tt=tt,
|
|
tg=tg,
|
|
skills={},
|
|
emphasis=emphasis,
|
|
scenario=SCENARIO_FREE_SEARCH,
|
|
)
|
|
sources = list(merged.sources or [])
|
|
if "catalog_context" not in sources:
|
|
sources.append("catalog_context")
|
|
merged.sources = sources
|
|
return merged
|
|
|
|
|
|
def enrich_target_from_planning_text_blobs(
|
|
cur,
|
|
target: PlanningTargetProfile,
|
|
*text_blobs: Optional[str],
|
|
) -> PlanningTargetProfile:
|
|
"""Additive Katalog-Signale aus Freitext (Anfrage, Start/Ziel, Notizen)."""
|
|
combined = " ".join(str(t or "").strip() for t in text_blobs if (t or "").strip())
|
|
if len(combined) < 4:
|
|
return target
|
|
focus, style, tt, tg, skills = resolve_planning_text_to_catalog_weights(cur, combined)
|
|
if not (focus or style or tt or tg or skills):
|
|
return target
|
|
merged = merge_query_overlay_into_target(
|
|
target,
|
|
focus=focus,
|
|
style=style,
|
|
tt=tt,
|
|
tg=tg,
|
|
skills=skills,
|
|
emphasis="additive",
|
|
scenario=SCENARIO_FREE_SEARCH,
|
|
)
|
|
sources = list(merged.sources or [])
|
|
if "text_catalog_signals" not in sources:
|
|
sources.append("text_catalog_signals")
|
|
merged.sources = sources
|
|
return merged
|
|
|
|
|
|
def catalog_context_from_mapping(raw: Any) -> Optional[ProgressionPlanningCatalogContext]:
|
|
if not raw or not isinstance(raw, Mapping):
|
|
return None
|
|
try:
|
|
ctx = ProgressionPlanningCatalogContext.model_validate(dict(raw))
|
|
except Exception:
|
|
return None
|
|
return ctx if catalog_context_has_items(ctx) else None
|
|
|
|
|
|
def load_catalog_context_from_graph_row(
|
|
planning_roadmap: Any,
|
|
) -> Optional[ProgressionPlanningCatalogContext]:
|
|
if not isinstance(planning_roadmap, dict):
|
|
return None
|
|
return catalog_context_from_mapping(planning_roadmap.get("planning_catalog_context"))
|
|
|
|
|
|
__all__ = [
|
|
"PlanningCatalogContextItem",
|
|
"ProgressionPlanningCatalogContext",
|
|
"catalog_context_from_mapping",
|
|
"catalog_context_has_items",
|
|
"catalog_items_to_weight_map",
|
|
"enrich_target_from_planning_text_blobs",
|
|
"load_catalog_context_from_graph_row",
|
|
"merge_catalog_context_into_target",
|
|
]
|