1686 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| ae6d28d5e2 |
fix(mera): pool_timeout 30→5 с — отдельным коммитом, с триггером отката
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m4s
Единственная правка ветки, которая меняет РЕЖИМ ОТКАЗА при исчерпании пула: было «медленно» (ждём коннект до 30 с), стало «быстро с ошибкой» (5 с и `sqlalchemy.exc.TimeoutError` → 500, глобального обработчика в app/main.py нет). И едет она во все сервисы образа — backend, scraper, tgbot (tradein-mvp/docker-compose.prod.yml), для скраппера и бота обоснования в коде нет: за 29 ч логов исчерпания пула не было ни разу, проверить новое значение на проде пока не на чем. Поэтому коммит последний в ветке: ветку можно мержить без него, а на проде — откатить одной командой (`git revert`). Обоснование самого значения: чекаут коннекта нельзя прервать `asyncio.wait_for`, он занимает поток `asyncio.to_thread` целиком, а пул потоков конечен (min(32, cpu+4)) — исчерпанный пул коннектов превращается в исчерпанный пул потоков. 5 с короче самого короткого бюджета источника (8 с Yandex/Cian/ house_meta; geocode 12 с, IMV 20 с — длиннее): занятый пул деградирует ОДИН источник, а не весь запрос. ТРИГГЕР ОТКАТА (вернуть 30 с) записан в комментарии рядом со значением: любое `QueuePool limit ... timed out` в логах бэкенда ЛИБО рост failed+zombie в `scrape_runs` после деплоя. Правка комментария по ревью (L2): «вчетверо больше любого бюджета внешнего источника (8 с)» было неточно — бюджеты 8 / 12 / 20 с, перечислены явно. Гейт `test_pool_checkout_wait_shorter_than_source_budget` переехал сюда же (в коммите без `pool_timeout` он был бы красным) и читает публичный `engine.pool.timeout()` вместо приватного `pool._timeout`. Refs #3083, #3408 |
|||
| cf71825c27 |
fix(mera): отмена по бюджету оставляла осиротевший поток в чужой Session
Ревью PR #3444, M1. `_with_budget` — это `asyncio.wait_for`, а `asyncio.to_thread` отменить нельзя: снимается только ожидание со стороны loop'а. Корутина умирает, поток продолжает работать с ТОЙ ЖЕ `Session`, а вызывающий тем временем идёт дальше по своим шагам ПО ТОЙ ЖЕ сессии — следующий источник, `_fetch_anchor_comps`, `_persist_estimate_and_commit`. Два потока в одной сессии дают «another operation is in progress» / InvalidRequestError на следующем шаге БД: у источников её глушит `except` вокруг вызова, у персиста оценки не глушит ничего — 500 и потерянная оценка клиента, ровно под нагрузкой, ради которой PR и делается. `_db_step` теперь пробрасывает отмену ПОСЛЕ того, как поток отпустил сессию (`asyncio.shield` + ожидание шага). Цена — бюджет источника переезжает на длину ОДНОГО шага БД, а не на длину фетча, ради которой бюджет заведён. Почему не `threading.Lock` на сессию (вариант из ревью): лок внутри `_db_step` сериализует только шаги, которые через `_db_step` и проходят, — а названный пострадавший `_persist_estimate_and_commit` (estimator.py:5203) это ГОЛЫЙ `asyncio.to_thread(db...)`, как и ещё 17 мест эстиматора; лока они не берут, и дыра осталась бы открытой ровно там, где она стоит 500. Ожидание же в точке отмены закрывает ВСЕХ последующих потребителей сессии разом и не заводит глобального состояния (`WeakKeyDictionary`). Гейт по значению — tests/test_3408_db_step_cancel_orphan.py: следующий шаг (голый `to_thread`, как персист) не входит в сессию, пока сирота не закончил. Семантика проверена на питоне прода (3.12): `wait_for` по-прежнему отдаёт TimeoutError, источник деградирует в None. Остальное из ревью: - M2: комментарий у `_MAX_DEFERRED_REFRESH_TASKS` обещал за ОБА фоновых источника, а верен только для Яндекса. Циан держит коннект весь фетч (до 25 с): транзакцию открывают `_load_from_cache`/`load_session`, закрывает `db.commit()` в конце (scraper_kit .../cian/valuation.py:163,176,595). Формулировка сужена, остаток назван явно: функция общая со скраппером (cian_history_backfill.py:458), где коммит в середине менял бы семантику батча, — нужен отдельный опт-ин путь. На ПОТОЛОК пула остаток не влияет (коннект на задачу один независимо от того, как долго держится), только на среднюю занятость. - L1: `db.rollback()` после упавшего `_db_step` (estimator.py:1186) удалён — откат уже сделан в потоке, а на loop'е это блокирующий вызов. - L4: в core/db.py записано, что «пул >= суммы объявленных потолков» — ПОЛ, а не гарантия: коннект держит и любая ручка с `Depends(get_db)`, а глобального обработчика `sqlalchemy.exc.TimeoutError` в app/main.py нет (проверено: единственный handler — RequestValidationError, core/http_errors.py:59). - L3: гейт пула больше не читает `pool._timeout` и не молчит при переименовании `_max_overflow` — публичный `pool.size()` + приватное поле за явным assert'ом. `pool_timeout` из этого коммита УБРАН намеренно: это единственная правка, которая меняет режим отказа с «медленно» на «быстро с ошибкой», и она едет во все сервисы образа (backend, scraper, tgbot). Возвращается отдельным коммитом в конце ветки — чтобы ветку можно было смержить без него или откатить одной командой. Refs #3083, #3408 |
|||
| 9f696299de |
fix(mera): sync-БД источников эстиматора — с event loop в поток и не через фетч
All checks were successful
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 9s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m16s
Замер на проде 11.09 (изнутри хоста, тот же контейнер): - одна оценка 0.44 с (повтор адреса) / 0.97 с (новый адрес), из них БД 252/458 мс; - N=8 параллельных — все 200, heartbeat /health p95 5-7 мс, max 113-160 мс: loop сегодня НЕ голодает, «добавить воркеров uvicorn» замером не подтверждается (и умножило бы на N оба семафора, пять in-process лимитеров и пул); - зато одна фоновая догрузка Яндекса держала коннект пула 8.5 с (лиз прокси 33.856 → запись 42.334), а таких задач разрешено 8 при пуле 15. Правки: - estimator `_db_step`: SELECT/UPSERT кэша источников уходят в `asyncio.to_thread` и завершают транзакцию — коннект возвращается в пул ДО внешнего HTTP; - core/db: max_overflow 10→15 (потолок 20 на процесс ≥ 4+4+8 объявленных потолков одновременности) и pool_timeout 30→5 с (короче бюджета источника 8 с, иначе занятый пул съедает и бюджет запроса, и поток to_thread). Локальный замер ДО/ПОСЛЕ на тех же величинах: loop стоял 301 мс (0 тиков соседней корутины) → 0.2 мс (23.5k тиков); ожидание коннекта соседом во время фетча — таймаут пула → 0.1 мс. Refs #3083, #3408 |
|||
|
|
a82382f47a |
feat(mera-b2c): микроразметка schema.org и картинки превью для публичного сайта
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m5s
Сайт открыт для индексации 10.09.2026, но поисковику он до сих пор
представлялся девятью страницами без единого структурированного факта о
том, кто их публикует и что продаёт, а ссылка на него в мессенджере
разворачивалась голым текстом без картинки.
МИКРОРАЗМЕТКА. Конструкторы узлов вынесены в `_lib/schema.ts` — чистый
модуль без React, по образцу соседнего `_lib/analytics.ts`. Все значения
берутся из `content.ts`, ни одной строки не продублировано:
- `Organization` + `WebSite` одним `@graph` в layout поддерева, то есть на
всех девяти страницах сразу. Остальные узлы ссылаются на организацию
через `@id`, а не повторяют реквизиты у себя.
- `Service` с `Offer` на лэндинге.
- `FAQPage` в блоке возражений.
- `BreadcrumbList` на статьях, статье, документах и странице для бизнеса —
ровно по тем крошкам, что видны на экране.
- Существующий `Article` переведён на ссылку `publisher: { "@id": ... }`.
РАЗМЕТКА НЕ ОБЕЩАЕТ ТОГО, ЧЕГО НЕТ. Два места, где это стоило внимания:
- `Offer.availability` вычисляется из `PUBLIC_ESTIMATE_ENABLED`. Флаг сейчас
`false` (платёжного контура в коде нет), поэтому в разметку уходит
`PreOrder`, а не `InStock`: цена опубликована, купить нельзя, и врать об
этом поисковику нечего. Включится приём оплаты — значение сменится само.
- `FAQPage` строится из того же массива `ITEMS`, который рисует `<details>`,
а не из `FAQ` напрямую: в `ITEMS` часть вопросов заменена макетными
формулировками, и разметка от сырого `FAQ` разошлась бы с видимым текстом
молча. Выдуманных дат, рейтингов и отзывов не добавлено нигде.
КАРТИНКИ. Две штуки, и это не дубль: `og-mera.png` 1200×630 — превью ссылки,
`logo-mera.png` 512×512 — логотип организации в разметке, который поисковик
обрезает близко к квадрату и где баннер превратился бы в обрезок надписи.
Исходники обеих лежат рядом в `scripts/og/*.html` вместе с командой
перерисовки: картинка должна оставаться правимой, а не только
переоткрываемой в графическом редакторе.
Готовые PNG, а не `opengraph-image.tsx`: satori внутри `ImageResponse` рисует
только переданными ему байтами шрифта и кириллицу по умолчанию не покрывает,
плюс не видит ни CSS Modules, ни наших `--b2c-*`. Разбор — в шапке
`scripts/og/og-mera.html`.
`images` продублирован в `twitter`: Next не переносит их из `openGraph`, когда
метаданные заданы объектом, и карточка обещала бы крупное превью без картинки.
ПЕРИМЕТР. Обе картинки лежат в `public/` и раздаются Next'ом по
basePath-корню, куда rewrite `@meraPages` не достаёт, — отсюда два отдельных
`handle` по образцу robots.txt. Оба названы в `ROOT_HANDLES_ALLOWED`, иначе
двусторонний гейт периметра покраснел бы, и это правильно: корневой `handle`
— вторая дверь в тот же периметр.
Смоук проверяет не только 200, но и Content-Type: перепутанный rewrite отдаёт
200 с HTML, обходчик мессенджера молча его отбрасывает, и по логам приложения
этого не видно — запрос туда не доходит.
Проверено: tsc, eslint, 215 тестов mera-public, изоляция B2C-дерева, caddy
validate, bash -n смоука, прод-сборка с basePath и разбор отрендеренного
HTML — JSON-LD парсится, og:image и twitter:image на месте.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiUFZ3rmTNpp3DRajUo8KQ
|
||
|
|
2220df8741 |
fix(mera): версия согласия ПДн отстала от новой редакции политики + смоук ждал не тот код
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m3s
ДВА ПОСЛЕДСТВИЯ #3436, обнаруженные на прогоне против прода. 1. ДЕПЛОЙ МЕРЫ БЫЛ ЗАБЛОКИРОВАН. В #3436 политика конфиденциальности получила раздел про cookie, то есть новую редакцию, и `PRIVACY_APPROVAL` во фронте стал «№ 2 от 10 сентября 2026 г.». Бэкендовая `_CONSENT_POLICY_VERSION` осталась на «2026-08-13», а между ними стоит гейт `test_consent_text_frontend_sync.py` — он и упал. Job `test` в deploy-tradein.yml падает → `deploy` пропускается по своему `needs.test.result != 'failure'` → прод остался на старом образе фронта, при том что Caddy обновился отдельным пайплайном. Внешне это выглядело как «задеплоилось наполовину»: UTM на редиректе со слэшем починился, а noindex и robots.txt — нет. Гейт сработал ровно как задуман: версия согласия обязана указывать на ту редакцию документа, которую человек реально видел, иначе снимок согласия в trade_in_leads.consent_policy_version подписан не тем документом. Правим версию, а не тест. Согласия, собранные до 10.09, остаются с "2026-08-13" — в этом и смысл хранить версию per-row. 2. СМОУК ЖДАЛ 404 ТАМ, ГДЕ ПРОД ОТВЕЧАЕТ 401. Проверка «карта сайта МЕРЫ не просачивается через B2B-домен» ожидала 404 от allowlist'а site-блока, но корень gendsgn.ru закрыт пилотным basic_auth, и гейт отвечает 401 РАНЬШЕ, чем запрос доходит до allowlist'а. Проверка была написана без прогона против прода — это честно отмечено в её же комментарии — и упала на первом же запуске. Заведён `check_any`: PASS на любом из перечисленных кодов. Здесь допустимы 401 и 404 — оба означают проверяемое («наружу этого адреса нет»), а какой рубеж ответил первым, к предмету проверки отношения не имеет. Жёсткое ожидание к тому же сломалось бы при снятии пилотного гейта. Красная строка осталась там, где ей место: 200 означал бы реальную течь. Проверено: `pytest tests/test_consent_text_frontend_sync.py` — 6 passed; полный сьют бэкенда МЕРЫ локально 5737 passed; `bash -n` на смоуке чист; `check_any` прогнан против живого gendsgn.ru — PASS на фактическом 401. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CiUFZ3rmTNpp3DRajUo8KQ |
||
|
|
671fef758e |
feat(mera): Метрика и GA4 на публичном контуре + открытие сайта для индексации
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m19s
CI / openapi-codegen-check (pull_request) Successful in 2m6s
CI / backend-tests (pull_request) Successful in 17m40s
ЗАЧЕМ. Статьи МЕРЫ публикуются с UTM-метками, но посмотреть, приходил ли по ним кто-нибудь, было физически нечем: веб-аналитики на публичном контуре не было вовсе. Заодно вскрылось, что «толкнуть в выдаче» тоже нельзя — всё дерево mera-public отдавало `robots: noindex, nofollow`. СЧЁТЧИКИ. Яндекс.Метрика и GA4 подключаются ТОЛЬКО в `mera-public/layout.tsx` и никогда в корневом `app/layout.tsx` — иначе счётчик уехал бы в закрытый контур (/v2, /admin, /scrapers, /history), где анонимных посетителей нет, а приватные маршруты сотрудников есть. Идентификаторы приходят build-time (`NEXT_PUBLIC_YM_ID` / `NEXT_PUBLIC_GA_ID`) — канон Dockerfile'а этого проекта: Next инлайнит NEXT_PUBLIC_* на сборке, runtime env их не подхватит. Пустое значение = тег не рендерится вовсе, никаких `ym(undefined)`. Оба build-arg'а прописаны в ОБОИХ блоках CI, включая retry-сборку без кеша. Вебвизор выключен намеренно. Он пишет ввод в поля, а на `/estimate` человек вводит адрес своей квартиры; раздел 9 политики этого не раскрывает. Включать следует одним заходом с правкой политики и маскировкой полей — в коде рядом записано, что именно понадобится. ЦЕЛИ ВОРОНКИ. Десять целей: клик по CTA, начало ввода адреса, адрес выбран, результат с разбивкой по вердикту (ok/thin/none), ошибка расчёта, ошибка валидации, отказ подсказок, показ платного тизера. Кнопок «Проверить квартиру» восемь штук в разных компонентах, все — обычные `<a>` через PublicLink, поэтому вместо восьми копий onClick один делегированный слушатель на document: девятая кнопка подключится сама. Цель «оплата успешна» НЕ заведена — вызова checkout во фронте нет вовсе, PAYMENTS_ENABLED выключен, страницы возврата не существует; вешать её пока не на что. ИНДЕКСАЦИЯ. Снят noindex со всех публичных страниц, добавлены `app/robots.ts` и `app/mera-public/sitemap.ts`, metadataBase, canonical на КОРОТКИЕ адреса, openGraph и JSON-LD Article на главной статье. robots.txt и sitemap.xml разведены по двум разным handle в Caddy не от хорошей жизни: у Next robots.txt — конвенция корня app/, а sitemap живёт в сегменте маршрута, и формы путей не совпадают. 152-ФЗ. Раздел 9 «Файлы cookie и веб-аналитика» в политике (обработчики названы поимённо — этого требует ч. 3 ст. 6) + уведомляющий, не блокирующий баннер. Гейт «названий площадок в публичной копии быть не должно» получил узкое исключение ровно на аналитические словосочетания в политике; голое «Яндекс» как площадка остаётся запрещённым и там. ПОПУТНЫЙ БАГ (замер на живом проде 10.09.2026). `meraocenka.ru/articles/` с UTM-метками отдавал 301 на адрес БЕЗ query — матчер @meraShortSlash собирал цель из regex-захвата пути и терял параметры. Код ответа при этом оставался 301, поэтому смоук проблему не видел. Мессенджеры и автолинкификаторы дописывают слэш сами, то есть атрибуция терялась именно на трафике по опубликованной ссылке. Починено тем же приёмом, что у соседних матчеров; в смоук добавлена проверка буквального Location. ГЕЙТЫ. `isPublicPath` и периметр-тест узнали про новые машинные адреса; noindex-гейт развёрнут (падает, если флаг вернулся) и расширен на строковую форму `robots: "noindex"`; заведена проверка, что корневых `handle` в site-блоке не появляется без объявления — раньше эту дверь гейт не видел. Проверено: tsc и eslint чисто, `npm run build` проходит, robots.txt и sitemap.xml отдаются по нужным адресам, при пустых ID в HTML нет ни одного обращения к mc.yandex.ru и googletagmanager, `caddy validate` валиден, изоляция mera-public от B2B не нарушена. Два теста LoginPage падают и на нетронутом дереве — не наши. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CiUFZ3rmTNpp3DRajUo8KQ |
||
|
|
9898b6bc02 |
feat(tradein/geocoder): регион-параметризация геокодера — region_code в geocode()/known_city_hint, --region-code у скрипта сделок, region_code у admin geocode-missing (#3051)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m6s
Геокодер был жёстко привязан к Свердловской области: viewbox 66 +
bounded=1, accept только при state ~ 'свердловск' и точке в bbox 66,
known_city_hint знал лишь города области → для 212 937 московских сделок
(address 'Москва, <улица>') геокод давал None либо ложный хит по
одноимённой улице области, а cache-ключ без города смешивал регионы.
Теперь регион приходит от вызывающего (deals.region_code): viewbox и
bbox из REGIONS[code], state-маркер per region ('свердловск'/'москва'),
ЕКБ-тиры (geoportal/cadastral/local houses) только при 66, city-хинт
через словарь региона → cache-ключ '|city=москва'. Дефолт 66 везде —
для существующих вызовов поведение байт-идентично (ревью двумя
линзами). Побочно: geocode-missing по умолчанию больше не берёт
listings с region_code NULL (16 930 неактивных чужих городов, которые
и раньше геокодились впустую).
|
||
| 12476df7a2 |
Merge pull request 'feat(tradein/estimator): deal_city_price_bands по ключу (region_code, city) — миграция 298, refresh per-region (#3051 sub-PR B)' (#3431) from feat/3051-bands-region-key into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m14s
Deploy Trade-In / build-backend (push) Successful in 1m4s
Deploy Trade-In / deploy (push) Successful in 1m54s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
|
|||
|
|
1d9adb4a24 |
feat(tradein/estimator): deal_city_price_bands по ключу (region_code, city) — миграция 298, refresh per-region, потребители (#3051 sub-PR B)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m10s
После импорта 212 937 московских ДКП (region_code=77) region_stats refresh'а (p1 для tier region_fallback) считался бы по пулу 66+77 и поднял бы floor ~250 малым городам области. Ключ bands становится (region_code, city): миграция 298 (колонка DEFAULT 66, смена PK через DO-guard, re-seed per-region, ЕКБ-исключение только для 66, doc_type=ДКП), тот же SQL в refresh-джобе, estimator/backtest читают bands по паре. Для 66 набор строк байт-идентичен прежнему (ревью двумя линзами). |
||
|
|
beb487c12e |
test(#3051): тесты трека «Москва» — импорт ДКП по 77, per-source чекпоинт, wildcard-хендлер, регион-скоуп ДКП-коридора, миграция 289, валидация deploy-скрипта
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m2s
Тесты пишутся ПОСЛЕ живой проверки функционала (правило от 08.09): импорт по 77 — run 6460 (212 937 строк), регион-фильтр коридора — smoke до/после импорта. 14 тестов: SQL-текст через inspect (regex \s+ для многострочных клозов), чистые функции, мок db по образцу 2846, subprocess bash с урезанным PATH (падение от валидации, не от отсутствия docker). |
||
| e0bef636e6 |
feat(tradein): bulk-дампы открытых данных ФНС по юрлицам + lookup по ИНН (#3429)
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m17s
Deploy Trade-In / build-backend (push) Successful in 1m3s
Deploy Trade-In / deploy (push) Successful in 7m11s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
|
|||
| 50f0674977 |
feat(tradein): слой ДТП из dtp-stat.ru в PostGIS + радиусные агрегаты (#3428)
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m9s
Deploy Trade-In / build-backend (push) Successful in 1m54s
Deploy Trade-In / deploy (push) Successful in 1m51s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
|
|||
|
|
1f3566c466 |
merge main в feat/mera-cbr-macro — оба handler'а (frt_mkd_load + cbr_macro_pull) в реестре
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 12s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m12s
|
||
|
|
2abab8218e |
feat(mera): макро-ряды ЦБ РФ — ипотека по субъектам и ключевая ставка
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m16s
Три XLSX ЦБ (выдачи, ставка, задолженность) в разрезе субъектов, помесячно с 01.2019, + ключевая ставка через SOAP DailyInfo.asmx (метод KeyRate; GET на нём не работает, только POST). Всё анонимно, без ключа. Таблицы: cbr_mortgage_series (region, period_month, series) и cbr_key_rate. fetched_at НЕ обновляется в DO UPDATE — по уроку #2846 колонка значит «когда мы ВПЕРВЫЕ увидели период» и по ней меряется такт публикации источника. Раскладка листов у ЦБ РАЗНАЯ, и это ловушка: в 02_11/02_13 шапка периодов в строке 3 текстом («Январь 2019»), а в 02_14 (задолженность) — уже в строке 2 и настоящими datetime. С захардкоженным индексом строки серия debt_rub молча давала НОЛЬ строк при зелёных тестах. Теперь строка шапки ищется динамически: берётся строка с наибольшим числом распознанных периодов среди первых шести. Проверено на живых файлах: каждая из трёх серий даёт 8 736 точек (96 территорий × 91 месяц, 01.2019–07.2026); по Свердловской области 91 месяц, последняя ставка 11.49. estimator.py не тронут: как ипотечная ставка войдёт в оценку — отдельное продуктовое решение, этот PR только про данные. Миграции 292 (таблицы) и 293 (сид расписания, enabled=false, interval_days 7). Новая зависимость: openpyxl. |
||
|
|
8aca820a46 |
feat(mera): загрузчик реестра МКД АИС ФРТ → обогащение houses
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m14s
Открытые данные АИС ППК «ФРТ» (бывш. Реформа ЖКХ), node 110 = реестр МКД региона 66: 41 790 строк, houseguid (ФИАС GUID) заполнен на 100% → join к houses.gar_house_guid без канонизации адреса. Анонимный GET, без ЕСИА. Заполняет ТОЛЬКО NULL-поля houses: year_built, material_walls, material_floors, total_floors, entrances, is_emergency, flat_count, heat_supply_type, gas_supply_type, hot_water + новые area_land, foundation_type, elevators_total. Замер по ЕКБ: wall_material 92.5% (было 75% от ДОМ.РФ), built_year 92.0% (было 86%), area_land 86.8% и is_emergency — признаков, которых не давал ни один из текущих источников. Осознанно НЕ мапится: - project_type → series_name: свободный текст и фактически дубль материала стен (пусто у 10 635 строк, «кирпичный» 1754, «нет данных» 1496); - energy_efficiency: решение миграции 284 + реальный класс лишь у ~10% домов (у 25 595 из 41 790 значение «Не присвоен»); - elevators_count → passenger_elevators: в источнике это ОБЩЕЕ число лифтов, в houses раздельно пассажирские и грузовые → отдельная колонка; - playground/sportsground: в источнике id справочника (498/499/500), не флаг. Дубликаты houseguid реальны и взаимодополняющи: 912 guid'ов, 968 лишних строк, у одной строки пары заполнен area_total, у парной нет. Строки СЛИВАЮТСЯ по полям (первое непустое побеждает), иначе бэкфилл терял бы данные ~2% домов. estimator.py не тронут: аналоги подбираются FROM listings без JOIN к houses, встраивание признаков в подбор когорты — отдельная задача. Миграции 290 (staging frt_mkd + колонки houses) и 291 (сид расписания, enabled=false, interval_days 30). robots.txt источника требует Crawl-delay 10. |
||
|
|
f820c3eac5 |
fix(msk-collector): ограниченный ретрай goto на таймауте навигации
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Авито изредка держит соединение до упора, goto падает по 90-секундному таймауту и прогон умирает целиком. В наблюдавшемся случае это был не отказ: вкладка показывала нормальную выдачу, маркеров фаервола и PoW не было. Но тихий отказ выглядит так же, поэтому ретрай не слепой: перед каждым повтором читаем то, что есть в документе, и прогоняем через _guard — реальный блок останавливает прогон со своей причиной. Исчерпали 3 попытки — жёсткий стоп с причиной nav_timeout, а не бесконечный повтор. |
||
|
|
3daf09c7e4 |
fix(msk-collector): узкий ретрай page.content() на гонке перенавигации
All checks were successful
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Авито дорисовывает выдачу после domcontentloaded, и content() иногда попадает ровно в смену документа — Playwright бросает "page is navigating and changing the content". Это не отказ площадки, но прогон падал целиком: на 1110-й карточке потеряно 100 несброшенных строк. Ретрай узкий — только на этот текст ошибки и только 4 попытки; любая другая ошибка поднимается как есть, гварды блокировки не ослаблены. runs/ (планы и CSV прогонов) в .gitignore. |
||
| e2045582ab |
Merge pull request 'feat(tradein/rosreestr): импорт ДКП по Москве (77) — canonical_city, raw_payload, wildcard-расписание rosreestr_dkp_import_*, per-source чекпоинт (#3051)' (#3422) from feat/3051-rosreestr-import-region-param into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 13s
Deploy Trade-In / build-browser (push) Successful in 39s
Deploy Trade-In / build-frontend (push) Successful in 2m13s
Deploy Trade-In / test (push) Successful in 4m22s
Deploy Trade-In / build-backend (push) Successful in 1m1s
Deploy Trade-In / deploy (push) Successful in 1m23s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 12s
|
|||
|
|
fcf5887225 |
merge(#3051): main (#3421) в ветку импорта по региону — московская дельта поверх region_code/doc_type
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m12s
#3421 въехал в main параллельно с той же миграцией 288 (deals.doc_type, параметры region_code/doc_types). Разрешение: 288 — целиком версия main; наша дельта (FDW-колонки okato/quarter_cad_number/district, выключенный seed rosreestr_dkp_import_77) переехала в 289. scheduler.py — doc_types из main + canonical_city-маппинг/raw_payload/per-source чекпоинт. deploy-скрипт — валидация REGION_CODE и DOC_TYPE (интерполируются в SQL текстом). |
||
|
|
fbe85fcc75 |
feat(tradein/estimator): регион-скоуп ДКП-коридора — фильтр по d.region_code (#3051 PR-A)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m9s
target_city резолвится ТОЛЬКО для городов Свердловской области (_resolve_target_city матчит SVERDLOVSK_OBLAST_CITIES) — для Москвы/любого нового региона city=None, и street ILIKE оставался единственным скоупом сделки: одноимённая улица чужого региона утекала в коридор. Добавлен d.region_code = CAST(:region_code AS int) в оба ДКП-запроса (_fetch_dkp_corridor) + region_code передаётся из обоих вызывающих (POST /estimate через geo.lat/lon, GET-rehydrate через row.lat/lon) с гарантией «не резолвится → DEFAULT_REGION_CODE (66)», не NULL (NULL в SQL-параметре обнулил бы фильтр целиком). Дефолт региона и джойн deal_city_price_bands не трогаются — следующие PR (B, F). Regression: 5622 passed, 37 skipped (полный прогон tests/). |
||
|
|
1f24f12af9 |
chore(#3051): снять новые тесты до живой проверки; валидация REGION_CODE в deploy-скрипте
All checks were successful
CI Trade-In / changes (pull_request) Successful in 12s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 14s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m13s
Тесты трека «Москва» пишутся отдельным заходом после живого прогона импорта по 77 — правило проекта с 2026-09-08. Правки существующих тестов (bind-параметр вместо литерала 66, канонический source rosreestr_dkp_import_77) остаются. REGION_CODE в import-rosreestr.sh подставляется в SQL текстом — допускаем только целое. |
||
| 541c4ff28d |
Merge pull request 'feat(#3051): deals.doc_type + регион и тип документа как параметры импорта Росреестра' (#3421) from feat/3051-deals-doc-type-region-param into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-browser (push) Successful in 41s
Deploy Trade-In / build-frontend (push) Successful in 2m22s
Deploy Trade-In / test (push) Successful in 4m24s
Deploy Trade-In / build-backend (push) Successful in 1m5s
Deploy Trade-In / deploy (push) Successful in 6m35s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 12s
|
|||
|
|
e9370818ea |
fix(#3051): lock_timeout в миграции 288 — гейт CI #2752
All checks were successful
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m11s
ALTER TABLE deals ADD COLUMN без SET LOCAL lock_timeout встаёт в очередь за чужой сессией и уводит за собой запросы приложения. Лучше упасть по таймауту и повторить деплой. |
||
|
|
84ee8e5990 |
feat(tradein/rosreestr): параметризовать импорт ДКП по региону, deals.doc_type (#3051)
Трек 2 подготовки Mera к Москве. import_rosreestr_dkp принимает region_code из params (default 66 — байт-в-байт прежнее поведение), валидирует его через app.services.regions.REGIONS. Регион с canonical_city (77 — Москва, Росреестр отдаёт округ/поселение вместо города) подставляет city/address через одну SQL-ветку на bind-параметре :canonical_city, а не Python if/else на код региона; city IS NOT NULL не фильтруется для такого региона (иначе теряется ~10% строк), исходные city/okato/quarter_cad_number/district уходят в raw_payload. Чекпоинт курсора (_resume_dkp_cursor) стал per-region: source для поиска предыдущего прогона строится через _dkp_source_for_region (66 сохраняет легаси-имя 'rosreestr_dkp_import', остальные — суффикс кода) — иначе прогон по 77 либо никогда не резюмился бы (source-литерал не матчил), либо, при более наивном фиксе, унёс бы курсор чужого региона. product_handlers регистрирует wildcard rosreestr_dkp_import_* (по образцу deactivate_stale_*/avito_city_sweep_*), deploy/import-rosreestr.sh получил REGION_CODE env (bash-путь не region-generic — city-override только в Python). Migration 288: deals.doc_type + backfill 'ДКП' для source=rosreestr, foreign table gendesign_rosreestr_deals расширена okato/quarter_cad_number/district (проверено live на прод-БД), выключенный seed rosreestr_dkp_import_77. |
||
|
|
bde38cdce7 |
fix(msk-collector): --resume берёт base_url из сохранённого плана
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Коридоры считаются под конкретный URL выдачи. При --resume код брал свежий args.base_url, поэтому запуск без повтора --base-url молча качал другую выдачу под тем же batch_id. Теперь URL из плана, расхождение с аргументом — явная ошибка вместо тихого выбора одного из двух. |
||
|
|
476fb9b77a |
chore(#3051): снять новые тесты — до живой проверки функционала тесты не пишем
Some checks failed
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Failing after 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m7s
В существующем test_rosreestr_dedup_key оставлена только правка, без которой он падает после параметризации (литералы region_code=66 / doc_type='ДКП' ушли из SQL живого импорта). Новый тест-файл и добавленная функция сняты: поведение ещё не проверено на живом импорте, тест зафиксировал бы догадку. |
||
|
|
7b33ed9a45 |
feat(#2989): локальный ручной сборщик SERP Авито по Москве и МО
Трек 1 эпика: нужен разовый корпус вторички Москвы+МО в msk_raw, а прод-скрейпер для этого не подходит — его расписания, прокси-пул и сайдкар держат ЕКБ и трогать их ради ручного замера нельзя. Поэтому скрипт: браузер — уже открытый Chrome владельца с залогиненным техаккаунтом (connect_over_cdp, своя вкладка, чужие вкладки/контекст/браузер не трогаем и не закрываем; своего профиля не поднимаем); парсер — импорт _parse_html / _extract_total_count / _is_firewall_page из scraper-kit, а не копия (копия разъедется с прод-парсером на первом же DOM-drift); заливка — поток в psql через ssh, потому что прямого доступа к прод-Postgres с локалки нет, а одиночный psql -c ломается на квотинге. avito_serp_ekb_only=False обязателен: с True парсер выбрасывает всё, где в URL нет /ekaterinburg/ — из московской выдачи не осталось бы ни одной карточки. Потолок Авито 30x60=1800 на запрос, поэтому план ценовых коридоров с бисекцией по ГЕОМЕТРИЧЕСКОЙ середине: цены логнормальны, арифметическая середина 1млн..100млн даёт вырожденно-пустую верхнюю половину. Коридор, который не влезает в 1800 даже на минимальной ширине, помечается truncated и недобор пишется в batches.notes — молчаливое усечение читалось бы как полный охват. Стоп на первом признаке блока (403/439, 429, firewall, PoW, 0 карточек при ненулевом счётчике) без ретраев: ретрай по забаненному техаккаунту только углубляет бан. Дефолт — --measure 100, полный проход только по явному --full. |
||
|
|
cfe529fd14 |
feat(#3051): deals.doc_type + регион и тип документа как параметры импорта Росреестра
ПОЧЕМУ: расширение на Москву упирается в два литерала. В источнике за 2024 по региону 77 лежат 30 627 ДДУ с медианой 112 743 против 107 005 ДКП с медианой 256 250 — это цены котлована, и без различимого признака в deals они развалят любую оценку. При этом тип сделки терялся при загрузке вовсе (в deals колонки не было), а фильтры region_code = 66 и doc_type = 'ДКП' стояли литералами в scheduler.import_rosreestr_dkp и в двойнике deploy/import-rosreestr.sh — сменить регион было нельзя, не правя код. ЧТО: - миграция 288: deals.doc_type text (idempotent) + бэкфилл 'ДКП' для source='rosreestr' (корректен, а не эвристика: всё загруженное прошло фильтр ДКП — и в импорте, и в 077) + явный region_code=66 в default_params расписания rosreestr_dkp_import вместо неявного дефолта в коде. Индекс НЕ добавлен: 2-3 значения, живые выборки идут по region_code/deal_date/geom — заведём частичный, когда появится режущий запрос; - import_rosreestr_dkp: region_code (default 66) и doc_types (default ['ДКП']) из params, фильтры через bind-параметры CAST(:region_code AS int) / ANY(CAST(:doc_types AS text[])), doc_type едет из SELECT в INSERT и в ON CONFLICT DO UPDATE. Дефолты сохраняют текущее прод-поведение байт-в-байт; - dedup_hash оставлен как 'ros:dkp:' || id: id уникален в источнике независимо от типа документа, а смена формы ключа осиротила бы уже загруженные строки (ровно то, что разгребала миграция 077); - deploy/import-rosreestr.sh: REGION_CODE / DOC_TYPE как env со старыми дефолтами, doc_type протащен через staging в deals; шапка про «ЕКБ квартиры» переписана честно — city-фильтр снят давно, скоуп = весь регион; - тесты: test_rosreestr_dedup_key переведён с ассертов на литералы на проверку «параметр + дефолт = скоуп 077»; новый test_3051_* проверяет bind-параметры реальным вызовом с моком Session, дефолты 66/['ДКП'], doc_type в колонках INSERT и текст 288. |
||
| f4d174283a |
chore(#3197): cian-login — без холостой аренды и ложного отказа (/login сайдкара override не берёт); честные докстринги
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m7s
Code-review хвоста #3197: на `cian-login` аренда из пула не доходила до сайдкара. `BrowserFetcher._post_login` не кладёт `payload["proxy"]` (это делают только `fetch`/`fetch_json`), а на приёме `login_handler` (browser/server.py:2814-2817) зовёт `_no_live_proxy(provider, None)` и `_ensure_browser(provider)` без override — `/login` proxy-override не принимает вовсе. Lease брался в `__aenter__` и освобождался в `__aexit__` без пользы и без health-вердикта по узлу. Хуже холостого хода: при пустом пуле в production `_acquire_lease` поднимает `NoProxyAvailableError` ДО POST, `cian_auto_login` ловит любое `Exception` → `502 Browser login failed`. То есть единственная ручка ВОССТАНОВЛЕНИЯ cian-сессии отказывала ровно во время инцидента с пулом. Комментарий в admin.py при этом утверждал, что фикс закрывает InvalidIP на логине — неправда. Убран `proxy_provider=` (фабрика остаётся ради endpoint/environment из одного места). `use_pool` без провайдера фетчер игнорирует сам — `_acquire_lease`: `use_pool AND provider is not None` — поэтому ни аренды, ни прод-отказа. `domclick-detail-debug` не тронут: он ходит через `fetch`, где override реально кладётся в тело и читается сайдкаром — там #3197 остаётся настоящим фиксом. Тесты для cian обратные по значению и падают на HEAD ветки: `test_cian_auto_login_does_not_lease_from_pool` (провайдер не передан, `acquire` не вызван) и `test_cian_auto_login_survives_empty_pool_in_production` (пустой пул на проде не отдаёт 502). Стаб cian — подкласс настоящего `BrowserFetcher`, чтобы второе утверждение шло через реальный `_acquire_lease`, а не через заглушку. Follow-up (отдельной задачей): сайдкар `/login` не принимает proxy override → логин cian всегда с env-узла (сейчас выключенный узел 9). |
|||
| 8761602e9b |
chore(tradein/proxy): последние две ручки admin.py — через фабрику фетчера; снят форс pool-режима у cian-history (#3197, #3386)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m5s
Два хвоста одной темы — проводка пула прокси в контейнере backend. #3197: `cian-login` и `domclick-detail-debug` были последними прямыми конструкциями `BrowserFetcher(source=, endpoint=)` мимо `build_browser_fetcher`. Без `proxy_provider`/`use_pool`/`environment` сайдкар брал свой env-узел `SCRAPER_PROXY_URL` (на проде выключенный узел 9: 407 → camoufox `InvalidIP`), а прод-отказ «пул пуст» (#2616) на этих путях был мёртв — он смотрит на `environment`, который до конструктора не доезжал. Соседи по эпику уже переведены (#3382 cian, #3389 yandex). Прямых конструкций без провайдера вне тестов больше не осталось: остальные (backfill-задачи, pipeline) пул получают своими kwargs, а `endpoint=None`-ветки providers — это документированный `config=None` для офлайн-тестов. #3386: `_PoolCurlConfig` в `cian_price_history` форсил `use_proxy_pool_curl=True`, потому что у контейнера `backend` не было переменной. #3387 задал `USE_PROXY_POOL_CURL: "true"` сервису `backend` в compose — зашитая константа стала лишней и делала рубильник неотключаемым ровно на этом пути (докстринг при этом описывал уже неверную причину). Теперь `RealScraperConfig()` напрямую. Тесты меряют значения, а не наличие kwarg'а: на откате исходников красные 4 параметризации нового `test_3197_admin_debug_browser_pool_wiring` (`assert None is not None` — провайдер не передан) и `test_price_history_honours_flag_off` (`assert ['cian'] == []` — пул дёргался при выключенном флаге). |
|||
| 7d3c0eab51 |
fix(#3398): acurl_proxy_url — вторая отмена не бросает lease; тест меряет наш release, а не teardown; потолок бюджета в докстринге
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m11s
|
|||
| e4feadefe6 |
fix(tradein/proxy): операции пула прокси уходят с event loop публичного /estimate (#3398)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m8s
Вход в `providers/_proxy.py::curl_proxy_url` синхронный и стоял ДО первого await во всех трёх async-сайтах `/estimate` (avito/imv, yandex/valuation, cian/valuation). `RealProxyProvider.acquire/mark_health/release` ходят в БД синхронно (своя SessionLocal на операцию), а публичный backend крутится на ОДНОМ воркере uvicorn (#3083): на cache-miss это 3 источника x (acquire + mark_health + release) блокирующих вызовов прямо на loop'е. `_with_budget(asyncio.wait_for)` синхронный вход прервать не может, а при исчерпанном пуле коннектов (5+10) checkout ждёт до 30 с — весь инстанс молчит. `acurl_proxy_url` — async-обёртка над тем же синхронным контекстом: вход и выход через `asyncio.to_thread`, выход тоже (иначе mark_health/release держали бы loop на выходе). Семантика прежняя: `NoProxyAvailableError` до запроса, BaseException-ветка (#3397: отмена → health=False), release в finally. `to_thread` копирует contextvars, поэтому `current_run_id` (#3404) виден в потоке как раньше. Новое по сравнению с sync-путём: отмена может прийти ВО ВРЕМЯ acquire (раньше это было невозможно по построению). Вход держится через `asyncio.shield` и добирается в except — иначе выданная в потоке аренда висела бы до reap_stale_leases. Переключены только три сайта `/estimate`. Sync-вызывающие и async-сайты под scheduler (cian/detail.py::fetch_detail, cian/newbuilding.py::resolve_cian_zhk_url_via_search) остаются на `curl_proxy_url` — там loop не обслуживает публичные запросы. |
|||
| 75b0931fad |
Merge pull request 'fix(cian): капча видна в отчёте прогона (ban_kinds.platform по типу) и банит узел на curl-пути (#3402 follow-up)' (#3406) from fix/3402-followups-ban-kinds-curl into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m10s
Deploy Trade-In / build-backend (push) Successful in 1m41s
Deploy Trade-In / deploy (push) Successful in 1m50s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
|
|||
| 224f91e8fa |
fix(cian): капча видна в отчёте прогона и банит узел на curl-пути (#3402)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m11s
Два follow-up из ревью #3403. 1. Капча-волна была невидима в счётчиках. `_note_refusal` ключевался только по HTTP-статусу, а капча приходит с 200 (свой детект по <title>) или без статуса (сайдкар) → рос один `listings_failed_fetch`, `ban_kinds` оставался пустым, и волна отказа площадки читалась как дрейф нашей разметки. Теперь диагноз берётся сперва по ТИПУ исключения (`ban_kind_of_exception`), статус — фолбэк. Инвариант #3196 сохранён: 'unknown' по типу И None по статусу по-прежнему ничего не пишут. `ban_kind_of_exception` расширен с `AvitoBlockedError` до `ProxyBanError` — это ровно тот mixin, по которому generic-прокси-слой уже снимает узел с выдачи источнику. Для Авито поведение не меняется (AvitoBlockedError его наследует), Cian/DomClick перестают приезжать как 'unknown'. 2. curl-путь детектил капчу, но не банил: общий parse-путь лежит ЗА границей `with curl_proxy_url(...)`, и `CianBlockedError` поднимался уже после `mark_health(ok=True)` — узел, которому Циан показывает капчу, оставался в выдаче Циану (дефект #2700, только на HTTP 200). Проверка перенесена ВНУТРЬ блока, `finally` хелпера сам делает `mark_banned(source='cian')`. Тесты по значению (оба красные на main): батч с капчей → ban_kinds == {platform: 1}; curl-путь + HTML капчи → mark_banned == [(1, 'cian')], mark_health(ok=True) нет. |
|||
|
|
f4099cb89a |
Merge remote-tracking branch 'forgejo/main' into feat/3404-proxy-run-attribution
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m4s
|
||
|
|
b89788ee99 |
feat(tradein/proxy): прогон знает свой узел, а снятый бан перестаёт стирать историю (#3404)
Выбор оператора мобильного прокси опирался на две ненадёжные опоры. Первая: `scrape_runs` не знала, через какой узел шёл прогон — колонка `proxy_id` была только у банов и ротаций. «Какой узел собрал 5 карточек из 21» не выяснялось ни одним запросом. Вторая: `clear_source_bans` делала DELETE, а зовётся она после КАЖДОЙ успешной ротации exit-IP. У #540723 (МегаФон) 23 успешные ротации и ноль строк банов, у #540722 (Tele2) ротаций почти не было и 7 банов. «7 против 0» читалось как «Tele2 хуже», хотя в той же мере это «у МегаФона историю стёрли 23 раза». Теперь: - `scrape_runs.proxy_id` — последний выданный прогону узел; полная цепочка (если узел менялся mid-run) копится в `counters.proxy_ids`. Пишет `proxy_pool.attribute_run_proxy` из единственной точки — сразу после выдачи лиза в `acquire()`, поэтому curl-путь, браузерный sticky lease и ре-acquire при ротации покрыты одинаково. `run_id` доходит до адаптера через ContextVar (`scraper_kit.orchestration.run_context`): протокол `ProxyProvider.acquire` его не несёт, а `RealProxyProvider` живёт одним объектом на весь планировщик. Best-effort: `lock_timeout` 2с и проглоченное исключение — диагностика не вправе ронять выдачу прокси или ждать на блокировке строки прогона. - `clear_source_bans` гасит строку (`banned_until = now()`, `ban_count = 0`, `cleared_at`/`cleared_reason`) вместо удаления. Эскалация сохраняется 1:1: формула в `mark_banned` берёт ПРЕДЫДУЩИЙ `ban_count` показателем степени, при нуле это ровно `SOURCE_BAN_BASE_HOURS` — как после DELETE. Строка доживает до штатного purge по `SOURCE_BAN_PURGE_DAYS`. Для всех читателей `scrape_proxy_source_bans` погашенная строка неотличима от отсутствующей: acquire, оба guard-подзапроса `mark_banned`, `proxy_egress` (ранжирование по `ban_count` даёт 0, как у узла без истории), admin `_active_ban` — все гейтятся по `banned_until > now()`. Ничего не бэкфиллится: связать прошедшие прогоны с узлами нечем (`leased_by` исторически = NON_RUN_LEASE_MARKER), врать восстановленным значением нельзя. Миграция 287. Тесты: 9 новых на обе части (главный — эскалация после гашения даёт базовые 6ч, а не удвоенные) + 14 существующих переведены с DELETE-семантики на гашение, включая проверку, что секрет ротации не утекает в новое `cleared_reason`. Полный прогон бэкенда: 5600 passed, 37 skipped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011WHFxVPWoBnSZihkdH1Uou |
||
| 957e9e8c01 |
fix(#3402): капча = «Captcha…» и «Вы не робот?» (бан+ротация); «Ошибка - Циан» — только лог на цикл наблюдения
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m8s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 1m27s
Ревью (⚠️ minor) на #3403: «Ошибка - Циан» может быть транзиентной 5xx-страницей, отданной с кодом 200, а не отказом конкретному узлу. Цена ошибки несимметрична — mark_banned эскалирует TTL до часов, поэтому 20-минутный сбой площадки выбил бы из выдачи весь пул. Один список маркеров этого различить не мог: и капча, и страница ошибки шли одним путём в BanPageDetectedError. Проба прода 06.09.2026 09:25 UTC (одна карточка по узлам через сайдкар): * узел 14, час назад отдававший «Captcha - база объявлений ЦИАН», вернул НАСТОЯЩУЮ карточку — капча снимается за 1-2 часа, то есть TTL бана по назначению; * узел 1 отдал ТРЕТИЙ вариант отказа — `<title>Вы не робот?`, 16 КБ (час назад — «Ошибка - Циан», 374 КБ). Прежние маркеры его не знали вовсе: отказ уезжал наверх как валидный HTML ровно так же, как до #3402. Маркеры разделены на два класса, одинаково в обоих слоях (образы backend и browser деплоятся раздельно и расходятся на часы): * КАПЧА — «captcha - база объявлений циан» + «вы не робот?»: безусловный отказ площадки, прежний путь (сайдкар → BanPageDetectedError → 403 + ban_page; kit → report_platform_ban + CianBlockedError). За ней нет контента, и узел, которому её показали, будет получать её дальше; * «ошибка - циан» — ТОЛЬКО ЛОГ: сайдкар отдаёт HTML клиенту как есть и пишет WARNING «страница ошибки Циана (title=…, upstream=…) — не бан, только лог (#3402)», kit при провале extract_state пишет WARNING и возвращает прежний None. Ни бана, ни рапорта, ни исключения — решение принимаем по частоте в логах за цикл наблюдения, а не по догадке о природе страницы. Нормализация заголовка прежняя (регистр/пробелы/тире). `_is_cian_refusal` → `_is_cian_captcha` + `_log_cian_error_page`; `_refusal_title` → `_page_title` и два кортежа маркеров рядом. Фальсификация: «вы не робот?» убран из маркеров обоих слоёв → kit 1 failed («DID NOT RAISE CianBlockedError»), сайдкар 3 failed («DID NOT RAISE BanPageDetectedError», `_is_cian_captcha` → assert False is True). Маркер возвращён, обе сьюты зелёные: backend 5599 passed / 35 skipped, browser 246 passed. |
|||
| ff979d4afd |
fix(tradein/cian): капча Циана с HTTP 200 — отказ площадки в обоих слоях (#3402)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 11s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 1m28s
CI Trade-In / backend-tests (pull_request) Successful in 5m11s
Циан отдаёт капчу (`<title>Captcha - база объявлений ЦИАН`, 44 КБ) и страницу
ошибки (`<title>Ошибка - Циан`, 374 КБ) с кодом 200. Детектор сайдкара их не знал
(_REFUSAL_STATUSES {403,429} + маркеры Авито/Домклика), HTML уезжал клиенту как
успех, extract_state возвращал None и провайдер печатал «defaultState extraction
failed» — отказ ПЛОЩАДКИ читался как дрейф НАШЕЙ разметки. Аренда при этом не
менялась: fetch() уже отрапортовал mark_health(ok=True), fail-streak обнулялся, и
один капча-узел сжигал батч целиком (6200: 0/210; 6123/6091/6052/6032/6010/5981:
0/400 — против 161/162 через здоровый узел на прогоне 13).
Два слоя, потому что образы backend и browser деплоятся раздельно и расходятся
на часы:
* сайдкар (browser/server.py) — детект по <title> на обоих путях (navigate и
подзапрос) → BanPageDetectedError → прежний путь #3288/#3379: 403 + ban_page +
ЧЕСТНЫЙ upstream-статус 200;
* kit (providers/cian/detail.py) — при провале extract_state те же маркеры →
CianBlockedError вместо тихого None, плюс report_platform_ban по живому lease.
Там же ветка SidecarBanPageError: отказ, опознанный сайдкаром, больше не
гасится общим `except` в «не смогли разобрать».
Слово `captcha` признаком быть не может: в нормальной карточке оно встречается 11
раз, на капче 17. Детект по <title> с нормализацией тире.
`_report_platform_ban` → `report_platform_ban` (публичный): тем же путём обязан
идти отказ, распознанный не сайдкаром, а провайдером. report_ban один только
пишет бан пары «узел×источник» — сменить сожжённую аренду ВНУТРИ батча позволяет
только fail-streak (_LEASE_ROTATE_AFTER_FAILS).
|
|||
| 06ad504505 |
Merge pull request 'refactor(tradein): одна реализация scrape_runs — kit orchestration/runs.py, app-модуль = алиас; counters везде мержатся (#3390)' (#3400) from refactor/3390-single-runs-module into main
All checks were successful
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m9s
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / build-backend (push) Successful in 1m42s
Deploy Trade-In / deploy (push) Successful in 7m48s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
|
|||
| ae6ad9e440 |
docs(#3390): убрать ложные «counters ЗАМЕНЯЕТ» из комментариев/докстрингов; гейт test_3168 — честная формулировка
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m51s
|
|||
| ec7838b7a9 |
fix(#3398): пустой пул в фоновой догрузке — WARNING без трейсбека (общая функция); комментарий про stateless
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Successful in 4m58s
На проде `ESTIMATE_EXTERNAL_SOURCES_BACKGROUND=true` (docker-compose.prod.yml:296), поэтому синхронный cian-вызов идёт с `fetch_on_miss=False` и возвращает None ДО прокси-слоя (`providers/cian/valuation.py:171`) — добавленная в этой ветке ветка WARNING в `estimate_quality` на проде почти не звучит. Настоящий фетч уходит в `_defer_external_refresh`, где `NoProxyAvailableError` попадал в общий `except Exception: logger.exception(...)` → ERROR + traceback → событие в GlitchTip на каждый /estimate по новому адресу: ровно тот шум, который PR и убирает. Правка в ОБЩЕЙ функции отложенной догрузки, а не в cian-ветке: через неё идут все источники фонового режима (yandex тоже — у него swallow живёт внутри `_get_or_fetch_yandex_valuation_cached`, дыры нет, но следующий источник получит поведение бесплатно). Для прочих исключений всё как было: `logger.exception`. Тест по значению: background=True + пустой пул в production → фоновая догрузка cian логирует WARNING «пул прокси пуст», записей ERROR/traceback у логгера эстиматора нет. Задача дожидается внутри того же loop'а и не снимая патчей (`_DEFERRED_REFRESH_TASKS` + `asyncio.gather`) — иначе `anyio.run` закрывает loop раньше старта задачи и тест был бы зелёным по построению. На HEAD ветки тест красный: ERROR app.services.estimator:estimator.py:910 deferred cian_valuation: догрузка не удалась (кэш не прогрет) + Traceback … NoProxyAvailableError. Комментарий у `_c_kwargs`: весь dict переиспользуется замыканием фоновой задачи, то есть `config`/`proxy_provider` — один инстанс на два возможно-одновременных вызова. Корректно ровно пока оба stateless (`RealScraperConfig` — read-only снимок настроек, `RealProxyProvider` без полей, короткая сессия БД на операцию); появится per-вызов состояние — фоновой задаче нужен свой инстанс. |
|||
| 36f2429fbe |
refactor(tradein/runs): одна реализация scrape_runs — kit, семантика counters мерж (#3390)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m58s
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
Две живые копии одного модуля с противоположной семантикой counters: app
`mark_done`/`mark_failed`/`mark_banned`/`update_heartbeat` ЗАМЕНЯЛИ
(`counters = CAST(:counters AS jsonb)`), kit — МЕРЖИЛИ
(`COALESCE(counters,'{}') || …`). Расхождение дважды за сутки дало ложные
выводы на ревью (#3388 «отдать только флаг, остальное домержится» — на
replace это стёрло бы измеренное; #3355). Разошлись и другие места: гейт
статуса, `honors_cancel` у mark_cancelled (был только в app), `mark_skipped`
(только в kit), `mark_backfill_finished`/`distinct_sources` (только в app).
Реализация теперь одна — `scraper_kit.orchestration.runs`; в неё перенесены
app-only функции. `app.services.scrape_runs` — алиас kit-модуля через
sys.modules, а не реэкспорт имён: реэкспорт разводит патч-цели
(`patch("app.services.scrape_runs.sentry_sdk")`, `patch.object(runs_mod,
"mark_done")` правили бы глобаль модуля-обёртки, а тело функции читает
глобаль kit'а) — тест остался бы зелёным, не подменив ничего. С алиасом оба
имени ведут в единственную реализацию, и ни один из ~40 вызывающих и ~30
патч-сайтов в тестах не правится.
Победила семантика мержа: у строки прогона несколько писателей (пульс,
финализатор, дрейн), каждый знает лишь свои ключи, и замена теряла чужие —
чекпоинт done_buckets (#930), метку interrupted (#3391), замер из пульса
(#3384). Обратной зависимости («вызывающий рассчитывает, что финализатор
УДАЛИТ ключ заменой») нет: строка создаётся пустой в create_run, резюм читает
counters ПРЕДЫДУЩЕГО прогона по его id.
Тесты по значению на обоих путях импорта (двойник сессии читает SQL: `||`
против CAST, WHERE-гейт из текста): пульс {a:5} + mark_failed {b:1} → {a,b};
пульс/финализатор по финализированной строке — no-op; mark_cancelled
отказывает источнику, который отмену не опрашивает. На main эти тесты
красные для app-пути.
Комментарии в app/services/scheduler.py и kit/pipeline.py, утверждавшие про
живого «перезаписывающего двойника», приведены в соответствие.
|
|||
| 8a5f75d5f4 |
fix(tradein/estimator): yandex/cian valuation в /estimate через пул прокси, пустой пул не роняет ответ (#3398)
All checks were successful
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m4s
Тот же корень, что у IMV (#3386 / PR #3397): оба вызова шли БЕЗ `proxy_provider` — `YandexValuationScraper(RealScraperConfig(), delay_provider=...)` и `estimate_via_cian_valuation(**_c_kwargs)`. `providers/_proxy.py::curl_proxy_url` считает `use_pool = флаг AND provider is not None`, поэтому пул был выключен по построению, а curl уходил на env-прокси SCRAPER_PROXY_URL (выключенный узел #2613): на проде 06.09 каждая проба `/estimate` давала `curl_cffi ProxyError: CONNECT tunnel failed, response 407`, а в логах это читалось как «yandex_valuation: empty result» — оба источника мертвы с 02.09. Kit-стороне правки не нужны: `YandexValuationScraper.__init__` и `estimate_via_cian_valuation` уже принимают kwarg-only `proxy_provider` и сами зовут `curl_proxy_url` (yandex — lease на сессию __aenter__/__aexit__, cian — lease на вызов, release в finally). Провайдер берётся из module-level импорта `RealProxyProvider` (он stateless: короткая сессия на операцию), поэтому один инстанс в `_c_kwargs` покрывает и основной вызов, и отложенную фоновую догрузку. Мягкая деградация сохранена и стала честной в логах: `NoProxyAvailableError` (проверка по цепочке причин `caused_by_no_proxy`) → WARNING «пул прокси пуст — продолжаем без Yandex/Cian» и прежний None-путь, а не ERROR «fetch failed» / «lookup failed» (запрос вообще не уходил — GlitchTip-событие тут было бы шумом). Строка «yandex_valuation: empty result» переписана: None неразличимо «дом не найден» и «фетч не дошёл», причина — в строке scraper_kit выше. Тесты (7, по значению; на main все красные): оба call site получают провайдера; пустой пул в production → оценка без этих источников, без исключения, HTTP не уходит, в логе «пул прокси пуст»; lease освобождён ровно один раз на успехе и на ошибке фетча — через настоящий `curl_proxy_url`, а не мок провайдера. |
|||
| 075cec4b57 |
Merge pull request 'fix(tradein/estimator): IMV-путь /estimate через пул прокси; пустой пул — честная деградация без IMV (#3386 п.в)' (#3397) from fix/3386-estimator-imv-proxy-pool into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 9s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m2s
Deploy Trade-In / build-backend (push) Successful in 1m39s
Deploy Trade-In / deploy (push) Successful in 1m14s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
|
|||
| 33cad4f82e |
fix(#3386): curl_proxy_url — health=False и на отмене (BaseException); admin IMV через пул; тест без create=True
All checks were successful
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m57s
|
|||
| e1f86e3c1b |
Merge pull request 'fix(scraper-kit/cian): фаза houses cian_city_sweep через пул прокси + стоп на пустом пуле (#3394)' (#3396) from fix/3394-cian-sweep-houses-proxy-pool into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m6s
Deploy Trade-In / build-backend (push) Successful in 1m48s
Deploy Trade-In / deploy (push) Successful in 3m1s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
|
|||
| 6f97995140 |
fix(tradein/estimator): IMV-путь берёт прокси из пула, пустой пул не ломает /estimate (#3386)
All checks were successful
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Successful in 4m55s
Оба вызова `evaluate_via_imv` в `_get_or_fetch_imv_cached` шли без `proxy_provider`, а `providers/_proxy.py::curl_proxy_url` считает `use_pool = флаг AND provider is not None` — пул был выключен по построению, curl-сессия уходила на env-прокси SCRAPER_PROXY_URL (мёртвый узел, #2613). Провайдер берётся из уже существующего module-level импорта `app.services.scraper_adapters` (в estimator цикла нет, в отличие от house_imv_backfill — там lazy import вынужденный). Lease — один на вызов IMV, acquire/release внутри `curl_proxy_url`, release в finally на всех выходах. Пустой пул в проде (`NoProxyAvailableError`, в т.ч. завёрнутый — проверка по цепочке причин `caused_by_no_proxy`) остаётся graceful: `_get_or_fetch_imv_cached` возвращает None, ответ отдаётся без IMV-якоря. Причина в логе теперь честная — «пул прокси пуст», а не «fetch failed» (запрос не уходил вовсе). |
|||
| fdae823762 |
fix(#3394): стоп по пустому пулу — по цепочке причин на уровне якоря; один errors_count; ban_kind=infra под тестом
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m55s
|
|||
| ba4fb03a05 |
fix(#3393): capped по сканированному окну — interrupted-строка не глушит лестницу навсегда; контроль zero-сторожа
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m55s
|
|||
| e12ece8fe6 |
fix(tradein/cian): houses-фаза cian_city_sweep идёт через пул прокси + стоп на пустом пуле (#3394)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m59s
`run_cian_city_sweep` звал `fetch_newbuilding(zhk_url, config=config)` без `proxy_provider`, хотя провайдер лежит в аргументах самого свипа и соседние фазы (SERP через CianScraper, detail через cian_fetch_detail) его передают. Внутри это давало `build_browser_fetcher(config, "cian", proxy_provider=None)` → `use_pool` эффективно False → POST /fetch без "proxy" → сайдкар брал свой env-узел SCRAPER_PROXY_URL, на проде выключенный (407 → camoufox InvalidIP → /fetch 503, факт #3386). Фаза houses давала 0/30 с 02.09 (run 6179: 24 × `houses failed ... 503 Service Unavailable`), строк `override=True` в логах сайдкара по ней не было ни одной. Остальные вызывающие `fetch_newbuilding` / `resolve_cian_zhk_url_via_search` провайдер уже передают (#2767/#2830/#3382), этот вызов был последним мимо пула. Второе: пустой пул поднимается ДО запроса, следующий дом упрётся ровно в то же самое — общий `except Exception` на дом превращал это в 30 одинаковых houses_failed и прогон уходил в 'done'. Теперь NoProxyAvailableError рвёт фазу и свип: `no_proxy_stop=1` в counters, mark_banned с ban_kind='infra' и сохранённым done_buckets (образец — #3389 yandex-nb-sweep, #3382). Ветка стоит ДО generic-except, иначе наш отказ инфраструктуры читался бы как «IP likely blocked» — бан площадки. |