latein6/app/app.py
2026-01-28 21:28:13 +00:00

206 lines
5.7 KiB
Python

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)