Compare commits

...

2 Commits

Author SHA1 Message Date
Tim Leikauf
5b99bef765 Merge branch 'claude/affectionate-agnesi-411173' 2026-05-06 22:19:58 +02:00
Tim Leikauf
5357805530 refactor: generate_questions auf natives Directus M2M umgestellt
- Dedup via deep field query auf Object (1 GET statt 2 Junction-GETs)
- Wörter batch-linked via PATCH objects/{id}.linked_words create
- Fragen batch-linked via PATCH objects/{id}.linked_questions create
- related_words + distractor_words via PATCH questions/{id} create
- Keine direkten Junction-Table-POSTs mehr

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-06 22:19:42 +02:00

60
app.py
View File

@@ -1165,20 +1165,17 @@ def generate_questions(obj_id: str):
# Mehrwortige Einträge → überspringen (KI-Fehler) # Mehrwortige Einträge → überspringen (KI-Fehler)
return tokens return tokens
# Bestehende Verlinkungen vorab laden (Dedup ohne N×GET) # Bestehende Verlinkungen vorab laden ein einziger GET auf das Objekt
ew_data, _ = _directus( obj_links_data, _ = _directus(
"GET", "GET",
f"/items/words_objects?filter[objects_id][_eq]={obj_id}&fields[]=words_id&limit=2000", f"/items/objects/{obj_id}"
f"?fields[]=linked_words.words_id"
f"&fields[]=linked_questions.questions_id",
token, token,
) )
existing_word_ids: set[str] = {e["words_id"] for e in (ew_data.get("data") or [])} obj_data = obj_links_data.get("data") or {}
existing_word_ids: set[str] = {lw["words_id"] for lw in (obj_data.get("linked_words") or [])}
eq_data, _ = _directus( existing_question_ids: set[str] = {lq["questions_id"] for lq in (obj_data.get("linked_questions") or [])}
"GET",
f"/items/questions_objects?filter[objects_id][_eq]={obj_id}&fields[]=questions_id&limit=200",
token,
)
existing_question_ids: set[str] = {e["questions_id"] for e in (eq_data.get("data") or [])}
# Alle eindeutigen Wörter aus allen Leveln vorab sammeln und einmalig laden # Alle eindeutigen Wörter aus allen Leveln vorab sammeln und einmalig laden
all_words_by_level: dict[str, int] = {} # title_de → first level seen all_words_by_level: dict[str, int] = {} # title_de → first level seen
@@ -1198,15 +1195,18 @@ def generate_questions(obj_id: str):
wid, is_new = _find_or_create_word(w, lvl_num, token) wid, is_new = _find_or_create_word(w, lvl_num, token)
global_word_map[w] = wid global_word_map[w] = wid
if wid not in existing_word_ids: if wid not in existing_word_ids:
new_word_links.append({"words_id": wid, "objects_id": obj_id}) new_word_links.append({"words_id": wid})
existing_word_ids.add(wid) existing_word_ids.add(wid)
stats["words_created" if is_new else "words_linked"] += 1 stats["words_created" if is_new else "words_linked"] += 1
except Exception as e: except Exception as e:
print(f"[generate_questions] word error '{w}': {e}") print(f"[generate_questions] word error '{w}': {e}")
# Alle neuen Wort-Verlinkungen in einem Batch speichern # Alle neuen Wort-Verlinkungen in einem Batch via natives M2M
if new_word_links: if new_word_links:
_directus("POST", "/items/words_objects", token, new_word_links) _directus("PATCH", f"/items/objects/{obj_id}", token,
{"linked_words": {"create": new_word_links}})
new_question_links: list[dict] = []
for lvl in levels: for lvl in levels:
level = int(lvl.get("level") or 1) level = int(lvl.get("level") or 1)
@@ -1230,27 +1230,27 @@ def generate_questions(obj_id: str):
stats["questions_created" if q_is_new else "questions_linked"] += 1 stats["questions_created" if q_is_new else "questions_linked"] += 1
# Frage ↔ Objekt (nur wenn noch nicht verknüpft) # Frage ↔ Objekt (für Batch am Ende sammeln)
if q_id not in existing_question_ids: if q_id not in existing_question_ids:
_directus("POST", "/items/questions_objects", token, new_question_links.append({"questions_id": q_id})
{"questions_id": q_id, "objects_id": obj_id})
existing_question_ids.add(q_id) existing_question_ids.add(q_id)
# related_words + distractor_words nur für neue Fragen (batch) # related_words + distractor_words nur für neue Fragen (batch, natives M2M)
if q_is_new: if q_is_new:
rw_links = [ q_patch: dict = {}
{"questions_id": q_id, "words_id": global_word_map[w]} rw = [{"words_id": global_word_map[w]} for w in words_list if w in global_word_map]
for w in words_list if w in global_word_map if rw:
] q_patch["related_words"] = {"create": rw}
if rw_links: dw = [{"words_id": global_word_map[w]} for w in distractor_list if w in global_word_map]
_directus("POST", "/items/questions_words", token, rw_links) if dw:
q_patch["distractor_words"] = {"create": dw}
if q_patch:
_directus("PATCH", f"/items/questions/{q_id}", token, q_patch)
dw_links = [ # Alle neuen Fragen-Verlinkungen in einem Batch via natives M2M
{"questions_id": q_id, "words_id": global_word_map[w]} if new_question_links:
for w in distractor_list if w in global_word_map _directus("PATCH", f"/items/objects/{obj_id}", token,
] {"linked_questions": {"create": new_question_links}})
if dw_links:
_directus("POST", "/items/questions_distractor_words", token, dw_links)
print(f"[generate_questions] obj={obj_id} stats={stats}") print(f"[generate_questions] obj={obj_id} stats={stats}")
return jsonify({"ok": True, "object_id": obj_id, "stats": stats}) return jsonify({"ok": True, "object_id": obj_id, "stats": stats})