feat(agent): strukturierte Ergebnis-Tabs — Impressum (Phase 1)
Der Compliance-Check legt zusätzlich einen strukturierten v3-AgentOutput pro Thema in result.agent_outputs ab (additiv; B18-HTML + Firehose-Mail bleiben unangetastet). Frontend: standardisiertes Ergebnis-Tab statt Firehose — Impressum-Tab (AgentResultTab) + "Alle Checks (roh)" (ChecklistView). - backend: _agent_outputs.py ruft den registrierten v3-ImpressumAgent, gewired in _orchestrator nach B18, surfaced via _phase_f_persist. - frontend: AgentResultView (aus AgentSlotCard extrahiert, DRY), AgentResultTab, ComplianceResultTabs; ComplianceCheckTab 490->391 Zeilen. - Tests: backend 2 passed, frontend 2 passed; tsc 0 neue Fehler; check-loc 0. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
"""Phase 1: der Compliance-Check legt einen strukturierten v3-AgentOutput
|
||||
pro Thema in state['agent_outputs'][topic] ab (für die Ergebnis-Tabs).
|
||||
|
||||
Offline + deterministisch: die einzige LLM-Stelle im registrierten
|
||||
ImpressumAgent ist `validate_present` (Semantic-Validator) — gemockt.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
import pytest
|
||||
|
||||
# Impressum mit Name+Anschrift+Geschäftsführer, aber OHNE Email, Telefon,
|
||||
# Handelsregister, USt-IdNr → erzwingt Findings (alle mit norm + action).
|
||||
IMPRESSUM_TEXT = (
|
||||
"Angaben gemäß § 5 TMG\n\n"
|
||||
"Musterfirma GmbH\n"
|
||||
"Musterstraße 1\n"
|
||||
"12345 Berlin\n\n"
|
||||
"Vertreten durch den Geschäftsführer: Max Mustermann\n\n"
|
||||
"Wir betreiben einen Online-Shop für Musterprodukte aller Art. "
|
||||
"Weitere Informationen finden Sie auf unserer Website.\n"
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _llm_offline(monkeypatch):
|
||||
"""Semantic-Validator (LLM) neutralisieren → rein deterministischer Lauf."""
|
||||
async def _no_validate(*_a, **_kw):
|
||||
return {}
|
||||
monkeypatch.setattr(
|
||||
"compliance.services.specialist_agents.impressum.agent.validate_present",
|
||||
_no_validate,
|
||||
raising=False,
|
||||
)
|
||||
|
||||
|
||||
def test_run_agent_outputs_populates_structured_impressum():
|
||||
from compliance.api.agent_check._agent_outputs import run_agent_outputs
|
||||
|
||||
state = {
|
||||
"doc_texts": {"impressum": IMPRESSUM_TEXT},
|
||||
"profile_dict": {"has_online_shop": True},
|
||||
"req": None,
|
||||
"extracted_profile": {"company_name": "Musterfirma GmbH"},
|
||||
"site_name": "musterfirma.de",
|
||||
"domain": "musterfirma.de",
|
||||
}
|
||||
asyncio.run(run_agent_outputs(state))
|
||||
|
||||
out = (state.get("agent_outputs") or {}).get("impressum")
|
||||
assert out is not None, "impressum AgentOutput muss im Ergebnis liegen"
|
||||
assert out["agent"] == "impressum"
|
||||
assert isinstance(out["findings"], list)
|
||||
# Unvollständiges Impressum → mind. ein Finding, jedes mit Abstellmaßnahme
|
||||
assert out["findings"], "erwarte Findings für ein unvollständiges Impressum"
|
||||
assert all(f.get("action") for f in out["findings"]), \
|
||||
"jedes Finding trägt eine Abstellmaßnahme (action)"
|
||||
# Auditfest: Rechtsgrundlage + Quelle je Finding
|
||||
assert all(f.get("norm") for f in out["findings"])
|
||||
assert all(f.get("sources") for f in out["findings"])
|
||||
# Aggregat-Felder fürs Speedometer vorberechnet
|
||||
assert out["mc_total"] >= 1
|
||||
# Linter-sauber: keine verbotenen Disclaimer-Begriffe im Output
|
||||
blob = str(out).lower()
|
||||
for term in ("rechtssicher", "garantiert", "gesetzeskonform"):
|
||||
assert term not in blob
|
||||
|
||||
|
||||
def test_run_agent_outputs_skips_short_text():
|
||||
from compliance.api.agent_check._agent_outputs import run_agent_outputs
|
||||
|
||||
state = {
|
||||
"doc_texts": {"impressum": "zu kurz"},
|
||||
"profile_dict": {},
|
||||
"req": None,
|
||||
}
|
||||
asyncio.run(run_agent_outputs(state))
|
||||
assert not (state.get("agent_outputs") or {}).get("impressum")
|
||||
Reference in New Issue
Block a user