Initial commit
This commit is contained in:
144
backend/app/main.py
Normal file
144
backend/app/main.py
Normal file
@@ -0,0 +1,144 @@
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import Depends, FastAPI, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from sqlalchemy.orm import Session
|
||||
from .database import Base, engine, get_db
|
||||
from . import models, schemas
|
||||
from .curriculum import QUESTIONS
|
||||
from .services import build_llm_reply, ensure_student_mastery, evaluate_answer, pick_next_skill, seed_skills
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
Base.metadata.create_all(bind=engine)
|
||||
db = next(get_db())
|
||||
try:
|
||||
seed_skills(db)
|
||||
finally:
|
||||
db.close()
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(title="Professeur Virtuel API", version="0.1.0", lifespan=lifespan)
|
||||
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=[
|
||||
"https://prof.open-squared.tech",
|
||||
"http://localhost:3000",
|
||||
"http://localhost:3001",
|
||||
],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
@app.get("/health")
|
||||
def health():
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@app.get("/students", response_model=list[schemas.StudentRead])
|
||||
def list_students(db: Session = Depends(get_db)):
|
||||
return db.query(models.Student).order_by(models.Student.id.asc()).all()
|
||||
|
||||
|
||||
@app.post("/students", response_model=schemas.StudentRead)
|
||||
def create_student(payload: schemas.StudentCreate, db: Session = Depends(get_db)):
|
||||
student = models.Student(**payload.model_dump())
|
||||
db.add(student)
|
||||
db.commit()
|
||||
db.refresh(student)
|
||||
ensure_student_mastery(db, student)
|
||||
return student
|
||||
|
||||
|
||||
@app.post("/session/start", response_model=schemas.ChatResponse)
|
||||
def start_session(student_id: int, db: Session = Depends(get_db)):
|
||||
student = db.query(models.Student).filter_by(id=student_id).first()
|
||||
if not student:
|
||||
raise HTTPException(status_code=404, detail="Élève introuvable")
|
||||
|
||||
ensure_student_mastery(db, student)
|
||||
message = (
|
||||
f"Bonjour {student.first_name} ! Je suis ton professeur virtuel. "
|
||||
"Aujourd'hui, on va apprendre pas à pas et faire un petit test pour voir ce que tu maîtrises déjà."
|
||||
)
|
||||
db.add(models.Message(student_id=student.id, role="assistant", content=message))
|
||||
db.commit()
|
||||
return schemas.ChatResponse(reply=message)
|
||||
|
||||
|
||||
@app.post("/chat", response_model=schemas.ChatResponse)
|
||||
def chat(payload: schemas.ChatRequest, db: Session = Depends(get_db)):
|
||||
student = db.query(models.Student).filter_by(id=payload.student_id).first()
|
||||
if not student:
|
||||
raise HTTPException(status_code=404, detail="Élève introuvable")
|
||||
|
||||
db.add(models.Message(student_id=student.id, role="user", content=payload.message))
|
||||
db.commit()
|
||||
|
||||
reply = build_llm_reply(db, payload.student_id, payload.message)
|
||||
|
||||
db.add(models.Message(student_id=student.id, role="assistant", content=reply))
|
||||
db.commit()
|
||||
return schemas.ChatResponse(reply=reply)
|
||||
|
||||
|
||||
@app.get("/progress/{student_id}", response_model=schemas.ProgressResponse)
|
||||
def get_progress(student_id: int, db: Session = Depends(get_db)):
|
||||
student = db.query(models.Student).filter_by(id=student_id).first()
|
||||
if not student:
|
||||
raise HTTPException(status_code=404, detail="Élève introuvable")
|
||||
|
||||
rows = (
|
||||
db.query(models.StudentSkillMastery, models.Skill)
|
||||
.join(models.Skill, models.Skill.id == models.StudentSkillMastery.skill_id)
|
||||
.filter(models.StudentSkillMastery.student_id == student_id)
|
||||
.order_by(models.Skill.subject.asc(), models.Skill.label.asc())
|
||||
.all()
|
||||
)
|
||||
progress = [
|
||||
schemas.SkillProgress(
|
||||
code=skill.code,
|
||||
subject=skill.subject,
|
||||
label=skill.label,
|
||||
mastery_score=mastery.mastery_score,
|
||||
confidence=mastery.confidence,
|
||||
evidence_count=mastery.evidence_count,
|
||||
)
|
||||
for mastery, skill in rows
|
||||
]
|
||||
return schemas.ProgressResponse(student=student, progress=progress)
|
||||
|
||||
|
||||
@app.get("/assessment/next/{student_id}", response_model=schemas.AssessmentQuestionResponse)
|
||||
def next_assessment(student_id: int, db: Session = Depends(get_db)):
|
||||
student = db.query(models.Student).filter_by(id=student_id).first()
|
||||
if not student:
|
||||
raise HTTPException(status_code=404, detail="Élève introuvable")
|
||||
skill = pick_next_skill(db, student_id)
|
||||
question = QUESTIONS[skill.code]["question"]
|
||||
return schemas.AssessmentQuestionResponse(skill_code=skill.code, skill_label=skill.label, question=question)
|
||||
|
||||
|
||||
@app.post("/assessment/answer", response_model=schemas.AssessmentAnswerResponse)
|
||||
def answer_assessment(payload: schemas.AssessmentAnswerRequest, db: Session = Depends(get_db)):
|
||||
student = db.query(models.Student).filter_by(id=payload.student_id).first()
|
||||
if not student:
|
||||
raise HTTPException(status_code=404, detail="Élève introuvable")
|
||||
if payload.skill_code not in QUESTIONS:
|
||||
raise HTTPException(status_code=400, detail="Compétence inconnue")
|
||||
|
||||
correct, feedback, mastery_score = evaluate_answer(
|
||||
db, payload.student_id, payload.skill_code, payload.answer
|
||||
)
|
||||
db.add(models.Message(student_id=student.id, role="assistant", content=feedback))
|
||||
db.commit()
|
||||
return schemas.AssessmentAnswerResponse(
|
||||
correct=correct,
|
||||
feedback=feedback,
|
||||
mastery_score=mastery_score,
|
||||
)
|
||||
Reference in New Issue
Block a user