e21984e0ad
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>
81 lines
2.9 KiB
Python
81 lines
2.9 KiB
Python
"""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")
|