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>
This commit is contained in:
60
app.py
60
app.py
@@ -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})
|
||||||
|
|||||||
Reference in New Issue
Block a user