From c3d0b482541c0c03074d12f15f2a48c7c8f124e5 Mon Sep 17 00:00:00 2001 From: sascha Date: Wed, 28 Jan 2026 18:22:39 +0000 Subject: [PATCH] app/app.py aktualisiert --- app/app.py | 277 ++++++++++++++++++++++------------------------------- 1 file changed, 114 insertions(+), 163 deletions(-) diff --git a/app/app.py b/app/app.py index 8084561..f0be7b4 100644 --- a/app/app.py +++ b/app/app.py @@ -7,9 +7,8 @@ from difflib import SequenceMatcher app = Flask(__name__) -# --- DATENBANK VERBINDUNG --- +# --- DB VERBINDUNG & RESET (Wie gehabt) --- def get_db_connection(): - """Stellt Verbindung zur Postgres-DB her, mit Warteschleife beim Start.""" max_retries = 10 for i in range(max_retries): try: @@ -21,67 +20,35 @@ 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 + time.sleep(2) + continue + raise Exception("DB Connection failed") -# --- 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() - - # 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) + cur.execute(f.read()) conn.commit() - print("Datenbank erfolgreich neu aufgebaut (Clean State)!") - else: - print("WARNUNG: init.sql nicht gefunden! Datenbank könnte leer sein.") - + print("DB Reset erfolgreich.") cur.close() conn.close() except Exception as e: - print(f"KRITISCHER FEHLER beim DB-Reset: {e}") + print(f"DB Reset Fehler: {e}") -# 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 für den Vergleich.""" - 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) - return final_res - -def clean_text_for_comparison(text): - """Macht Text klein und entfernt Leerzeichen.""" +def clean_text(text): return text.lower().strip() +def check_part(user_in, correct_str): + variants = [x.strip().lower() for x in correct_str.split('/')] + return user_in in variants -# --- ROUTEN --- +# --- API ROUTEN --- @app.route('/') def index(): @@ -89,167 +56,151 @@ def index(): @app.route('/api/pages') def get_pages(): - """Liefert verfügbare Buchseiten.""" + # Wir filtern jetzt nach Typ! + mode_type = request.args.get('type', 'vocab') # 'vocab' oder 'irregular' + conn = get_db_connection() cur = conn.cursor() - # Seiten aus der Vokabel-Tabelle - cur.execute("SELECT DISTINCT page FROM vocabulary ORDER BY page") + if mode_type == 'irregular': + cur.execute("SELECT DISTINCT page FROM irregular_verbs ORDER BY page") + else: + 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() - # 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) + pages = sorted([r[0] for r in rows]) + return jsonify(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') + main_mode = data.get('mainMode') # 'vocab' oder 'irregular' + sub_mode = data.get('subMode') # 'de-en', 'start-german', etc. 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() - - # --- MODUS: UNREGELMÄSSIGE VERBEN --- - if mode == 'irregular': + + if main_mode == 'irregular': + # Verben Logik 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 auf diesen Seiten gefunden (Wähle 206 oder 207).'}) - - return jsonify({ - 'type': 'irregular', - '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] - }) - # --- MODUS: NORMALE VOKABELN --- + if not row: return jsonify({'error': 'Keine Verben gefunden.'}) + + # Entscheiden basierend auf sub_mode + if sub_mode == 'start-german': + # Frage: Deutsch -> Antwort: Alle 3 Formen + return jsonify({ + 'type': 'irregular_full', + 'question': row[3], # Deutsch + 'answer_infinitive': row[0], + 'answer_simple': row[1], + 'answer_participle': row[2], + 'page': row[4] + }) + else: + # Frage: Infinitiv -> Antwort: Past & Participle (Standard) + return jsonify({ + 'type': 'irregular_standard', + 'question': row[0], # Infinitive + 'german_hint': row[3], + 'answer_simple': row[1], + 'answer_participle': row[2], + 'page': row[4] + }) + else: + # Vokabel Logik 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 auf den gewählten Seiten 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 + + if not row: return jsonify({'error': 'Keine Vokabeln gefunden.'}) + + q_text, a_text = "", "" + if sub_mode == 'de-en': + q_text, a_text = row[1], row[0] + elif sub_mode == 'en-de': + q_text, a_text = row[0], row[1] else: # random - if random.random() > 0.5: - question_text = row[1] - answer_text = row[0] - else: - question_text = row[0] - answer_text = row[1] - + if random.random() > 0.5: q_text, a_text = row[1], row[0] + else: q_text, a_text = row[0], row[1] + return jsonify({ 'type': 'vocab', - 'question': question_text, - 'answer': answer_text, + 'question': q_text, + 'answer': a_text, 'page': row[2] }) @app.route('/api/check', methods=['POST']) def check_answer(): - """Prüft die Antwort des Nutzers.""" data = request.json - - # --- 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', '')) - - correct_simple = data.get('correct_simple', '') - correct_participle = data.get('correct_participle', '') - - # 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 + q_type = data.get('type') - is_simple_ok = check_part(user_simple, correct_simple) - is_participle_ok = check_part(user_participle, correct_participle) + # 1. Verben: Alles abfragen (Start: Deutsch) + if q_type == 'irregular_full': + u_inf = clean_text(data.get('infinitive', '')) + u_simp = clean_text(data.get('simple', '')) + u_part = clean_text(data.get('participle', '')) - if is_simple_ok and is_participle_ok: - return jsonify({'status': 'correct', 'msg': 'Perfekt! Beide Formen richtig.'}) + ok_inf = check_part(u_inf, data.get('correct_infinitive', '')) + ok_simp = check_part(u_simp, data.get('correct_simple', '')) + ok_part = check_part(u_part, data.get('correct_participle', '')) + + if ok_inf and ok_simp and ok_part: + return jsonify({'status': 'correct', 'msg': 'Perfekt! Alles 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'Lösung: {correct_simple} | {correct_participle}' - }) + sol = f"{data['correct_infinitive']} -> {data['correct_simple']} -> {data['correct_participle']}" + return jsonify({'status': 'wrong', 'msg': f'Leider falsch. Lösung: {sol}'}) - # --- CHECK: NORMALE VOKABELN --- - else: - user_input = data.get('input', '') - db_answer = data.get('correct', '') + # 2. Verben: Standard (Start: Infinitiv) + elif q_type == 'irregular_standard': + u_simp = clean_text(data.get('simple', '')) + u_part = clean_text(data.get('participle', '')) - 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: + ok_simp = check_part(u_simp, data.get('correct_simple', '')) + ok_part = check_part(u_part, data.get('correct_participle', '')) + + if ok_simp and ok_part: return jsonify({'status': 'correct', 'msg': 'Richtig!'}) - - # Fuzzy Logic (Tippfehler erkennen) - 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 # Bei kurzen Wörtern strenger sein - - if best_ratio >= threshold: - return jsonify({ - 'status': 'typo', - 'msg': f'Fast richtig! Meintest du: "{best_match}"? (Es war "{user_input}")' - }) + else: + sol = f"{data['correct_simple']} -> {data['correct_participle']}" + return jsonify({'status': 'wrong', 'msg': f'Lösung: {sol}'}) + + # 3. Vokabeln + else: + u_input = clean_text(data.get('input', '')) + correct_raw = data.get('correct', '') - return jsonify({'status': 'wrong', 'msg': f'Falsch. Lösung: {db_answer}'}) + # Split variants (e.g. "car; auto") + valid_answers = [clean_text(x) for x in correct_raw.split(';')] + # Add version without brackets "(to) go" -> "go" + for v in list(valid_answers): + if '(' in v: valid_answers.append(v.replace('(','').replace(')','').strip()) + + if u_input in valid_answers: + return jsonify({'status': 'correct', 'msg': 'Richtig!'}) + + # Fuzzy Check + best_ratio = 0 + for v in valid_answers: + ratio = SequenceMatcher(None, u_input, v).ratio() + if ratio > best_ratio: best_ratio = ratio + + if best_ratio >= 0.8: + return jsonify({'status': 'typo', 'msg': f'Fast richtig! Lösung: {correct_raw}'}) + + return jsonify({'status': 'wrong', 'msg': f'Falsch. Lösung: {correct_raw}'}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) \ No newline at end of file