"""Knowledge graph API endpoints.""" import logging from fastapi import APIRouter, Depends, HTTPException, Query from fastapi.responses import JSONResponse from app.core.exceptions import EntityNotFoundError, DatasetError from app.schemas.knowledge_graph import KnowledgeGraphResponse from app.services.knowledge_graph_service import KnowledgeGraphService from app.api.v2.dependencies import get_knowledge_graph_service logger = logging.getLogger(__name__) router = APIRouter(prefix="/ai/v2/knowledge-graph", tags=["knowledge-graph"]) # NOTE: Static-segment routes MUST be declared before dynamic /{lo_id} routes # so FastAPI doesn't swallow "path" as a lo_id value. @router.get( "/path/{start_lo}/{target_lo}", summary="Get shortest path between two learning outcomes", description="Find the shortest dependency path between two learning outcomes in the knowledge graph" ) async def get_learning_path_between_los( start_lo: str, target_lo: str, knowledge_graph_service: KnowledgeGraphService = Depends(get_knowledge_graph_service) ) -> JSONResponse: """Get learning path between two learning outcomes.""" try: path = knowledge_graph_service.get_learning_path(start_lo, target_lo) return JSONResponse(content={ "start_lo": start_lo, "target_lo": target_lo, "path": path, "steps": len(path) }) except EntityNotFoundError as exc: logger.warning("Learning path query failed - entity not found: %s", exc) raise HTTPException(status_code=404, detail=str(exc)) from exc except Exception as exc: logger.error("Learning path query failed: %s", exc) raise HTTPException(status_code=500, detail="Internal server error") from exc @router.get( "/{lo_id}/prerequisites", summary="Get prerequisites for a learning outcome", description="Get all prerequisite learning outcomes for a given LO" ) async def get_prerequisites( lo_id: str, max_depth: int = Query(default=None, ge=1, le=10, description="Maximum depth to traverse"), knowledge_graph_service: KnowledgeGraphService = Depends(get_knowledge_graph_service) ) -> JSONResponse: """Get prerequisites for a learning outcome.""" try: prerequisites = knowledge_graph_service.get_prerequisites(lo_id, max_depth) return JSONResponse(content={ "lo_id": lo_id, "prerequisites": prerequisites, "count": len(prerequisites) }) except EntityNotFoundError as exc: logger.warning("Prerequisites query failed - entity not found: %s", exc) raise HTTPException(status_code=404, detail=str(exc)) from exc except Exception as exc: logger.error("Prerequisites query failed: %s", exc) raise HTTPException(status_code=500, detail="Internal server error") from exc @router.get( "/{lo_id}/successors", summary="Get successors for a learning outcome", description="Get all successor learning outcomes for a given LO" ) async def get_successors( lo_id: str, max_depth: int = Query(default=None, ge=1, le=10, description="Maximum depth to traverse"), knowledge_graph_service: KnowledgeGraphService = Depends(get_knowledge_graph_service) ) -> JSONResponse: """Get successors for a learning outcome.""" try: successors = knowledge_graph_service.get_successors(lo_id, max_depth) return JSONResponse(content={ "lo_id": lo_id, "successors": successors, "count": len(successors) }) except EntityNotFoundError as exc: logger.warning("Successors query failed - entity not found: %s", exc) raise HTTPException(status_code=404, detail=str(exc)) from exc except Exception as exc: logger.error("Successors query failed: %s", exc) raise HTTPException(status_code=500, detail="Internal server error") from exc @router.get( "/{lo_id}", response_model=KnowledgeGraphResponse, summary="Get knowledge graph for a learning outcome", description="Retrieve full knowledge graph info including prerequisites and successors for a learning outcome" ) async def get_knowledge_graph( lo_id: str, max_depth: int = Query(default=2, ge=1, le=5, description="Maximum depth to traverse"), knowledge_graph_service: KnowledgeGraphService = Depends(get_knowledge_graph_service) ) -> KnowledgeGraphResponse: """Get knowledge graph information for a learning outcome.""" try: return knowledge_graph_service.get_knowledge_graph(lo_id, max_depth) except EntityNotFoundError as exc: logger.warning("Knowledge graph query failed - entity not found: %s", exc) raise HTTPException(status_code=404, detail=str(exc)) from exc except DatasetError as exc: logger.error("Knowledge graph query failed - dataset error: %s", exc) raise HTTPException(status_code=503, detail="Knowledge graph service unavailable") from exc except Exception as exc: logger.error("Knowledge graph query failed - unexpected error: %s", exc) raise HTTPException(status_code=500, detail="Internal server error") from exc