diff --git a/app/src/core/system_status.py b/app/src/core/system_status.py
new file mode 100644
index 0000000..3bc652a
--- /dev/null
+++ b/app/src/core/system_status.py
@@ -0,0 +1,91 @@
+from __future__ import annotations
+
+import platform
+from dataclasses import dataclass
+
+from src.core.constants import APP_NAME, APP_VERSION
+from src.core.config import load_settings
+
+
+@dataclass(slots=True)
+class ComponentStatus:
+ name: str
+ state: str
+ details: str
+
+
+@dataclass(slots=True)
+class SystemSnapshot:
+ app_name: str
+ app_version: str
+ app_env: str
+ python_version: str
+ os_name: str
+ timezone_name: str
+ components: list[ComponentStatus]
+
+
+def get_system_snapshot() -> SystemSnapshot:
+ settings = load_settings()
+
+ components = [
+ ComponentStatus(
+ name="Бот",
+ state="🟢 работает",
+ details="Процесс бота запущен и обрабатывает команды.",
+ ),
+ ComponentStatus(
+ name="Telegram",
+ state="🟢 OK",
+ details="Polling активен, базовая маршрутизация подключена.",
+ ),
+ ComponentStatus(
+ name="Биржа",
+ state="🟡 не подключена",
+ details="Интеграция с биржей будет добавлена на следующем этапе.",
+ ),
+ ComponentStatus(
+ name="База данных",
+ state="🟡 не подключена",
+ details="Слой хранения пока только подготовлен структурно.",
+ ),
+ ]
+
+ return SystemSnapshot(
+ app_name=APP_NAME,
+ app_version=APP_VERSION,
+ app_env=settings.app_env,
+ python_version=platform.python_version(),
+ os_name=f"{platform.system()} {platform.release()}",
+ timezone_name=settings.tz,
+ components=components,
+ )
+
+
+def build_system_text() -> str:
+ snapshot = get_system_snapshot()
+
+ component_lines = []
+ for component in snapshot.components:
+ component_lines.append(
+ f"{component.state} {component.name}\n"
+ f"— {component.details}"
+ )
+
+ components_block = "\n\n".join(component_lines)
+
+ return (
+ "⚙️ Система\n\n"
+ "Статус компонентов\n"
+ f"{components_block}\n\n"
+ "Окружение\n"
+ f"- приложение: {snapshot.app_name} {snapshot.app_version}\n"
+ f"- env: {snapshot.app_env}\n"
+ f"- python: {snapshot.python_version}\n"
+ f"- os: {snapshot.os_name}\n"
+ f"- timezone: {snapshot.timezone_name}\n\n"
+ "Справка\n"
+ "/start — стартовый экран\n"
+ "/menu — показать меню\n"
+ "/help — открыть системную справку"
+ )
diff --git a/app/src/telegram/handlers/start.py b/app/src/telegram/handlers/start.py
index 5b2f50c..0b9dfab 100644
--- a/app/src/telegram/handlers/start.py
+++ b/app/src/telegram/handlers/start.py
@@ -1,9 +1,12 @@
+from __future__ import annotations
+
from aiogram import F, Router
from aiogram.filters import Command
from aiogram.types import Message
+from src.core.system_status import build_system_text
from src.telegram.keyboards.reply import build_main_menu_keyboard
-from src.telegram.menus import MAIN_MENU_TEXT, SYSTEM_TEXT
+from src.telegram.menus import MAIN_MENU_TEXT
router = Router(name="start")
@@ -11,19 +14,31 @@ router = Router(name="start")
@router.message(Command("start"))
async def cmd_start(message: Message) -> None:
- await message.answer(MAIN_MENU_TEXT, reply_markup=build_main_menu_keyboard())
+ await message.answer(
+ MAIN_MENU_TEXT,
+ reply_markup=build_main_menu_keyboard(),
+ )
@router.message(Command("menu"))
async def cmd_menu(message: Message) -> None:
- await message.answer(MAIN_MENU_TEXT, reply_markup=build_main_menu_keyboard())
+ await message.answer(
+ MAIN_MENU_TEXT,
+ reply_markup=build_main_menu_keyboard(),
+ )
@router.message(Command("help"))
async def cmd_help(message: Message) -> None:
- await message.answer(SYSTEM_TEXT, reply_markup=build_main_menu_keyboard())
+ await message.answer(
+ build_system_text(),
+ reply_markup=build_main_menu_keyboard(),
+ )
@router.message(F.text == "Меню")
async def menu_shortcut(message: Message) -> None:
- await message.answer(MAIN_MENU_TEXT, reply_markup=build_main_menu_keyboard())
+ await message.answer(
+ MAIN_MENU_TEXT,
+ reply_markup=build_main_menu_keyboard(),
+ )
diff --git a/app/src/telegram/handlers/system.py b/app/src/telegram/handlers/system.py
index 5d9b22b..6548cca 100644
--- a/app/src/telegram/handlers/system.py
+++ b/app/src/telegram/handlers/system.py
@@ -1,10 +1,9 @@
-import platform
+from __future__ import annotations
from aiogram import F, Router
from aiogram.types import Message
-from src.core.constants import APP_NAME, APP_VERSION
-from src.telegram.menus import SYSTEM_TEXT
+from src.core.system_status import build_system_text
router = Router(name="system")
@@ -12,10 +11,4 @@ router = Router(name="system")
@router.message(F.text.in_({"⚙️ Система", "⚙ Система"}))
async def open_system(message: Message) -> None:
- runtime_info = (
- "\n\nRuntime\n"
- f"- app: {APP_NAME} {APP_VERSION}\n"
- f"- python: {platform.python_version()}\n"
- f"- os: {platform.system()} {platform.release()}"
- )
- await message.answer(SYSTEM_TEXT + runtime_info)
+ await message.answer(build_system_text())
diff --git a/docs/decisions/0004-system-screen.md b/docs/decisions/0004-system-screen.md
new file mode 100644
index 0000000..67e90f9
--- /dev/null
+++ b/docs/decisions/0004-system-screen.md
@@ -0,0 +1,14 @@
+# 0004 — System Screen
+
+## Решение
+Сделать `Система` отдельным системным экраном, который показывает не только справку, но и снимок состояния приложения.
+
+## Причины
+- это удобно для диагностики
+- это удобно для локальной разработки
+- это удобно для проверки на Synology
+- это закладывает основу для health checks
+
+## Последствия
+- справка больше не живет отдельно от системного экрана
+- системный экран становится точкой входа для технического контроля проекта
diff --git a/docs/stages/stage-02-system.md b/docs/stages/stage-02-system.md
new file mode 100644
index 0000000..d41205c
--- /dev/null
+++ b/docs/stages/stage-02-system.md
@@ -0,0 +1,46 @@
+# Stage 02 — System
+
+## Цель
+Сделать раздел `⚙️ Система` реальным центром контроля приложения, а не просто статическим экраном.
+
+## Что добавляется
+- сервис `system_status`, который собирает снимок состояния приложения
+- единый текст системы, который используется и кнопкой `⚙️ Система`, и командой `/help`
+- отображение:
+ - статуса бота
+ - статуса Telegram-слоя
+ - статуса интеграции с биржей
+ - статуса слоя хранения
+ - текущего окружения
+
+## Что должен увидеть пользователь
+Экран вида:
+
+- Бот: работает
+- Telegram: OK
+- Биржа: не подключена
+- База данных: не подключена
+- env / python / os / timezone
+
+## Почему это важно
+На следующих этапах сюда будут добавляться:
+- ping биржи
+- статус WebSocket
+- статус БД
+- статус Redis
+- последние ошибки
+- health checks
+
+## Как проверить
+1. Запустить бота локально
+2. Открыть Telegram
+3. Нажать `⚙️ Система`
+4. Проверить команду `/help`
+5. Убедиться, что экран одинаково полезен из кнопки и из команды
+
+## Commit message
+Рекомендуемый commit:
+
+```bash
+git commit -m "stage 02 system status screen"
+```