BorisovAI

Блог

Публикации о процессе разработки, решённых задачах и изученных технологиях

Найдено 5 заметокСбросить фильтры
Новая функция

Как машина научилась видеть тренды раньше рынка

# Охота на тренды: как мы учим машину видеть будущее Вчера сидел в офисе и слушал, как бизнес снова волнуется: доходы падают, рынок неопределён, никто не знает, на что ставить. А потом понял — проблема не в рынке, а в том, что мы **слепые**. Мы видим только то, что уже произошло, а не то, что начинает происходить. Вот тогда и родилась задача: построить систему, которая ловит тренды до того, как они станут очевидными. ## С чего всё начиналось Сначала я попытался дать определение: тренд — это когда что-то новое становится популярным, потому что это действительно меняет жизнь людей. Написал, прочитал, понял, что это полная туфта. Слишком размыто, слишком философично. На собеседовании такое не пройдёт. Три дня размышлений — и вдруг щёлкнуло. **Объект.** Начнём с объекта. React.js, алюминиевые вилки, нейросети, биткоин — неважно что. Каждый объект существует в каком-то количестве экземпляров. И когда это количество **резко меняется** — вот это и есть тренд. Восходящий или нисходящий, но именно это. Дальше логика развернулась сама: объекты объединяются в классы. React.js — это объект из класса "JavaScript-фреймворки", который входит в категорию "современные фронтенд-инструменты". И здесь началось самое интересное. ## Архитектура, которая растёт сама Ключевой инсайт пришёл неожиданно: объект может **протянуть за собой весь класс**. Например, если взлетает спрос на вилки в целом, то растут и алюминиевые, и пластиковые одновременно. Но это не просто рост — это свойства объекта, которые нужно отслеживать отдельно. Я понял, что база должна быть построена не вокруг трендов, а вокруг **объектов**. Каждый объект хранит: - Количество экземпляров (конкретное число или статистику) - Скорость изменения этого количества - *Эмоциональную напряженность* вокруг него (обсуждения в сети, упоминания, дискуссии) - Иерархию: класс → категория → суперкатегория Последний пункт казался странным на первый взгляд. Но потом я понял: это нужно, чтобы поймать **масштабируемость тренда**. Если тренд на React 19 завтра умрёт, выйдет React 20 — но категория "JavaScript-фреймворки" будет актуальна годами. ## Откуда берём данные? Здесь я поймал себя на ошибке: слишком сужал поиск. Вместо "React 19 новые фичи" нужно смотреть на "эволюция современных фронтенд-фреймворков". Первое привязано к версии, второе охватывает реальный тренд целиком. То же с трендом на нейросети: не "ChatGPT выпустил новую версию", а "AI-ассистенты в работе разработчика" — это охватывает ChatGPT, GitHub Copilot, Claude и сюда же войдут новые инструменты. Система должна **автоматически выделять объекты** из обсуждений, присваивать им свойства и отслеживать скорость изменения. Нужен парсер новостей, форумов, GitHub-трендов, Stack Overflow. И математический движок, который из этого шума выделит сигнал. ## Что дальше Прототип уже в работе. Первая версия ловит объекты из текстов, классифицирует их, строит иерархию. Потом будет предикция: "это может стать трендом в Q2". И финал — рекомендации: "вам стоит обратить внимание на эти три объекта". Учимся мы методом проб и ошибок, как всегда. Но уже ясно: когда видишь тренд на стадии зарождения, а не на пике волны — это совсем другое ощущение. 😄 Jest: решение проблемы, о существовании которой ты не знал, способом, который не понимаешь.

#clipboard#javascript
11 февр. 2026 г.
Исправление

Когда GitLab Runner нашел 5 ошибок TypeScript за 9 секунд

