app/app.py aktualisiert

This commit is contained in:
sascha 2026-01-28 19:30:07 +00:00
parent 4b4d5d117e
commit 090ccc9892

View File

@ -2,11 +2,17 @@ import os
import random import random
import time import time
import psycopg2 import psycopg2
from flask import Flask, render_template, request, jsonify from flask import Flask, render_template, request, jsonify, session
from difflib import SequenceMatcher from difflib import SequenceMatcher
app = Flask(__name__) app = Flask(__name__)
# Sessions Cookies
app.secret_key = 'super-secret-key-for-english-trainer'
# Wie viele der letzten Wörter sollen pro Nutzer blockiert werden?
HISTORY_LIMIT = 15
# --- DB VERBINDUNG & RESET --- # --- DB VERBINDUNG & RESET ---
def get_db_connection(): def get_db_connection():
max_retries = 10 max_retries = 10
@ -43,32 +49,35 @@ reset_db_on_startup()
# --- HILFSFUNKTIONEN --- # --- HILFSFUNKTIONEN ---
def clean_text(text): def clean_text(text):
"""Basis-Reinigung für Verben-Input"""
return text.lower().strip() return text.lower().strip()
def clean_vocab_input(text): def clean_vocab_input(text):
"""
Spezielle Reinigung für Vokabeln:
1. Kleinbuchstaben & Leerzeichen weg.
2. Führendes 'to ' entfernen (damit 'go' == 'to go').
3. Klammern entfernen (z.B. '(to)' -> weg).
"""
t = text.lower().strip() t = text.lower().strip()
# Klammern entfernen
t = t.replace('(', '').replace(')', '').replace('[', '').replace(']', '') t = t.replace('(', '').replace(')', '').replace('[', '').replace(']', '')
t = t.strip() t = t.strip()
# Führendes "to " entfernen (nur wenn es ein ganzes Wort ist)
if t.startswith('to '): if t.startswith('to '):
t = t[3:].strip() t = t[3:].strip()
return t return t
def check_part(user_in, correct_str): def check_part(user_in, correct_str):
variants = [x.strip().lower() for x in correct_str.split('/')] variants = [x.strip().lower() for x in correct_str.split('/')]
return user_in in variants return user_in in variants
def update_user_history(word):
"""Speichert das Wort im Cookie des Nutzers"""
# History aus Cookie laden (oder leere Liste)
history = session.get('history', [])
# Wort hinzufügen
history.append(word)
# Begrenzen auf die letzten X
if len(history) > HISTORY_LIMIT:
history = history[-HISTORY_LIMIT:]
# Zurück in den Cookie speichern
session['history'] = history
# --- API ROUTEN --- # --- API ROUTEN ---
@app.route('/') @app.route('/')
@ -102,15 +111,39 @@ def get_question():
conn = get_db_connection() conn = get_db_connection()
cur = conn.cursor() cur = conn.cursor()
# Persönliche History aus dem Cookie holen
user_history = session.get('history', [])
row = None
# --- LOGIK FÜR UNREGELMÄSSIGE VERBEN ---
if main_mode == 'irregular': if main_mode == 'irregular':
query = "SELECT infinitive, simple_past, past_participle, german, page FROM irregular_verbs WHERE page = ANY(%s) ORDER BY RANDOM() LIMIT 1" # Wir bauen den Query dynamisch: Schließe Wörter aus History aus
cur.execute(query, (pages,)) if user_history:
query_smart = "SELECT infinitive, simple_past, past_participle, german, page FROM irregular_verbs WHERE page = ANY(%s) AND infinitive NOT IN %s ORDER BY RANDOM() LIMIT 1"
cur.execute(query_smart, (pages, tuple(user_history)))
else:
query_smart = "SELECT infinitive, simple_past, past_participle, german, page FROM irregular_verbs WHERE page = ANY(%s) ORDER BY RANDOM() LIMIT 1"
cur.execute(query_smart, (pages,))
row = cur.fetchone() row = cur.fetchone()
cur.close()
conn.close()
if not row: return jsonify({'error': 'Keine Verben gefunden.'}) # Fallback: Wenn durch History keine Wörter mehr übrig sind -> Reset (Zufall aus allem)
if not row:
print("User History voll/blockiert alles -> Fallback!")
query_fallback = "SELECT infinitive, simple_past, past_participle, german, page FROM irregular_verbs WHERE page = ANY(%s) ORDER BY RANDOM() LIMIT 1"
cur.execute(query_fallback, (pages,))
row = cur.fetchone()
if not row:
cur.close(); conn.close()
return jsonify({'error': 'Keine Verben gefunden.'})
# Speichern im Cookie (Infinitive)
update_user_history(row[0])
cur.close(); conn.close()
if sub_mode == 'start-german': if sub_mode == 'start-german':
return jsonify({ return jsonify({
@ -131,14 +164,32 @@ def get_question():
'page': row[4] 'page': row[4]
}) })
# --- LOGIK FÜR NORMALE VOKABELN ---
else: else:
query = "SELECT english, german, page FROM vocabulary WHERE page = ANY(%s) ORDER BY RANDOM() LIMIT 1" # Auch hier: Exclude History
cur.execute(query, (pages,)) if user_history:
query_smart = "SELECT english, german, page FROM vocabulary WHERE page = ANY(%s) AND english NOT IN %s ORDER BY RANDOM() LIMIT 1"
cur.execute(query_smart, (pages, tuple(user_history)))
else:
query_smart = "SELECT english, german, page FROM vocabulary WHERE page = ANY(%s) ORDER BY RANDOM() LIMIT 1"
cur.execute(query_smart, (pages,))
row = cur.fetchone() row = cur.fetchone()
cur.close()
conn.close()
if not row: return jsonify({'error': 'Keine Vokabeln gefunden.'}) # Fallback
if not row:
query_fallback = "SELECT english, german, page FROM vocabulary WHERE page = ANY(%s) ORDER BY RANDOM() LIMIT 1"
cur.execute(query_fallback, (pages,))
row = cur.fetchone()
if not row:
cur.close(); conn.close()
return jsonify({'error': 'Keine Vokabeln gefunden.'})
# Speichern im Cookie (Englisch als ID)
update_user_history(row[0])
cur.close(); conn.close()
q_text, a_text = "", "" q_text, a_text = "", ""
if sub_mode == 'de-en': if sub_mode == 'de-en':
@ -161,11 +212,10 @@ def check_answer():
data = request.json data = request.json
q_type = data.get('type') q_type = data.get('type')
# --- 1. & 2. VERBEN (Keine Typos, strikt) --- # --- VERBEN ---
if q_type.startswith('irregular'): if q_type.startswith('irregular'):
is_correct = False is_correct = False
msg = ""
if q_type == 'irregular_full': if q_type == 'irregular_full':
u_inf = clean_text(data.get('infinitive', '')) u_inf = clean_text(data.get('infinitive', ''))
u_simp = clean_text(data.get('simple', '')) u_simp = clean_text(data.get('simple', ''))
@ -189,40 +239,31 @@ def check_answer():
if is_correct: if is_correct:
return jsonify({'status': 'correct', 'msg': 'Richtig!'}) return jsonify({'status': 'correct', 'msg': 'Richtig!'})
else: else:
# Bei Verben schicken wir KEINE Lösung im msg Feld, das macht das Frontend erst am Ende
return jsonify({'status': 'wrong', 'msg': 'Leider nicht ganz richtig.'}) return jsonify({'status': 'wrong', 'msg': 'Leider nicht ganz richtig.'})
# --- 3. VOKABELN (Mit "to"-Logik und Typos) --- # --- VOKABELN ---
else: else:
u_input_raw = data.get('input', '') u_input_raw = data.get('input', '')
correct_raw = data.get('correct', '') correct_raw = data.get('correct', '')
# Bereinigter Vergleich (ohne "to ", ohne Klammern)
u_clean = clean_vocab_input(u_input_raw) u_clean = clean_vocab_input(u_input_raw)
# Antwortmöglichkeiten vorbereiten (Splitten bei ';')
valid_answers = [] valid_answers = []
raw_parts = correct_raw.split(';') raw_parts = correct_raw.split(';')
for p in raw_parts: for p in raw_parts:
# Original rein
valid_answers.append(clean_vocab_input(p)) valid_answers.append(clean_vocab_input(p))
# Falls "(to) go" drin stand -> das wird durch clean_vocab_input schon zu "go"
# Aber falls "to go" drin stand -> wird zu "go"
# 1. Direkter Treffer
if u_clean in valid_answers: if u_clean in valid_answers:
return jsonify({'status': 'correct', 'msg': 'Richtig!'}) return jsonify({'status': 'correct', 'msg': 'Richtig!'})
# 2. Fuzzy Match (Rechtschreibprüfung)
best_ratio = 0 best_ratio = 0
best_match = "" best_match = ""
for v in valid_answers: for v in valid_answers:
ratio = SequenceMatcher(None, u_clean, v).ratio() ratio = SequenceMatcher(None, u_clean, v).ratio()
if ratio > best_ratio: if ratio > best_ratio:
best_ratio = ratio best_ratio = ratio
best_match = v # Das bereinigte Wort als Vorschlag best_match = v
# Toleranz: 85% Übereinstimmung, bei kurzen Wörtern (<5 Zeichen) strenger (90%)
threshold = 0.85 threshold = 0.85
if len(best_match) < 5: if len(best_match) < 5:
threshold = 0.9 threshold = 0.9
@ -233,7 +274,6 @@ def check_answer():
'msg': f'Fast richtig! Meintest du: "{best_match}"? (Achte auf die Schreibweise)' 'msg': f'Fast richtig! Meintest du: "{best_match}"? (Achte auf die Schreibweise)'
}) })
# 3. Falsch
return jsonify({'status': 'wrong', 'msg': 'Leider falsch.'}) return jsonify({'status': 'wrong', 'msg': 'Leider falsch.'})
if __name__ == '__main__': if __name__ == '__main__':