From 275563252427555b0e19e839d8301e68460050f9 Mon Sep 17 00:00:00 2001 From: admin Date: Sun, 26 Apr 2026 10:27:12 +0200 Subject: [PATCH] Optimize generate_questions: word cache + gunicorn timeout 300s - Cache all words globally across levels (avoids 500+ Directus calls) - Add Procfile with --timeout 300 to prevent gunicorn killing long requests Co-Authored-By: Claude Sonnet 4.6 --- Procfile | 1 + app.py | 75 +++++++++++++++++++++++++------------------------------- 2 files changed, 35 insertions(+), 41 deletions(-) create mode 100644 Procfile diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..e2d771e --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: gunicorn app:app --bind 0.0.0.0:8000 --timeout 300 --workers 2 diff --git a/app.py b/app.py index 1b7dd5c..dc9fb14 100644 --- a/app.py +++ b/app.py @@ -1029,7 +1029,7 @@ def generate_questions(obj_id: str): if not levels: return jsonify({"error": "No levels in AI response"}), 500 - # Junction-Collections sicherstellen + # Junction-Collections sicherstellen (einmalig) for col, f1, f2 in [ ("words_objects", "words_id", "objects_id"), ("questions_objects", "questions_id", "objects_id"), @@ -1044,6 +1044,31 @@ def generate_questions(obj_id: str): "questions_linked": 0, } + # Alle eindeutigen Wörter aus allen Leveln vorab sammeln und einmalig laden + all_words_by_level: dict[str, int] = {} # title_de → first level seen + for lvl in levels: + level = int(lvl.get("level") or 1) + for w in lvl.get("words", []) + lvl.get("distractor_words", []) + [lvl.get("short_answer", "")]: + w = str(w).strip() + if w and w not in all_words_by_level: + all_words_by_level[w] = level + + # Wörter einmalig anlegen / finden (globaler Cache über alle Level) + global_word_map: dict[str, str] = {} # title_de → id + for w, lvl_num in all_words_by_level.items(): + try: + wid, is_new = _find_or_create_word(w, lvl_num, token) + global_word_map[w] = wid + _ensure_link( + "words_objects", + {"words_id": wid, "objects_id": obj_id}, + {"words_id": wid, "objects_id": obj_id}, + token, + ) + stats["words_created" if is_new else "words_linked"] += 1 + except Exception as e: + print(f"[generate_questions] word error '{w}': {e}") + for lvl in levels: level = int(lvl.get("level") or 1) q_de = (lvl.get("question") or "").strip() @@ -1055,39 +1080,7 @@ def generate_questions(obj_id: str): if not q_de: continue - # Kurzantwort-Wort - short_answer_id = None - if short_text: - try: - wid, is_new = _find_or_create_word(short_text, level, token) - short_answer_id = wid - _ensure_link( - "words_objects", - {"words_id": wid, "objects_id": obj_id}, - {"words_id": wid, "objects_id": obj_id}, - token, - ) - stats["words_created" if is_new else "words_linked"] += 1 - except Exception as e: - print(f"[generate_questions] short_answer error: {e}") - - # Alle einzigartigen Wörter verarbeiten - all_unique = list(dict.fromkeys(words_list + distractor_list)) - word_map: dict[str, str] = {} # title_de → id - - for w in all_unique: - try: - wid, is_new = _find_or_create_word(w, level, token) - word_map[w] = wid - _ensure_link( - "words_objects", - {"words_id": wid, "objects_id": obj_id}, - {"words_id": wid, "objects_id": obj_id}, - token, - ) - stats["words_created" if is_new else "words_linked"] += 1 - except Exception as e: - print(f"[generate_questions] word error '{w}': {e}") + short_answer_id = global_word_map.get(short_text) if short_text else None # Frage anlegen / verknüpfen try: @@ -1106,23 +1099,23 @@ def generate_questions(obj_id: str): token, ) - # related_words (bestehende Junction questions_words) + # related_words for w in words_list: - if w in word_map: + if w in global_word_map: _ensure_link( "questions_words", - {"questions_id": q_id, "words_id": word_map[w]}, - {"questions_id": q_id, "words_id": word_map[w]}, + {"questions_id": q_id, "words_id": global_word_map[w]}, + {"questions_id": q_id, "words_id": global_word_map[w]}, token, ) # distractor_words for w in distractor_list: - if w in word_map: + if w in global_word_map: _ensure_link( "questions_distractor_words", - {"questions_id": q_id, "words_id": word_map[w]}, - {"questions_id": q_id, "words_id": word_map[w]}, + {"questions_id": q_id, "words_id": global_word_map[w]}, + {"questions_id": q_id, "words_id": global_word_map[w]}, token, )