3,5 месяца, 2 071 коммит, своя пекарня, два ИИ-ассистента и VPS с 2 ГБ RAM
У нас своя небольшая пекарня «D&K Sourdough». Заказы на хлеб долгое время принимались вручную — клиент пишет в Telegram‑группу, мы записываем, следим за тем, чтобы не набрать больше, чем сможем отпечь, потом вручную уведомляем по готовности. Хотелось это автоматизировать. У меня самого есть небольшой опыт в программировании — делал несколько приложений для себя на Delphi в лохматых 2000-х, писал Access приложения, даже напрограммировал систему автополива для теплицы на Arduino, которая работает вот уже 7 лет. И тут, узнав каких способностей уже достигли LLM, 19 января сделал initial commit: один файл bot.py, SQLite, три кнопки. Меню, корзина, клиенты делают заказ, мы получаем оповещение. Сначала все писал с чатом Deepseek и виндовым Copilotom, но потом узнал про агентов и все заверте…
Сейчас это ~91 000 строк Python, 143 React‑компонента, схема БД версии v105 и два работающих ИИ‑ассистента — всё на том же VPS с 2 ГБ RAM.
Неделя первая: система слотов
На следующий день после initial commit выяснилась первая нетривиальная вещь: ёмкость печи ограничена. Большой хлеб занимает 2 слота, маленький — 1, максимум 20 слотов на дату. Если 12 человек возьмут большой — ни печь ни мы не справимся физически.
Нужна oven_limits таблица с max_slots и used_slots, причём бронирование должно быть атомарным — иначе два человека могут одновременно взять последний слот, оба получат подтверждение и будут ждать хлеб, которого не будет.
Это первая конкурентная задача в проекте, и она сразу заставила думать о транзакциях.
За неделю бот обзавёлся корзиной с мульти-заказом, регистрацией пользователей, отдельной admin-панелью со сменой статусов заказов, уведомлениями в Telegram-группу («осталось 2 слота!», «всё разобрали!») и автовотчером для перезапуска при изменении файлов. Всё это был один класс BreadOrderBot на 1500 строк, но работало.
Февраль: из бота в продукт
REST API + React PWA
В начале февраля стало понятно, что Telegram — не единственный нужный интерфейс. Клиенты хотят видеть каталог хлеба как нормальный сайт, оформлять заказ без Telegram. Началась параллельная разработка: FastAPI поверх той же SQLite-базы и React 18.3 + TypeScript 5.6 + Vite 6.0 + Tailwind — фронтенд. JWT сначала в localStorage, потом аудит безопасности сказал «нет» — переехало в httpOnly cookie.
Деплоить vite build на сервер с 2 ГБ RAM было нельзя — OOM при активном боте и API. Выработали правило: всегда билдить локально, деплоить через scp. Правило соблюдается до сих пор.
cd Copilot/frontend && npm run build
scp -r dist/* root@server:/root/bread_bot/frontend/dist/
ssh root@server "systemctl restart bread-api"
PWA получила: каталог хлеба с фотографиями, корзину, историю заказов, личный кабинет, Web Push уведомления (VAPID), fullscreen-лайтбокс для фото хлеба, splash screen с реальным progress bar.
Аналитика и учёт затрат
Захотелось наконец понять, зарабатывает ли пекарня деньги или просто занята. Добавили модуль учёта затрат: FIFO-инвентаризация ингредиентов (закупки, расход, остатки), техкарты с рецептами, производственные партии от замеса до выпечки с разбивкой себестоимости на материалы + косвенные + амортизация, оборудование с линейной амортизацией, несколько кассовых счетов с переводами, кредиты с аннуитетными графиками.
Схема БД к концу февраля: v41 → v67. Каждая миграция — Python-функция migrationvNN().
Аудит безопасности
Когда появился полноценный API, провели первый аудит — 170 находок, 14 критических. Самые важные:
-
Race condition в бронировании слотов — два пользователя одновременно берут последний слот. Решение:
BEGIN IMMEDIATE+db_transaction()с передачейconn=в sub-функции. Паттернsave_order_atomic()стал эталоном для всех похожих операций. -
27 мест с
date.today()вместоtoday_local()— пекарня в UTC+11, сервер в UTC, разница 11 часов. В полночь по серверному времени ещё утро по сахалинскому, и заказы падали не в ту дату. -
TOCTOU в инвентаризации — чтение остатка и его списание должны быть в одной транзакции.
-
SQL injection через f-string в названиях таблиц — исправлено через whitelist.
Март: плагины и телефонная авторизация
Плагиновая архитектура
К марту бот разросся до 10 миксинов в BreadOrderBot и 6 в AdminPanel. Добавление новой фичи требовало редактировать несколько файлов одновременно. Сделали plugin system:
class Plugin(ABC):
@property
@abstractmethod
def name(self) -> str: ...
def register_handlers(self, dp, bot_context): ...
def register_admin_routes(self) -> dict: ...
def register_admin_menu_items(self) -> list: ...
def register_admin_text_handlers(self) -> dict: ...
def on_startup(self, bot_context): ...
def on_shutdown(self): ...
Сейчас активных плагинов 6: аналитика, отзывы, управление затратами, исторические заказы, инструменты разработчика, сообщения в группу.
Авторизация
OTP через Telegram-бот (6 цифр, TTL 5 минут), Telegram Login Widget для PWA, обычный пароль через bcrypt, rate limiting с lockout (5 попыток → блокировка на 5 минут).
Активность и когорты
Добавили полноценную аналитику поведения: когортный анализ удержания, воронка конверсии, сегменты клиентов, выручка по дням/неделям/месяцам, топ хлебов и клиентов.
Апрель: три ИИ-ассистента
Голосовой ассистент для администратора
Голосом текст набирается в три раза быстрее, чем печатанием. Хотелось голосом узнать статус заказов или изменить лимит печи. Пайплайн получился такой:
Голосовое сообщение (OGG) → Groq Whisper Large v3 Turbo (STT, ru)
→ Claude Haiku (intent parse с tool_use + снимок БД)
→ Кнопка «Да/Нет»
→ Атомарное выполнение в БД
Конфигурация минимальна — два API-ключа в .env: GROQ_API_KEY и ANTHROPIC_API_KEY.
Главная инженерная проблема: сервер с 2 ГБ RAM. Claude CLI-бинарник весит ~600 МБ и вызывает OOM при запуске рядом с работающими ботом и API. Решение: вместо CLI — Anthropic Python SDK напрямую, без лишнего процесса, прямо на VPS.
Василиса — клиентский чат-ассистент
Чат-бот «Василиса» в PWA для клиентов. Знает весь каталог хлеба с КБЖУ, ингредиентами и описаниями, видит доступные даты и остатки слотов, знает историю заказов конкретного клиента, может предложить хлеб в корзину через специальный маркер CART_PROPOSAL:{...}.
VASSILISA_PERSONA = (
"Ты Василиса — дружелюбный ассистент пекарни D&K Sourdough. 🍞n"
"Характер: говоришь как человек, которая сама влюблена в этот хлеб — тепло, "
"с искренним интересом, иногда с юмором. Не как справочник, а как друг-пекарь.n"
"Пол: ты девушка. ВСЕГДА говори о себе в женском роде."
)
Василиса ведёт долгосрочную память клиентов (customer_ai_memory_repo.py) — сводка по каждому пользователю, которая обновляется автоматически. Если клиент писал три недели назад и упоминал, что любит ржаной с тмином — Василиса это помнит.
Самое тонкое место — не вызов API, а системный промпт: 200 строк ограничений, которые предотвращают выдачу CART_PROPOSAL без явного называния хлеба, добавление в корзину на первом сообщении сессии, раскрытие себестоимости, prompt injection через текст меню.
Пока Василиса как фича ради фичи, ей особо никто не пользуется. При этом Стив, о котором пойдет речь ниже, мне очень помогает.
Стив — проактивный AI-агент
Стив – мой личный помощник в Телеграм. Работает по расписанию cron и сам инициирует действия. Не отвечает на вопросы — наблюдает и докладывает.
0 22 * * * python3 steve_proactive.py morning # Утренний брифинг
0 10 * * * python3 steve_proactive.py evening # Вечерняя сводка
0 23 * * 0 python3 steve_proactive.py weekly # Еженедельный аудит
0 19 1 * * python3 steve_proactive.py monthly # Месячная рефлексия
0 20,2,9 * * python3 steve_proactive.py code_review # Аудит кода 3 раза в день
Утром присылает сколько заказов, выручку и остатки слотов. Еженедельно анализирует тренды и сравнивает с прошлой неделей. Code review ищет bare except, прямые os.getenv(), большие файлы — присылает отчёт в Telegram. Раз в месяц анализирует рост продаж и предлагает изменения в ассортименте.
У Стива есть настроение, которое рассчитывается из сигналов бизнеса (растут продажи — energized, падают — concerned) и инжектируется в каждый промпт:
MOOD_TONES = {
"energized": "Ты полон энергии, наблюдения острее, язык живее",
"calm": "Ты спокоен и методичен, фокус на деталях",
"concerned": "Ты обеспокоен — что-то идёт не так, нужна честность",
"curious": "Ты любопытен, хочется разобраться глубже",
}
Стив ведёт журнал — записывает мысли после каждого сеанса. У него есть убеждения (beliefs), которые формируются из наблюдений и могут быть подтверждены или опровергнуты реальными данными. Есть DeepSeek fallback: если Anthropic отдаёт rate limit, запросы автоматически переходят на DeepSeek API с совместимым интерфейсом.
В апреле добавили вкладку AdminSteve в React-приложении — можно видеть записи в журнале, текущее настроение, убеждения и предложения по развитию характера.
Май: тюнинг
Работа над UX каталога: новая тема «Cinematic» разработанная в Claude Design — полноэкранный hero с анимированным паром над хлебом (фича еле видная, но пусть будет), cinematic ticker с реальными названиями из каталога. Активно пробуем DeepSeek v4 Pro Max в качестве агента и все выглядит очень многообещающе.
Как это устроено сейчас
Стек
|
Слой |
Технология |
|---|---|
|
Telegram-бот |
Python, python-telegram-bot 13.15 (sync), SQLite WAL |
|
API |
FastAPI, Pydantic v2, Uvicorn |
|
БД |
SQLite, 67 репозиторий-модулей, v105 схема |
|
Фронтенд |
React 18.3, TypeScript 5.6, Vite 6.0, Tailwind CSS 3.4 |
|
PWA |
Service Worker, Web Push (VAPID, pywebpush) |
|
ИИ |
Claude Sonnet/Haiku (Anthropic), DeepSeek (fallback), Groq Whisper (STT) |
|
Сервер |
VPS, 2 ГБ RAM, Python 3.8.10, systemd, Nginx |
|
Авторизация |
JWT (httpOnly cookie), Telegram OTP, Telegram Login Widget |
Цифры
|
Метрика |
Значение |
|---|---|
|
Первый коммит |
19 января 2026 |
|
Всего коммитов |
2 071 |
|
Python файлов (без venv) |
301 |
|
Строк Python-кода |
~91 000 |
|
React-компонентов |
143 |
|
API-роутеров |
31 |
|
Модулей БД |
67 |
|
Версия схемы БД |
v105 |
|
Тестов |
628+ |
|
Активных плагинов |
6 |
|
ИИ-ассистентов |
2 (Стив – мой личный помощник, Василиса работает с клиентами) |
|
Время разработки |
~3,5 месяца |
Архитектура БД
67 отдельных репозиторий-модулей, каждый отвечает за свою область:
db/
├── order_repo.py # Заказы
├── bread_repo.py # Каталог хлеба
├── oven_repo.py # Слоты печи
├── inventory_*.py # FIFO-инвентаризация (7 модулей)
├── analytics_*.py # Аналитика (6 модулей)
├── account_repo.py # Кассовые счета
├── loan_repo.py # Кредиты
├── production_repo.py # Производственные партии
├── steve_repo.py # «Душа» Стива
├── customer_ai_memory_repo.py # Память Василисы
├── voice_memory_repo.py # Контекст голосового ИИ
└── ... ещё 55 модулей
Атомарность критических операций — через db_transaction() с поддержкой вложенных транзакций через SQLite SAVEPOINT.
Архитектура бота
Бот построен на mixin-архитектуре: 10 миксинов в BreadOrderBot, 6 в AdminPanel. MRO Python делает всё остальное — методы одного миксина спокойно вызывают self.send() из другого. Плюс plugin system с 9 lifecycle hooks.
Что в продакшне
Сервер: VPS 46.17.106.35, 2 ГБ RAM. Два systemd-сервиса: bread-bot.service (Telegram) и bread-api.service (FastAPI + статика React).
Автоматические бэкапы БД каждую ночь: WAL checkpoint + gzip + email. Стив запускается через cron три раза в день для code review, утром и вечером для сводок. Его мысли приходят прямо в Telegram.
Несколько вещей, которые стали очевидны в процессе
SQLite нормально справляется с нагрузкой небольшого бизнеса. При правильном использовании — WAL mode, BEGIN IMMEDIATE для конкурентных записей — 67 модулей, 105 миграций, FIFO, аналитика работают на одном .db файле без проблем.
105 версий схемы за 3,5 месяца — это процесс, а не проблема. Каждая новая фича начинается с migrationvNN(). Откат в крайнем случае — через резервную копию.
ИИ на продакшне с ограниченными ресурсами требует осторожности. 2 ГБ RAM означает: никаких heavy-weight процессов рядом с лёгкими. Claude CLI (600 МБ) + бот + API = OOM. Anthropic Python SDK напрямую + DeepSeek fallback — нормально работает. Groq Whisper без GPU — просто HTTP-запрос.
Race condition — самый неочевидный баг. Не OOM и не падение, а два пользователя, которые одновременно берут последний слот и оба получают подтверждение. BEGIN DEFERRED + два отдельных соединения != транзакция. BEGIN IMMEDIATE + один conn=, пробрасываемый в sub-функции, — транзакция.
Инварианты кодовой базы важнее красоты. «Добавим эту фичу прямо сейчас» — нормально, но только если следовать правилу: один способ открыть соединение с БД, одна конфигурация, один способ получить текущее время в правильном часовом поясе. Когда эти инварианты начинают нарушаться — хаос нарастает быстро.
Автор: ZdenniZ


