OUAREDAEK commited on
Commit
f9d99e3
·
verified ·
1 Parent(s): a739ae1

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +353 -0
app.py ADDED
@@ -0,0 +1,353 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py optimisé pour Hugging Face
2
+ from flask import Flask, render_template, request, jsonify, send_from_directory
3
+ from datetime import datetime, timedelta
4
+ import json
5
+ import os
6
+ import numpy as np
7
+ from pathlib import Path
8
+
9
+ app = Flask(__name__)
10
+
11
+ # ======================================================
12
+ # CONFIGURATION POUR HUGGING FACE
13
+ # ======================================================
14
+ BASE_DIR = Path(__file__).parent
15
+ DATA_DIR = BASE_DIR / "data"
16
+ DATA_DIR.mkdir(exist_ok=True)
17
+
18
+ # Chemins des fichiers de données
19
+ COMPETENCES_FILE = BASE_DIR / "competences.json"
20
+ JOURNAL_FILE = DATA_DIR / "journal.json"
21
+ PLANNER_FILE = DATA_DIR / "planner.json"
22
+ LEARNER_STATE_FILE = DATA_DIR / "learner_state.json"
23
+
24
+ # ======================================================
25
+ # INITIALISATION DES DONNÉES
26
+ # ======================================================
27
+
28
+ def load_json_file(file_path, default_data):
29
+ """Charge un fichier JSON ou retourne les données par défaut"""
30
+ try:
31
+ if file_path.exists():
32
+ with open(file_path, 'r', encoding='utf-8') as f:
33
+ return json.load(f)
34
+ except:
35
+ pass
36
+ return default_data
37
+
38
+ def save_json_file(file_path, data):
39
+ """Sauvegarde des données dans un fichier JSON"""
40
+ try:
41
+ with open(file_path, 'w', encoding='utf-8') as f:
42
+ json.dump(data, f, ensure_ascii=False, indent=2)
43
+ return True
44
+ except:
45
+ return False
46
+
47
+ # Charger les données
48
+ COMPETENCES = load_json_file(COMPETENCES_FILE, [
49
+ {"id": "D-MCONJ", "label": "Marques de la conjugaison", "category": "Discours"},
50
+ {"id": "M-COMP", "label": "Construction des phrases", "category": "Maîtrise"},
51
+ {"id": "P-GRAM", "label": "Orthographe grammaticale", "category": "Phrase"},
52
+ {"id": "T-ORG", "label": "Organisation textuelle", "category": "Texte"}
53
+ ])
54
+
55
+ JOURNAL = load_json_file(JOURNAL_FILE, [])
56
+ PLANNER = load_json_file(PLANNER_FILE, [])
57
+ LEARNER_STATE = load_json_file(LEARNER_STATE_FILE, {
58
+ "retention": {},
59
+ "engagement": "stable",
60
+ "risk": "low",
61
+ "days_inactive": 0,
62
+ "total_sessions": 0,
63
+ "success_rate": 0.0
64
+ })
65
+
66
+ # Intervalles SM-2 de base
67
+ SM2_INTERVALS = [1, 3, 7, 14, 30, 60]
68
+
69
+ # ======================================================
70
+ # FONCTIONS UTILITAIRES
71
+ # ======================================================
72
+
73
+ def get_reminder_for_quality(quality_level):
74
+ """Retourne un rappel adapté au niveau de qualité"""
75
+ reminders = [
76
+ "🧠 Révision fréquente conseillée pour renforcer la mémoire",
77
+ "📚 Répétition espacée standard recommandée",
78
+ "🚀 Excellente progression, continuez ainsi !",
79
+ "🏆 Niveau expert, rétention à long terme optimale"
80
+ ]
81
+ return reminders[min(quality_level, 3)]
82
+
83
+ def get_intervals_for_quality(quality_level):
84
+ """Retourne les intervalles adaptés au niveau de qualité"""
85
+ quality_multipliers = [0.7, 1.0, 1.5, 2.0]
86
+ multiplier = quality_multipliers[min(quality_level, 3)]
87
+ return [int(interval * multiplier) for interval in SM2_INTERVALS]
88
+
89
+ # ======================================================
90
+ # ROUTES PRINCIPALES
91
+ # ======================================================
92
+
93
+ @app.route('/')
94
+ def home():
95
+ """Page d'accueil"""
96
+ return render_template('index.html', competences=COMPETENCES)
97
+
98
+ @app.route('/api/competences')
99
+ def get_competences():
100
+ return jsonify(COMPETENCES)
101
+
102
+ @app.route('/api/planner/visualize')
103
+ def visualize_planner():
104
+ """Génère les données de visualisation pour la courbe SM-2"""
105
+ try:
106
+ competence = request.args.get('competence', 'Compétence')
107
+ quality = int(request.args.get('quality', 1))
108
+
109
+ intervals = get_intervals_for_quality(quality)
110
+
111
+ return jsonify({
112
+ 'success': True,
113
+ 'intervals': intervals,
114
+ 'competence': competence,
115
+ 'quality_level': quality,
116
+ 'description': f'Courbe SM-2 adaptée (Niveau {quality})'
117
+ })
118
+ except Exception as e:
119
+ return jsonify({'success': False, 'error': str(e)})
120
+
121
+ @app.route('/api/planner/generate', methods=['POST'])
122
+ def generate_planner():
123
+ try:
124
+ data = request.json
125
+ competence = data.get('competence', 'Compétence')
126
+ start_date = datetime.strptime(data.get('start_date', '2026-01-01'), '%Y-%m-%d')
127
+ duration = int(data.get('session_duration', 20))
128
+ repetitions = int(data.get('repetitions', 6))
129
+ quality_level = int(data.get('quality_level', 1))
130
+
131
+ # Obtenir les intervalles adaptés
132
+ adjusted_intervals = get_intervals_for_quality(quality_level)
133
+
134
+ plan = []
135
+ current_date = start_date
136
+
137
+ for i in range(min(repetitions, len(adjusted_intervals))):
138
+ plan.append({
139
+ 'date': current_date.strftime('%Y-%m-%d'),
140
+ 'jour': current_date.strftime('%A'),
141
+ 'competence': competence,
142
+ 'repetition': i + 1,
143
+ 'duree': f'{duration} minutes',
144
+ 'intervalle': f'+{adjusted_intervals[i]} jours',
145
+ 'type': 'Apprentissage' if i == 0 else 'Révision espacée',
146
+ 'strategie': 'Active Recall + Élaboration',
147
+ 'reminder': get_reminder_for_quality(quality_level)
148
+ })
149
+ current_date += timedelta(days=adjusted_intervals[i])
150
+
151
+ # Sauvegarder
152
+ PLANNER.extend(plan)
153
+ save_json_file(PLANNER_FILE, PLANNER)
154
+
155
+ return jsonify({
156
+ 'success': True,
157
+ 'plan': plan,
158
+ 'intervals': adjusted_intervals,
159
+ 'quality_level': quality_level
160
+ })
161
+ except Exception as e:
162
+ return jsonify({'success': False, 'error': str(e)})
163
+
164
+ @app.route('/api/journal', methods=['POST'])
165
+ def save_journal():
166
+ try:
167
+ entry = request.json
168
+ entry['timestamp'] = datetime.now().isoformat()
169
+ entry['date'] = datetime.now().strftime('%Y-%m-%d %H:%M')
170
+
171
+ JOURNAL.append(entry)
172
+ save_json_file(JOURNAL_FILE, JOURNAL)
173
+
174
+ # Mettre à jour les statistiques
175
+ if 'auto_eval' in entry:
176
+ try:
177
+ eval_score = float(entry['auto_eval'])
178
+ LEARNER_STATE['total_sessions'] += 1
179
+ LEARNER_STATE['success_rate'] = (
180
+ (LEARNER_STATE['success_rate'] * (LEARNER_STATE['total_sessions'] - 1) + eval_score)
181
+ / LEARNER_STATE['total_sessions']
182
+ )
183
+ save_json_file(LEARNER_STATE_FILE, LEARNER_STATE)
184
+ except:
185
+ pass
186
+
187
+ return jsonify({'success': True, 'message': 'Journal sauvegardé'})
188
+ except Exception as e:
189
+ return jsonify({'success': False, 'error': str(e)})
190
+
191
+ @app.route('/api/journal/entries')
192
+ def get_journal_entries():
193
+ """Récupère les dernières entrées du journal"""
194
+ limit = int(request.args.get('limit', 5))
195
+ entries = JOURNAL[-limit:] if len(JOURNAL) > limit else JOURNAL
196
+ return jsonify(entries)
197
+
198
+ @app.route('/api/notifications')
199
+ def get_notification():
200
+ messages = [
201
+ "🌱 5 minutes aujourd'hui renforcent durablement votre mémoire.",
202
+ "⏰ Une courte révision maintenant évite l'oubli.",
203
+ "💪 Votre régularité montre une vraie progression.",
204
+ "🤔 Quelle stratégie vous aide le plus aujourd'hui ?"
205
+ ]
206
+
207
+ message = np.random.choice(messages)
208
+
209
+ return jsonify({
210
+ 'message': message,
211
+ 'timestamp': datetime.now().isoformat(),
212
+ 'type': 'reminder'
213
+ })
214
+
215
+ @app.route('/api/notifications/intelligent')
216
+ def get_intelligent_notification():
217
+ """Notifications plus élaborées"""
218
+ notifications = [
219
+ {
220
+ 'message': "🌱 5 minutes aujourd'hui renforcent durablement votre mémoire.",
221
+ 'type': 'motivation',
222
+ 'effectiveness_score': 0.85
223
+ },
224
+ {
225
+ 'message': "⏰ Une courte révision maintenant évite l'oubli.",
226
+ 'type': 'rappel_court',
227
+ 'effectiveness_score': 0.92
228
+ },
229
+ {
230
+ 'message': "💪 Votre régularité montre une vraie progression.",
231
+ 'type': 'encouragement',
232
+ 'effectiveness_score': 0.78
233
+ },
234
+ {
235
+ 'message': "🤔 Quelle stratégie vous aide le plus aujourd'hui ?",
236
+ 'type': 'metacognition',
237
+ 'effectiveness_score': 0.88
238
+ }
239
+ ]
240
+
241
+ notification = np.random.choice(notifications)
242
+ notification['timestamp'] = datetime.now().isoformat()
243
+
244
+ return jsonify(notification)
245
+
246
+ @app.route('/api/stats')
247
+ def get_stats():
248
+ return jsonify({
249
+ 'total_sessions': LEARNER_STATE.get('total_sessions', 0),
250
+ 'success_rate': round(LEARNER_STATE.get('success_rate', 0), 2),
251
+ 'journal_entries': len(JOURNAL),
252
+ 'planned_sessions': len(PLANNER),
253
+ 'engagement': LEARNER_STATE.get('engagement', 'stable')
254
+ })
255
+
256
+ @app.route('/api/learner/stats')
257
+ def get_learner_stats():
258
+ """Statistiques détaillées de l'apprenant"""
259
+ retention_data = LEARNER_STATE.get('retention', {})
260
+
261
+ # Calculer le niveau d'engagement
262
+ total_sessions = LEARNER_STATE.get('total_sessions', 0)
263
+ if total_sessions > 20:
264
+ engagement = 'high'
265
+ elif total_sessions > 10:
266
+ engagement = 'medium'
267
+ else:
268
+ engagement = 'low'
269
+
270
+ # Calculer le niveau de risque
271
+ days_inactive = LEARNER_STATE.get('days_inactive', 0)
272
+ if days_inactive > 7:
273
+ risk = 'high'
274
+ elif days_inactive > 3:
275
+ risk = 'medium'
276
+ else:
277
+ risk = 'low'
278
+
279
+ return jsonify({
280
+ 'total_sessions': total_sessions,
281
+ 'success_rate': round(LEARNER_STATE.get('success_rate', 0), 2),
282
+ 'journal_entries': len(JOURNAL),
283
+ 'planned_sessions': len(PLANNER),
284
+ 'engagement_level': engagement,
285
+ 'risk_level': risk,
286
+ 'days_inactive': days_inactive,
287
+ 'retention_rates': retention_data,
288
+ 'notification_effectiveness': {
289
+ 'motivation': 0.85,
290
+ 'rappel_court': 0.92,
291
+ 'encouragement': 0.78,
292
+ 'metacognition': 0.88
293
+ }
294
+ })
295
+
296
+ @app.route('/api/sm2/explain')
297
+ def explain_sm2():
298
+ return jsonify({
299
+ 'title': 'Algorithme SM-2',
300
+ 'description': 'Répétition espacée optimisée pour la rétention mémoire',
301
+ 'quality_levels': {
302
+ '0': 'Difficulté - Révisions fréquentes nécessaires',
303
+ '1': 'Moyen - Intervalles standard',
304
+ '2': 'Bon - Consolidation progressive',
305
+ '3': 'Excellent - Rétention long terme'
306
+ },
307
+ 'intervals': SM2_INTERVALS,
308
+ 'principles': [
309
+ 'Réviser juste avant d\'oublier',
310
+ 'Augmenter l\'intervalle progressivement',
311
+ 'Adapter selon la difficulté',
312
+ 'Être régulier dans la pratique'
313
+ ],
314
+ 'recommendations': [
315
+ 'Commencez avec des sessions courtes (10-20 minutes)',
316
+ 'Augmentez la durée progressivement',
317
+ 'Notez votre niveau de confiance après chaque session',
318
+ 'Ajustez les intervalles selon vos résultats'
319
+ ]
320
+ })
321
+
322
+ # ======================================================
323
+ # ROUTES STATIQUES (pour Hugging Face)
324
+ # ======================================================
325
+
326
+ @app.route('/static/<path:filename>')
327
+ def serve_static(filename):
328
+ return send_from_directory('static', filename)
329
+
330
+ @app.route('/favicon.ico')
331
+ def favicon():
332
+ return '', 204
333
+
334
+ # ======================================================
335
+ # LANCEMENT
336
+ # ======================================================
337
+
338
+ if __name__ == '__main__':
339
+ # Configuration pour Hugging Face Spaces
340
+ port = int(os.environ.get('PORT', 7860))
341
+ debug = os.environ.get('FLASK_DEBUG', 'False').lower() == 'true'
342
+
343
+ print(f"🚀 Démarrage de l'application SRL sur le port {port}")
344
+ print(f"📚 Compétences chargées: {len(COMPETENCES)}")
345
+ print(f"📔 Entrées journal: {len(JOURNAL)}")
346
+ print(f"📅 Sessions planifiées: {len(PLANNER)}")
347
+
348
+ app.run(
349
+ host='0.0.0.0',
350
+ port=port,
351
+ debug=debug,
352
+ use_reloader=False
353
+ )