# Copyright (c) 2026 Krzysztof Rudnicki. MIT licence. """What the bot notices about a day, and what it stays quiet about.""" from __future__ import annotations from typing import TYPE_CHECKING from signal_bot.stats import curiosities from signal_bot.stats.facts import DayFacts, gather from signal_bot.stats.highlights import Highlight, ratio, select from signal_bot.stats.queries_day import day_rows, with_silent from tests.stats_seed import ALA, BASIA, GROUP, KUHY, Shape, wrote if TYPE_CHECKING: from signal_bot.store import Store def facts_for(store: Store, day: str, roster: list[tuple[str, str]] | None = None) -> DayFacts: """Gather a day the way publish_report does.""" rows = with_silent(day_rows(store.events, GROUP, day), roster or []) return gather(store.events, GROUP, day, rows) def test_day_one_notices_nothing(store: Store) -> None: """Almost every candidate has no history to stand against yet.""" wrote(store, "2026-09-01", KUHY, 3) fired = [c for c in curiosities.for_day(facts_for(store, "2026-09-01")) if c] assert [c.kind for c in fired] == ["only_one_spoke"] def test_facts_expose_the_days_crown_holders(store: Store) -> None: wrote(store, "2026-09-01", ALA, 4) wrote(store, "2026-09-01", KUHY, 4) wrote(store, "2026-09-01", BASIA, 1) assert sorted(n for _, n in facts_for(store, "2026-09-01").leaders) == ["Ala", "kuhy"] assert facts_for(store, "2026-09-09").leaders == [] def test_a_personal_record_reports_what_it_beat(store: Store) -> None: wrote(store, "2026-09-01", ALA, 5) wrote(store, "2026-09-02", KUHY, 1) wrote(store, "2026-09-02", ALA, 9) found = curiosities.personal_record(facts_for(store, "2026-09-02")) assert found is not None assert "Ala 9 — rekord życiowy (poprzedni 5, 1 września)" in found.text def test_a_first_ever_day_is_not_announced_as_a_record(store: Store) -> None: """Everybody's first day is their best; saying so every time is noise.""" wrote(store, "2026-09-01", ALA, 5) assert curiosities.personal_record(facts_for(store, "2026-09-01")) is None def test_the_busiest_day_ever(store: Store) -> None: wrote(store, "2026-09-01", KUHY, 3) wrote(store, "2026-09-02", KUHY, 9) found = curiosities.group_record_day(facts_for(store, "2026-09-02")) assert found is not None assert "najgadatliwszy dzień w historii: 9 wiadomości" in found.text def test_a_quiet_day_is_not_a_record(store: Store) -> None: wrote(store, "2026-09-01", KUHY, 9) wrote(store, "2026-09-02", KUHY, 3) assert curiosities.group_record_day(facts_for(store, "2026-09-02")) is None def test_a_media_record_needs_more_than_one_photo(store: Store) -> None: wrote(store, "2026-09-01", KUHY, 3, Shape(media=1)) assert curiosities.media_record(facts_for(store, "2026-09-01")) is None wrote(store, "2026-09-02", KUHY, 4, Shape(media=3)) found = curiosities.media_record(facts_for(store, "2026-09-02")) assert found is not None assert "rekord zdjęć: 3" in found.text def test_a_crown_streak_needs_two_days(store: Store) -> None: wrote(store, "2026-09-01", ALA, 5) wrote(store, "2026-09-01", KUHY, 1) assert curiosities.crown_streak(facts_for(store, "2026-09-01")) is None wrote(store, "2026-09-02", ALA, 5) wrote(store, "2026-09-02", KUHY, 1) found = curiosities.crown_streak(facts_for(store, "2026-09-02")) assert found is not None assert "Ala z koroną 2 dni z rzędu" in found.text def test_a_shared_crown_names_everyone(store: Store) -> None: for day in ("2026-09-01", "2026-09-02"): wrote(store, day, ALA, 4) wrote(store, day, KUHY, 4) found = curiosities.crown_streak(facts_for(store, "2026-09-02")) assert found is not None assert "Ala i kuhy z koroną" in found.text def test_breaking_a_silence(store: Store) -> None: wrote(store, "2026-09-01", BASIA, 2) for day in ("2026-09-02", "2026-09-03", "2026-09-04"): wrote(store, day, KUHY, 3) wrote(store, "2026-09-05", BASIA, 1) wrote(store, "2026-09-05", KUHY, 9) found = curiosities.silence_broken(facts_for(store, "2026-09-05")) assert found is not None assert "Basia odezwał się po 3 dni ciszy" in found.text def test_a_short_gap_is_not_worth_mentioning(store: Store) -> None: wrote(store, "2026-09-01", BASIA, 2) wrote(store, "2026-09-02", KUHY, 3) wrote(store, "2026-09-03", BASIA, 2) assert curiosities.silence_broken(facts_for(store, "2026-09-03")) is None def test_a_monologue(store: Store) -> None: wrote(store, "2026-09-01", KUHY, 7) found = curiosities.only_one_spoke(facts_for(store, "2026-09-01")) assert found is not None assert "cały dzień mówił tylko kuhy" in found.text def test_one_single_message_is_not_a_monologue(store: Store) -> None: wrote(store, "2026-09-01", KUHY, 1) assert curiosities.only_one_spoke(facts_for(store, "2026-09-01")) is None def test_dominating_the_day(store: Store) -> None: wrote(store, "2026-09-01", ALA, 8) wrote(store, "2026-09-01", KUHY, 2) found = curiosities.dominant_share(facts_for(store, "2026-09-01")) assert found is not None assert "Ala to 80% dzisiejszych wiadomości" in found.text def test_an_evenly_shared_day_is_not_dominated(store: Store) -> None: wrote(store, "2026-09-01", ALA, 5) wrote(store, "2026-09-01", KUHY, 4) wrote(store, "2026-09-01", BASIA, 4) assert curiosities.dominant_share(facts_for(store, "2026-09-01")) is None def test_a_weekday_needs_a_baseline_before_it_can_be_unusual(store: Store) -> None: """Two Tuesdays are not an average worth quoting.""" wrote(store, "2026-09-01", KUHY, 5) wrote(store, "2026-09-08", KUHY, 50) assert curiosities.weekday_average(facts_for(store, "2026-09-08")) is None def test_an_unusual_weekday(store: Store) -> None: for day in ("2026-09-01", "2026-09-08", "2026-09-15"): wrote(store, day, KUHY, 10) wrote(store, "2026-09-22", KUHY, 30) found = curiosities.weekday_average(facts_for(store, "2026-09-22")) assert found is not None assert "wtorki mają średnio 10 — dziś o 200% więcej" in found.text def test_a_typical_weekday_says_nothing(store: Store) -> None: for day in ("2026-09-01", "2026-09-08", "2026-09-15", "2026-09-22"): wrote(store, day, KUHY, 10) assert curiosities.weekday_average(facts_for(store, "2026-09-22")) is None def test_a_quiet_weekday_reads_as_fewer(store: Store) -> None: for day in ("2026-09-01", "2026-09-08", "2026-09-15"): wrote(store, day, KUHY, 20) wrote(store, "2026-09-22", KUHY, 5) found = curiosities.weekday_average(facts_for(store, "2026-09-22")) assert found is not None assert "mniej" in found.text def test_a_night_owl(store: Store) -> None: wrote(store, "2026-09-01", KUHY, 3, Shape(hour=1)) found = curiosities.night_owl(facts_for(store, "2026-09-01")) assert found is not None assert "kuhy pisał o 01:00" in found.text def test_an_ordinary_evening_is_not_a_night_owl(store: Store) -> None: wrote(store, "2026-09-01", KUHY, 3, Shape(hour=22)) assert curiosities.night_owl(facts_for(store, "2026-09-01")) is None def test_one_line_per_family(store: Store) -> None: """Three sentences about the same person are one fact, not three.""" picks = select( [ Highlight("a", "voice", 60, 1.0, "A"), Highlight("b", "voice", 50, 1.0, "B"), Highlight("c", "record", 40, 1.0, "C"), ], 3, ) assert [p.kind for p in picks] == ["a", "c"] def test_selection_is_ordered_and_bounded() -> None: picks = select( [ Highlight("low", "one", 10, 1.0, ""), None, Highlight("high", "two", 90, 1.0, ""), Highlight("mid", "three", 50, 1.0, ""), ], 2, ) assert [p.kind for p in picks] == ["high", "mid"] def test_ties_break_on_name_so_a_day_renders_the_same_twice() -> None: picks = select( [ Highlight("zebra", "one", 50, 1.0, ""), Highlight("alpha", "two", 50, 1.0, ""), ], 2, ) assert [p.kind for p in picks] == ["alpha", "zebra"] def test_nothing_to_say_is_an_empty_list() -> None: assert select([None, None], 3) == [] def test_ratio_survives_a_zero_baseline() -> None: assert ratio(5, 0) == 5.0 assert ratio(10, 5) == 1.0