[EPIC] МЕРА: 47 мест, где код написан, отревьюен, смержен — и ни разу не сработал на проде #2674

Open
opened 2026-08-05 19:12:32 +00:00 by bot-backend · 18 comments
Collaborator

Системный поиск после того, как за один вечер случайно нашлись четыре таких места подряд. Пять независимых линз, каждая сверяла код с прод-данными: значения перечислений, колонки с писателем но пустые, сигналы о сбоях, расписания и флаги, недостижимые условия. Все числа — с tradein-postgres, каждая находка обязана была иметь и файл:строка, и число с прода.

Найдено 47. Корневая вынесена отдельно в #2673: за всю историю продукта наружу не ушло ни одного уведомления, потому что в GlitchTip нет ни правил, ни получателей. Это и объясняет, почему остальные сорок шесть выжили.


Деньги (4)

Домовая оценка Авито всегда запрашивается как «косметический ремонт» — 2685 из 2685 запросов, при том что реальный микс ремонта в этих же домах: стандарт 4564, хороший 4118, требует ремонта 2279, отличный 1631. То есть «косметика» — лишь 36% реальных лотов. Маппинг ремонта уже существует и используется поштучным путём; в домовом стоит литерал. 2.5 месяца.

Неизвестный тип дома молча уходит как «панель» — самый дешёвый класс. 363 дома из 2685 не имеют типа нигде, плюс 75 домов с типом в camelCase (monolithBrick), который не совпадает со словарём после приведения к нижнему регистру. Итог отправленного: панель 1499, кирпич 656, монолит 367, блок 163.

Монитор устаревания СберИндекса сработал 9 раз и не создал ни одного события — написан уровнем warning, а событиями становятся только error. Данные внешнего бенчмарка цен стоят с 1 июня.

Оценка Циана, седьмой источник эстиматора, умерла 29 июня — куки протухли, 37 дней ни одной строки. Ни алерта, ни следа в статусах.

Наблюдаемость (11)

Разобрано в #2673. Кроме описанного там: run_type вырожден в одно значение во всех 3236 прогонах; счётчики HTTP-запросов, ошибок, вернувшихся и исчезнувших объявлений — ноль во всех прогонах, писателя нет вообще; is_outlier не пишет ни одна строка кода, а админский показатель «помечено выбросов» рапортует ноль как достижение; метрика расхождения цен между площадками структурно недостижима — ни у одного объявления нет двух источников; статус «не найдено» у домовой оценки недостижим, потому что 404 от геокодера не обрабатывается и все 39 таких домов лежат в «ошибке».

REDIS_URL не задан в прод-окружении — кэш поиска всю жизнь стучится в localhost и получает отказ, хотя redis на хосте есть.

Потеря данных (12)

У всех 25 055 подсказок Avito IMV потеряны фотографии — парсер выбрасывает ссылку, а колонка не входит в запрос вставки. 74 дня.

cadastral_number пуст у всех 93 350 объявлений — при том что по нему стоит фильтр поиска и нулевой тир матчинга домов. Отсюда же: тир «точное совпадение по кадастру» недостижим по построению (кадастр появляется у объявления после матчинга, а не до), а тир «точное совпадение по ФИАС» — мёртвый код, ни один вызывающий не может передать нужный параметр.

В дневной истории объявлений статус «снято» не пишется никогда — оба места вызова захардкожены на «активно». Значит дата снятия объявления, лучший доступный сигнал «скорее всего продано», не запрашивается из истории, а восстанавливается на глаз.

Журнал событий объявлений знает пять типов, писатель умеет один: изменение цены. Снятие, возврат, редактирование, первое появление — ноль за всё время.

sale_type не нормализован: Домклик пишет русские фразы, Авито и Циан — английские значения.

Тиерные коэффициенты выкупа — мёртвые таблицы: посчитаны один раз миграцией, не обновляются, не читаются.

«Строительная серия» и «высота потолков» есть в живом ответе Циана, но их нет в карте разбора — а интерфейс здания серию показывает.

Сохранение расписания из админки всегда сдвигает следующий запуск на «завтра» и игнорирует заданную периодичность; комментарий в коде утверждает обратное, а описанной в нём ветки никогда не существовало.

avito_full_load: миграция перевела источник на недельный такт, но окно ретроспективы осталось двухдневным — при успехе прогон видит двое суток из семи. avito_full_load_exhaustive: срабатывает по графику, успешным был один прогон из семи, с 21 июня ни одного.

Мёртвый код (8)

has_panorama парсится и покрыт тестами, но в базу не попадает ни одной строкой. Скачивание CSV ДОМ.РФ реализовано и покрыто тестами, но не вызывается ниоткуда. Дедуп-хелперы эстиматора живут только в тестах: 25 тестовых ссылок, ноль прод-вызовов. BROWSER_BLOCK_RESOURCES выставлен во всех трёх контейнерах, а такого имени в коде нет вообще. Пачка колонок без единого писателя. filters_hash читает несуществующий ключ ответа — 0 из 1658.