# GitLab Runner сломал сборку: как мы спасали TypeScript проект Понедельник, 10 февраля. В 17:32 на сервере **vmi3037455** запустился очередной CI/CD пайплайн нашего проекта **trend-analisis**. GitLab Runner 18.8.0 уверенно начал свою работу: клонировал репозиторий, переключился на коммит f7646397 в ветке main, установил зависимости. Всё шло как надо, пока... Сначала казалось, что всё в порядке. `npm ci` отработал чисто: 500 пакетов установилось за 9 секунд, уязвимостей не найдено. Команда `npm run build -- --mode production` запустилась, TypeScript компилятор включился. И вот тут — **взрыв**. Пять ошибок TypeScript сломали всю сборку. Сначала я подумал, что это очередное невезение с типизацией React компонентов. Но посмотрев внимательнее на стек ошибок, понял: это не просто синтаксические проблемы. Это был признак того, что в коде **фронтенда рассинхронизировались типы** между компонентом и API. Проблема первая: в файле `src/routes/_dashboard/analyze.$jobId.report.tsx` компонент ожидал свойства **trend_description** и **trend_sources** на объекте AnalysisReport, но они попросту не существовали в типе. Это классический случай, когда один разработчик обновил API контракт, а другой забыл синхронизировать тип на фронтенде. Проблема вторая: импорт `@/hooks/use-latest-analysis` исчез из проекта. Компонент `src/routes/_dashboard/trend.$trendId.tsx` отчаянно его искал, но находил только воздух. Кто-то либо удалил хук, либо переместил его, не обновив импорты. Проблема третья совсем коварная: в роутере используется типизированная навигация (похоже, TanStack Router), и при переходе на страницу `/analyze/$jobId/report` не хватало параметра **search** в типе. Компилятор был совершенно прав — мы пытались пройти валидацию типов с неполными данными. Иронично, что всё это выглядит как обычная рабочая пятница в любом JavaScript проекте. TypeScript здесь одновременно наш спаситель и палач: он не позволит нам развернуть баг в production, но заставляет потратить время на то, чтобы привести типы в порядок. **Интересный факт:** GitLab Runner использует **shallow clone** с глубиной 20 коммитов для экономии трафика — видите параметр `git depth set to 20`. Это означает, что пайплайн работает быстро, но иногда может не найти необходимые коммиты при работе с историей. В данном случае это не помешало, но стоит помнить при отладке. В итоге перед нами встала классическая задача: синхронизировать типы TypeScript, переимпортировать удалённые хуки и обновить навигацию роутера. Сборка не пройдёт, пока всё это не будет в порядке. Это момент, когда TypeScript раскрывает свою суть: быть стеной между плохим кодом и production. Дальше предстояла работа по восстановлению целостности типов и проверка, не сломали ли мы что-нибудь ещё в спешке. Welcome to the JavaScript jungle! 😄

#clipboard#javascript#git#api
10 февр. 2026 г.
Исправление

Когда API успешен, но ответ пуст: охота на невидимого врага

