From 3965bc5bf9d01997c7321fc6581d6f711e8da697 Mon Sep 17 00:00:00 2001 From: team3 Date: Sun, 21 Jun 2026 17:27:10 +0200 Subject: [PATCH] update --- backend/bausteine.py | 73 ++++++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/backend/bausteine.py b/backend/bausteine.py index f299056..fedaed2 100644 --- a/backend/bausteine.py +++ b/backend/bausteine.py @@ -52,7 +52,7 @@ BAUSTEINE_STEPS = ( "Subbausteine finden", "Subbausteine wählen", "Subbausteine klären", "Stufen finden", "Stufen wählen", "Stufen 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", "Stufen finden", "Stufen wählen", "Stufen 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 return (("Quelle laden",) if q["type"] == "link" else ()) + mitte @@ -165,7 +165,7 @@ PHASEN = ( ("Subbausteine", ("Subbausteine finden", "Subbausteine wählen", "Subbausteine klären")), ("Stufen", ("Stufen finden", "Stufen wählen", "Stufen 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()) +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: """Block E: drei Phasen — Finden (1 Generator je Baustein, parallel), Wählen (Code: 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(): return None - # Phase „Fragen wählen": Code — nur relevante Subbausteine, Dubletten je Baustein raus. - set_p("Fragen wählen…", step=_step_idx(topic, "Fragen wählen")) - roh_by_c = {} - for c, (titel, rel) in enumerate(bausteine, 1): + # Phase „Fragen wählen": Code — Dubletten je Baustein raus, Subbaustein-Titel locker + # auf die relevanten mappen (nichts wegen Titel-Abweichung verwerfen). + def _waehle(c, rel): eintraege = _frage_muster_schema(_json_datei(roh_path(c))) or [] - rel_set = set(rel) gesehen, sauber = set(), [] for e in eintraege: norm = _norm_frage(e["frage"]) - if e["subbaustein"] not in rel_set or norm in gesehen: + if norm in gesehen: continue gesehen.add(norm) - sauber.append(e) - roh_by_c[c] = sauber + sauber.append({**e, "subbaustein": _match_sub(e["subbaustein"], rel)}) + 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). 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(): return None - # Final: geklärte Tabelle je Baustein, Fallback auf Roh-Muster. - ergebnis = {} - for c, (titel, rel) in enumerate(bausteine, 1): - rel_set = set(rel) - final = _frage_muster_schema(_json_datei(final_path(c))) - if not final: - final = roh_by_c[c] - ergebnis[titel] = [e for e in final if e["subbaustein"] in rel_set] + # Geklärte Tabelle je Baustein, Fallback auf Roh-Muster. Subbaustein-Titel locker mappen. + def _finalisiere(c, rel): + final = _frage_muster_schema(_json_datei(final_path(c))) or roh_by_c[c] + return [{**e, "subbaustein": _match_sub(e["subbaustein"], rel)} for e in final] + + ergebnis = {titel: _finalisiere(c, rel) for c, (titel, rel) in enumerate(bausteine, 1)} + + # 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