# app.py optimisé pour Hugging Face from flask import Flask, render_template, request, jsonify, send_from_directory from datetime import datetime, timedelta import json import os import numpy as np from pathlib import Path app = Flask(__name__) # ====================================================== # CONFIGURATION POUR HUGGING FACE # ====================================================== BASE_DIR = Path(__file__).parent DATA_DIR = BASE_DIR / "data" DATA_DIR.mkdir(exist_ok=True) # Chemins des fichiers de données COMPETENCES_FILE = BASE_DIR / "competences.json" JOURNAL_FILE = DATA_DIR / "journal.json" PLANNER_FILE = DATA_DIR / "planner.json" LEARNER_STATE_FILE = DATA_DIR / "learner_state.json" # ====================================================== # INITIALISATION DES DONNÉES # ====================================================== def load_json_file(file_path, default_data): """Charge un fichier JSON ou retourne les données par défaut""" try: if file_path.exists(): with open(file_path, 'r', encoding='utf-8') as f: return json.load(f) except: pass return default_data def save_json_file(file_path, data): """Sauvegarde des données dans un fichier JSON""" try: with open(file_path, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) return True except: return False # Charger les données COMPETENCES = load_json_file(COMPETENCES_FILE, [ {"id": "D-MCONJ", "label": "Marques de la conjugaison", "category": "Discours"}, {"id": "M-COMP", "label": "Construction des phrases", "category": "Maîtrise"}, {"id": "P-GRAM", "label": "Orthographe grammaticale", "category": "Phrase"}, {"id": "T-ORG", "label": "Organisation textuelle", "category": "Texte"} ]) JOURNAL = load_json_file(JOURNAL_FILE, []) PLANNER = load_json_file(PLANNER_FILE, []) LEARNER_STATE = load_json_file(LEARNER_STATE_FILE, { "retention": {}, "engagement": "stable", "risk": "low", "days_inactive": 0, "total_sessions": 0, "success_rate": 0.0 }) # Intervalles SM-2 de base SM2_INTERVALS = [1, 3, 7, 14, 30, 60] # ====================================================== # FONCTIONS UTILITAIRES # ====================================================== def get_reminder_for_quality(quality_level): """Retourne un rappel adapté au niveau de qualité""" reminders = [ "🧠 Révision fréquente conseillée pour renforcer la mémoire", "📚 Répétition espacée standard recommandée", "🚀 Excellente progression, continuez ainsi !", "🏆 Niveau expert, rétention à long terme optimale" ] return reminders[min(quality_level, 3)] def get_intervals_for_quality(quality_level): """Retourne les intervalles adaptés au niveau de qualité""" quality_multipliers = [0.7, 1.0, 1.5, 2.0] multiplier = quality_multipliers[min(quality_level, 3)] return [int(interval * multiplier) for interval in SM2_INTERVALS] # ====================================================== # ROUTES PRINCIPALES # ====================================================== @app.route('/') def home(): """Page d'accueil""" return render_template('index.html', competences=COMPETENCES) @app.route('/api/competences') def get_competences(): return jsonify(COMPETENCES) @app.route('/api/planner/visualize') def visualize_planner(): """Génère les données de visualisation pour la courbe SM-2""" try: competence = request.args.get('competence', 'Compétence') quality = int(request.args.get('quality', 1)) intervals = get_intervals_for_quality(quality) return jsonify({ 'success': True, 'intervals': intervals, 'competence': competence, 'quality_level': quality, 'description': f'Courbe SM-2 adaptée (Niveau {quality})' }) except Exception as e: return jsonify({'success': False, 'error': str(e)}) @app.route('/api/planner/generate', methods=['POST']) def generate_planner(): try: data = request.json competence = data.get('competence', 'Compétence') start_date = datetime.strptime(data.get('start_date', '2026-01-01'), '%Y-%m-%d') duration = int(data.get('session_duration', 20)) repetitions = int(data.get('repetitions', 6)) quality_level = int(data.get('quality_level', 1)) # Obtenir les intervalles adaptés adjusted_intervals = get_intervals_for_quality(quality_level) plan = [] current_date = start_date for i in range(min(repetitions, len(adjusted_intervals))): plan.append({ 'date': current_date.strftime('%Y-%m-%d'), 'jour': current_date.strftime('%A'), 'competence': competence, 'repetition': i + 1, 'duree': f'{duration} minutes', 'intervalle': f'+{adjusted_intervals[i]} jours', 'type': 'Apprentissage' if i == 0 else 'Révision espacée', 'strategie': 'Active Recall + Élaboration', 'reminder': get_reminder_for_quality(quality_level) }) current_date += timedelta(days=adjusted_intervals[i]) # Sauvegarder PLANNER.extend(plan) save_json_file(PLANNER_FILE, PLANNER) return jsonify({ 'success': True, 'plan': plan, 'intervals': adjusted_intervals, 'quality_level': quality_level }) except Exception as e: return jsonify({'success': False, 'error': str(e)}) @app.route('/api/journal', methods=['POST']) def save_journal(): try: entry = request.json entry['timestamp'] = datetime.now().isoformat() entry['date'] = datetime.now().strftime('%Y-%m-%d %H:%M') JOURNAL.append(entry) save_json_file(JOURNAL_FILE, JOURNAL) # Mettre à jour les statistiques if 'auto_eval' in entry: try: eval_score = float(entry['auto_eval']) LEARNER_STATE['total_sessions'] += 1 LEARNER_STATE['success_rate'] = ( (LEARNER_STATE['success_rate'] * (LEARNER_STATE['total_sessions'] - 1) + eval_score) / LEARNER_STATE['total_sessions'] ) save_json_file(LEARNER_STATE_FILE, LEARNER_STATE) except: pass return jsonify({'success': True, 'message': 'Journal sauvegardé'}) except Exception as e: return jsonify({'success': False, 'error': str(e)}) @app.route('/api/journal/entries') def get_journal_entries(): """Récupère les dernières entrées du journal""" limit = int(request.args.get('limit', 5)) entries = JOURNAL[-limit:] if len(JOURNAL) > limit else JOURNAL return jsonify(entries) @app.route('/api/notifications') def get_notification(): messages = [ "🌱 5 minutes aujourd'hui renforcent durablement votre mémoire.", "⏰ Une courte révision maintenant évite l'oubli.", "💪 Votre régularité montre une vraie progression.", "🤔 Quelle stratégie vous aide le plus aujourd'hui ?" ] message = np.random.choice(messages) return jsonify({ 'message': message, 'timestamp': datetime.now().isoformat(), 'type': 'reminder' }) @app.route('/api/notifications/intelligent') def get_intelligent_notification(): """Notifications plus élaborées""" notifications = [ { 'message': "🌱 5 minutes aujourd'hui renforcent durablement votre mémoire.", 'type': 'motivation', 'effectiveness_score': 0.85 }, { 'message': "⏰ Une courte révision maintenant évite l'oubli.", 'type': 'rappel_court', 'effectiveness_score': 0.92 }, { 'message': "💪 Votre régularité montre une vraie progression.", 'type': 'encouragement', 'effectiveness_score': 0.78 }, { 'message': "🤔 Quelle stratégie vous aide le plus aujourd'hui ?", 'type': 'metacognition', 'effectiveness_score': 0.88 } ] notification = np.random.choice(notifications) notification['timestamp'] = datetime.now().isoformat() return jsonify(notification) @app.route('/api/stats') def get_stats(): return jsonify({ 'total_sessions': LEARNER_STATE.get('total_sessions', 0), 'success_rate': round(LEARNER_STATE.get('success_rate', 0), 2), 'journal_entries': len(JOURNAL), 'planned_sessions': len(PLANNER), 'engagement': LEARNER_STATE.get('engagement', 'stable') }) @app.route('/api/learner/stats') def get_learner_stats(): """Statistiques détaillées de l'apprenant""" retention_data = LEARNER_STATE.get('retention', {}) # Calculer le niveau d'engagement total_sessions = LEARNER_STATE.get('total_sessions', 0) if total_sessions > 20: engagement = 'high' elif total_sessions > 10: engagement = 'medium' else: engagement = 'low' # Calculer le niveau de risque days_inactive = LEARNER_STATE.get('days_inactive', 0) if days_inactive > 7: risk = 'high' elif days_inactive > 3: risk = 'medium' else: risk = 'low' return jsonify({ 'total_sessions': total_sessions, 'success_rate': round(LEARNER_STATE.get('success_rate', 0), 2), 'journal_entries': len(JOURNAL), 'planned_sessions': len(PLANNER), 'engagement_level': engagement, 'risk_level': risk, 'days_inactive': days_inactive, 'retention_rates': retention_data, 'notification_effectiveness': { 'motivation': 0.85, 'rappel_court': 0.92, 'encouragement': 0.78, 'metacognition': 0.88 } }) @app.route('/api/sm2/explain') def explain_sm2(): return jsonify({ 'title': 'Algorithme SM-2', 'description': 'Répétition espacée optimisée pour la rétention mémoire', 'quality_levels': { '0': 'Difficulté - Révisions fréquentes nécessaires', '1': 'Moyen - Intervalles standard', '2': 'Bon - Consolidation progressive', '3': 'Excellent - Rétention long terme' }, 'intervals': SM2_INTERVALS, 'principles': [ 'Réviser juste avant d\'oublier', 'Augmenter l\'intervalle progressivement', 'Adapter selon la difficulté', 'Être régulier dans la pratique' ], 'recommendations': [ 'Commencez avec des sessions courtes (10-20 minutes)', 'Augmentez la durée progressivement', 'Notez votre niveau de confiance après chaque session', 'Ajustez les intervalles selon vos résultats' ] }) # ====================================================== # ROUTES STATIQUES (pour Hugging Face) # ====================================================== @app.route('/static/') def serve_static(filename): return send_from_directory('static', filename) @app.route('/favicon.ico') def favicon(): return '', 204 # ====================================================== # LANCEMENT # ====================================================== if __name__ == '__main__': # Configuration pour Hugging Face Spaces port = int(os.environ.get('PORT', 7860)) debug = os.environ.get('FLASK_DEBUG', 'False').lower() == 'true' print(f"🚀 Démarrage de l'application SRL sur le port {port}") print(f"📚 Compétences chargées: {len(COMPETENCES)}") print(f"📔 Entrées journal: {len(JOURNAL)}") print(f"📅 Sessions planifiées: {len(PLANNER)}") app.run( host='0.0.0.0', port=port, debug=debug, use_reloader=False )