Technical Design · Anceva Bot · v0.1

Телеграм-бот для Центра Коррекции Речи — архитектура, UX, админка

Сопровождающий документ к ANALYSIS.html. Описывает, как именно устроен бот изнутри, из чего состоит админка, какие кнопки видят клиент и администратор, как ведётся диалог, как передаётся человеку, как разворачивается и что считать. Дальше работаем по этому документу.

Python 3.12 aiogram 3 Gemini 2 Flash + Pro SQLite → Postgres FastAPI + htmx Railway deploy 2 бота · Mini App · без WhatsApp

01Обзор системы

Что строим в одном абзаце и в одной картинке.

Telegram-бот + веб-админка + (опционально) второй «пульт»-бот для админа в Telegram. LLM внутри — Gemini Flash для рабочего диалога, Gemini Pro на сложные ветви. БД хранит клиентов, детей, диалоги, сообщения, брони, оплаты, шаблоны, задачи. Планировщик гоняет напоминания и follow-up. Админка даёт веб-интерфейс для Маши и Агнии. Хэндофф «бот ↔ человек» — одной кнопкой в обе стороны.

1
клиентский бот
принимает родителей в Telegram
1
веб-админка
FastAPI + htmx
1
админ-бот
мобильный «пульт» в Telegram
~12
таблиц БД
core domain

02Стек и ключевые решения

СлойЧто берёмАльтернативаПочему так
ЯзыкPython 3.12Node.js, Goбыстрая разработка, лучшая экосистема под LLM и телегу
Telegramaiogram 3python-telegram-bot, aiohttpasync, FSM, dispatcher из коробки, активная поддержка
LLMgoogle-generativeai + Flash / ProOpenAIбиллинг у заказчика, щедрый free tier, нативная мультимодалка
БД (MVP)SQLite + WALсразу Postgresноль инфраструктуры на старте, легко бэкапить, миграция через Alembic
БД (prod)Postgres 16при росте — нужны FTS, параллелизм, бэкапы
ORMSQLAlchemy 2 + Alembicpeewee, raw SQLстандарт, миграции, тайпы
PydanticPydantic v2dataclassesвалидация + структурированный вывод LLM
Админка backendFastAPIFlask, Djangoasync, тайпы, один язык с ботом
Админка frontendhtmx + Tailwind + Alpine.jsReact SPAбез сборки, быстро, один разработчик справляется
Авторизация админаTelegram Login Widgetemail/passwordадмины уже в Telegram, нет паролей для утечки
Фоновые задачи (MVP)APScheduler в том же процессеCeleryна старте нагрузка мизерная
Фоновые задачи (prod)Celery + RedisRQ, Dramatiqпри росте — надо отдельный воркер
Хранение медиаtelegram file_id + SQLite BLOB для мини-кэшаS3, локальный FSTelegram хранит бесплатно, file_id вечный
ДеплойRailway (~$5/мес)Fly.io, VPSwebhook держится 24/7, простой CI
CI/CDGitHub Actionsавто-деплой на push в main
ОшибкиSentry (free tier)без этого в проде не выжить
Логиstructlog в stdoutELKRailway пишет stdout в свою консоль
Форматтерruff + blackстандарт 2026
Тестыpytest + pytest-asyncioстандарт
Render не берём На бесплатном тарифе засыпает через 15 мин бездействия. Для Telegram-webhook это смертельно. Railway / Fly.io держат контейнер 24/7 за ~$5/мес.

03Архитектура

Какие процессы крутятся и как они общаются.

