Spaces:
Sleeping
Sleeping
| # 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 | |
| # ====================================================== | |
| def home(): | |
| """Page d'accueil""" | |
| return render_template('index.html', competences=COMPETENCES) | |
| def get_competences(): | |
| return jsonify(COMPETENCES) | |
| 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)}) | |
| 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)}) | |
| 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)}) | |
| 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) | |
| 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' | |
| }) | |
| 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) | |
| 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') | |
| }) | |
| 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 | |
| } | |
| }) | |
| 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) | |
| # ====================================================== | |
| def serve_static(filename): | |
| return send_from_directory('static', filename) | |
| 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 | |
| ) |