diff --git a/app/app.py b/app/app.py index 6235052..7b38c3e 100644 --- a/app/app.py +++ b/app/app.py @@ -38,6 +38,19 @@ init_db() # --- HELPER --- +def normalize_latin(text): + """Entfernt Makrons für den toleranten Vergleich (ā -> a)""" + if not text: return "" + replacements = { + 'ā': 'a', 'ē': 'e', 'ī': 'i', 'ō': 'o', 'ū': 'u', + 'Ā': 'A', 'Ē': 'E', 'Ī': 'I', 'Ō': 'O', 'Ū': 'U', + 'ȳ': 'y', 'Ȳ': 'Y' + } + t = text + for k, v in replacements.items(): + t = t.replace(k, v) + return t + 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'] @@ -49,50 +62,45 @@ def is_middle_relevant(text): return True def clean_german_answers(text): - # Alles in Klammern entfernen und splitten 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 Grammatik (erlaubt unvollständige Eingaben)""" - u = user_in.strip().lower() - c = correct.strip().lower() + # 1. Normalisieren (Striche wegdenken für Vergleich) + u_norm = normalize_latin(user_in.strip().lower()) + c_norm = normalize_latin(correct.strip().lower()) - if u == c: return 'correct' + # 2. Exakter Abgleich (auf Basis der normalisierten Version) + if u_norm == c_norm: 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) + u_parts = get_parts(u_norm) + c_parts = get_parts(c_norm) if not u_parts: return 'wrong' - # Sind alle eingegebenen Teile in der Lösung vorhanden? all_user_parts_correct = all(p in c_parts for p in u_parts) if all_user_parts_correct: - # Wenn weniger Teile als nötig -> unvollständig - if len(u_parts) < len(c_parts): - return 'incomplete' + if len(u_parts) < len(c_parts): return 'incomplete' return 'correct' - # Tippfehler-Check - ratio = SequenceMatcher(None, u, c).ratio() + ratio = SequenceMatcher(None, u_norm, c_norm).ratio() if ratio > 0.85: return 'typo' return 'wrong' def check_string_match(user_in, correct): - """Standard String Vergleich""" - u = user_in.strip().lower() - c = correct.strip().lower() - if not u: return 'wrong' + u_norm = normalize_latin(user_in.strip().lower()) + c_norm = normalize_latin(correct.strip().lower()) - if u == c: return 'correct' + if not u_norm: return 'wrong' + if u_norm == c_norm: return 'correct' - ratio = SequenceMatcher(None, u, c).ratio() + ratio = SequenceMatcher(None, u_norm, c_norm).ratio() if ratio > 0.85: return 'typo' return 'wrong' @@ -161,28 +169,22 @@ def check(): inputs = data.get('inputs', []) attempt_num = data.get('attempt', 1) - # Wir initialisieren die Ergebnis-Liste mit "falsch", damit wir sie später füllen können results = [None] * len(inputs) - correct_items = 0 total_items = len(inputs) has_incomplete = False - # 1. GRAMMATIK (Mitte) direkt prüfen (feste Position) + # 1. GRAMMATIK for i, inp in enumerate(inputs): if inp.get('type') == 'middle': status = check_middle_logic(inp.get('value', ''), inp.get('correct', '')) msg = 'Unvollständig! Da fehlt noch etwas.' if status == 'incomplete' else '' if status == 'incomplete': has_incomplete = True - results[i] = {'status': status, 'correct': inp.get('correct', ''), 'msg': msg} if status in ['correct', 'typo']: correct_items += 1 - # 2. DEUTSCH (German) - Pool-Logik für Reihenfolge-Unabhängigkeit - - # Sammle alle deutschen User-Eingaben und ihre Indizes - german_user_inputs = [] # Liste von {'index': 1, 'value': '...'} - # Sammle alle korrekten deutschen Lösungen (Pool) + # 2. DEUTSCH + german_user_inputs = [] german_correct_pool = [] for i, inp in enumerate(inputs): @@ -190,45 +192,33 @@ def check(): german_user_inputs.append({'index': i, 'value': inp.get('value', '')}) german_correct_pool.append(inp.get('correct', '')) - # Welche Pool-Einträge wurden schon "verbraucht"? used_pool_indices = set() - # Jetzt prüfen wir jede User-Eingabe gegen den GANZEN Pool for u_item in german_user_inputs: idx = u_item['index'] val = u_item['value'] found_match = False - matched_correct_val = "" - # Suche im Pool nach einem Match, der noch nicht benutzt wurde for pool_i, pool_val in enumerate(german_correct_pool): - if pool_i in used_pool_indices: - continue + if pool_i in used_pool_indices: continue - # Standard Prüfung für Deutsch status = check_string_match(val, pool_val) if status in ['correct', 'typo']: - # Treffer! results[idx] = {'status': status, 'correct': pool_val} - used_pool_indices.add(pool_i) # Lösung als "benutzt" markieren + used_pool_indices.add(pool_i) correct_items += 1 found_match = True break - # Wenn KEIN Match im Pool gefunden wurde if not found_match: - # Wir zeigen als Lösung einfach die an, die ursprünglich an dieser Position stand - # (auch wenn die Position eigentlich egal ist, irgendwas müssen wir anzeigen) original_correct = inputs[idx].get('correct', '') results[idx] = {'status': 'wrong', 'correct': original_correct} - # Punkteberechnung score_fraction = 0 if total_items > 0: base_score = correct_items / total_items - if attempt_num > 1: - base_score = base_score * 0.5 + if attempt_num > 1: base_score = base_score * 0.5 score_fraction = base_score all_correct = (correct_items == total_items) @@ -241,5 +231,4 @@ def check(): }) if __name__ == '__main__': - # Production settings app.run(host='0.0.0.0', port=5000) \ No newline at end of file