This commit is contained in:
team3
2026-06-24 07:52:05 +02:00
parent 12125d0f2d
commit 3b1e84e17d
13 changed files with 46 additions and 224 deletions

View File

@@ -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: