Актуальный checkpoint проекта после настройки PostgreSQL, конфигурации приложения, SQLAlchemy и Alembic.
Nyamstack — учебный pet-проект: Telegram-бот для учёта питания и КБЖУ.
Главная цель разработки — не просто быстро получить готовый бот, а постепенно пройти реальные этапы backend-разработки:
- проектирование данных;
- настройка окружения;
- работа с PostgreSQL;
- SQLAlchemy ORM;
- миграции Alembic;
- repositories/services;
- интеграция с aiogram;
- тестирование.
Проект автор реализует самостоятельно.
Агент выполняет роль технического наставника и code-review агента:
- разбивает разработку на маленькие этапы;
- объясняет, зачем нужен каждый этап;
- проверяет реальное состояние репозитория сам;
- проводит review написанного кода;
- прямо указывает ошибки и последствия;
- не пишет готовую реализацию без явного запроса пользователя;
- не меняет рабочий код без явного запроса пользователя;
- может редактировать документацию и явно запрошенные файлы.
Если репозиторий доступен агенту, агент сам читает нужные файлы и выполняет безопасные диагностические команды. Не нужно просить пользователя присылать код или вывод терминала, если это можно проверить самостоятельно.
Рабочая среда:
- macOS 26;
- Visual Studio Code;
- Python 3.13;
- виртуальное окружение
.venv; - Git-репозиторий связан с
origin; - основная ветка —
main.
Текущий стек:
- Python 3.13;
- aiogram;
- SQLAlchemy 2.x;
- asyncpg;
- PostgreSQL;
- Alembic;
- pydantic-settings;
- Docker / Docker Compose;
- pytest планируется для тестирования.
Добавлен greenlet, потому что он требуется SQLAlchemy для async-работы через текущий механизм выполнения.
Redis и LLM-интеграция могут появиться позже, но не добавляются раньше реальной необходимости.
Основные директории:
app/
bot/
config/
database/
repositories/
tests/
scripts/
alembic/
Назначение слоёв:
app/bot— Telegram-интерфейс, handlers, callbacks, ответы пользователю. Не содержит SQL и основную бизнес-логику.app/services— будущий слой бизнес-логики. Пока директория может отсутствовать или быть пустой.app/repositories— будущий слой работы с БД через SQLAlchemy.app/database— engine, session, ORM-модели, техническая инфраструктура PostgreSQL.app/config— настройки приложения черезpydantic-settings.tests— будущие автоматические тесты.scripts— временные/служебные диагностические скрипты.alembic— миграции БД.
Не создавать дополнительные абстрактные слои (utils, common, core, managers и т. п.) без необходимости.
Используются .env и .env.example.
.env:
- реальные значения;
- не должен попадать в Git.
.env.example:
- пример нужных переменных;
- без реальных секретов;
- должен коммититься.
Текущие переменные:
POSTGRES_DB=nyamstack
POSTGRES_USER=nyamstack
POSTGRES_PASSWORD=change_me
POSTGRES_HOST=localhost
POSTGRES_PORT=5432В app/config/config.py создан Settings на базе pydantic-settings.
Текущий подход:
- в
.envхранятся отдельныеPOSTGRES_*; database_urlне хранится отдельно;database_urlсобирается внутриSettingsкак property.
Формат URL:
postgresql+asyncpg://USER:PASSWORD@HOST:PORT/DB
PostgreSQL поднят через compose.yaml.
Сервис:
postgres;- image:
postgres:15; - порт:
5432:5432; - данные хранятся в volume
pgdata; POSTGRES_DB,POSTGRES_USER,POSTGRES_PASSWORDберутся из.env.
Проверка контейнера:
docker compose psПроверка подключения через приложение:
python -m scripts.check_dbОжидаемый результат:
1 engine ok
1 factory ok
В app/database/connection.py настроены:
engineчерезcreate_async_engine(settings.database_url);async_session_factoryчерезasync_sessionmaker;expire_on_commit=False.
Принятое решение:
- не создавать глобальную открытую session;
- использовать фабрику сессий;
- repositories позже будут получать/создавать
AsyncSessionдля операций с БД.
Alembic переинициализирован через async-шаблон:
alembic init -t async alembicВ alembic/env.py сделано:
- импортируется
settings; sqlalchemy.urlподставляется изsettings.database_url;- импортируется
Base; target_metadata = Base.metadata.
Проверка:
alembic currentКоманда успешно подключается к PostgreSQL. Миграций пока нет.
alembic/versions/ — папка для файлов миграций.
В app/database/models.py создан базовый класс:
class Base(DeclarativeBase):
passНачата модель User для таблицы users.
Поля User по архитектуре:
id—INTEGER, primary key;telegram_id—BIGINT,NOT NULL,UNIQUE, не primary key;created_at— дата и время создания пользователя,NOT NULL;calorie_target—INTEGER, nullable;protein_target—INTEGER, nullable;fat_target—INTEGER, nullable;carbs_target—INTEGER, nullable.
Важно: на момент checkpoint модель User начата, но перед миграцией требуется финальный review и исправление синтаксиса/форматирования, если оно есть в текущем файле.
Зафиксированы четыре основные сущности:
users;products;user_products;meals.
Общая проверенная база брендовых продуктов.
Поля:
id;name;brand;calories_per_100g;protein_per_100g;fat_per_100g;carbs_per_100g.
На MVP отдельный usage_count не нужен.
Личные продукты пользователя.
Поля:
id;user_id→users.id;name;brand, nullable;- КБЖУ на 100 г;
moderation_statusминимум:pending,approved,rejected;global_product_id→products.id, nullable;created_at.
global_product_id не UNIQUE: несколько пользовательских записей могут ссылаться на один глобальный продукт.
Факт добавления еды в рацион.
Одна строка meals — снимок события:
кто съел + что съел + сколько + итоговое КБЖУ порции + источник данных + время добавления.
Поля:
meal_id;user_id→users.id;name;weight_g;nutrition_source;- итоговые
calories,protein,fat,carbs; created_at.
nutrition_source:
approved_product;user_product;ai_estimated.
Исторические meals не должны пересчитываться при изменении исходного продукта.
Конкретный продукт с известными КБЖУ.
Пример:
Простоквашино творог 5% 150 г
Логика:
- искать в личной базе пользователя;
- затем в общей проверенной базе;
- пересчитать КБЖУ на фактический вес порции.
Обычное блюдо без точных данных.
Пример:
омлет из 3 яиц
Логика:
- оценивается LLM;
- результат помечается как
ai_estimated; - после подтверждения сохраняется в
meals; - не должен автоматически попадать в
products.
В базе и промежуточных вычислениях сохраняется достаточная точность.
Округление преимущественно выполняется при отображении пользователю.
Пример:
- две порции по
12.4г белка; - точный расчёт:
24.8; - отображение:
25г.
Дневные цели пользователя хранятся целыми числами.
Зафиксированы решения по:
- назначению
users,products,user_products,meals; - разделению
brandedиgeneric; - хранению внутреннего
users.idотдельно отtelegram_id; - раздельным колонкам КБЖУ вместо JSON;
- обязательности КБЖУ для продуктов;
nutrition_source;moderation_statusминимум из трёх состояний;- хранению снимка КБЖУ в
meals; - правилу округления;
- отказу от
usage_countна MVP; - использовании async SQLAlchemy + asyncpg;
- хранении
POSTGRES_*и сборкеdatabase_urlвнутриSettings; - использовании async-шаблона Alembic.
TIMESTAMPилиTIMESTAMPTZдля дат.- Конкретные размеры
NUMERIC(precision, scale). - SQL-тип
weight_g. - Способ хранения
moderation_statusиnutrition_source. - Поведение foreign key при удалении пользователя или продукта.
- Нужны ли дополнительные индексы.
- Нужен ли составной
UNIQUEдля продуктов. - Как именно реализовать fuzzy search.
- Как будет устроено удаление и редактирование пользовательских продуктов.
- Где именно проводить границу транзакции: service layer или repository layer.
Следующий рабочий шаг:
- Провести финальный review
app/database/models.py. - Исправить модель
User, если есть синтаксические ошибки или проблемы типизации. - Создать первую миграцию Alembic для
users. - Проверить содержимое миграции до применения.
- Применить миграцию к PostgreSQL.
- Проверить, что таблица
usersпоявилась в базе.
После этого:
- Реализовать минимальный
UserRepository. - Проверить создание/поиск пользователя.
- Затем переходить к
products,user_products,meals.
Не переходить к aiogram-сценариям, пока базовый слой данных не готов.