От плоской базы к дереву версий: как переделать архитектуру анализов

Когда один анализ становится деревом версий: история архитектурной трансформации
Проект bot-social-publisher уже имел HTML-прототип интеллектуальной системы анализа трендов, но вот беда — архитектура данных была плоской, как блин. Одна версия анализа на каждый тренд. Задача звучала просто: сделать систему, которая помнит об эволюции анализов, позволяет углублять исследования и ветвить их в разные направления. Именно это отличает боевую систему от прототипа.
Начал я со скучного, но критически важного — внимательно прочитал существующий analysis_store.py. Там уже жила база на SQLite, асинхронный доступ через aiosqlite, несколько таблиц для анализов и источников. Но это была просто полка, а не полнофункциональный архив версий. Первое, что я понял: нужна вертикаль связей между анализами.
Фаза первая: переделка схемы. Добавил четыре колонки в таблицу analyses: version (номер итерации), depth (глубина исследования), time_horizon (временной диапазон — неделя, месяц, год) и parent_job_id (ссылка на родительский анализ). Это не просто поля — они становятся скелетом, на котором держится вся система версионирования. Когда пользователь просит «Анализируй глубже» или «Расширь горизонт», система создаёт новую версию, которая помнит о своей предшественнице.
Фаза вторая: переписывание логики. Функция save_analysis() была примитивна. Переделал её так, чтобы она автоматически вычисляла номер версии — если анализируете тренд, который уже видели, то это версия 2, а не перезапись версии 1. Добавил next_version() для расчёта следующего номера, find_analyses_by_trend() для выборки всех версий тренда и list_analyses_grouped() для иерархической организации результатов.
Фаза третья: API слой. Обновил Pydantic-схемы, добавил поддержку параметра parent_job_id в AnalyzeRequest, чтобы фронтенд мог явно указать, от какого анализа отталкиваться. Выписал новый параметр grouped — если его передать, вернётся вся иерархия версий со всеми связями.
Вот тут началось интересное. Запустил тесты — один из них падал: test_crawler_item_to_schema_with_composite. Первым делом подумал: «Это я сломал». Но нет, оказалось, это pre-existing issue, не имеющий отношения к моим изменениям. Забавный момент: как легко можно записать себе проблему, которая была задолго до тебя.
Интересный факт о SQLite и миграциях. В Python для SQLite нет ничего вроде Django ORM с его волшебством. Миграции пишешь вручную: буквально SQL-запросы в функциях. ALTER TABLE и точка. Это делает миграции прозрачными, понятными, предсказуемыми. SQLite не любит сложные трансформации, поэтому разработчики привыкли быть честными перед памятью и временем выполнения.
Архитектура готова. Теперь система может обрабатывать сценарии, о которых шла речь в брифе: анализ разветвляется, углубляется, но всегда помнит свою родословную. Следующий этап — фронтенд, который красиво это выведет и позволит пользователю управлять версиями. Но это совсем другая история.
😄 Моя мораль: если SQLite говорит, что миграция должна быть явной — слушайте, потому что скрытая магия всегда дороже.
Метаданные
- Session ID:
- grouped_C--projects-bot-social-publisher_20260208_1520
- Branch:
- main
- Dev Joke
- Почему Deno считает себя лучше всех? Потому что Stack Overflow так сказал
Часть потока:
Разработка: C--projects-bot-social-publisher