From e4b1b1e510001b4aa41564c25ec9c0404245f7a0 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 20 Oct 2026 17:05:52 +0200 Subject: [PATCH] feat: render full analytics as tables --- VERSION | 2 +- analytics.py | 28 +++++++++++++++++++++++----- bot.py | 2 +- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/VERSION b/VERSION index b51d2a4..216edd8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0+65 +0.1.0+66 diff --git a/analytics.py b/analytics.py index faa868c..46070d6 100644 --- a/analytics.py +++ b/analytics.py @@ -127,12 +127,30 @@ def all_metrics(period: str = "all", db_path: str | None = None) -> dict[str, ob return result -def render_report(period: str = "day", db_path: str | None = None) -> str: +def _html_table(headers: list[str], rows: list[list[object]]) -> str: + header = "".join(f"{h}" for h in headers) + body = "".join("" + "".join(f"{v}" for v in row) + "" for row in rows) + return f"{header}{body}
" + + +def render_report(period: str = "all", db_path: str | None = None) -> str: metrics = all_metrics(period, db_path) types = metrics["message_types"] generation_types = {k[1]: v for k, v in types.items() if k[0] == "generation"} training_types = {k[1]: v for k, v in types.items() if k[0] == "training"} - def line(label: str, value: object) -> str: - return f"{label}: {value}" - rows = ["

📊 Статистика бота

", f"

Период: {period}

", "

Пользователи

", line("Пользователей", metrics["users"]), line("Активных", metrics["active_users"]), "

Генерация

", line("Постов", metrics["post_generations"]), line("Идей", metrics["idea_generations"]), line("Правок", metrics["post_revisions"]), line("Сообщений", metrics["generation_messages"]), line("Типы", ", ".join(f"{k}: {v}" for k, v in generation_types.items()) or "нет"), "

Обучение

", line("Сообщений", metrics["training_messages"]), line("Типы", ", ".join(f"{k}: {v}" for k, v in training_types.items()) or "нет")] - return "

" + "
".join(rows) + "

" + users = metrics["users"] + active = metrics["active_users"] + user_rows = [["Всего", users], ["Активные", active]] + activity_rows = [["Генерации постов", metrics["post_generations"]], ["Идеи", metrics["idea_generations"]], ["Правки постов", metrics["post_revisions"]], ["Сообщения", metrics["generation_messages"]]] + generation_type_rows = [[key, value] for key, value in sorted(generation_types.items())] or [["Нет данных", 0]] + training_type_rows = [[key, value] for key, value in sorted(training_types.items())] or [["Нет данных", 0]] + training_rows = [["Сообщения", metrics["training_messages"]]] + return ( + "

📊 Полная статистика бота

" + f"

Период: {period}

" + "

Пользователи

" + _html_table(["Показатель", "Количество"], user_rows) + + "

Генерация

" + _html_table(["Показатель", "Количество"], activity_rows) + + "

Типы сообщений в генерации

" + _html_table(["Тип", "Количество"], generation_type_rows) + + "

Обучение

" + _html_table(["Показатель", "Количество"], training_rows) + + "

Типы сообщений в обучении

" + _html_table(["Тип", "Количество"], training_type_rows) + ) diff --git a/bot.py b/bot.py index 5f5f75c..72a494d 100644 --- a/bot.py +++ b/bot.py @@ -1529,7 +1529,7 @@ async def stats_command(message: Message, state: FSMContext) -> None: return if OWNER_USER_ID and (not message.from_user or message.from_user.id != OWNER_USER_ID): return - period = ((message.text or "").split(maxsplit=1)[1] if len((message.text or "").split()) > 1 else "day") + period = ((message.text or "").split(maxsplit=1)[1] if len((message.text or "").split()) > 1 else "all") report = await asyncio.to_thread(analytics.render_report, period) if message.bot is None: return