# Когда AI молчит: охота на призрак пустого ответа В одном из проектов случилось странное — система обращалась к API, получала ответ, но внутри него... ничего. Как в доме с открытыми дверями, но все комнаты пусты. Задача была простая: разберись, почему сообщение от пользователя *Coriollon* через Telegram генерирует пустой результат, хотя API клиента уверенно докладывает об успехе. История началась 9 февраля в 12:23 с обычной команды. Пользователь отправил в бот сообщение «Создавай», и система маршрутизировала запрос в CLI с моделью Sonnet — всё как надо. Промпт собрали, отправили на API. Система была настроена с максимум тремя повторными попытками при ошибках. Логично, правда? Первый запрос обработался за 26 с лишним секунд. API вернул успех. Но в поле `result` зияла пустота. Не ошибка, не исключение — просто пустая строка. Система поняла: что-то не так, нужно пробовать ещё. Через 5 секунд — вторая попытка. Снова успех на бумаге, снова пустой ответ. Третий раз был поспешен: через 10 секунд ещё один запрос, и снова тишина. Что интересно — в логах были видны все признаки нормальной работы. Модель обработала 5000+ символов промпта, израсходовала токены, потратила API-бюджет. Кэш работал прекрасно — вторая и третья попытки переиспользовали 47000+ закэшированных токенов. Но конечный продукт — результат для пользователя — остался фантомом. Здесь скрывается коварная особенность асинхронных систем: успешный HTTP-статус и валидный JSON в ответе ещё не гарантируют, что внутри есть полезная нагрузка. API может спокойно ответить 200 OK, но с пустым полем результата. Механизм повторных попыток поймал проблему, но не смог её решить — повторял одно и то же три раза, как сломанный проигрыватель. На четвёртый раз система сдалась и выбросила ошибку: *«CLI returned empty response»*. Урок был ценный: валидация ответа от внешних сервисов должна быть двухуровневой. Первый уровень — проверяем HTTP-статус и структуру JSON. Второй уровень, важнее — проверяем, что в ответе есть актуальные данные. Просто наличие полей недостаточно; нужна проверка *содержания*. В нашем случае пустое значение в `result` должно было сработать как маячок уже на первой попытке, а не ждать третьей. Кэширование в таких ситуациях работает против нас — оно закрепляет проблему. Если первый запрос вернул пусто, и мы кэшировали эту пустоту, второй и третий запросы будут питаться из одного источника, перечитывая одну и ту же ошибку. Лекарство простое, но необычное: кэшировать нужно не все ответы подряд, а только те, которые прошли валидацию содержимого. Итог: система заработала, но теперь с более умным механизмом выявления невидимых ошибок. Повторные попытки стали умнее — они теперь различают, когда нужно переопробовать запрос, а когда отклонить ответ как невалидный. Пользователь Coriollon теперь получает либо результат, либо честную ошибку, но уже не это мучительное молчание. 😄 **Node.js — единственная технология, где «это работает» считается документацией.**

#clipboard#api#security
9 февр. 2026 г.
Новая функция

Боевой тест Telegram-бота: когда теория встречается с реальностью

# Проверяем Telegram-бота в боевых условиях: тестируем управление доступом Когда создаёшь бота для Telegram, одно дело — писать тесты в PyTest, и совсем другое — убедиться, что он работает с реальными аккаунтами и обрабатывает команды так, как задумано. Вот я и пришёл к моменту, когда нужно было залезть в грязь и провести самый важный тест: запустить бота и написать ему сообщение из Telegram. ## Задача была простая, но критичная Я добавил в бота новую фишку — управление доступом через команды `/manage`. Идея: в групповом чате владелец может сделать его приватным (`/manage add`), и тогда бот будет отвечать только ему. Затем команда `/manage remove` открывает доступ для всех обратно. Плюс ещё `/recall` и `/remember` для сохранения данных в памяти чата. Звучит просто, но нужно убедиться, что: - бот действительно игнорирует сообщения посторонних в приватном режиме; - владелец всегда может управлять ботом; - после отключения приватности всё работает как раньше. ## Как я это проверял Сначала поднял бота локально: ``` python telegram_main.py ``` Затем начались «полевые испытания»: 1. **Первый скрин-тест** — написал боту `/manage add` из своего аккаунта. Бот должен был записать ID чата в таблицу БД `managed_chats` и включить режим приватности. Отправил обычное сообщение — бот ответил. ✅ 2. **Второй аккаунт** — попросил друга отправить то же сообщение из другого Telegram. Бот молчал, хотя обычно отвечает на всё. Middleware `permission_check.py` срабатывал корректно и блокировал обработку. ✅ 3. **Финальный тест** — написал `/manage remove` и снова попросил друга отправить сообщение. На этот раз бот ответил. Приватность снята, всё доступно. ✅ ## Что оказалось неочевидным Интеграционное тестирование в Telegram выявило одну тонкость: когда ты работаешь с асинхронным обработчиком команд в aiogram, timing-зависимые проверки могут создавать гонки условий. У меня был момент, когда команда `/manage add` срабатывала, но middleware проверял доступ *до* того, как запись попала в БД. Пришлось добавить небольшой await после insert'а, чтобы гарантировать консистентность. Ещё обнаружил: если ты работаешь с SQLite и одновременно несколько обработчиков пишут в таблицу, нужен явный `commit()` или использовать контекстный менеджер транзакций. Иначе другой процесс не увидит изменения до commit'а. ## Что дальше? После успешных интеграционных тестов я задокументировал всё в README.md — добавил секцию про управление доступом с примерами команд. Создал отдельный файл `docs/CHAT_MANAGEMENT.md` с полной архитектурой ChatManager, схемой БД и API reference для всех методов класса. Теперь у меня есть надёжная система для создания приватных чатов с ботом. Это можно использовать для ассистентов, которые работают с конфиденциальными данными, или для модераторов в больших группах. Главный вывод: прежде чем гордиться unit-тестами, обязательно проверь свой код в реальной среде. Иногда именно там появляются неожиданные проблемы, которые не поймать никаким PyTest. 😄 Что общего у Telegram API и инструкций по использованию телефона? И то, и другое люди игнорируют в пользу метода проб и ошибок.

#clipboard#python#api#security
9 февр. 2026 г.
Новая функция

Одна БД для всех: как мы добавили чаты без архитектурного хаоса

# Одна база на всех: как мы добавили управление чатами без архитектурного хаоса Когда проект растёт, растут и его аппетиты. В нашем Telegram-боте на основе Python уже была отличная инфраструктура — `UserManager` для управления пользователями, собственная SQLite база в `data/agent.db`, асинхронные запросы через `aiosqlite`. Но вот беда: чат-менеджер ещё не появился. А он нам был нужен. Стояла вот какая задача: нужно отслеживать, какие чаты управляет бот, кто их владелец, какой это тип чата (приватный, группа, супергруппа, канал). При этом не создавать отдельную базу данных — это же кошмар для девопса — а переиспользовать существующую инфраструктуру. **Первым делом** заглянул в текущую архитектуру. Увидел, что всё уже завязано на одной БД, один конфиг, одна логика подключения. Идеально. Значит, нужна просто одна новая таблица — `managed_chats`. Задумал её как простую структуру: `chat_id` как первичный ключ, `owner_id` для связи с пользователем, `chat_type` с проверкой типов через `CHECK`, поле `title` для названия и JSON-колонка `settings` на будущее. Обычно на этом месте разработчик бы создал абстрактный `ChatRepository` с двадцатью методами и паттерном `Builder`. Я же решил сделать проще — скопировать философию `UserManager` и создать классический `ChatManager`. Три-четыре асинхронных метода: добавить чат, проверить, управляется ли он, получить владельца. Всё на `aiosqlite`, как и везде в проекте. **Неожиданно выяснилось**, что индексы — это не украшение. Когда начну искать чаты по владельцу, индекс на `owner_id` будет спасением. SQLite не любит полные скены таблиц, если можно обойтись поиском по индексу. Интересный момент: SQLite часто недооценивают в стартапах, думают, что это игрушка. На самом деле она справляется с миллионами записей, если её правильно использовать. Индексы, `PRAGMA` для оптимизации, подготовленные statements — и у вас есть боевая база данных. Многие проекты потом переходят на PostgreSQL только потому, что привыкли к MySQL, а не из реальной нужды. В итоге получилась чистая архитектура: одна БД, одна точка подключения, новая таблица без какого-либо дублирования логики. `ChatManager` живёт рядом с `UserManager`, используют одни и те же библиотеки и утилиты. Когда понадобятся сложные запросы — индекс уже есть. Когда захотим добавить настройки чата — JSON-поле ждёт. И никаких лишних микросервисов. Следующий шаг — интегрировать это в обработчики событий Telegram API. Но это уже другая история. 😄 Почему база данных никогда не посещает вечеринки? Её постоянно блокирует другой клиент!

#clipboard#python#javascript#git#security
9 февр. 2026 г.