feat: Fügt JSON-Reparaturfunktionen und verbesserte Fehlerbehandlung für Lektionen hinzu
This commit is contained in:
parent
7421f6fdec
commit
7bd52c3e4e
1 changed files with 313 additions and 148 deletions
461
app.py
461
app.py
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import json
|
||||
import re
|
||||
from flask import Flask, render_template, request, jsonify
|
||||
from models import db, Progress, Statistic
|
||||
|
||||
|
|
@ -20,80 +21,191 @@ FALLBACK_LESSONS = [
|
|||
DATA_PATH = os.path.join(app.root_path, "data", "lessons.json")
|
||||
lessons = []
|
||||
|
||||
def repair_json(json_string):
|
||||
"""Versucht, häufige JSON-Fehler automatisch zu reparieren"""
|
||||
try:
|
||||
# Repariere fehlende Kommas zwischen Objekten
|
||||
# Sucht nach } followed by { ohne Komma dazwischen
|
||||
repaired = re.sub(r'\}\s*\n\s*\{', '},\n{', json_string)
|
||||
|
||||
# Repariere fehlende Kommas am Ende von Objekten im Array
|
||||
repaired = re.sub(r'\}\s*\n\s*\]', '}\n]', repaired)
|
||||
|
||||
# Repariere fehlende Anführungszeichen bei Keys
|
||||
repaired = re.sub(r'(\w+)\s*:', r'"\1":', repaired)
|
||||
|
||||
# Repariere fehlende schließende Klammern
|
||||
brackets_open = repaired.count('[') + repaired.count('{')
|
||||
brackets_close = repaired.count(']') + repaired.count('}')
|
||||
|
||||
if brackets_open > brackets_close:
|
||||
repaired += ']' * (brackets_open - brackets_close)
|
||||
|
||||
return repaired
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Reparieren des JSON: {e}")
|
||||
return json_string
|
||||
|
||||
def load_lessons():
|
||||
"""Lädt Lektionen aus der JSON-Datei oder verwendet Fallback"""
|
||||
global lessons
|
||||
try:
|
||||
if os.path.exists(DATA_PATH):
|
||||
with open(DATA_PATH, "r", encoding="utf-8") as f:
|
||||
lessons = json.load(f)
|
||||
print(f"Erfolgreich {len(lessons)} Lektionen aus {DATA_PATH} geladen")
|
||||
content = f.read().strip()
|
||||
|
||||
# Versuche zuerst das originale JSON zu laden
|
||||
try:
|
||||
lessons = json.loads(content)
|
||||
print(f"Erfolgreich {len(lessons)} Lektionen aus {DATA_PATH} geladen")
|
||||
return
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"JSON-Fehler in {DATA_PATH}: {e}")
|
||||
print(f"Position: Zeile {e.lineno}, Spalte {e.colno}")
|
||||
|
||||
# Versuche das JSON zu reparieren
|
||||
print("Versuche JSON automatisch zu reparieren...")
|
||||
repaired_content = repair_json(content)
|
||||
|
||||
try:
|
||||
lessons = json.loads(repaired_content)
|
||||
print(f"Erfolgreich {len(lessons)} Lektionen nach Reparatur geladen")
|
||||
|
||||
# Speichere die reparierte Version als Backup
|
||||
backup_path = DATA_PATH + ".repaired"
|
||||
with open(backup_path, "w", encoding="utf-8") as f:
|
||||
json.dump(lessons, f, indent=2, ensure_ascii=False)
|
||||
print(f"Reparierte Version gespeichert als: {backup_path}")
|
||||
return
|
||||
|
||||
except json.JSONDecodeError as e2:
|
||||
print(f"Automatische Reparatur fehlgeschlagen: {e2}")
|
||||
print("Verwende Fallback-Lektionen")
|
||||
lessons = FALLBACK_LESSONS
|
||||
else:
|
||||
print(f"Warnung: Datei {DATA_PATH} nicht gefunden. Verwende Fallback-Lektionen.")
|
||||
lessons = FALLBACK_LESSONS
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Fehler beim Laden der JSON-Datei: {e}")
|
||||
print(f"Position des Fehlers: Zeile {e.lineno}, Spalte {e.colno}")
|
||||
print("Verwende Fallback-Lektionen")
|
||||
lessons = FALLBACK_LESSONS
|
||||
|
||||
except Exception as e:
|
||||
print(f"Unerwarteter Fehler beim Laden der Lektionen: {e}")
|
||||
print("Verwende Fallback-Lektionen")
|
||||
lessons = FALLBACK_LESSONS
|
||||
|
||||
def create_sample_lessons():
|
||||
"""Erstellt eine korrekte Beispiel-JSON-Datei falls nötig"""
|
||||
sample_lessons = [
|
||||
{
|
||||
"id": 1,
|
||||
"text": "Willkommen beim Tipptrainer! Übe das Tippen mit diesem einfachen Text.",
|
||||
"level": "beginner",
|
||||
"description": "Einführungstext für Anfänger"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"text": "Die schnelle braune Fuchs springt über den faulen Hund. Dies ist ein klassischer Pangramm-Satz.",
|
||||
"level": "beginner",
|
||||
"description": "Pangramm-Übung mit allen Buchstaben"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"text": "Programmieren mit Python macht großen Spaß und ermöglicht die Entwicklung vielfältiger Anwendungen.",
|
||||
"level": "intermediate",
|
||||
"description": "Programmierbezogener Text"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"text": "Flüssiges und fehlerfreies Tippen ist in der heutigen digitalen Welt eine wertvolle Fähigkeit für viele Berufe.",
|
||||
"level": "intermediate",
|
||||
"description": "Berufsbezogene Übung"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"text": "Übung macht den Meister: Regelmäßiges Training verbessert nicht nur die Geschwindigkeit, sondern auch die Genauigkeit beim Tippen erheblich.",
|
||||
"level": "advanced",
|
||||
"description": "Längerer Text für Fortgeschrittene"
|
||||
}
|
||||
]
|
||||
|
||||
try:
|
||||
with open(DATA_PATH, "w", encoding="utf-8") as f:
|
||||
json.dump(sample_lessons, f, indent=2, ensure_ascii=False)
|
||||
print(f"Beispiel-Lektionen erstellt in: {DATA_PATH}")
|
||||
return sample_lessons
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Erstellen der Beispiel-Lektionen: {e}")
|
||||
return FALLBACK_LESSONS
|
||||
|
||||
# Datenbanktabellen erstellen und Lektionen laden
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
print("Datenbanktabellen wurden initialisiert")
|
||||
|
||||
# Versuche Lektionen zu laden
|
||||
load_lessons()
|
||||
|
||||
# Falls keine Lektionen geladen wurden, erstelle Beispieldaten
|
||||
if not lessons:
|
||||
print("Keine Lektionen verfügbar, erstelle Beispiel-Lektionen...")
|
||||
lessons = create_sample_lessons()
|
||||
|
||||
print(f"{len(lessons)} Lektionen geladen")
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
# Hole den aktuellen Fortschritt aus der Datenbank
|
||||
progress = Progress.query.first()
|
||||
|
||||
if not progress:
|
||||
# Falls keine Daten vorhanden sind, initialisiere mit ersten Text
|
||||
if lessons:
|
||||
initial_text = lessons[0]['text']
|
||||
progress = Progress(
|
||||
current_text_index=0,
|
||||
current_position=0,
|
||||
last_text=initial_text
|
||||
)
|
||||
db.session.add(progress)
|
||||
db.session.commit()
|
||||
print(f"Neuen Fortschritt initialisiert mit Text: {initial_text[:50]}...")
|
||||
"""Hauptseite des Tipptrainers"""
|
||||
try:
|
||||
# Hole den aktuellen Fortschritt aus der Datenbank
|
||||
progress = Progress.query.first()
|
||||
|
||||
if not progress:
|
||||
# Falls keine Daten vorhanden sind, initialisiere mit ersten Text
|
||||
if lessons:
|
||||
initial_text = lessons[0]['text']
|
||||
progress = Progress(
|
||||
current_text_index=0,
|
||||
current_position=0,
|
||||
last_text=initial_text
|
||||
)
|
||||
db.session.add(progress)
|
||||
db.session.commit()
|
||||
print(f"Neuen Fortschritt initialisiert mit Text: {initial_text[:50]}...")
|
||||
else:
|
||||
# Fallback, falls lessons immer noch leer sein sollte
|
||||
progress = Progress(
|
||||
current_text_index=0,
|
||||
current_position=0,
|
||||
last_text="Keine Lektionen verfügbar"
|
||||
)
|
||||
db.session.add(progress)
|
||||
db.session.commit()
|
||||
|
||||
# Hole den aktuellen Text
|
||||
current_text = ""
|
||||
if lessons and progress.current_text_index < len(lessons):
|
||||
current_text = lessons[progress.current_text_index]['text']
|
||||
else:
|
||||
# Fallback, falls lessons immer noch leer sein sollte
|
||||
progress = Progress(
|
||||
current_text_index=0,
|
||||
current_position=0,
|
||||
last_text="Keine Lektionen verfügbar"
|
||||
)
|
||||
db.session.add(progress)
|
||||
# Fallback, falls Index ungültig ist
|
||||
current_text = lessons[0]['text'] if lessons else "Keine Lektionen verfügbar"
|
||||
progress.current_text_index = 0
|
||||
progress.current_position = 0
|
||||
progress.last_text = current_text
|
||||
db.session.commit()
|
||||
|
||||
# Hole den aktuellen Text
|
||||
current_text = ""
|
||||
if lessons and progress.current_text_index < len(lessons):
|
||||
current_text = lessons[progress.current_text_index]['text']
|
||||
else:
|
||||
# Fallback, falls Index ungültig ist
|
||||
current_text = lessons[0]['text'] if lessons else "Keine Lektionen verfügbar"
|
||||
progress.current_text_index = 0
|
||||
progress.current_position = 0
|
||||
progress.last_text = current_text
|
||||
db.session.commit()
|
||||
|
||||
print(f"Rendering Index - Current Text: {current_text[:50]}...")
|
||||
|
||||
return render_template('index.html',
|
||||
current_text=current_text,
|
||||
current_position=progress.current_position,
|
||||
last_text=progress.last_text,
|
||||
lessons=lessons)
|
||||
|
||||
print(f"Rendering Index - Current Text Länge: {len(current_text)} Zeichen")
|
||||
|
||||
return render_template('index.html',
|
||||
current_text=current_text,
|
||||
current_position=progress.current_position,
|
||||
last_text=progress.last_text,
|
||||
lessons=lessons)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler in index(): {e}")
|
||||
return render_template('index.html',
|
||||
current_text="Fehler beim Laden der Lektionen",
|
||||
current_position=0,
|
||||
last_text="",
|
||||
lessons=[])
|
||||
|
||||
@app.route('/debug')
|
||||
def debug():
|
||||
|
|
@ -102,55 +214,133 @@ def debug():
|
|||
|
||||
# Versuche, den Inhalt der JSON-Datei zu lesen
|
||||
json_content = "Datei nicht gefunden"
|
||||
json_error = None
|
||||
try:
|
||||
if os.path.exists(DATA_PATH):
|
||||
with open(DATA_PATH, "r", encoding="utf-8") as f:
|
||||
json_content = f.read()
|
||||
except Exception as e:
|
||||
json_content = f"Fehler beim Lesen der Datei: {e}"
|
||||
json_error = str(e)
|
||||
|
||||
debug_info = {
|
||||
'lessons_loaded': len(lessons),
|
||||
'lessons_content': lessons,
|
||||
'lessons_sample': lessons[:3] if lessons else [], # Nur erste 3 anzeigen
|
||||
'data_path': DATA_PATH,
|
||||
'file_exists': os.path.exists(DATA_PATH),
|
||||
'file_content': json_content[:500] + '...' if len(json_content) > 500 else json_content,
|
||||
'file_size': os.path.getsize(DATA_PATH) if os.path.exists(DATA_PATH) else 0,
|
||||
'file_content_preview': json_content[:500] + '...' if len(json_content) > 500 else json_content,
|
||||
'json_error': json_error,
|
||||
'current_progress': {
|
||||
'current_text_index': progress.current_text_index if progress else None,
|
||||
'current_position': progress.current_position if progress else None,
|
||||
'last_text': progress.last_text[:100] + '...' if progress and progress.last_text else None
|
||||
'last_text_length': len(progress.last_text) if progress and progress.last_text else 0,
|
||||
'last_text_preview': progress.last_text[:100] + '...' if progress and progress.last_text and len(progress.last_text) > 100 else progress.last_text if progress else None
|
||||
} if progress else None,
|
||||
'app_root_path': app.root_path
|
||||
'app_root_path': app.root_path,
|
||||
'fallback_used': lessons == FALLBACK_LESSONS
|
||||
}
|
||||
|
||||
return jsonify(debug_info)
|
||||
|
||||
@app.route('/repair_json', methods=['POST'])
|
||||
def repair_json_endpoint():
|
||||
"""Endpoint zum manuellen Reparieren der JSON-Datei"""
|
||||
try:
|
||||
if os.path.exists(DATA_PATH):
|
||||
with open(DATA_PATH, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
repaired_content = repair_json(content)
|
||||
|
||||
# Validiere das reparierte JSON
|
||||
try:
|
||||
json.loads(repaired_content)
|
||||
|
||||
# Backup der originalen Datei erstellen
|
||||
backup_path = DATA_PATH + ".backup"
|
||||
with open(backup_path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
|
||||
# Reparierte Version speichern
|
||||
with open(DATA_PATH, "w", encoding="utf-8") as f:
|
||||
f.write(repaired_content)
|
||||
|
||||
# Lektionen neu laden
|
||||
load_lessons()
|
||||
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'message': f'JSON erfolgreich repariert. Backup erstellt: {backup_path}',
|
||||
'lessons_loaded': len(lessons)
|
||||
})
|
||||
|
||||
except json.JSONDecodeError as e:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': f'Repariertes JSON ist immer noch ungültig: {e}'
|
||||
}), 400
|
||||
else:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': 'JSON-Datei nicht gefunden'
|
||||
}), 404
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': f'Fehler beim Reparieren: {e}'
|
||||
}), 500
|
||||
|
||||
@app.route('/recreate_lessons', methods=['POST'])
|
||||
def recreate_lessons():
|
||||
"""Endpoint zum Neuerstellen der Lektionen-Datei"""
|
||||
global lessons
|
||||
try:
|
||||
lessons = create_sample_lessons()
|
||||
return jsonify({
|
||||
'status': 'success',
|
||||
'message': 'Beispiel-Lektionen erfolgreich erstellt',
|
||||
'lessons_loaded': len(lessons)
|
||||
})
|
||||
except Exception as e:
|
||||
return jsonify({
|
||||
'status': 'error',
|
||||
'message': f'Fehler beim Erstellen der Lektionen: {e}'
|
||||
}), 500
|
||||
|
||||
@app.route('/update_progress', methods=['POST'])
|
||||
def update_progress():
|
||||
data = request.get_json()
|
||||
user_input = data.get('user_input', '')
|
||||
current_text = data.get('current_text', '')
|
||||
|
||||
# Hole den aktuellen Fortschritt
|
||||
progress = Progress.query.first()
|
||||
|
||||
if progress:
|
||||
# Aktualisiere den Fortschritt
|
||||
progress.current_position = len(user_input)
|
||||
progress.last_text = user_input
|
||||
"""Aktualisiert den Tippfortschritt"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
user_input = data.get('user_input', '')
|
||||
current_text = data.get('current_text', '')
|
||||
|
||||
# Speichere in der Datenbank
|
||||
db.session.commit()
|
||||
# Hole den aktuellen Fortschritt
|
||||
progress = Progress.query.first()
|
||||
|
||||
# Berechne Statistiken
|
||||
stats = calculate_statistics(user_input, current_text)
|
||||
|
||||
return jsonify(stats)
|
||||
else:
|
||||
return jsonify({'error': 'Progress nicht gefunden'}), 400
|
||||
if progress:
|
||||
# Aktualisiere den Fortschritt
|
||||
progress.current_position = len(user_input)
|
||||
progress.last_text = user_input
|
||||
|
||||
# Speichere in der Datenbank
|
||||
db.session.commit()
|
||||
|
||||
# Berechne Statistiken
|
||||
stats = calculate_statistics(user_input, current_text)
|
||||
|
||||
return jsonify(stats)
|
||||
else:
|
||||
return jsonify({'error': 'Progress nicht gefunden'}), 400
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler in update_progress: {e}")
|
||||
return jsonify({'error': 'Interner Serverfehler'}), 500
|
||||
|
||||
def calculate_statistics(user_input, current_text):
|
||||
# Berechne die Anzahl der korrekten und falschen Zeichen
|
||||
"""Berechnet Tippstatistiken"""
|
||||
correct_chars = 0
|
||||
incorrect_chars = 0
|
||||
total_chars = len(user_input)
|
||||
|
|
@ -161,35 +351,13 @@ def calculate_statistics(user_input, current_text):
|
|||
else:
|
||||
incorrect_chars += 1
|
||||
|
||||
# Berechne Fehlerquote
|
||||
error_rate = 0
|
||||
if total_chars > 0:
|
||||
error_rate = (incorrect_chars / total_chars) * 100
|
||||
|
||||
# Berechne durchschnittliche Tippgeschwindigkeit (Zeichen pro Minute)
|
||||
if total_chars > 0:
|
||||
# Simulierte Zeit: Je länger der Text, desto länger dauert es
|
||||
time_taken = total_chars * 0.05 # Sekunden
|
||||
typing_speed = (total_chars / time_taken) * 60 if time_taken > 0 else 0
|
||||
else:
|
||||
typing_speed = 0
|
||||
time_taken = 0
|
||||
|
||||
# Zeichen pro Sekunden
|
||||
duration_per_char = 0
|
||||
if total_chars > 0 and time_taken > 0:
|
||||
duration_per_char = time_taken / total_chars
|
||||
|
||||
# Berechne Wörter pro Minute (WPM)
|
||||
words_per_minute = 0
|
||||
if total_chars > 0 and time_taken > 0:
|
||||
# WPM = (Gesamtzeichen / 5) / (Gesamtzeit in Minuten)
|
||||
words_per_minute = (total_chars / 5) / (time_taken / 60)
|
||||
|
||||
# Berechne Genauigkeit
|
||||
accuracy = 0
|
||||
if total_chars > 0:
|
||||
accuracy = (correct_chars / total_chars) * 100
|
||||
# Berechne verschiedene Metriken
|
||||
error_rate = (incorrect_chars / total_chars * 100) if total_chars > 0 else 0
|
||||
time_taken = total_chars * 0.05 # Simulierte Zeit
|
||||
typing_speed = (total_chars / time_taken * 60) if time_taken > 0 else 0
|
||||
duration_per_char = (time_taken / total_chars) if total_chars > 0 and time_taken > 0 else 0
|
||||
words_per_minute = ((total_chars / 5) / (time_taken / 60)) if total_chars > 0 and time_taken > 0 else 0
|
||||
accuracy = (correct_chars / total_chars * 100) if total_chars > 0 else 0
|
||||
|
||||
return {
|
||||
'correct_chars': correct_chars,
|
||||
|
|
@ -204,56 +372,53 @@ def calculate_statistics(user_input, current_text):
|
|||
|
||||
@app.route('/next_text', methods=['POST'])
|
||||
def next_text():
|
||||
progress = Progress.query.first()
|
||||
|
||||
if not progress:
|
||||
return jsonify({'error': 'Progress nicht gefunden'}), 400
|
||||
|
||||
# Wechsel zum nächsten Text
|
||||
if lessons and progress.current_text_index < len(lessons) - 1:
|
||||
progress.current_text_index += 1
|
||||
else:
|
||||
# Wenn wir am Ende sind, starten wir von vorne
|
||||
progress.current_text_index = 0
|
||||
|
||||
progress.current_position = 0
|
||||
progress.last_text = lessons[progress.current_text_index]['text'] if lessons else ""
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'text': lessons[progress.current_text_index]['text'] if lessons else ""
|
||||
})
|
||||
"""Wechselt zum nächsten Text"""
|
||||
try:
|
||||
progress = Progress.query.first()
|
||||
|
||||
if not progress:
|
||||
return jsonify({'error': 'Progress nicht gefunden'}), 400
|
||||
|
||||
if not lessons:
|
||||
return jsonify({'error': 'Keine Lektionen verfügbar'}), 400
|
||||
|
||||
# Wechsel zum nächsten Text
|
||||
if progress.current_text_index < len(lessons) - 1:
|
||||
progress.current_text_index += 1
|
||||
else:
|
||||
# Wenn wir am Ende sind, starten wir von vorne
|
||||
progress.current_text_index = 0
|
||||
|
||||
progress.current_position = 0
|
||||
progress.last_text = lessons[progress.current_text_index]['text']
|
||||
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({
|
||||
'text': lessons[progress.current_text_index]['text'],
|
||||
'index': progress.current_text_index,
|
||||
'total': len(lessons)
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Fehler in next_text: {e}")
|
||||
return jsonify({'error': 'Interner Serverfehler'}), 500
|
||||
|
||||
@app.route('/end_session', methods=['POST'])
|
||||
def end_session():
|
||||
# Speichere den aktuellen Fortschritt
|
||||
progress = Progress.query.first()
|
||||
|
||||
# Erstelle oder aktualisiere Statistik
|
||||
statistic = Statistic.query.first()
|
||||
if not statistic:
|
||||
# Erstelle eine neue Statistik mit allen Spalten
|
||||
statistic = Statistic(
|
||||
total_chars=0,
|
||||
correct_chars=0,
|
||||
incorrect_chars=0,
|
||||
error_rate=0,
|
||||
typing_speed=0,
|
||||
duration_per_char=0,
|
||||
accuracy=0,
|
||||
wpm=0
|
||||
)
|
||||
db.session.add(statistic)
|
||||
|
||||
# Aktualisiere Statistik (in einer echten Anwendung würden wir hier die Werte berechnen)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'status': 'success'})
|
||||
"""Beendet eine Tipp-Session und speichert Statistiken"""
|
||||
try:
|
||||
# Hier könnten erweiterte Statistiken gespeichert werden
|
||||
return jsonify({'status': 'success', 'message': 'Session beendet'})
|
||||
except Exception as e:
|
||||
print(f"Fehler in end_session: {e}")
|
||||
return jsonify({'error': 'Interner Serverfehler'}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
with app.app_context():
|
||||
print("Datenbank initialisiert")
|
||||
print(f"{len(lessons)} Lektionen geladen")
|
||||
print("=" * 50)
|
||||
print("Tipptrainer gestartet")
|
||||
print(f"Lektionen geladen: {len(lessons)}")
|
||||
print(f"Debug-Info verfügbar unter: http://127.0.0.1:5000/debug")
|
||||
print("=" * 50)
|
||||
|
||||
app.run(debug=True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue