app/app.py hinzugefügt
This commit is contained in:
parent
e7a6a473d0
commit
10c9e0529f
206
app/app.py
Normal file
206
app/app.py
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
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)
|
||||||
Loading…
x
Reference in New Issue
Block a user