"""Testet die gezielte Belegsuche der EU-Fassung. Laeuft ohne Netzzugriff und ohne Kosten, das planende Modell und die europaeische Suche sind durch Testdoubles ersetzt. Aufruf aus dem Projektstamm: venv/bin/python tests/test_eu_belegsuche.py """ import asyncio import json import os import sys sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "src")) import agents.eu_researcher as eur # noqa: E402 from agents.claude_client import ClaudeUsage # noqa: E402 from config import ( # noqa: E402 EU_VERIFY_HITS_PER_QUERY, EU_VERIFY_MAX_ITEMS, STAAN_COST_PER_QUERY_USD, ) ok = 0 fail = 0 def pruefe(name, bedingung, extra=""): global ok, fail if bedingung: ok += 1 print(" OK " + name) else: fail += 1 print(" FEHL " + name + " " + str(extra)) zustand = {"plan_prompt": "", "suchen": []} plan_antwort = {"queries": []} async def fake_bedrock(prompt, model=None, raw_text=False, timeout=None): zustand["plan_prompt"] = prompt return json.dumps(plan_antwort), ClaudeUsage(input_tokens=10, output_tokens=5, cost_usd=0.002) treffer_je_anfrage = {} async def fake_staan(q, market="de-de", extra_snippets=True, max_snippets=3, full_content=False, extra_exclude=None, timeout=None): zustand["suchen"].append({"q": q, "market": market}) return treffer_je_anfrage.get(q, []) def treffer(n, praefix="t", host="example.org"): return [{ "title": praefix + " Titel " + str(i), "hostname": host, "url": "https://" + host + "/" + praefix + str(i), "snippet": "Auszug " + str(i), "extra_snippets": ["Zusatz " + str(i)], } for i in range(1, n + 1)] eur.call_bedrock = fake_bedrock eur.staan_search = fake_staan def neu(): zustand["suchen"].clear() treffer_je_anfrage.clear() print("\nA) Planung ist punktbezogen und fuehrt die Anfragen aus") neu() plan_antwort["queries"] = [ {"q": "Ceuta Opferzahl offiziell", "market": "de-de", "for_items": [1]}, {"q": "Ceuta border deaths toll", "market": "en-us", "for_items": [1, 2]}, {"q": "Sanchez Ceuta declaracion", "market": "es-es", "for_items": [2]}, ] treffer_je_anfrage["Ceuta Opferzahl offiziell"] = treffer(2, "a") treffer_je_anfrage["Ceuta border deaths toll"] = treffer(2, "b") treffer_je_anfrage["Sanchez Ceuta declaracion"] = treffer(2, "c") block, usage, ausgefuehrt = asyncio.run(eur.plan_verification_searches( title="Ceuta", search_items=["57 Tote gemeldet", "Sanchez sprach von Angriff"], output_language="Deutsch", max_queries=7, )) pruefe("A1 alle geplanten Anfragen ausgefuehrt", len(zustand["suchen"]) == 3, zustand["suchen"]) pruefe("A2 ausgefuehrte Anfragen zurueckgemeldet", len(ausgefuehrt) == 3, ausgefuehrt) pruefe("A3 jeder Punkt steht im Planungsauftrag", "57 Tote gemeldet" in zustand["plan_prompt"] and "Sanchez sprach von Angriff" in zustand["plan_prompt"]) pruefe("A4 Planungsauftrag verlangt Punktzuordnung", "for_items" in zustand["plan_prompt"]) pruefe("A5 Obergrenze steht im Auftrag", "7" in zustand["plan_prompt"]) pruefe("A6 alle Treffer nummeriert im Block", block.count("[S") == 6, block.count("[S")) pruefe("A7 Auszuege uebernommen", block.count("Auszug") >= 6, block.count("Auszug")) pruefe("A8 unbekannter Markt faellt auf die Vorgabe zurueck", [s["market"] for s in zustand["suchen"]] == ["de-de", "en-us", "de-de"], [s["market"] for s in zustand["suchen"]]) erwartet = round(0.002 + 3 * STAAN_COST_PER_QUERY_USD, 6) pruefe("A9 Suchkosten eingerechnet", round(usage.cost_usd, 6) == erwartet, usage.cost_usd) print("\nB) Dieselbe Quelle steht nur einmal im Belegblock") neu() plan_antwort["queries"] = [ {"q": "anfrage eins", "market": "de-de"}, {"q": "anfrage zwei", "market": "de-de"}, ] treffer_je_anfrage["anfrage eins"] = treffer(3, "x") treffer_je_anfrage["anfrage zwei"] = treffer(3, "x") # identische URLs block_b, _, _ = asyncio.run(eur.plan_verification_searches( title="Ceuta", search_items=["Punkt"], max_queries=7, )) pruefe("B1 Dubletten entfernt", block_b.count("[S") == 3, block_b.count("[S")) pruefe("B2 Nummerierung bleibt luecklos", all(("[S" + str(i) + "]") in block_b for i in (1, 2, 3))) print("\nC) Nachrunde meidet bereits gestellte Anfragen") neu() plan_antwort["queries"] = [ {"q": "alte anfrage", "market": "de-de"}, {"q": "neue anfrage", "market": "de-de"}, ] treffer_je_anfrage["neue anfrage"] = treffer(2, "n") block_c, _, ausgefuehrt_c = asyncio.run(eur.plan_verification_searches( title="Ceuta", search_items=["Punkt"], max_queries=5, avoid_queries=["alte anfrage"], label="Nachhak-Suche", )) pruefe("C1 bereits gestellte Anfrage wird verworfen", [s["q"] for s in zustand["suchen"]] == ["neue anfrage"], zustand["suchen"]) pruefe("C2 Sperrliste steht im Planungsauftrag", "alte anfrage" in zustand["plan_prompt"]) pruefe("C3 nur die neue Anfrage gemeldet", ausgefuehrt_c == ["neue anfrage"], ausgefuehrt_c) print("\nD) Grenzen werden eingehalten") neu() plan_antwort["queries"] = [{"q": "q" + str(i), "market": "de-de"} for i in range(1, 11)] for i in range(1, 11): treffer_je_anfrage["q" + str(i)] = treffer(20, "y" + str(i)) block_d, _, ausgefuehrt_d = asyncio.run(eur.plan_verification_searches( title="Ceuta", search_items=["Punkt"], max_queries=4, )) pruefe("D1 hoechstens max_queries Anfragen", len(ausgefuehrt_d) == 4, ausgefuehrt_d) pruefe("D2 Treffer je Anfrage begrenzt", block_d.count("[S") == 4 * EU_VERIFY_HITS_PER_QUERY, block_d.count("[S")) neu() plan_antwort["queries"] = [{"q": "q1", "market": "de-de"}] treffer_je_anfrage["q1"] = treffer(1, "z") asyncio.run(eur.plan_verification_searches( title="Ceuta", search_items=["Punkt " + str(i) for i in range(1, 40)], max_queries=2, )) gezaehlt = sum(1 for i in range(1, 40) if ("Punkt " + str(i)) in zustand["plan_prompt"]) pruefe("D3 Zahl der Pruefpunkte begrenzt", gezaehlt == EU_VERIFY_MAX_ITEMS, gezaehlt) print("\nE) Ausfaelle fuehren nicht zum Abbruch") neu() plan_antwort["queries"] = [{"q": "kaputt", "market": "de-de"}, {"q": "heil", "market": "de-de"}] treffer_je_anfrage["heil"] = treffer(2, "h") async def staan_mit_fehler(q, **kw): zustand["suchen"].append({"q": q, "market": kw.get("market")}) if q == "kaputt": raise eur.StaanError("Suche nicht erreichbar") return treffer_je_anfrage.get(q, []) eur.staan_search = staan_mit_fehler block_e, usage_e, ausgefuehrt_e = asyncio.run(eur.plan_verification_searches( title="Ceuta", search_items=["Punkt"], max_queries=5, )) eur.staan_search = fake_staan pruefe("E1 gescheiterte Suche uebersprungen", ausgefuehrt_e == ["heil"], ausgefuehrt_e) pruefe("E2 gefundene Belege bleiben erhalten", block_e.count("[S") == 2, block_e.count("[S")) pruefe("E3 nur bezahlte Suchen berechnet", round(usage_e.cost_usd, 6) == round(0.002 + STAAN_COST_PER_QUERY_USD, 6), usage_e.cost_usd) neu() plan_antwort["queries"] = [] block_f, _, ausgefuehrt_f = asyncio.run(eur.plan_verification_searches( title="Ceuta", search_items=["Punkt"], max_queries=5, )) pruefe("E4 leere Planung liefert leeren Block", block_f == "" and ausgefuehrt_f == []) block_g, _, _ = asyncio.run(eur.plan_verification_searches( title="Ceuta", search_items=[], max_queries=5, )) pruefe("E5 ohne Pruefpunkte keine Suche", block_g == "") print("\nErgebnis: " + str(ok) + " bestanden, " + str(fail) + " fehlgeschlagen") sys.exit(1 if fail else 0)