PerseverAI / app.py
OUAREDAEK's picture
Upload app.py with huggingface_hub
f9d99e3 verified
Raw
History Blame Contribute Delete
12.5 kB
# 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/<path:filename>')
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
)