update
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
Gate: exakte Partition + Coverage (jeder Soll-Punkt ≥1 Baustein)."""
|
||||
import math
|
||||
|
||||
from . import config, db, engine, llm, textkit
|
||||
from . import config, db, engine, graph, llm, textkit
|
||||
|
||||
|
||||
def _atom_rang(atom_id: int) -> tuple:
|
||||
@@ -27,7 +27,7 @@ async def lernziele(task: dict) -> engine.Ergebnis:
|
||||
else:
|
||||
atome = db.query("SELECT * FROM atome WHERE topic=? AND "
|
||||
"status='aktiv' AND soll_id IS NULL", task["topic"])
|
||||
punkt = "Weitere Grundlagen zum Thema"
|
||||
punkt = config.TEXTE["sammel_punkt"]
|
||||
if not atome:
|
||||
teil[m["id"]] = "fertig"
|
||||
continue
|
||||
@@ -45,7 +45,7 @@ async def lernziele(task: dict) -> engine.Ergebnis:
|
||||
run_id=task["run_id"], stufe="struktur", knoten="lernziele",
|
||||
item=task["item"], role="judge",
|
||||
n=sum(len(a) for (_, _, _, a) in aktive.values()),
|
||||
skill_namen=["planer", "deutsch-praezise", "lernziele"],
|
||||
skill_namen=graph.skills_von(task["knoten"]),
|
||||
werte={"soll_punkte": "\n\n".join(
|
||||
f"=== SOLL {i} ===\nPUNKT: {punkt}\nATOME:\n" +
|
||||
"\n".join(f"[{a['id']}] {a['titel']}" for a in atome)
|
||||
@@ -78,9 +78,9 @@ async def lernziele(task: dict) -> engine.Ergebnis:
|
||||
rest = ids_alle - zugeordnet # deterministisches Sammelziel — nichts verlieren
|
||||
if rest:
|
||||
ziel_id = db.insert(
|
||||
"lernziele", topic=task["topic"], titel="Grundlagen",
|
||||
soll_id=soll_id,
|
||||
text=f"Der Lernende kann Grundlagen zu '{punkt}' erklären.")
|
||||
"lernziele", topic=task["topic"],
|
||||
titel=config.TEXTE["sammel_titel"], soll_id=soll_id,
|
||||
text=config.TEXTE["sammel_ziel"].format(punkt=punkt))
|
||||
for aid in rest:
|
||||
db.update("atome", "id=?", (aid,), ziel_id=ziel_id)
|
||||
teil[m["id"]] = "fertig"
|
||||
@@ -115,17 +115,17 @@ async def bausteine(task: dict) -> engine.Ergebnis:
|
||||
antwort = await llm.call(
|
||||
run_id=task["run_id"], stufe="struktur", knoten="bausteine",
|
||||
item=task["item"], role="judge", n=len(atome),
|
||||
skill_namen=["planer", "deutsch-praezise", "bausteine"],
|
||||
skill_namen=graph.skills_von(task["knoten"]),
|
||||
werte={"ziel": ziel["text"],
|
||||
"band": f"{config.BAUSTEIN_MIN_ATOME}-{config.BAUSTEIN_MAX_ATOME}",
|
||||
"atome": "\n".join(f"[{a['id']}] {a['titel']}" for a in atome)})
|
||||
if antwort is None: # Call-Ausfall: neuer Versuch statt stiller Fallback
|
||||
raise RuntimeError("bausteine ohne Ergebnis")
|
||||
gruppen = _gruppen_validieren(antwort, atome)
|
||||
gruppen = _min_erzwingen(_gruppen_validieren(antwort, atome))
|
||||
for i, gruppe in enumerate(gruppen):
|
||||
titel = ziel["titel"] or gruppe[0]["titel"]
|
||||
if len(gruppen) > 1:
|
||||
titel = f"{titel} (Teil {i + 1})"
|
||||
titel = config.TEXTE["teil"].format(titel=titel, n=i + 1)
|
||||
bid = db.insert("bausteine", topic=task["topic"], ziel_id=ziel["id"],
|
||||
titel=titel[:120], ord=i)
|
||||
for j, a in enumerate(gruppe):
|
||||
@@ -134,6 +134,31 @@ async def bausteine(task: dict) -> engine.Ergebnis:
|
||||
neue_tasks=[{"knoten": "ordnung", "item": "ordnung"}])
|
||||
|
||||
|
||||
def _min_erzwingen(gruppen: list[list[dict]]) -> list[list[dict]]:
|
||||
"""Splitter-Bremse (Audit: 8 „Teil"-Sections mit 1-2 Atomen): Gruppen
|
||||
unter BAUSTEIN_MIN_ATOME mit dem kleineren Nachbarn mergen, solange das
|
||||
MAX hält. Quellreihenfolge bleibt erhalten."""
|
||||
geaendert = True
|
||||
while geaendert and len(gruppen) > 1:
|
||||
geaendert = False
|
||||
for i, g in enumerate(gruppen):
|
||||
if len(g) >= config.BAUSTEIN_MIN_ATOME:
|
||||
continue
|
||||
nachbarn = sorted((j for j in (i - 1, i + 1)
|
||||
if 0 <= j < len(gruppen)),
|
||||
key=lambda j: len(gruppen[j]))
|
||||
for j in nachbarn:
|
||||
if len(g) + len(gruppen[j]) <= config.BAUSTEIN_MAX_ATOME:
|
||||
a, b = min(i, j), max(i, j)
|
||||
gruppen[a] = gruppen[a] + gruppen[b]
|
||||
del gruppen[b]
|
||||
geaendert = True
|
||||
break
|
||||
if geaendert:
|
||||
break
|
||||
return gruppen
|
||||
|
||||
|
||||
def _gruppen_validieren(antwort: str | None, atome: list[dict]) -> list[list[dict]]:
|
||||
"""LLM-Split validieren (jede ID genau einmal); sonst deterministischer
|
||||
divmod-Split entlang der Quellreihenfolge."""
|
||||
@@ -159,22 +184,38 @@ def _gruppen_validieren(antwort: str | None, atome: list[dict]) -> list[list[dic
|
||||
return gruppen
|
||||
|
||||
|
||||
def _ziel_kerne(ziele: list[dict]) -> list[set]:
|
||||
"""Inhaltswort-Kerne aller Lernziele: Boilerplate wird GEMESSEN statt
|
||||
gelistet (Lektion 106 — die alte deutsche Floskel-Liste wäre in jeder
|
||||
anderen Sprache ein No-op). Stämme, die in der Mehrheit der Ziele
|
||||
vorkommen, sind Schablone („Der Lernende kann …") und fliegen raus."""
|
||||
stamm_je_ziel = [textkit.stamm_tokens(z["text"]) for z in ziele]
|
||||
n = len(stamm_je_ziel)
|
||||
alle = set().union(*stamm_je_ziel) if stamm_je_ziel else set()
|
||||
boilerplate = {t for t in alle
|
||||
if sum(t in s for s in stamm_je_ziel) > max(2, n * 0.5)}
|
||||
return [{t for t in s if len(t) > 3} - boilerplate for s in stamm_je_ziel]
|
||||
|
||||
|
||||
def _ziele_dedup(topic: str) -> int:
|
||||
"""Synonyme Lernziele deterministisch mergen (Audit: „einordnen" vs.
|
||||
„verorten" erzeugte Doppel-Bausteine → Writer-Echos). Falsch-Merge ist
|
||||
harmlos: Atome bleiben erhalten, hängen nur am selben Ziel."""
|
||||
„verorten" erzeugte Doppel-Bausteine → Writer-Echos). Nur Paare mit
|
||||
gleichem Soll-Punkt; Falsch-Merge ist harmlos: Atome bleiben erhalten,
|
||||
hängen nur am selben Ziel."""
|
||||
ziele = db.query("SELECT * FROM lernziele WHERE topic=? ORDER BY id", topic)
|
||||
kerne = _ziel_kerne(ziele)
|
||||
merges = 0
|
||||
for i, a in enumerate(ziele):
|
||||
if a.get("_weg"):
|
||||
continue
|
||||
for b in ziele[i + 1:]:
|
||||
if b.get("_weg"):
|
||||
for j, b in enumerate(ziele[i + 1:], i + 1):
|
||||
if b.get("_weg") or a["soll_id"] != b["soll_id"]:
|
||||
continue
|
||||
ka, kb = kerne[i], kerne[j]
|
||||
kern_jaccard = len(ka & kb) / len(ka | kb) if ka and kb else 0.0
|
||||
if textkit.titel_kern(a["titel"] or a["text"]) == \
|
||||
textkit.titel_kern(b["titel"] or b["text"]) or \
|
||||
textkit.jaccard(a["text"], b["text"]) >= \
|
||||
config.ZIEL_DEDUP_JACCARD:
|
||||
kern_jaccard >= config.ZIEL_DEDUP_JACCARD:
|
||||
db.update("atome", "ziel_id=?", (b["id"],), ziel_id=a["id"])
|
||||
db.execute("DELETE FROM bausteine WHERE ziel_id=?", b["id"])
|
||||
db.execute("DELETE FROM lernziele WHERE id=?", b["id"])
|
||||
@@ -223,7 +264,7 @@ async def ordnung(task: dict) -> engine.Ergebnis:
|
||||
antwort = await llm.call(
|
||||
run_id=task["run_id"], stufe="struktur", knoten="ordnung",
|
||||
item=f"perm:kapitel:{kap['id']}", role="judge", n=len(bs),
|
||||
skill_namen=["planer", "deutsch-praezise", "ordnung"],
|
||||
skill_namen=graph.skills_von(task["knoten"]),
|
||||
werte={"art": "Bausteine",
|
||||
"liste": "\n".join(f"[{b['id']}] {b['titel']}" for b in prior),
|
||||
"prior": ",".join(map(str, ids))})
|
||||
@@ -251,7 +292,7 @@ async def _kapitel_schnitt(task: dict, soll: list[dict]) -> list[tuple[str, list
|
||||
antwort = await llm.call(
|
||||
run_id=task["run_id"], stufe="struktur", knoten="ordnung",
|
||||
item="themen_schnitt", role="judge", n=len(ids),
|
||||
skill_namen=["planer", "deutsch-praezise", "themen-schnitt"],
|
||||
skill_namen=graph.skills_von(task["knoten"], extra="schnitt"),
|
||||
werte={"thema": thema, "richtwert": config.KAPITEL_SOLL_PUNKTE,
|
||||
"punkte": "\n".join(f"[{s['id']}] {s['punkt']}" for s in soll)})
|
||||
if antwort:
|
||||
@@ -269,7 +310,8 @@ async def _kapitel_schnitt(task: dict, soll: list[dict]) -> list[tuple[str, list
|
||||
detail="Schnitt ungültig, √n-Fallback")
|
||||
n_kap = max(1, round(math.sqrt(len(ids))))
|
||||
groesse = math.ceil(len(ids) / n_kap)
|
||||
return [(f"Teil {i + 1}", ids[i * groesse:(i + 1) * groesse])
|
||||
return [(config.TEXTE["teil"].format(titel=thema, n=i + 1),
|
||||
ids[i * groesse:(i + 1) * groesse])
|
||||
for i in range(n_kap) if ids[i * groesse:(i + 1) * groesse]]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user