app/app.py aktualisiert

This commit is contained in:
sascha 2026-01-28 18:22:39 +00:00
parent d691adec05
commit c3d0b48254

View File

@ -7,9 +7,8 @@ from difflib import SequenceMatcher
app = Flask(__name__) app = Flask(__name__)
# --- DATENBANK VERBINDUNG --- # --- DB VERBINDUNG & RESET (Wie gehabt) ---
def get_db_connection(): def get_db_connection():
"""Stellt Verbindung zur Postgres-DB her, mit Warteschleife beim Start."""
max_retries = 10 max_retries = 10
for i in range(max_retries): for i in range(max_retries):
try: try:
@ -21,67 +20,35 @@ def get_db_connection():
) )
return conn return conn
except psycopg2.OperationalError: except psycopg2.OperationalError:
if i < max_retries - 1: time.sleep(2)
print("Datenbank noch nicht bereit... warte 2 Sekunden.") continue
time.sleep(2) raise Exception("DB Connection failed")
continue
else:
print("FEHLER: Konnte keine Verbindung zur Datenbank herstellen.")
raise
# --- RESET LOGIK (STATELESS CONTAINER) ---
def reset_db_on_startup(): 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()
# 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'): if os.path.exists('init.sql'):
with open('init.sql', 'r', encoding='utf-8') as f: with open('init.sql', 'r', encoding='utf-8') as f:
sql_commands = f.read() cur.execute(f.read())
cur.execute(sql_commands)
conn.commit() conn.commit()
print("Datenbank erfolgreich neu aufgebaut (Clean State)!") print("DB Reset erfolgreich.")
else:
print("WARNUNG: init.sql nicht gefunden! Datenbank könnte leer sein.")
cur.close() cur.close()
conn.close() conn.close()
except Exception as e: 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() reset_db_on_startup()
# --- HILFSFUNKTIONEN --- # --- HILFSFUNKTIONEN ---
def expand_variants(text): def clean_text(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."""
return text.lower().strip() 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('/') @app.route('/')
def index(): def index():
@ -89,167 +56,151 @@ def index():
@app.route('/api/pages') @app.route('/api/pages')
def get_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() conn = get_db_connection()
cur = conn.cursor() cur = conn.cursor()
# Seiten aus der Vokabel-Tabelle if mode_type == 'irregular':
cur.execute("SELECT DISTINCT page FROM vocabulary ORDER BY page") 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() 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()
# Listen zusammenführen und sortieren pages = sorted([r[0] for r in rows])
pages = set([r[0] for r in rows]) return jsonify(pages)
for r in rows_verbs:
pages.add(r[0])
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') main_mode = data.get('mainMode') # 'vocab' oder 'irregular'
sub_mode = data.get('subMode') # 'de-en', 'start-german', etc.
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()
# --- MODUS: UNREGELMÄSSIGE VERBEN --- if main_mode == 'irregular':
if 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" 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,))
row = cur.fetchone() row = cur.fetchone()
cur.close() cur.close()
conn.close() conn.close()
if not row: if not row: return jsonify({'error': 'Keine Verben gefunden.'})
return jsonify({'error': 'Keine Verben auf diesen Seiten gefunden (Wähle 206 oder 207).'})
return jsonify({ # Entscheiden basierend auf sub_mode
'type': 'irregular', if sub_mode == 'start-german':
'question': row[0], # Infinitive (z.B. "go") # Frage: Deutsch -> Antwort: Alle 3 Formen
'german_hint': row[3], # Deutsch (z.B. "gehen") return jsonify({
'answer_simple': row[1], # Simple Past (z.B. "went") 'type': 'irregular_full',
'answer_participle': row[2], # Past Participle (z.B. "gone") 'question': row[3], # Deutsch
'page': row[4] '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]
})
# --- MODUS: NORMALE VOKABELN ---
else: else:
# Vokabel Logik
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,))
row = cur.fetchone() row = cur.fetchone()
cur.close() cur.close()
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 = "" q_text, a_text = "", ""
answer_text = "" if sub_mode == 'de-en':
q_text, a_text = row[1], row[0]
if mode == 'de-en': elif sub_mode == 'en-de':
question_text = row[1] # Deutsch q_text, a_text = row[0], row[1]
answer_text = row[0] # Englisch
elif mode == 'en-de':
question_text = row[0] # Englisch
answer_text = row[1] # Deutsch
else: # random else: # random
if random.random() > 0.5: if random.random() > 0.5: q_text, a_text = row[1], row[0]
question_text = row[1] else: q_text, a_text = row[0], row[1]
answer_text = row[0]
else:
question_text = row[0]
answer_text = row[1]
return jsonify({ return jsonify({
'type': 'vocab', 'type': 'vocab',
'question': question_text, 'question': q_text,
'answer': answer_text, 'answer': a_text,
'page': row[2] 'page': row[2]
}) })
@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
q_type = data.get('type')
# --- CHECK: UNREGELMÄSSIGE VERBEN --- # 1. Verben: Alles abfragen (Start: Deutsch)
if data.get('type') == 'irregular': if q_type == 'irregular_full':
user_simple = clean_text_for_comparison(data.get('input_simple', '')) u_inf = clean_text(data.get('infinitive', ''))
user_participle = clean_text_for_comparison(data.get('input_participle', '')) u_simp = clean_text(data.get('simple', ''))
u_part = clean_text(data.get('participle', ''))
correct_simple = data.get('correct_simple', '') ok_inf = check_part(u_inf, data.get('correct_infinitive', ''))
correct_participle = data.get('correct_participle', '') ok_simp = check_part(u_simp, data.get('correct_simple', ''))
ok_part = check_part(u_part, data.get('correct_participle', ''))
# Hilfsfunktion: Prüft ob Eingabe in "was/were" enthalten ist if ok_inf and ok_simp and ok_part:
def check_part(user_in, correct_str): return jsonify({'status': 'correct', 'msg': 'Perfekt! Alles richtig.'})
# 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
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: else:
# Detailliertes Feedback, was falsch war sol = f"{data['correct_infinitive']} -> {data['correct_simple']} -> {data['correct_participle']}"
msg = "Leider falsch." return jsonify({'status': 'wrong', 'msg': f'Leider falsch. Lösung: {sol}'})
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({ # 2. Verben: Standard (Start: Infinitiv)
'status': 'wrong', elif q_type == 'irregular_standard':
'msg': f'Lösung: {correct_simple} | {correct_participle}' u_simp = clean_text(data.get('simple', ''))
}) u_part = clean_text(data.get('participle', ''))
# --- CHECK: NORMALE VOKABELN --- 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!'})
else:
sol = f"{data['correct_simple']} -> {data['correct_participle']}"
return jsonify({'status': 'wrong', 'msg': f'Lösung: {sol}'})
# 3. Vokabeln
else: else:
user_input = data.get('input', '') u_input = clean_text(data.get('input', ''))
db_answer = data.get('correct', '') correct_raw = data.get('correct', '')
clean_user = clean_text_for_comparison(user_input) # Split variants (e.g. "car; auto")
valid_answers_raw = expand_variants(db_answer) valid_answers = [clean_text(x) for x in correct_raw.split(';')]
valid_answers_clean = [clean_text_for_comparison(v) for v in valid_answers_raw] # Add version without brackets "(to) go" -> "go"
for v in list(valid_answers):
if '(' in v: valid_answers.append(v.replace('(','').replace(')','').strip())
if clean_user in valid_answers_clean: if u_input in valid_answers:
return jsonify({'status': 'correct', 'msg': 'Richtig!'}) return jsonify({'status': 'correct', 'msg': 'Richtig!'})
# Fuzzy Logic (Tippfehler erkennen) # Fuzzy Check
best_ratio = 0 best_ratio = 0
best_match = "" for v in valid_answers:
for correct_option in valid_answers_clean: ratio = SequenceMatcher(None, u_input, v).ratio()
ratio = SequenceMatcher(None, clean_user, correct_option).ratio() if ratio > best_ratio: best_ratio = ratio
if ratio > best_ratio:
best_ratio = ratio
best_match = correct_option
threshold = 0.85 if best_ratio >= 0.8:
if len(best_match) <= 4: return jsonify({'status': 'typo', 'msg': f'Fast richtig! Lösung: {correct_raw}'})
threshold = 0.9 # Bei kurzen Wörtern strenger sein
if best_ratio >= threshold: return jsonify({'status': 'wrong', 'msg': f'Falsch. Lösung: {correct_raw}'})
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__': if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000) app.run(host='0.0.0.0', port=5000)