update
This commit is contained in:
@@ -27,12 +27,12 @@ from lernen import FRAGETYPEN
|
||||
from paths import arbeit_dir, bausteine_path, frage_muster_path, project_dir, subbausteine_path, quelle_path, quelle_crawl_dir, safe_ordner
|
||||
from crawl import crawl
|
||||
from pipeline import (
|
||||
CANCELLED, FAILED, GenContext, _extra, _gather_fortschritt, _log, _prompt, _race, _relevanz_schema, _rest_schema,
|
||||
CANCELLED, FAILED, GenContext, _extra, _gather_fortschritt, _log, _prompt, _race, _relevanz_schema,
|
||||
_runde_schema, _semaphore, _str_liste, _stufen_schema, _timeout, run_single_slot,
|
||||
)
|
||||
from textkit import (
|
||||
_eindeutige_titel, _lade_bausteine, _norm_titel, _parse_auswahl, _parse_subbausteine, _titel,
|
||||
_titel_aufloesen, _titel_index, _vormerge,
|
||||
_titel_aufloesen, _titel_index,
|
||||
)
|
||||
|
||||
# Subbausteine (Websuche je Baustein) chunken: 1 Agent je ~10 Bausteine, gedeckelt.
|
||||
@@ -490,31 +490,13 @@ def _relevanz_komplett(data) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def _frage_muster_schema(data) -> list[dict] | None:
|
||||
"""{"muster": [{subbaustein, typ, frage}, …]} → Liste valider Einträge · sonst None.
|
||||
|
||||
typ muss ein bekannter Fragetyp sein; subbaustein + frage nicht leer. Leere Liste → None.
|
||||
"""
|
||||
if not isinstance(data, dict) or not isinstance(data.get("muster"), list):
|
||||
return None
|
||||
out = []
|
||||
for e in data["muster"]:
|
||||
if not isinstance(e, dict):
|
||||
return None
|
||||
sub = str(e.get("subbaustein", "")).strip()
|
||||
typ = str(e.get("typ", "")).strip().casefold()
|
||||
frage = str(e.get("frage", "")).strip()
|
||||
if not sub or typ not in FRAGETYPEN or not frage:
|
||||
return None
|
||||
out.append({"subbaustein": sub, "typ": typ, "frage": frage})
|
||||
return out or None
|
||||
|
||||
|
||||
def _frage_muster_chunk_schema(data) -> list[dict] | None:
|
||||
"""{"muster": [{baustein, subbaustein, typ, frage}, …]} → Liste valider Einträge · sonst None.
|
||||
|
||||
Wie _frage_muster_schema, aber mit `baustein` (Zuordnung im 10er-Chunk). Ungültige
|
||||
Einzel-Einträge werden übersprungen (nicht die ganze Liste verworfen)."""
|
||||
Mit `baustein` (Zuordnung im 10er-Chunk). Ungültige Einzel-Einträge werden
|
||||
übersprungen (nicht die ganze Liste verworfen)."""
|
||||
if not isinstance(data, dict) or not isinstance(data.get("muster"), list):
|
||||
return None
|
||||
out = []
|
||||
@@ -567,54 +549,10 @@ def _lpt_chunks(gewichte: list[int], target: int) -> list[list[int]]:
|
||||
return [b for b in bins if b]
|
||||
|
||||
|
||||
def _merge_finder(num: int, idx: dict, finder: list[dict]) -> tuple[list[str], list[str]]:
|
||||
"""Subbausteine eines Bausteins über die Finder mergen → (konsens ≥2, rest ==1)."""
|
||||
counts: dict[str, int] = {}
|
||||
repr_text: dict[str, str] = {}
|
||||
order: list[str] = []
|
||||
for d in finder:
|
||||
# die Marker-Liste dieses Finders für genau diesen Baustein
|
||||
subs = next((s for marker, s in d.items() if _titel_aufloesen(idx, marker) == num), [])
|
||||
gesehen: set[str] = set()
|
||||
for sub in subs:
|
||||
key = _norm_titel(sub)
|
||||
if not key or key in gesehen:
|
||||
continue
|
||||
gesehen.add(key)
|
||||
if key not in counts:
|
||||
counts[key], repr_text[key] = 0, sub
|
||||
order.append(key)
|
||||
counts[key] += 1
|
||||
konsens = [repr_text[k] for k in order if counts[k] >= 2]
|
||||
rest = [repr_text[k] for k in order if counts[k] == 1]
|
||||
return konsens, rest
|
||||
|
||||
|
||||
def _final_text(chunk: list[int], entries: dict, daten: dict) -> str:
|
||||
"""Marker-Datei aus reinem Konsens (wenn kein Judge nötig)."""
|
||||
teile = []
|
||||
for num in chunk:
|
||||
konsens = daten[num][0]
|
||||
if konsens:
|
||||
teile.append(f"<!-- baustein: {_titel(entries[num])} -->\n" + "\n".join(f"- {s}" for s in konsens))
|
||||
return "\n".join(teile) + "\n"
|
||||
|
||||
|
||||
def _judge_block(chunk: list[int], entries: dict, daten: dict) -> str:
|
||||
"""Eingabe für den Subbaustein-Judge: pro Baustein Konsens + Strittiges."""
|
||||
lines = []
|
||||
for num in chunk:
|
||||
konsens, rest = daten[num]
|
||||
lines.append(f"BAUSTEIN: {_titel(entries[num])}")
|
||||
lines.append("Konsens:")
|
||||
lines.extend(f"- {s}" for s in konsens)
|
||||
if not konsens:
|
||||
lines.append("- (noch keiner)")
|
||||
if rest:
|
||||
lines.append("Strittig:")
|
||||
lines.extend(f"- {s}" for s in rest)
|
||||
lines.append("")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
async def _subbausteine_block(ctx: GenContext, set_p, files: dict, entries: dict, instructions: str) -> dict | None:
|
||||
@@ -1191,14 +1129,6 @@ async def _frage_muster_block(ctx: GenContext, set_p, files: dict, sidecar: dict
|
||||
|
||||
# ── Inventar in der DB: Recherche-Loop · Konsolidierung · Klärung ────────────
|
||||
|
||||
def _cited_sources(text: str) -> set[str]:
|
||||
"""Zitierte Quellen je Eintrag = 3. ' — '-Segment (URL bzw. Dateiname), kleingeschrieben."""
|
||||
out = set()
|
||||
for eintrag in _parse_auswahl(text).values():
|
||||
teile = [t.strip() for t in eintrag.split(" — ")]
|
||||
if len(teile) >= 3 and teile[-1]:
|
||||
out.add(teile[-1].lower())
|
||||
return out
|
||||
|
||||
|
||||
def _crawl_index(ordner) -> dict[str, str]:
|
||||
@@ -1221,29 +1151,8 @@ def _crawl_index(ordner) -> dict[str, str]:
|
||||
return idx
|
||||
|
||||
|
||||
def _abgedeckt(zitiert: set[str], crawl_idx: dict[str, str]) -> set[str]:
|
||||
"""Zitierte Quellen → Menge kanonischer Crawl-Seiten-Keys (was nicht passt, fällt weg)."""
|
||||
out = set()
|
||||
for z in zitiert:
|
||||
key = crawl_idx.get(z) or crawl_idx.get(z.rstrip("/"))
|
||||
if key:
|
||||
out.add(key)
|
||||
return out
|
||||
|
||||
|
||||
def _fokus_text(bereits: list[str], offen: list[str]) -> str:
|
||||
"""Re-Prompt-Block: noch nicht abgedeckte Crawl-Seiten + bereits gefundene Titel."""
|
||||
teile = []
|
||||
if offen:
|
||||
liste = "\n".join(f"- {n}" for n in offen[:150])
|
||||
teile.append(
|
||||
"NOCH NICHT ABGEDECKTE QUELLEN-DATEIEN — lies ZUERST genau diese im Quell-Ordner "
|
||||
f"und ergänze daraus die noch fehlenden Bausteine:\n{liste}"
|
||||
)
|
||||
if bereits:
|
||||
liste = "\n".join(f"- {t}" for t in bereits)
|
||||
teile.append("BEREITS GEFUNDEN (NICHT wiederholen — liefere nur NEUE Bausteine):\n" + liste)
|
||||
return ("\n\n" + "\n\n".join(teile)) if teile else ""
|
||||
|
||||
|
||||
async def _set_inventar(topic: str, eintrag: str, status: str) -> None:
|
||||
|
||||
@@ -492,17 +492,6 @@ async def set_baustein_score(topic: str, baustein: str, score: int) -> int:
|
||||
return score
|
||||
|
||||
|
||||
async def set_baustein_streak(topic: str, baustein: str, streak: int) -> None:
|
||||
"""Persistiert die Streak-Folge (Bonus) absolut — überlebt Reload/Session-Wechsel."""
|
||||
db = await get_db()
|
||||
await db.execute(
|
||||
"""INSERT INTO baustein_progress (topic, baustein, streak, updated_at)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(topic, baustein) DO UPDATE SET
|
||||
streak = excluded.streak, updated_at = excluded.updated_at""",
|
||||
(topic, baustein, streak, _now()),
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def set_baustein_verstanden(topic: str, baustein: str) -> bool:
|
||||
@@ -793,11 +782,6 @@ async def get_step_status(topic: str, schritt: str) -> str:
|
||||
return row[0] if row else "offen"
|
||||
|
||||
|
||||
async def list_pipeline_state(topic: str) -> dict[str, str]:
|
||||
db = await get_db()
|
||||
cursor = await db.execute("SELECT schritt, status FROM pipeline_state WHERE topic = ?", (topic,))
|
||||
rows = await cursor.fetchall()
|
||||
return {s: st for s, st in rows}
|
||||
|
||||
|
||||
async def delete_pipeline_state(topic: str, schritte: list[str] | None = None) -> None:
|
||||
@@ -810,24 +794,8 @@ async def delete_pipeline_state(topic: str, schritte: list[str] | None = None) -
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def set_quelle(topic: str, type: str, ort: str = "", spec: str = "") -> None:
|
||||
db = await get_db()
|
||||
await db.execute(
|
||||
"""INSERT INTO quelle (topic, type, ort, spec, updated_at) VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(topic) DO UPDATE SET
|
||||
type = excluded.type, ort = excluded.ort, spec = excluded.spec, updated_at = excluded.updated_at""",
|
||||
(topic, type, ort, spec, _now()),
|
||||
)
|
||||
await db.commit()
|
||||
|
||||
|
||||
async def get_quelle(topic: str) -> dict | None:
|
||||
db = await get_db()
|
||||
cursor = await db.execute("SELECT type, ort, spec FROM quelle WHERE topic = ?", (topic,))
|
||||
row = await cursor.fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
return {"type": row[0], "ort": row[1], "spec": row[2]}
|
||||
|
||||
|
||||
async def delete_quelle(topic: str) -> None:
|
||||
|
||||
@@ -79,7 +79,7 @@ async def _load_subbausteine(topic: str) -> dict[str, list[dict]]:
|
||||
|
||||
|
||||
def _zuteilung_subs(chunk: list[dict], entries: dict[int, str], subs_by_titel: dict[str, list[dict]]) -> str:
|
||||
"""Wie _zuteilung_text, aber listet pro Baustein seine Subbausteine mit Stufe."""
|
||||
"""Listet je Kapitel die Bausteine, darunter ihre Subbausteine mit Stufe."""
|
||||
lines: list[str] = []
|
||||
for ch in chunk:
|
||||
lines.append(f"KAPITEL: {ch['title']}")
|
||||
|
||||
@@ -116,11 +116,6 @@ def _str_liste(val) -> list[str] | None:
|
||||
return None if any(not x for x in out) else out
|
||||
|
||||
|
||||
def _rest_schema(data):
|
||||
"""{"uebernehmen": [str]} → Liste (leer erlaubt) · sonst None."""
|
||||
if not isinstance(data, dict):
|
||||
return None
|
||||
return _str_liste(data.get("uebernehmen"))
|
||||
|
||||
|
||||
def _runde_schema(data, final: bool = False):
|
||||
|
||||
@@ -54,11 +54,6 @@ def _content_json(topic: str, fmt: str) -> dict | None:
|
||||
return None
|
||||
|
||||
|
||||
def _kapitel_titel(topic: str, fmt: str) -> set[str] | None:
|
||||
content = _content_json(topic, fmt)
|
||||
if content is None:
|
||||
return None
|
||||
return {c.get("title") for c in content.get("chapters", [])}
|
||||
|
||||
|
||||
def _section_titel(topic: str, fmt: str) -> set[str] | None:
|
||||
|
||||
@@ -42,28 +42,6 @@ def _eindeutige_titel(entries: dict[int, str]) -> dict[int, str]:
|
||||
return out
|
||||
|
||||
|
||||
def _vormerge(listen: list[dict[int, str]]) -> list[tuple[str, int]]:
|
||||
"""Vereinigt Baustein-Listen: exakte Titel-Dubletten (per _norm_titel) zusammenführen.
|
||||
|
||||
→ [("Titel — Beschreibung", nennungen)] in Erstnennungs-Reihenfolge.
|
||||
nennungen = Anzahl der Listen, die den Titel nennen (Dublette innerhalb
|
||||
einer Liste zählt nicht doppelt). Repräsentant ist die Erstnennung; ein
|
||||
drittes " — "-Segment (Quelle) wird verworfen.
|
||||
"""
|
||||
merged: dict[str, tuple[str, int]] = {}
|
||||
for liste in listen:
|
||||
gesehen: set[str] = set()
|
||||
for text in liste.values():
|
||||
key = _norm_titel(_titel(text))
|
||||
if key in gesehen:
|
||||
continue
|
||||
gesehen.add(key)
|
||||
if key in merged:
|
||||
repr_text, n = merged[key]
|
||||
merged[key] = (repr_text, n + 1)
|
||||
else:
|
||||
merged[key] = (" — ".join(text.split(" — ")[:2]), 1)
|
||||
return list(merged.values())
|
||||
|
||||
|
||||
def _titel_index(entries: dict[int, str]) -> dict[str, int]:
|
||||
@@ -231,9 +209,3 @@ def _split_chunks(chapters: list[dict], n: int) -> list[list[dict]]:
|
||||
return chunks
|
||||
|
||||
|
||||
def _zuteilung_text(chunk: list[dict], entries: dict[int, str]) -> str:
|
||||
lines = []
|
||||
for ch in chunk:
|
||||
lines.append(f"KAPITEL: {ch['title']}")
|
||||
lines.extend(f"- {entries[num]}" for num in ch["nums"])
|
||||
return "\n".join(lines)
|
||||
|
||||
Reference in New Issue
Block a user