app/app.py aktualisiert
This commit is contained in:
parent
4b4d5d117e
commit
090ccc9892
116
app/app.py
116
app/app.py
@ -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__':
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user