"""Sentence completion: phrases from the user's own sent mail, plus starters.""" from __future__ import annotations from collections import Counter from app.completion import build_phrases, choose, complete, html_to_text, own_text def personal(*texts: str) -> dict[str, Counter[str]]: out: dict[str, Counter[str]] = {} for (ctx, cont), n in build_phrases(texts).items(): out.setdefault(ctx, Counter())[cont] = n return out def test_own_text_drops_quotes_and_signature() -> None: for quote in ( "On Mon, 5 Oct 2026 Anna wrote:", "W dniu 5.10.2026 Anna pisze:", "Am 05.10.2026 um 10:00 schrieb Anna:", "-----Original Message-----", "From: Anna ", "--", ): text = f"Sure, I'll send it today.\n\n{quote}\nOld text here." assert own_text(text).strip() == "Sure, I'll send it today.", quote assert own_text("Yes.\n> quoted line\nAgreed.") == "Yes.\nAgreed." def test_only_repeated_phrases_are_kept() -> None: phrases = build_phrases( ["I'll send the drawings by Friday.", "I'll send the drawings by Friday."] ) assert phrases[("the", "drawings by Friday.")] == 2 once = build_phrases(["A private sentence written once."]) assert once == {} def test_personal_phrases_complete_in_any_language() -> None: mine = personal( "I'll send the drawings by Friday.", "I'll send the drawings by Friday.", "Prześlę rysunki do piątku.", "Prześlę rysunki do piątku.", ) assert complete(mine, "I'll send the dr") == "awings by Friday." assert complete(mine, "Prześlę rys") == "unki do piątku." assert complete(mine, "Prześlę rysunki ") == "do piątku." def test_starter_phrases_and_spacing() -> None: assert complete({}, "I look forward ") == "to hearing from you." assert complete({}, "I look forward") == " to hearing from you." # space added assert complete({}, "Z pow") == "ażaniem," assert complete({}, "Mit freundlichen ") == "Grüßen" def test_nothing_after_punctuation_or_on_an_empty_line() -> None: assert complete({}, "Thanks,") is None assert complete({}, "Done. ") is None assert complete({}, "") is None assert complete({}, " ") is None def test_suggestion_stops_where_habits_part() -> None: # "see you" is followed by "soon" or "tomorrow" equally: suggest only "you". assert choose(Counter({"you": 4, "you soon": 2, "you tomorrow": 2})) == "you" assert choose(Counter({"you": 4, "you soon": 4, "you soon then": 3})) == "you soon then" def test_polish_gmail_quotes_and_own_signature_are_left_out() -> None: text = ( "Dziękuję, prześlę jutro.\n" "Imaginary Pixels – visual effects studio\n" "pon., 5 paź 2026 o 10:00 Anna Nowak napisał(a):\n" "> stary tekst" ) sig = html_to_text( "
Imaginary Pixels – visual effects studio
+48 602 691 231
" ) assert own_text(text, [sig]).strip() == "Dziękuję, prześlę jutro." def test_numbers_addresses_and_links_are_never_learned() -> None: texts = ["Call me at +48 602 691 231 or write to me@x.example today."] * 3 learned = " ".join(f"{c} {n}" for (c, n) in build_phrases(texts)) assert "602" not in learned and "@" not in learned assert ("call me", "at") in build_phrases(texts)