"""Deterministische Fake-Welt (CREATOR_FAKE=1): ersetzt LLM, Suche und Laden. Die Fakes LESEN den Prompt — Zitate kommen nur, wenn sie wörtlich im gelieferten Text stehen (Zitat-Gate wird mitgetestet). Präparierte Eigenschaften: - Dublette über 2 Quellen (HTTP ↔ Hypertext Transfer Protocol, Akronym-Kanal) - Soll-Punkt „Fehlerbehandlung" ohne Atom (erst die Web-Nachrecherche deckt ihn) - Kapitel-übergreifende Satz-Dopplung, die der Fix per Verweis ersetzt Fehlender Handler → harter Fehler (nie still raten).""" import re from . import skills FUELL = ("Dieser Absatz liefert zusätzlichen Zusammenhang für Lernende. " * 4).strip() S1 = "Ein Protokoll regelt den Austausch von Nachrichten zwischen zwei Partnern im Netz." S2 = "HTTP steuert die Übertragung von Webseiten zwischen Browser und Server." S3 = "Das Hypertext Transfer Protocol überträgt Inhalte im World Wide Web." S4 = "Kommunikation braucht klare Regeln für beide Seiten." S5 = ("Beim Wiederholungsmuster wird eine fehlgeschlagene Anfrage nach kurzer " "Wartezeit erneut gesendet.") S6A = "Ohne saubere Fehlerbehandlung scheitern Systeme im Alltag." S7A = "Ein Vorteil ist bessere Konzentration bei der Arbeit." S7B = "Ein weiterer Vorteil ist weniger Stress im Alltag." S6B = "Fehlerbehandlung entscheidet über die Zuverlässigkeit eines Systems." DUP = ("Fehler lassen sich nie ganz vermeiden, entscheidend ist der richtige " "Umgang mit ihnen.") P1 = "Grundlagen der Protokolle und Regeln erklären" P2 = "Das HTTP-Protokoll verstehen" P3 = "Fehlerbehandlung beherrschen" QUELLEN = { "https://beispiel.de/grundlagen": f"Einführung in das Thema.\n\n{FUELL}\n\n{S1}\n\n{FUELL}\n\n{S2}\n\n" f"{FUELL}\n\n{S6A}\n\n{FUELL}", "https://beispiel.de/vertiefung": f"Vertiefende Betrachtung.\n\n{FUELL}\n\n{S4}\n\n{FUELL}\n\n{S3}\n\n" f"{FUELL}\n\n{S6B}\n\n{S7A} {S7B}\n\n{FUELL}", "https://beispiel.de/fehler": f"Umgang mit Fehlern.\n\n{FUELL}\n\n{S5}\n\n{FUELL}\n\n{FUELL}", } SOLL = [(P1, [S1, S4]), (P2, [S2, S3]), (P3, [S6A, S6B])] ATOME = [ # (titel, typ, definition, beleg-satz) ("Protokoll", "begriff", "Ein Protokoll ist die Vereinbarung, nach der zwei Partner Nachrichten austauschen.", S1), ("HTTP", "begriff", "HTTP ist das Protokoll für die Übertragung von Webseiten.", S2), ("Hypertext Transfer Protocol", "begriff", "Das Hypertext Transfer Protocol dient der Übertragung von Inhalten im Web.", S3), ("Kommunikationsregeln", "aussage", "Erfolgreiche Kommunikation setzt klare, beidseitig bekannte Regeln voraus.", S4), ("Vorteil Konzentration", "aussage", "Die Methode verbessert die Konzentration bei der Arbeit.", S7A), ("Vorteil Stressabbau", "aussage", "Die Methode senkt den empfundenen Stress im Alltag.", S7B), ("Wiederholungsmuster", "verfahren", "Beim Wiederholungsmuster wird eine fehlgeschlagene Anfrage nach Wartezeit wiederholt.", S5), ] _ATOM_SOLL = {"Protokoll": P1, "Kommunikationsregeln": P1, "HTTP": P2, "Hypertext Transfer Protocol": P2, "Wiederholungsmuster": P3, "Vorteil Konzentration": P1, "Vorteil Stressabbau": P1} _DUBLETTEN = {frozenset({"HTTP", "Hypertext Transfer Protocol"})} _ENTHALTEN = {frozenset({"Basisregeln", "Basisregeln und Sonderfaelle"})} def suche(query: str) -> list[dict]: q = query.casefold() if "fehlerbehandlung" in q: return [{"titel": "Fehlerbehandlung", "url": "https://beispiel.de/fehler", "backend": "fake"}] if "r2" in q or " v2" in q: return [] # Runde 2: nichts Neues → Sättigung if "ueberblick" in q: return [{"titel": "Grundlagen", "url": "https://beispiel.de/grundlagen", "backend": "fake"}] if "tutorial" in q: return [{"titel": "Vertiefung", "url": "https://beispiel.de/vertiefung", "backend": "fake"}] return [] def laden(url: str) -> str: return QUELLEN.get(url, "") def _abschnitt(prompt: str, marke: str) -> str: m = re.search(rf"{marke}:\n(.*?)(?=\n\n[A-ZÄÖÜ\-]{{3,}}[^a-z]*:\n|\nAusgabeformat|\Z)", prompt, re.DOTALL) return m.group(1) if m else prompt def _ids_liste(text: str) -> list[tuple[int, str]]: return [(int(m.group(1)), m.group(2).strip()) for m in re.finditer(r"^\[(\d+)\]\s*(.+)$", text, re.MULTILINE)] # ── Handler je Aufgabe ── def _nummerierte_abschnitte(p: str) -> dict[int, str]: """`=== ABSCHNITT n ===`-Blöcke aus dem Bündel-Prompt lesen.""" return {int(m.group(1)): m.group(2) for m in re.finditer( r"=== ABSCHNITT (\d+) ===\n(.*?)(?=\n\n=== ABSCHNITT |\n\nRegeln:|\Z)", p, re.DOTALL)} def _recherche_plan(p: str) -> str: runde = re.search(r"\(Runde (\d+)\)", p) r = runde.group(1) if runde else "1" m = re.search(r"BLICKWINKEL:\n(.*?)\n\nBISHERIGE", p, re.DOTALL) lenses = re.findall(r"^- (\w+)$", m.group(1) if m else "", re.MULTILINE) return "\n".join(f"===QUERY===\nLENS: {l}\nTEXT: thema {l} r{r} nr{i}" for l in (lenses or ["x"]) for i in (1, 2)) def _soll_extraktion(p: str) -> str: out = [] for i, text in _nummerierte_abschnitte(p).items(): n = 0 for punkt, belege in SOLL: for beleg in belege: if beleg in text: out.append(f"===PUNKT===\nABSCHNITT: {i}\nTEXT: {punkt}\n" f"BELEG: {beleg}") n += 1 if not n: out.append(f"===LEER===\nABSCHNITT: {i}") return "\n".join(out) def _soll_konsens(p: str) -> str: gruppen: dict[str, list[int]] = {} for i, text in _ids_liste(_abschnitt(p, "KANDIDATEN")): gruppen.setdefault(text, []).append(i) return "\n".join(f"===GRUPPE===\nIDS: {','.join(map(str, ids))}\nTEXT: {text}" for text, ids in gruppen.items()) def _atom_extraktion(p: str) -> str: out = [] for i, text in _nummerierte_abschnitte(p).items(): n = 0 for titel, typ, definition, satz in ATOME: if satz in text: out.append(f"===ATOM===\nABSCHNITT: {i}\nTITEL: {titel}\n" f"TYP: {typ}\nDEFINITION: {definition}\nZITAT: {satz}") n += 1 if not n: out.append(f"===LEER===\nABSCHNITT: {i}") return "\n".join(out) def _anker_fix(p: str) -> str: return "\n".join(f"===FIX===\nID: {i}\nZITAT: KEINER" for i, _ in _ids_liste(_abschnitt(p, "ATOME"))) def _dedup_urteil(p: str) -> str: out = [] for m in re.finditer(r"PAAR (\d+):\nA: \[\d+\] ([^:]+):.*?\nB: \[\d+\] ([^:]+):", p, re.DOTALL): nr, ta, tb = m.group(1), m.group(2).strip(), m.group(3).strip() paar = frozenset({ta, tb}) urteil = ("dublette" if paar in _DUBLETTEN else "enthalten" if paar in _ENTHALTEN else "verschieden") out.append(f"===URTEIL===\nPAAR: {nr}\nURTEIL: {urteil}\nGRUND: fake") return "\n".join(out) or "===URTEIL===\nPAAR: 1\nURTEIL: verschieden\nGRUND: leer" def _soll_zuordnung(p: str) -> str: soll_map = {text: i for i, text in _ids_liste(_abschnitt(p, "SOLL-PUNKTE"))} out = [] for atom_id, zeile in _ids_liste(_abschnitt(p, "ATOME")): titel = zeile.split(":")[0].strip() punkt = _ATOM_SOLL.get(titel, "") ziel = soll_map.get(punkt) out.append(f"===ZUORDNUNG===\nATOM: {atom_id}\n" f"SOLL: {ziel if ziel is not None else 'KEINER'}") return "\n".join(out) def _luecke_nachextraktion(p: str) -> str: out = [] for m in re.finditer(r"=== LUECKE ===\nSOLL: (\d+)\nPUNKT: .*?\nFENSTER:\n" r"(.*?)(?=\n\n=== LUECKE ===|\n\nRegeln:|\Z)", p, re.DOTALL): sid, fenster = m.group(1), m.group(2) if S5 in fenster: t, typ, d, s = next(a for a in ATOME if a[3] == S5) out.append(f"===ATOM===\nSOLL: {sid}\nTITEL: {t}\nTYP: {typ}\n" f"DEFINITION: {d}\nZITAT: {s}") else: out.append(f"===LEER===\nSOLL: {sid}") return "\n".join(out) def _verdichtung(p: str) -> str: vorteile, andere = [], [] for i, zeile in _ids_liste(_abschnitt(p, "ATOME")): (vorteile if "Vorteil" in zeile else andere).append(str(i)) out = [] if vorteile: out.append(f"===EINHEIT===\nIDS: {','.join(vorteile)}") out += [f"===EINHEIT===\nIDS: {i}" for i in andere] return "\n".join(out) def _lernziele(p: str) -> str: out = [] for m in re.finditer(r"=== SOLL (\d+) ===\nPUNKT: (.*?)\nATOME:\n" r"(.*?)(?=\n\n=== SOLL |\n\nRegeln:|\Z)", p, re.DOTALL): i, punkt = m.group(1), m.group(2) ids = [str(x) for x, _ in _ids_liste(m.group(3))] out.append(f"===ZIEL===\nSOLL: {i}\nTITEL: {punkt.split()[0][:30]}\n" f"TEXT: Der Lernende kann {punkt} anwenden.\n" f"ATOME: {','.join(ids)}") return "\n".join(out) def _bausteine(p: str) -> str: ids = [str(i) for i, _ in _ids_liste(_abschnitt(p, "ATOME"))] return f"===BAUSTEIN===\nTITEL: Baustein\nATOME: {','.join(ids)}" def _themen_schnitt(p: str) -> str: fehler, rest = [], [] for i, text in _ids_liste(_abschnitt(p, "SOLL-PUNKTE")): (fehler if "Fehlerbehandlung" in text else rest).append(str(i)) out = [] if rest: out.append(f"===KAPITEL===\nTITEL: Grundlagen\nPUNKTE: {','.join(rest)}") if fehler: out.append(f"===KAPITEL===\nTITEL: Fehlerbehandlung\nPUNKTE: {','.join(fehler)}") return "\n".join(out) def _ordnung(p: str) -> str: prior = re.search(r"^PRIOR: ([\d,]+)$", p, re.MULTILINE) return f"===ORDNUNG===\nIDS: {prior.group(1) if prior else ''}" def _writer(p: str) -> str: atome = re.findall(r"\n\*\*(.+?)\*\* — (.+)", p) teile = [] for aid, titel, definition in atome: # Erster Satz bewusst KEIN Definitions-Echo (semantischer det-Check) teile.append(f"\n\nSchauen wir uns das Konzept " f"nun genauer an. {definition} " f"{FUELL} Damit ist {titel} eingeordnet.") teile.append(DUP) # gepflanzte Dopplung — Redundanz-Check muss sie finden return "===TEXT===\n" + "\n\n".join(teile) def _kapitel_intro(p: str) -> str: kap = re.search(r"^KAPITEL: (.+)$", p, re.MULTILINE) return (f"===INTRO===\nDieses Kapitel behandelt {kap.group(1) if kap else 'X'}. " "Es baut auf dem bisher Gelernten auf.") def _guide_pruefung(p: str) -> str: ids = re.findall(r"^=== SECTION (\d+) ===$", p, re.MULTILINE) return "\n".join(f"===LEER===\nSECTION: {i}" for i in dict.fromkeys(ids)) or "===LEER===\nSECTION: 0" def _guide_fix(p: str) -> str: section = _abschnitt(p, "SECTION") zeilen = [z for z in section.splitlines() if "Fehler lassen sich nie ganz vermeiden" not in z] zeilen.append("Wie bereits bei den Grundlagen beschrieben, zählt der " "Umgang mit Fehlern.") return "===TEXT===\n" + "\n".join(zeilen).strip() def _redundanz_urteil(p: str) -> str: out = [] for m in re.finditer(r"PAAR (\d+):\nA: (.*)\nB: (.*)", p): redundant = "Fehler lassen sich nie ganz vermeiden" in m.group(2) out.append(f"===URTEIL===\nPAAR: {m.group(1)}\n" f"URTEIL: {'redundant' if redundant else 'didaktisch_gewollt'}") return "\n".join(out) or "===URTEIL===\nPAAR: 1\nURTEIL: didaktisch_gewollt" _HANDLER = { "recherche-plan": _recherche_plan, "soll-extraktion": _soll_extraktion, "soll-konsens": _soll_konsens, "quellen-urteil": lambda p: ("===URTEIL===\nNUETZLICH: nein\nGRUND: Werbung" if "Werbeliste" in p else "===URTEIL===\nNUETZLICH: ja\nGRUND: lehrt"), "lektorat": lambda p: "===LEER===\nOK: ja", "atom-extraktion": _atom_extraktion, "anker-fix": _anker_fix, "dedup-urteil": _dedup_urteil, "soll-zuordnung": _soll_zuordnung, "luecke-nachextraktion": _luecke_nachextraktion, "luecke-freispruch": lambda p: "===URTEIL===\nURTEIL: unerfuellbar\nGRUND: fake", "verdichtung": _verdichtung, "lernziele": _lernziele, "bausteine": _bausteine, "themen-schnitt": _themen_schnitt, "ordnung": _ordnung, "writer": _writer, "kapitel-intro": _kapitel_intro, "guide-pruefung": _guide_pruefung, "guide-fix": _guide_fix, "redundanz-urteil": _redundanz_urteil, "klaerung": lambda p: "===URTEIL===\nURTEIL: kein_mangel\nGRUND: fake", } def antwort(prompt: str) -> tuple[int, str, str]: """→ (rc, text, err). Fehlender Handler = harter Fehler, nie still raten.""" name = skills.aufgaben_name(prompt) handler = _HANDLER.get(name) if handler is None: return 1, "", f"kein Fake-Handler für Aufgabe '{name}'" return 0, handler(prompt), ""