#!/usr/bin/env python3 import random import os from typing import List from fastapi import FastAPI, Request, Response from fastapi.responses import HTMLResponse, FileResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from pydantic import BaseModel from fpdf import FPDF app = FastAPI() # Mount static files and templates app.mount("/static", StaticFiles(directory="app/static"), name="static") templates = Jinja2Templates(directory="app/templates") class MathExercisesPDF(FPDF): def header(self): self.set_font('Helvetica', 'B', 16) self.cell(0, 10, 'Exercices de Multiplication et Division', 0, 1, 'C', new_x="LMARGIN", new_y="NEXT") self.ln(10) def footer(self): self.set_y(-15) self.set_font('Helvetica', 'I', 8) self.cell(0, 10, f'Page {self.page_no()}', 0, 0, 'C', new_x="RIGHT", new_y="TOP") class ExerciseRequest(BaseModel): min_table: int max_table: int num_exercises: int = 15 def generate_exercises(min_table: int, max_table: int, num_exercises: int = 15) -> List[str]: """Génère des exercices de multiplication et division aléatoires mélangés sans doublons""" exercises = [] used_operations = set() # Pour éviter les doublons # Générer le nombre exact d'exercices demandé attempts = 0 max_attempts = num_exercises * 10 # Limite pour éviter une boucle infinie while len(exercises) < num_exercises and attempts < max_attempts: attempts += 1 # Choisir deux nombres aléatoires entre min_table et max_table a = random.randint(min_table, max_table) b = random.randint(min_table, max_table) result = a * b # Choisir aléatoirement le type d'exercice (seulement multiplication ou division) exercise_type = random.choice(['multiplication', 'division']) if exercise_type == 'multiplication': # Exercice de multiplication operation_key = f"mult_{a}_{b}" # Clé unique pour cette opération if operation_key not in used_operations: exercise = f"{a} · {b} = ____" exercises.append(exercise) used_operations.add(operation_key) else: # division # Exercice de division divisor = random.choice([a, b]) operation_key = f"div_{result}_{divisor}" # Clé unique pour cette opération if operation_key not in used_operations: exercise = f"{result} : {divisor} = ____" exercises.append(exercise) used_operations.add(operation_key) # Si nous n'avons pas pu générer suffisamment d'exercices uniques, # compléter avec des variations if len(exercises) < num_exercises: remaining = num_exercises - len(exercises) for _ in range(remaining): # Générer des variations avec des nombres légèrement différents a = random.randint(min_table, max_table) b = random.randint(min_table, max_table) result = a * b # Choisir aléatoirement le type d'exercice exercise_type = random.choice(['multiplication', 'division']) if exercise_type == 'multiplication': exercise = f"{a} · {b} = ____" else: # division divisor = random.choice([a, b]) exercise = f"{result} : {divisor} = ____" exercises.append(exercise) return exercises def create_math_exercises_pdf(min_table: int, max_table: int, num_exercises: int = 15) -> str: """Crée un fichier PDF avec des exercices de mathématiques mélangés en 3 colonnes""" pdf = MathExercisesPDF() pdf.add_page() pdf.set_font('Helvetica', '', 12) # Ajouter des informations sur la plage de tables if min_table == max_table: table_info = f'Tables de multiplication et division pour {min_table}' else: table_info = f'Tables de multiplication et division de {min_table} à {max_table}' pdf.cell(0, 10, table_info, 0, 1, 'C', new_x="LMARGIN", new_y="NEXT") pdf.ln(5) # Générer les exercices exercises = generate_exercises(min_table, max_table, num_exercises) # Pas d'en-têtes de colonnes # Répartir les exercices en 3 colonnes num_per_column = (num_exercises + 2) // 3 # Arrondi vers le haut col1_exercises = exercises[:num_per_column] col2_exercises = exercises[num_per_column:num_per_column*2] col3_exercises = exercises[num_per_column*2:] # Ajouter les exercices numérotés de 1 à n dans les colonnes max_rows = max(len(col1_exercises), len(col2_exercises), len(col3_exercises)) for i in range(max_rows): # Colonne 1 if i < len(col1_exercises): exercise_num = i + 1 col1_text = f"{exercise_num}. {col1_exercises[i]}" else: col1_text = "" # Colonne 2 if i < len(col2_exercises): exercise_num = i + 1 + len(col1_exercises) col2_text = f"{exercise_num}. {col2_exercises[i]}" else: col2_text = "" # Colonne 3 if i < len(col3_exercises): exercise_num = i + 1 + len(col1_exercises) + len(col2_exercises) col3_text = f"{exercise_num}. {col3_exercises[i]}" else: col3_text = "" # Ajouter la ligne pdf.cell(60, 10, col1_text, 0, 0, 'L', new_x="RIGHT", new_y="TOP") pdf.cell(60, 10, col2_text, 0, 0, 'L', new_x="RIGHT", new_y="TOP") pdf.cell(60, 10, col3_text, 0, 1, 'L', new_x="LMARGIN", new_y="NEXT") # Sauvegarder le PDF if min_table == max_table: filename = f'app/static/exercices_mathematiques_table_{min_table}_{num_exercises}_exercices.pdf' else: filename = f'app/static/exercices_mathematiques_tables_{min_table}_a_{max_table}_{num_exercises}_exercices.pdf' pdf.output(filename) return filename @app.get("/", response_class=HTMLResponse) async def read_root(request: Request): return templates.TemplateResponse("index.html", {"request": request}) @app.post("/generate") async def generate_exercises_endpoint(request: ExerciseRequest): try: if request.min_table < 1 or request.max_table < 1: return {"error": "Les tables doivent être supérieures à 0"} if request.min_table > request.max_table: return {"error": "La table minimale doit être inférieure ou égale à la table maximale"} if request.num_exercises < 1: return {"error": "Le nombre d'exercices doit être supérieur à 0"} filename = create_math_exercises_pdf( request.min_table, request.max_table, request.num_exercises ) # Extract just the filename for the download link pdf_filename = os.path.basename(filename) return { "message": "PDF généré avec succès!", "pdf_filename": pdf_filename, "min_table": request.min_table, "max_table": request.max_table, "num_exercises": request.num_exercises } except Exception as e: return {"error": f"Erreur lors de la création du PDF: {str(e)}"} @app.get("/download/{filename}") async def download_pdf(filename: str): file_path = f"app/static/{filename}" if os.path.exists(file_path): return FileResponse( path=file_path, media_type='application/pdf', filename=filename ) return {"error": "File not found"} if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)