This commit is contained in:
team3
2026-06-21 17:27:10 +02:00
parent 920046b774
commit 3965bc5bf9

View File

@@ -52,7 +52,7 @@ BAUSTEINE_STEPS = (
"Subbausteine finden", "Subbausteine wählen", "Subbausteine klären", "Subbausteine finden", "Subbausteine wählen", "Subbausteine klären",
"Stufen finden", "Stufen wählen", "Stufen klären", "Stufen finden", "Stufen wählen", "Stufen klären",
"Relevanz finden", "Relevanz wählen", "Relevanz klären", "Relevanz finden", "Relevanz wählen", "Relevanz klären",
"Fragen finden", "Fragen wählen", "Fragen klären", "Fragen finden", "Fragen wählen", "Fragen klären", "Fragen prüfen",
) )
@@ -148,7 +148,7 @@ def _bausteine_steps(topic: str) -> tuple:
"Subbausteine finden", "Subbausteine wählen", "Subbausteine klären", "Subbausteine finden", "Subbausteine wählen", "Subbausteine klären",
"Stufen finden", "Stufen wählen", "Stufen klären", "Stufen finden", "Stufen wählen", "Stufen klären",
"Relevanz finden", "Relevanz wählen", "Relevanz klären", "Relevanz finden", "Relevanz wählen", "Relevanz klären",
"Fragen finden", "Fragen wählen", "Fragen klären", "Fragen finden", "Fragen wählen", "Fragen klären", "Fragen prüfen",
) )
mitte = base + (("Ergänzung",) if q["type"] == "projekt" else ()) + rest mitte = base + (("Ergänzung",) if q["type"] == "projekt" else ()) + rest
return (("Quelle laden",) if q["type"] == "link" else ()) + mitte return (("Quelle laden",) if q["type"] == "link" else ()) + mitte
@@ -165,7 +165,7 @@ PHASEN = (
("Subbausteine", ("Subbausteine finden", "Subbausteine wählen", "Subbausteine klären")), ("Subbausteine", ("Subbausteine finden", "Subbausteine wählen", "Subbausteine klären")),
("Stufen", ("Stufen finden", "Stufen wählen", "Stufen klären")), ("Stufen", ("Stufen finden", "Stufen wählen", "Stufen klären")),
("Relevanz", ("Relevanz finden", "Relevanz wählen", "Relevanz klären")), ("Relevanz", ("Relevanz finden", "Relevanz wählen", "Relevanz klären")),
("Fragen", ("Fragen finden", "Fragen wählen", "Fragen klären")), ("Fragen", ("Fragen finden", "Fragen wählen", "Fragen klären", "Fragen prüfen")),
) )
@@ -859,6 +859,20 @@ def _norm_frage(t: str) -> str:
return " ".join(str(t or "").lower().split()) return " ".join(str(t or "").lower().split())
def _match_sub(agent_sub: str, rel: list[str]) -> str:
"""Subbaustein-Titel des Agenten auf den passenden relevanten Titel mappen — exakt,
dann normalisiert, dann Teilstring (der Agent lässt z. B. das Präfix „Frage: " weg).
Kein Treffer → Agent-Titel behalten. So geht KEIN Muster durch Titel-Abweichung verloren."""
if agent_sub in rel:
return agent_sub
an = _norm_titel(agent_sub)
for r in rel:
rn = _norm_titel(r)
if an and rn and (an == rn or an in rn or rn in an):
return r
return agent_sub
async def _frage_muster_block(ctx: GenContext, set_p, files: dict, sidecar: dict, instructions: str) -> dict | None: async def _frage_muster_block(ctx: GenContext, set_p, files: dict, sidecar: dict, instructions: str) -> dict | None:
"""Block E: drei Phasen — Finden (1 Generator je Baustein, parallel), Wählen (Code: """Block E: drei Phasen — Finden (1 Generator je Baustein, parallel), Wählen (Code:
relevante Subs + Dedup), Klären (Kritiker je Baustein bereinigt die Tabelle). relevante Subs + Dedup), Klären (Kritiker je Baustein bereinigt die Tabelle).
@@ -905,20 +919,21 @@ async def _frage_muster_block(ctx: GenContext, set_p, files: dict, sidecar: dict
if is_cancelled(): if is_cancelled():
return None return None
# Phase „Fragen wählen": Code — nur relevante Subbausteine, Dubletten je Baustein raus. # Phase „Fragen wählen": Code — Dubletten je Baustein raus, Subbaustein-Titel locker
set_p("Fragen wählen…", step=_step_idx(topic, "Fragen wählen")) # auf die relevanten mappen (nichts wegen Titel-Abweichung verwerfen).
roh_by_c = {} def _waehle(c, rel):
for c, (titel, rel) in enumerate(bausteine, 1):
eintraege = _frage_muster_schema(_json_datei(roh_path(c))) or [] eintraege = _frage_muster_schema(_json_datei(roh_path(c))) or []
rel_set = set(rel)
gesehen, sauber = set(), [] gesehen, sauber = set(), []
for e in eintraege: for e in eintraege:
norm = _norm_frage(e["frage"]) norm = _norm_frage(e["frage"])
if e["subbaustein"] not in rel_set or norm in gesehen: if norm in gesehen:
continue continue
gesehen.add(norm) gesehen.add(norm)
sauber.append(e) sauber.append({**e, "subbaustein": _match_sub(e["subbaustein"], rel)})
roh_by_c[c] = sauber return sauber
set_p("Fragen wählen…", step=_step_idx(topic, "Fragen wählen"))
roh_by_c = {c: _waehle(c, rel) for c, (titel, rel) in enumerate(bausteine, 1)}
# Phase „Fragen klären": Kritiker je Baustein bereinigt die Tabelle (eindeutig, distinkt). # Phase „Fragen klären": Kritiker je Baustein bereinigt die Tabelle (eindeutig, distinkt).
async def _klaere(c, titel): async def _klaere(c, titel):
@@ -943,14 +958,34 @@ async def _frage_muster_block(ctx: GenContext, set_p, files: dict, sidecar: dict
if is_cancelled(): if is_cancelled():
return None return None
# Final: geklärte Tabelle je Baustein, Fallback auf Roh-Muster. # Geklärte Tabelle je Baustein, Fallback auf Roh-Muster. Subbaustein-Titel locker mappen.
ergebnis = {} def _finalisiere(c, rel):
for c, (titel, rel) in enumerate(bausteine, 1): final = _frage_muster_schema(_json_datei(final_path(c))) or roh_by_c[c]
rel_set = set(rel) return [{**e, "subbaustein": _match_sub(e["subbaustein"], rel)} for e in final]
final = _frage_muster_schema(_json_datei(final_path(c)))
if not final: ergebnis = {titel: _finalisiere(c, rel) for c, (titel, rel) in enumerate(bausteine, 1)}
final = roh_by_c[c]
ergebnis[titel] = [e for e in final if e["subbaustein"] in rel_set] # Phase „Fragen prüfen": Bausteine mit relevanten Subs aber 0 Mustern eine Runde nachholen.
set_p(f"Fragen prüfen ({len(bausteine)} Bausteine)…", step=_step_idx(topic, "Fragen prüfen"))
leer = [(c, t, r) for c, (t, r) in enumerate(bausteine, 1) if not ergebnis.get(t)]
if leer:
_log(topic, f"Frage-Muster: {len(leer)} Baustein(e) ohne Muster — Nachrunde")
for c, t, r in leer:
roh_path(c).unlink(missing_ok=True)
final_path(c).unlink(missing_ok=True)
await asyncio.gather(*[_finde(c, t, r) for c, t, r in leer], return_exceptions=True)
if is_cancelled():
return None
for c, t, r in leer:
roh_by_c[c] = _waehle(c, r)
await asyncio.gather(*[_klaere(c, t) for c, t, r in leer], return_exceptions=True)
if is_cancelled():
return None
for c, t, r in leer:
ergebnis[t] = _finalisiere(c, r)
rest = [t for c, t, r in leer if not ergebnis.get(t)]
if rest:
_log(topic, f"Frage-Muster: {len(rest)} Baustein(e) bleiben leer (Fallback Live): {rest[:5]}")
return ergebnis return ergebnis