"""Tests fuer die Akteursaufloesung. Ohne spaCy und ohne Datenbank lauffaehig: geprueft wird die Aufloesung, nicht das Modell. Die Beispiele stammen aus dem Bestand, nicht aus der Phantasie. """ import datetime import json import unittest import akteure def lexikon(): """Kleines Lexikon mit genau den Faellen, die hier gebraucht werden.""" eintraege = [ {"id": "per:merz_friedrich", "typ": "person", "name": "Friedrich Merz", "aliase": [], "land": "DEU", "rolle": "GOV", "notiz": "CDU"}, {"id": "per:putin_wladimir", "typ": "person", "name": "Wladimir Putin", "aliase": ["Wladimir Wladimirowitsch Putin"], "land": "RUS", "rolle": "GOV", "notiz": None}, {"id": "per:wolf_katja", "typ": "person", "name": "Katja Wolf", "aliase": [], "land": "DEU", "rolle": "GOV", "notiz": "BSW"}, {"id": "per:mueller_anna", "typ": "person", "name": "Anna Mueller", "aliase": [], "land": "DEU", "rolle": "LEG", "notiz": "SPD"}, {"id": "per:mueller_bernd", "typ": "person", "name": "Bernd Mueller", "aliase": [], "land": "DEU", "rolle": "LEG", "notiz": "CDU"}, {"id": "par:afd", "typ": "partei", "name": "Alternative fuer Deutschland", "aliase": ["AfD"], "land": "DEU", "rolle": None, "notiz": None}, {"id": "par:cdu", "typ": "partei", "name": "CDU", "aliase": ["Christlich Demokratische Union"], "land": "DEU", "rolle": None, "notiz": None}, {"id": "sta:ukraine", "typ": "staat", "name": "Ukraine", "aliase": [], "land": "UKR", "rolle": None, "notiz": None}, {"id": "amt:bundeskanzler", "typ": "amt", "name": "Bundeskanzler", "aliase": ["Kanzler"], "land": "DEU", "rolle": "GOV", "notiz": None}, ] zeiten = [ {"amt_id": "amt:bundeskanzler", "person_id": "per:merz_friedrich", "von": datetime.date(2025, 5, 6), "bis": None}, {"amt_id": "amt:bundeskanzler", "person_id": "per:scholz_olaf", "von": datetime.date(2021, 12, 8), "bis": datetime.date(2025, 5, 6)}, ] return akteure.Lexikon(eintraege, zeiten) class Vergleichsform(unittest.TestCase): def test_umlaute_und_fall(self): self.assertEqual(akteure.vergleichsform("Grüne"), "gruene") self.assertEqual(akteure.vergleichsform("STRASSE"), "strasse") def test_satzzeichen_fallen_weg(self): self.assertEqual(akteure.vergleichsform("F.A.Z."), "f a z") self.assertEqual(akteure.vergleichsform("Merz:"), "merz") def test_akzente(self): self.assertEqual(akteure.vergleichsform("Émile Zola"), "emile zola") class Anrede(unittest.TestCase): def test_amtsbezeichnung_faellt_weg(self): self.assertEqual(akteure.ohne_anrede("us-praesident trump"), "trump") self.assertEqual(akteure.ohne_anrede("bundesinnenminister dobrindt"), "dobrindt") def test_kette(self): self.assertEqual(akteure.ohne_anrede("der bundeskanzler merz"), "merz") def test_ohne_anrede_unveraendert(self): self.assertEqual(akteure.ohne_anrede("friedrich merz"), "friedrich merz") def test_reine_anrede_bleibt_stehen(self): # Sonst bliebe nichts uebrig, womit sich nachschlagen liesse. self.assertTrue(akteure.ohne_anrede("bundeskanzler")) class Distanz(unittest.TestCase): def test_gleich(self): self.assertEqual(akteure.distanz("merz", "merz", 2), 0) def test_ein_zeichen(self): self.assertEqual(akteure.distanz("merz", "mertz", 2), 1) def test_abbruch_ueber_grenze(self): self.assertGreater(akteure.distanz("merz", "schulze", 1), 1) def test_grenze_nach_laenge(self): self.assertEqual(akteure.fuzzy_grenze(4), 0) self.assertEqual(akteure.fuzzy_grenze(8), 1) self.assertEqual(akteure.fuzzy_grenze(15), 2) class Nachschlagen(unittest.TestCase): def setUp(self): self.l = lexikon() def test_voller_name(self): ids, methode = self.l.schlage_nach("friedrich merz") self.assertEqual(ids, {"per:merz_friedrich"}) self.assertEqual(methode, "alias") def test_alias(self): ids, methode = self.l.schlage_nach("afd") self.assertEqual(ids, {"par:afd"}) self.assertEqual(methode, "alias") def test_nachname_eindeutig(self): ids, methode = self.l.schlage_nach("putin") self.assertEqual(ids, {"per:putin_wladimir"}) self.assertEqual(methode, "nachname") def test_nachname_mehrdeutig(self): ids, methode = self.l.schlage_nach("mueller") self.assertEqual(len(ids), 2) self.assertEqual(methode, "nachname") def test_gesperrter_nachname(self): # "Wolf" steht auf der Sperrliste; der volle Name geht trotzdem. ids, _ = self.l.schlage_nach("wolf") self.assertEqual(ids, set()) ids, _ = self.l.schlage_nach("katja wolf") self.assertEqual(ids, {"per:wolf_katja"}) def test_unbekannt(self): ids, methode = self.l.schlage_nach("michael mendl") self.assertEqual(ids, set()) self.assertIsNone(methode) def test_kurze_form_ohne_fuzzy(self): # Vier Zeichen, Grenze 0: "golf" darf nicht zu "wolf" werden. ids, _ = self.l.schlage_nach("golf") self.assertEqual(ids, set()) class Amtszeit(unittest.TestCase): def setUp(self): self.l = lexikon() def test_inhaber_heute(self): self.assertEqual(self.l.inhaber("amt:bundeskanzler", datetime.date(2026, 9, 7)), ["per:merz_friedrich"]) def test_inhaber_frueher(self): self.assertEqual(self.l.inhaber("amt:bundeskanzler", datetime.date(2023, 3, 1)), ["per:scholz_olaf"]) def test_amt_wird_gegen_datum_aufgeloest(self): spannen = [{"feld": "titel", "form": "Kanzler", "start": 0, "ende": 7, "ner_typ": "MISC"}] alt = akteure.aufloesen(self.l, spannen, datetime.date(2023, 3, 1)) neu = akteure.aufloesen(self.l, spannen, datetime.date(2026, 9, 7)) self.assertEqual(alt[0]["id"], "amt:bundeskanzler") self.assertEqual(alt[0]["inhaber"], "per:scholz_olaf") self.assertEqual(neu[0]["inhaber"], "per:merz_friedrich") def test_ohne_datum_kein_inhaber(self): spannen = [{"feld": "titel", "form": "Kanzler", "start": 0, "ende": 7, "ner_typ": "MISC"}] e = akteure.aufloesen(self.l, spannen, None) self.assertNotIn("inhaber", e[0]) class Aufloesung(unittest.TestCase): def setUp(self): self.l = lexikon() def spanne(self, form, feld="titel"): return {"feld": feld, "form": form, "start": 0, "ende": len(form), "ner_typ": "PER"} def test_unaufgeloest_bleibt_stehen(self): e = akteure.aufloesen(self.l, [self.spanne("Michael Mendl")], None) self.assertIsNone(e[0]["id"]) self.assertEqual(e[0]["methode"], "ner_unaufgeloest") self.assertEqual(e[0]["form"], "Michael Mendl") def test_mehrdeutig_bleibt_offen(self): e = akteure.aufloesen(self.l, [self.spanne("Müller")], None) self.assertIsNone(e[0]["id"]) self.assertIn("mehrdeutig", e[0]["methode"]) self.assertEqual(len(e[0]["kandidaten"]), 2) def test_kontext_entscheidet(self): # Steht die SPD daneben, ist Anna Mueller gemeint - nicht Bernd. spannen = [self.spanne("Müller"), self.spanne("SPD", "teaser")] self.l.akteure["per:mueller_anna"]["notiz"] = "SPD" e = akteure.aufloesen(self.l, spannen, None) self.assertEqual(e[0]["id"], "per:mueller_anna") self.assertEqual(e[0]["methode"], "nachname") def test_ner_typ_wird_nicht_uebernommen(self): # Das Modell haelt "Merz" oft fuer einen Ort. Das Lexikon nicht. s = self.spanne("Merz") s["ner_typ"] = "LOC" e = akteure.aufloesen(self.l, [s], None) self.assertEqual(e[0]["typ"], "person") self.assertEqual(e[0]["ner_typ"], "LOC") def test_konfidenz_faellt_mit_der_methode(self): voll = akteure.aufloesen(self.l, [self.spanne("Friedrich Merz")], None) kurz = akteure.aufloesen(self.l, [self.spanne("Putin")], None) self.assertGreater(voll[0]["konfidenz"], kurz[0]["konfidenz"]) class Nutzlast(unittest.TestCase): def setUp(self): self.l = lexikon() def test_zaehlt_mit(self): spannen = [{"feld": "titel", "form": f, "start": 0, "ende": 1, "ner_typ": "PER"} for f in ("Friedrich Merz", "AfD", "Mendl")] n = akteure.nutzlast(akteure.aufloesen(self.l, spannen, None), "test/9") self.assertEqual(n["nennungen"], 3) self.assertEqual(n["aufgeloest"], 2) self.assertEqual(n["typen"], {"person": 1, "partei": 1}) self.assertEqual(n["lexikon_version"], "test/9") def test_keine_zeichenkette_ueber_hundert(self): """Spiegelt den Constraint p2_nutzlast_ohne_text.""" spannen = [{"feld": "titel", "form": "x" * 500, "start": 0, "ende": 500, "ner_typ": "ORG"}] def pruefe(wert): if isinstance(wert, str): self.assertLessEqual(len(wert), 100, wert[:40]) elif isinstance(wert, dict): for v in wert.values(): pruefe(v) elif isinstance(wert, list): for v in wert: pruefe(v) # Die Begrenzung sitzt in erkennungen(); hier wird geprueft, dass # eine bereits gekappte Form die Nutzlast nicht sprengt. spannen[0]["form"] = spannen[0]["form"][:akteure.FORM_MAX] n = akteure.nutzlast(akteure.aufloesen(self.l, spannen, None), "test/9") pruefe(n) def test_ist_serialisierbar(self): spannen = [{"feld": "titel", "form": "AfD", "start": 0, "ende": 3, "ner_typ": "ORG"}] n = akteure.nutzlast(akteure.aufloesen(self.l, spannen, None), "test/9") self.assertIsInstance(json.dumps(n), str) if __name__ == "__main__": unittest.main()