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 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 10:27:12 +02:00
parent 0360bcd1e6
commit 2755632524
2 changed files with 35 additions and 41 deletions

1
Procfile Normal file
View File

@@ -0,0 +1 @@
web: gunicorn app:app --bind 0.0.0.0:8000 --timeout 300 --workers 2

75
app.py
View File

@@ -1029,7 +1029,7 @@ def generate_questions(obj_id: str):
if not levels: if not levels:
return jsonify({"error": "No levels in AI response"}), 500 return jsonify({"error": "No levels in AI response"}), 500
# Junction-Collections sicherstellen # Junction-Collections sicherstellen (einmalig)
for col, f1, f2 in [ for col, f1, f2 in [
("words_objects", "words_id", "objects_id"), ("words_objects", "words_id", "objects_id"),
("questions_objects", "questions_id", "objects_id"), ("questions_objects", "questions_id", "objects_id"),
@@ -1044,6 +1044,31 @@ def generate_questions(obj_id: str):
"questions_linked": 0, "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: for lvl in levels:
level = int(lvl.get("level") or 1) level = int(lvl.get("level") or 1)
q_de = (lvl.get("question") or "").strip() q_de = (lvl.get("question") or "").strip()
@@ -1055,39 +1080,7 @@ def generate_questions(obj_id: str):
if not q_de: if not q_de:
continue continue
# Kurzantwort-Wort short_answer_id = global_word_map.get(short_text) if short_text else None
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}")
# Frage anlegen / verknüpfen # Frage anlegen / verknüpfen
try: try:
@@ -1106,23 +1099,23 @@ def generate_questions(obj_id: str):
token, token,
) )
# related_words (bestehende Junction questions_words) # related_words
for w in words_list: for w in words_list:
if w in word_map: if w in global_word_map:
_ensure_link( _ensure_link(
"questions_words", "questions_words",
{"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": word_map[w]}, {"questions_id": q_id, "words_id": global_word_map[w]},
token, token,
) )
# distractor_words # distractor_words
for w in distractor_list: for w in distractor_list:
if w in word_map: if w in global_word_map:
_ensure_link( _ensure_link(
"questions_distractor_words", "questions_distractor_words",
{"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": word_map[w]}, {"questions_id": q_id, "words_id": global_word_map[w]},
token, token,
) )