import os import random import time import psycopg2 from flask import Flask, render_template, request, jsonify from difflib import SequenceMatcher app = Flask(__name__) # DB Verbindung mit Retry-Logik def get_db_connection(): max_retries = 5 for i in range(max_retries): try: conn = psycopg2.connect( host=os.environ.get('DB_HOST', 'postgres'), database=os.environ.get('DB_NAME', 'englisch6a'), user=os.environ.get('DB_USER', 'englisch6a'), password=os.environ.get('DB_PASS', 'englisch') ) return conn except psycopg2.OperationalError: if i < max_retries - 1: time.sleep(2) continue else: raise # Erweiterte Init-Funktion: Checkt auf BEIDE Tabellen def init_db_if_needed(): try: conn = get_db_connection() cur = conn.cursor() # Check ob alte Vokabeltabelle da ist cur.execute("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'vocabulary');") vocab_exists = cur.fetchone()[0] # Check ob NEUE Verben-Tabelle da ist cur.execute("SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'irregular_verbs');") verbs_exists = cur.fetchone()[0] # Wenn eine fehlt, führen wir init.sql aus (dank "CREATE TABLE IF NOT EXISTS" ist das sicher) if not vocab_exists or not verbs_exists: 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() conn.close() except Exception as e: print(f"Fehler bei DB-Init: {e}") # Beim Start einmal ausführen init_db_if_needed() def expand_variants(text): """ Zerlegt 'Wort1; Wort2' in eine Liste """ parts = text.split(';') results = [] for p in parts: clean = p.strip() if clean: results.append(clean) # Umgang mit Klammern z.B. "(to) go" -> "to go" und "go" final_res = [] for r in results: final_res.append(r) if '(' in r: without_parens = r.replace('(', '').replace(')', '').strip() final_res.append(without_parens) # Optional: auch Version ganz ohne Inhalt der Klammer? # Hier einfachheitshalber: Klammern weg ist die Variante. return final_res def clean_text_for_comparison(text): return text.lower().strip() @app.route('/') def index(): return render_template('index.html') @app.route('/api/pages') def get_pages(): conn = get_db_connection() cur = conn.cursor() cur.execute("SELECT DISTINCT page FROM vocabulary ORDER BY page") rows = cur.fetchall() cur.close() conn.close() pages = [r[0] for r in rows] # Füge die Verben-Seiten manuell hinzu, falls noch nicht in vocabulary for p in [206, 207]: if p not in pages: pages.append(p) pages.sort() return jsonify(pages) @app.route('/api/question', methods=['POST']) def get_question(): data = request.json mode = data.get('mode', 'random') pages = data.get('pages', []) conn = get_db_connection() cur = conn.cursor() # --- NEU: UNREGELMÄSSIGE VERBEN MODUS --- if mode == 'irregular': 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,)) row = cur.fetchone() cur.close() conn.close() if not row: return jsonify({'error': 'Keine Verben für diese Seiten gefunden.'}) return jsonify({ 'type': 'irregular', 'question': row[0], # Infinitive 'german_hint': row[3], 'answer_simple': row[1], 'answer_participle': row[2], 'page': row[4] }) # --- ALTER MODUS (Vokabeln) --- else: query = "SELECT english, german, page FROM vocabulary WHERE page = ANY(%s) ORDER BY RANDOM() LIMIT 1" cur.execute(query, (pages,)) row = cur.fetchone() cur.close() conn.close() if not row: return jsonify({'error': 'Keine Vokabeln gefunden.'}) question_text = "" answer_text = "" if mode == 'de-en': question_text = row[1] # Deutsch answer_text = row[0] # Englisch elif mode == 'en-de': question_text = row[0] # Englisch answer_text = row[1] # Deutsch else: # random if random.random() > 0.5: question_text = row[1] answer_text = row[0] else: question_text = row[0] answer_text = row[1] return jsonify({ 'type': 'vocab', 'question': question_text, 'answer': answer_text, 'page': row[2] }) @app.route('/api/check', methods=['POST']) def check_answer(): data = request.json # --- NEU: CHECK FÜR VERBEN --- if data.get('type') == 'irregular': user_simple = clean_text_for_comparison(data.get('input_simple', '')) user_participle = clean_text_for_comparison(data.get('input_participle', '')) correct_simple = data.get('correct_simple', '') correct_participle = data.get('correct_participle', '') # Wir müssen "was/were" oder "burnt/burned" handhaben -> split am slash def check_part(user_in, correct_str): variants = [x.strip().lower() for x in correct_str.split('/')] return user_in in variants is_simple_ok = check_part(user_simple, correct_simple) is_participle_ok = check_part(user_participle, correct_participle) if is_simple_ok and is_participle_ok: return jsonify({'status': 'correct', 'msg': 'Perfekt! Beide Formen richtig.'}) else: return jsonify({ 'status': 'wrong', 'msg': f'Leider falsch. Lösung: {correct_simple} | {correct_participle}' }) # --- ALTER CHECK (Vokabeln) --- else: user_input = data.get('input', '') db_answer = data.get('correct', '') clean_user = clean_text_for_comparison(user_input) valid_answers_raw = expand_variants(db_answer) valid_answers_clean = [clean_text_for_comparison(v) for v in valid_answers_raw] if clean_user in valid_answers_clean: return jsonify({'status': 'correct', 'msg': 'Richtig!'}) # Fuzzy Logic (Typo Check) best_ratio = 0 best_match = "" for correct_option in valid_answers_clean: ratio = SequenceMatcher(None, clean_user, correct_option).ratio() if ratio > best_ratio: best_ratio = ratio best_match = correct_option threshold = 0.85 if len(best_match) <= 4: threshold = 0.9 if best_ratio >= threshold: return jsonify({ 'status': 'typo', 'msg': f'Fast richtig! Meintest du: "{best_match}"? (Es war "{user_input}")' }) return jsonify({'status': 'wrong', 'msg': f'Falsch. Lösung: {db_answer}'}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)