| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749 |
- # -*- coding: utf-8 -*-
- """
- Tests fuer das Entity-System.
- Testet EntityDefinition, EntityRegistry, EntityResolver,
- Datum-/Zeit-/Dauer-Aufloesung und Integration mit dem Matcher.
- """
- import re
- from datetime import date, time, timedelta
- import pytest
- from trixy_core.nlp.entities import (
- EntityDefinition,
- EntityRegistry,
- EntityResolver,
- ResolvedEntity,
- _next_weekday,
- _parse_absolute_date,
- _parse_time_string,
- _format_date_tts,
- _format_time_tts,
- _format_duration_tts,
- )
- # ============================================================================
- # Fixtures
- # ============================================================================
- @pytest.fixture
- def registry():
- """EntityRegistry mit Testdaten."""
- r = EntityRegistry()
- r.load_from_dict({
- "datum": {
- "open": False,
- "values": [
- "heute", "morgen", "uebermorgen", "übermorgen",
- "montag", "dienstag", "mittwoch", "donnerstag",
- "freitag", "samstag", "sonntag",
- "nächste woche", "am wochenende",
- ],
- "regex": [
- r"\d{1,2}\.\d{1,2}\.",
- r"\d{1,2}\.\d{1,2}\.\d{4}",
- ],
- },
- "zeit": {
- "open": False,
- "values": ["mittag", "mitternacht", "abend", "morgens", "nachmittag"],
- "regex": [
- r"\d{1,2} uhr",
- r"\d{1,2} uhr \d{1,2}",
- r"\d{1,2}:\d{2}",
- r"halb \w+",
- ],
- },
- "dauer": {
- "open": False,
- "values": ["eine minute", "eine stunde", "eine halbe stunde"],
- "regex": [
- r"\d+ (sekunden|sekunde)",
- r"\d+ (minuten|minute)",
- r"\d+ (stunden|stunde)",
- ],
- },
- "stadt": {
- "open": True,
- "values": ["berlin", "hamburg", "münchen", "muenchen"],
- },
- "person": {
- "open": True,
- "values": [],
- },
- })
- return r
- @pytest.fixture
- def resolver(registry):
- """EntityResolver mit Registry."""
- return EntityResolver(registry)
- # ============================================================================
- # EntityDefinition
- # ============================================================================
- class TestEntityDefinition:
- """Tests fuer EntityDefinition."""
- def test_defaults(self):
- """Standard-Werte pruefen."""
- ed = EntityDefinition(name="test")
- assert ed.name == "test"
- assert ed.values == set()
- assert ed.multi_word_values == set()
- assert ed.open is False
- assert ed.regex_patterns == []
- def test_mit_werten(self):
- """Definition mit Werten erstellen."""
- ed = EntityDefinition(
- name="stadt",
- values={"berlin", "hamburg"},
- open=True,
- )
- assert "berlin" in ed.values
- assert ed.open is True
- # ============================================================================
- # EntityRegistry
- # ============================================================================
- class TestEntityRegistry:
- """Tests fuer EntityRegistry."""
- def test_get_existierend(self, registry):
- """Existierenden Entity-Typ abrufen."""
- entity = registry.get("datum")
- assert entity is not None
- assert entity.name == "datum"
- assert "heute" in entity.values
- assert "morgen" in entity.values
- def test_get_nicht_existierend(self, registry):
- """Nicht existierenden Entity-Typ abrufen."""
- assert registry.get("unbekannt") is None
- def test_matches_bekannter_wert(self, registry):
- """Bekannter Wert sollte matchen."""
- assert registry.matches("heute", "datum") is True
- assert registry.matches("morgen", "datum") is True
- assert registry.matches("montag", "datum") is True
- def test_matches_unbekannter_wert_strikt(self, registry):
- """Unbekannter Wert bei strikter Entity sollte nicht matchen."""
- assert registry.matches("xyz", "datum") is False
- assert registry.matches("foobar", "zeit") is False
- def test_matches_unbekannter_wert_offen(self, registry):
- """Unbekannter Wert bei offener Entity sollte matchen."""
- assert registry.matches("tokio", "stadt") is True
- assert registry.matches("xyz", "stadt") is True
- def test_matches_mehrwort(self, registry):
- """Mehrwort-Werte sollten matchen."""
- assert registry.matches("nächste woche", "datum") is True
- assert registry.matches("am wochenende", "datum") is True
- def test_matches_regex(self, registry):
- """Regex-Werte sollten matchen."""
- assert registry.matches("15.03.", "datum") is True
- assert registry.matches("1.1.2026", "datum") is True
- assert registry.matches("15 uhr", "zeit") is True
- assert registry.matches("9:30", "zeit") is True
- assert registry.matches("halb drei", "zeit") is True
- assert registry.matches("5 minuten", "dauer") is True
- def test_matches_regex_kein_match(self, registry):
- """Nicht passende Regex sollte nicht matchen."""
- assert registry.matches("xyz", "dauer") is False
- def test_matches_unbekannter_typ(self, registry):
- """Unbekannter Typ sollte alles akzeptieren."""
- assert registry.matches("whatever", "unbekannt") is True
- def test_mehrwort_match(self, registry):
- """Mehrwort-Match ab Position testen."""
- tokens = ["nächste", "woche"]
- result = registry.match_multi_word(tokens, 0, "datum")
- assert result is not None
- assert result[0] == "nächste woche"
- assert result[1] == 2
- def test_mehrwort_match_kein_treffer(self, registry):
- """Kein Mehrwort-Match."""
- tokens = ["berlin", "test"]
- result = registry.match_multi_word(tokens, 0, "datum")
- assert result is None
- def test_mehrwort_regex_match(self, registry):
- """Mehrwort-Regex-Match (z.B. '5 minuten')."""
- tokens = ["5", "minuten"]
- result = registry.match_multi_word(tokens, 0, "dauer")
- assert result is not None
- assert result[0] == "5 minuten"
- assert result[1] == 2
- def test_entity_types(self, registry):
- """entity_types sollte alle registrierten Typen listen."""
- types = registry.entity_types
- assert "datum" in types
- assert "zeit" in types
- assert "dauer" in types
- assert "stadt" in types
- assert "person" in types
- def test_load_from_dict_leer(self):
- """Leeres Dict sollte keine Fehler verursachen."""
- r = EntityRegistry()
- r.load_from_dict({})
- assert r.entity_types == []
- # ============================================================================
- # EntityResolver — Datum
- # ============================================================================
- class TestResolveDatum:
- """Tests fuer den Datum-Resolver."""
- def test_heute(self, resolver):
- """'heute' sollte heutiges Datum ergeben."""
- result = resolver.resolve("heute", "datum")
- assert isinstance(result, ResolvedEntity)
- assert result.entity_type == "datum"
- assert result.value == date.today()
- assert result.formatted["iso"] == date.today().isoformat()
- assert result.formatted["tts"] == "heute"
- def test_morgen(self, resolver):
- """'morgen' sollte morgiges Datum ergeben."""
- result = resolver.resolve("morgen", "datum")
- assert result.value == date.today() + timedelta(days=1)
- assert result.formatted["tts"] == "morgen"
- def test_uebermorgen(self, resolver):
- """'uebermorgen' sollte uebermorgen ergeben."""
- result = resolver.resolve("uebermorgen", "datum")
- assert result.value == date.today() + timedelta(days=2)
- assert result.formatted["tts"] == "uebermorgen"
- def test_uebermorgen_umlaut(self, resolver):
- """'übermorgen' mit Umlaut sollte auch funktionieren."""
- result = resolver.resolve("übermorgen", "datum")
- assert result.value == date.today() + timedelta(days=2)
- def test_wochentag(self, resolver):
- """Wochentag sollte naechstes Vorkommen ergeben."""
- result = resolver.resolve("montag", "datum")
- assert result.value.weekday() == 0 # Montag
- assert result.value > date.today()
- def test_am_wochenende(self, resolver):
- """'am wochenende' sollte naechsten Samstag ergeben."""
- result = resolver.resolve("am wochenende", "datum")
- assert result.value.weekday() == 5 # Samstag
- def test_absolutes_datum(self, resolver):
- """'15.03.' sollte korrekt geparst werden."""
- result = resolver.resolve("15.03.", "datum")
- assert result.value.day == 15
- assert result.value.month == 3
- def test_absolutes_datum_mit_jahr(self, resolver):
- """'15.03.2027' sollte korrekt geparst werden."""
- result = resolver.resolve("15.03.2027", "datum")
- assert result.value == date(2027, 3, 15)
- def test_formatted_eu(self, resolver):
- """EU-Format sollte DD.MM.YYYY sein."""
- result = resolver.resolve("heute", "datum")
- assert result.formatted["eu"] == date.today().strftime("%d.%m.%Y")
- def test_formatted_api(self, resolver):
- """API-Format sollte ISO sein."""
- result = resolver.resolve("morgen", "datum")
- expected = (date.today() + timedelta(days=1)).isoformat()
- assert result.formatted["api"] == expected
- def test_morgen_ist_today_plus_1(self, resolver):
- """'morgen' muss exakt date.today() + timedelta(days=1) sein."""
- today = date.today()
- expected = today + timedelta(days=1)
- result = resolver.resolve("morgen", "datum")
- assert result.value == expected, (
- f"'morgen' sollte {expected.isoformat()} sein, "
- f"aber ist {result.value}"
- )
- assert result.formatted["iso"] == expected.isoformat()
- assert result.formatted["api"] == expected.isoformat()
- def test_heute_ist_today(self, resolver):
- """'heute' muss exakt date.today() sein."""
- expected = date.today()
- result = resolver.resolve("heute", "datum")
- assert result.value == expected
- def test_uebermorgen_ist_today_plus_2(self, resolver):
- """'uebermorgen' muss exakt date.today() + 2 sein."""
- expected = date.today() + timedelta(days=2)
- result = resolver.resolve("uebermorgen", "datum")
- assert result.value == expected
- def test_wochentag_ist_in_zukunft_und_korrekt(self, resolver):
- """Wochentag muss in der Zukunft liegen und der richtige Wochentag sein."""
- today = date.today()
- for name, weekday_num in [
- ("montag", 0), ("dienstag", 1), ("mittwoch", 2),
- ("donnerstag", 3), ("freitag", 4), ("samstag", 5), ("sonntag", 6),
- ]:
- result = resolver.resolve(name, "datum")
- assert result.value.weekday() == weekday_num, (
- f"'{name}' sollte Wochentag {weekday_num} sein, "
- f"aber ist {result.value.weekday()}"
- )
- assert result.value > today, (
- f"'{name}' sollte nach heute ({today}) liegen"
- )
- # Maximal 7 Tage in der Zukunft
- assert (result.value - today).days <= 7
- def test_morgen_formatted_outputs(self, resolver):
- """'morgen' muss alle Formatvarianten korrekt liefern."""
- result = resolver.resolve("morgen", "datum")
- tomorrow = date.today() + timedelta(days=1)
- assert result.formatted["iso"] == tomorrow.isoformat()
- assert result.formatted["eu"] == tomorrow.strftime("%d.%m.%Y")
- assert result.formatted["tts"] == "morgen"
- assert result.formatted["api"] == tomorrow.isoformat()
- def test_unbekannter_wert(self, resolver):
- """Unbekannter Wert sollte Fallback liefern."""
- result = resolver.resolve("xyz", "datum")
- assert result.entity_type == "datum"
- assert result.value == "xyz" # Generischer Resolver
- def test_raw_erhalten(self, resolver):
- """Rohwert sollte erhalten bleiben."""
- result = resolver.resolve("morgen", "datum")
- assert result.raw == "morgen"
- # ============================================================================
- # EntityResolver — Zeit
- # ============================================================================
- class TestResolveZeit:
- """Tests fuer den Zeit-Resolver."""
- def test_mittag(self, resolver):
- """'mittag' sollte 12:00 ergeben."""
- result = resolver.resolve("mittag", "zeit")
- assert result.value == time(12, 0)
- assert result.formatted["24h"] == "12:00"
- assert result.formatted["tts"] == "12 Uhr"
- def test_mitternacht(self, resolver):
- """'mitternacht' sollte 00:00 ergeben."""
- result = resolver.resolve("mitternacht", "zeit")
- assert result.value == time(0, 0)
- def test_uhrzeit_format(self, resolver):
- """'15 uhr' sollte korrekt geparst werden."""
- result = resolver.resolve("15 uhr", "zeit")
- assert result.value == time(15, 0)
- assert result.formatted["tts"] == "15 Uhr"
- def test_uhrzeit_mit_minuten(self, resolver):
- """'15:30' sollte korrekt geparst werden."""
- result = resolver.resolve("15:30", "zeit")
- assert result.value == time(15, 30)
- assert result.formatted["24h"] == "15:30"
- def test_uhrzeit_uhr_minuten(self, resolver):
- """'15 uhr 30' sollte korrekt geparst werden."""
- result = resolver.resolve("15 uhr 30", "zeit")
- assert result.value == time(15, 30)
- def test_halb_drei(self, resolver):
- """'halb drei' sollte 2:30 ergeben."""
- result = resolver.resolve("halb drei", "zeit")
- assert result.value == time(2, 30)
- def test_abend(self, resolver):
- """'abend' sollte 18:00 ergeben."""
- result = resolver.resolve("abend", "zeit")
- assert result.value == time(18, 0)
- def test_unbekannte_zeit(self, resolver):
- """Unbekannte Zeitangabe sollte Fallback liefern."""
- result = resolver.resolve("xyz", "zeit")
- assert result.entity_type == "zeit"
- assert result.value == "xyz"
- # ============================================================================
- # EntityResolver — Dauer
- # ============================================================================
- class TestResolveDauer:
- """Tests fuer den Dauer-Resolver."""
- def test_minuten(self, resolver):
- """'5 minuten' sollte 300 Sekunden ergeben."""
- result = resolver.resolve("5 minuten", "dauer")
- assert result.value == 300
- assert result.formatted["seconds"] == "300"
- assert result.formatted["minutes"] == "5"
- assert result.formatted["tts"] == "5 Minuten"
- def test_sekunden(self, resolver):
- """'30 sekunden' sollte 30 Sekunden ergeben."""
- result = resolver.resolve("30 sekunden", "dauer")
- assert result.value == 30
- assert result.formatted["seconds"] == "30"
- assert result.formatted["tts"] == "30 Sekunden"
- def test_stunden(self, resolver):
- """'2 stunden' sollte 7200 Sekunden ergeben."""
- result = resolver.resolve("2 stunden", "dauer")
- assert result.value == 7200
- assert result.formatted["hours"] == "2"
- assert result.formatted["tts"] == "2 Stunden"
- def test_eine_minute(self, resolver):
- """'eine minute' sollte 60 Sekunden ergeben."""
- result = resolver.resolve("eine minute", "dauer")
- assert result.value == 60
- def test_eine_halbe_stunde(self, resolver):
- """'eine halbe stunde' sollte 1800 Sekunden ergeben."""
- result = resolver.resolve("eine halbe stunde", "dauer")
- assert result.value == 1800
- assert result.formatted["minutes"] == "30"
- def test_eine_stunde(self, resolver):
- """'eine stunde' sollte 3600 Sekunden ergeben."""
- result = resolver.resolve("eine stunde", "dauer")
- assert result.value == 3600
- def test_1_sekunde(self, resolver):
- """'1 sekunde' sollte 1 Sekunde ergeben."""
- result = resolver.resolve("1 sekunde", "dauer")
- assert result.value == 1
- assert result.formatted["tts"] == "1 Sekunde"
- def test_unbekannte_dauer(self, resolver):
- """Unbekannte Dauer sollte Fallback liefern."""
- result = resolver.resolve("xyz", "dauer")
- assert result.entity_type == "dauer"
- assert result.value == "xyz"
- # ============================================================================
- # EntityResolver — Generisch
- # ============================================================================
- class TestResolveGeneric:
- """Tests fuer den generischen Resolver."""
- def test_unbekannter_entity_typ(self, resolver):
- """Unbekannter Entity-Typ sollte generisch aufgeloest werden."""
- result = resolver.resolve("test", "unbekannt")
- assert result.entity_type == "unbekannt"
- assert result.value == "test"
- assert result.formatted["raw"] == "test"
- def test_stadt(self, resolver):
- """Stadt (offene Entity, kein spezieller Resolver) → generisch."""
- result = resolver.resolve("berlin", "stadt")
- assert result.entity_type == "stadt"
- assert result.value == "berlin"
- def test_raw_wird_getrimmt(self, resolver):
- """Rohwert wird getrimmt."""
- result = resolver.resolve(" berlin ", "stadt")
- assert result.value == "berlin"
- # ============================================================================
- # Hilfsfunktionen
- # ============================================================================
- class TestHilfsfunktionen:
- """Tests fuer interne Hilfsfunktionen."""
- def test_next_weekday(self):
- """Naechster Wochentag berechnen."""
- today = date.today()
- # Naechster Montag
- result = _next_weekday(today, 0)
- assert result.weekday() == 0
- assert result > today
- def test_parse_absolute_date_dd_mm(self):
- """Absolutes Datum DD.MM. parsen."""
- result = _parse_absolute_date("15.03.")
- assert result is not None
- assert result.day == 15
- assert result.month == 3
- def test_parse_absolute_date_dd_mm_yyyy(self):
- """Absolutes Datum DD.MM.YYYY parsen."""
- result = _parse_absolute_date("15.03.2027")
- assert result == date(2027, 3, 15)
- def test_datum_mit_monatname(self):
- """'5 mai' sollte ein Datum mit Tag 5, Monat 5 ergeben."""
- result = _parse_absolute_date("5 mai")
- assert result is not None
- assert result.day == 5
- assert result.month == 5
- def test_datum_mit_monatname_vergangenheit(self):
- """Vergangenes Datum mit Monatsname sollte naechstes Jahr ergeben."""
- today = date.today()
- # Waehle ein Datum das sicher in der Vergangenheit liegt
- past_month = today.month - 1 if today.month > 1 else 12
- past_day = 1
- result = _parse_absolute_date(f"{past_day} {_get_month_name(past_month)}")
- assert result is not None
- if past_month < today.month or (past_month == today.month and past_day < today.day):
- assert result.year == today.year + 1
- else:
- assert result.year == today.year
- def test_datum_maerz_varianten(self):
- """'maerz' und 'märz' sollten beide als Maerz erkannt werden."""
- result1 = _parse_absolute_date("1 maerz")
- result2 = _parse_absolute_date("1 märz")
- assert result1 is not None
- assert result2 is not None
- assert result1.month == 3
- assert result2.month == 3
- assert result1 == result2
- def test_parse_absolute_date_ungueltig(self):
- """Ungueltiges Datum sollte None ergeben."""
- assert _parse_absolute_date("abc") is None
- assert _parse_absolute_date("32.13.") is None
- def test_parse_time_string_hh_mm(self):
- """Zeitformat HH:MM parsen."""
- assert _parse_time_string("15:30") == (15, 30)
- assert _parse_time_string("9:00") == (9, 0)
- def test_parse_time_string_hh_uhr(self):
- """Zeitformat 'HH Uhr' parsen."""
- assert _parse_time_string("15 uhr") == (15, 0)
- def test_parse_time_string_hh_uhr_mm(self):
- """Zeitformat 'HH Uhr MM' parsen."""
- assert _parse_time_string("15 uhr 30") == (15, 30)
- def test_parse_time_string_halb(self):
- """'halb drei' → (2, 30)."""
- assert _parse_time_string("halb drei") == (2, 30)
- def test_parse_time_string_ungueltig(self):
- """Ungueltiger Zeitstring sollte None ergeben."""
- assert _parse_time_string("abc") is None
- def test_format_date_tts(self):
- """TTS-Formatierung fuer Datum."""
- today = date.today()
- assert _format_date_tts(today) == "heute"
- assert _format_date_tts(today + timedelta(days=1)) == "morgen"
- assert _format_date_tts(today + timedelta(days=2)) == "uebermorgen"
- def test_format_time_tts(self):
- """TTS-Formatierung fuer Zeit."""
- assert _format_time_tts(time(15, 0)) == "15 Uhr"
- assert _format_time_tts(time(15, 30)) == "15 Uhr 30"
- def test_format_duration_tts(self):
- """TTS-Formatierung fuer Dauer."""
- assert _format_duration_tts(30) == "30 Sekunden"
- assert _format_duration_tts(1) == "1 Sekunde"
- assert _format_duration_tts(60) == "1 Minute"
- assert _format_duration_tts(300) == "5 Minuten"
- assert _format_duration_tts(3600) == "1 Stunde"
- assert _format_duration_tts(7200) == "2 Stunden"
- assert _format_duration_tts(5400) == "1 Stunde 30 Minuten"
- # ============================================================================
- # Integration: Matcher + Entities
- # ============================================================================
- class TestMatcherIntegration:
- """Integrationstests: KeywordMatcher mit Entity-System."""
- @pytest.fixture
- def entity_matcher(self, registry):
- """Matcher mit Entity-Registry."""
- from trixy_core.nlp.keyword_matcher import KeywordIntentMatcher
- m = KeywordIntentMatcher()
- m.set_entity_registry(registry)
- return m
- def test_datum_slot_match(self, entity_matcher):
- """Pattern mit {date:datum} sollte 'morgen' matchen."""
- entity_matcher.compile_intents([{
- "name": "weather_forecast",
- "patterns": ["wetter {date:datum}"],
- }])
- result = entity_matcher.match("Wetter morgen")
- assert result is not None
- assert result.intent == "weather_forecast"
- assert "date" in result.slots
- slot = result.slots["date"]
- assert isinstance(slot, ResolvedEntity)
- assert slot.entity_type == "datum"
- assert slot.value == date.today() + timedelta(days=1)
- def test_datum_slot_strikt_ablehnung(self, entity_matcher):
- """Pattern mit {date:datum} sollte unbekannte Werte ablehnen."""
- entity_matcher.compile_intents([{
- "name": "weather_forecast",
- "patterns": ["wetter {date:datum}"],
- }])
- result = entity_matcher.match("Wetter xyz")
- assert result is None
- def test_stadt_slot_offen(self, entity_matcher):
- """Pattern mit {city:stadt} sollte beliebige Werte akzeptieren."""
- entity_matcher.compile_intents([{
- "name": "current_weather",
- "patterns": ["wetter [in] {city:stadt}"],
- }])
- result = entity_matcher.match("Wetter in Tokyo")
- assert result is not None
- assert "city" in result.slots
- slot = result.slots["city"]
- assert isinstance(slot, ResolvedEntity)
- assert slot.value == "tokyo"
- def test_stadt_slot_bekannter_wert(self, entity_matcher):
- """Bekannter Stadtwert sollte gematcht werden."""
- entity_matcher.compile_intents([{
- "name": "current_weather",
- "patterns": ["wetter [in] {city:stadt}"],
- }])
- result = entity_matcher.match("Wetter in Berlin")
- assert result is not None
- assert "city" in result.slots
- def test_dauer_slot_match(self, entity_matcher):
- """Pattern mit {duration:dauer} sollte '5 minuten' matchen."""
- entity_matcher.compile_intents([{
- "name": "set_timer",
- "patterns": ["timer {duration:dauer}"],
- }])
- result = entity_matcher.match("Timer 5 Minuten")
- assert result is not None
- assert "duration" in result.slots
- slot = result.slots["duration"]
- assert isinstance(slot, ResolvedEntity)
- assert slot.value == 300
- def test_slot_ohne_entity_typ(self, entity_matcher):
- """Slot ohne Entity-Typ sollte wie bisher als String funktionieren."""
- entity_matcher.compile_intents([{
- "name": "volume_set",
- "patterns": ["lautstaerke [auf] {level}"],
- }])
- result = entity_matcher.match("Lautstärke auf 50")
- assert result is not None
- assert "level" in result.slots
- assert isinstance(result.slots["level"], str)
- def test_pattern_mit_entity_typ_parse(self, entity_matcher):
- """Slot mit Entity-Typ sollte korrekt geparst werden."""
- from trixy_core.nlp.keyword_matcher import PatternToken
- tokens = entity_matcher._parse_pattern("{date:datum}")
- assert len(tokens) == 1
- assert tokens[0].kind == "slot"
- assert tokens[0].slot_name == "date"
- assert tokens[0].entity_type == "datum"
- def test_dauer_slot_strikt_ablehnung(self, entity_matcher):
- """Pattern mit {duration:dauer} sollte 'xyz' ablehnen."""
- entity_matcher.compile_intents([{
- "name": "set_timer",
- "patterns": ["timer {duration:dauer}"],
- }])
- result = entity_matcher.match("Timer xyz")
- assert result is None
- def test_wetter_morgen_resolved_date(self, entity_matcher):
- """'Wetter morgen' → ResolvedEntity mit date.today() + 1."""
- entity_matcher.compile_intents([{
- "name": "weather_forecast",
- "patterns": ["wetter {date:datum}"],
- }])
- result = entity_matcher.match("Wetter morgen")
- assert result is not None
- assert result.intent == "weather_forecast"
- slot = result.slots["date"]
- assert isinstance(slot, ResolvedEntity)
- tomorrow = date.today() + timedelta(days=1)
- assert slot.value == tomorrow, (
- f"'morgen' sollte {tomorrow.isoformat()} sein, "
- f"aber ist {slot.value}"
- )
- def test_wetter_montag_resolved_date(self, entity_matcher):
- """'Wetter Montag' → ResolvedEntity mit naechstem Montag."""
- entity_matcher.compile_intents([{
- "name": "weather_forecast",
- "patterns": ["wetter {date:datum}"],
- }])
- result = entity_matcher.match("Wetter Montag")
- assert result is not None
- slot = result.slots["date"]
- assert isinstance(slot, ResolvedEntity)
- assert slot.value.weekday() == 0 # Montag
- assert slot.value > date.today()
- def test_wie_wird_am_montag_das_wetter(self, entity_matcher):
- """'Wie wird am Montag das Wetter' → weather_forecast mit Montag."""
- entity_matcher.compile_intents([{
- "name": "weather_forecast",
- "patterns": [
- "wie wird {das|} [am] {date:datum} [das] wetter",
- "wie wird [das] wetter [am] {date:datum}",
- ],
- }, {
- "name": "current_weather",
- "patterns": [
- "(wie ist|wie wird) [denn] {das|} wetter [draussen|gerade|heute]",
- ],
- }])
- result = entity_matcher.match("Wie wird am Montag das Wetter")
- assert result is not None
- assert result.intent == "weather_forecast"
- slot = result.slots["date"]
- assert isinstance(slot, ResolvedEntity)
- assert slot.value.weekday() == 0 # Montag
- assert slot.value > date.today()
- # ============================================================================
- # Hilfsfunktionen fuer Tests
- # ============================================================================
- def _get_month_name(month: int) -> str:
- """Gibt den deutschen Monatsnamen (lowercase) fuer Tests zurueck."""
- names = [
- "", "januar", "februar", "maerz", "april", "mai", "juni",
- "juli", "august", "september", "oktober", "november", "dezember",
- ]
- return names[month]
|