Блог
Публикации о процессе разработки, решённых задачах и изученных технологиях
Бот, который помнит, где остановился: история оптимизации
# Как мы научили бота-публикатора читать только новое и не зацикливаться Работаю над **bot-social-publisher** — инструментом, который автоматизирует публикацию контента в соцсети. За время разработки проект рос и требовал всё более изощренных решений. Недавно пришло время для серьёзного апдейта: версия 2.2 превратилась в настоящий рефакторинг с половиной архитектуры. Основная боль была в том, что бот каждый раз перечитывал **весь лог событий** с самого начала. Проект растёт, логов накапливается тонны, и перечитывать их каждый раз — это пустая трата ресурсов. Первым делом внедрил **incremental file reading**: теперь каждый collector (собиратель событий) сохраняет позицию в файле и читает только новый контент. Позиции и состояния переносят перезапуски — данные не теряются. Второе узкое место: события из одного проекта приходят разреженно и хаотично. Если публикация выходит с опозданием, сессия кажется невнятной. Ввел **project grouping** — теперь все сессии из одного проекта, которые случились в окне 24 часа, объединяются в одну публикацию. Начало звучать куда более логично. Но бот просто агрегировал события — не очень информативно. Подключил **SearXNG news provider**, чтобы вплетать в промпты релевантные технологические новости. И добавил **content selector** с алгоритмом скоринга, который отбирает 40–60 самых информативных строк из лога. Выглядит как машинное обучение, а на деле простая эвристика, которая работает хорошо. Далее натолкнулся на проблему качества текста. LLM первый раз генерирует контент, но грамматика хромает. Внедрил **proofreading pass** — второй вызов LLM, но уже как редактор. Он проходит по тексту и чистит пунктуацию, стиль, грамматику. Результат — ночь и день. Когда LLM генерирует заголовок, иногда получаются дубли. Вместо того чтобы просто выпустить дубль, добавил **title deduplication** с авто-регенерацией (до трёх попыток). А ещё реализовал **tray notifications** — теперь разработчик видит нативные уведомления ОС о публикациях и ошибках. И главное: добавил **PID lock**, чтобы предотвратить запуск нескольких инстансов одновременно. Интересный момент: **PyInstaller**. Когда собираешь exe-бандл, пути до ресурсов перестают работать. Правильное разрешение путей в APP_DIR/BUNDLE_DIR — то есть нужно отдельно обрабатывать контекст запуска из exe. Мелочь, но без этого бандл просто не запустится. Ещё поменял логику пороговых значений: вместо min_lines теперь min_chars. Когда работаешь с короткими строками, количество символов точнее отражает объём контента, чем количество строк. И как положено, добавил AGPL-v3 лицензию ко всем файлам исходника. В итоге v2.2 — это не просто апдейт, а переосмысление архитектуры вокруг идеи: **не перечитывай лишнее, интеллектуально выбирай информацию, дважды проверяй качество, предотврати конфликты**. Бот теперь быстрее, умнее и его легче деплоить. 😄 Знаешь, почему логирование через **RotatingFileHandler** — лучший друг разработчика? Потому что диск полный. С ротацией логов хотя бы видно, когда именно он полный.
Когда конфиги падают: война Traefik с несуществующим middleware
# Когда конфиги кусаются: история про зависимые middleware в Traefik Проект `borisovai-admin` — это не просто админ-панель, это целая инфраструктурная система с аутентификацией через Authelia, обратным прокси на Traefik и кучей moving parts, которые должны работать в идеальной гармонии. И вот в один прекрасный день выясняется: когда ты разворачиваешь систему без Authelia, всё падает с ошибкой 502, потому что Traefik мечтательно ищет middleware `authelia@file`, которого просто нет в конфиге. **Завязка проблемы была в статических конфигах.** Мы жёстко прописали ссылку на `authelia@file` прямо в Traefik-конфигурацию, и это сработало, когда Authelia установлена. Но стоило её отключить или просто не устанавливать — бум, 502 ошибка. Получается, конфиги были сильно связаны с опциональным компонентом. Это классический случай, когда инфраструктурный код требует гибкости. Решение разбилось на несколько фронтов. Во-первых, пришлось **убрать жёсткую ссылку на `authelia@file` из статических конфигов Traefik** — теперь это просто не указывается в базовых настройках. Во-вторых, создали правильную цепочку инициализации: - `install-authelia.sh` теперь сам добавляет `authelia@file` в `config.json` и настраивает OIDC при установке Authelia; - `configure-traefik.sh` проверяет переменную `AUTHELIA_INSTALLED` и условно подключает middleware; - `deploy-traefik.sh` перепроверяет, установлена ли Authelia на сервере, и если да — переустанавливает `authelia@file`. Неожиданный бонус обнаружился в `install-management-ui.sh` — там был неправильный путь к `mgmt_client_secret`. Исправили по ходу. А `authelia.yml` вообще выкинули из репозитория, потому что его генерирует сам скрипт установки. Зачем держать в git то, что всегда одинаково генерируется? **Интересный момент про middleware в Docker-сообществе:** люди часто забывают, что middleware — это не просто функция, это *объект конфигурации*, который должен быть определён до использования. Traefik здесь строг: ты не можешь ссылаться на middleware, которого не существует. Это похоже на попытку вызвать функцию, которая не импортирована в Python. Простая ошибка, но очень болезненная в production-системах, потому что приводит к отказу в обслуживании. **Итоговая архитектура** получилась намного гибче: система работает как с Authelia, так и без неё, конфиги не лежат мёртвым грузом в репо, а инсталляторы действительно знают, что они делают. Это хороший пример того, как *опциональные зависимости* требуют условной логики не только в коде приложения, но и в инфраструктурных скриптах. Главный урок: если компонент опциональный, не прописывай его в статические конфиги. Пусть они туда добавляются динамически при необходимости. 😄 Что будет, если Fedora обретёт сознание? Первым делом она удалит свою документацию.
SSO за выходные: как я запустил Authelia на боевом сервере
# Authelia в боевых условиях: как я собрал Single Sign-On за выходные Задача была амбициозная: в проекте **borisovai-admin** нужно было внедрить полноценную систему единой авторизации. На площадке работают несколько приложений — Management UI, n8n, Mailu, и каждое требует свой вход. Кошмар для пользователя и сущее издевательство над принципом DRY. Решение напрашивалось само: **Authelia** — современный SSO-сервер, который справляется с аутентификацией одной рукой и может интегрироваться практически с чем угодно. ## С чего я начал Первым делом создал `install-authelia.sh` — полный скрипт установки, который берёт на себя всю рутину: скачивает бинарник, генерирует секреты, прописывает конфиги и регистрирует Authelia как systemd-сервис. Это был ключевой момент — автоматизация означала, что процесс установки можно повторить в три команды без магических танцев с палочкой. Потом встала задача интеграции с **Traefik**, который у нас отвечает за маршрутизацию. Здесь нужен был `ForwardAuth` — middleware, который перехватывает запросы и проверяет, авторизован ли пользователь. Создал `authelia.yml` с настройкой ForwardAuth для `auth.borisovai.ru/tech`. Суть простая: любой запрос сначала идёт в Authelia, и если она вас узнала — пропускаем дальше, если нет — отправляем на страницу входа. ## Dual-mode, или как угодить двум господам одновременно Самое интересное началось, когда понадобилось поддержать сразу два способа авторизации. Management UI должна работать и как классическое веб-приложение с сессиями, и как API с **Bearer-токенами** через **OIDC** (OpenID Connect). Пришлось написать `server.js` с логикой, которая проверяет, что именно пришло в запросе: если есть Bearer-токен — валидируем через OIDC, если нет — смотрим на сессию. Включил в проект `express-openid-connect` — стандартную библиотеку для интеграции OIDC в Express. Хитрость в том, что Authelia может быть и провайдером OIDC, и middleware ForwardAuth одновременно. Просто берёшь конфиг для OIDC из Management UI, подтягиваешь его в `config.json` через автоопределение (этим займется `install-management-ui.sh`), и всё начинает работать как часы. ## Неожиданный поворот с logout Оказалось, что обычный logout в веб-приложении — это не просто удалить cookie. Если вы авторизовались через OIDC, нужно ещё уведомить Authelia, что сессия закончена. Пришлось настроить пять HTML-страниц с поддержкой OIDC redirect: пользователь нажимает logout, приложение отправляет его в Authelia, Authelia убивает сессию и редиректит обратно на страницу выхода. Выглядит просто, но заставляет задуматься о том, как много движущихся частей в современном веб. ## Интересный факт: ForwardAuth vs Reverse Proxy Authentication Знаешь ли ты, что многие разработчики путают эти два подхода? ForwardAuth — это когда *сам прокси* отправляет запрос на сервер аутентификации. А Reverse Proxy Authentication — это когда *сервер приложения* полностью отдаёт авторизацию на откуп прокси. Authelia работает с обоими, но ForwardAuth даёт больше контроля — приложение всё равно может принять дополнительные решения на основе данных пользователя. ## Итог: от идеи к prod Всё сложилось в единую систему благодаря интеграции на уровне `install-all.sh` — компонент `INSTALL_AUTHELIA` занимает шаг [7.5/10], что означает: это не первый день, но далеко не последний штрих. Management UI теперь умеет сама себя конфигурировать, находя Authelia в сети, подтягивая OIDC-конфиг и автоматически подключаясь. Главное, чему я научился: SSO — это не просто чёрный ящик, куда ты кидаешь пароли. Это *экосистема*, где каждый компонент должен понимать друг друга: ForwardAuth, OIDC, сессии, logout. И когда всё это работает вместе, пользователь вводит пароль *один раз* и может спокойно прыгать между всеми приложениями. Вот это да. Почему React расстался с разработчиком? Слишком много зависимостей в отношениях 😄
Туннели и таймауты: как мы скрепили инфраструктуру воедино
# Туннели, фронт и конфиги: как мы выстроили инфраструктуру для нескольких машин Проект **borisovai-admin** достиг того момента, когда одного сервера стало недостаточно. Нужно было управлять несколькими машинами, пробрасывать сетевые соединения между ними и всё это как-то красиво завернуть для пользователя. История о том, как мы за один вечер построили систему туннелей с веб-интерфейсом и потом долго разбирались с таймаутами Traefik. ## Начало: туннели нужны вчера Задача выглядела просто: нужен интерфейс для управления туннелями между машинами. Но просто никогда не бывает, правда? Первое, что я сделал — запустил фреймворк **frp** (Fast Reverse Proxy). Это отличный инструмент для туннелирования, когда основной сервер скрыт за NAT или брандмауэром. Быстрый, надёжный, с минимальными зависимостями. Спроектировал простую UI в `tunnels.html` — список активных туннелей, кнопки для создания новых, удаления старых. Ничего сложного, но эффективно. На бэкенде добавил 5 API endpoints в `server.js` для управления состоянием туннелей. Параллельно обновил скрипты инсталляции: `install-all.sh` и отдельный `install-frps.sh` для установки FRP сервера, плюс `frpc-template` для конфигурации клиентов на каждой машине. Главное — добавил навигационную ссылку «Туннели» на все страницы админ-панели. Мелочь, но юзабилити выросла в разы. ## Неожиданный враг: Traefik и его таймауты Вроде всё работало, но потом начали падать большие файлы при скачивании через GitLab. Проблема: **Traefik** по умолчанию использует достаточно агрессивные таймауты. Стоило большому файлу загружаться более пары минут — и соединение рубилось. Пришлось менять конфигурацию Traefik: установил `readTimeout` в 600 секунд (10 минут) и добавил специальный `serversTransport` именно для GitLab. Создал скрипт `configure-traefik.sh`, который генерирует две динамические конфигурации: `gitlab-buffering` и `serversTransport`. Теперь файлы загружаются спокойно, даже если это 500 мегабайт архива. ## Пока делал это, понял одно Знаете, что самое интересное в **Traefik**? Это микросервис-балансировщик, который любит называться облегчённым, но на практике требует огромного внимания к деталям. Неправильный таймаут — и ваше приложение выглядит медленным. Правильный — и всё летает. Это как тюнинг двигателя: одна скрепка в нужном месте, и мир меняется. ## Реорганизация и масштабирование Пока занимался инфраструктурой, понял, что документация разрослась и стала трудна в навигации. Переделал структуру `docs/` под новые реальности: разделил на `agents/`, `dns/`, `plans/`, `setup/`, `troubleshooting/`. Каждая папка отвечает за свой кусок практики. Добавил в `config/contabo-sm-139/` полный набор конфигураций конкретного сервера (traefik, systemd, mailu, gitlab) и обновил `upload-single-machine.sh` для поддержки загрузки этих конфигов. Теперь новую машину можно развернуть, не пересматривая весь интернет. ## Что получилось в итоге За вечер родилась полноценная система управления туннелями с приличным интерфейсом, автоматизацией и нормальной документацией. Проект теперь легко масштабируется на новые серверы. Плюс узнал, что Traefik — это не просто балансировщик, а целая философия правильной конфигурации микросервисов. Дальше в планах: расширение аналитики для туннелей, SSO интеграция и лучший мониторинг сетевых соединений. Но это уже другая история. 😄 **Разработчик**: «Я знаю Traefik». **HR**: «На каком уровне?». **Разработчик**: «На уровне стака StackOverflow с пятью вкладками одновременно».
Связь вместо хаоса: как мы научили анализы разговаривать с трендами
# Как мы научили систему анализа трендов видеть связи между явлениями Работаем над проектом **trend-analysis** — это система, которая анализирует тренды в данных и выявляет причинно-следственные связи. Всё интересно, но вот беда: когда аналитик хочет глубже погрузиться в конкретный тренд, система не могла его за ручку взять и показать, откуда вообще взялась эта информация. Анализы существовали сами по себе, графики — сами по себе. Нужно было связать их воедино. Задача была чёткой: добавить возможность связывать анализы напрямую с конкретными трендами по ID. Звучит просто, но это касалось сразу нескольких слоёв архитектуры. **Первым делом расширили API запросов**: добавили параметр `trend_id` в запрос к анализу. Теперь при создании анализа система знает, какой именно тренд его вызвал. Логично, но раньше этой связи просто не было. Дальше — самая интересная часть. Нужно было хранить эту информацию, поэтому добавили поле `trend_id` в таблицу `analyses`. Но одного сохранения мало — нужно было ещё и *по-человечески* отображать результат. Началось с описаний эффектов: когда система выявляет причинно-следственную связь (это называется `causal_chain` в кодовой базе), она может объяснить, *почему* один фактор влияет на другой. Мы вытащили эти рассуждения (`rationale`) и превратили их в читаемые описания эффектов — теперь они отображаются прямо на интерактивном графе. Но вот неожиданность: граф строится из узлов, а узлы — это просто точки без контекста. Добавили поле `description` к каждому узлу, чтобы при наведении мышкой пользователь видел, что это вообще за узел и на что он влияет. Мелкое изменение, но пользователям нравится. Потом пришлось разбираться с интернационализацией. Карточки зон влияния и типы графиков должны были переводиться на разные языки. Добавили `i18n` переводы — теперь система говорит с пользователем на его языке, а не на смеси английского и технических термов. Всё это потребовало фиксов в типах TypeScript для навигации по параметрам поиска — система должна была *знать*, какие параметры можно передавать и как они называются. Без этого была бы путаница с undefined и ошибки во время выполнения. **Вот интересный момент**: когда работаешь с причинно-следственными связями в данных, очень легко создать «спагетти-граф» — такой, где всё связано со всем, и пользователь теряется. Важный паттерн в таких системах — *скрывать сложность слоями*. Сначала показываешь главные узлы и связи, потом — при клике — раскрываешь подробности. Мы это учитывали при добавлении описаний. **В итоге**: система стала гораздо более связной. Теперь аналитик видит не просто скучную таблицу с анализами, а *историю* того, как конкретный тренд повлиял на другие явления, с объяснениями на его языке. Граф перестал быть набором непонятных точек и стал рассказывать. Обновили CHANGELOG, и задача легла в историю проекта. Следующий шаг — добавить возможность сохранять эти связи как правила, чтобы система сама училась предсказывать новые влияния. Но это уже совсем другая история. 😄 Почему граф анализа пошёл к психологу? Потому что у него было слишком много *глубоких связей*.
Как отличить тренд от мусора: архитектура умного скоринга
# Когда скоринг тренда встречается с фактами: как мы научили компьютер различать шум и сигнал Всё началось с простого вопроса в проекте **trend-analisis**: как отличить действительно важный тренд от того, что просто много раз переписали в интернете? Первый скоринг работал, но был примитивен — считал кол-во источников и всё. А если один агрегатор новостей переиндексировал статью сто раз? Тогда по его логике это уже суперважный тренд, хотя на деле — просто мусор. Поэтому решили пойти в наступление. Нужна была система со вкусом, которая бы понимала разницу между настоящей значимостью тренда и просто шумом в информационном поле. Так родилась идея **Scoring V2**. Архитектура получилась трёхслойной. Первый слой — это **urgency** (срочность) и **quality** (качество), оба по шкале 0–100. Urgency отражает, насколько свежее и горячее событие, а quality говорит о том, из скольких разных *реальных источников* (не агрегаторов!) пришла информация. На основе этой пары метрик система выдаёт одну из четырёх рекомендаций: **ACT_NOW** (кидай в новости прямо сейчас), **MONITOR** (держим под контролем), **EVERGREEN** (медленный, долгоиграющий тренд) и **IGNORE** (это просто шум). Второй слой — интеграция с **Tavily API**. Это платформа для работы с новостями, которая даёт не просто список источников, а полный граф цитирований. TavilyAdapter в нашем коде считает unique domain count — то есть сколько *разных доменов* процитировали друг друга. Если один агрегатор (New Feed, Hacker News агрегатор — словили паттерны в `AGGREGATOR_PATTERNS`) выплюнул эту новость, мы его фильтруем. Остаются только оригинальные источники. Реальная магия произошла в методе `fetch_news()` с порогом цитирования. Мы выставили границу: если тренд цитируется меньше чем из пяти уникальных доменов, он недостаточно «реален», чтобы его считать. Просто фантом информационного поля. На фронте всё стало нагляднее. Добавили **RecommendationBadge** с крупным значком рекомендации и **UrgencyQualityIcons** — двойной индикатор, как в мобильных приложениях. Но главное изменение — источники больше не просто цифры (`5 sources`), теперь это кликабельные ссылки на URL. Пользователь может сразу прыгнуть на оригинальный источник вместо того, чтобы видеть размытую статистику. На бекенде настроился enrichment loop в **Crawler**: при обработке трендов с Hacker News, GitHub и arXiv теперь подтягиваются `tavily_citations` — полный профиль цитирований. Все константы вынесли в **Config**: `TAVILY_CITATION_BASELINES` (пороги для разных источников) и `AGGREGATOR_PATTERNS` (чёрный список перепечатанок). Самое интересное из истории Tavily: эта платформа появилась как ответ на хаос с информационной надёжностью. Её создатели заметили, что в эпоху AI-генерируемого контента источники становятся таким же валютой, как золото в разведке. Поэтому они решили сделать источники прозрачными и проверяемыми прямо из API. На выходе получилась система, которая *по-настоящему* различает сигнал и шум. **CHANGELOG.md** с чёткой историей всех изменений, **SCORING_V2_PLAN.md** с логикой расчётов (на будущее, чтобы кто-то не сломал), и **TAVILY_CITATION_APPROACH.md** с подводными камнями (коих оказалось немало). Всё задокументировано, чтобы следующий разработчик не потратил неделю на обратный инжиниринг. Что дальше? Теперь можно экспериментировать с весами urgency и quality, обучать модель на реальных данных пользователей. А Scoring V2 — это просто фундамент. Крепкий, надёжный, проверенный фундамент. 😄 Комментарий в коде: «Это должно работать» — убеждение из четырёх слов, которое разработчик дал самому себе вчера в 23:00, и он не особо в это верил.
Как научить машину отличать тренд от вирусного баяна
# Как мы разобрались, что такое тренд: от анализа данных к системе оценки Проект **trend-analisis** встал перед банальной, но хитрой проблемой: как машина может понять, что именно тренд? Не просто популярно, а действительно взлетает? В тренде идёт конкретное изменение? Вот я и взялся за исследование, которое потом превратилось в полноценную методологию оценки. Задача была амбициозная: построить систему двойной оценки для **Scoring V2**, которая бы учитывала и срочность события, и его качество. Потому что в реальной жизни одного лайка или шеера недостаточно — нужно смотреть на скорость роста, вовлечённость аудитории, стабильность интереса. Как отличить вирусный баян от настоящего тренда? Вот это вопрос. Первым делом я начал с сырых данных из **trending_items** — просто выгрузил всё, что там лежало, и начал искать закономерности. Представь: тысячи событий, метрики, сигналы. Нужно было понять, какие из них реально говорят о качестве тренда, а какие — это просто шум. Потом пошёл в глубокий анализ. Я собрал экспертные оценки, посмотрел, как эксперты оценивают эти же события. Интересное выяснилось: не все сигналы, которые кажутся важными, на самом деле коррелируют с реальной ценностью тренда. Например, большое число упоминаний может быть, а качество обсуждения — нулевое. Поэтому родилась идея **dual-score методологии**: отдельно считаем срочность (velocity), отдельно — качество (engagement и credibility). Третьим шагом я валидировал алгоритмы на граничных случаях. Что если тренд появился в маленьком сообществе, но растёт экспоненциально? Что если большой аккаунт один раз поделился — это считать за тренд? Документировал все edge cases, чтобы позже разработчики не натыкались на сюрпризы. Но вот беда: при анализе данных я понял, что некоторые критические сигналы вообще не собираются. Например, скорость, с которой люди делятся контентом в разных каналах, или как быстро растёт количество новых участников в обсуждении. Вот я и составил план по сбору этих данных — velocity и engagement метрики, которые нужно будет добавить в pipeline. На выходе получилась серия документов: от сырого анализа к финальной методологии, с проверкой алгоритмов и планом развития. Это не просто коммит — это полный цикл исследования, задокументированный так, чтобы любой разработчик мог взять и реализовать на основе выводов. Главный урок: прежде чем писать код системы, сначала нужно понять её логику. Потратить время на исследование — это не потеря времени, это инвестиция в правильную архитектуру. Потом код пишется в 10 раз быстрее. 😄 Почему scikit-learn считает себя лучше всех? Потому что Stack Overflow так сказал.
Как мы научили алгоритм видеть тренды раньше конкурентов
# Как мы научили систему различать тренды: от сырых данных к методологии V2 Проект **trend-analysis** потихоньку превращался в монстра. У нас были данные о трендирующих элементах, мы их собирали, анализировали, но что-то было не так. Алгоритм скорингования, который считался идеальным месяц назад, начал давать странные результаты: вчерашние хиты вдруг переставали быть релевантными, а настоящие тренды долго оставались невидимыми. Задача была простой на словах, но коварной в деталях: построить методологию скорингования V2, которая будет разбираться не только в том, *что* тренды, но и в том, *почему* они такие и *как долго* они просуществуют. ## Первый день: копание в данных Первым делом я создал серию исследовательских отчётов. Начал с **01-raw-data.md** — взял весь объём трендирующих элементов из базы и просто посмотрел на цифры без предубеждений. Какие сигналы вообще есть? Какие данные полны, а какие похожи на швейцарский сыр? Это как быть детективом на месте преступления — сначала нужно понять, что именно ты видишь. Потом пригласил в процесс экспертов. **02-expert-analysis.md** — это был мозговой штурм в текстовом формате. Эксперты смотрели на сигналы и говорили: «Это шум», «А это золото», «Вот это вообще баг в системе». Получилась карта того, какие сигналы действительно имеют вес при определении тренда. ## Вторая волна: архитектура методологии Третий отчёт, **03-final-methodology.md**, был поворотным. Мы поняли, что один скор — это неправда. Тренд не может быть описан одним числом. Родилась идея *dual-score методологии*: отдельно скор срочности (urgency) и скор качества (quality). Срочность показывает, насколько быстро что-то растёт прямо сейчас. Качество показывает, насколько это вообще надёжный сигнал. Представь: старая система видела вирусный мем, который взлетел за час, и кричала о тренде. А потом через два часа мем забыли, и система выглядела глупо. Новый подход говорит: «Да, это срочно, но качество низкое — боюсь, это не настоящий тренд, а всего лишь всплеск». **04-algorithms-validation.md** — это была проверка на прочность. Мы взяли исторические данные и прогнали их через алгоритм валидации. Ловили edge cases: что если все сигналы нулевые? Что если они противоречивы? Что на границах данных? Каждый баг, который нашли в теории, исправили до того, как код вообще написали. ## Последняя фаза: осознание пробелов **05-data-collection-gap.md** был честным разговором с самими собой. Мы поняли, что нам не хватает информации. Нет данных о *velocity* (как быстро растёт тренд во времени) и настоящего измерения *engagement*. Мы просто не собирали эту информацию раньше. Вот здесь и пришёл **06-data-collection-plan.md** — план того, как мы будем собирать недостающие сигналы. Не просто добавляя SQL-запросы, а продумав, какие именно метрики дадут нам полную картину. ## Что дальше? Весь этот мозговой марафон — это фундамент для реальной реализации. Теперь, когда мы начнём писать код для Scoring V2, мы знаем, что делаем и почему. Нет наугад, нет сомнений. Только чёткая методология и валидированные алгоритмы. Главный урок: иногда самая важная часть разработки — это вообще не код, а понимание. Потратили неделю на исследование вместо месяца отладки кривого скорингования. Карма благодарна. 😄 Почему scikit-learn считает себя лучше всех? Потому что Stack Overflow так сказал.
Scoring V2: система, которая отличает настоящие тренды от шума
# Scoring V2: когда трендам нужна оценка честности Проект **trend-analysis** разросся до того, что мы уже собирали тренды из трёх источников одновременно — Hacker News, GitHub и arXiv. Но вот беда: не все тренды одинаково полезны. Одна заметка набирает 500 апвотов за счёт сенсационного заголовка, другая медленно растёт, потому что действительно важна. Третья вообще сплошь переподсказывается из десяти агрегаторов. Нужна была система, которая не просто считает, что популярнее, а понимает, *почему* это актуально и стоит ли на это вообще обращать внимание. Задача была чёткая: построить **Scoring V2** — систему метрик, которая будет ставить каждому тренду две оценки (по 100-балльной шкале) и выдавать конкретную рекомендацию. Не просто «это популярно», а **ACT_NOW** («действуй сейчас!»), **MONITOR** («присматриваем»), **EVERGREEN** («это на века») или **IGNORE** («не трать время»). Первым делом разобрались с метриками. **Urgency** — это по сути скорость роста: насколько быстро тренд набирает обороты в последние часы. **Quality** — это честность источника и уникальность. Вот здесь и пригодилась идея с **Tavily**: мы начали считать количество уникальных доменов, которые цитируют эту новость. Если одну статью перепостили на 50 агрегаторских сайтах, но всего там одна оригинальная ссылка — это ненастоящий тренд, это просто вирусное перепосчикание. Реализовали **TavilyAdapter** с методами для подсчёта цитирований и фильтрации агрегаторов. В конфигах добавили шаблоны для распознавания паттернов типичных переупаковщиков новостей — Medium, Dev.to, Hashnode и прочих. **TrendScorer** теперь рассчитывает обе метрики и выбирает рекомендацию по простой логике: если urgency высокий И quality высокий — то ACT_NOW, если только один из них — MONITOR, и так далее. На фронтенде добавили новые компоненты — **RecommendationBadge** показывает рекомендацию цветом и текстом, а **UrgencyQualityIcons** визуализирует обе оценки сразу. Самое интересное: раньше источники были просто счётчиками («30 упоминаний»), теперь это массивы URL-ов, по которым можно кликнуть и увидеть, где именно упоминается тренд. Навигация в разделе Categories теперь работает через URL-параметры — появилась возможность нормально использовать кнопку назад в браузере. **Неочевидный факт о системах рекомендаций:** большинство разработчиков ошибочно считают, что стоит комбинировать все метрики в один скор и сортировать по нему. На деле гораздо полезнее иметь несколько ортогональных метрик (которые не зависят друг от друга) и давать юзеру выбор, на что смотреть. Плюс конкретные рекомендации (вроде ACT_NOW) куда понятнее, чем абстрактный скор 7.3 из 10. В итоге получилась система, которая не просто шумит о популярности, а реально помогает разобраться в том, что сейчас происходит в IT. Весь код, логика и даже типичные ловушки документировали в **CHANGELOG.md** и отдельных markdown-ах про Scoring V2 и подход с Tavily. Следующий шаг — добавить машинное обучение, чтобы baseline-ы для цитаций настраивались автоматически. 😄 Документация V2 получилась более объёмной, чем сам код, но это не баг, это фича — значит, потом будет меньше вопросов.
Копируй из Word без мусора: 73 теста для идеального paste
# Как перетащить HTML из Word прямо в редактор: история о 73 тестах и пути до конца Разработчик столкнулся с классической задачей: пользователи копируют текст из Google Docs и Word, вставляют в редактор, а получают хаос из стилей и тегов. Нужна была полноценная система конвертации HTML из буфера обмена в понятный редактору формат. Решение представляло собой цепь обработки данных, которая превращает сырой HTML в аккуратный markdown. **ClipboardEvent → cleanPastedHtml → parseHtmlToMarkdown → markdownToDocument → insertRunsAtCursor** — звучит как сценарий фильма про спасение данных, но на деле это elegantly выстроенный pipeline, где каждый этап отвечает за свою задачу. Первый этап очищает HTML от мусора браузерных расширений, второй парсит его в markdown, третий преобразует markdown в структуру документа редактора, и финальный вставляет текст в нужное место. Параллельно были добавлены два новых плагина. **StrikethroughPlugin** обрабатывает зачёркивание текста (~~текст~~ преобразуется в `<del>`), а **HrPlugin** работает с горизонтальными линиями (три дефиса становятся `<hr>`). Эти маленькие помощники часто забывают в редакторах, но они критичны для пользователей, которые привыкли к полноценной разметке. Сложность была в деталях. Google Docs и Word добавляют в HTML слои стилей и вспомогательных атрибутов, которые нужно умело отфильтровать. Таблицы в формате GitHub Flavored Markdown требуют особой обработки, вложенные списки — своего алгоритма. Разработчик должен был учесть все эти нюансы и при этом сохранить чистоту кода. Здесь пригодилось основательное тестирование. **73 теста**, где 56 уже существовали, а 15 добавились специально для paste-функционала, плюс 2 для inline-клавиатуры. Это не просто цифра в коммите — это гарантия, что система работает с Google Docs, Word, обычным текстом и экзотическими HTML-конструкциями. Каждый зелёный тест — это отсутствие потенциального баг-репорта в будущем. **Интересный факт**: история буфера обмена в браузерах началась ещё с Internet Explorer 5, но вот полноценный Clipboard API появился совсем недавно. Раньше разработчикам приходилось использовать `execCommand('paste')` и надеяться на лучшее. Современный подход с `ClipboardEvent` намного надёжнее и позволяет контролировать процесс на каждом шаге. В итоге пользователи теперь могут без опасений копировать содержимое из любых источников — редактор сам разберёт HTML и выдаст чистый результат. Все 73 теста зелёные, что означает: система готова к боевым условиям. 😄 **Почему JavaScript разработчики не любят природу? Там нет консоли для отладки.**