update
This commit is contained in:
@@ -20,7 +20,7 @@ from pathlib import Path
|
||||
|
||||
import database as db
|
||||
from agents import kill_process, cancel_scope, clear_scope
|
||||
from config import KONSENS_GRACE, RECHERCHE_GRACE, KONSENS_MAX_RUNDEN, DEFAULT_PROVIDER
|
||||
from config import KONSENS_GRACE, RECHERCHE_GRACE, KONSENS_MAX_RUNDEN, DEFAULT_PROVIDER, CRAWL_KEEP_PATTERNS, CRAWL_NOISE_PATTERNS, CRAWL_MIN_CHARS
|
||||
from fsutil import atomic_write_text, atomic_write_json
|
||||
from jsonio import read_json_file as _json_datei
|
||||
from lernen import FRAGETYPEN
|
||||
@@ -46,10 +46,11 @@ RECHERCHE_BATCH = 20 # Crawl-Seiten je Batch
|
||||
RECHERCHE_READERS = 2 # Reader-Agenten je Batch (Konsens ≥2 innerhalb des Batches)
|
||||
RECHERCHE_THEMA_AGENTEN = 5 # Web-Modus (Quelle „thema", kein Crawl-Ordner)
|
||||
RECHERCHE_KAPPE = 1800 # Sicherheits-Deckel je Batch-Agent
|
||||
SICHTUNG_CHUNK = 80 # Crawl-Seiten je Sichtungs-Chunk (Content/Noise-Triage)
|
||||
SICHTUNG_READERS = 3 # Sichter je Chunk; Merge per Mehrheit, Tie-Break = Content
|
||||
# Sichtung (Content/Noise) ist jetzt ein deterministischer Regel-Filter (config.CRAWL_*).
|
||||
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
|
||||
# Frage-Muster-Chunks per LPT nach Sub-Last balancieren (Makespan), statt nach Baustein-Anzahl.
|
||||
FRAGE_CHUNK_SUBS = 50 # Ziel-Summe relevanter Subs je Chunk
|
||||
|
||||
log = logging.getLogger("creator.bausteine")
|
||||
|
||||
@@ -324,16 +325,17 @@ def active_bausteine() -> list[dict]:
|
||||
|
||||
|
||||
def reset_bausteine(topic: str) -> None:
|
||||
"""„Löschen": räumt nur die generierten Bausteine weg (Dateien ab Inventar). BEHÄLT
|
||||
Quelle (quelle.json), Crawl-Ordner und Sichtung. Voller Wipe inkl. Quelle/Crawl nur
|
||||
über „Thema löschen" (DELETE /topics → rmtree(topic_dir))."""
|
||||
_reset_ab_phase(topic, "Inventar")
|
||||
"""„Entfernen": löscht den GESAMTEN Bausteine-Bereich — Crawl, Sichtung, Inventar … Fragen.
|
||||
BEHÄLT nur die Themen-Config `quelle.json` (Typ/Link/Spec). Re-Generieren crawlt neu.
|
||||
(Crawl/Sichtung gehören zu den Bausteinen; nur die Config ist „Thema".)"""
|
||||
files = _bausteine_files(topic)
|
||||
files["final"].unlink(missing_ok=True)
|
||||
files["sidecar"].unlink(missing_ok=True)
|
||||
files["frage_muster"].unlink(missing_ok=True)
|
||||
shutil.rmtree(quelle_crawl_dir(topic), ignore_errors=True) # Crawl gehört zu Bausteinen
|
||||
shutil.rmtree(files["arbeit"], ignore_errors=True)
|
||||
_bausteine_errors.pop(topic, None)
|
||||
|
||||
|
||||
async def reset_bausteine_db(topic: str) -> None:
|
||||
"""DB-Pendant zu reset_bausteine: Inventar…Fragen aus der DB; Coverage/Sichtung + Quelle bleiben."""
|
||||
await _reset_db_ab_phase(topic, "Inventar")
|
||||
# quelle.json bleibt bewusst stehen — das ist die Themen-Config.
|
||||
|
||||
|
||||
def _phase_idx(label: str) -> int:
|
||||
@@ -550,6 +552,21 @@ def _n_chunks(count: int, size: int = SUBBAUSTEIN_CHUNK) -> int:
|
||||
return min(SUBBAUSTEIN_MAX, max(1, math.ceil(count / size)))
|
||||
|
||||
|
||||
def _lpt_chunks(gewichte: list[int], target: int) -> list[list[int]]:
|
||||
"""Indizes lastbalanciert auf Chunks verteilen (LPT, Makespan-minimal). Gewicht = Kosten je Index.
|
||||
K = ceil(Gesamtgewicht/target); schwerste zuerst in den jeweils leichtesten Bin. → Index-Listen."""
|
||||
if not gewichte:
|
||||
return []
|
||||
K = max(1, math.ceil(sum(gewichte) / max(1, target)))
|
||||
bins: list[list[int]] = [[] for _ in range(K)]
|
||||
last = [0] * K
|
||||
for i in sorted(range(len(gewichte)), key=lambda x: gewichte[x], reverse=True):
|
||||
j = min(range(K), key=lambda b: last[b])
|
||||
bins[j].append(i)
|
||||
last[j] += gewichte[i]
|
||||
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] = {}
|
||||
@@ -753,7 +770,18 @@ async def _stufen_block(ctx: GenContext, set_p, files: dict, roh: dict, instruct
|
||||
items = [(titel, sub) for titel, subs in roh.items() for sub in subs] # globale id = index+1
|
||||
if not items:
|
||||
return {titel: [] for titel in roh}
|
||||
chunks = _chunk_nums(list(range(len(items))), _n_chunks(len(items), STUFE_CHUNK))
|
||||
# Chunks aus GANZEN Bausteinen packen (keinen Baustein splitten) → Rater sieht je Baustein
|
||||
# alle Subs und kann relativ einstufen. Item-Indizes je Baustein in roh-Reihenfolge.
|
||||
chunks, cur, i = [], [], 0
|
||||
for _titel_b, subs in roh.items():
|
||||
g = list(range(i, i + len(subs)))
|
||||
i += len(subs)
|
||||
if cur and len(cur) + len(g) > STUFE_CHUNK:
|
||||
chunks.append(cur)
|
||||
cur = []
|
||||
cur.extend(g)
|
||||
if cur:
|
||||
chunks.append(cur)
|
||||
n = len(chunks)
|
||||
|
||||
def rater_paths(c):
|
||||
@@ -769,7 +797,14 @@ async def _stufen_block(ctx: GenContext, set_p, files: dict, roh: dict, instruct
|
||||
vorhanden = sum(1 for p in paths if _stufen_schema(_json_datei(p), local_set))
|
||||
if vorhanden >= 2:
|
||||
return True
|
||||
enum = "\n".join(f"{k}. [{items[j][0]}] {items[j][1]}" for k, j in enumerate(item_idxs, 1))
|
||||
enum_zeilen, cur_b = [], None
|
||||
for k, j in enumerate(item_idxs, 1):
|
||||
b, sub = items[j]
|
||||
if b != cur_b:
|
||||
enum_zeilen.append(f"\nBAUSTEIN: {b}")
|
||||
cur_b = b
|
||||
enum_zeilen.append(f"{k}. {sub}")
|
||||
enum = "\n".join(enum_zeilen).strip()
|
||||
offen = [(i, p) for i, p in enumerate(paths, 1) if not _stufen_schema(_json_datei(p), local_set)]
|
||||
slots = [{
|
||||
"key": f"bausteine-{topic}-stufe-c{c}-{i}",
|
||||
@@ -1002,7 +1037,7 @@ async def _frage_muster_block(ctx: GenContext, set_p, files: dict, sidecar: dict
|
||||
if not bausteine:
|
||||
return {}
|
||||
typen_block = "\n".join(f"- {k}: {v}" for k, v in FRAGETYPEN.items())
|
||||
chunks = _chunk_nums(list(range(len(bausteine))), _n_chunks(len(bausteine))) # ~10 Bausteine/Chunk
|
||||
chunks = _lpt_chunks([len(rel) for _, rel in bausteine], FRAGE_CHUNK_SUBS) # lastbalanciert nach Sub-Zahl
|
||||
|
||||
def roh_path(ci):
|
||||
return arbeit / f"frage-muster-c{ci}.json"
|
||||
@@ -1223,65 +1258,25 @@ async def _set_inventar(topic: str, eintrag: str, status: str) -> None:
|
||||
await db.set_baustein_status(topic, norm, status)
|
||||
|
||||
|
||||
def _sichtung_schema(data) -> list[str] | None:
|
||||
"""{"noise": ["datei.txt", …]} → Liste Dateinamen (leer erlaubt) · sonst None."""
|
||||
if not isinstance(data, dict) or not isinstance(data.get("noise"), list):
|
||||
return None
|
||||
return [s for x in data["noise"] if (s := str(x).strip())]
|
||||
|
||||
|
||||
async def _sichte_inhalt(ctx: GenContext, set_p, files: dict, ordner, pages: list[str]) -> set[str]:
|
||||
"""Content/Noise-Triage nach dem Crawl. Je Chunk SICHTUNG_READERS Sichter; Merge je Seite
|
||||
per Mehrheit, Tie-Break = Content (konservativ). Fehler/Ausfall → Content (fail-open).
|
||||
→ Menge der Content-Dateinamen (nie leer, solange pages nicht leer)."""
|
||||
topic, provider, is_cancelled = ctx.topic, ctx.provider, ctx.is_cancelled
|
||||
pages = list(pages)
|
||||
if not pages:
|
||||
return set()
|
||||
arbeit = files["arbeit"]
|
||||
def _sichte_regeln(ordner, pages: list[str]) -> tuple[list[str], list[str]]:
|
||||
"""Deterministischer Content/Noise-Filter (config.CRAWL_*). Substring-Match (klein) gegen
|
||||
URL + Dateiname. Reihenfolge: keep > noise > min_chars > behalten. → (content, noise)."""
|
||||
ordner = Path(ordner)
|
||||
|
||||
def _snippet(fn: str) -> str:
|
||||
content, noise = [], []
|
||||
for fn in pages:
|
||||
zeilen = _read(ordner / fn).splitlines()
|
||||
url = zeilen[0][len("QUELLE:"):].strip() if zeilen and zeilen[0].startswith("QUELLE:") else ""
|
||||
body = " ".join(z.strip() for z in zeilen[1:] if z.strip())[:200]
|
||||
return f"- {fn} · {url} · {body}"
|
||||
|
||||
chunks = _chunk_nums(sorted(pages), max(1, math.ceil(len(pages) / SICHTUNG_CHUNK)))
|
||||
|
||||
async def _chunk(ci: int, chunk: list[str]) -> set[str]:
|
||||
seiten = "\n".join(_snippet(fn) for fn in chunk)
|
||||
paths = [arbeit / f"sichtung-c{ci}-{i}.json" for i in range(1, SICHTUNG_READERS + 1)]
|
||||
for p in paths:
|
||||
p.unlink(missing_ok=True)
|
||||
slots = [{
|
||||
"key": f"bausteine-{topic}-sichtung-c{ci}-{i}",
|
||||
"prompt": _prompt("Bausteine-Seiten-Sichtung", topic=topic, seiten=seiten, out_path=p),
|
||||
"role": "judge", "capabilities": "files",
|
||||
"payload": (lambda result, p=p: _sichtung_schema(_json_datei(p))),
|
||||
} for i, p in enumerate(paths, 1)]
|
||||
ergebnisse = await _race(topic, f"Seiten-Sichtung {ci}", slots, 1,
|
||||
_timeout("recherche_mapping", len(chunk)), provider,
|
||||
cancelled=is_cancelled, grace=KONSENS_GRACE)
|
||||
cs = set(chunk)
|
||||
stimmen = [set(r) & cs for r in (ergebnisse or [])] # Noise-Menge je Reader (nur Chunk-Seiten)
|
||||
R = len(stimmen)
|
||||
if not R:
|
||||
return set() # fail-open: kein Urteil → alles Content
|
||||
# Seite = Noise nur bei strenger Mehrheit; Gleichstand/Minderheit → Content.
|
||||
return {page for page in chunk if 2 * sum(page in s for s in stimmen) > R}
|
||||
|
||||
async def melde(d, t):
|
||||
set_p(f"Sichte Seiten {d}/{t}…")
|
||||
|
||||
set_p("Sichte Seiten…", step=_step_idx(topic, "Quelle aufbereiten"))
|
||||
teile = await _gather_fortschritt([_chunk(ci, c) for ci, c in enumerate(chunks, 1)], len(chunks), melde)
|
||||
if is_cancelled():
|
||||
return set(pages)
|
||||
noise = set().union(*(t for t in teile if isinstance(t, set))) if teile else set()
|
||||
content = set(pages) - noise
|
||||
_log(topic, f"Sichtung: {len(content)} Content / {len(noise)} Noise von {len(pages)} ({len(chunks)} Chunks × {SICHTUNG_READERS} Sichter)")
|
||||
return content or set(pages) # alles Noise? → fail-open, alle behalten
|
||||
body = "\n".join(zeilen[1:]).strip()
|
||||
hay = f"{url}\n{fn}".lower()
|
||||
if any(p in hay for p in CRAWL_KEEP_PATTERNS):
|
||||
content.append(fn)
|
||||
elif any(p in hay for p in CRAWL_NOISE_PATTERNS):
|
||||
noise.append(fn)
|
||||
elif len(body) < CRAWL_MIN_CHARS:
|
||||
noise.append(fn)
|
||||
else:
|
||||
content.append(fn) # Default: behalten — alles mit Inhalt bleibt
|
||||
return content, noise
|
||||
|
||||
|
||||
async def _quelle_aufbereiten(ctx: GenContext, set_p, files: dict, q: dict, ordner) -> bool:
|
||||
@@ -1307,12 +1302,11 @@ async def _quelle_aufbereiten(ctx: GenContext, set_p, files: dict, q: dict, ordn
|
||||
await asyncio.to_thread(_pdfs_konvertieren, ordner)
|
||||
pages = sorted(set(_crawl_index(ordner).values()))
|
||||
if pages:
|
||||
set_p("Sichte Seiten…", step=_step_idx(topic, "Quelle aufbereiten"))
|
||||
await db.delete_coverage(topic)
|
||||
content = await _sichte_inhalt(ctx, set_p, files, ordner, pages)
|
||||
if is_cancelled():
|
||||
return False
|
||||
noise = [p for p in pages if p not in content]
|
||||
await db.mark_inhalt(topic, sorted(content), noise)
|
||||
content, noise = _sichte_regeln(ordner, pages) # deterministischer Regel-Filter
|
||||
await db.mark_inhalt(topic, sorted(content), sorted(noise))
|
||||
_log(topic, f"Sichtung: {len(content)} Content / {len(noise)} Noise von {len(pages)} (Regeln)")
|
||||
await db.set_step_status(topic, "Quelle aufbereiten", "fertig")
|
||||
return True
|
||||
|
||||
|
||||
@@ -42,6 +42,16 @@ RECHERCHE_GRACE = 900
|
||||
# Prüf-Loops lassen Rest-Beanstandungen danach stehen.
|
||||
KONSENS_MAX_RUNDEN = 3
|
||||
|
||||
# Crawler-Sichtung (Content/Noise) — deterministischer Regel-Filter statt LLM.
|
||||
# Match: Substring (klein) gegen URL UND Dateiname. Reihenfolge: keep > noise > min_chars > behalten.
|
||||
# Sonderregeln einfach hier ergänzen.
|
||||
CRAWL_KEEP_PATTERNS = ["learn-unit", "learn-course"] # immer Content
|
||||
CRAWL_NOISE_PATTERNS = [ # eindeutig themenfremd → raus
|
||||
"clubs", "events", "podcasts", "resources", "-u-",
|
||||
"academy", "pricing", "/plans", "career", "newsletter", "impressum", "login",
|
||||
]
|
||||
CRAWL_MIN_CHARS = 400 # zu wenig Text → raus
|
||||
|
||||
# Timeouts pro Agenten-Schritt: (Basis-Sekunden, Sekunden pro Baustein/Section).
|
||||
# Gilt für alle Provider gleich — wer zu langsam ist, wird neu gestartet bzw. überholt.
|
||||
TIMEOUTS = {
|
||||
|
||||
@@ -864,8 +864,9 @@ async def delete_guide_content(topic: str, format: str | None = None) -> None:
|
||||
|
||||
|
||||
async def delete_topic_pipeline(topic: str) -> None:
|
||||
"""Alle Pipeline-Inhalte eines Themas verwerfen (Inventar/Subs/Muster/Coverage/State/Quelle)."""
|
||||
"""Bausteine-Bereich eines Themas verwerfen (Inventar/Subs/Muster/Coverage/State).
|
||||
NICHT die Themen-Config `quelle` — die wird separat verwaltet (delete_quelle)."""
|
||||
db = await get_db()
|
||||
for tab in ("bausteine", "subbausteine", "frage_muster", "recherche_coverage", "pipeline_state", "quelle"):
|
||||
for tab in ("bausteine", "subbausteine", "frage_muster", "recherche_coverage", "pipeline_state"):
|
||||
await db.execute(f"DELETE FROM {tab} WHERE topic = ?", (topic,))
|
||||
await db.commit()
|
||||
|
||||
@@ -15,9 +15,9 @@ from database import (
|
||||
create_element, list_elements, get_element, update_element, delete_element,
|
||||
list_baustein_progress, get_baustein_progress, set_offene_frage,
|
||||
set_baustein_score, set_baustein_absolviert, set_baustein_verstanden, set_baustein_gemeistert, delete_baustein_daten,
|
||||
delete_topic_pipeline, get_guide_content, delete_guide_content,
|
||||
delete_topic_pipeline, delete_quelle, get_guide_content, delete_guide_content,
|
||||
)
|
||||
from bausteine import generate_bausteine, cancel_bausteine, bausteine_status, active_bausteine, reset_bausteine, reset_bausteine_db, lade_quelle, lade_uebersicht, subbausteine_titel, lade_frage_muster
|
||||
from bausteine import generate_bausteine, cancel_bausteine, bausteine_status, active_bausteine, reset_bausteine, lade_quelle, lade_uebersicht, subbausteine_titel, lade_frage_muster
|
||||
from elements import generate_element, chat_with_guide, chat_with_element, check_element, style_element, refine_suggestion
|
||||
from lernen import NOETIG, MASTERY, MEISTERN, NIVEAUS, baustein_chat, baustein_diskussion, baustein_element_anlegen, pruefung_bewertung, pruefung_bewertung_schnell, pruefung_frage, pruefung_frage_variante, quiz_generieren, lueckwahl_generieren, lueckentext_generieren, lueckentext_pruefen, score_berechnen, floor_aus_meilensteinen, _zufall_malus
|
||||
from guide import generate_guide, guide_slot_dateien, guide_fertig_step
|
||||
@@ -88,6 +88,7 @@ async def remove_topic(topic: str):
|
||||
await delete_topic(topic)
|
||||
await delete_baustein_daten(topic)
|
||||
await delete_topic_pipeline(topic)
|
||||
await delete_quelle(topic) # Themen-Config (DB) — beim Thema-Löschen mit weg
|
||||
await delete_guide_content(topic)
|
||||
shutil.rmtree(topic_dir(topic), ignore_errors=True)
|
||||
return {"ok": True}
|
||||
@@ -172,8 +173,8 @@ async def cancel_bausteine_route(topic: str):
|
||||
|
||||
@router.delete("/bausteine")
|
||||
async def remove_bausteine(topic: str):
|
||||
reset_bausteine(topic) # Dateien ab Inventar — Quelle/Crawl bleiben
|
||||
await reset_bausteine_db(topic) # DB ab Inventar — Coverage/Sichtung + Quelle bleiben
|
||||
reset_bausteine(topic) # Dateien: Crawl + Sichtung + Inventar…Fragen weg; quelle.json bleibt
|
||||
await delete_topic_pipeline(topic) # DB: Bausteine-Bereich weg; Themen-Config (quelle) bleibt
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
Du sortierst gecrawlte Seiten zum Lern-Thema "{topic}". Markiere NUR die Seiten, die eindeutig KEINEN fachlichen Lerninhalt tragen.
|
||||
|
||||
SEITEN (Dateiname · URL · Auszug):
|
||||
{seiten}
|
||||
|
||||
Regeln:
|
||||
- `noise` = eindeutig kein FACHLICHER Lerninhalt zum Thema. Markiere insbesondere:
|
||||
- Veranstaltungs-/Termin-, Mitglieder-/Profil-, Vereins-/Community-, Podcast-/Video-Verzeichnisse, Login/Impressum/Karriere.
|
||||
- **Marketing/Vertrieb:** Positionierung, ICP/Zielgruppe, Editionen/Preise/Pläne, Plattform-/Produkt-Werbung ohne How-to.
|
||||
- **Meta/Navigation:** Übersichts-/Index-/Landing-Seiten ohne eigenen Stoff, Lernpfad-/Kurs-Intros, Voraussetzungs-/„Was du lernst"-Seiten.
|
||||
- **Fremde Tracks:** Seiten eines ANDEREN Lern-Pfads/Tracks oder Levels als dieses Thema (z.B. Frontend statt Backend, „Intermediate"/„Advanced" wenn das Thema „Essentials" ist).
|
||||
- Im ZWEIFEL bei ECHTER Technik NICHT markieren: konkrete Konzepte, Anleitungen, Code, Konfiguration, Verfahren bleiben Content — auch Deployment/PaaS/Betrieb.
|
||||
- Nutze NUR die exakten Dateinamen aus der Liste oben.
|
||||
|
||||
Schreibe NUR die JSON-Datei nach {out_path} (kein weiterer Text):
|
||||
{{"noise": ["exakter-dateiname.txt", "…"]}}
|
||||
|
||||
Ist nichts eindeutig themenfremd, schreibe {{"noise": []}}.
|
||||
@@ -3,13 +3,13 @@ Mehrere Agenten haben die Subbausteine des Themas "{topic}" nach Schwierigkeit e
|
||||
STRITTIGE SUBBAUSTEINE (mit den abgegebenen Stimmen):
|
||||
{strittig}
|
||||
|
||||
Stufen:
|
||||
- **einfach**: Grundlegend, Anfänger versteht es sofort, Kernfunktion.
|
||||
- **mittel**: Braucht etwas Erklärung, gängige Variante oder wichtiges Detail.
|
||||
- **schwer**: Sonderfall, Tücke, fortgeschritten, leicht falsch zu machen.
|
||||
Stufe = Lern-/Verständnisaufwand (nicht Seltenheit/Obskurität):
|
||||
- **einfach**: Basis-Fakt/Definition, sofort verständlich. Auch Versions-Details, Pfade, Konstanten, einzelne Fakten.
|
||||
- **mittel**: braucht Erklärung — mehrere Teile, Zusammenhang, typische Anwendung.
|
||||
- **schwer**: komplexes Zusammenspiel mehrerer Konzepte, tieferes Verständnis nötig. NICHT „schwer" nur weil selten/neu/Edge-Case.
|
||||
|
||||
Regeln:
|
||||
- Wäge die Stimmen ab, entscheide fachlich. Im Zweifel die niedrigere Stufe.
|
||||
- Wäge die Stimmen ab, entscheide fachlich nach Lernaufwand. Im Zweifel die niedrigere Stufe.
|
||||
- Für JEDE strittige Nummer genau eine Stufe.
|
||||
|
||||
Schreibe NUR die JSON-Datei nach: {out_path}
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
Stufe jeden Subbaustein des Themas "{topic}" nach Schwierigkeit ein: einfach, mittel oder schwer. Die Stufe steuert, wann ein Lerner den Punkt sieht (Anfänger sehen nur „einfach", Fortgeschrittene alles).
|
||||
Stufe jeden Subbaustein des Themas "{topic}" nach **Lern-/Verständnisaufwand** ein: einfach, mittel oder schwer. Die Stufe steuert, wann ein Lerner den Punkt sieht (Anfänger sehen nur „einfach", Fortgeschrittene alles).
|
||||
|
||||
SUBBAUSTEINE (Nummer in Klammern der zugehörige Baustein):
|
||||
SUBBAUSTEINE (nach Baustein gruppiert):
|
||||
{subbausteine}
|
||||
|
||||
Stufen:
|
||||
- **einfach**: Grundlegend. Ein Anfänger versteht es sofort. Die Kernfunktion, ohne die der Baustein nicht nutzbar ist.
|
||||
- **mittel**: Braucht etwas Erklärung. Eine gängige Variante, ein wichtiges Detail, ein zweiter Schritt.
|
||||
- **schwer**: Sonderfall, Tücke, fortgeschritten. Leicht falsch zu machen, selten gebraucht, oder setzt mehr Verständnis voraus.
|
||||
Was die Stufe misst — wie viel Aufwand, den Punkt zu VERSTEHEN und ANZUWENDEN:
|
||||
- **einfach**: Basis-Fakt oder Definition. Sofort verständlich. Der Einstieg in den Baustein.
|
||||
- **mittel**: braucht etwas Erklärung. Mehrere Teile, ein Zusammenhang, eine typische Anwendung.
|
||||
- **schwer**: komplexes Zusammenspiel mehrerer Konzepte. Setzt tieferes Verständnis voraus, ist gedanklich anspruchsvoll.
|
||||
|
||||
WICHTIG — was NICHT die Stufe bestimmt:
|
||||
- Seltenheit ist KEINE Schwierigkeit. Ein Nischen-Feature kann trotzdem einfach sein.
|
||||
- Versions-Details (z.B. „seit 6.4.12.0"), Konstanten, Pfade, einzelne Fakten → meist **einfach**, nicht schwer.
|
||||
- Edge-Cases/Gotchas, die nur eine simple Regel sind (z.B. „Name muss dem Ordner entsprechen") → **einfach/mittel**, nicht schwer.
|
||||
- „schwer" NUR, wenn der Punkt selbst gedanklich schwer ist — nicht weil er obskur oder neu ist.
|
||||
|
||||
Regeln:
|
||||
- Stufe jeden Punkt einzeln, gemessen an SEINEM Baustein. Im Zweifel die niedrigere Stufe.
|
||||
- Jeder Baustein sollte mindestens einen „einfach"-Punkt haben (den Einstieg).
|
||||
- Stufe RELATIV im jeweiligen Baustein: vergleiche die Punkte eines Bausteins miteinander.
|
||||
- Die meisten Punkte sind einfach/mittel. „schwer" nur für die echten Brocken.
|
||||
- Jeder Baustein hat mindestens einen „einfach"-Punkt (den Einstieg). Im Zweifel die niedrigere Stufe.
|
||||
- Urteile nur — erfinde nichts dazu, ändere keine Subbausteine.
|
||||
|
||||
Schreibe NUR die JSON-Datei nach: {out_path}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
Mehrere Agenten haben unabhängig die Subbausteine je Baustein des Themas "{topic}" ermittelt. Pro Baustein ist der KONSENS (von mindestens zwei Agenten genannt) und der strittige REST (nur einmal genannt) gelistet. Entscheide je Baustein die finale Subbaustein-Liste.
|
||||
Unten je Baustein des Themas "{topic}" die gefundenen Subbausteine. Gib je Baustein die **bereinigte** finale Liste zurück.
|
||||
|
||||
{bausteine}
|
||||
|
||||
Regeln:
|
||||
- Übernimm alle Konsens-Subbausteine.
|
||||
- Einen strittigen Rest-Punkt AUFNEHMEN, wenn er ein eigener, korrekter Teilpunkt des Bausteins ist, den der Konsens nicht abdeckt.
|
||||
- VERWERFEN, wenn er eine Dublette/Variante eines vorhandenen Punkts ist, zu fein granular, am Rand des Themas oder fachlich zweifelhaft.
|
||||
- Die Anzahl folgt der Schwierigkeit: trivialer Baustein wenige Punkte, komplexer viele. Blähe nichts auf, lass nichts Wesentliches weg.
|
||||
- Subbausteine wörtlich übernehmen, nicht umformulieren. Jeder Punkt atomar (eine Aussage).
|
||||
- **Dubletten zusammenführen:** Subbausteine, die denselben Punkt mit anderen Worten sagen, sind EINER. Behalte den klarsten, streiche die Umformulierungen. (z.B. „Broadcast Mode verfügbar" und „Broadcast-Modus aktivieren" → einer.)
|
||||
- Behalte alle fachlich **DISTINKTEN** Teilpunkte vollständig — nichts Wesentliches weglassen.
|
||||
- Verwirf, was zu fein granular, am Rand des Themas oder fachlich zweifelhaft ist.
|
||||
- Jeder Punkt atomar (eine Aussage). Behaltene Punkte WÖRTLICH übernehmen, nicht umformulieren, nichts erfinden.
|
||||
- Die Anzahl folgt der Schwierigkeit: lieber wenige distinkte als viele redundante Punkte.
|
||||
|
||||
Schreibe NUR die Datei {out_path} — pro Baustein ein baustein-Marker (Titel EXAKT wie oben), darunter die finale Subbaustein-Liste:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user