diff --git a/app/gen_op.py b/app/gen_op.py new file mode 100755 index 0000000..2045833 --- /dev/null +++ b/app/gen_op.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 +import random +import io +from fpdf import FPDF + +def generer_nombre(): + """Génère un nombre naturel entre 1 et 9""" + return random.randint(1, 9) + +def evaluer_expression(expr): + """Évalue une expression mathématique en remplaçant × par *""" + try: + # Remplacer × par * pour l'évaluation + expr_eval = expr.replace('×', '*') + return eval(expr_eval) + except: + return -1 # En cas d'erreur, considérer comme négatif + +def generer_expression(): + """Génère une expression aléatoire respectant les contraintes""" + # On choisit aléatoirement un type d'expression (avec ou sans parenthèses) + # On vérifie que le résultat est positif + expressions_positives = [ + lambda: f"({generer_nombre()} + {generer_nombre()}) × {generer_nombre()} - {generer_nombre()}", + lambda: f"{generer_nombre()} + {generer_nombre()} × {generer_nombre()} - {generer_nombre()}", + lambda: f"({generer_nombre()} + {generer_nombre()}) × ({generer_nombre()} + {generer_nombre()})", + lambda: f"{generer_nombre()} + {generer_nombre()} × {generer_nombre()} - {generer_nombre()} + {generer_nombre()}", + lambda: f"({generer_nombre()} + {generer_nombre()}) × {generer_nombre()} - {generer_nombre()} + {generer_nombre()}", + lambda: f"{generer_nombre()} × {generer_nombre()} + {generer_nombre()} - {generer_nombre()} × {generer_nombre()}", + lambda: f"({generer_nombre()} + {generer_nombre()}) + {generer_nombre()} × {generer_nombre()} - {generer_nombre()}", + lambda: f"{generer_nombre()} + {generer_nombre()} × ({generer_nombre()} + {generer_nombre()}) - {generer_nombre()}", + lambda: f"({generer_nombre()} + {generer_nombre()}) + {generer_nombre()} × {generer_nombre()} - {generer_nombre()}", + lambda: f"{generer_nombre()} × ({generer_nombre()} + {generer_nombre()}) + {generer_nombre()} - {generer_nombre()}", + lambda: f"{generer_nombre()} + {generer_nombre()} × {generer_nombre()} - {generer_nombre()} × {generer_nombre()} + {generer_nombre()}", + lambda: f"({generer_nombre()} + {generer_nombre()}) × {generer_nombre()} - {generer_nombre()} + {generer_nombre()} - {generer_nombre()}", + ] + + # Essayer jusqu'à ce qu'on obtienne une expression avec résultat positif + while True: + choix = random.choice(expressions_positives) + expr = choix() + resultat = evaluer_expression(expr) + if resultat >= 0: + return expr + +def generer_feuille_exercices(n_exercices=20): + """Génère une feuille d'exercices en format 2 colonnes""" + expressions = [generer_expression() for _ in range(n_exercices)] + + # Format 2 colonnes : on affiche par paires + output = "" + for i in range(0, n_exercices, 2): + expr1 = f"{i+1}. {expressions[i]}" + expr2 = f"{i+2}. {expressions[i+1]}" if i+1 < len(expressions) else "" + # Alignement pour deux colonnes + output += f"{expr1:<36} {expr2}\n" + " " * 36 + " \n" * 3 + return output + +def generer_pdf_exercices_en_memoire(n_exercices=20): + """Génère une feuille d'exercices au format PDF en mémoire""" + pdf = FPDF() + pdf.add_page() + pdf.set_font("Helvetica", size=12) + + # Titre + pdf.cell(200, 10, text="Feuille d'exercices d'opérations", new_x="LMARGIN", new_y="NEXT", align='C') + pdf.ln(10) + + # Contenu en 2 colonnes + expressions = [generer_expression() for _ in range(n_exercices)] + + for i in range(0, n_exercices, 2): + # Colonne 1 + expr1 = f"{i+1}. {expressions[i]}" + pdf.cell(90, 10, text=expr1, border=0, align='L') + + # Colonne 2 + if i+1 < len(expressions): + expr2 = f"{i+2}. {expressions[i+1]}" + pdf.cell(90, 10, text=expr2, border=0, align='L') + pdf.ln(10) + + # Espacement supplémentaire - ajout de deux lignes vides supplémentaires + pdf.ln(15) + + # Retourner les données PDF en mémoire + return pdf.output() + +def generer_pdf_exercices(n_exercices=20, output_file="exercices.pdf"): + """Génère une feuille d'exercices au format PDF""" + pdf_data = generer_pdf_exercices_en_memoire(n_exercices) + + # Sauvegarde du PDF + with open(output_file, "wb") as f: + f.write(pdf_data) + return output_file diff --git a/app/main.py b/app/main.py index 9a4a5d3..23564c0 100644 --- a/app/main.py +++ b/app/main.py @@ -6,6 +6,7 @@ import io import boto3 import zipfile import tempfile +import importlib.util from botocore.exceptions import ClientError from typing import List from fastapi import FastAPI, Request, Form @@ -16,6 +17,9 @@ from fpdf import FPDF import logging +# Import the functions from gen_op.py +from app.gen_op import generer_pdf_exercices_en_memoire + # Custom filter to exclude health check logs class HealthCheckFilter(logging.Filter): def filter(self, record: logging.LogRecord) -> bool: @@ -163,6 +167,9 @@ class ExerciseRequest(BaseModel): max_table: int num_exercises: int = 15 +class OperationExerciseRequest(BaseModel): + num_exercises: int = 20 + def generate_exercises( min_table: int, max_table: int, num_exercises: int = 15 @@ -305,6 +312,26 @@ def create_math_exercises_pdf( return pdf_filename +def create_operation_exercises_pdf(num_exercises: int = 20) -> str: + """Crée un fichier PDF avec des exercices d'opérations et l'upload sur S3""" + import datetime + + # Add timestamp to filename + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + pdf_filename = f"exercices_operations_{num_exercises}_exercices_{timestamp}.pdf" + + # Generate PDF in memory using gen-op functions + pdf_data = generer_pdf_exercices_en_memoire(num_exercises) + + # Upload to S3 + upload_success = upload_to_s3(pdf_data, S3_BUCKET_NAME, pdf_filename, "application/pdf") + + if not upload_success: + raise Exception("Failed to upload PDF to S3") + + return pdf_filename + + @app.get("/", response_class=HTMLResponse) async def read_root(request: Request): return templates.TemplateResponse("index.html", {"request": request}) @@ -333,6 +360,21 @@ async def generate_exercises_endpoint(request: ExerciseRequest): return {"error": f"Erreur lors de la création du PDF: {str(e)}"} +@app.post("/generate-operations") +async def generate_operation_exercises_endpoint(request: OperationExerciseRequest): + try: + if request.num_exercises < 1: + return {"error": "Le nombre d'exercices doit être supérieur à 0"} + + pdf_filename = create_operation_exercises_pdf(request.num_exercises) + + # Return redirect to automatically download the file + return RedirectResponse(url=f"/download/{pdf_filename}", status_code=303) + + 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): # Download file from S3 diff --git a/app/templates/index.html b/app/templates/index.html index c4136bf..0e7c146 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -33,14 +33,15 @@

Générateur d'Exercices de Mathématiques

-

Créez des exercices de multiplication et division personnalisés en PDF

+

Créez des exercices de multiplication, division et opérations complexes personnalisés en PDF

+
-
Paramètres des Exercices
+
Paramètres des Exercices (Multiplications/Divisions)
@@ -62,7 +63,7 @@
@@ -70,6 +71,27 @@
+ +
+
+
Paramètres des Exercices d'Opérations
+ +
+ + +
Nombre total d'exercices d'opérations à générer (entre 1 et 100)
+
+ +
+ +
+ +
+
+
@@ -161,8 +183,40 @@
-
Trois Colonnes
-

Les exercices sont présentés en trois colonnes pour économiser du papier.

+
Colonnes Variables
+

Les exercices de multiplication/division sont présentés en trois colonnes, les opérations complexes en deux colonnes.

+
+
+
+
+ +
+
+
+
+ + + + +
+
+
Opérations Complexes
+

Générez des exercices avec des expressions mathématiques complexes incluant parenthèses, additions, soustractions et multiplications.

+
+
+
+ +
+
+
+ + + + +
+
+
Stockage Cloud
+

Tous les fichiers générés sont automatiquement stockés dans le cloud et peuvent être téléchargés ultérieurement.

@@ -522,7 +576,80 @@ resultContainer.classList.remove('d-none'); } finally { // Reset button state - buttonText.textContent = 'Générer le PDF'; + buttonText.textContent = 'Générer le PDF (Mult/Div)'; + spinner.classList.add('d-none'); + generateBtn.disabled = false; + } + }); + + // Handle operation exercises form submission + document.getElementById('operationExerciseForm').addEventListener('submit', async function(e) { + e.preventDefault(); + + const numExercises = parseInt(document.getElementById('numOperationExercises').value); + + const generateBtn = document.getElementById('generateOperationBtn'); + const buttonText = document.getElementById('operationButtonText'); + const spinner = document.getElementById('operationSpinner'); + + // Show loading state + buttonText.textContent = 'Génération en cours...'; + spinner.classList.remove('d-none'); + generateBtn.disabled = true; + + try { + const response = await fetch('/generate-operations', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + num_exercises: numExercises + }) + }); + + // Check if it's a redirect response (for automatic download) + if (response.redirected) { + // Automatically trigger download + window.location.href = response.url; + + // Refresh the PDF list (reset to first page) + setTimeout(() => loadPdfList(1), 1000); + + // Show success message + const resultContainer = document.getElementById('resultContainer'); + const resultMessage = document.getElementById('resultMessage'); + resultMessage.innerHTML = ` +
+
PDF d'opérations généré avec succès!
+

Le téléchargement devrait commencer automatiquement.

+ +
+ `; + resultContainer.classList.remove('d-none'); + } else { + // Handle JSON response (error case) + const result = await response.json(); + const resultContainer = document.getElementById('resultContainer'); + const resultMessage = document.getElementById('resultMessage'); + + if (result.error) { + resultMessage.innerHTML = `
${result.error}
`; + } + + resultContainer.classList.remove('d-none'); + } + } catch (error) { + const resultContainer = document.getElementById('resultContainer'); + const resultMessage = document.getElementById('resultMessage'); + + resultMessage.innerHTML = `
Erreur: ${error.message}
`; + resultContainer.classList.remove('d-none'); + } finally { + // Reset button state + buttonText.textContent = 'Générer le PDF (Opérations)'; spinner.classList.add('d-none'); generateBtn.disabled = false; } diff --git a/deploy/overlays/production/kustomization.yaml b/deploy/overlays/production/kustomization.yaml index 70f19cc..f4b2d08 100644 --- a/deploy/overlays/production/kustomization.yaml +++ b/deploy/overlays/production/kustomization.yaml @@ -13,7 +13,7 @@ resources: images: - name: math-exercises newName: harbor.cl1.parano.ch/library/math-exercice - newTag: 1.0.8 + newTag: 1.0.9 # Production-specific labels