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:
1
Procfile
Normal file
1
Procfile
Normal file
@@ -0,0 +1 @@
|
|||||||
|
web: gunicorn app:app --bind 0.0.0.0:8000 --timeout 300 --workers 2
|
||||||
75
app.py
75
app.py
@@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user