Initial commit
This commit is contained in:
138
backend/app/services.py
Normal file
138
backend/app/services.py
Normal file
@@ -0,0 +1,138 @@
|
||||
import os
|
||||
from typing import List
|
||||
from openai import OpenAI
|
||||
from sqlalchemy.orm import Session
|
||||
from . import models
|
||||
from .curriculum import QUESTIONS, SKILLS
|
||||
|
||||
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
||||
|
||||
|
||||
SYSTEM_PROMPT = """
|
||||
Tu es ProfAmi, un professeur virtuel français pour enfants de 8 à 12 ans.
|
||||
Règles :
|
||||
- Tu parles toujours en français simple et chaleureux.
|
||||
- Tu donnes des explications très courtes, puis un mini exemple.
|
||||
- Tu tiens compte du niveau de l'élève et de ses faiblesses indiquées dans le contexte.
|
||||
- Tu n'inventes pas la progression : elle est fournie dans le contexte.
|
||||
- Tu encourages sans infantiliser.
|
||||
- Quand l'élève se trompe, tu expliques calmement puis proposes une question très simple.
|
||||
- Tu enseignes principalement le programme national français niveau primaire/cycle 3.
|
||||
""".strip()
|
||||
|
||||
|
||||
def seed_skills(db: Session) -> None:
|
||||
for skill in SKILLS:
|
||||
existing = db.query(models.Skill).filter(models.Skill.code == skill["code"]).first()
|
||||
if not existing:
|
||||
db.add(models.Skill(**skill))
|
||||
db.commit()
|
||||
|
||||
|
||||
def ensure_student_mastery(db: Session, student: models.Student) -> None:
|
||||
all_skills = db.query(models.Skill).all()
|
||||
for skill in all_skills:
|
||||
found = (
|
||||
db.query(models.StudentSkillMastery)
|
||||
.filter_by(student_id=student.id, skill_id=skill.id)
|
||||
.first()
|
||||
)
|
||||
if not found:
|
||||
db.add(models.StudentSkillMastery(student_id=student.id, skill_id=skill.id))
|
||||
db.commit()
|
||||
|
||||
|
||||
def get_student_context(db: Session, student_id: int) -> str:
|
||||
student = db.query(models.Student).filter_by(id=student_id).first()
|
||||
mastery = (
|
||||
db.query(models.StudentSkillMastery, models.Skill)
|
||||
.join(models.Skill, models.Skill.id == models.StudentSkillMastery.skill_id)
|
||||
.filter(models.StudentSkillMastery.student_id == student_id)
|
||||
.all()
|
||||
)
|
||||
recent_messages = (
|
||||
db.query(models.Message)
|
||||
.filter_by(student_id=student_id)
|
||||
.order_by(models.Message.created_at.desc())
|
||||
.limit(6)
|
||||
.all()
|
||||
)
|
||||
|
||||
lines: List[str] = [
|
||||
f"Élève: {student.first_name}, {student.age} ans, classe {student.grade}.",
|
||||
"Progression par compétence:",
|
||||
]
|
||||
for mastery_row, skill in mastery:
|
||||
lines.append(
|
||||
f"- {skill.label}: score={mastery_row.mastery_score:.1f}, confiance={mastery_row.confidence:.2f}, preuves={mastery_row.evidence_count}"
|
||||
)
|
||||
lines.append("Historique récent:")
|
||||
for message in reversed(recent_messages):
|
||||
lines.append(f"- {message.role}: {message.content}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def build_llm_reply(db: Session, student_id: int, user_message: str) -> str:
|
||||
context = get_student_context(db, student_id)
|
||||
response = client.responses.create(
|
||||
model="gpt-4.1-mini",
|
||||
input=[
|
||||
{"role": "system", "content": SYSTEM_PROMPT},
|
||||
{
|
||||
"role": "user",
|
||||
"content": f"Contexte pédagogique:\n{context}\n\nMessage de l'élève:\n{user_message}",
|
||||
},
|
||||
],
|
||||
temperature=0.7,
|
||||
)
|
||||
return response.output_text.strip()
|
||||
|
||||
|
||||
def pick_next_skill(db: Session, student_id: int) -> models.Skill:
|
||||
weakest = (
|
||||
db.query(models.StudentSkillMastery)
|
||||
.filter_by(student_id=student_id)
|
||||
.order_by(models.StudentSkillMastery.mastery_score.asc())
|
||||
.first()
|
||||
)
|
||||
return db.query(models.Skill).filter_by(id=weakest.skill_id).first()
|
||||
|
||||
|
||||
def evaluate_answer(db: Session, student_id: int, skill_code: str, answer: str):
|
||||
q = QUESTIONS[skill_code]
|
||||
normalized_student = answer.strip().lower()
|
||||
normalized_expected = q["expected_answer"].strip().lower()
|
||||
correct = normalized_student == normalized_expected
|
||||
|
||||
skill = db.query(models.Skill).filter_by(code=skill_code).first()
|
||||
mastery = (
|
||||
db.query(models.StudentSkillMastery)
|
||||
.filter_by(student_id=student_id, skill_id=skill.id)
|
||||
.first()
|
||||
)
|
||||
|
||||
if correct:
|
||||
mastery.mastery_score = min(100.0, mastery.mastery_score + 8)
|
||||
mastery.confidence = min(1.0, mastery.confidence + 0.15)
|
||||
feedback = q["feedback_ok"]
|
||||
else:
|
||||
mastery.mastery_score = max(0.0, mastery.mastery_score - 6)
|
||||
mastery.confidence = min(1.0, mastery.confidence + 0.1)
|
||||
feedback = q["feedback_ko"]
|
||||
|
||||
mastery.evidence_count += 1
|
||||
|
||||
db.add(
|
||||
models.AssessmentAttempt(
|
||||
student_id=student_id,
|
||||
skill_code=skill_code,
|
||||
question=q["question"],
|
||||
expected_answer=q["expected_answer"],
|
||||
student_answer=answer,
|
||||
is_correct=1 if correct else 0,
|
||||
feedback=feedback,
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
db.refresh(mastery)
|
||||
return correct, feedback, mastery.mastery_score
|
||||
Reference in New Issue
Block a user