flowchart LR TG[Telegram API] -- webhook --> BOT[Bot Process
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, общается с Gemini
  • admin_bot — внутренний бот для Маши/Агнии, long-polling в параллельном процессе
  • web — FastAPI, отдаёт админку, дёргает те же сервисы
  • scheduler — один процесс APScheduler, запускает follow-up, напоминания, триггеры

На MVP все четыре живут в одном Railway-контейнере через supervisord или honcho. При росте — разнесём.

Общие модули

  • domain/ — Pydantic-модели, бизнес-правила (цены, перерасчёты, статусы)
  • db/ — SQLAlchemy модели + Alembic миграции
  • llm/ — обёртка Gemini, промты, structured output
  • dialog/ — 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Схема БД

erDiagram CLIENTS ||--o{ CHILDREN : "у родителя" CLIENTS ||--o{ DIALOGS : "ведёт" CLIENTS ||--o{ PAYMENTS : "платит" DIALOGS ||--|{ MESSAGES : "состоит из" CHILDREN ||--o{ DIAGNOSTICS : "проходит" CHILDREN ||--o{ LESSONS : "посещает" CHILDREN ||--o| ANKETAS : "заполнена 1 раз" SPECIALISTS ||--o{ LESSONS : "ведёт" SPECIALISTS ||--o{ DIAGNOSTICS : "проводит" ADMINS ||--o{ DIALOGS : "берёт" TASKS }o--|| CLIENTS : "на клиента" TEMPLATES ||--o{ MESSAGES : "шаблон"

Основные таблицы

ТаблицаКлючевые поляНазначение
clientsid, tg_user_id, phone, name, source, state, locale, referrer_client_id, created_at, last_seen_atродитель или подросток
childrenid, client_id, full_name, dob, gender, home_language, main_direction, assigned_specialist_id, statusотдельно от клиента — у одной мамы может быть двое
anketaschild_id, filled_at, answers JSON, videos[], docs[]заполняется 1 раз перед ДИ
dialogsid, client_id, started_at, mode (bot/admin), taken_by_admin_id, taken_at, gemini_context JSON, current_stateодин родитель = один долгий диалог
messagesid, dialog_id, direction, sender, tg_message_id, type, content, media_file_id, media_transcript, extracted JSON, created_atкаждое сообщение в обе стороны
diagnosticsid, child_id, scheduled_at, specialist_id, status, price, payment_status, anketa_received, videos_received, result_noteДИ — разовое событие
lessonsid, child_id, scheduled_at, specialist_id, room, duration_min, price, statusрегулярные занятия
paymentsid, client_id, child_id, period (YYYY-MM), amount, method (cash/kaspi), status, kaspi_check_file_id, paid_atмесячные счета + чеки
specialistsid, name, directions[], language, timetable, activeпедагоги центра
adminsid, tg_user_id, name, role (owner/admin/specialist), permissionsкто имеет доступ
tasksid, client_id, fire_at, kind, payload JSON, status, done_atочередь follow-up, напоминаний, триггеров
templatesid, slug, title, body, placeholders[], categoryтексты из чек-листа, редактируемые из админки
broadcastsid, created_by, segment_filter JSON, body, status, statsмассовые рассылки
audit_logid, admin_id, action, payload, atчто админ сделал и когда
price_tiersid, direction, format, duration, per_week, price, valid_from, valid_toверсионный прайс
Важно children — отдельно от clients, потому что у одной мамы часто двое-трое детей ходят (Бейсембековы, Бутовы, Гимазетдиновы в корпусе). И наоборот — за одного ребёнка в чат пишут и мама, и бабушка, и муж. Один ребёнок = одна карточка, родителей может быть несколько.

06Состояния диалога (FSM)

На уровне высокого state machine. Внутри — LLM генерирует реплики, но роутер знает, где мы в воронке.

stateDiagram-v2 [*] --> greeting greeting --> got_parent_name: имя получено got_parent_name --> got_age: возраст got_age --> branching branching --> collecting: ветка выбрана collecting --> presenting_di: собрано достаточно presenting_di --> picking_slot: родитель согласен picking_slot --> getting_child_info getting_child_info --> invoicing invoicing --> awaiting_check awaiting_check --> booked: чек принят booked --> sent_anketa sent_anketa --> anketa_filled: форма получена anketa_filled --> pre_reminder pre_reminder --> di_done: ДИ прошла di_done --> active: начал ходить active --> paused: болезнь/отпуск paused --> active active --> lost: 30 дней молчания lost --> returning: сам написал returning --> active branching --> escalated: сложный диагноз collecting --> escalated: эмоциональный кризис awaiting_check --> escalated: оплата не пришла 48ч escalated --> admin_mode admin_mode --> any_state: админ отпустил

Переходы — как триггерятся

  • По событию от пользователя — пришло сообщение, нажата кнопка
  • По таймеру — scheduler тикает, проверяет условия, переводит состояние
  • По админу — админ взял диалог / отпустил / пометил «закрыть»
  • По правилу LLM — Gemini возвращает next_state в structured output, роутер переводит

07Жизненный цикл одного сообщения

  1. Telegram webhook прилетел в botUpdate попал в aiogram dispatcher, middlewares подтянули client, dialog из БД
  2. Проверка режима диалогаЕсли dialog.mode = admin — сохраняем сообщение в БД, не вызываем LLM, пушим уведомление админу
  3. Media → транскриптЕсли голос/фото/видео — вызов Gemini multimodal, результат кладём в message.media_transcript
  4. Сбор контекстаБерём последние N сообщений диалога + карточку клиента + дерево состояний → формируем prompt
  5. Вызов GeminiFlash в 95% случаев; Pro — если сложная ветвь (заикание, ДЦП, аутизм). Structured output: {reply, extracted, next_state, escalate, confidence}
  6. Проверка эскалацииЕсли escalate=true или сработало регулярное правило — меняем mode на admin, шлём карточку в админ-бот, отвечаем клиенту заглушкой «передаю специалисту»
  7. Сохранение извлечённых данныхParent name / age / direction / concerns записываются в children, clients, dialogs.gemini_context
  8. Отправка ответаLLM-реплика (1-3 коротких сообщения), при необходимости inline-кнопки (слоты, «оплатить», «подтвердить»)
  9. Обновление состоянияdialog.current_state = next_state; при смене — триггер задач для планировщика (напоминание, follow-up)
  10. Аудитmessage saved, structlog пишет в stdout, при ошибке — Sentry

08Бот со стороны клиента — что он видит

Первый вход · /start

Центр Коррекции Речи Марии Анцевой приветствует Вас 🌿 Я виртуальный помощник. Помогу записать ребёнка на диагностику, подобрать формат занятий и ответить на вопросы. Подскажите, как к Вам обращаться?

Inline-кнопки дают быстрый путь в нужную ветку. Если родитель проигнорирует и напишет просто «Здравствуйте, сыну 2 года не говорит» — бот всё равно поймёт и пойдёт дальше по стандартной ветке.

Подбор слота

Айгерим, подобрал для вас ближайшие окна для диагностики. Выберите удобное:

Оплата и бронь

Чтобы закрепить время за вами, нужна предоплата по Kaspi. Сумма: 9 000 ₸
[отправлено фото чека]
Благодарим! Оплата получена, бронь закреплена 🌿 Наш адрес: Ерубаева 68/2, 3 этаж, 301 кабинет Ссылка на 2ГИС

Анкета через Telegram Mini App

Имя, ещё один важный этап — анкета ребёнка. Заполняется в одном окне, 5-7 минут. Нужна к четвергу, до 10:30.

Кнопка открывает Telegram Web App — форма прямо внутри мессенджера. После заполнения бот получает JSON и пишет «спасибо, данные получены». Попутно в форме — загрузка видео и фото заключений.

Меню активного клиента

Когда ребёнок уже ходит, у клиента в боте появляется persistent menu с основными действиями. Доступно в любое время через кнопку меню Telegram:

📅 Занятие завтра в 14:30. Подтвердите, пожалуйста.
📅 Расписание 💳 Оплаты 📝 Перенести 👤 Позвать админа

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

4
Новые заявки
сегодня
7
В работе
ждут ответа
3
Диагностики
завтра
2
Эскалации
нужно внимание
Ниже — лента эскалаций, сегодняшние занятия, просроченные оплаты
  • KPI сверху: заявки сегодня, в работе, диагностики завтра, эскалации, оплаты просроченные, загрузка специалистов
  • Лента событий (реалтайм через htmx-SSE): «+ новая заявка», «+ оплата получена», «! эскалация»
  • Быстрые действия: «Рассылка сегодня», «Добавить клиента вручную», «Открыть Inbox»

11.2 Inbox — чаты

Айгерим
Здравствуйте, сыну 1,8…
Гульзира ⚠
Эскалация: сложный запрос
Татьяна
Мария Сергеевна, это мама Миланы…

Фильтры

  • Все / Новые / Без ответа / Эскалированные / Мои / Возвращенцы / Спящие
  • По направлению (логопед, дефектолог, ПШ, М+М, АФК)
  • По специалисту
  • Поиск по имени и телефону

Кнопки в строке

  • Открыть — переход к диалогу (view-only, бот продолжает)
  • Взять — хэндофф, mode=admin, бот замолкает
  • Пометить спамом — скрыть из inbox
  • Быстрый ответ — inline, без перехода

11.3 Экран одного чата

Лента сообщений · mode: bot 🤖
[здесь переписка клиент ↔ бот, прокрутка, медиа с превью]
Айгерим
Ребёнок: Имат, 1,8
Запрос: запуск речи
Стадия: picking_slot
  • Лента сообщений слева, карточка клиента справа (имя, ребёнок, запрос, стадия, история)
  • Кнопка «Взять диалог» — одна, большая, видно сразу
  • Быстрые действия: шаблон, счёт, слот, открыть анкету
  • Внизу — поле ввода; при 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-бот для Маши и Агнии. Не веб-админка, а быстрые операции с телефона без захода в браузер.

Задачи

  • Получать уведомления о важных событиях (эскалация, оплата, просрочка)
  • Видеть и брать активные диалоги с клиентами
  • Отвечать клиенту через бота, не открывая веб
  • Быстрые команды для частых вопросов

Уведомления

🔔 Эскалация Айгерим (новый клиент) Сын 1,8 · запрос: запуск речи Последнее: «не знаю, куда бежать 😔» Стадия: collecting_context

Команды

/todayСегодняшние занятия и диагностики
/tomorrowЗавтрашние — для подтверждений
/inboxСписок чатов, требующих ответа
/escalationsАктивные эскалации
/unpaidПросроченные оплаты
/broadcastЗапустить рассылку по шаблону
/client <имя>Карточка клиента — summary
/statsКороткие цифры за сегодня

Ответ клиенту из админ-бота

Когда Маша «взяла» диалог, она просто пишет текст в админ-бот — он пересылает клиенту через клиентского бота. Клиент видит один и тот же аккаунт, ничего не меняется. Для Маши это выглядит как обычная переписка в Telegram. Возврат боту — кнопка «Отдать боту».

Клиент: Айгерим (взят вами 5 мин назад)
Айгерим: Мы на 10 минут опоздаем, пробки…
Хорошо, ждём, не переживайте 🌿

13Хэндофф: бот ↔ человек

sequenceDiagram autonumber participant U as Клиент (Telegram) participant B as Клиентский бот participant DB as БД participant AB as Админ-бот participant A as Админ (Маша) U->>B: «Психиатр сказал аутизм, не знаю куда бежать 😔» B->>DB: save message B->>B: LLM: escalate=true, reason="эмоциональный + сложный диагноз" B->>DB: dialog.mode = admin B->>AB: push эскалация с карточкой AB->>A: 🔔 уведомление B->>U: «Я сейчас передам Марии Сергеевне, она свяжется в течение дня 🌿» A->>AB: жмёт «Взять» AB->>DB: taken_by_admin_id = A.id U->>B: новое сообщение B->>DB: save (no LLM response) B->>AB: пересылка админу A->>AB: пишет ответ AB->>B: ответ B->>U: текст от «имени бота» A->>AB: «Отдать боту» AB->>DB: dialog.mode = bot Note over B: LLM подхватывает всё, включая реплики админа B->>U: продолжает диалог

Триггеры автоматической эскалации

  • Слова-маркеры: «жалоба», «вернуть деньги», «отказаться», «куда бежать»
  • Эмоциональный тон (определяет 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В анкету/анамнез ребёнка
Чек KaspiOCR суммы, даты, номераGemini VisionАвто-матчинг с ожидаемой оплатой
PDF документыИзвлечение текстаGemini с PDF inputЗаключения, выписки

Хранение

  • Не качаем файлы на диск, храним telegram.file_id
  • Транскрипт/описание/OCR результат — в messages.media_transcript
  • По клику в админке — ре-рендерим через Telegram file API (live preview)
  • Исключение: видео для ДИ — качаем на диск и храним 30 дней (потом удаляем), чтобы специалист мог спокойно смотреть

15LLM-слой

Что выбираем и когда

МодельГде используемПримечание
gemini-2.0-flash95% диалогов — квалификация, ответы, подбор слотаБесплатный лимит, мультимодальность
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:00SQLite 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-ответы логируются полностью (для анализа качества)
Чего боимся Утечка чатов (ПД родителей и детей). Защита: приватная БД, закрытый доступ, SSO через Telegram, регулярные бэкапы на внешнее хранилище с шифрованием. Никаких клиентских данных в публичном git или публичных отчётах.

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, без медиа-обработки
stagingRailway previewОтдельный бот-токен, отдельная БД, проверка перед релизом
productionRailway 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, общая БД.
HostingRailway $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 Маши на старте или можно оставить только веб-админку

Technical Design v0.1 · Anceva Bot · сопровождающий документ к ANALYSIS.html
Последнее обновление: