"""Run registered v3 specialist agents and surface their structured AgentOutput per topic for the standardized result tabs. Additive to the legacy B-wiring HTML (`_b18_wiring`): this does NOT replace it — it puts a clean, typed `AgentOutput` into `state["agent_outputs"][topic]`, which `_phase_f_persist` forwards into the API result so the frontend can render a per-topic tab. Phase 1 ships only impressum; the topic map extends to cookie / vendor / … as those agents get wired (same contract, no code change here beyond the map). Once the tabs are the source of truth, B18's v1 path retires. """ from __future__ import annotations import logging from compliance.services.specialist_agents import REGISTRY, AgentInput logger = logging.getLogger(__name__) # topic key (matches state["doc_texts"]) -> registered agent_id _TOPIC_AGENTS: dict[str, str] = { "impressum": "impressum", } _MIN_TEXT = 100 def _derive_scope(profile_dict: dict) -> list[str]: """Business-scope aus dem erkannten Profil — identisch zu B18, damit der Tab denselben Scope sieht wie die bestehende Auswertung. Das Rechtsform-Gate kommt in einer späteren Phase (eigene Klassifizierung).""" scope: set[str] = set() if profile_dict.get("has_online_shop"): scope.add("ecommerce") if profile_dict.get("is_regulated_profession"): scope.add("regulated_profession") if profile_dict.get("industry") in ("insurance", "Finance", "finance"): scope.add("insurance") return sorted(scope) async def run_agent_outputs(state: dict) -> None: """Für jedes Topic mit registriertem v3-Agent + ausreichend Text: Agent laufen lassen und den strukturierten AgentOutput ablegen.""" doc_texts = state.get("doc_texts") or {} profile_dict = state.get("profile_dict") or {} req = state.get("req") company_name = ( (getattr(req, "company_name", None) or "") or (state.get("extracted_profile") or {}).get("company_name", "") or state.get("site_name", "") ) origin_domain = ( getattr(req, "origin_domain", None) or "" ) or state.get("domain", "") scope = _derive_scope(profile_dict) outputs: dict[str, dict] = state.get("agent_outputs") or {} for topic, agent_id in _TOPIC_AGENTS.items(): text = (doc_texts.get(topic) or "").strip() if len(text) < _MIN_TEXT: continue agent = REGISTRY.get(agent_id) if agent is None: logger.warning("agent_outputs: agent '%s' not registered", agent_id) continue try: out = await agent.evaluate(AgentInput( doc_type=topic, text=text, business_scope=scope, company_name=company_name, origin_domain=origin_domain, )) outputs[topic] = out.model_dump(mode="json") logger.info( "agent_outputs[%s]: %d findings, confidence %.2f", topic, len(out.findings), out.confidence, ) except Exception as e: # noqa: BLE001 — best-effort, never break the run logger.warning("agent_outputs[%s] failed: %s", topic, e) if outputs: state["agent_outputs"] = outputs