from fastapi import APIRouter, HTTPException, Query from pydantic import BaseModel, Field from typing import List, Optional, Dict, Any from uuid import uuid4 from datetime import datetime, date from clients import model, qdrant from qdrant_client.models import PointStruct, VectorParams, Distance, PointIdsList import os router = APIRouter() # ---- Models ---- class Exercise(BaseModel): id: str = Field(default_factory=lambda: str(uuid4())) title: str summary: str short_description: str keywords: List[str] = [] link: Optional[str] = None discipline: str group: Optional[str] = None age_group: str target_group: str min_participants: int duration_minutes: int capabilities: Dict[str, int] = {} category: str purpose: str execution: str notes: str preparation: str method: str equipment: List[str] = [] class PhaseExercise(BaseModel): exercise_id: str cond_load: Dict[str, Any] = {} coord_load: Dict[str, Any] = {} instructions: str class PlanPhase(BaseModel): name: str duration_minutes: int method: str method_notes: str exercises: List[PhaseExercise] class TrainingPlan(BaseModel): id: str = Field(default_factory=lambda: str(uuid4())) title: str short_description: str collection: str discipline: str group: Optional[str] = None dojo: str date: date plan_duration_weeks: int focus_areas: List[str] = [] predecessor_plan_id: Optional[str] = None age_group: str created_at: datetime = Field(default_factory=datetime.utcnow) phases: List[PlanPhase] class DeleteResponse(BaseModel): status: str count: int collection: str source: Optional[str] = None type: Optional[str] = None # ---- CRUD Endpoints for Exercise ---- @router.post("/exercise", response_model=Exercise) def create_exercise(ex: Exercise): # Ensure Exercise collection exists if not qdrant.collection_exists("exercises"): qdrant.recreate_collection( collection_name="exercises", vectors_config=VectorParams( size=model.get_sentence_embedding_dimension(), distance=Distance.COSINE ) ) vec = model.encode(f"{ex.title}. {ex.summary}").tolist() point = PointStruct(id=ex.id, vector=vec, payload=ex.dict()) qdrant.upsert(collection_name="exercises", points=[point]) return ex @router.get("/exercise", response_model=List[Exercise]) def list_exercises( discipline: Optional[str] = Query(None), group: Optional[str] = Query(None), tags: Optional[str] = Query(None) ): filters = [] if discipline: filters.append({"key": "discipline", "match": {"value": discipline}}) if group: filters.append({"key": "group", "match": {"value": group}}) if tags: for t in tags.split(","): filters.append({"key": "keywords", "match": {"value": t.strip()}}) pts, _ = qdrant.scroll( collection_name="exercises", scroll_filter={"must": filters} if filters else None, limit=10000 ) return [Exercise(**pt.payload) for pt in pts] # ---- CRUD Endpoints for TrainingPlan ---- @router.post("/plan", response_model=TrainingPlan) def create_plan(plan: TrainingPlan): # Ensure TrainingPlan collection exists if not qdrant.collection_exists("training_plans"): qdrant.recreate_collection( collection_name="training_plans", vectors_config=VectorParams( size=model.get_sentence_embedding_dimension(), distance=Distance.COSINE ) ) vec = model.encode(f"{plan.title}. {plan.short_description}").tolist() point = PointStruct(id=plan.id, vector=vec, payload=plan.dict()) qdrant.upsert(collection_name="training_plans", points=[point]) return plan @router.get("/plan", response_model=List[TrainingPlan]) def list_plans( collection: str = Query("training_plans"), discipline: Optional[str] = Query(None), group: Optional[str] = Query(None), dojo: Optional[str] = Query(None) ): if not qdrant.collection_exists(collection): return [] pts, _ = qdrant.scroll(collection_name=collection, limit=10000) result = [] for pt in pts: pl = TrainingPlan(**pt.payload) if discipline and pl.discipline != discipline: continue if group and pl.group != group: continue if dojo and pl.dojo != dojo: continue result.append(pl) return result # ---- Delete Endpoints ---- @router.delete("/delete-source", response_model=DeleteResponse) def delete_by_source( collection: str = Query(...), source: Optional[str] = Query(None), type: Optional[str] = Query(None) ): if not qdrant.collection_exists(collection): raise HTTPException(status_code=404, detail=f"Collection '{collection}' nicht gefunden.") filt = [] if source: filt.append({"key": "source", "match": {"value": source}}) if type: filt.append({"key": "type", "match": {"value": type}}) if not filt: raise HTTPException(status_code=400, detail="Mindestens ein Filterparameter muss angegeben werden.") pts, _ = qdrant.scroll(collection_name=collection, scroll_filter={"must": filt}, limit=10000) ids = [str(p.id) for p in pts] if not ids: return DeleteResponse(status="🔍 Keine Einträge gefunden.", count=0, collection=collection) qdrant.delete(collection_name=collection, points_selector=PointIdsList(points=ids)) return DeleteResponse(status="🗑️ gelöscht", count=len(ids), collection=collection) @router.delete("/delete-collection", response_model=DeleteResponse) def delete_collection( collection: str = Query(...) ): if not qdrant.collection_exists(collection): raise HTTPException(status_code=404, detail=f"Collection '{collection}' nicht gefunden.") qdrant.delete_collection(collection_name=collection) return DeleteResponse(status="🗑️ gelöscht", count=0, collection=collection)