import os import random import time import re import psycopg2 from flask import Flask, render_template, request, jsonify from difflib import SequenceMatcher app = Flask(__name__) def get_db_connection(): max_retries = 10 for i in range(max_retries): try: conn = psycopg2.connect( host=os.environ.get('DB_HOST', 'postgres-lat'), database=os.environ.get('DB_NAME', 'latein'), user=os.environ.get('DB_USER', 'latein'), password=os.environ.get('DB_PASS', 'latein') ) return conn except psycopg2.OperationalError: time.sleep(2) continue raise Exception("DB Connection failed") def init_db(): conn = get_db_connection() cur = conn.cursor() if os.path.exists('init.sql'): with open('init.sql', 'r', encoding='utf-8') as f: cur.execute(f.read()) conn.commit() cur.close() conn.close() # Start-Initialisierung init_db() # --- HELPER FUNKTIONEN --- def is_middle_relevant(text): if not text: return False signals = [' m', ' f', ' n', 'm.', 'f.', 'n.', 'pl', 'sg', 'gen', 'dat', 'akk', 'abl', 'perf', 'ppp'] t = text.lower() ignore = ['adv', 'präp', 'konj', 'subj', 'interj'] is_signal = any(s in t for s in signals) is_ignore = any(i in t for i in ignore) if is_ignore and not is_signal: return False return True def clean_german_answers(text): text = re.sub(r'\(.*?\)', '', text) text = re.sub(r'\[.*?\]', '', text) parts = re.split(r'[;,]', text) return [p.strip() for p in parts if p.strip()] def check_middle_logic(user_in, correct): """Prüft auf Unvollständigkeit (Komma-Trennung)""" u = user_in.strip().lower() c = correct.strip().lower() if u == c: return 'correct' def get_parts(s): return [x.strip() for x in s.split(',') if x.strip()] u_parts = get_parts(u) c_parts = get_parts(c) if not u_parts: return 'wrong' all_user_parts_correct = all(p in c_parts for p in u_parts) if all_user_parts_correct: if len(u_parts) < len(c_parts): return 'incomplete' return 'correct' ratio = SequenceMatcher(None, u, c).ratio() if ratio > 0.85: return 'typo' return 'wrong' def check_string_match(user_in, correct): u = user_in.strip().lower() c = correct.strip().lower() if u == c: return 'correct' ratio = SequenceMatcher(None, u, c).ratio() if ratio > 0.85: return 'typo' return 'wrong' # --- ROUTEN --- @app.route('/') def index(): return render_template('index.html') @app.route('/api/lessons') def get_lessons(): conn = get_db_connection() cur = conn.cursor() cur.execute("SELECT DISTINCT lesson FROM vocabulary") rows = cur.fetchall() cur.close() conn.close() lessons = [r[0] for r in rows] # Sortierung: Erst Zahlen, dann Strings, "Original" ganz hinten def sort_key(val): if val.isdigit(): return (0, int(val)) if val.startswith("Original"): return (2, val) return (1, val) lessons.sort(key=sort_key) return jsonify(lessons) @app.route('/api/question', methods=['POST']) def get_question(): data = request.json selected_lessons = data.get('lessons', []) ask_middle_setting = data.get('askMiddle', True) conn = get_db_connection() cur = conn.cursor() query = "SELECT id, lesson, latin, middle, german FROM vocabulary WHERE lesson = ANY(%s) ORDER BY RANDOM() LIMIT 1" cur.execute(query, (selected_lessons,)) row = cur.fetchone() cur.close() conn.close() if not row: return jsonify({'error': 'Keine Vokabeln gefunden'}) vid, lesson, latin, middle, german_raw = row german_answers = clean_german_answers(german_raw) middle_req = False hint = "" if middle: if ask_middle_setting and is_middle_relevant(middle): middle_req = True else: hint = middle return jsonify({ 'id': vid, 'latin': latin, 'hint': hint, 'middle_req': middle_req, 'middle_correct': middle if middle_req else "", 'german_count': len(german_answers), 'german_correct': german_answers }) @app.route('/api/check', methods=['POST']) def check(): data = request.json inputs = data.get('inputs', []) attempt_num = data.get('attempt', 1) results = [] total_items = len(inputs) correct_items = 0 has_incomplete = False for inp in inputs: user_val = inp.get('value', '') correct_val = inp.get('correct', '') field_type = inp.get('type', 'german') status = 'wrong' if field_type == 'middle': status = check_middle_logic(user_val, correct_val) else: status = check_string_match(user_val, correct_val) res_entry = {'status': status, 'correct': correct_val} if status == 'incomplete': res_entry['msg'] = 'Unvollständig! Da fehlt noch etwas.' has_incomplete = True results.append(res_entry) if status in ['correct', 'typo']: correct_items += 1 score_fraction = 0 if total_items > 0: base_score = correct_items / total_items if attempt_num > 1: base_score = base_score * 0.5 score_fraction = base_score all_correct = (correct_items == total_items) return jsonify({ 'results': results, 'all_correct': all_correct, 'score': score_fraction, 'has_incomplete': has_incomplete }) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)