Логин Циана задекларирован в конфиге, но отсутствует в проде — автоперелогин недоступен, сбор истории стоит 38 дней. Куки Домклика протухли 3 августа, единственный сигнал — предупреждение в лог; заблаговременного предупреждения нет вовсе (Циану его сделали в #2658, Домклику — нет).


Что во всём этом общее

Ни одно из сорока семи не ловится тестами — по построению. Тест пишут на случай, который автор себе представлял; здесь ломается ровно то, чего он не представлял: ключ ответа площадки называется иначе, условие «И» недостижимо, уровень лога не долетает, колонка не попала в запрос вставки, значение захардкожено литералом.

Ловится это одним вопросом, заданным проду: сколько раз эта ветка, это поле, этот статус реально появлялись за всё время? Ответ «ноль» означает одно из трёх — мёртвый код, недостижимое условие или потерянные данные. Все три стоит увидеть.

Отсюда предложение, которое дороже любого отдельного фикса: сделать этот вопрос регулярным. Не разовый аудит, а проверка, которая раз в неделю сверяет «что код умеет писать» с «что реально появилось» и приносит разницу.

Порядок

  1. #2673 — доставка уведомлений. Пока её нет, всё остальное чинится вслепую.
  2. Четыре денежных — они прямо искажают оценку.
  3. Потеря данных — по мере того, как данные понадобятся.
  4. Мёртвый код — вычистить, чтобы не создавал иллюзию работающей функции.

Полный список с числами и файл:строка по каждой находке — в результатах прогона, приложу разбором по подзадачам.

Системный поиск после того, как за один вечер **случайно** нашлись четыре таких места подряд. Пять независимых линз, каждая сверяла код с прод-данными: значения перечислений, колонки с писателем но пустые, сигналы о сбоях, расписания и флаги, недостижимые условия. Все числа — с `tradein-postgres`, каждая находка обязана была иметь и `файл:строка`, и число с прода. **Найдено 47.** Корневая вынесена отдельно в #2673: за всю историю продукта наружу не ушло ни одного уведомления, потому что в GlitchTip нет ни правил, ни получателей. Это и объясняет, почему остальные сорок шесть выжили. --- ## Деньги (4) **Домовая оценка Авито всегда запрашивается как «косметический ремонт»** — 2685 из 2685 запросов, при том что реальный микс ремонта в этих же домах: стандарт 4564, хороший 4118, требует ремонта 2279, отличный 1631. То есть «косметика» — лишь 36% реальных лотов. Маппинг ремонта уже существует и используется поштучным путём; в домовом стоит литерал. 2.5 месяца. **Неизвестный тип дома молча уходит как «панель»** — самый дешёвый класс. 363 дома из 2685 не имеют типа нигде, плюс 75 домов с типом в camelCase (`monolithBrick`), который не совпадает со словарём после приведения к нижнему регистру. Итог отправленного: панель 1499, кирпич 656, монолит 367, блок 163. **Монитор устаревания СберИндекса сработал 9 раз и не создал ни одного события** — написан уровнем `warning`, а событиями становятся только `error`. Данные внешнего бенчмарка цен стоят с 1 июня. **Оценка Циана, седьмой источник эстиматора, умерла 29 июня** — куки протухли, 37 дней ни одной строки. Ни алерта, ни следа в статусах. ## Наблюдаемость (11) Разобрано в #2673. Кроме описанного там: `run_type` вырожден в одно значение во всех 3236 прогонах; счётчики HTTP-запросов, ошибок, вернувшихся и исчезнувших объявлений — ноль во всех прогонах, писателя нет вообще; `is_outlier` не пишет ни одна строка кода, а админский показатель «помечено выбросов» рапортует ноль как достижение; метрика расхождения цен между площадками структурно недостижима — ни у одного объявления нет двух источников; статус «не найдено» у домовой оценки недостижим, потому что 404 от геокодера не обрабатывается и все 39 таких домов лежат в «ошибке». **`REDIS_URL` не задан в прод-окружении** — кэш поиска всю жизнь стучится в localhost и получает отказ, хотя redis на хосте есть. ## Потеря данных (12) **У всех 25 055 подсказок Avito IMV потеряны фотографии** — парсер выбрасывает ссылку, а колонка не входит в запрос вставки. 74 дня. **`cadastral_number` пуст у всех 93 350 объявлений** — при том что по нему стоит фильтр поиска и нулевой тир матчинга домов. Отсюда же: тир «точное совпадение по кадастру» **недостижим по построению** (кадастр появляется у объявления после матчинга, а не до), а тир «точное совпадение по ФИАС» — мёртвый код, ни один вызывающий не может передать нужный параметр. **В дневной истории объявлений статус «снято» не пишется никогда** — оба места вызова захардкожены на «активно». Значит дата снятия объявления, лучший доступный сигнал «скорее всего продано», не запрашивается из истории, а восстанавливается на глаз. **Журнал событий объявлений** знает пять типов, писатель умеет один: изменение цены. Снятие, возврат, редактирование, первое появление — ноль за всё время. **`sale_type` не нормализован**: Домклик пишет русские фразы, Авито и Циан — английские значения. **Тиерные коэффициенты выкупа — мёртвые таблицы**: посчитаны один раз миграцией, не обновляются, не читаются. **«Строительная серия» и «высота потолков»** есть в живом ответе Циана, но их нет в карте разбора — а интерфейс здания серию показывает. **Сохранение расписания из админки всегда сдвигает следующий запуск на «завтра»** и игнорирует заданную периодичность; комментарий в коде утверждает обратное, а описанной в нём ветки никогда не существовало. **`avito_full_load`**: миграция перевела источник на недельный такт, но окно ретроспективы осталось двухдневным — при успехе прогон видит двое суток из семи. **`avito_full_load_exhaustive`**: срабатывает по графику, успешным был один прогон из семи, с 21 июня ни одного. ## Мёртвый код (8) `has_panorama` парсится и покрыт тестами, но в базу не попадает ни одной строкой. Скачивание CSV ДОМ.РФ реализовано и покрыто тестами, но не вызывается ниоткуда. Дедуп-хелперы эстиматора живут только в тестах: 25 тестовых ссылок, ноль прод-вызовов. `BROWSER_BLOCK_RESOURCES` выставлен во всех трёх контейнерах, а такого имени в коде нет вообще. Пачка колонок без единого писателя. `filters_hash` читает несуществующий ключ ответа — 0 из 1658. **Логин Циана задекларирован в конфиге, но отсутствует в проде** — автоперелогин недоступен, сбор истории стоит 38 дней. **Куки Домклика протухли 3 августа**, единственный сигнал — предупреждение в лог; заблаговременного предупреждения нет вовсе (Циану его сделали в #2658, Домклику — нет). --- ## Что во всём этом общее Ни одно из сорока семи не ловится тестами — **по построению**. Тест пишут на случай, который автор себе представлял; здесь ломается ровно то, чего он не представлял: ключ ответа площадки называется иначе, условие «И» недостижимо, уровень лога не долетает, колонка не попала в запрос вставки, значение захардкожено литералом. Ловится это одним вопросом, заданным проду: **сколько раз эта ветка, это поле, этот статус реально появлялись за всё время?** Ответ «ноль» означает одно из трёх — мёртвый код, недостижимое условие или потерянные данные. Все три стоит увидеть. Отсюда предложение, которое дороже любого отдельного фикса: **сделать этот вопрос регулярным**. Не разовый аудит, а проверка, которая раз в неделю сверяет «что код умеет писать» с «что реально появилось» и приносит разницу. ## Порядок 1. #2673 — доставка уведомлений. Пока её нет, всё остальное чинится вслепую. 2. Четыре денежных — они прямо искажают оценку. 3. Потеря данных — по мере того, как данные понадобятся. 4. Мёртвый код — вычистить, чтобы не создавал иллюзию работающей функции. Полный список с числами и `файл:строка` по каждой находке — в результатах прогона, приложу разбором по подзадачам.
Author
Collaborator

Working on this in PR #2682 — три находки класса «схема обещает, писатель не пишет»: фото/метрики подсказок IMV (25 055 строк с NULL), статус «снято» в listings_snapshots (394 704 строки константой active), четыре недостающих типа событий listing_source_events (8288 строк одного типа из пяти).

Working on this in PR #2682 — три находки класса «схема обещает, писатель не пишет»: фото/метрики подсказок IMV (25 055 строк с NULL), статус «снято» в listings_snapshots (394 704 строки константой active), четыре недостающих типа событий listing_source_events (8288 строк одного типа из пяти).
Author
Collaborator

Поправка: находка про СберИндекс была истолкована неверно

В списке денежных я написал: «монитор устаревания СберИндекса сработал 9 раз и не создал ни одного события, данные внешнего бенчмарка стоят с 1 июня». Первая половина верна, вторая — нет, и вывод из неё был неправильный.

Ревью PR #2681 разобрало механику по прод-данным:

13–16.07  тревога=1  возраст 73,74,75,76
17.07     тревога=0  возраст 46      ← день, когда отработала загрузка
18–31.07  тревога=0  возраст 47..60
01–05.08  тревога=1  возраст 61..65  ← идёт сейчас

Загрузка индекса ходит раз в 28 дней и приносит период на месяц новее, а возраст считается от первого числа покрытого месяца. Значит в момент самой свежей загрузки возраст уже 46, и к следующей естественно дорастает до 74. Порог — 60.

То есть пила 46→74 пересекает порог каждый цикл, примерно 14 дней из 28. Девять срабатываний — это не улика застоя данных, а замер нашего собственного такта загрузки.

Практический вывод меняется на противоположный: если бы правка уровня поехала как есть, главным событием новой «честной телеметрии» стал бы сигнал, который гаснет сам, без единого действия оператора. Ровно то, чего мы избегаем — ложная тревога, которая приучает не читать алерты.

Что делать вместо этого

Лучший вариант — не трогать порог, а ускорить загрузку: раз в 7 дней вместо 28 (это девять запросов к открытому API). Тогда возраст упирается примерно в 53, и порог 60 наконец начинает означать именно «Сбер перестал публиковать».

Побочно это чинит вторую вещь, которую я не заметил: у оценщика есть собственная проверка свежести бенчмарка с порогом 35 дней, и при текущем такте она срабатывает почти на каждой оценке — потому что возраст стартует с 46 и меньше уже не бывает.

Что остаётся в силе

Остальные три денежные находки не затронуты. И сама по себе проблема «предупреждение не долетает до системы событий» реальна — просто на примере СберИндекса она демонстрировалась ошибочно: там не долетало то, что и не должно было лететь.

Отдельно завожу latent-находку, всплывшую при том же разборе: ветка «неизвестный источник» в планировщике выполняется каждые 60 секунд и после #2658 в этом состоянии писала бы 1440 строк прогонов в сутки. Сейчас не срабатывала ни разу, но это мина того же класса.

## Поправка: находка про СберИндекс была истолкована неверно В списке денежных я написал: «монитор устаревания СберИндекса сработал 9 раз и не создал ни одного события, данные внешнего бенчмарка стоят с 1 июня». Первая половина верна, **вторая — нет**, и вывод из неё был неправильный. Ревью PR #2681 разобрало механику по прод-данным: ``` 13–16.07 тревога=1 возраст 73,74,75,76 17.07 тревога=0 возраст 46 ← день, когда отработала загрузка 18–31.07 тревога=0 возраст 47..60 01–05.08 тревога=1 возраст 61..65 ← идёт сейчас ``` Загрузка индекса ходит **раз в 28 дней** и приносит период на месяц новее, а возраст считается от первого числа покрытого месяца. Значит **в момент самой свежей загрузки возраст уже 46**, и к следующей естественно дорастает до 74. Порог — 60. То есть пила 46→74 пересекает порог **каждый цикл, примерно 14 дней из 28**. Девять срабатываний — это не улика застоя данных, а **замер нашего собственного такта загрузки**. Практический вывод меняется на противоположный: если бы правка уровня поехала как есть, главным событием новой «честной телеметрии» стал бы сигнал, который **гаснет сам, без единого действия оператора**. Ровно то, чего мы избегаем — ложная тревога, которая приучает не читать алерты. ### Что делать вместо этого Лучший вариант — не трогать порог, а **ускорить загрузку**: раз в 7 дней вместо 28 (это девять запросов к открытому API). Тогда возраст упирается примерно в 53, и порог 60 наконец начинает означать именно «Сбер перестал публиковать». Побочно это чинит вторую вещь, которую я не заметил: у оценщика есть собственная проверка свежести бенчмарка с порогом 35 дней, и при текущем такте она **срабатывает почти на каждой оценке** — потому что возраст стартует с 46 и меньше уже не бывает. ### Что остаётся в силе Остальные три денежные находки не затронуты. И сама по себе проблема «предупреждение не долетает до системы событий» реальна — просто на примере СберИндекса она демонстрировалась ошибочно: там не долетало то, что и не должно было лететь. Отдельно завожу latent-находку, всплывшую при том же разборе: ветка «неизвестный источник» в планировщике выполняется каждые 60 секунд и после #2658 в этом состоянии писала бы **1440 строк прогонов в сутки**. Сейчас не срабатывала ни разу, но это мина того же класса.
Author
Collaborator

Working on this in PR #2684 — четыре находки «показатель, который не может быть ненулевым» (is_outlier / счётчики scrape_runs / run_type / фильтр источников).

Working on this in PR #2684 — четыре находки «показатель, который не может быть ненулевым» (is_outlier / счётчики scrape_runs / run_type / фильтр источников).
Author
Collaborator

Working on the matching-tier findings in PR #2688 (кадастр объявлений / тир по кадастру / тир по ФИАС).

Итог замеров на проде: верхние тиры не срабатывали ни разу — 0 из 49 502 строк house_sources имеют cadastr_exact или fias_exact; всё сматчено слабее (fingerprint 58.97%, new 22.65%, geo_proximity 18.36%). Цена этого — 781 лишняя строка в houses (8.3% таблицы), 653 кластера, 6 389 объявлений на дублях одного и того же здания ГАР (риск #1772). Проверено read-only прогоном mapping-SQL дедупа: старый ключ даёт 0 пар, расширенный — 781.

Working on the matching-tier findings in PR #2688 (кадастр объявлений / тир по кадастру / тир по ФИАС). Итог замеров на проде: верхние тиры не срабатывали ни разу — 0 из 49 502 строк `house_sources` имеют `cadastr_exact` или `fias_exact`; всё сматчено слабее (fingerprint 58.97%, new 22.65%, geo_proximity 18.36%). Цена этого — **781 лишняя строка в `houses` (8.3% таблицы), 653 кластера, 6 389 объявлений** на дублях одного и того же здания ГАР (риск #1772). Проверено read-only прогоном mapping-SQL дедупа: старый ключ даёт 0 пар, расширенный — 781.
Author
Collaborator

Working on this in PR #2689 — раздел «Мёртвый код», восемь находок. Две из них оказались не мёртвым кодом, а оборванной проводкой: загрузчик ДОМ.РФ (написан с #2013, но Handler'а и строки расписания не существовало — 29 978 строк staging с одним loaded_at 12.07, то есть единственный ручной запуск) и filters_hash (Циан кладёт ключ на уровень выше, чем его читали: 139/139 сырых ответов несут хеш при 0/1658 заполненных колонок). Ещё одна подключена: has_panorama. Удалены две таблицы тиерных коэффициентов, две колонки без писателя, две дедуп-обёртки без прод-вызовов и показатель расхождения цен, который не мог быть ненулевым. BROWSER_BLOCK_RESOURCES оставлена инертной, но сервис теперь предупреждает о ней на старте — трафик от её пропажи не вырос, image глушит camoufox block_images.

Working on this in PR #2689 — раздел «Мёртвый код», восемь находок. Две из них оказались не мёртвым кодом, а оборванной проводкой: загрузчик ДОМ.РФ (написан с #2013, но Handler'а и строки расписания не существовало — 29 978 строк staging с одним loaded_at 12.07, то есть единственный ручной запуск) и filters_hash (Циан кладёт ключ на уровень выше, чем его читали: 139/139 сырых ответов несут хеш при 0/1658 заполненных колонок). Ещё одна подключена: has_panorama. Удалены две таблицы тиерных коэффициентов, две колонки без писателя, две дедуп-обёртки без прод-вызовов и показатель расхождения цен, который не мог быть ненулевым. BROWSER_BLOCK_RESOURCES оставлена инертной, но сервис теперь предупреждает о ней на старте — трафик от её пропажи не вырос, image глушит camoufox block_images.
Author
Collaborator

Сводка за сутки: 11 PR смержено, метод эпика проверен на себе

Закрыто

Деньги — 3 из 4.

  • Домовая оценка Авито больше не запрашивается как «косметический ремонт» и не отправляет неизвестный тип дома как «панель» — #2675 (хвост по camelCase — #2678).
  • Монитор устаревания СберИндекса поднялся с warning до уровня, который становится событием, — #2681. Заодно там же пришлось починить такт загрузки, иначе новый ERROR был бы ложной тревогой.
  • Четвёртая — оценка Циана — не чинится кодом, см. ниже.

Наблюдаемость. run_type, четыре счётчика без писателя, is_outlier, показатели админки, которые не могли быть ненулевыми, — #2684 и #2689. Корневая #2673 остаётся открытой и остаётся корневой.

Потеря данных. Фотографии подсказок IMV — писатель починен (#2682) и 21 480 из 25 055 строк восстановлены задним числом (#2693; остальные 3 575 невосстановимы — сырой ответ перетирался при повторной оценке). Тиры сопоставления домов — #2688. sale_type перестал быть третьим словарём — #2696. Окно ретроспективы Авито приведено к недельному такту — #2685. Сохранение расписания перестало игнорировать такт — #2694 (задето 16 из 50 источников, включая квартальный опрос Росреестра с тактом 28).

Мёртвый код. Разобран весь блок — #2689. Три места оказались не мёртвыми, а оборванными: загрузка ДОМ.РФ (не было ни строки расписания, ни обработчика), хеш фильтров Циана (читали на уровень глубже — 0 → 139 из 139), признак панорамы (парсился, не сохранялся).


Три находки оказались другим диагнозом, чем предполагалось

Это результат, а не оговорка: неверный диагноз здесь стоил бы либо удаления готовой работы, либо бесконечной задачи.

position_in_serp — не оборванная проводка, а невыразимый механизм. Колонка лежит в снимках с ключом «объявление × сутки», а позиция это свойство пары «объявление × конкретный прогон выдачи». За одни сутки в таблицу писали 13 разных прогонов от четырёх источников; внутри одного обхода объявление приезжает с разным индексом от перекрывающихся якорей. Старый ON CONFLICT осадил бы индекс последнего писателя дня — хуже NULL, потому что читалось бы как факт. Колонка подписана, тест сторожит попытку «подключить» обратно (#2694).

«Снято» в дневной истории — тоже невыразимо, и это доказано контрольной группой: событие «снято/вернулось» измеряет полноту нашего обхода, а не жизнь объявления. Источник с почти полным покрытием даёт ноль «возвратов», источник с третью покрытия — 218 в сутки.

«Серия и высота потолков не разбираются» — предпосылка ложна. Разбор работает; на экране пусто по другой причине (см. #2700).


Что метод эпика нашёл сверх списка

Проверка каждого пункта числом с прода вскрыла то, чего в исходных 47 не было:

  • #2698 — домовая оценка Авито мертва 34 дня: {"saved": 0, "errors": 35, "checked": 50} в каждом прогоне, статус done.
  • #2700 — detail-страницы Циана отдают 403 на 50 из 50 попыток пятнадцатый день; все домовые поля Циана нулевые во всех 9 457 домах.
  • #2702 — отметки времени прогонов не охватывают их работу: 153 прогона из 487 «длятся» дольше собственного окна, пятичасовой записан как 32 миллисекунды.
  • #2703 — сторож «ноль результатов» структурно слеп к backfill-задачам: читает поле, которого нет в их счётчиках → видит 0 всегда → стрик не прерывается никогда → анти-спам «один раз на стрик» становится «один раз навсегда». Ровно поэтому 78 пустых прогонов из 158 прошли молча.
  • #2701 — 38% дневных снимков не знают своего прогона: три вызова из девяти теряют run_id, в двух значение лежит в той же функции.
  • #2699 — высота потолков в двух колонках: эстиматор читает одну, карта приоритетов проекта каноном называет другую, 855 значений Циана не видит никто.
  • #2690 — попытка схлопнуть 781 дубль домов остановлена: «независимый идентификатор здания» оказался детерминированной функцией того же адреса, то есть обходом гео-стража, а не вторым наблюдением. 764 из 781 пары страж заблокировал бы.

Что упирается во владельца — #2704

Собрано в одно место: получатель уведомлений, учётные записи Циана и Домклика, ключ Яндекс-геокодера, токен ротации прокси, два решения (#2661, #2680) и статус бана ДОМ.РФ. Восемь пунктов, ни один не чинится кодом.

Существенно, что два из них блокируют проверку уже сделанной работы: правки разбора Циана нельзя подтвердить, пока идёт 403, а починенные сторожа будут срабатывать в пустоту, пока не задан получатель.


Про предложение сделать вопрос регулярным

Тело эпика предлагает проверку, которая раз в неделю сверяет «что код умеет писать» с «что реально появилось». Сутки работы дали материал, уточняющий это предложение: проверять надо не только «появилось ли», но и «почему ноль». За сутки три нуля означали три разные вещи — «уже починено вчера», «заблокировано выше по потоку», «структурно неприменимо к этому источнику» — и требовали трёх разных действий. Ноль без названной причины запускает не ту работу.

# Сводка за сутки: 11 PR смержено, метод эпика проверен на себе ## Закрыто **Деньги — 3 из 4.** - Домовая оценка Авито больше не запрашивается как «косметический ремонт» и не отправляет неизвестный тип дома как «панель» — #2675 (хвост по camelCase — #2678). - Монитор устаревания СберИндекса поднялся с `warning` до уровня, который становится событием, — #2681. Заодно там же пришлось починить такт загрузки, иначе новый `ERROR` был бы ложной тревогой. - Четвёртая — оценка Циана — **не чинится кодом**, см. ниже. **Наблюдаемость.** `run_type`, четыре счётчика без писателя, `is_outlier`, показатели админки, которые не могли быть ненулевыми, — #2684 и #2689. Корневая #2673 остаётся открытой и остаётся корневой. **Потеря данных.** Фотографии подсказок IMV — писатель починен (#2682) и **21 480 из 25 055 строк восстановлены задним числом** (#2693; остальные 3 575 невосстановимы — сырой ответ перетирался при повторной оценке). Тиры сопоставления домов — #2688. `sale_type` перестал быть третьим словарём — #2696. Окно ретроспективы Авито приведено к недельному такту — #2685. Сохранение расписания перестало игнорировать такт — #2694 (задето 16 из 50 источников, включая квартальный опрос Росреестра с тактом 28). **Мёртвый код.** Разобран весь блок — #2689. Три места оказались не мёртвыми, а оборванными: загрузка ДОМ.РФ (не было ни строки расписания, ни обработчика), хеш фильтров Циана (читали на уровень глубже — **0 → 139 из 139**), признак панорамы (парсился, не сохранялся). --- ## Три находки оказались другим диагнозом, чем предполагалось Это результат, а не оговорка: неверный диагноз здесь стоил бы либо удаления готовой работы, либо бесконечной задачи. **`position_in_serp` — не оборванная проводка, а невыразимый механизм.** Колонка лежит в снимках с ключом «объявление × сутки», а позиция это свойство пары «объявление × конкретный прогон выдачи». За одни сутки в таблицу писали 13 разных прогонов от четырёх источников; внутри одного обхода объявление приезжает с разным индексом от перекрывающихся якорей. Старый `ON CONFLICT` осадил бы индекс последнего писателя дня — **хуже NULL**, потому что читалось бы как факт. Колонка подписана, тест сторожит попытку «подключить» обратно (#2694). **«Снято» в дневной истории — тоже невыразимо**, и это доказано контрольной группой: событие «снято/вернулось» измеряет полноту нашего обхода, а не жизнь объявления. Источник с почти полным покрытием даёт ноль «возвратов», источник с третью покрытия — 218 в сутки. **«Серия и высота потолков не разбираются» — предпосылка ложна.** Разбор работает; на экране пусто по другой причине (см. #2700). --- ## Что метод эпика нашёл сверх списка Проверка каждого пункта числом с прода вскрыла то, чего в исходных 47 не было: - **#2698** — домовая оценка Авито мертва 34 дня: `{"saved": 0, "errors": 35, "checked": 50}` в каждом прогоне, статус `done`. - **#2700** — detail-страницы Циана отдают **403 на 50 из 50** попыток пятнадцатый день; все домовые поля Циана нулевые во всех 9 457 домах. - **#2702** — отметки времени прогонов не охватывают их работу: **153 прогона из 487** «длятся» дольше собственного окна, пятичасовой записан как **32 миллисекунды**. - **#2703** — сторож «ноль результатов» структурно слеп к backfill-задачам: читает поле, которого нет в их счётчиках → видит 0 всегда → стрик не прерывается никогда → анти-спам «один раз на стрик» становится «один раз навсегда». Ровно поэтому 78 пустых прогонов из 158 прошли молча. - **#2701** — 38% дневных снимков не знают своего прогона: три вызова из девяти теряют `run_id`, в двух значение лежит в той же функции. - **#2699** — высота потолков в двух колонках: эстиматор читает одну, карта приоритетов проекта каноном называет другую, 855 значений Циана не видит никто. - **#2690** — попытка схлопнуть 781 дубль домов остановлена: «независимый идентификатор здания» оказался детерминированной функцией того же адреса, то есть обходом гео-стража, а не вторым наблюдением. 764 из 781 пары страж заблокировал бы. --- ## Что упирается во владельца — #2704 Собрано в одно место: получатель уведомлений, учётные записи Циана и Домклика, ключ Яндекс-геокодера, токен ротации прокси, два решения (#2661, #2680) и статус бана ДОМ.РФ. Восемь пунктов, ни один не чинится кодом. Существенно, что **два из них блокируют проверку уже сделанной работы**: правки разбора Циана нельзя подтвердить, пока идёт 403, а починенные сторожа будут срабатывать в пустоту, пока не задан получатель. --- ## Про предложение сделать вопрос регулярным Тело эпика предлагает проверку, которая раз в неделю сверяет «что код умеет писать» с «что реально появилось». Сутки работы дали материал, уточняющий это предложение: **проверять надо не только «появилось ли», но и «почему ноль»**. За сутки три нуля означали три разные вещи — «уже починено вчера», «заблокировано выше по потоку», «структурно неприменимо к этому источнику» — и требовали трёх разных действий. Ноль без названной причины запускает не ту работу.
Author
Collaborator

Что проверять завтра: девять предсказаний с точными критериями

За сутки смержено много такого, что нельзя было проверить в момент мержа — производитель бежит по расписанию. Чтобы завтрашняя проверка была проверкой, а не пересказом, фиксирую критерии заранее. Предсказание, записанное после факта, ничего не стоит.

Все времена UTC.

когда что критерий приёмки что означает провал
07.08 00:39 newbuilding_enrich продвижение сигнала живости ≫ 0 (было 0.016–0.032 с) сигнал шлётся всё ещё раз в начале
07.08 01:54 yandex_detail_backfill enriched > 0 и unenrichable_pending ≈ 3 535 гейт по виду ссылки не отсекает то, что должен
07.08 02:02 cian_history_backfill статус skipped, причина «куки протухли» если НЕ skipped — честный отказ сломался
07.08 06:44–07:49 деактивация по TTL домклик пропущен (skipped_unhealthy=1, 62 подтверждения против порога 200); avito/cian/yandex исполняются если домклик исполнится — гейт здоровья не работает и выкосит живое
07.08 07:54 avito_detail_backfill строка «причина: …» в scrape_runs.error причина по-прежнему невосстановима
08.08 04:15 house_dedup_merge 93 слияния и столько же записей в журнале; откат по журналу выполним журнал не пишется в той же транзакции
08.08 (такт 3 дня) house_imv_backfill saved > 0, NS_ERROR_PROXY_BAD_GATEWAY исчез из причин прокси-пул подключён не там
10.08 14:16 avito_full_load хотя бы один прогон доходит до done тракт не починен, решение по такту принимать не на чем
без расписания браузерная проба узла первый непригодный узел получает browser_unfit_since проба исполняется, но по назначению не срабатывает

Отдельно про последнюю строку

Браузерная проба прокси сегодня отработала (browser_checked: 4, browser_ok: 4) — то есть исполняется и не даёт ложных отказов. Но что она ловит непригодный узел, не доказано: сейчас все четыре здоровы по обоим трактам. Это подтвердится только когда узел деградирует естественным образом — по истории такое случалось регулярно.

Держать это как «работает» было бы преувеличением. Держу как «работает, по назначению не срабатывало».

Почему список именно такой

Каждая строка — это то, что сегодня было записано словами «не проверено, а не не работает». За сутки этот оборот употреблялся девять раз, и каждый раз он был честным: код доехал, а производитель ещё не бежал. Но «не проверено» имеет срок годности: если через неделю оно так и не проверится, это уже не осторожность, а незаконченная работа.

Отсюда практика, которую предлагаю закрепить: у каждого «не проверено» должны быть дата и критерий. Без них оно превращается в вечное «вроде сделано».

# Что проверять завтра: девять предсказаний с точными критериями За сутки смержено много такого, что **нельзя было проверить в момент мержа** — производитель бежит по расписанию. Чтобы завтрашняя проверка была проверкой, а не пересказом, фиксирую критерии заранее. Предсказание, записанное после факта, ничего не стоит. Все времена UTC. | когда | что | критерий приёмки | что означает провал | |---|---|---|---| | **07.08 00:39** | `newbuilding_enrich` | продвижение сигнала живости **≫ 0** (было 0.016–0.032 с) | сигнал шлётся всё ещё раз в начале | | **07.08 01:54** | `yandex_detail_backfill` | `enriched > 0` **и** `unenrichable_pending ≈ 3 535` | гейт по виду ссылки не отсекает то, что должен | | **07.08 02:02** | `cian_history_backfill` | статус `skipped`, причина «куки протухли» | если НЕ skipped — честный отказ сломался | | **07.08 06:44–07:49** | деактивация по TTL | **домклик пропущен** (`skipped_unhealthy=1`, 62 подтверждения против порога 200); avito/cian/yandex исполняются | если домклик исполнится — гейт здоровья не работает и выкосит живое | | **07.08 07:54** | `avito_detail_backfill` | строка «причина: …» в `scrape_runs.error` | причина по-прежнему невосстановима | | **08.08 04:15** | `house_dedup_merge` | 93 слияния **и столько же записей в журнале**; откат по журналу выполним | журнал не пишется в той же транзакции | | **08.08 (такт 3 дня)** | `house_imv_backfill` | `saved > 0`, `NS_ERROR_PROXY_BAD_GATEWAY` исчез из причин | прокси-пул подключён не там | | **10.08 14:16** | `avito_full_load` | хотя бы один прогон доходит до `done` | тракт не починен, решение по такту принимать не на чем | | без расписания | браузерная проба узла | первый **непригодный** узел получает `browser_unfit_since` | проба исполняется, но по назначению не срабатывает | ## Отдельно про последнюю строку Браузерная проба прокси сегодня отработала (`browser_checked: 4, browser_ok: 4`) — то есть **исполняется и не даёт ложных отказов**. Но что она **ловит** непригодный узел, не доказано: сейчас все четыре здоровы по обоим трактам. Это подтвердится только когда узел деградирует естественным образом — по истории такое случалось регулярно. Держать это как «работает» было бы преувеличением. Держу как «работает, по назначению не срабатывало». ## Почему список именно такой Каждая строка — это то, что **сегодня было записано словами «не проверено, а не не работает»**. За сутки этот оборот употреблялся девять раз, и каждый раз он был честным: код доехал, а производитель ещё не бежал. Но «не проверено» имеет срок годности: если через неделю оно так и не проверится, это уже не осторожность, а незаконченная работа. Отсюда практика, которую предлагаю закрепить: **у каждого «не проверено» должны быть дата и критерий**. Без них оно превращается в вечное «вроде сделано».
Author
Collaborator

Проверка девяти предсказаний — одной командой

Чтобы завтрашняя проверка не свелась к «посмотрим, как оно», собрал её в скрипт. Он лежит на сервере (/tmp/verify_predictions.sql) и выдаёт таблицу «критерий → факт → вердикт».

ssh gendesign 'docker exec -i tradein-postgres psql -U tradein -d tradein -f - < /tmp/verify_predictions.sql'

Сейчас (23:40 UTC, до первого прогона) он отвечает так:

критерий                                            факт                        вердикт
1. newbuilding_enrich: heartbeat движется           ждём (прогона не было)      —
2. yandex_detail_backfill: обогащает                ждём (прогона не было)      —
3. cian_history_backfill: честный пропуск           ждём (прогона не было)      —
4. TTL: домклик пропущен по гейту здоровья          ждём (прогона не было)      —
4b. TTL: avito/cian/yandex исполнились              ждём (прогонов не было)     —
5. avito_detail_backfill: причина в записи          ждём (прогона не было)      —
6. ban_kind: диагноз не по умолчанию                ждём (banned не было)       —
7. observed_at: метки различны внутри прогона       ждём (снимков не было)      —
8. Логи переживают деплой                           проверяется journalctl      см. ниже
9. Браузерная проба: поймала непригодный узел       0 непригодных из 4          ждём деградации

«Ждём» здесь — не заглушка, а различимое состояние. Скрипт специально не выдаёт «ОК» при отсутствии данных: пустая выборка и успешная проверка обязаны выглядеть по-разному, иначе получится ровно тот молчаливый пропуск, который мы сегодня разбирали трижды.

Строка 8 проверяется вне SQL — надо показать запись контейнера, которого больше нет:

ssh gendesign 'docker run --rm -v /:/host:ro alpine chroot /host sh -c \
  "TZ=UTC journalctl -t tradein-scraper -o short-iso --since \"2026-08-07 00:30\" --until \"2026-08-07 08:00\""'

Обёртка через docker нужна потому, что пользователь деплоя не входит в группы чтения журнала (снимается разово командой usermod -aG adm gendesign). И обязательно TZ=UTC: флаг вывода в UTC меняет только формат, а границы окна разбираются в локальном времени — без него команда молча показывает чужие три часа ночи.

Что скрипт НЕ проверяет

Слияние домов 08.08 и полный обход Авито 10.08 — они за пределами ближайшей ночи, для них критерии записаны в таблице выше, но кода в скрипте нет.

Строка 9 останется «ждём» неопределённо долго: она сработает только когда узел деградирует сам. Это честнее, чем считать механизм проверенным.

## Проверка девяти предсказаний — одной командой Чтобы завтрашняя проверка не свелась к «посмотрим, как оно», собрал её в скрипт. Он лежит на сервере (`/tmp/verify_predictions.sql`) и выдаёт таблицу «критерий → факт → вердикт». ```bash ssh gendesign 'docker exec -i tradein-postgres psql -U tradein -d tradein -f - < /tmp/verify_predictions.sql' ``` Сейчас (23:40 UTC, до первого прогона) он отвечает так: ``` критерий факт вердикт 1. newbuilding_enrich: heartbeat движется ждём (прогона не было) — 2. yandex_detail_backfill: обогащает ждём (прогона не было) — 3. cian_history_backfill: честный пропуск ждём (прогона не было) — 4. TTL: домклик пропущен по гейту здоровья ждём (прогона не было) — 4b. TTL: avito/cian/yandex исполнились ждём (прогонов не было) — 5. avito_detail_backfill: причина в записи ждём (прогона не было) — 6. ban_kind: диагноз не по умолчанию ждём (banned не было) — 7. observed_at: метки различны внутри прогона ждём (снимков не было) — 8. Логи переживают деплой проверяется journalctl см. ниже 9. Браузерная проба: поймала непригодный узел 0 непригодных из 4 ждём деградации ``` **«Ждём» здесь — не заглушка, а различимое состояние.** Скрипт специально не выдаёт «ОК» при отсутствии данных: пустая выборка и успешная проверка обязаны выглядеть по-разному, иначе получится ровно тот молчаливый пропуск, который мы сегодня разбирали трижды. Строка 8 проверяется вне SQL — надо показать запись контейнера, которого больше нет: ```bash ssh gendesign 'docker run --rm -v /:/host:ro alpine chroot /host sh -c \ "TZ=UTC journalctl -t tradein-scraper -o short-iso --since \"2026-08-07 00:30\" --until \"2026-08-07 08:00\""' ``` Обёртка через docker нужна потому, что пользователь деплоя не входит в группы чтения журнала (снимается разово командой `usermod -aG adm gendesign`). И **обязательно `TZ=UTC`**: флаг вывода в UTC меняет только формат, а границы окна разбираются в локальном времени — без него команда молча показывает чужие три часа ночи. ## Что скрипт НЕ проверяет Слияние домов 08.08 и полный обход Авито 10.08 — они за пределами ближайшей ночи, для них критерии записаны в таблице выше, но кода в скрипте нет. Строка 9 останется «ждём» неопределённо долго: она сработает только когда узел деградирует сам. Это честнее, чем считать механизм проверенным.
Author
Collaborator

Проверка здоровья после суток правок

За сутки смержено больше двадцати изменений, многие в скрапере. Проверил, не стало ли где-то хуже — потому что «много починили» и «система здоровее» это разные утверждения.

Прогоны:

дата     всего  done  failed  banned  skipped  zombie
02.08      94    89      1       3       0       1
03.08      87    85      0       2       0       0
04.08      71    71      0       0       0       0
05.08      77    76      0       0       0       0
06.08      86    79      2       3       1       0

Объём в норме (86 при обычных 71–94). Доля done упала с 100% до 92% — и это не регресс, а результат: failed, banned и skipped появились ровно там, где раньше молча стояло done. Пустой прогон перестал называться успешным.

Собрано объявлений:

источник   02.08   03.08   04.08   05.08   06.08
avito       2164    1012     332     227    1242
cian         278    1174      36     838    1165
yandex       520     544     689    1930    1423
domklik        0       7       3      59       0
──────────────────────────────────────────────────
итого       2962    2737    1060    3054    3830

3 830 — больше, чем в любой из четырёх предыдущих дней. Авито восстановился в 5.5 раза относительно вчера.

Честно про атрибуцию

Приписать рост конкретной правке не могу. За сутки приземлились: подключение домовой оценки к пулу прокси (#2708), повтор на той же странице в сайдкаре (#2721), браузерная проба узлов (#2736), гейт по виду ссылки у Яндекса (#2738), брейкер у Авито (#2739) — плюс чужая прокси-волна днём раньше. Разделить их вклад по одному дню нельзя, и делать вид, что можно, я не буду.

Что сказать честно: сбор не пострадал ни от одной из правок, а суммарно вырос. Это то, ради чего проверка и делалась.

Что осталось нулевым и почему

Домклик — ноль, но он на нуле всю неделю, а не с сегодня: куки протухли 3 августа, плюс антибот. Это известный блокер, ждущий владельца (#2704, п. 3), а не следствие правок.

Оговорка о методе

Пять дней — короткое окно, и 4 августа (1060) показывает, насколько велик естественный разброс. Один хороший день не доказывает устойчивого улучшения. Правильная проверка — через неделю; тогда же станет видно, держится ли восстановление Авито.

## Проверка здоровья после суток правок За сутки смержено больше двадцати изменений, многие в скрапере. Проверил, не стало ли где-то хуже — потому что «много починили» и «система здоровее» это разные утверждения. **Прогоны:** ``` дата всего done failed banned skipped zombie 02.08 94 89 1 3 0 1 03.08 87 85 0 2 0 0 04.08 71 71 0 0 0 0 05.08 77 76 0 0 0 0 06.08 86 79 2 3 1 0 ``` Объём в норме (86 при обычных 71–94). Доля `done` упала с 100% до 92% — и это **не регресс, а результат**: `failed`, `banned` и `skipped` появились ровно там, где раньше молча стояло `done`. Пустой прогон перестал называться успешным. **Собрано объявлений:** ``` источник 02.08 03.08 04.08 05.08 06.08 avito 2164 1012 332 227 1242 cian 278 1174 36 838 1165 yandex 520 544 689 1930 1423 domklik 0 7 3 59 0 ────────────────────────────────────────────────── итого 2962 2737 1060 3054 3830 ``` **3 830 — больше, чем в любой из четырёх предыдущих дней.** Авито восстановился в 5.5 раза относительно вчера. ## Честно про атрибуцию Приписать рост конкретной правке **не могу**. За сутки приземлились: подключение домовой оценки к пулу прокси (#2708), повтор на той же странице в сайдкаре (#2721), браузерная проба узлов (#2736), гейт по виду ссылки у Яндекса (#2738), брейкер у Авито (#2739) — плюс чужая прокси-волна днём раньше. Разделить их вклад по одному дню нельзя, и делать вид, что можно, я не буду. Что сказать честно: **сбор не пострадал ни от одной из правок**, а суммарно вырос. Это то, ради чего проверка и делалась. ## Что осталось нулевым и почему Домклик — ноль, но он на нуле **всю неделю**, а не с сегодня: куки протухли 3 августа, плюс антибот. Это известный блокер, ждущий владельца (#2704, п. 3), а не следствие правок. ## Оговорка о методе Пять дней — короткое окно, и 4 августа (1060) показывает, насколько велик естественный разброс. Один хороший день не доказывает устойчивого улучшения. Правильная проверка — через неделю; тогда же станет видно, держится ли восстановление Авито.
Author
Collaborator

Учёт по пунктам: что закрыто, что осталось, что оказалось неверным

Проверил замерами те находки, по состоянию которых у меня не было уверенности. Два пункта самого списка не подтвердились.

Опровергнуто заново (сегодня, замером)

«Тиерные коэффициенты — мёртвые таблицы, не читаются». Таблица содержит 369 строк и читается тремя местами: эстиматором (9 обращений), обработчиками продукта (6) и задачей обновления (9). Утверждение «не читаются» неверно. Про «не обновляются» — задача обновления существует и стоит в расписании; отдельно проверять её эффект не стал.

«BROWSER_BLOCK_RESOURCES — такого имени в коде нет вообще». Имя в коде есть, и разрешено правильно: переменная действительно не читается как рабочий флаг (с #1812), но внесена в реестр отставных, сервис предупреждает о ней на старте, а тест сторожит, чтобы она не начала снова что-то менять. Закрыто в #2689 — то есть находка была верна по сути («ни на что не влияет»), но неверна в формулировке.

Подтверждено и по-прежнему открыто

cadastral_number пуст у всех 94 108 объявлений — ровно как в списке, ничего не изменилось.

Метрика расхождения цен между площадками недостижима: объявлений, у которых больше одного источника, — ноль. Структурная невозможность подтверждена.

Статус «не найдено» у домовой оценки недостижим: домов в состоянии «ошибка» стало 46 (в списке было 39). То есть 404 от геокодера по-прежнему уезжает в общую ошибку.

Оценка Циана мертва — последняя строка 29 июня, ждёт учётной записи (#2704, п. 2).

Метод эпика: что он дал и где ошибался

Из проверенных сегодня формулировок две из шести оказались неточными, причём обе — в сторону преувеличения («не читается» вместо «читается», «имени нет» вместо «имя есть, но отставлено»). Обе неточности одного вида: вывод сделан по одному признаку (нет вызова в одном месте / нет совпадения в одном поиске), а признак оказался неполным.

Это ровно тот же промах, который за сутки повторился у меня трижды — искал по неверному признаку и получал уверенный неправильный ответ. Показательно, что метод эпика («каждое утверждение несёт число с прода») от этого не защищает: число было верным, неверной была его интерпретация.

Вывод для дальнейшего: число с прода отвечает на вопрос, который ему задали. Прежде чем строить на нём вывод, стоит проверить, тот ли вопрос — тем же способом, каким проверяют путь данных.

## Учёт по пунктам: что закрыто, что осталось, что оказалось неверным Проверил замерами те находки, по состоянию которых у меня не было уверенности. Два пункта самого списка не подтвердились. ### Опровергнуто заново (сегодня, замером) **«Тиерные коэффициенты — мёртвые таблицы, не читаются».** Таблица содержит 369 строк и **читается тремя местами**: эстиматором (9 обращений), обработчиками продукта (6) и задачей обновления (9). Утверждение «не читаются» неверно. Про «не обновляются» — задача обновления существует и стоит в расписании; отдельно проверять её эффект не стал. **«`BROWSER_BLOCK_RESOURCES` — такого имени в коде нет вообще».** Имя в коде **есть**, и разрешено правильно: переменная действительно не читается как рабочий флаг (с #1812), но внесена в реестр отставных, сервис **предупреждает о ней на старте**, а тест сторожит, чтобы она не начала снова что-то менять. Закрыто в #2689 — то есть находка была верна по сути («ни на что не влияет»), но неверна в формулировке. ### Подтверждено и по-прежнему открыто **`cadastral_number` пуст у всех 94 108 объявлений** — ровно как в списке, ничего не изменилось. **Метрика расхождения цен между площадками недостижима**: объявлений, у которых больше одного источника, — **ноль**. Структурная невозможность подтверждена. **Статус «не найдено» у домовой оценки недостижим**: домов в состоянии «ошибка» стало **46** (в списке было 39). То есть 404 от геокодера по-прежнему уезжает в общую ошибку. **Оценка Циана мертва** — последняя строка 29 июня, ждёт учётной записи (#2704, п. 2). ### Метод эпика: что он дал и где ошибался Из проверенных сегодня формулировок **две из шести** оказались неточными, причём обе — в сторону преувеличения («не читается» вместо «читается», «имени нет» вместо «имя есть, но отставлено»). Обе неточности одного вида: **вывод сделан по одному признаку** (нет вызова в одном месте / нет совпадения в одном поиске), а признак оказался неполным. Это ровно тот же промах, который за сутки повторился у меня трижды — искал по неверному признаку и получал уверенный неправильный ответ. Показательно, что метод эпика («каждое утверждение несёт число с прода») от этого не защищает: число было верным, неверной была его интерпретация. Вывод для дальнейшего: **число с прода отвечает на вопрос, который ему задали.** Прежде чем строить на нём вывод, стоит проверить, тот ли вопрос — тем же способом, каким проверяют путь данных.
Author
Collaborator

ВАЖНО для чтения завтрашних результатов: перед ночью осталcя один прокси общего назначения

Предполётная проверка в 00:25 UTC, за 14 минут до первого прогона:

id  метка                  включён  подряд отказов  посл. браузерная проба
 1  asocks-residential-1      да           0           19:03   (affinity: domclick)
 9  asocks-mobile-1          НЕТ           7           19:03
10  asocks-mobile-2           да           0           19:03
11  asocks-mobile-3          НЕТ           5           21:34

Два узла из четырёх выключились автоматически (7 и 5 отказов подряд). Узел id=1 закреплён за Домкликом и Авито не выдаётся. Остаётся один узел общего назначения на все источники.

В журнале в 00:05 — свежий httpx.ProxyError: 502 Bad Gateway, то есть отказы происходят прямо сейчас.

Что это значит для девяти критериев

Провал любого из них этой ночью может означать «не было прокси», а не «правка не работает». Это ровно та ошибка чтения, против которой весь список и составлялся, поэтому фиксирую заранее:

  • критерии 1, 3, 7 (сигнал живости, честный пропуск, метки времени) от прокси не зависят — их вердикт будет чистым;
  • критерии 2, 5 (обогащение Яндекса, причина у Авито) зависят: без прокси прогон отработает вхолостую, и «enriched=0» скажет о пуле, а не о гейте;
  • критерий 4 (гейт деактивации) зависит косвенно и опасно: если ночью нечем собирать, подтверждений станет мало у всех источников, и гейт может пропустить не только Домклик. Формально это верное срабатывание, но интерпретировать его как «гейт работает» будет преждевременно.

Ожидание по узлам

Проверка здоровья идёт в 00:35 и переспрашивает выключенные узлы — они могут вернуться так же автоматически, как ушли. Смотреть надо на состояние после 00:35, а не на нынешнее.

Отдельно: браузерная проба по всем узлам последний раз проходила в 19:03–21:34, то есть у неё свой, более редкий такт. Она пока ничего не пометила непригодным — но и не проверяла с тех пор, как начались отказы. Это и есть первый шанс критерия 9 сработать по назначению.

Корень тот же

Три узла общего назначения на все источники — теснота, о которой #2638: ротация написана и не может включиться без токена. Пока его нет, «нет свободного прокси» будет повторяться независимо от того, сколько правок мы сделаем в коде.

## ВАЖНО для чтения завтрашних результатов: перед ночью осталcя один прокси общего назначения Предполётная проверка в 00:25 UTC, за 14 минут до первого прогона: ``` id метка включён подряд отказов посл. браузерная проба 1 asocks-residential-1 да 0 19:03 (affinity: domclick) 9 asocks-mobile-1 НЕТ 7 19:03 10 asocks-mobile-2 да 0 19:03 11 asocks-mobile-3 НЕТ 5 21:34 ``` **Два узла из четырёх выключились автоматически** (7 и 5 отказов подряд). Узел id=1 закреплён за Домкликом и Авито не выдаётся. Остаётся **один** узел общего назначения на все источники. В журнале в 00:05 — свежий `httpx.ProxyError: 502 Bad Gateway`, то есть отказы происходят прямо сейчас. ## Что это значит для девяти критериев **Провал любого из них этой ночью может означать «не было прокси», а не «правка не работает».** Это ровно та ошибка чтения, против которой весь список и составлялся, поэтому фиксирую заранее: - критерии 1, 3, 7 (сигнал живости, честный пропуск, метки времени) от прокси **не зависят** — их вердикт будет чистым; - критерии 2, 5 (обогащение Яндекса, причина у Авито) зависят: без прокси прогон отработает вхолостую, и «enriched=0» скажет о пуле, а не о гейте; - критерий 4 (гейт деактивации) зависит **косвенно и опасно**: если ночью нечем собирать, подтверждений станет мало **у всех** источников, и гейт может пропустить не только Домклик. Формально это верное срабатывание, но интерпретировать его как «гейт работает» будет преждевременно. ## Ожидание по узлам Проверка здоровья идёт в 00:35 и переспрашивает выключенные узлы — они могут вернуться так же автоматически, как ушли. Смотреть надо на состояние **после** 00:35, а не на нынешнее. Отдельно: браузерная проба по всем узлам последний раз проходила в 19:03–21:34, то есть у неё свой, более редкий такт. Она пока ничего не пометила непригодным — но и не проверяла с тех пор, как начались отказы. Это и есть первый шанс критерия 9 сработать по назначению. ## Корень тот же Три узла общего назначения на все источники — теснота, о которой #2638: ротация написана и не может включиться без токена. Пока его нет, «нет свободного прокси» будет повторяться независимо от того, сколько правок мы сделаем в коде.
Author
Collaborator

Ночная проверка, промежуточный итог: подтверждено шесть из девяти

02:20 UTC.

критерий                                        факт                             вердикт
1. newbuilding_enrich: heartbeat движется        440.4 с (было 0.016-0.032)       ОК
2. yandex_detail_backfill: обогащает             198 обогащено, 0 отказов,        ОК
                                                 непригодных 3535 — точно
3. cian_history_backfill: честный пропуск        skipped + причина + стрик        ОК
7. observed_at: метки различны                   123 строки / 123 метки           ОК
8. логи переживают деплой                        контейнер 21:57, журнал с 21:30  ОК
+  загрузка ДОМ.РФ по расписанию                 первый запуск за историю         ОК (сверх списка)

4/4b. деактивация по TTL                         прогон в 06:44-07:49             ждём
5.    причина у avito_detail_backfill            прогон в 07:54                   ждём
6.    ban_kind не по умолчанию                   ждёт banned-прогона              ждём
9.    браузерная проба ловит непригодный         ждёт деградации ОПРЕДЕЛЁННОГО вида ждём

Где моя разметка помех оказалась неверной

Критерий 2 я отнёс к зависимым и предупреждал, что при одном живом прокси его ноль скажет о пуле, а не о правке. Ноля не случилось: 198 обогащений, ни одного отказа. Путь обогащения Яндекса от нехватки прокси не пострадал вовсе.

То есть разметка была избыточно осторожной. Ошибка в безопасную сторону — пометил помехой то, что помехой не оказалось; обратное (счесть чистым испорченное) стоило бы дороже. Но отметить надо: разметка помех тоже бывает неверной, и проверяется она тем же способом, что и всё остальное — фактом.

Число непригодных ссылок при этом совпало точно: предсказывал ≈3535, получилось 3535.

Уточнение к критерию 9

Формулировка «ждём деградации узла» оказалась неточной, и это выяснилось этой же ночью. Два узла деградировали — но по HTTP, то есть провалили и дешёвую пробу тоже, за что и были выключены. Браузерная проба идёт только по включённым узлам, и это верно по построению: у мёртвого для HTTP узла нечего уточнять.

Значит критерий ждёт не «деградации», а узла, который пройдёт HTTP и провалит браузер. Сегодняшняя деградация — не того вида.

Правильная формулировка ожидания: «ждём события определённого вида», иначе первая же поломка будет ошибочно принята за проверку. Я допустил эту неточность сам, в собственном критерии.

## Ночная проверка, промежуточный итог: подтверждено шесть из девяти 02:20 UTC. ``` критерий факт вердикт 1. newbuilding_enrich: heartbeat движется 440.4 с (было 0.016-0.032) ОК 2. yandex_detail_backfill: обогащает 198 обогащено, 0 отказов, ОК непригодных 3535 — точно 3. cian_history_backfill: честный пропуск skipped + причина + стрик ОК 7. observed_at: метки различны 123 строки / 123 метки ОК 8. логи переживают деплой контейнер 21:57, журнал с 21:30 ОК + загрузка ДОМ.РФ по расписанию первый запуск за историю ОК (сверх списка) 4/4b. деактивация по TTL прогон в 06:44-07:49 ждём 5. причина у avito_detail_backfill прогон в 07:54 ждём 6. ban_kind не по умолчанию ждёт banned-прогона ждём 9. браузерная проба ловит непригодный ждёт деградации ОПРЕДЕЛЁННОГО вида ждём ``` ## Где моя разметка помех оказалась неверной Критерий 2 я отнёс к **зависимым** и предупреждал, что при одном живом прокси его ноль скажет о пуле, а не о правке. Ноля не случилось: 198 обогащений, **ни одного отказа**. Путь обогащения Яндекса от нехватки прокси не пострадал вовсе. То есть разметка была избыточно осторожной. Ошибка в безопасную сторону — пометил помехой то, что помехой не оказалось; обратное (счесть чистым испорченное) стоило бы дороже. Но отметить надо: **разметка помех тоже бывает неверной, и проверяется она тем же способом, что и всё остальное — фактом.** Число непригодных ссылок при этом совпало **точно**: предсказывал ≈3535, получилось 3535. ## Уточнение к критерию 9 Формулировка «ждём деградации узла» оказалась неточной, и это выяснилось этой же ночью. Два узла деградировали — но **по HTTP**, то есть провалили и дешёвую пробу тоже, за что и были выключены. Браузерная проба идёт только по включённым узлам, и это верно по построению: у мёртвого для HTTP узла нечего уточнять. Значит критерий ждёт не «деградации», а узла, который **пройдёт HTTP и провалит браузер**. Сегодняшняя деградация — не того вида. Правильная формулировка ожидания: **«ждём события определённого вида»**, иначе первая же поломка будет ошибочно принята за проверку. Я допустил эту неточность сам, в собственном критерии.
Author
Collaborator

Критерий 2 подтверждён не только счётчиком, но и содержанием

«Обогащено 492» само по себе не доказывает пользы — можно проставить отметку о попытке и ничего не записать. Проверил, что именно легло в строки:

обогащено за ночь      492
  с описанием          490   99.6%
  с площадью кухни     467   94.9%
  с высотой потолков   398   80.9%
  с просмотрами          0   ← у Яндекса такого поля нет; пробелом НЕ является

Ноль просмотров тут ожидаем и к #2669 отношения не имеет: там чинился разбор фразы у Циана, а Яндекс просмотров не отдаёт вовсе. Отмечаю явно, чтобы этот ноль потом не был прочитан как дефект — сегодня уже трижды выяснялось, что ноль без названной причины запускает не ту работу.

Гейт по виду ссылки считает ровно то, что обещал

осталось активных без обогащения   12 603
из них с годными ссылками           9 079
разница                             3 524   ← предсказывалось ≈3 535

Расхождение в 11 строк — естественный дрейф: за ночь часть объявлений сменила состояние. Порядок и смысл совпали точно.

Оценка на будущее

При нынешнем темпе (492 за ночной прогон) очередь из 9 079 годных разбирается примерно за 18 ночей, если ничего не мешает. Это первая осмысленная оценка срока — раньше её нельзя было дать, потому что голова очереди была забита ссылками, которые разбор не принимал в принципе.

Число стоит перепроверить через неделю: темп зависит от доступности прокси, а он этой ночью был минимальным (два узла из четырёх выключены).

## Критерий 2 подтверждён не только счётчиком, но и содержанием «Обогащено 492» само по себе не доказывает пользы — можно проставить отметку о попытке и ничего не записать. Проверил, что именно легло в строки: ``` обогащено за ночь 492 с описанием 490 99.6% с площадью кухни 467 94.9% с высотой потолков 398 80.9% с просмотрами 0 ← у Яндекса такого поля нет; пробелом НЕ является ``` Ноль просмотров тут ожидаем и к #2669 отношения не имеет: там чинился разбор фразы у Циана, а Яндекс просмотров не отдаёт вовсе. Отмечаю явно, чтобы этот ноль потом не был прочитан как дефект — сегодня уже трижды выяснялось, что ноль без названной причины запускает не ту работу. ## Гейт по виду ссылки считает ровно то, что обещал ``` осталось активных без обогащения 12 603 из них с годными ссылками 9 079 разница 3 524 ← предсказывалось ≈3 535 ``` Расхождение в 11 строк — естественный дрейф: за ночь часть объявлений сменила состояние. Порядок и смысл совпали точно. ## Оценка на будущее При нынешнем темпе (492 за ночной прогон) очередь из 9 079 годных разбирается примерно за **18 ночей**, если ничего не мешает. Это первая осмысленная оценка срока — раньше её нельзя было дать, потому что голова очереди была забита ссылками, которые разбор не принимал в принципе. Число стоит перепроверить через неделю: темп зависит от доступности прокси, а он этой ночью был минимальным (два узла из четырёх выключены).
Author
Collaborator

Уточнение критерия 4 до прогона: посчитал, что гейт увидит

Критерий про деактивацию я пометил опасным — он может подтвердиться по неверной причине: если ночью нечем собирать, подтверждений станет мало у всех источников, и гейт пропустит не только Домклик. Чтобы это различить, посчитал заранее, что именно гейт увидит.

Состояние на 03:20 UTC (прогоны с 06:44 до 07:49):

источник   порог   подтверждений за 3 суток   запас    предсказание
avito       1500          1540                 +40     исполнится, НО НА ГРАНИ
cian         500          2076               +1576     исполнится
yandex       500          4042               +3542     исполнится
domklik      200            62                −138     ПРОПУЩЕН

У Авито запас 2.7%. До прогона три с половиной часа; если за это время несколько объявлений выпадут из трёхсуточного окна, он опустится ниже порога и тоже будет пропущен.

Как читать оба исхода

Домклик пропущен, остальные исполнились — предсказание сбылось полностью, гейт работает как задумано.

Пропущены Домклик и Авито — это НЕ провал гейта, а верное срабатывание на честно тонких данных: сбор по Авито этой ночью действительно был скудным (два прокси из четырёх выключены). Читать это как «гейт слишком жаден» будет ошибкой. Но и записывать в подтверждение нельзя — подтвердится не то, что проверялось.

Пропущен кто-то из Циана или Яндекса — вот это был бы настоящий провал: у них запас в три и семь раз.

Зачем это здесь

Предсказание «домклик должен быть пропущен» было верным, но недостаточным: оно не различало два разных мира, в которых оно сбывается. Числа выше различают.

Это второй раз за ночь, когда предварительный расчёт оказывается ценнее самого наблюдения: первый был с разметкой помех, где я ошибся в безопасную сторону и узнал об этом только потому, что разметка существовала.

## Уточнение критерия 4 до прогона: посчитал, что гейт увидит Критерий про деактивацию я пометил **опасным** — он может подтвердиться по неверной причине: если ночью нечем собирать, подтверждений станет мало у всех источников, и гейт пропустит не только Домклик. Чтобы это различить, посчитал заранее, что именно гейт увидит. Состояние на 03:20 UTC (прогоны с 06:44 до 07:49): ``` источник порог подтверждений за 3 суток запас предсказание avito 1500 1540 +40 исполнится, НО НА ГРАНИ cian 500 2076 +1576 исполнится yandex 500 4042 +3542 исполнится domklik 200 62 −138 ПРОПУЩЕН ``` **У Авито запас 2.7%.** До прогона три с половиной часа; если за это время несколько объявлений выпадут из трёхсуточного окна, он опустится ниже порога и тоже будет пропущен. ## Как читать оба исхода **Домклик пропущен, остальные исполнились** — предсказание сбылось полностью, гейт работает как задумано. **Пропущены Домклик и Авито** — это **НЕ провал гейта**, а верное срабатывание на честно тонких данных: сбор по Авито этой ночью действительно был скудным (два прокси из четырёх выключены). Читать это как «гейт слишком жаден» будет ошибкой. Но и записывать в подтверждение нельзя — подтвердится не то, что проверялось. **Пропущен кто-то из Циана или Яндекса** — вот это был бы настоящий провал: у них запас в три и семь раз. ## Зачем это здесь Предсказание «домклик должен быть пропущен» было верным, но недостаточным: оно не различало два разных мира, в которых оно сбывается. Числа выше различают. Это второй раз за ночь, когда предварительный расчёт оказывается ценнее самого наблюдения: первый был с разметкой помех, где я ошибся в безопасную сторону и узнал об этом только потому, что разметка существовала.
Author
Collaborator

Поправка к моему же расчёту: я считал не тем запросом, что гейт

Час назад я выложил числа, которые гейт увидит в 06:44. Решил проверить их тем же путём, каким идёт код — и нашёл, что считал иначе.

Гейт (_build_confirmations_sql) берёт:

  • колонку свежести из параметров источника — у Домклика это scraped_at, а не last_seen_at;
  • фильтр по сегменту, когда сегменты заданы — у Циана и Яндекса только vtorichka.

Мой запрос брал last_seen_at для всех и без фильтра по сегменту.

источник                                 порог   было у меня   верно    запас
avito    (last_seen_at, все сегменты)     1500       1540        1672    +11.5%
cian     (last_seen_at, ТОЛЬКО vtorichka)  500       2076        1613     ×3.2
yandex   (last_seen_at, ТОЛЬКО vtorichka)  500       4042        2214     ×4.4
domklik  (SCRAPED_AT, все сегменты)        200         62          59     −70%

Вывод не изменился: Домклик пропускается, три остальных исполняются. Но запасы у Циана и Яндекса вдвое скромнее, чем я написал (×3.2 и ×4.4 вместо ×4 и ×8), а у Домклика дефицит чуть глубже.

Зачем это здесь

Предсказание, посчитанное не тем запросом, что проверяемый код, — это то же самое, что проверять правку не на том пути, по которому идут данные. За сутки я допустил этот промах трижды (вызвал функцию в обход обёртки, получил ноль из-за своей экранировки, отдал владельцу сервис как чужой, потому что искал файл вместо сервиса). Здесь поймал до факта, а не после.

Оговорка о том, чего не проверил: окно в 3 суток взято из умолчания кода — в параметрах расписаний оно не задано. Если умолчание другое, все четыре числа сместятся, но соотношение с порогами вряд ли изменит знак.

## Поправка к моему же расчёту: я считал не тем запросом, что гейт Час назад я выложил числа, которые гейт увидит в 06:44. Решил проверить их **тем же путём, каким идёт код** — и нашёл, что считал иначе. Гейт (`_build_confirmations_sql`) берёт: - **колонку свежести из параметров источника** — у Домклика это `scraped_at`, а не `last_seen_at`; - **фильтр по сегменту**, когда сегменты заданы — у Циана и Яндекса только `vtorichka`. Мой запрос брал `last_seen_at` для всех и без фильтра по сегменту. ``` источник порог было у меня верно запас avito (last_seen_at, все сегменты) 1500 1540 1672 +11.5% cian (last_seen_at, ТОЛЬКО vtorichka) 500 2076 1613 ×3.2 yandex (last_seen_at, ТОЛЬКО vtorichka) 500 4042 2214 ×4.4 domklik (SCRAPED_AT, все сегменты) 200 62 59 −70% ``` **Вывод не изменился:** Домклик пропускается, три остальных исполняются. Но запасы у Циана и Яндекса вдвое скромнее, чем я написал (×3.2 и ×4.4 вместо ×4 и ×8), а у Домклика дефицит чуть глубже. ## Зачем это здесь Предсказание, посчитанное **не тем запросом**, что проверяемый код, — это то же самое, что проверять правку не на том пути, по которому идут данные. За сутки я допустил этот промах трижды (вызвал функцию в обход обёртки, получил ноль из-за своей экранировки, отдал владельцу сервис как чужой, потому что искал файл вместо сервиса). Здесь поймал до факта, а не после. Оговорка о том, чего не проверил: окно в 3 суток взято из умолчания кода — в параметрах расписаний оно не задано. Если умолчание другое, все четыре числа сместятся, но соотношение с порогами вряд ли изменит знак.
Author
Collaborator

Находка, которая уточняет сам эпик: улучшение приписали не той правке

Разбор #2625 (капча засчитывается как успех) дал результат, важный для метода, а не только для задачи.

Симптом ушёл — но не от того, что его чинило

Доля прогонов done с нулевым результатом у развёрток Циана и Яндекса:

эра done из них с нулём banned
до правки #2642 (21.07 → 04.08) 89 42 (47%) 0
после (04.08 → 10.08) 30 1 (3%) 0

Падение 42 → 1 выглядит триумфом детекта капчи из #2642. Но banned = 0 в обеих эрах — то есть детект не сработал на проде ни разу за 5.5 суток. Симптом убрали совсем другие правки: прокси-пул и разбор (#2796, #2798, #2800-#2805). Сбор просто заработал.

Правильность #2642 сегодня подтверждена только юнит-тестами. Обещанная верификация «на живом эпизоде капчи» не состоялась, потому что эпизода не было.

Это отдельный вид ошибки, которого в эпике ещё не было. Не «код не сработал» и не «метка врёт», а улучшение, приписанное не той причине. Оно опаснее обоих: механизм считается проверенным, хотя проверка не проводилась, а настоящая причина улучшения остаётся неназванной — и если она откатится, никто не поймёт, почему симптом вернулся.

Признак, по которому это ловится: счётчик самой правки не сдвинулся. Если механизм чинит X, а счётчик его срабатываний равен нулю, то X починило что-то другое.

Настоящая дыра была противоположной той, что в задаче

Задача говорила про «пустые выдачи». Честная пустота как раз отличима — валидный ответ с пустым списком. Дыра была в отказе дойти.

Прогон 3557 и ещё 27 таких за 90 суток: все якоря кончились отказом, собрано ноль, статус done. Самый показательный — развёртка Нижнего Тагила, проверил построчно:

07-15 … 07-30   done   240 с   якорей 1   ошибок 1   лотов 0

Шестнадцать суток подряд, каждый прогон ровно 240 секунд (таймаут якоря), и каждый раз «успешно». По краям — 14.07 со 155 лотами и 31.07 с 91: то есть источник был жив до и после, а посередине шестнадцать дней тишины, которую никто не мог увидеть.

Детект #2642 их не видит и правильно не видит: он считает попытки разбора, а транспортный отказ туда намеренно не попадает — «наш прокси сдох» не должно выглядеть баном площадки.

Три случая разведены

случай статус
площадка отбила (структура не извлеклась ни разу) banned
площадка честно отдала пустоту done
мы не дошли (таймаут, исключение якоря) donefailed

Признак — собственная бухгалтерия прогона: отказов не меньше числа якорей при измеренном нуле. Он доказывает, что каждый якорь кончился отказом и собрано ноль; он не доказывает, кто виноват — поэтому failed без диагноза причины, а не banned. Дёргать ротацию прокси на догадке нельзя.

На список антибот-маркеров новый страж не опирается вовсе — прямое следствие урока 09.08, где antibot_markers=none означало «наш список не знает этой блокировки», а не «блокировки нет».

Ложной тревоги нет: за 90 суток 132 прогона с отказами, но ненулевым сбором, и 37 прогонов честной пустоты остаются done. Перекрашиваются ровно 28 описанных.

## Находка, которая уточняет сам эпик: улучшение приписали не той правке Разбор #2625 (капча засчитывается как успех) дал результат, важный для метода, а не только для задачи. ### Симптом ушёл — но не от того, что его чинило Доля прогонов `done` с нулевым результатом у развёрток Циана и Яндекса: | эра | `done` | из них с нулём | `banned` | |---|---|---|---| | до правки #2642 (21.07 → 04.08) | 89 | **42 (47%)** | **0** | | после (04.08 → 10.08) | 30 | **1 (3%)** | **0** | Падение 42 → 1 выглядит триумфом детекта капчи из #2642. **Но `banned = 0` в обеих эрах** — то есть детект не сработал на проде **ни разу за 5.5 суток**. Симптом убрали совсем другие правки: прокси-пул и разбор (#2796, #2798, #2800-#2805). Сбор просто заработал. Правильность #2642 сегодня подтверждена **только юнит-тестами**. Обещанная верификация «на живом эпизоде капчи» не состоялась, потому что эпизода не было. **Это отдельный вид ошибки, которого в эпике ещё не было.** Не «код не сработал» и не «метка врёт», а **улучшение, приписанное не той причине**. Оно опаснее обоих: механизм считается проверенным, хотя проверка не проводилась, а настоящая причина улучшения остаётся неназванной — и если она откатится, никто не поймёт, почему симптом вернулся. Признак, по которому это ловится: **счётчик самой правки не сдвинулся**. Если механизм чинит X, а счётчик его срабатываний равен нулю, то X починило что-то другое. ### Настоящая дыра была противоположной той, что в задаче Задача говорила про «пустые выдачи». Честная пустота как раз отличима — валидный ответ с пустым списком. Дыра была в **отказе дойти**. Прогон 3557 и ещё 27 таких за 90 суток: все якоря кончились отказом, собрано ноль, статус `done`. Самый показательный — развёртка Нижнего Тагила, проверил построчно: ``` 07-15 … 07-30 done 240 с якорей 1 ошибок 1 лотов 0 ``` **Шестнадцать суток подряд**, каждый прогон ровно 240 секунд (таймаут якоря), и каждый раз «успешно». По краям — 14.07 со 155 лотами и 31.07 с 91: то есть источник был жив до и после, а посередине шестнадцать дней тишины, которую никто не мог увидеть. Детект #2642 их не видит **и правильно не видит**: он считает попытки *разбора*, а транспортный отказ туда намеренно не попадает — «наш прокси сдох» не должно выглядеть баном площадки. ### Три случая разведены | случай | статус | |---|---| | площадка отбила (структура не извлеклась ни разу) | `banned` | | площадка честно отдала пустоту | `done` | | **мы не дошли** (таймаут, исключение якоря) | ~~`done`~~ → **`failed`** | Признак — собственная бухгалтерия прогона: отказов не меньше числа якорей при **измеренном** нуле. Он **доказывает**, что каждый якорь кончился отказом и собрано ноль; он **не доказывает**, кто виноват — поэтому `failed` **без** диагноза причины, а не `banned`. Дёргать ротацию прокси на догадке нельзя. На список антибот-маркеров новый страж не опирается вовсе — прямое следствие урока 09.08, где `antibot_markers=none` означало «наш список не знает этой блокировки», а не «блокировки нет». Ложной тревоги нет: за 90 суток 132 прогона с отказами, но ненулевым сбором, и 37 прогонов честной пустоты остаются `done`. Перекрашиваются ровно 28 описанных.
Author
Collaborator

Проверка на проде: что из 47 живо сегодня (12.08, только чтение)

Прошло пять суток, честного текущего статуса не было ни у одной находки. Шесть проверяющих опросили прод, каждую группу отдельно перепроверял скептик, которому запрещено было сводить «вывод» и «обоснование» в один бит.

Охват 42 находки: починены 23, живы 14, проверить нельзя 3 (со сроком), неприменимы 2.

Граница этой проверки, до всего остального

Скептики перепроверили 5 находок из 42 (12%). Вывод не опровергнут ни разу — но остальные 37 держатся на одном исполнителе каждая, и это надо читать буквально, а не как «всё подтвердилось».


Опровергнутые предпосылки — самое ценное

Не менее восьми пунктов эпика были неточны или уже неверны в момент подачи.

Четыре пункта закрылись до постановки. Блок «мёртвый код и учётки» починен 06.08 (f5b39e6f, 3d38d589), эпик подан 07.08. Кэш поиска — 06.08 22:37, за сутки. Уровень сообщений монитора СберИндекса поднят с предупреждения до ошибки 06.08 (3e1b9a8b).

Механизм назван неверно — это дороже всего, потому что уводит починку не туда:

в эпике на самом деле
«кадастр квартиры не пишется, парсер выбрасывает» тракт полон от парсера до вставки; ни одна из пяти площадок его не отдаёт — это не потеря данных
«высоты потолков нет в карте разбора» разбор есть с #1791; 855 значений писались в дублирующую колонку, которую оценщик не читает
«строительной серии нет в карте разбора» сопоставление есть с #2435 и покрыто тестами; не работал уровень чтения — ключ лежал на другом уровне вложенности
«расхождение цен структурно недостижимо» оборванная проводка: функция связывания рабочая, боевых вызовов ноль
«уровень по кадастру недостижим по построению» не «подать некуда», а «подать нечего» — приёмник сквозной, площадки молчат

Путаница величин. «Оценка Циана мертва 37 дней» — это две разные длительности: нуля данных было 42.5 суток, а 37 — молчание тревоги.

«Событиями становятся только ошибки» для учётных данных Циана исторически неверно: карточка уровня «предупреждение» имеет счётчик 63 за 30.05-30.06 — предупреждения долетали и внезапно замолчали. Механизм отказа другой. (Для монитора СберИндекса предпосылка подтверждена точно: 9 срабатываний → 0 событий, после правки 7 из 7.)

Однородные на вид пункты разного размера. Ремонт был жёстким литералом (100% на 2633 запросах), тип дома — значением по умолчанию только у нераспознанных (55.9%); правка сдвинула долю панели на 15.5 пп, а не обнулила.


У нуля причина: две находки надо ЗАКРЫТЬ, а не чинить

ноль число почему не дефект
кадастр квартиры у объявлений 0 из 99 167 (все пять площадок) площадки его не публикуют; приёмник рабочий
события «снято/вернулось» 0 за всю историю признак активности меряет «мы видели», а не «объявление есть»: домклик при 99.9% покрытия — 0 возвратов, яндекс при 34-43% — до 155 в сутки, авито 13.07 — 3023 «снятия» при базовой ставке 1-4. Точность ≈4%

Вывод верен, обоснование неверно — 2 случая

Это не придирка: правка по неверному обоснованию не сработает, а замер по слепому признаку даёт зелёный результат без предмета.

«Правка расписаний не срабатывала ни разу за 6 суток» — выведено из совпадения меток «изменено» и «последний прогон». Но метку «изменено» переписывает каждый прогон (три места планировщика). Все включённые расписания прогонялись за 1-3 суток, значит любое сохранение раньше последнего прогона физически неотличимо от его отсутствия. Правильная формулировка — «срабатывания ненаблюдаемы», а не «их было ноль». Вывод при этом верен: 25 из 25 расписаний имеют корректный интервал.

«13 трёхдневных и 11 недельных» — не воспроизводится ни при каком группировании. Факт: 17 трёхдневных, 7 недельных, 1 двадцативосьмидневное. Итог (25) верен, слагаемые нет.


Три следующих шага, по убыванию цены

1. Переоценить 2633 домовые оценки на неверных параметрах. 98.2% всей истории (2633 из 2680) посчитаны с зашитым «косметическим» ремонтом, у 1471 тип дома подставлен «панелью». Правка исправила 47 строк (1.8%). При нынешнем темпе естественное обновление займёт свыше 300 суток.

2. Порог свежести внешнего ориентира. Взято в работу отдельно — см. ниже.

3. Две строки учётных данных Циана в окружение прода. Цена уплачена: 41 день пропусков сбора, 42.5 суток нуля данных. Ручка автоперехода написана и покрыта тестами, отвечает 400, потому что 0 из 2 переменных заданы. Нынешняя сессия жива до 09.09 — запас 28 суток. Действие ваше, ключи я не подбираю.

Ближайшее за тройкой: обход Домклика — 59 объявлений в сутки против 5364 у Циана, его восстановление разблокирует три отложенные проверки.

## Проверка на проде: что из 47 живо сегодня (12.08, только чтение) Прошло пять суток, честного текущего статуса не было ни у одной находки. Шесть проверяющих опросили прод, каждую группу отдельно перепроверял скептик, которому запрещено было сводить «вывод» и «обоснование» в один бит. **Охват 42 находки: починены 23, живы 14, проверить нельзя 3 (со сроком), неприменимы 2.** ### Граница этой проверки, до всего остального Скептики перепроверили **5 находок из 42 (12%)**. Вывод не опровергнут ни разу — но остальные 37 держатся на одном исполнителе каждая, и это надо читать буквально, а не как «всё подтвердилось». --- ## Опровергнутые предпосылки — самое ценное **Не менее восьми пунктов эпика были неточны или уже неверны в момент подачи.** **Четыре пункта закрылись до постановки.** Блок «мёртвый код и учётки» починен 06.08 (f5b39e6f, 3d38d589), эпик подан 07.08. Кэш поиска — 06.08 22:37, за сутки. Уровень сообщений монитора СберИндекса поднят с предупреждения до ошибки 06.08 (3e1b9a8b). **Механизм назван неверно — это дороже всего, потому что уводит починку не туда:** | в эпике | на самом деле | |---|---| | «кадастр квартиры не пишется, парсер выбрасывает» | тракт полон от парсера до вставки; **ни одна из пяти площадок его не отдаёт** — это не потеря данных | | «высоты потолков нет в карте разбора» | разбор есть с #1791; 855 значений писались в **дублирующую колонку**, которую оценщик не читает | | «строительной серии нет в карте разбора» | сопоставление есть с #2435 и покрыто тестами; не работал уровень **чтения** — ключ лежал на другом уровне вложенности | | «расхождение цен структурно недостижимо» | **оборванная проводка**: функция связывания рабочая, боевых вызовов ноль | | «уровень по кадастру недостижим по построению» | не «подать некуда», а **«подать нечего»** — приёмник сквозной, площадки молчат | **Путаница величин.** «Оценка Циана мертва 37 дней» — это две разные длительности: нуля данных было **42.5 суток**, а 37 — молчание тревоги. **«Событиями становятся только ошибки»** для учётных данных Циана исторически неверно: карточка уровня «предупреждение» имеет счётчик 63 за 30.05-30.06 — предупреждения долетали и внезапно замолчали. Механизм отказа другой. (Для монитора СберИндекса предпосылка подтверждена точно: 9 срабатываний → 0 событий, после правки 7 из 7.) **Однородные на вид пункты разного размера.** Ремонт был жёстким литералом (100% на 2633 запросах), тип дома — значением по умолчанию только у нераспознанных (55.9%); правка сдвинула долю панели на 15.5 пп, а не обнулила. --- ## У нуля причина: две находки надо ЗАКРЫТЬ, а не чинить | ноль | число | почему не дефект | |---|---|---| | кадастр квартиры у объявлений | 0 из 99 167 (все пять площадок) | площадки его не публикуют; приёмник рабочий | | события «снято/вернулось» | 0 за всю историю | признак активности меряет «мы видели», а не «объявление есть»: домклик при 99.9% покрытия — 0 возвратов, яндекс при 34-43% — до 155 в сутки, авито 13.07 — 3023 «снятия» при базовой ставке 1-4. Точность ≈4% | --- ## Вывод верен, обоснование неверно — 2 случая Это не придирка: правка по неверному обоснованию не сработает, а замер по слепому признаку даёт зелёный результат без предмета. **«Правка расписаний не срабатывала ни разу за 6 суток»** — выведено из совпадения меток «изменено» и «последний прогон». Но метку «изменено» переписывает **каждый прогон** (три места планировщика). Все включённые расписания прогонялись за 1-3 суток, значит любое сохранение раньше последнего прогона физически неотличимо от его отсутствия. Правильная формулировка — **«срабатывания ненаблюдаемы»**, а не «их было ноль». Вывод при этом верен: 25 из 25 расписаний имеют корректный интервал. **«13 трёхдневных и 11 недельных»** — не воспроизводится ни при каком группировании. Факт: 17 трёхдневных, 7 недельных, 1 двадцативосьмидневное. Итог (25) верен, слагаемые нет. --- ## Три следующих шага, по убыванию цены **1. Переоценить 2633 домовые оценки на неверных параметрах.** 98.2% всей истории (2633 из 2680) посчитаны с зашитым «косметическим» ремонтом, у 1471 тип дома подставлен «панелью». Правка исправила 47 строк (1.8%). При нынешнем темпе естественное обновление займёт **свыше 300 суток**. **2. Порог свежести внешнего ориентира.** Взято в работу отдельно — см. ниже. **3. Две строки учётных данных Циана в окружение прода.** Цена уплачена: 41 день пропусков сбора, 42.5 суток нуля данных. Ручка автоперехода написана и покрыта тестами, отвечает 400, потому что **0 из 2** переменных заданы. Нынешняя сессия жива до **09.09** — запас 28 суток. Действие ваше, ключи я не подбираю. Ближайшее за тройкой: обход Домклика — **59 объявлений в сутки против 5364 у Циана**, его восстановление разблокирует три отложенные проверки.
Author
Collaborator

Поправка к двум денежным пунктам: они разного размера, и не того, что заявлено

Замерил оба против истины, а не по счётчику отправленного. Оказалось, что пункты, поданные как равные, различаются в шестнадцать раз — и я сам вчера повторил преувеличение в сводке для владельца.

Тип дома — дефект в 2.8%, а не в 55.9%

Эпик: «неизвестный тип дома молча уходит как панель… панель 1499». Верно как счётчик отправленного. Но «отправлено панель» и «отправлено неверно» — разные утверждения.

Сверка house_imv_evaluations (эра до 06.08, 2633 строки) с houses.house_type, с применением словаря самого кода (_HOUSE_TYPE_TO_IMV: monolith→monolithic, monolith_brick→monolithic):

домов доля
истина неизвестна — сказать нечего 1658 63.0%
верно 901 34.2%
доказуемо неверно 74 2.8%

Состав ошибок: панель вместо монолита 46, панель вместо кирпича 7, блок вместо монолита 6, остальное единицами.

Ловушка, на которой я сначала ошибся вдвое. Без применения словаря «несовпадений» получается 236 — но 136 из них это пары monolithic (наш запрос) против monolith (наша же колонка), то есть разница вокабуляров, а не ошибка. Сверять надо после отображения, которым пользуется код, иначе меряешь собственный словарь.

Отдельно: 1658 домов с неизвестным типом — это «неприменимо», а не «испорчено». Переоценка им ничего не даст, результат будет тот же.

Ремонт — дефект в 46.3%, и он СМЕЩЁН ВНИЗ

Здесь стоял литерал 'cosmetic' у всех 2633. Сверка с модой repair_state по объявлениям того же дома (needs_repair→required, standard→cosmetic, good→euro, excellent→designer):

домов доля
моды нет — сказать нечего 444 16.9%
литерал случайно совпал 971 36.9%
литерал неверен 1218 46.3%

И главное — у ошибки есть направление:

мода «хороший»   659  → спрашивали дешевле, чем дом есть
мода «отличный»  286  → спрашивали дешевле
                 ───
                 945  занижено (77.6% всех ошибок)
мода «требует ремонта»  273  → спрашивали дороже

То есть якорь систематически занижен: у Авито спрашивали цену более дешёвого класса отделки, чем у дома на самом деле, в 945 случаях из 2633 (35.9%).

Почему это меняет порядок работ

Эпик перечислял оба пункта в блоке «Деньги» как однородные. Замер говорит: тип дома — 2.8% и без систематического знака, ремонт — 46.3% со смещением вниз. Чинить и переоценивать надо ради второго; первый закрывается почти целиком тем, что правка уже смержена.

Третье, чего в эпике не было

У якоря в оценке (estimator.py:1179) нет гейта свежести вообще — только recommended_price > 0 и band-guard по комнатам/площади, а fetched_at служит лишь последним ключом сортировки. При этом докстринг функции утверждает: «популирована (~2951 домов, fresh)».

Таблица не свежая: 2633 строки из 2680 старше 40 суток и посчитаны на литерале. Ложное утверждение в докстринге переживает правку и вводит в заблуждение следующего читателя — оно само по себе дефект.

Взято в работу отдельной веткой: замерить денежный сдвиг и выбрать между гейтом эры и приоритетной переоценкой по числу, а не по объёму работы. При нынешнем темпе (22-25 оценок за прогон, прогон раз в ~3 суток) полная переоценка 2633 домов заняла бы свыше 300 суток.

## Поправка к двум денежным пунктам: они разного размера, и не того, что заявлено Замерил оба против **истины**, а не по счётчику отправленного. Оказалось, что пункты, поданные как равные, различаются в шестнадцать раз — и я сам вчера повторил преувеличение в сводке для владельца. ### Тип дома — дефект в 2.8%, а не в 55.9% Эпик: «неизвестный тип дома молча уходит как панель… панель 1499». Верно как счётчик **отправленного**. Но «отправлено панель» и «отправлено неверно» — разные утверждения. Сверка `house_imv_evaluations` (эра до 06.08, 2633 строки) с `houses.house_type`, **с применением словаря самого кода** (`_HOUSE_TYPE_TO_IMV`: monolith→monolithic, monolith_brick→monolithic): | | домов | доля | |---|---:|---:| | истина неизвестна — сказать нечего | 1658 | 63.0% | | верно | 901 | 34.2% | | **доказуемо неверно** | **74** | **2.8%** | Состав ошибок: панель вместо монолита 46, панель вместо кирпича 7, блок вместо монолита 6, остальное единицами. **Ловушка, на которой я сначала ошибся вдвое.** Без применения словаря «несовпадений» получается 236 — но 136 из них это пары `monolithic` (наш запрос) против `monolith` (наша же колонка), то есть **разница вокабуляров, а не ошибка**. Сверять надо после отображения, которым пользуется код, иначе меряешь собственный словарь. Отдельно: 1658 домов с неизвестным типом — это «неприменимо», а не «испорчено». Переоценка им ничего не даст, результат будет тот же. ### Ремонт — дефект в 46.3%, и он СМЕЩЁН ВНИЗ Здесь стоял литерал `'cosmetic'` у всех 2633. Сверка с модой `repair_state` по объявлениям **того же дома** (needs_repair→required, standard→cosmetic, good→euro, excellent→designer): | | домов | доля | |---|---:|---:| | моды нет — сказать нечего | 444 | 16.9% | | литерал случайно совпал | 971 | 36.9% | | **литерал неверен** | **1218** | **46.3%** | И главное — **у ошибки есть направление**: ``` мода «хороший» 659 → спрашивали дешевле, чем дом есть мода «отличный» 286 → спрашивали дешевле ─── 945 занижено (77.6% всех ошибок) мода «требует ремонта» 273 → спрашивали дороже ``` То есть якорь систематически **занижен**: у Авито спрашивали цену более дешёвого класса отделки, чем у дома на самом деле, в 945 случаях из 2633 (35.9%). ### Почему это меняет порядок работ Эпик перечислял оба пункта в блоке «Деньги» как однородные. Замер говорит: тип дома — **2.8% и без систематического знака**, ремонт — **46.3% со смещением вниз**. Чинить и переоценивать надо ради второго; первый закрывается почти целиком тем, что правка уже смержена. ### Третье, чего в эпике не было У якоря в оценке (`estimator.py:1179`) **нет гейта свежести вообще** — только `recommended_price > 0` и band-guard по комнатам/площади, а `fetched_at` служит лишь последним ключом сортировки. При этом докстринг функции утверждает: «популирована (~2951 домов, **fresh**)». Таблица не свежая: 2633 строки из 2680 старше 40 суток и посчитаны на литерале. Ложное утверждение в докстринге переживает правку и вводит в заблуждение следующего читателя — оно само по себе дефект. Взято в работу отдельной веткой: замерить денежный сдвиг и выбрать между гейтом эры и приоритетной переоценкой **по числу**, а не по объёму работы. При нынешнем темпе (22-25 оценок за прогон, прогон раз в ~3 суток) полная переоценка 2633 домов заняла бы свыше 300 суток.
lekss361 added the
observability
priority/p1
scope/backend
tech-debt
tradein
labels 2026-08-16 10:25:14 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2674
No description provided.