feat(agent): Impressum Rechtsform-Gates + USt-optional (Phase 3)
Die 8 Audit-Klassifizierungs-Felder (scan_context) treiben jetzt den business_scope der Agenten (vorher gespeichert, aber nicht genutzt). Rechtsform-Gates als opt-out (excludes_scope): Verein -> kein Handelsregister-Finding, e.K. -> kein Vertretungsberechtigte-Finding; unbekannte Rechtsform bleibt anwendbar. USt-IdNr optional -> fehlt = kein Finding. Rechts-Zuordnung vom Domain-Experten bestaetigt. - _classification.py: scan_context_to_scope (8 Felder -> scope-Tokens) - mcs.py: MC.excludes_scope + MC.optional; IMP-MC-004/006 Gate-Tokens; IMP-MC-005 optional; scope_matches respektiert excludes_scope - agent.py: optional -> kein Finding bei Abwesenheit - _agent_outputs.py: scope = scan_context vereinigt LLM-Profil-Fallback - Tests gruen: v3 25, Groundtruth 13, CI-Pfad 14 (+ SSE-Loop-Fix) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""Phase 2: SSE-Plumbing für den Compliance-Check.
|
||||
|
||||
Deckt emit (Queue-Push), _format_sse (SSE-Zeilenformat) und den
|
||||
event_generator (hello → Events → stream_close bei 'complete') ab.
|
||||
Queue + Generator laufen innerhalb eines asyncio.run (sonst bindet
|
||||
asyncio.Queue in Py3.9 an einen ggf. geschlossenen Loop).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -11,15 +11,8 @@ import asyncio
|
||||
from compliance.api.agent_check import _sse
|
||||
|
||||
|
||||
def test_emit_pushes_and_format():
|
||||
cid = "sse-test-1"
|
||||
_sse.new_queue(cid)
|
||||
_sse.emit(cid, {"type": "topic", "topic": "impressum", "output": {"x": 1}})
|
||||
q = _sse._check_queues[cid]
|
||||
assert q.qsize() == 1
|
||||
ev = q.get_nowait()
|
||||
assert ev["type"] == "topic" and ev["topic"] == "impressum"
|
||||
line = _sse._format_sse(ev)
|
||||
def test_format_sse_line():
|
||||
line = _sse._format_sse({"type": "topic", "topic": "impressum"})
|
||||
assert line.startswith("data: ") and line.endswith("\n\n")
|
||||
assert '"impressum"' in line
|
||||
|
||||
@@ -29,21 +22,20 @@ def test_emit_is_noop_without_queue():
|
||||
_sse.emit("does-not-exist-xyz", {"type": "topic"})
|
||||
|
||||
|
||||
def test_event_generator_streams_topic_then_closes_on_complete():
|
||||
cid = "sse-test-gen"
|
||||
_sse.new_queue(cid)
|
||||
_sse.emit(cid, {"type": "topic", "topic": "impressum", "output": {}})
|
||||
_sse.emit(cid, {"type": "complete", "status": "completed"})
|
||||
|
||||
async def collect():
|
||||
out = []
|
||||
def test_emit_and_event_generator_streams_then_closes():
|
||||
async def scenario():
|
||||
cid = "sse-test-gen"
|
||||
_sse.new_queue(cid)
|
||||
_sse.emit(cid, {"type": "topic", "topic": "impressum", "output": {}})
|
||||
_sse.emit(cid, {"type": "complete", "status": "completed"})
|
||||
out: list[str] = []
|
||||
async for line in _sse.event_generator(cid):
|
||||
out.append(line)
|
||||
if len(out) > 12: # safety
|
||||
break
|
||||
return out
|
||||
|
||||
blob = "".join(asyncio.run(collect()))
|
||||
blob = "".join(asyncio.run(scenario()))
|
||||
assert '"type": "hello"' in blob
|
||||
assert '"topic": "impressum"' in blob
|
||||
assert '"type": "complete"' in blob
|
||||
|
||||
Reference in New Issue
Block a user