Files
open-school/backend/app/main.py
2026-04-05 07:35:28 +00:00

145 lines
5.2 KiB
Python

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,
)