app/app.py aktualisiert

This commit is contained in:
sascha 2026-01-28 15:58:43 +00:00
parent 615d39c79c
commit 795502fe7a

View File

@ -7,9 +7,10 @@ from difflib import SequenceMatcher
app = Flask(__name__) app = Flask(__name__)
# DB Verbindung mit Retry-Logik # --- DATENBANK VERBINDUNG ---
def get_db_connection(): def get_db_connection():
max_retries = 5 """Stellt Verbindung zur Postgres-DB her, mit Warteschleife beim Start."""
max_retries = 10
for i in range(max_retries): for i in range(max_retries):
try: try:
conn = psycopg2.connect( conn = psycopg2.connect(
@ -21,45 +22,44 @@ def get_db_connection():
return conn return conn
except psycopg2.OperationalError: except psycopg2.OperationalError:
if i < max_retries - 1: if i < max_retries - 1:
print("Datenbank noch nicht bereit... warte 2 Sekunden.")
time.sleep(2) time.sleep(2)
continue continue
else: else:
print("FEHLER: Konnte keine Verbindung zur Datenbank herstellen.")
raise raise
# Erweiterte Init-Funktion: Checkt auf BEIDE Tabellen # --- RESET LOGIK (STATELESS CONTAINER) ---
def init_db_if_needed(): def reset_db_on_startup():
"""Löscht beim Start alles und lädt die init.sql neu."""
print("Starte Datenbank-Reset...")
try: try:
conn = get_db_connection() conn = get_db_connection()
cur = conn.cursor() cur = conn.cursor()
# Check ob alte Vokabeltabelle da ist # Wir lesen die init.sql und führen sie aus.
cur.execute("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'vocabulary');") # Voraussetzung: In init.sql steht am Anfang 'DROP TABLE IF EXISTS ...'
vocab_exists = cur.fetchone()[0] if os.path.exists('init.sql'):
with open('init.sql', 'r', encoding='utf-8') as f:
# Check ob NEUE Verben-Tabelle da ist sql_commands = f.read()
cur.execute("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'irregular_verbs');") cur.execute(sql_commands)
verbs_exists = cur.fetchone()[0] conn.commit()
print("Datenbank erfolgreich neu aufgebaut (Clean State)!")
# Wenn eine fehlt, führen wir init.sql aus (dank "CREATE TABLE IF NOT EXISTS" ist das sicher) else:
if not vocab_exists or not verbs_exists: print("WARNUNG: init.sql nicht gefunden! Datenbank könnte leer sein.")
print("Datenbank unvollständig. Initialisiere aus init.sql...")
if os.path.exists('init.sql'):
with open('init.sql', 'r', encoding='utf-8') as f:
sql_commands = f.read()
cur.execute(sql_commands)
conn.commit()
print("Datenbank initialisiert.")
cur.close() cur.close()
conn.close() conn.close()
except Exception as e: except Exception as e:
print(f"Fehler bei DB-Init: {e}") print(f"KRITISCHER FEHLER beim DB-Reset: {e}")
# Beim Start einmal ausführen # Dies wird ausgeführt, sobald der Container startet (bevor der Webserver läuft)
init_db_if_needed() reset_db_on_startup()
# --- HILFSFUNKTIONEN ---
def expand_variants(text): def expand_variants(text):
""" Zerlegt 'Wort1; Wort2' in eine Liste """ """Zerlegt 'Wort1; Wort2' in eine Liste für den Vergleich."""
parts = text.split(';') parts = text.split(';')
results = [] results = []
for p in parts: for p in parts:
@ -74,44 +74,61 @@ def expand_variants(text):
if '(' in r: if '(' in r:
without_parens = r.replace('(', '').replace(')', '').strip() without_parens = r.replace('(', '').replace(')', '').strip()
final_res.append(without_parens) final_res.append(without_parens)
# Optional: auch Version ganz ohne Inhalt der Klammer?
# Hier einfachheitshalber: Klammern weg ist die Variante.
return final_res return final_res
def clean_text_for_comparison(text): def clean_text_for_comparison(text):
"""Macht Text klein und entfernt Leerzeichen."""
return text.lower().strip() return text.lower().strip()
# --- ROUTEN ---
@app.route('/') @app.route('/')
def index(): def index():
return render_template('index.html') return render_template('index.html')
@app.route('/api/pages') @app.route('/api/pages')
def get_pages(): def get_pages():
"""Liefert verfügbare Buchseiten."""
conn = get_db_connection() conn = get_db_connection()
cur = conn.cursor() cur = conn.cursor()
# Seiten aus der Vokabel-Tabelle
cur.execute("SELECT DISTINCT page FROM vocabulary ORDER BY page") cur.execute("SELECT DISTINCT page FROM vocabulary ORDER BY page")
rows = cur.fetchall() rows = cur.fetchall()
# Seiten aus der Verben-Tabelle (falls vorhanden)
cur.execute("SELECT DISTINCT page FROM irregular_verbs ORDER BY page")
rows_verbs = cur.fetchall()
cur.close() cur.close()
conn.close() conn.close()
pages = [r[0] for r in rows]
# Füge die Verben-Seiten manuell hinzu, falls noch nicht in vocabulary # Listen zusammenführen und sortieren
for p in [206, 207]: pages = set([r[0] for r in rows])
if p not in pages: for r in rows_verbs:
pages.append(p) pages.add(r[0])
pages.sort()
return jsonify(pages) sorted_pages = sorted(list(pages))
return jsonify(sorted_pages)
@app.route('/api/question', methods=['POST']) @app.route('/api/question', methods=['POST'])
def get_question(): def get_question():
"""Holt eine zufällige Frage basierend auf Modus und Seiten."""
data = request.json data = request.json
mode = data.get('mode', 'random') mode = data.get('mode', 'random')
pages = data.get('pages', []) pages = data.get('pages', [])
# Fallback: Wenn keine Seiten gewählt, nimm einfach Seite 206 (oder leer)
if not pages:
# Optional: Hier könnte man "alle" Logik einbauen,
# aber Frontend sendet meistens was.
pass
conn = get_db_connection() conn = get_db_connection()
cur = conn.cursor() cur = conn.cursor()
# --- NEU: UNREGELMÄSSIGE VERBEN MODUS --- # --- MODUS: UNREGELMÄSSIGE VERBEN ---
if mode == 'irregular': if mode == 'irregular':
query = "SELECT infinitive, simple_past, past_participle, german, page FROM irregular_verbs WHERE page = ANY(%s) ORDER BY RANDOM() LIMIT 1" query = "SELECT infinitive, simple_past, past_participle, german, page FROM irregular_verbs WHERE page = ANY(%s) ORDER BY RANDOM() LIMIT 1"
cur.execute(query, (pages,)) cur.execute(query, (pages,))
@ -120,18 +137,18 @@ def get_question():
conn.close() conn.close()
if not row: if not row:
return jsonify({'error': 'Keine Verben für diese Seiten gefunden.'}) return jsonify({'error': 'Keine Verben auf diesen Seiten gefunden (Wähle 206 oder 207).'})
return jsonify({ return jsonify({
'type': 'irregular', 'type': 'irregular',
'question': row[0], # Infinitive 'question': row[0], # Infinitive (z.B. "go")
'german_hint': row[3], 'german_hint': row[3], # Deutsch (z.B. "gehen")
'answer_simple': row[1], 'answer_simple': row[1], # Simple Past (z.B. "went")
'answer_participle': row[2], 'answer_participle': row[2], # Past Participle (z.B. "gone")
'page': row[4] 'page': row[4]
}) })
# --- ALTER MODUS (Vokabeln) --- # --- MODUS: NORMALE VOKABELN ---
else: else:
query = "SELECT english, german, page FROM vocabulary WHERE page = ANY(%s) ORDER BY RANDOM() LIMIT 1" query = "SELECT english, german, page FROM vocabulary WHERE page = ANY(%s) ORDER BY RANDOM() LIMIT 1"
cur.execute(query, (pages,)) cur.execute(query, (pages,))
@ -140,7 +157,7 @@ def get_question():
conn.close() conn.close()
if not row: if not row:
return jsonify({'error': 'Keine Vokabeln gefunden.'}) return jsonify({'error': 'Keine Vokabeln auf den gewählten Seiten gefunden.'})
question_text = "" question_text = ""
answer_text = "" answer_text = ""
@ -168,9 +185,10 @@ def get_question():
@app.route('/api/check', methods=['POST']) @app.route('/api/check', methods=['POST'])
def check_answer(): def check_answer():
"""Prüft die Antwort des Nutzers."""
data = request.json data = request.json
# --- NEU: CHECK FÜR VERBEN --- # --- CHECK: UNREGELMÄSSIGE VERBEN ---
if data.get('type') == 'irregular': if data.get('type') == 'irregular':
user_simple = clean_text_for_comparison(data.get('input_simple', '')) user_simple = clean_text_for_comparison(data.get('input_simple', ''))
user_participle = clean_text_for_comparison(data.get('input_participle', '')) user_participle = clean_text_for_comparison(data.get('input_participle', ''))
@ -178,8 +196,9 @@ def check_answer():
correct_simple = data.get('correct_simple', '') correct_simple = data.get('correct_simple', '')
correct_participle = data.get('correct_participle', '') correct_participle = data.get('correct_participle', '')
# Wir müssen "was/were" oder "burnt/burned" handhaben -> split am slash # Hilfsfunktion: Prüft ob Eingabe in "was/were" enthalten ist
def check_part(user_in, correct_str): def check_part(user_in, correct_str):
# correct_str könnte "was/were" oder "burnt/burned" sein
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
@ -189,12 +208,17 @@ def check_answer():
if is_simple_ok and is_participle_ok: if is_simple_ok and is_participle_ok:
return jsonify({'status': 'correct', 'msg': 'Perfekt! Beide Formen richtig.'}) return jsonify({'status': 'correct', 'msg': 'Perfekt! Beide Formen richtig.'})
else: else:
# Detailliertes Feedback, was falsch war
msg = "Leider falsch."
if not is_simple_ok: msg += f" Simple Past war '{user_simple}' (Richtig: {correct_simple})."
if not is_participle_ok: msg += f" Participle war '{user_participle}' (Richtig: {correct_participle})."
return jsonify({ return jsonify({
'status': 'wrong', 'status': 'wrong',
'msg': f'Leider falsch. Lösung: {correct_simple} | {correct_participle}' 'msg': f'Lösung: {correct_simple} | {correct_participle}'
}) })
# --- ALTER CHECK (Vokabeln) --- # --- CHECK: NORMALE VOKABELN ---
else: else:
user_input = data.get('input', '') user_input = data.get('input', '')
db_answer = data.get('correct', '') db_answer = data.get('correct', '')
@ -206,7 +230,7 @@ def check_answer():
if clean_user in valid_answers_clean: if clean_user in valid_answers_clean:
return jsonify({'status': 'correct', 'msg': 'Richtig!'}) return jsonify({'status': 'correct', 'msg': 'Richtig!'})
# Fuzzy Logic (Typo Check) # Fuzzy Logic (Tippfehler erkennen)
best_ratio = 0 best_ratio = 0
best_match = "" best_match = ""
for correct_option in valid_answers_clean: for correct_option in valid_answers_clean:
@ -217,7 +241,7 @@ def check_answer():
threshold = 0.85 threshold = 0.85
if len(best_match) <= 4: if len(best_match) <= 4:
threshold = 0.9 threshold = 0.9 # Bei kurzen Wörtern strenger sein
if best_ratio >= threshold: if best_ratio >= threshold:
return jsonify({ return jsonify({