This commit is contained in:
team3
2026-06-29 15:35:41 +02:00
parent 753b95b689
commit 87fc9b209a
6 changed files with 597 additions and 78 deletions

View File

@@ -56,8 +56,12 @@ SUBBAUSTEIN_KAPPE = 900 # Subbaustein-Finde-Loop je Chunk (15 min)
KONSOLIDIERUNG_CHUNK = 600 # bis hierher EIN globaler Judge (dedupt alles); darüber chunked + Merge-Pass — nur Fallback-Pfad KONSOLIDIERUNG_CHUNK = 600 # bis hierher EIN globaler Judge (dedupt alles); darüber chunked + Merge-Pass — nur Fallback-Pfad
DEDUP_MAX_RUNDEN = 3 # finaler Dedup-Pass: max. Iterationen (die kleinere Liste blockt je Runde neu) DEDUP_MAX_RUNDEN = 3 # finaler Dedup-Pass: max. Iterationen (die kleinere Liste blockt je Runde neu)
DEDUP_MIN_DELTA = 3 # Abbruch, wenn eine Runde weniger als dieses % der Liste entfernt (konvergiert) DEDUP_MIN_DELTA = 3 # Abbruch, wenn eine Runde weniger als dieses % der Liste entfernt (konvergiert)
DEDUP_PAAR_FLOOR = 0.6 # Mindest-Cosine fürs Kandidaten-Paar (Complete-Link aggregiert → kein Chaining)
DEDUP_PAARE_CHUNK = 40 # Paare je Judge-Paket (Pairwise-Verifikation statt Block-Mischer)
FILTER_CHUNK = 35 # zu beurteilende Bausteine je Judge im Degradier-Pass (volle Liste als Kontext)
# Frage-Muster-Chunks per LPT nach Sub-Last balancieren (Makespan), statt nach Baustein-Anzahl. # Frage-Muster-Chunks per LPT nach Sub-Last balancieren (Makespan), statt nach Baustein-Anzahl.
FRAGE_CHUNK_SUBS = 50 # Ziel-Summe relevanter Subs je Chunk FRAGE_CHUNK_SUBS = 50 # Ziel-Summe relevanter Subs je Chunk
FRAGE_MAX_RUNDEN = 3 # Nachhol-Runden für Subs ohne Muster (LLM lässt pro Chunk ~18 % aus)
FAKTEN_CHUNK_SUBS = 25 # Fakten-Extraktion: kleinere Chunks (Fakten sind umfangreicher als Muster) FAKTEN_CHUNK_SUBS = 25 # Fakten-Extraktion: kleinere Chunks (Fakten sind umfangreicher als Muster)
FAKTEN_CHECK_PANEL = 3 # Judges je Chunk im Fakten-Check (Mehrheit beanstandet) FAKTEN_CHECK_PANEL = 3 # Judges je Chunk im Fakten-Check (Mehrheit beanstandet)
KONSOLIDIERUNG_PANEL = 3 # Mapping-Judges je Chunk (Panel → Reconcile statt Einzel-Judge) KONSOLIDIERUNG_PANEL = 3 # Mapping-Judges je Chunk (Panel → Reconcile statt Einzel-Judge)
@@ -71,7 +75,7 @@ _bausteine_cancelled: set[str] = set()
_bausteine_step: dict[str, int] = {} _bausteine_step: dict[str, int] = {}
BAUSTEINE_STEPS = ( BAUSTEINE_STEPS = (
"Recherche", "Konsolidierung", "Klärung", "Recherche", "Konsolidierung", "Klärung", "Dedup", "Bausteine-Filter",
"Subbausteine finden", "Subbausteine wählen", "Subbausteine klären", "Subbausteine finden", "Subbausteine wählen", "Subbausteine klären",
"Fakten finden", "Fakten prüfen", "Fakten fix", "Fakten finden", "Fakten prüfen", "Fakten fix",
"Stufen finden", "Stufen wählen", "Stufen klären", "Stufen finden", "Stufen wählen", "Stufen klären",
@@ -207,7 +211,7 @@ def _bausteine_steps(topic: str) -> tuple:
laufen alle Pakete parallel; der Schritt bleibt, bis das letzte Paket fertig ist. laufen alle Pakete parallel; der Schritt bleibt, bis das letzte Paket fertig ist.
""" """
q = lade_quelle(topic) q = lade_quelle(topic)
base = ("Recherche", "Konsolidierung", "Klärung", "Dedup") base = ("Recherche", "Konsolidierung", "Klärung", "Dedup", "Bausteine-Filter")
rest = ( rest = (
"Subbausteine finden", "Subbausteine wählen", "Subbausteine klären", "Subbausteine finden", "Subbausteine wählen", "Subbausteine klären",
"Fakten finden", "Fakten prüfen", "Fakten fix", "Fakten finden", "Fakten prüfen", "Fakten fix",
@@ -237,7 +241,7 @@ def _melde_p(set_p, topic: str, schritt: str):
# Sonderschritte (Quelle laden, Ergänzung) gehören zur Phase „Inventar". # Sonderschritte (Quelle laden, Ergänzung) gehören zur Phase „Inventar".
PHASEN = ( PHASEN = (
("Quelle", ("Quelle aufbereiten",)), ("Quelle", ("Quelle aufbereiten",)),
("Inventar", ("Recherche", "Konsolidierung", "Klärung", "Dedup", "Ergänzung")), ("Inventar", ("Recherche", "Konsolidierung", "Klärung", "Dedup", "Bausteine-Filter", "Ergänzung")),
("Subbausteine", ("Subbausteine finden", "Subbausteine wählen", "Subbausteine klären")), ("Subbausteine", ("Subbausteine finden", "Subbausteine wählen", "Subbausteine klären")),
("Fakten", ("Fakten finden", "Fakten prüfen", "Fakten fix")), ("Fakten", ("Fakten finden", "Fakten prüfen", "Fakten fix")),
("Stufen", ("Stufen finden", "Stufen wählen", "Stufen klären")), ("Stufen", ("Stufen finden", "Stufen wählen", "Stufen klären")),
@@ -298,7 +302,8 @@ def _alle_slot_dateien(files: dict) -> list[Path]:
dyn = (list(arbeit.glob("subbaustein-*")) + list(arbeit.glob("fakten-*")) + list(arbeit.glob("stufe-*")) + list(arbeit.glob("relevanz-*")) dyn = (list(arbeit.glob("subbaustein-*")) + list(arbeit.glob("fakten-*")) + list(arbeit.glob("stufe-*")) + list(arbeit.glob("relevanz-*"))
+ list(arbeit.glob("frage-muster-*")) + list(arbeit.glob("gliederung-*")) + list(arbeit.glob("artefakt-*")) + list(arbeit.glob("frage-muster-*")) + list(arbeit.glob("gliederung-*")) + list(arbeit.glob("artefakt-*"))
+ list(arbeit.glob("recherche-*")) + list(arbeit.glob("konsolidierung-*")) + list(arbeit.glob("recherche-*")) + list(arbeit.glob("konsolidierung-*"))
+ list(arbeit.glob("klaerung*")) + list(arbeit.glob("dedup-*"))) if arbeit.is_dir() else [] + list(arbeit.glob("klaerung*")) + list(arbeit.glob("dedup-*"))
+ list(arbeit.glob("inventar-filter*"))) if arbeit.is_dir() else []
return [ return [
*files["recherche"], files["recherche_mapping"], *files["recherche"], files["recherche_mapping"],
*(p for slots in files["auswahl"].values() for p in slots), *(p for slots in files["auswahl"].values() for p in slots),
@@ -1577,31 +1582,79 @@ async def _frage_muster_block(ctx: GenContext, set_p, files: dict, sidecar: dict
final_by_titel = _final_by_titel(range(len(chunks))) final_by_titel = _final_by_titel(range(len(chunks)))
ergebnis = {t: (final_by_titel.get(t) or roh_by_titel.get(t) or []) for t, _ in bausteine} ergebnis = {t: (final_by_titel.get(t) or roh_by_titel.get(t) or []) for t, _ in bausteine}
# Phase „Fragen prüfen": Chunks mit ≥1 leeren Baustein eine Runde nachholen. # Phase „Fragen prüfen": pro-Sub-Vollständigkeit. Generatoren stürzen zufällig ab (~15 %),
# je Chunk 1 Agent ohne Retry → Subs (ganze Bausteine) fallen still durch. Darum mehrere
# Runden, die NUR die fehlenden Subs gezielt nachfordern (kurze Pakete, Frage-Muster-Recherche).
set_p("Fragen prüfen…", step=_step_idx(topic, "Fragen prüfen")) set_p("Fragen prüfen…", step=_step_idx(topic, "Fragen prüfen"))
leer_titel = {t for t, _ in bausteine if not ergebnis.get(t)}
if leer_titel: def _fehlende_subs() -> list[tuple[str, list[str]]]:
nach = [ci for ci, idxs in enumerate(chunks) if any(bausteine[i][0] in leer_titel for i in idxs)] out = []
_log(topic, f"Frage-Muster: {len(leer_titel)} Baustein(e) ohne Muster — Nachrunde über {len(nach)} Chunk(s)") for t, subs in bausteine:
for ci in nach: hab = {_norm_titel(e["subbaustein"]) for e in ergebnis.get(t) or []}
roh_path(ci).unlink(missing_ok=True) miss = [s for s in subs if _norm_titel(s) not in hab]
final_path(ci).unlink(missing_ok=True) if miss:
await finde_alle(nach) out.append((t, miss))
return out
def _nach_block(items): # items: [(baustein_titel, [fehlende sub_titel])]
bloecke = []
for titel, subs in items:
zeilen = []
for s in subs:
z = f"- {s}"
fk = fakten_by.get((titel, _norm_titel(s)))
if fk and (ft := _fakten_zeilen(fk)):
z += "\n" + "\n".join(" " + l for l in ft.split("\n"))
zeilen.append(z)
bloecke.append(f"BAUSTEIN: {titel}\nSUBBAUSTEINE:\n" + "\n".join(zeilen))
return "\n\n".join(bloecke)
async def _nachfordere(runde, pi, items):
fp = arbeit / f"frage-muster-nach{runde}-c{pi}.json"
if _frage_muster_chunk_schema(_json_datei(fp)):
return # Resume
subs_total = sum(len(s) for _, s in items)
await run_single_slot(
ctx, f"Frage-Muster Nachholung R{runde}/{pi}",
key=f"bausteine-{topic}-frage-muster-nach{runde}-c{pi}",
prompt=_prompt("Frage-Muster-Recherche", topic=topic, bausteine=_nach_block(items),
out_path=fp, extra=_extra(instructions)),
role="fast", capabilities="files",
payload=lambda result, p=fp: _frage_muster_chunk_schema(_json_datei(p)),
timeout=_timeout("frage_muster", subs_total),
)
for runde in range(1, FRAGE_MAX_RUNDEN + 1):
fehlend = _fehlende_subs()
if not fehlend:
break
n_subs = sum(len(s) for _, s in fehlend)
_log(topic, f"Frage-Muster Runde {runde}: {n_subs} Sub(s) in {len(fehlend)} Baustein(en) ohne Muster — Nachforderung")
pakete = _lpt_chunks([len(s) for _, s in fehlend], FRAGE_CHUNK_SUBS)
paket_items = [[fehlend[i] for i in idxs] for idxs in pakete]
await _gather_fortschritt(
[_nachfordere(runde, pi, items) for pi, items in enumerate(paket_items)],
len(paket_items), _melde_p(set_p, topic, "Fragen prüfen"))
if is_cancelled(): if is_cancelled():
return None return None
for titel, eintraege in _waehle_all(nach).items(): # Output je Paket parsen + neu gewonnene Subs mergen (Vorhandene nicht überschreiben).
roh_by_titel[titel] = eintraege for pi, items in enumerate(paket_items):
await klaere_alle(nach) titel_subs = {t: subs for t, subs in items}
if is_cancelled(): ctitel = list(titel_subs.keys())
return None for e in _frage_muster_chunk_schema(_json_datei(arbeit / f"frage-muster-nach{runde}-c{pi}.json")) or []:
for titel, eintraege in _final_by_titel(nach).items(): titel = _match_sub(e["baustein"], ctitel)
final_by_titel[titel] = eintraege if titel not in titel_subs:
for t, _ in bausteine: continue
if not ergebnis.get(t): sub = _match_sub(e["subbaustein"], titel_subs[titel])
ergebnis[t] = final_by_titel.get(t) or roh_by_titel.get(t) or [] hab = {_norm_titel(x["subbaustein"]) for x in ergebnis.get(titel) or []}
rest = [t for t, _ in bausteine if not ergebnis.get(t)] if _norm_titel(sub) in hab:
if rest: continue
_log(topic, f"Frage-Muster: {len(rest)} Baustein(e) bleiben leer (Fallback Live): {rest[:5]}") ergebnis.setdefault(titel, []).append({"subbaustein": sub, "frage": e["frage"]})
rest = _fehlende_subs()
if rest:
n = sum(len(s) for _, s in rest)
_log(topic, f"Frage-Muster: {n} Sub(s) in {len(rest)} Baustein(en) bleiben nach {FRAGE_MAX_RUNDEN} Runden leer: {[t for t, _ in rest][:5]}")
return ergebnis return ergebnis
@@ -1972,6 +2025,21 @@ def _aspekt_marker(titel: str) -> int:
return sum(1 for m in _ASPEKT_MARKER if m in t) return sum(1 for m in _ASPEKT_MARKER if m in t)
_VERWEIS_RE = re.compile(r'^(Satz|Lemma|Korollar|Bemerkung|Definition)\s*[\d.]+\s*(\([a-z]\)|[a-z])?\s*$', re.I)
def _ist_verweis(titel: str) -> bool:
"""True für reine Verweis-/Platzhalter-Titel OHNE sprechenden Inhalt: „Satz 7.18", „Lemma 6.2",
„Korollar 6.18" (Nummer ohne Namen) sowie markierte Stellen „Bedingung (**)". NICHT „Satz 6.24:
Cook/Levin" (hat Namen) und NICHT kurze Fachsymbole wie „P⊆NP"/„Σ*" (echte Konzepte)."""
t = titel.strip()
if _VERWEIS_RE.match(t):
return True
if re.search(r'\(\*+\)', t): # markierte Stelle „(**)" / „(*)"
return True
return False
def _canonical(kandidaten: list[dict], idxs: list[int], gesehen_norm: set[str]) -> dict: def _canonical(kandidaten: list[dict], idxs: list[int], gesehen_norm: set[str]) -> dict:
"""Repräsentant eines Clusters = das Hauptkonzept (wenigste Eigenschafts-Marker — das Problem """Repräsentant eines Clusters = das Hauptkonzept (wenigste Eigenschafts-Marker — das Problem
selbst, nicht „… ∈ NP"); Tie → häufigster norm-Titel → meiste Reader. Titel global eindeutig selbst, nicht „… ∈ NP"); Tie → häufigster norm-Titel → meiste Reader. Titel global eindeutig
@@ -1983,7 +2051,9 @@ def _canonical(kandidaten: list[dict], idxs: list[int], gesehen_norm: set[str])
def gewicht(nb: str): def gewicht(nb: str):
ms = by_norm[nb] ms = by_norm[nb]
reader = set().union(*[set(kandidaten[m]["reader"]) for m in ms]) if ms else set() reader = set().union(*[set(kandidaten[m]["reader"]) for m in ms]) if ms else set()
return (-_aspekt_marker(nb), len(ms), len(reader)) # aspekt-arm zuerst = Hauptkonzept # Verweis-/Platzhalter-Titel ("Satz 7.18") zuletzt — sprechendes Mitglied bevorzugen.
echt = not _ist_verweis(kandidaten[ms[0]]["titel"])
return (echt, -_aspekt_marker(nb), len(ms), len(reader))
best = max(by_norm, key=gewicht) best = max(by_norm, key=gewicht)
k = max(by_norm[best], key=lambda m: len(kandidaten[m]["beschreibung"])) k = max(by_norm[best], key=lambda m: len(kandidaten[m]["beschreibung"]))
@@ -2231,10 +2301,17 @@ async def _klaere_inventar(ctx: GenContext, set_p, files: dict) -> bool:
return True return True
set_p("Klärung läuft…", step=_step_idx(topic, "Klärung")) set_p("Klärung läuft…", step=_step_idx(topic, "Klärung"))
rest_rows = await db.list_bausteine(topic, status="rest") rest_rows = await db.list_bausteine(topic, status="rest")
if rest_rows: # Durchgängiges Gate (EDC „Define"): auch Konsens-Bausteine mit Verweis-/Platzhalter-Titel
# prüfen („Satz 7.18", „Korollar 6.18", „Bedingung (**)") — sie umgehen sonst jede Prüfung.
verdaechtig = [b for b in await db.list_bausteine(topic, status="konsens") if _ist_verweis(b["titel"])]
pruef_rows = rest_rows + verdaechtig
if pruef_rows:
arbeit = files["arbeit"] arbeit = files["arbeit"]
paths = [arbeit / f"klaerung-j{j}.json" for j in range(1, KONSOLIDIERUNG_PANEL + 1)] paths = [arbeit / f"klaerung-j{j}.json" for j in range(1, KONSOLIDIERUNG_PANEL + 1)]
offen = [(j, p) for j, p in enumerate(paths, 1) if _runde_schema(_json_datei(p), final=True) is None] # final=False: ein Judge mit versehentlich nicht-leerem `rest` darf nicht komplett ausfallen
# (sonst kollabiert das Panel auf 1 Judge). Sein `aufnehmen` zählt; rest-Einträge gelten als
# nicht-aufgenommen. Die „rest leer"-Vorgabe steht weiter im Prompt.
offen = [(j, p) for j, p in enumerate(paths, 1) if _runde_schema(_json_datei(p)) is None]
for _, p in offen: for _, p in offen:
p.unlink(missing_ok=True) p.unlink(missing_ok=True)
if offen: if offen:
@@ -2242,19 +2319,20 @@ async def _klaere_inventar(ctx: GenContext, set_p, files: dict) -> bool:
"key": f"bausteine-{topic}-klaerung-j{j}", "key": f"bausteine-{topic}-klaerung-j{j}",
"prompt": _prompt( "prompt": _prompt(
"Bausteine-Klaerung", topic=topic, "Bausteine-Klaerung", topic=topic,
rest="\n".join(f"- {b['titel']}" for b in rest_rows), rest="\n".join(f"- {b['titel']}{b['beschreibung']}" if b['beschreibung'] else f"- {b['titel']}"
for b in pruef_rows),
final="\n- Entscheide JEDEN Eintrag. `rest` MUSS leer sein.", final="\n- Entscheide JEDEN Eintrag. `rest` MUSS leer sein.",
out_path=p, out_path=p,
), ),
"role": "judge", "capabilities": "files", "role": "judge", "capabilities": "files",
"payload": (lambda result, p=p: _runde_schema(_json_datei(p), final=True)), "payload": (lambda result, p=p: _runde_schema(_json_datei(p))),
} for j, p in offen] } for j, p in offen]
vorhanden = KONSOLIDIERUNG_PANEL - len(offen) vorhanden = KONSOLIDIERUNG_PANEL - len(offen)
await _race(topic, "Klärung", slots, max(1, 2 - vorhanden), await _race(topic, "Klärung", slots, max(1, 2 - vorhanden),
_timeout("auswahl_mapping", len(rest_rows)), provider, cancelled=is_cancelled, grace=KONSENS_GRACE) _timeout("auswahl_mapping", len(pruef_rows)), provider, cancelled=is_cancelled, grace=KONSENS_GRACE)
if is_cancelled(): if is_cancelled():
return False return False
outs = [r for p in paths if (r := _runde_schema(_json_datei(p), final=True))] outs = [r for p in paths if (r := _runde_schema(_json_datei(p)))]
if not outs: if not outs:
_bausteine_errors[topic] = "Klärung fehlgeschlagen" _bausteine_errors[topic] = "Klärung fehlgeschlagen"
return False return False
@@ -2263,18 +2341,85 @@ async def _klaere_inventar(ctx: GenContext, set_p, files: dict) -> bool:
for aufnehmen, _ in outs: for aufnehmen, _ in outs:
for nt in {_norm_titel(_titel(t)) for t in aufnehmen}: for nt in {_norm_titel(_titel(t)) for t in aufnehmen}:
votes[nt] = votes.get(nt, 0) + 1 votes[nt] = votes.get(nt, 0) + 1
for b in rest_rows: # Umbenenn-Vorschläge (additiv aus dem Roh-JSON — _runde_schema kennt das Feld nicht):
# behaltene Verweis-/Platzhalter-Titel → sprechender Name aus dem Inhalt. Alt-Titel-Norm
# bleibt stabil (bricht den votes-Match nicht); je Alt-Titel der häufigste Vorschlag.
umbenenn: dict[str, dict[str, int]] = {}
for p in paths:
d = _json_datei(p)
umb = d.get("umbenennen") if isinstance(d, dict) else None
if isinstance(umb, dict):
for alt, neu in umb.items():
neu = str(neu).strip()
if neu:
umbenenn.setdefault(_norm_titel(str(alt)), {}).setdefault(neu, 0)
umbenenn[_norm_titel(str(alt))][neu] += 1
gesehen_norm = {b["titel_norm"] for b in await db.list_bausteine(topic, status="konsens")}
for b in pruef_rows:
auf = votes.get(b["titel_norm"], 0) * 2 >= len(outs) auf = votes.get(b["titel_norm"], 0) * 2 >= len(outs)
await db.set_baustein_status(topic, b["titel_norm"], "konsens" if auf else "verworfen") if not auf:
await db.set_baustein_status(topic, b["titel_norm"], "verworfen")
continue
neu_titel = None
if _ist_verweis(b["titel"]) and (vors := umbenenn.get(b["titel_norm"])):
kand = max(vors, key=lambda k: (vors[k], len(k)))
if not _ist_verweis(kand):
neu_titel = kand
if neu_titel:
nn, t, n = _norm_titel(neu_titel), neu_titel, 2
while nn in gesehen_norm:
t, nn, n = f"{neu_titel} ({n})", _norm_titel(f"{neu_titel} ({n})"), n + 1
gesehen_norm.add(nn)
await db.set_baustein_status(topic, b["titel_norm"], "konsens", titel=t, neu_norm=nn)
else:
await db.set_baustein_status(topic, b["titel_norm"], "konsens")
await db.set_step_status(topic, "Klärung", "fertig") await db.set_step_status(topic, "Klärung", "fertig")
return True return True
def _paare_schema(data) -> dict[int, bool] | None:
"""{"paare": {"1": "ja", "2": "nein", …}} → {paar_nr: True/False} · sonst None."""
if not isinstance(data, dict) or not isinstance(data.get("paare"), dict):
return None
out: dict[int, bool] = {}
for k, v in data["paare"].items():
try:
nr = int(k)
except (ValueError, TypeError):
continue
out[nr] = str(v).strip().casefold() in ("ja", "yes", "true", "1")
return out or None
def _cliquen(n: int, kanten: list[tuple[int, int]]) -> list[list[int]]:
"""Complete-Link: greedy maximale Cliquen über die bestätigten Dubletten-Kanten. Eine Gruppe
entsteht nur, wenn ALLE ihre Knoten paarweise verbunden sind → kein Chaining (A=B + B=C bildet
KEINE Gruppe {A,B,C}, solange A=C fehlt). Nur Cliquen ≥2 werden zurückgegeben."""
adj: dict[int, set[int]] = {i: set() for i in range(n)}
for a, b in kanten:
adj[a].add(b)
adj[b].add(a)
benutzt: set[int] = set()
gruppen: list[list[int]] = []
for v in sorted(range(n), key=lambda x: -len(adj[x])):
if v in benutzt or not adj[v]:
continue
clique = {v}
for u in sorted(adj[v], key=lambda x: -len(adj[x])):
if u not in benutzt and clique <= adj[u] | {u}: # u mit ALLEN bisherigen verbunden
clique.add(u)
if len(clique) >= 2:
gruppen.append(sorted(clique))
benutzt |= clique
return gruppen
async def _dedup_inventar(ctx: GenContext, set_p, files: dict) -> bool: async def _dedup_inventar(ctx: GenContext, set_p, files: dict) -> bool:
"""Finaler iterativer Dedup-Pass über die fertige Konsens-Liste: Embedding-Block + LLM- """Finaler Dedup-Pass über die fertige Konsens-Liste: Pairwise-Verifikation (Entity
Gruppierung. Fängt Dubletten, die Konsolidierung (Block-Grenzen, Cap) und Klärung (lange Resolution). Embedding liefert Kandidaten-PAARE (Cosine ≥ DEDUP_PAAR_FLOOR), ein Judge
Liste, Lost-in-the-Middle) übersehen. Pro Gruppe bleibt EIN Repräsentant, der Rest wird bestätigt JEDES Paar einzeln (ja = dieselbe Dublette). NUR bestätigte Paare werden zu
verworfen. Iteriert, weil die kleinere Liste je Runde neu blockt (Cross-Block-Reste).""" Merge-Kanten (Union-Find) — kein Chaining, kein Aspekt-Über-Mergen wie beim Block-Mischer.
Pro Gruppe bleibt EIN Repräsentant (Hauptkonzept), der Rest wird verworfen."""
topic, is_cancelled = ctx.topic, ctx.is_cancelled topic, is_cancelled = ctx.topic, ctx.is_cancelled
if await db.get_step_status(topic, "Dedup") == "fertig": if await db.get_step_status(topic, "Dedup") == "fertig":
return True return True
@@ -2283,39 +2428,178 @@ async def _dedup_inventar(ctx: GenContext, set_p, files: dict) -> bool:
return True return True
set_p("Dedup…", step=_step_idx(topic, "Dedup")) set_p("Dedup…", step=_step_idx(topic, "Dedup"))
arbeit = files["arbeit"] arbeit = files["arbeit"]
for runde in range(1, DEDUP_MAX_RUNDEN + 1): konsens = await db.list_bausteine(topic, status="konsens")
konsens = await db.list_bausteine(topic, status="konsens") if len(konsens) >= 2:
if len(konsens) < 2: import numpy as np
break
texts = [f"{b['titel']}{b['beschreibung']}" if b["beschreibung"] else b["titel"] for b in konsens] texts = [f"{b['titel']}{b['beschreibung']}" if b["beschreibung"] else b["titel"] for b in konsens]
sims = await asyncio.to_thread(embedding.embed_sims, texts) sims = await asyncio.to_thread(embedding.embed_sims, texts)
if sims is None: if sims is not None:
break n = len(konsens)
blocks = await asyncio.to_thread(embedding.capped_blocks, sims, None, None) iu = np.triu_indices(n, k=1)
gruppen = await _block_gruppieren(ctx, set_p, arbeit, konsens, blocks, kand = [(int(iu[0][m]), int(iu[1][m])) for m in np.where(sims[iu] >= DEDUP_PAAR_FLOOR)[0]]
praefix=f"dedup-r{runde}", schritt="Dedup") _log(topic, f"Dedup: {len(kand)} Kandidaten-Paare (Cosine ≥ {DEDUP_PAAR_FLOOR}) → Pairwise-Filter")
if is_cancelled(): pakete = [kand[i:i + DEDUP_PAARE_CHUNK] for i in range(0, len(kand), DEDUP_PAARE_CHUNK)]
return False
weg = 0 def paar_path(pi): return arbeit / f"dedup-paar-c{pi}.json"
for idxs in gruppen:
if len(idxs) < 2: async def _filt(pi, paare):
continue fp = paar_path(pi)
# Repräsentant = Hauptkonzept (wenigste Eigenschafts-Marker), dann kürzester Titel; Rest verwerfen. if _paare_schema(_json_datei(fp)):
rep = min(idxs, key=lambda k: (_aspekt_marker(konsens[k]["titel"]), len(konsens[k]["titel"]), k)) return # Resume
for k in idxs: zeilen = "\n\n".join(
if k != rep: f"{j + 1}.\nA: {konsens[a]['titel']}{konsens[a]['beschreibung']}"
await db.set_baustein_status(topic, konsens[k]["titel_norm"], "verworfen") f"\nB: {konsens[b]['titel']}{konsens[b]['beschreibung']}"
weg += 1 for j, (a, b) in enumerate(paare))
atomic_write_json(arbeit / f"dedup-runde-{runde}.json", await run_single_slot(
{"vorher": len(konsens), "entfernt": weg, ctx, f"Dedup-Paare {pi}",
"gruppen": [[konsens[k]["titel"] for k in g] for g in gruppen if len(g) > 1]}, indent=1) key=f"bausteine-{topic}-dedup-paar-c{pi}",
_log(topic, f"Dedup Runde {runde}: {len(konsens)}{len(konsens) - weg} ({weg})") prompt=_prompt("Bausteine-Paar-Filter", topic=topic, paare=zeilen, out_path=fp),
if weg <= max(1, len(konsens) * DEDUP_MIN_DELTA // 100): # konvergiert → Schluss role="judge", capabilities="files",
break payload=lambda result, p=fp: _paare_schema(_json_datei(p)),
timeout=_timeout("auswahl_mapping", len(paare)),
)
await _gather_fortschritt([_filt(pi, p) for pi, p in enumerate(pakete)],
len(pakete), _melde_p(set_p, topic, "Dedup"))
if is_cancelled():
return False
# Bestätigte "ja"-Kanten sammeln, dann COMPLETE-LINK (greedy Cliquen) statt Single-Link
# Union-Find — verhindert Chaining (A=B + B=C mergt NICHT A,C ohne direktes A=C).
kanten, ja = [], 0
for pi, paare in enumerate(pakete):
urteil = _paare_schema(_json_datei(paar_path(pi))) or {}
for j, (a, b) in enumerate(paare):
if urteil.get(j + 1):
kanten.append((a, b))
ja += 1
gruppen = _cliquen(n, kanten)
weg = 0
for idxs in gruppen:
# Repräsentant = Hauptkonzept (wenigste Eigenschafts-Marker), dann kürzester Titel.
rep = min(idxs, key=lambda k: (_aspekt_marker(konsens[k]["titel"]), len(konsens[k]["titel"]), k))
for k in idxs:
if k != rep:
await db.set_baustein_status(topic, konsens[k]["titel_norm"], "verworfen")
weg += 1
from collections import Counter
atomic_write_json(arbeit / "dedup-runde-1.json",
{"vorher": n, "entfernt": weg, "paare_geprueft": len(kand), "paare_ja": ja,
"clique_groessen": dict(sorted(Counter(len(g) for g in gruppen).items())),
"gruppen": [[konsens[k]["titel"] for k in g] for g in gruppen]}, indent=1)
_log(topic, f"Dedup (pairwise): {n}{n - weg} ({weg}); {ja}/{len(kand)} Paare bestätigt")
await db.set_step_status(topic, "Dedup", "fertig") await db.set_step_status(topic, "Dedup", "fertig")
return True return True
def _filter_schema(data) -> dict[int, int] | None:
"""{"fragmente": {"3": 7, "12": 8}} → {baustein_nr: eltern_nr} · None bei ungültiger Struktur.
Leeres dict = gültig (nichts zu degradieren). Eltern ≠ sich selbst."""
if not isinstance(data, dict) or not isinstance(data.get("fragmente"), dict):
return None
out: dict[int, int] = {}
for k, v in data["fragmente"].items():
try:
nr, eltern = int(k), int(v)
except (ValueError, TypeError):
continue
if nr != eltern:
out[nr] = eltern
return out
# Reine Notation/Symbole ohne eigenständiges Konzept — eng gehalten (FP~0 an aak geprüft;
# „KNF"/„MST"/„NP" treffen NICHT). Diese werden autonom verworfen (brauchen keinen Eltern).
_FILTER_NOTATION = re.compile(r'^\s*\|.{1,6}\|\s*$|^Güte\s+\d+\s*$')
# Eigenschaft-/Laufzeit-Verdacht — markiert Zeilen fürs Judge-Urteil (KEIN Auto-Drop, FP zu hoch:
# „NP-Schwere", Reduktionen mit „∈NP" sind echte Bausteine). Ergänzt _aspekt_marker.
_FILTER_PRAEDIKAT = re.compile(
r'ist NP-(vollständig|schwer)|NP-(Vollständigkeit|Schwere) von|ETH (Konsequenz|Lower Bound)'
r'|Approximationsschema nach|Laufzeit O\(|∈ ?NP', re.I)
def _filter_verdacht(b: dict) -> bool:
"""Heuristik-Flag: könnte eine Eigenschaft/ein Detail eines anderen Bausteins sein."""
return _aspekt_marker(b["titel"]) > 0 or bool(_FILTER_PRAEDIKAT.search(f"{b['titel']} {b['beschreibung'] or ''}"))
async def _filter_inventar(ctx: GenContext, set_p, files: dict) -> bool:
"""Degradier-Pass (Granularität): trennt echte Bausteine von Fragmenten (Eigenschaften,
Beweis-Gadgets, Notation, Laufzeit-Details). Jeder Judge sieht die VOLLE Baustein-Liste
(Self-Containment ist relational) und markiert Fragmente MIT Eltern-Baustein aus der Liste.
Fragment + Eltern-in-Liste → verworfen (Inhalt kommt als Subbaustein des Eltern zurück).
Ohne Eltern oder im Zweifel → behalten (kein Konzept-Verlust)."""
topic, is_cancelled = ctx.topic, ctx.is_cancelled
if await db.get_step_status(topic, "Bausteine-Filter") == "fertig":
return True
set_p("Bausteine-Filter…", step=_step_idx(topic, "Bausteine-Filter"))
arbeit = files["arbeit"]
konsens_all = await db.list_bausteine(topic, status="konsens")
# Sicherheitsnetz: reine Notation autonom verwerfen (FP~0, kein Eltern nötig). Der Judge
# übersieht solche Symbole zuverlässig (Recall-Problem), darum deterministisch vorab.
konsens, notation_weg = [], []
for b in konsens_all:
if _FILTER_NOTATION.search(b["titel"]):
await db.set_baustein_status(topic, b["titel_norm"], "verworfen")
notation_weg.append(b["titel"])
else:
konsens.append(b)
if notation_weg:
_log(topic, f"Bausteine-Filter: {len(notation_weg)} reine Notation verworfen: {notation_weg[:6]}")
if len(konsens) < 2:
await db.set_step_status(topic, "Bausteine-Filter", "fertig")
return True
n = len(konsens)
# ⚠ markiert Verdachts-Zeilen (Eigenschaft/Laufzeit) — der Judge MUSS sie pro Eintrag prüfen.
def _zeile(i, b):
mark = "" if _filter_verdacht(b) else ""
return f"{i}. {mark}{b['titel']}{b['beschreibung']}" if b["beschreibung"] else f"{i}. {mark}{b['titel']}"
voll = "\n".join(_zeile(i, b) for i, b in enumerate(konsens, 1))
chunks = [list(range(i, min(i + FILTER_CHUNK, n + 1))) for i in range(1, n + 1, FILTER_CHUNK)]
def filt_path(ci): return arbeit / f"inventar-filter-c{ci}.json"
async def _beurteile(ci, nummern):
fp = filt_path(ci)
if _filter_schema(_json_datei(fp)) is not None:
return # Resume
await run_single_slot(
ctx, f"Bausteine-Filter {ci}",
key=f"bausteine-{topic}-inventar-filter-c{ci}",
prompt=_prompt("Bausteine-Filter", topic=topic, liste=voll,
von=nummern[0], bis=nummern[-1], out_path=fp),
role="judge", capabilities="files",
payload=lambda result, p=fp: _filter_schema(_json_datei(p)),
timeout=_timeout("auswahl_mapping", len(nummern)),
)
await _gather_fortschritt([_beurteile(ci, nm) for ci, nm in enumerate(chunks)],
len(chunks), _melde_p(set_p, topic, "Bausteine-Filter"))
if is_cancelled():
return False
frag: dict[int, int] = {}
for ci, nummern in enumerate(chunks):
urteil = _filter_schema(_json_datei(filt_path(ci))) or {}
nset = set(nummern)
for nr, eltern in urteil.items():
if 1 <= eltern <= n and nr in nset:
frag[nr] = eltern
# Ketten-Schutz: ein Baustein, der selbst Eltern eines Fragments ist, bleibt (sein Kind braucht den Anker).
eltern_set = set(frag.values())
weg, debug = 0, []
for nr, eltern in frag.items():
if nr in eltern_set:
continue
b = konsens[nr - 1]
await db.set_baustein_status(topic, b["titel_norm"], "verworfen")
weg += 1
debug.append({"fragment": b["titel"], "eltern": konsens[eltern - 1]["titel"]})
atomic_write_json(arbeit / "inventar-filter.json",
{"vorher": n, "degradiert": weg, "fragmente": debug}, indent=1)
_log(topic, f"Bausteine-Filter: {n}{n - weg} ({weg} Fragmente → Subbausteine)")
await db.set_step_status(topic, "Bausteine-Filter", "fertig")
return True
# --- Gliederung (Bausteine-Artefakt: Kapitel-Struktur, vom Guide nur gelesen) --- # --- Gliederung (Bausteine-Artefakt: Kapitel-Struktur, vom Guide nur gelesen) ---
def _gliederung_komplett(files: dict) -> bool: def _gliederung_komplett(files: dict) -> bool:
@@ -2709,7 +2993,7 @@ async def _reset_db_ab_phase(topic: str, label: str) -> None:
await db.delete_subbausteine(topic) await db.delete_subbausteine(topic)
if idx <= 1: # Inventar: Inventar + Recherche-Schritte — Sichtung bleibt if idx <= 1: # Inventar: Inventar + Recherche-Schritte — Sichtung bleibt
await db.delete_bausteine(topic) await db.delete_bausteine(topic)
await db.delete_pipeline_state(topic, ["Recherche", "Konsolidierung", "Klärung", "Dedup"]) await db.delete_pipeline_state(topic, ["Recherche", "Konsolidierung", "Klärung", "Dedup", "Bausteine-Filter"])
if idx <= 0: # Quelle: Sichtung neu (Coverage/inhalt + Schritt) if idx <= 0: # Quelle: Sichtung neu (Coverage/inhalt + Schritt)
await db.delete_coverage(topic) await db.delete_coverage(topic)
await db.delete_pipeline_state(topic, ["Quelle aufbereiten"]) await db.delete_pipeline_state(topic, ["Quelle aufbereiten"])
@@ -2788,6 +3072,10 @@ async def generate_bausteine(topic: str, instructions: str = "", provider: str =
if is_cancelled(): if is_cancelled():
abgebrochen() abgebrochen()
return return
if not await _filter_inventar(ctx, set_p, files):
if is_cancelled():
abgebrochen()
return
konsens_rows = await db.list_bausteine(topic, status="konsens") konsens_rows = await db.list_bausteine(topic, status="konsens")
entries = { entries = {
i: (f"{b['titel']}{b['beschreibung']}" if b["beschreibung"] else b["titel"]) i: (f"{b['titel']}{b['beschreibung']}" if b["beschreibung"] else b["titel"])

View File

@@ -764,14 +764,18 @@ async def list_bausteine(topic: str, status: str | None = None) -> list[dict]:
return out return out
async def set_baustein_status(topic: str, titel_norm: str, status: str, titel: str | None = None, beschreibung: str | None = None) -> None: async def set_baustein_status(topic: str, titel_norm: str, status: str, titel: str | None = None, beschreibung: str | None = None, neu_norm: str | None = None) -> None:
"""Status setzen; optional Titel/Beschreibung aktualisieren (z.B. nach semantischem Merge).""" """Status setzen; optional Titel/Beschreibung aktualisieren (z.B. nach semantischem Merge).
`neu_norm` benennt den Norm-Schlüssel um (Klärung: Verweis-Titel → sprechender Name). Nur
sicher, solange noch keine Subbausteine/Fakten am alten `titel_norm` hängen."""
db = await get_db() db = await get_db()
fields = {"status": status, "updated_at": _now()} fields = {"status": status, "updated_at": _now()}
if titel is not None: if titel is not None:
fields["titel"] = titel fields["titel"] = titel
if beschreibung is not None: if beschreibung is not None:
fields["beschreibung"] = beschreibung fields["beschreibung"] = beschreibung
if neu_norm is not None and neu_norm != titel_norm:
fields["titel_norm"] = neu_norm
sets = ", ".join(f"{k} = :{k}" for k in fields) sets = ", ".join(f"{k} = :{k}" for k in fields)
await db.execute( await db.execute(
f"UPDATE bausteine SET {sets} WHERE topic = :topic AND titel_norm = :titel_norm", f"UPDATE bausteine SET {sets} WHERE topic = :topic AND titel_norm = :titel_norm",

View File

@@ -0,0 +1,177 @@
# Frage-Muster für Lern-Prüfung: aak
---
## BAUSTEIN: CliqueAndIndependentSet-Problem
### Subbaustein: Independent Set: Knotenmenge ohne Kanten zwischen je zwei Knoten
**Muster:** Was ist die formale Definition eines Independent Set in einem Graphen G = (V, E)?
### Subbaustein: Komplementgraph (V, E) bildet IS und Clique aufeinander ab
**Muster:** Wie hängt die Existenz einer Clique in G mit der Existenz eines Independent Set in G' (dem Komplementgraphen) zusammen?
### Subbaustein: Existenz von Independent Set der Größe k ist NP-vollständig
**Muster:** Durch welche Polynomialzeitreduktion lässt sich zeigen, dass Independent Set NP-schwer ist?
### Subbaustein: Existenz von Clique der Größe k ist NP-vollständig
**Muster:** Wie wird in Satz 6.26 die NP-Schwere von k-Clique bewiesen?
### Subbaustein: Reduktion k-CLIQUE ⪯ k-INDEPENDENT-SET mittels Komplementgraph
**Muster:** Wie transformiert man eine Instanz (G, k) von CLIQUE in eine Instanz von INDEPENDENT-SET?
### Subbaustein: Clique: Knotenmenge, in der je zwei Knoten durch eine Kante verbunden sind
**Muster:** Wann ist eine Knotenmenge C ⊆ V eine Clique in einem Graphen G = (V, E)?
### Subbaustein: Complementärgraph: Independent Set in G ist Clique in G̅
**Muster:** Welche Beziehung besteht zwischen einem Independent Set in G und einer Clique in G̅?
### Subbaustein: Beide Probleme sind NP-vollständig
**Muster:** Welche fundamentale Konsequenz ergibt sich aus Satz 6.16, wenn ein NP-vollständiges Problem in P liegt?
### Subbaustein: Frage: Existiert Independent Set bzw. Clique der Größe k in G?
**Muster:** Was ist die Eingabe und was die Ausgabe bei den Entscheidungsproblemen k-Clique und k-Independent-Set?
### Subbaustein: Komplementarität: Independent Set in G = Clique im Komplementgraphen
**Muster:** Warum sind Clique und Independent Set gegenseitig in Polynomialzeit aufeinander reduzierbar?
### Subbaustein: CLIQUE: Knotenmenge in der je zwei Knoten durch Kante verbunden sind
**Muster:** Welche Bedingung müssen alle Knotenpaare einer Clique erfüllen?
### Subbaustein: Komplementär: IS in G = CLIQUE in G̅
**Muster:** Was bleibt bei der Bildung des Komplementgraphen G̅ gleich und was ändert sich?
### Subbaustein: Existenz von IS bzw. CLIQUE der Größe k ist NP-vollständig
**Muster:** Welche untere Schranke für die Laufzeit von Algorithmen für Independent Set folgt aus der ETH?
### Subbaustein: Formale Sprachen: CLIQUE = {(G,k) | G enthält Clique ≥ k}
**Muster:** Wie ist die formale Sprache CLIQUE über dem Alphabet Σ = {0, 1} kodiert?
### Subbaustein: Formale Sprachen: INDEPENDENT-SET = {(G,k) | G enthält IS ≥ k}
**Muster:** Welche Struktur hat die formale Sprache INDEPENDENT-SET?
### Subbaustein: Komplementgraph G̅: Kantenmenge E̅ = {(u,v) | u≠v und {u,v} ∉ E}
**Muster:** Wie unterscheiden sich die Kantenmengen von G und seinem Komplementgraphen G̅?
### Subbaustein: K-Clique in G entspricht K-Unabhängige-Menge in G̅
**Muster:** Bleibt die Größe k bei der Reduktion von k-Clique auf k-Independent-Set erhalten?
### Subbaustein: Beide Probleme sind in NP (Verifizierer existiert)
**Muster:** Welche Eigenschaft müssen Zertifikat und Verifizierer für ein Problem in NP erfüllen?
### Subbaustein: NP-vollständig via gegenseitige Reduktion über Komplementgraph
**Muster:** Wie folgt aus Korollar 6.18 die NP-Vollständigkeit von Independent Set?
---
## BAUSTEIN: Reduktion CLIQUE → CLIQUE-NOMEMBER
### Subbaustein: Füge isolierten Knoten v zu G hinzu: G' = G {v}
**Muster:** Wie wird bei der Reduktion von CLIQUE auf CLIQUE-NOMEMBER der neue Graph G' konstruiert?
### Subbaustein: G hat k-Clique ⟺ G' hat (k+1)-Clique mit v
**Muster:** Warum kann der hinzugefügte Knoten v in keiner gültigen k-Clique von G' enthalten sein?
### Subbaustein: Reduktion beweist CLIQUE-NOMEMBER ∈ NP-vollständig
**Muster:** Welche drei Bedingungen müssen erfüllt sein, damit CLIQUE-NOMEMBER als NP-vollständig gilt?
### Subbaustein: CLIQUE: Eingabe Graph G, Frage: existiert K-clique?
**Muster:** Was ist die Eingabe und was ist die Frage beim Entscheidungsproblem CLIQUE?
### Subbaustein: CLIQUE-NOMEMBER: existiert Knoten der nicht in jeder maximalen Clique liegt?
**Muster:** Wie ist das Problem CLIQUE-NOMEMBER gemäß Skript 6.50 formal definiert?
### Subbaustein: Reduktion: isolierten Knoten v zu G hinzufügen → G'
**Muster:** Welche Elemente werden bei der Reduktion von CLIQUE auf CLIQUE-NOMEMBER gegenüber der ursprünglichen Instanz verändert?
### Subbaustein: G' hat Clique der Größe k genau dann wenn G eine hat
**Muster:** Warum bleibt die Cliquengröße k bei der Reduktion unverändert?
### Subbaustein: v ist in G' in keiner k-Clique (isoliert)
**Muster:** Welche Eigenschaft hat der Knoten v in der konstruierten Instanz (G', v, k)?
### Subbaustein: G hat Clique der Größe k ⟺ G' hat Clique der Größe k ohne v
**Muster:** Wie hängt eine k-Clique in G mit einer k-Clique in G' zusammen?
### Subbaustein: Polynomielle Transformation
**Muster:** Warum ist die beschriebene Reduktion in polynomieller Zeit berechenbar?
---
## BAUSTEIN: Independent Set
### Subbaustein: Für alle u, v ∈ S gilt: {u, v} ∉ E
**Muster:** Welche Bedingung muss für je zwei Knoten eines Independent Set gelten?
### Subbaustein: Komplementär zur Clique
**Muster:** Wie hängt ein Independent Set in G mit einer Clique im Komplementgraphen G' zusammen?
### Subbaustein: NP-vollständiges Problem
**Muster:** Welche Komplexitätsklasse enthält Independent Set und wie wurde dies bewiesen?
### Subbaustein: Knotenmenge ohne Kanten zwischen je zwei Knoten der Menge
**Muster:** Was bedeutet es, dass die Knoten eines Independent Set paarweise nicht adjazent sind?
### Subbaustein: Komplementär zum Clique-Problem
**Muster:** In welchem Graphen entspricht ein Independent Set einer Clique?
### Subbaustein: Independent Set S⊆V: keine Kante zwischen je zwei Knoten in S
**Muster:** Wie unterscheidet sich ein Independent Set von einer beliebigen Teilmenge von V?
### Subbaustein: INDEPENDENT-SET = {(G,k) | G enthält unabhängige Menge der Größe ≥k}
**Muster:** Welche Sprache formalisiert das Entscheidungsproblem Independent Set?
---
## BAUSTEIN: Tiefensuche (DFS) für Zykluserkennung
### Subbaustein: Weiß: unbesuchter Knoten, Grau: aktuell in Bearbeitung, Schwarz: fertig
**Muster:** Welche Farbe hat ein Knoten während er von der DFS bearbeitet wird und welche nach Abschluss?
### Subbaustein: DFS-Zykluserkennung in O(V+E) bei adjacency List
**Muster:** Warum beträgt die Laufzeit der DFS-Zykluserkennung bei Adjazenzliste Θ(|V|+|E|)?
### Subbaustein: Schwarz: vollständig bearbeiteter Knoten
**Muster:** Wann wird ein Knoten in der DFS schwarz gefärbt?
### Subbaustein: Rückkante (grau → weiß): signalisiert Zyklus
**Muster:** Zu einem Knoten welcher Farbe muss eine Kante führen, um einen Zyklus anzuzeigen?
### Subbaustein: Tree Edge (weiß): Kante zu unbesuchtem Knoten
**Muster:** Welche Kante wird als Tree Edge bezeichnet?
### Subbaustein: Grau: aktuell in Bearbeitung (in DFS-Weite)
**Muster:** Was bedeutet es, wenn ein Knoten während der DFS grau gefärbt ist?
---
## BAUSTEIN: Turingmaschine für 0^n (Zweierpotenz)
### Subbaustein: Eingabe: n Nullen in unärer Codierung
**Muster:** In welcher Codierung wird die Eingabezahl n der TM für 0^n dargestellt?
### Subbaustein: Akzeptiert nur wenn n = 2^k für ein k ≥ 0
**Muster:** Nach welchem Kriterium entscheidet die TM, ob eine Eingabe akzeptiert wird?
### Subbaustein: Phase 1: Markiere jede zweite 0 mit x (alternierend)
**Muster:** Wie markiert die TM die Nullen im ersten Schritt?
---
## BAUSTEIN: 3-SAT zu 3-Färbung Reduktion
### Subbaustein: Knotenzahl linear in Variablen und Klauseln
**Muster:** Aus welchen Komponenten setzt sich die Knotenmenge V der konstruierten Instanz zusammen?
### Subbaustein: Dreieck erzwingt drei verschiedene Farben für die drei Knoten
**Muster:** Warum benötigen die drei Knoten xi, x̄i und vi eines jeden Dreiecks drei verschiedene Farben?
---
## BAUSTEIN: MC-Knapsack
### Subbaustein: Ziel: Maximierung des Gesamtwerts
**Muster:** Was ist die Zielfunktion beim Maximum-Cut Knapsack Problem?
---
**Gesamt: 48 Frage-Muster**

View File

@@ -0,0 +1,39 @@
Unten ist die vollständige Baustein-Liste eines Lern-Guides zum Thema "{topic}", durchnummeriert. Deine Aufgabe: Trenne **echte Bausteine** von **Fragmenten**, die in Wahrheit zu einem anderen Baustein der Liste gehören.
VOLLSTÄNDIGE LISTE (als Kontext — du brauchst sie, um Eltern zu finden):
{liste}
BEURTEILE die Nummern **{von} bis {bis}** — gehe sie **EINZELN** durch, einen Eintrag nach dem anderen. Die übrigen Einträge sind nur Kontext (mögliche Eltern).
## Vorgehen pro Eintrag (zwingend für JEDEN einzeln)
Für jeden Eintrag {von}{bis}:
1. Was ist das **Subjekt**? (Worüber wird geredet?)
2. Ist dieses Subjekt selbst ein anderer Eintrag der Liste — und sagt der Eintrag darüber nur eine EIGENSCHAFT, einen BEWEIS-TEIL, eine NOTATION oder ein LAUFZEIT-DETAIL aus?
- **Ja → Fragment**, Eltern = die Nummer dieses Subjekts.
- Nein, es steht für sich → Baustein (behalten).
Mit **⚠** markierte Zeilen sind Verdachtsfälle (Eigenschaft/Laufzeit/Notation) — prüfe sie besonders sorgfältig. Entscheide am Inhalt, nicht an der Markierung.
## Was ist ein BAUSTEIN (eigenständige Lerneinheit — behalten)?
Ein Baustein ist self-contained: Man kann ihn erklären, OHNE einen anderen Baustein als Subjekt vorauszusetzen.
- Ein **Problem**: „3-SAT", „Clique", „Knapsack", „Dominating Set".
- Ein **Verfahren/Algorithmus**: „LPT Scheduling", „Christofides", „FPTAS".
- Eine **Definition/ein Konzept**: „NP", „Reduktion", „Verifizierer", „KNF".
- Ein **benannter Satz MIT eigener Aussage**: „Cook-Levin: SAT ist NP-vollständig".
## Was ist ein FRAGMENT (gehört zu einem anderen Baustein → degradieren)?
Self-Containment-Test: Setzt der Eintrag ein ANDERES Konzept der Liste als Subjekt voraus? Dann ist er dessen Eigenschaft/Teil, kein eigener Baustein.
- **Eigenschaft/Status** eines Problems X (das selbst in der Liste steht): „X ist NP-vollständig", „X ∈ NP", „NP-Schwere von X", „Approximationsgüte von X". → Eltern = X.
- **Beweis-/Reduktions-Gadget**: „αEnde", „A-Komponente", „Dummy Items", „Schedule D*", „Knoten z", Hilfsvariablen. → Eltern = der Satz/die Reduktion, in deren Beweis es vorkommt.
- **Reine Notation/Symbol**: „|x|", „Σ∗", „Güte 2". → Eltern = die definierende Definition.
- **Laufzeit-/Größen-Detail**: „O(|V|⁴) Verifizierer-Laufzeit", „|V'| = |V| bei Reduktion", „Reduktion in O(|E|)". → Eltern = der Algorithmus/die Reduktion.
## Regeln
- Ein Fragment wird NUR degradiert, wenn sein **Eltern-Baustein in der Liste steht** (gib dessen Nummer an). Findest du keinen Eltern → behalte es (kein „Eltern").
- Zweifel betrifft die EIGENSTÄNDIGKEIT: Ist unklar, ob ein Eintrag für sich steht → behalten. Aber eine klare Eigenschaft/Notation/ein Beweis-Teil MIT Eltern in der Liste IST ein Fragment — nicht aus Vorsicht behalten.
- Eine eigenständige **Reduktion zwischen zwei Problemen** ist ein Baustein, KEIN Fragment („3-SAT ≤ Clique").
- Urteile am INHALT (nach dem „—"), nicht am Titel.
Schreibe NUR die JSON-Datei nach: {out_path}
Format (nur die Fragment-Nummern aus {von}{bis}, je mit Eltern-Nummer; `fragmente` darf leer sein):
{{"fragmente": {{"12": 5, "13": 5, "27": 19}}}}

View File

@@ -1,20 +1,26 @@
Zum Thema "{topic}" sind unten Bausteine, die in der Recherche nur EINMAL genannt wurden. Die Quelle wurde vollständig durchsucht. Entscheide für JEDEN Eintrag: ist er ein echter, eigenständiger Baustein → `aufnehmen`, oder nicht → verwerfen (in keine Liste). Zum Thema "{topic}" stehen unten Baustein-Kandidaten, jeder als "Titel — Beschreibung". **Beurteile am INHALT (nach dem —), nicht am Titel.** Entscheide für JEDEN Eintrag: echter, eigenständiger Baustein → `aufnehmen`, sonst verwerfen (in keine Liste).
EINZELN GENANNTE EINTRÄGE (jeweils entscheiden): KANDIDATEN (jeweils entscheiden):
{rest} {rest}
Was ein Baustein IST: eine eigenständige LERNEINHEIT — ein Konzept, ein Verfahren, ein Problem, eine Definition, ein benannter Satz MIT inhaltlicher Aussage. Was ein Baustein IST: eine eigenständige LERNEINHEIT — ein Konzept, ein Verfahren, ein Problem, eine Definition, ein benannter Satz MIT inhaltlicher Aussage.
Was KEIN Baustein ist (→ verwerfen): **Define-Test (entscheidend):** Beschreibt der INHALT ein eigenständiges Konzept, das man einem Lernenden erklären kann — OHNE Bezug auf einen konkreten Beweisschritt oder eine bestimmte Skript-Stelle? Ja → aufnehmen. Nein → verwerfen.
- Bloße Verweise/Nummern ohne Inhalt: "Satz 7.18", "Bemerkung 7.22", "Lemma 6.2", "D*".
Was KEIN Baustein ist (→ verwerfen, AUCH wenn eine Beschreibung dabei steht):
- Hilfskonstrukte, die nur INNERHALB eines Beweises/einer Reduktion existieren: "Knoten z", "Hilfsvariable", "Bedingung (**)" (ein Hilfsknoten / eine markierte Bedingung — kein eigenständiges Konzept).
- Beweis-Fragmente/Zwischenschritte: "Beweis (b) Güte", "Beweis ⊂ Richtung", "Eigenschaft (a)". - Beweis-Fragmente/Zwischenschritte: "Beweis (b) Güte", "Beweis ⊂ Richtung", "Eigenschaft (a)".
- Generische Platzhalter ohne eigenes Konzept: "Optimale Lösung", "Lösung" (nur im Kontext eines konkreten Problems sinnvoll).
- Reine Verweise OHNE inhaltliche Beschreibung: "Satz 7.18" (nichts Sprechendes nach dem —).
- Zu Vages oder erkennbar Erfundenes. - Zu Vages oder erkennbar Erfundenes.
**Verweis-Titel MIT echtem Inhalt → aufnehmen UND umbenennen:** Hat ein Eintrag einen nichtssagenden Titel ("Satz 7.18", "Korollar 6.18", "Lemma 6.2"), beschreibt die Beschreibung aber ein echtes Konzept → nimm ihn auf und gib im Feld `umbenennen` einen sprechenden Namen aus dem Inhalt. Im `aufnehmen`-Eintrag bleibt der **Original-Titel** stehen (für die Zuordnung); der neue Name steht NUR in `umbenennen`.
Beispiele: Beispiele:
- "Clique-Member — k-Clique, die einen festen Knoten v enthält" → **aufnehmen** (eigenständiges Problem). - "Clique-Member — k-Clique, die einen festen Knoten v enthält" → **aufnehmen** (eigenständiges Problem).
- "Satz 7.18" → **verwerfen** (bloßer Verweis, keine Aussage). - "Satz 7.18 — MGA hat Worst-Case-Güte 2" → **aufnehmen** + `umbenennen`: "Satz 7.18" → "MGA-Algorithmus (Güte 2)".
- "Beweis (b) Güte" → **verwerfen** (Fragment). - "Bedingung (**) — Ungleichungen pi ≤ pk im Scheduling-Beweis" → **verwerfen** (Hilfskonstrukt im Beweis).
- "Satz 7.8: ΔTSP1 hat Güte 2" → **aufnehmen** (benannter Satz MIT Aussage). - "Satz 7.18" (keine Beschreibung) → **verwerfen** (bloßer Verweis).
Regeln: Regeln:
- Im Zweifel an der Eigenständigkeit → eher aufnehmen. Dubletten werden später separat entfernt; hier zählt nur: echter Baustein oder Müll. - Im Zweifel an der Eigenständigkeit → eher aufnehmen. Dubletten werden später separat entfernt; hier zählt nur: echter Baustein oder Müll.
@@ -22,5 +28,5 @@ Regeln:
Schreibe NUR die JSON-Datei nach: {out_path} Schreibe NUR die JSON-Datei nach: {out_path}
Format (kein weiterer Text in der Datei): Format (kein weiterer Text in der Datei; `umbenennen` darf leer sein):
{{"aufnehmen": ["Titel — Kurzbeschreibung"], "rest": []}} {{"aufnehmen": ["Titel — Kurzbeschreibung"], "umbenennen": {{"alter Titel": "sprechender Name"}}, "rest": []}}

View File

@@ -6,6 +6,11 @@ Regeln:
- Ein Baustein löst GENAU EIN PROBLEM. Varianten derselben Lösung gehören in den einen Baustein, nicht als eigene Einträge (richtig: `<p>` ist ein Baustein, `<input>` mit allen Typen ist ein Baustein, "Ich-Botschaften" ist ein Baustein, "Present Perfect" mit allen Signalwörtern ist ein Baustein; falsch: 21 Einträge für jeden input-Typ oder je ein Eintrag pro unregelmäßigem Verb, aber auch Sammeleinträge, die mehrere Probleme mischen). - Ein Baustein löst GENAU EIN PROBLEM. Varianten derselben Lösung gehören in den einen Baustein, nicht als eigene Einträge (richtig: `<p>` ist ein Baustein, `<input>` mit allen Typen ist ein Baustein, "Ich-Botschaften" ist ein Baustein, "Present Perfect" mit allen Signalwörtern ist ein Baustein; falsch: 21 Einträge für jeden input-Typ oder je ein Eintrag pro unregelmäßigem Verb, aber auch Sammeleinträge, die mehrere Probleme mischen).
- Ein Baustein ist ATOMAR: genau eine Idee, vollständig in sich. Test: Man kann nichts entfernen, ohne ihn unvollständig zu machen — und es fehlt nichts, um ihn zu verstehen. - Ein Baustein ist ATOMAR: genau eine Idee, vollständig in sich. Test: Man kann nichts entfernen, ohne ihn unvollständig zu machen — und es fehlt nichts, um ihn zu verstehen.
- Granularität: ein Baustein ist eine LERNEINHEIT, kein Lexikon-Eintrag. Familien, die zusammen gelernt werden (z. B. die font-*-Eigenschaften, die Modalverben), sind EIN Baustein. - Granularität: ein Baustein ist eine LERNEINHEIT, kein Lexikon-Eintrag. Familien, die zusammen gelernt werden (z. B. die font-*-Eigenschaften, die Modalverben), sind EIN Baustein.
- Ebene (wichtig): ein Baustein ist self-contained — erklärbar, OHNE einen ANDEREN Baustein als Subjekt vorauszusetzen. Das sind Probleme, Verfahren/Algorithmen, Definitionen, benannte Sätze mit eigener Aussage. Ein Detail, das ein anderes Konzept voraussetzt, gehört IN dessen Baustein (als Teil), nicht als eigener Eintrag:
- Eine EIGENSCHAFT eines Konzepts gehört zum Konzept: „Clique" ist ein Baustein; „Clique ist NP-vollständig", „Clique ∈ NP", „Laufzeit von Clique" sind Teile davon — kein eigener Eintrag.
- Ein BEWEIS-/Reduktions-Bauteil gehört zum Satz/zur Reduktion: „A-Komponente", „Dummy-Item", „αEnde", „Hilfsvariable" — kein eigener Eintrag.
- Reine NOTATION/Symbole gehören zu ihrer Definition: „|x|", „Σ∗" — kein eigener Eintrag.
- Eine eigenständige REDUKTION zwischen zwei Problemen ist dagegen ein eigener Baustein („3-SAT ≤ Clique").
- KEINE Kategorien, KEINE Bewertung, KEINE Reihenfolge nach Wichtigkeit — nur eine flache, durchnummerierte Liste. - KEINE Kategorien, KEINE Bewertung, KEINE Reihenfolge nach Wichtigkeit — nur eine flache, durchnummerierte Liste.
- Es gibt KEINE Ziel-Anzahl. Höre erst auf, wenn die Recherche nichts Neues mehr hergibt. - Es gibt KEINE Ziel-Anzahl. Höre erst auf, wenn die Recherche nichts Neues mehr hergibt.
- Erfinde nichts: nimm nur Bausteine auf, die du in der Recherche belegt hast. Notiere pro Baustein die Quelle (URL bzw. Dateipfad). Gibt es keine Einzel-Quelle, reicht die Sammel-Quelle (Handbuch-Kapitel, Lehrbuch, Übersichtsseite, Verzeichnis). - Erfinde nichts: nimm nur Bausteine auf, die du in der Recherche belegt hast. Notiere pro Baustein die Quelle (URL bzw. Dateipfad). Gibt es keine Einzel-Quelle, reicht die Sammel-Quelle (Handbuch-Kapitel, Lehrbuch, Übersichtsseite, Verzeichnis).