From c08ec2a6d01bbd0c80720bc1fe09c638d2c29a09 Mon Sep 17 00:00:00 2001 From: Matee ur Rehman Date: Mon, 7 Sep 2026 12:21:31 +0500 Subject: [PATCH] Add HTTP endpoint for roadmap_generator (POST /generate-roadmap, JWT-auth'd) --- ai-ml/roadmap_generator/app/main.py | 86 +++++++++++++++++++ .../app/models/api_models.py | 31 +++++++ 2 files changed, 117 insertions(+) create mode 100644 ai-ml/roadmap_generator/app/main.py create mode 100644 ai-ml/roadmap_generator/app/models/api_models.py diff --git a/ai-ml/roadmap_generator/app/main.py b/ai-ml/roadmap_generator/app/main.py new file mode 100644 index 0000000..151225c --- /dev/null +++ b/ai-ml/roadmap_generator/app/main.py @@ -0,0 +1,86 @@ +""" +Team Lambda Roadmap Generator API — FastAPI service. + +Run from ai-ml/ (so roadmap_generator.* and quiz_generator.* +imports resolve): + uvicorn roadmap_generator.app.main:app --reload --port + +Interactive docs (once running): http://127.0.0.1:/docs +""" + +from fastapi import FastAPI, HTTPException, Depends +from fastapi.middleware.cors import CORSMiddleware + +# Reusing the JWT auth dependency already built for quiz_generator +# (Contract v1, Section 10) — same as weak_topic_detection's endpoint. +from quiz_generator.app.auth import get_current_user_id + +from roadmap_generator.app.models.api_models import ( + GenerateRoadmapRequest, + GenerateRoadmapResponse, +) +from roadmap_generator.app.services.roadmap_service import RoadmapService + +app = FastAPI(title="StudyMind Roadmap Generator API — Team Lambda") + +app.add_middleware( + CORSMiddleware, + allow_origins=["http://localhost:5173"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +_service: RoadmapService | None = None + + +def get_service() -> RoadmapService: + global _service + if _service is None: + _service = RoadmapService() + return _service + + +@app.get("/health") +def health_check(): + return {"status": "ok"} + + +@app.post("/generate-roadmap", response_model=GenerateRoadmapResponse) +def generate_roadmap_endpoint( + body: GenerateRoadmapRequest, + user_id: str = Depends(get_current_user_id), +) -> GenerateRoadmapResponse: + """ + Requires "Authorization: Bearer ". + + user_id is derived from the verified token and required for + consistency with Contract v1's auth pattern (every Lambda + endpoint requires auth), but this module is general-purpose and + doesn't read per-user content — it only uses the topic_names the + caller supplies. If a future version personalizes roadmaps using + a user's own ingested content or weak-topic results, user_id is + already available here to wire that in without another contract + change. + """ + service = get_service() + + try: + roadmap = service.generate_roadmap( + topic_names=body.topic_names, + subject=body.subject, + step_count=body.step_count, + priorities=body.priorities, + ) + except ValueError as exc: + raise HTTPException(status_code=400, detail=str(exc)) from exc + except RuntimeError as exc: + raise HTTPException(status_code=502, detail=str(exc)) from exc + + return GenerateRoadmapResponse( + success=True, + message=f"Generated {roadmap.total_steps} steps.", + subject=roadmap.subject, + steps=[step.model_dump() for step in roadmap.steps], + total_steps=roadmap.total_steps, + ) \ No newline at end of file diff --git a/ai-ml/roadmap_generator/app/models/api_models.py b/ai-ml/roadmap_generator/app/models/api_models.py new file mode 100644 index 0000000..f52087c --- /dev/null +++ b/ai-ml/roadmap_generator/app/models/api_models.py @@ -0,0 +1,31 @@ +from typing import Optional + +from pydantic import BaseModel, Field + + +class GenerateRoadmapRequest(BaseModel): + """Request body for POST /generate-roadmap.""" + + topic_names: list[str] = Field( + ..., min_length=1, description="List of topic/subject names to build a roadmap for." + ) + subject: str = Field( + default="", description="Optional overall subject/title for the roadmap." + ) + step_count: int = Field( + default=6, ge=3, le=15, description="Number of roadmap steps to generate." + ) + priorities: Optional[dict[str, str]] = Field( + default=None, + description="Optional {topic_name: 'high'|'normal'|'low'} hints to influence ordering.", + ) + + +class GenerateRoadmapResponse(BaseModel): + """Response body for POST /generate-roadmap.""" + + success: bool + message: str + subject: str = "" + steps: list[dict] = Field(default_factory=list) + total_steps: int = 0 \ No newline at end of file