From 795502fe7a96ed92a61ac3809dbeac4cc4a00599 Mon Sep 17 00:00:00 2001 From: sascha Date: Wed, 28 Jan 2026 15:58:43 +0000 Subject: [PATCH] app/app.py aktualisiert --- app/app.py | 124 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 74 insertions(+), 50 deletions(-) diff --git a/app/app.py b/app/app.py index 2ea03c7..8084561 100644 --- a/app/app.py +++ b/app/app.py @@ -7,9 +7,10 @@ from difflib import SequenceMatcher app = Flask(__name__) -# DB Verbindung mit Retry-Logik +# --- DATENBANK VERBINDUNG --- 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): try: conn = psycopg2.connect( @@ -21,45 +22,44 @@ def get_db_connection(): return conn except psycopg2.OperationalError: if i < max_retries - 1: + print("Datenbank noch nicht bereit... warte 2 Sekunden.") time.sleep(2) continue else: + print("FEHLER: Konnte keine Verbindung zur Datenbank herstellen.") raise -# Erweiterte Init-Funktion: Checkt auf BEIDE Tabellen -def init_db_if_needed(): +# --- RESET LOGIK (STATELESS CONTAINER) --- +def reset_db_on_startup(): + """Löscht beim Start alles und lädt die init.sql neu.""" + print("Starte Datenbank-Reset...") 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.") - + # Wir lesen die init.sql und führen sie aus. + # Voraussetzung: In init.sql steht am Anfang 'DROP TABLE IF EXISTS ...' + 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 erfolgreich neu aufgebaut (Clean State)!") + else: + print("WARNUNG: init.sql nicht gefunden! Datenbank könnte leer sein.") + cur.close() conn.close() except Exception as e: - print(f"Fehler bei DB-Init: {e}") + print(f"KRITISCHER FEHLER beim DB-Reset: {e}") -# Beim Start einmal ausführen -init_db_if_needed() +# Dies wird ausgeführt, sobald der Container startet (bevor der Webserver läuft) +reset_db_on_startup() + +# --- HILFSFUNKTIONEN --- def expand_variants(text): - """ Zerlegt 'Wort1; Wort2' in eine Liste """ + """Zerlegt 'Wort1; Wort2' in eine Liste für den Vergleich.""" parts = text.split(';') results = [] for p in parts: @@ -74,44 +74,61 @@ def expand_variants(text): 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): + """Macht Text klein und entfernt Leerzeichen.""" return text.lower().strip() + +# --- ROUTEN --- + @app.route('/') def index(): return render_template('index.html') @app.route('/api/pages') def get_pages(): + """Liefert verfügbare Buchseiten.""" conn = get_db_connection() cur = conn.cursor() + + # Seiten aus der Vokabel-Tabelle cur.execute("SELECT DISTINCT page FROM vocabulary ORDER BY page") 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() 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) + + # Listen zusammenführen und sortieren + pages = set([r[0] for r in rows]) + for r in rows_verbs: + pages.add(r[0]) + + sorted_pages = sorted(list(pages)) + return jsonify(sorted_pages) @app.route('/api/question', methods=['POST']) def get_question(): + """Holt eine zufällige Frage basierend auf Modus und Seiten.""" data = request.json mode = data.get('mode', 'random') 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() cur = conn.cursor() - # --- NEU: UNREGELMÄSSIGE VERBEN MODUS --- + # --- MODUS: UNREGELMÄSSIGE VERBEN --- 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,)) @@ -120,18 +137,18 @@ def get_question(): conn.close() 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({ 'type': 'irregular', - 'question': row[0], # Infinitive - 'german_hint': row[3], - 'answer_simple': row[1], - 'answer_participle': row[2], + 'question': row[0], # Infinitive (z.B. "go") + 'german_hint': row[3], # Deutsch (z.B. "gehen") + 'answer_simple': row[1], # Simple Past (z.B. "went") + 'answer_participle': row[2], # Past Participle (z.B. "gone") 'page': row[4] }) - # --- ALTER MODUS (Vokabeln) --- + # --- MODUS: NORMALE VOKABELN --- else: query = "SELECT english, german, page FROM vocabulary WHERE page = ANY(%s) ORDER BY RANDOM() LIMIT 1" cur.execute(query, (pages,)) @@ -140,7 +157,7 @@ def get_question(): conn.close() if not row: - return jsonify({'error': 'Keine Vokabeln gefunden.'}) + return jsonify({'error': 'Keine Vokabeln auf den gewählten Seiten gefunden.'}) question_text = "" answer_text = "" @@ -168,9 +185,10 @@ def get_question(): @app.route('/api/check', methods=['POST']) def check_answer(): + """Prüft die Antwort des Nutzers.""" data = request.json - # --- NEU: CHECK FÜR VERBEN --- + # --- CHECK: UNREGELMÄSSIGE 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', '')) @@ -178,8 +196,9 @@ def check_answer(): correct_simple = data.get('correct_simple', '') 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): + # correct_str könnte "was/were" oder "burnt/burned" sein variants = [x.strip().lower() for x in correct_str.split('/')] return user_in in variants @@ -189,12 +208,17 @@ def check_answer(): if is_simple_ok and is_participle_ok: return jsonify({'status': 'correct', 'msg': 'Perfekt! Beide Formen richtig.'}) 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({ '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: user_input = data.get('input', '') db_answer = data.get('correct', '') @@ -206,7 +230,7 @@ def check_answer(): if clean_user in valid_answers_clean: return jsonify({'status': 'correct', 'msg': 'Richtig!'}) - # Fuzzy Logic (Typo Check) + # Fuzzy Logic (Tippfehler erkennen) best_ratio = 0 best_match = "" for correct_option in valid_answers_clean: @@ -217,7 +241,7 @@ def check_answer(): threshold = 0.85 if len(best_match) <= 4: - threshold = 0.9 + threshold = 0.9 # Bei kurzen Wörtern strenger sein if best_ratio >= threshold: return jsonify({