01Обзор системы
Что строим в одном абзаце и в одной картинке.
Telegram-бот + веб-админка + (опционально) второй «пульт»-бот для админа в Telegram. LLM внутри — Gemini Flash для рабочего диалога, Gemini Pro на сложные ветви. БД хранит клиентов, детей, диалоги, сообщения, брони, оплаты, шаблоны, задачи. Планировщик гоняет напоминания и follow-up. Админка даёт веб-интерфейс для Маши и Агнии. Хэндофф «бот ↔ человек» — одной кнопкой в обе стороны.
02Стек и ключевые решения
| Слой | Что берём | Альтернатива | Почему так |
|---|---|---|---|
| Язык | Python 3.12 | Node.js, Go | быстрая разработка, лучшая экосистема под LLM и телегу |
| Telegram | aiogram 3 | python-telegram-bot, aiohttp | async, FSM, dispatcher из коробки, активная поддержка |
| LLM | google-generativeai + Flash / Pro | OpenAI | биллинг у заказчика, щедрый free tier, нативная мультимодалка |
| БД (MVP) | SQLite + WAL | сразу Postgres | ноль инфраструктуры на старте, легко бэкапить, миграция через Alembic |
| БД (prod) | Postgres 16 | — | при росте — нужны FTS, параллелизм, бэкапы |
| ORM | SQLAlchemy 2 + Alembic | peewee, raw SQL | стандарт, миграции, тайпы |
| Pydantic | Pydantic v2 | dataclasses | валидация + структурированный вывод LLM |
| Админка backend | FastAPI | Flask, Django | async, тайпы, один язык с ботом |
| Админка frontend | htmx + Tailwind + Alpine.js | React SPA | без сборки, быстро, один разработчик справляется |
| Авторизация админа | Telegram Login Widget | email/password | админы уже в Telegram, нет паролей для утечки |
| Фоновые задачи (MVP) | APScheduler в том же процессе | Celery | на старте нагрузка мизерная |
| Фоновые задачи (prod) | Celery + Redis | RQ, Dramatiq | при росте — надо отдельный воркер |
| Хранение медиа | telegram file_id + SQLite BLOB для мини-кэша | S3, локальный FS | Telegram хранит бесплатно, file_id вечный |
| Деплой | Railway (~$5/мес) | Fly.io, VPS | webhook держится 24/7, простой CI |
| CI/CD | GitHub Actions | — | авто-деплой на push в main |
| Ошибки | Sentry (free tier) | — | без этого в проде не выжить |
| Логи | structlog в stdout | ELK | Railway пишет stdout в свою консоль |
| Форматтер | ruff + black | — | стандарт 2026 |
| Тесты | pytest + pytest-asyncio | — | стандарт |
03Архитектура
Какие процессы крутятся и как они общаются.
aiogram + handlers] TG -.- ABOT[Admin Bot Process
aiogram pull] ADMIN[Admin Browser] -- HTTPS --> WEB[FastAPI
admin backend] BOT --> DB[(SQLite / Postgres)] ABOT --> DB WEB --> DB BOT --> LLM[Gemini API
Flash + Pro] BOT --> TG ABOT --> TG WEB --> TG SCH[Scheduler
APScheduler] --> DB SCH --> TG SCH --> BOT TG -.media.-> BOT BOT --> SENTRY[Sentry] WEB --> SENTRY classDef svc fill:#eef0ff,stroke:#3a52d6,color:#1b1f24 classDef ext fill:#fff,stroke:#747d88,color:#1b1f24 classDef store fill:#e4f3ea,stroke:#1f9254,color:#1b1f24 class BOT,ABOT,WEB,SCH svc class TG,LLM,SENTRY ext class DB store
Процессы
bot— клиентский бот, webhook-ом принимает апдейты Telegram, общается с Geminiadmin_bot— внутренний бот для Маши/Агнии, long-polling в параллельном процессеweb— FastAPI, отдаёт админку, дёргает те же сервисыscheduler— один процесс APScheduler, запускает follow-up, напоминания, триггеры
На MVP все четыре живут в одном Railway-контейнере через supervisord или honcho. При росте — разнесём.
Общие модули
domain/— Pydantic-модели, бизнес-правила (цены, перерасчёты, статусы)db/— SQLAlchemy модели + Alembic миграцииllm/— обёртка Gemini, промты, structured outputdialog/— FSM, роутер по состояниям, сборка промтаtemplates/— хранимые тексты из чек-листаintegrations/— Kaspi, Calendar (позже)common/— логгер, конфиг, ошибки
04Модули и файловая структура
anceva_bot/
├─ app/
│ ├─ bot/
│ │ ├─ __init__.py
│ │ ├─ main.py # запуск клиентского бота
│ │ ├─ handlers/
│ │ │ ├─ start.py # /start, онбординг
│ │ │ ├─ dialog.py # основной поток текстовых сообщений
│ │ │ ├─ media.py # голосовые, фото, видео, документы
│ │ │ ├─ callbacks.py # inline-кнопки (перенос, подтверждение, оплата)
│ │ │ ├─ commands.py # /cancel, /admin, /menu
│ │ │ └─ payment.py # приём чека Kaspi
│ │ └─ middlewares/
│ │ ├─ throttle.py # rate limit
│ │ ├─ client_ctx.py # подгрузка client из БД
│ │ └─ admin_mode.py # если mode=admin — не отвечаем, только сохраняем
│ │
│ ├─ admin_bot/
│ │ ├─ main.py
│ │ └─ handlers/ # уведомления о хэндоффе, /сегодня, /завтра и т.д.
│ │
│ ├─ web/
│ │ ├─ main.py # FastAPI приложение
│ │ ├─ routers/
│ │ │ ├─ auth.py # Telegram Login
│ │ │ ├─ dashboard.py
│ │ │ ├─ inbox.py
│ │ │ ├─ clients.py
│ │ │ ├─ schedule.py
│ │ │ ├─ payments.py
│ │ │ ├─ broadcasts.py
│ │ │ ├─ templates.py
│ │ │ └─ settings.py
│ │ ├─ templates/ # Jinja2 шаблоны (htmx)
│ │ └─ static/ # Tailwind output, немного JS
│ │
│ ├─ dialog/
│ │ ├─ states.py # enum состояний FSM
│ │ ├─ router.py # выбирает handler по state
│ │ ├─ tree.py # дерево сценариев по возрасту
│ │ ├─ extractor.py # Gemini structured output → dict
│ │ └─ escalation.py # правила эскалации админу
│ │
│ ├─ llm/
│ │ ├─ client.py # обёртка google-generativeai
│ │ ├─ prompts/ # системные промты, шаблоны
│ │ │ ├─ master.md
│ │ │ ├─ by_direction/
│ │ │ └─ extract_schema.py
│ │ └─ multimodal.py # обработка audio/image/video
│ │
│ ├─ domain/
│ │ ├─ models.py # Pydantic: Client, Child, Dialog, Message
│ │ ├─ pricing.py # расчёт стоимости, перерасчёты
│ │ ├─ calendar.py # слоты, окна специалистов
│ │ └─ rules.py # бизнес-правила (1 перенос бесплатно и т.п.)
│ │
│ ├─ db/
│ │ ├─ models.py # SQLAlchemy
│ │ ├─ session.py
│ │ └─ repositories/ # доступ к данным по сущностям
│ │
│ ├─ scheduler/
│ │ ├─ main.py # APScheduler loop
│ │ └─ jobs/
│ │ ├─ reminders.py # за 24ч до занятия
│ │ ├─ followup.py # 24/48/7 дней молчания
│ │ ├─ monthly_invoices.py # 1 число — счета
│ │ └─ seasonal.py # ДР, 8 марта, Наурыз
│ │
│ ├─ integrations/
│ │ ├─ kaspi.py # ссылка, парсинг чека
│ │ └─ calendar.py # Google Calendar (позже)
│ │
│ └─ common/
│ ├─ config.py # pydantic-settings из .env
│ ├─ logger.py # structlog
│ └─ errors.py
│
├─ migrations/ # Alembic
├─ tests/
├─ scripts/ # одноразовые утилиты
├─ pyproject.toml
├─ .env.example
├─ Procfile # web, bot, admin_bot, scheduler
└─ README.md
05Схема БД
Основные таблицы
| Таблица | Ключевые поля | Назначение |
|---|---|---|
clients | id, tg_user_id, phone, name, source, state, locale, referrer_client_id, created_at, last_seen_at | родитель или подросток |
children | id, client_id, full_name, dob, gender, home_language, main_direction, assigned_specialist_id, status | отдельно от клиента — у одной мамы может быть двое |
anketas | child_id, filled_at, answers JSON, videos[], docs[] | заполняется 1 раз перед ДИ |
dialogs | id, client_id, started_at, mode (bot/admin), taken_by_admin_id, taken_at, gemini_context JSON, current_state | один родитель = один долгий диалог |
messages | id, dialog_id, direction, sender, tg_message_id, type, content, media_file_id, media_transcript, extracted JSON, created_at | каждое сообщение в обе стороны |
diagnostics | id, child_id, scheduled_at, specialist_id, status, price, payment_status, anketa_received, videos_received, result_note | ДИ — разовое событие |
lessons | id, child_id, scheduled_at, specialist_id, room, duration_min, price, status | регулярные занятия |
payments | id, client_id, child_id, period (YYYY-MM), amount, method (cash/kaspi), status, kaspi_check_file_id, paid_at | месячные счета + чеки |
specialists | id, name, directions[], language, timetable, active | педагоги центра |
admins | id, tg_user_id, name, role (owner/admin/specialist), permissions | кто имеет доступ |
tasks | id, client_id, fire_at, kind, payload JSON, status, done_at | очередь follow-up, напоминаний, триггеров |
templates | id, slug, title, body, placeholders[], category | тексты из чек-листа, редактируемые из админки |
broadcasts | id, created_by, segment_filter JSON, body, status, stats | массовые рассылки |
audit_log | id, admin_id, action, payload, at | что админ сделал и когда |
price_tiers | id, direction, format, duration, per_week, price, valid_from, valid_to | версионный прайс |
children — отдельно от clients, потому что у одной мамы часто двое-трое детей ходят (Бейсембековы, Бутовы, Гимазетдиновы в корпусе). И наоборот — за одного ребёнка в чат пишут и мама, и бабушка, и муж. Один ребёнок = одна карточка, родителей может быть несколько.06Состояния диалога (FSM)
На уровне высокого state machine. Внутри — LLM генерирует реплики, но роутер знает, где мы в воронке.
Переходы — как триггерятся
- По событию от пользователя — пришло сообщение, нажата кнопка
- По таймеру — scheduler тикает, проверяет условия, переводит состояние
- По админу — админ взял диалог / отпустил / пометил «закрыть»
- По правилу LLM — Gemini возвращает
next_stateв structured output, роутер переводит
07Жизненный цикл одного сообщения
- Telegram webhook прилетел в botUpdate попал в aiogram dispatcher, middlewares подтянули
client,dialogиз БД - Проверка режима диалогаЕсли
dialog.mode = admin— сохраняем сообщение в БД, не вызываем LLM, пушим уведомление админу - Media → транскриптЕсли голос/фото/видео — вызов Gemini multimodal, результат кладём в
message.media_transcript - Сбор контекстаБерём последние N сообщений диалога + карточку клиента + дерево состояний → формируем prompt
- Вызов GeminiFlash в 95% случаев; Pro — если сложная ветвь (заикание, ДЦП, аутизм). Structured output:
{reply, extracted, next_state, escalate, confidence} - Проверка эскалацииЕсли
escalate=trueили сработало регулярное правило — меняем mode на admin, шлём карточку в админ-бот, отвечаем клиенту заглушкой «передаю специалисту» - Сохранение извлечённых данныхParent name / age / direction / concerns записываются в
children,clients,dialogs.gemini_context - Отправка ответаLLM-реплика (1-3 коротких сообщения), при необходимости inline-кнопки (слоты, «оплатить», «подтвердить»)
- Обновление состояния
dialog.current_state = next_state; при смене — триггер задач для планировщика (напоминание, follow-up) - Аудитmessage saved, structlog пишет в stdout, при ошибке — Sentry
08Бот со стороны клиента — что он видит
Первый вход · /start
Inline-кнопки дают быстрый путь в нужную ветку. Если родитель проигнорирует и напишет просто «Здравствуйте, сыну 2 года не говорит» — бот всё равно поймёт и пойдёт дальше по стандартной ветке.
Подбор слота
Оплата и бронь
Анкета через Telegram Mini App
Кнопка открывает Telegram Web App — форма прямо внутри мессенджера. После заполнения бот получает JSON и пишет «спасибо, данные получены». Попутно в форме — загрузка видео и фото заключений.
Меню активного клиента
Когда ребёнок уже ходит, у клиента в боте появляется persistent menu с основными действиями. Доступно в любое время через кнопку меню Telegram:
09Команды и кнопки бота
Команды (slash)
| Команда | Что делает |
|---|---|
/start | Старт или перезапуск диалога; онбординг |
/menu | Показать клавиатуру действий |
/admin | «Позвать живого администратора» — эскалация |
/schedule | Показать график занятий ребёнка |
/payment | Текущий счёт и история |
/cancel | Отменить текущее действие (например, заполнение анкеты) |
/help | Короткая справка |
Inline-кнопки (по ходу диалога)
| Контекст | Кнопки |
|---|---|
| Старт | Я родитель · Я подросток |
| Выбор слота | 3 даты + «Другие варианты» |
| Оплата | Перейти к Kaspi · Прислать чек |
| Анкета | Открыть анкету (Mini App) |
| Напоминание о занятии | Буду · Не смогу · Опоздаю на N мин |
| Перенос | Дни недели · Отмена |
| Прощание/отказ | Напишите, как будет возможность 🌿 · Позвать админа |
Persistent reply keyboard — активные клиенты
┌──────────────┬──────────────┐
│ 📅 Расписание │ 💳 Оплаты │
├──────────────┼──────────────┤
│ 📝 Перенести │ ❓ Вопрос │
├──────────────┴──────────────┤
│ 👤 Позвать администратора │
└─────────────────────────────┘
Эта клавиатура появляется после завершения онбординга и первой оплаты. Новым клиентам на этапе квалификации — не показываем, чтобы не отвлекать.
10Админка — общий взгляд
Веб-панель на FastAPI + htmx. Работает в любом браузере, включая мобильный. Авторизация через Telegram Login Widget — один клик, без паролей.
Принципы
- Одно окно — admin не переключается между чатами WhatsApp, Google Sheets и тетрадкой. Всё здесь
- Любая операция — один клик — выставить счёт, перенести, отправить напоминание, забрать диалог у бота
- Оптимизировано под мобильный — Маша смотрит с телефона. Экран — адаптив от 320px
- Не заставляем набирать текст зря — шаблоны, автозаполнение, reply-с-подсказкой
- Аудит всего — кто что сделал и когда. Роли: owner, admin, specialist (видит только своих)
11Экраны админки — по каждому
11.1 Dashboard
- KPI сверху: заявки сегодня, в работе, диагностики завтра, эскалации, оплаты просроченные, загрузка специалистов
- Лента событий (реалтайм через htmx-SSE): «+ новая заявка», «+ оплата получена», «! эскалация»
- Быстрые действия: «Рассылка сегодня», «Добавить клиента вручную», «Открыть Inbox»
11.2 Inbox — чаты
Фильтры
- Все / Новые / Без ответа / Эскалированные / Мои / Возвращенцы / Спящие
- По направлению (логопед, дефектолог, ПШ, М+М, АФК)
- По специалисту
- Поиск по имени и телефону
Кнопки в строке
- Открыть — переход к диалогу (view-only, бот продолжает)
- Взять — хэндофф, mode=admin, бот замолкает
- Пометить спамом — скрыть из inbox
- Быстрый ответ — inline, без перехода
11.3 Экран одного чата
- Лента сообщений слева, карточка клиента справа (имя, ребёнок, запрос, стадия, история)
- Кнопка «Взять диалог» — одна, большая, видно сразу
- Быстрые действия: шаблон, счёт, слот, открыть анкету
- Внизу — поле ввода; при mode=bot подсказка «это уйдёт от имени бота». Опционально — «сначала забрать диалог»
- Клавиши / — вставить шаблон, Shift+Enter — новая строка, Enter — отправить
11.4 Клиенты
- Таблица всех клиентов/детей с фильтром и поиском
- Статусы: новый · запись · активный · пауза · спящий · возвращенец · отток
- Клик по строке — карточка: анкета, специалист, расписание, оплаты, история диалога, медиа
- Кнопки: «Написать», «Сменить специалиста», «Сменить время», «Поставить на паузу», «Архивировать»
11.5 Расписание
- Календарь по неделе × специалистам; drag-and-drop для переноса
- Фильтры: по специалисту, по кабинету, по направлению
- Клик по слоту — детали занятия/диагностики; кнопки «Перенести», «Отменить», «Написать родителю»
- Кнопка «Предложить слот» — генерирует список свободных окон для конкретного ребёнка и шлёт через бота
11.6 Оплаты
- Календарь месяца: план, факт, задолженность
- Кнопка «Сформировать счета на {месяц}» — генерирует по всем активным клиентам (с учётом перерасчётов, правил) и показывает preview
- Каждый счёт — «Отправить» / «Править сумму» / «Добавить комментарий»
- Приём чека — автоматически матчится по сумме и периоду; если не матчится — попадает в «требует ручного»
- Напоминания: неоплатившим 5 числа автоматом уходит шаблон; 7 числа — повторно; 10 — эскалация админу
11.7 Рассылки
- Сегменты: все активные · ПШ · М+М · индивидуальные · спящие 30+ · возвращенцы
- Шаблон текста + предпросмотр на тестовом чате
- Кнопка «Отправить» — двойное подтверждение, аудит
- Статистика: доставлено / прочитано / ответили
- Готовые сценарии: «Утренник», «Смена расписания», «8 марта», «Наурыз», «Изменение прайса»
11.8 Шаблоны
- Все тексты из чек-листа — редактируемые (приветствие, описание ДИ по направлениям, правила переноса, прайс, оплата, анкета-напоминание)
- Placeholders:
{parent_name},{child_name},{slot},{amount} - Версионность — откат к предыдущей версии
- Тестовая отправка себе в Telegram
11.9 Настройки
- Специалисты — карточки, активность, расписание, направления, язык
- Прайс — таблица с действующими тарифами, возможность «с {дата} новый тариф»
- Правила — сроки переноса, отработок, перерасчёта (меняется редко, но должно быть в UI)
- Роли и доступы — добавить админа, дать/снять права
- Интеграции — ключи Gemini, Kaspi-ссылка, webhook url
12Админ-бот — пульт в кармане
Отдельный Telegram-бот для Маши и Агнии. Не веб-админка, а быстрые операции с телефона без захода в браузер.
Задачи
- Получать уведомления о важных событиях (эскалация, оплата, просрочка)
- Видеть и брать активные диалоги с клиентами
- Отвечать клиенту через бота, не открывая веб
- Быстрые команды для частых вопросов
Уведомления
Команды
/today | Сегодняшние занятия и диагностики |
/tomorrow | Завтрашние — для подтверждений |
/inbox | Список чатов, требующих ответа |
/escalations | Активные эскалации |
/unpaid | Просроченные оплаты |
/broadcast | Запустить рассылку по шаблону |
/client <имя> | Карточка клиента — summary |
/stats | Короткие цифры за сегодня |
Ответ клиенту из админ-бота
Когда Маша «взяла» диалог, она просто пишет текст в админ-бот — он пересылает клиенту через клиентского бота. Клиент видит один и тот же аккаунт, ничего не меняется. Для Маши это выглядит как обычная переписка в Telegram. Возврат боту — кнопка «Отдать боту».
13Хэндофф: бот ↔ человек
Триггеры автоматической эскалации
- Слова-маркеры: «жалоба», «вернуть деньги», «отказаться», «куда бежать»
- Эмоциональный тон (определяет LLM: confidence score)
- Сложные / противоречивые диагнозы (аутизм, ДЦП, шизофрения)
- Прямой запрос: «хочу поговорить с человеком»
- Клиент распознан как возвращенец (матч по tg_id/phone)
- LLM вернул
confidence < 0.5три раза подряд - Оплата ДИ — сумма в чеке не совпала с ожидаемой
- Молчание после брони >24ч
- Подросток 14-16 пишет сам
Что происходит при mode=admin
- Клиентский бот принимает сообщения клиента, но не зовёт LLM
- Все сообщения всё равно сохраняются в БД и пересылаются в админ-бот
- Админ пишет ответ — он уходит через клиентского бота, клиент видит один и тот же аккаунт
- Когда админ жмёт «Отдать» — мы не вызываем LLM сразу, ждём нового сообщения клиента
- На следующем сообщении клиента LLM получает полный контекст, включая то, что писал админ
14Обработка медиа
| Тип | Что делаем | Как | Зачем |
|---|---|---|---|
| Голосовое | Транскрибируем + понимаем содержание | Gemini Flash с audio input (inline) | В чек-листе сказано исключить голосовые, но родители их шлют массово. Бот читает и продолжает диалог текстом. |
| Фото ребёнка | Описание, сохранение | Gemini Vision → краткое описание | Показать специалисту перед ДИ |
| Видео ребёнка | Сохранение + ключевые кадры | Gemini multimodal → описание поведения/речи | Ценнейший материал для диагностики |
| Фото заключения врача | OCR + извлечение диагнозов | Gemini Vision с запросом на JSON | В анкету/анамнез ребёнка |
| Чек Kaspi | OCR суммы, даты, номера | Gemini Vision | Авто-матчинг с ожидаемой оплатой |
| PDF документы | Извлечение текста | Gemini с PDF input | Заключения, выписки |
Хранение
- Не качаем файлы на диск, храним
telegram.file_id - Транскрипт/описание/OCR результат — в
messages.media_transcript - По клику в админке — ре-рендерим через Telegram file API (live preview)
- Исключение: видео для ДИ — качаем на диск и храним 30 дней (потом удаляем), чтобы специалист мог спокойно смотреть
15LLM-слой
Что выбираем и когда
| Модель | Где используем | Примечание |
|---|---|---|
gemini-2.0-flash | 95% диалогов — квалификация, ответы, подбор слота | Бесплатный лимит, мультимодальность |
gemini-2.0-flash-lite | Классификация коротких реплик (intent), извлечение полей | Ещё дешевле, быстрее |
gemini-2.5-pro | Сложные ветви — заикание, аутизм, ЗПРР/ЗРР с противоречиями, длинные кейсы | Используем экономно, платим |
Структурированный вывод — контракт
Каждый ответ LLM — JSON-объект, который роутер понимает и применяет к состоянию:
{
"reply": [
"Айгерим, спасибо, что написали 🌿",
"Подскажите, ребёнок понимает обращённую речь, когда просите что-то показать?"
],
"extracted": {
"parent_name": "Айгерим",
"child": {
"age_years": 1,
"age_months": 8,
"direction_hint": "speech_launch"
}
},
"next_state": "collecting_context",
"escalate": false,
"escalate_reason": null,
"confidence": 0.92,
"requested_media": ["video_free_play"]
}
Сборка промта — всегда одна формула
SYSTEM = master_prompt (правила, тон, запреты)
+ current_state (короткая инструкция что делать сейчас)
+ current_branch (возрастная ветка)
+ templates (актуальные шаблоны из БД)
+ pricing (текущие тарифы)
CONTEXT = client_card (имя, дети, история)
+ last_N_messages (обе стороны)
+ admin_notes (если админ что-то оставил)
USER = текущее сообщение клиента
OUTPUT_SCHEMA = JSON как выше
Защита от срывов
- Жёсткие правила в system prompt — «не называть цену до квалификации», «не ставить диагнозы»
- Post-processing фильтр — проверяет reply на запрещённые слова (конкретные цены не по правилам, медицинские диагнозы)
- Если фильтр срабатывает — не отправляем, эскалируем
- Rate limit на клиента — не больше N сообщений/минуту в обе стороны
- Sentry ловит все исключения и промты с
confidence < 0.3
16Планировщик — что крутится в фоне
| Задача | Когда | Действие |
|---|---|---|
| Follow-up 24ч | 24 часа после последнего сообщения клиента, если нет ответа | Мягкое напоминание: «Айгерим, удобно ли продолжить? 🌿» |
| Follow-up 48ч | 48 часов (если не ответили на первое) | «Если не получается сейчас — напишите, как будет возможно» |
| Реактивация 7 дней | 7 дней молчания | Помечаем lost, шлём финальное сообщение |
| Реактивация 30 дней | Для lost и paused | «Как Ваши дела? Готовы возобновить?» |
| Напоминание о занятии | За 24ч до занятия в 18:00 | Кнопки «Буду / Не смогу / Опоздаю» |
| Напоминание о ДИ | За 24ч + за 2 часа | Подтвердить, напомнить адрес |
| Напоминание об анкете | За 48ч до ДИ, если не заполнена | «Анкету не заполнили — пришлю ссылку ещё раз?» |
| Генерация счетов | 1 число каждого месяца, 10:00 | Считает занятия, создаёт записи в payments, ставит invoiced |
| Напоминание об оплате | 5 число 18:00; 7 число 12:00; 10 число эскалация | Шаблон + Kaspi-ссылка |
| ДР ребёнка | Утром в день рождения | Шаблон поздравления Маши |
| Сезонные | 8 марта, Наурыз, 1 сент, Новый год | Ручное одобрение в админке, потом рассылка |
| Чистка медиа | Ежедневно, 3:00 | Удалить скачанные видео старше 30 дней |
| Бэкап БД | Ежедневно, 4:00 | SQLite dump → S3 / Google Drive |
17Интеграции
MVP
- Telegram Bot API — webhook, medias, inline keyboards
- Gemini API — google-generativeai SDK
- Kaspi — один общий pay-link, приём чеков фото, OCR суммы
- Sentry — отлов ошибок
Фаза 2
- Google Calendar — двусторонняя синхронизация расписания специалистов
- Google Sheets — экспорт «ДЕТИ ДАТЫ» для совместимости с текущими таблицами Маши
- Kaspi Business API — если появится доступ, авто-трекинг входящих платежей
Фаза 3
- Instagram DM — Meta Business API, приём первого сообщения с переадресацией в Telegram
- 2ГИС — deeplink «написать в Telegram» в профиле
- WhatsApp — не делаем. Решено в рамках MVP оставить только Telegram.
Не делаем пока
- 1С / полноценная CRM — только если центр начнёт быстро расти
- Свои платежи (эквайринг) — Каспи покрывает
- Мобильное приложение — Telegram Mini App покрывает
18Безопасность и роли
Данные
- Все персональные данные родителей и детей — только в нашей БД
- Webhook Telegram — HTTPS only, secret token в заголовке
- API-ключи (Gemini, Sentry, Railway) — в
.env, не в git - БД шифруется на уровне провайдера (Railway Postgres)
- SQLite на старте — файл в персистентном volume Railway, бэкап ежедневно
- Медиа от клиентов —
file_idв БД, сами файлы живут в Telegram CDN
Роли
| Роль | Кто | Что может |
|---|---|---|
owner | Мария Сергеевна | Всё. Настройки, прайс, роли, рассылки, аналитика. |
admin | Агния и другие админы | Inbox, клиенты, расписание, оплаты. Рассылки — с одобрения owner. |
specialist | Педагоги | Только свои клиенты и только просмотр + комментарии. |
methodist | Маргарита Наильевна | Клиенты, анкеты, отчёты. Без финансов. |
Аудит
- Каждое действие админа — запись в
audit_log - Кто взял диалог, кто отправил рассылку, кто поменял прайс — видно
- Админка показывает «Изменено: Агния · 2 часа назад» на всех редактируемых полях
- LLM-ответы логируются полностью (для анализа качества)
19Деплой
Railway — один контейнер, четыре процесса
# Procfile
web: uvicorn app.web.main:app --host 0.0.0.0 --port $PORT
bot: python -m app.bot.main
admin_bot: python -m app.admin_bot.main
scheduler: python -m app.scheduler.main
- Railway хостит все процессы в одном контейнере через
honcho - Webhook Telegram смотрит на
<app>.railway.app/webhook/<secret> - Persistent volume 1 GB для SQLite + временных медиа
- Бэкап БД ежедневно в Google Drive (одноразовая настройка)
Окружения
| Env | Где | Что |
|---|---|---|
local | Мой ПК | SQLite + long polling, без медиа-обработки |
staging | Railway preview | Отдельный бот-токен, отдельная БД, проверка перед релизом |
production | Railway main | Основной бот, основная админка, бэкапы |
CI / CD
# .github/workflows/deploy.yml
on:
push:
branches: [main]
jobs:
test:
- ruff check
- pytest
deploy:
needs: test
- railway up --service anceva-bot
Push в main → тесты → авто-деплой. В staging — push в branch staging. Откат — одна команда railway rollback.
20Этапы работ
Фаза 0 · Подготовка · готово
- ✅ Анализ чатов и чек-листа
- ✅ ANALYSIS.html для Маши
- ✅ Этот документ — TECH.html
- → Согласовать объём MVP
- → Получить токены: Bot, Gemini, Sentry
Фаза 1 · MVP бот · 2–3 нед
- Проект skeleton, БД SQLite, миграции
- Клиентский бот (токен #1): приём, квалификация по дереву
- Админ-бот (токен #2): уведомления, хэндофф, команды
- LLM-слой: мастер-промт, structured output
- Мультимодальность: голос + фото + видео
- Telegram Mini App — анкета с согласием ПД и загрузкой медиа
- Kaspi-ссылка, приём чека (без OCR)
- Базовый scheduler: follow-up + напоминание
- Развёртывание на Railway, подключение домена
- Shadow mode первые 1-2 недели — все реплики через одобрение Маши
Фаза 2 · Админка · 1–2 нед
- FastAPI + htmx skeleton
- Auth через Telegram Login
- Inbox, экран чата, карточка клиента
- Расписание (простой календарь)
- Оплаты (генерация + отправка)
- Шаблоны (редактор)
- Рассылки
Фаза 3 · Операционка · 1–2 нед
- Миграция на Postgres
- Google Calendar sync
- OCR чеков Kaspi, авто-матчинг
- Сезонные триггеры и автопоздравления
- Реактивация спящих
- Подключение купленного домена (DNS + SSL на Railway)
Фаза 4 · Аналитика и рост
- Дашборд KPI
- Реферальная программа в боте
- A/B шаблонов
- Авто-аналитика диалогов (темы, тон, отказы)
- Интеграция Instagram / WhatsApp при необходимости
Фаза 5 · Опционально
- Полноценный Telegram Mini App (анкета → кабинет родителя)
- Экспорт отчётов для педагогов
- Автогенерация характеристик (черновик по занятиям)
- Персональные pay-link (уникальные на каждую оплату)
21Риски и открытые вопросы
Технические риски
- Лимиты Gemini Flash на free tier — 15 RPM, 1M TPM, 1500 RPD. При росте входящего потока можем упереться. Митигация: кэш повторяющихся шаблонных ответов, downgrade на flash-lite для классификации.
- Telegram rate limits — 30 msg/s в целом, 1 msg/s на одного пользователя. Для активных рассылок 200+ клиентов — батчинг.
- Сбои webhook — если контейнер уснёт, Telegram будет копить апдейты. Railway в этом плане надёжен, Render — нет.
- LLM-сбои — если Gemini упал, падаем в fallback «простите, вас передадим специалисту» + авто-эскалация.
Продуктовые риски
- Клиент сейчас в WhatsApp, не в Telegram. Если строим только TG — нужен переход. Либо интеграция WhatsApp через Green API (~$30/мес, работает через QR и ваш номер). Это главный вопрос к тебе и Маше.
- Маша может выключить бота. Любое неаккуратное сообщение родителю — и доверие утекает. Защита: все первые 20 диалогов — в shadow mode, бот пишет черновик, админ одобряет.
- Перерасчёты и финансы — зона тонкая. Если бот ошибётся в сумме — конфликт. На MVP все оплаты формируются, но отправка — только после одобрения админом.
Решено по MVP · 24 апреля 2026
| Вопрос | Решение | Последствия для разработки |
|---|---|---|
| WhatsApp в MVP | Нет только Telegram | Маркетинговая задача — разворачивать новые заявки в TG. Старые клиенты продолжают в WhatsApp вне бота. |
| Анкета | Telegram Mini App | Делаем TG Web App. Нужен HTTPS + домен. Форма — Alpine/Tailwind, submit → backend через initData. |
| Количество ботов | Два клиентский + админ-пульт | Два токена, два процесса aiogram, общая БД. |
| Hosting | Railway $5/мес | Webhook, persistent volume под SQLite, авто-деплой из GitHub. |
| Домен | Отдельный (покупается заказчиком) | Поддомены: admin.* — веб-админка, корень или app.* — Telegram Mini App. SSL через Let's Encrypt на Railway. |
| Согласие ПД | В Telegram Mini App | Чекбокс при первом открытии анкеты + ссылка на политику. Без согласия — форма не отправляется. Хранение: clients.pd_consent_at. |
| Первичный импорт | Нет с нуля | База стартует пустая. Функция «добавить клиента вручную» в админке — на случай, если старого клиента нужно ввести. |
Что из этого следует для плана работ
- WhatsApp-интеграция вычёркивается из фазы 3. Экономим ~3–5 дней разработки и ~$30/мес на Green API.
- Telegram Mini App — добавляется модуль
app/miniapp/:anketa.html,consent.html,kabinet.html(позже). Отдельный FastAPI роутер отдаёт страницы сinitData-валидацией. - Domain setup — как только домен куплен, прописываем DNS на Railway (CNAME), включаем SSL. Mini App требует https.
- Админ-бот — отдельный
BOT_TOKEN_ADMINв env, отдельныйsupervisord-процесс. - Правовая часть — политика ПД хранится в
templates, редактируется из админки, версионируется. Mini App подтягивает последнюю актуальную.
Остаются открытыми
- Точный бренд-нейм и ник клиентского бота в Telegram (зависит от домена)
- Будут ли у центра дополнительные админы, кроме Маши и Агнии — нужно для ролей
- Ставить ли бот в shadow mode на первые 1-2 недели (все реплики через одобрение Маши) — рекомендую
- Нужен ли экспорт в Google Sheets Маши на старте или можно оставить только веб-админку