Commit graph

177 commits

Author SHA1 Message Date
bot-backend
034ab48cfe fix(tradein/scraper): собирать вторичку Циана по области, а не только новостройки
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
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 2m38s
2026-08-02 14:00:22 +03:00
bot-backend
b586b5ff68 fix(tradein/snapshot): починить зависающий запрос снапшотов и добавить бюджет времени (#2607)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
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 2m37s
Root cause: event-diff CTE джойнил "today" (снимок за CURRENT_DATE) с "prior"
(DISTINCT ON по всей listing_source_snapshots, ~2.6-2.8M строк) обычным JOIN.
Планировщик оценивал today в 1 строку (свежевставленные в той же транзакции
строки ANALYZE ещё не видел) → Nested Loop без Materialize пересчитывал
DISTINCT ON по всей таблице заново на каждую из ~80-140k реальных строк today
(EXPLAIN на проде: cost≈300k на этом шаге) — прогон не укладывался ни в 6h
zombie-порог, ни в сутки, каждую ночь минимум с 19 июля.

Переписано на JOIN LATERAL (per-row indexed point-lookup через idx_lss_source_date,
cost упал до ~4.4/строку). Плюс budget_sec → SET LOCAL statement_timeout как
defense-in-depth (по образцу geocode_missing_listings) — задача теперь честно
падает в mark_failed вместо того чтобы висеть сутками, если план когда-нибудь
разрегрессирует снова.

Зомби-детектор (reap_zombies) не тронут — он только помечает scrape_runs.status,
не убивает backend (нет pid/application_name в схеме run'а); pg_terminate_backend
для этого — отдельный follow-up, не в этом PR.
2026-08-02 11:55:43 +03:00
16237573ed chore(tradein/proxy): выпилить мёртвые узлы mobileproxy и их ключи из пула (#2613) (#2614)
All checks were successful
Deploy Trade-In / changes (push) Successful in 18s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m28s
Deploy Trade-In / test (push) Successful in 2m53s
Deploy Trade-In / build-backend (push) Successful in 28s
Deploy Trade-In / deploy (push) Successful in 1m21s
DELETE 4 мёртвых mobileproxy-узлов (id 2/3/4/5) из scrape_proxies. Подписка закрыта, узлы мертвы с 4-9 июля; у трёх в rotate_url лежал чужой API-ключ открытым текстом (источник блокера PR #2611). scrape_proxy_rotations пуста, единственный FK не мешает. Условие по домену (url LIKE mobileproxy.space), не по id.
2026-08-01 20:05:06 +00:00
887df54908 Merge pull request 'feat(tradein/proxy): ротация exit-IP ASocks по бану со счётчиком и громким отказом (#2600)' (#2611) from feat/tradein-asocks-rotation into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 13s
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 2m39s
Deploy Trade-In / build-backend (push) Successful in 1m3s
Deploy Trade-In / deploy (push) Successful in 1m2s
2026-08-01 19:23:51 +00:00
bot-backend
f83ca44179 fix(tradein/data): убрать ложный region_code=66 у объявлений чужих городов (#2604)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
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 2m47s
Миграция 200 (номер 199 занят параллельным PR #2611, не смержен в main).
UPDATE listings SET region_code=NULL WHERE source='avito' и slug города в
source_url не входит в наши шесть (ekaterinburg/nizhniy_tagil/
kamensk-uralskiy/pervouralsk/verhnyaya_pyshma/serov). Строки — наследие
массового заброса 18 июня до появления гео-фильтра карточек (f0264237,
20 июня), канал закрыт, все 16930 строк is_active=false.

NULL вместо настоящего региона: колонку не читает ни одна живая выборка,
восстанавливать регион по тексту не будем. Idempotent (region_code IS NOT
NULL guard). Только UPDATE, без DDL.
2026-08-01 21:52:00 +03:00
bot-backend
b1563b86cb feat(tradein/proxy): ротация exit-IP ASocks по бану со счётчиком и громким отказом (#2600)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
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 2m40s
2026-08-01 21:41:12 +03:00
bot-backend
ad76fe844a fix(tradein/geocode): бэкфилл listings.city из слага города в URL Авито (#2594)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
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 2m27s
2026-08-01 01:46:03 +03:00
bot-backend
0a4e126b30 fix(tradein/scraper): проставлять город объявления из контекста развёртки (#2594)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
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 2m28s
Скрапер знает город в момент сбора (city_slug из CITY_LOCATIONS/CITY_ANCHORS,
scraper_kit.orchestration.pipeline), но раньше нигде его не записывал. Провайдеры
(avito/cian) часто отдают адрес БЕЗ города в тексте ("ул. Победы, 30" вместо
"Нижний Тагил, ул. Победы, 30" — cian даже явно вырезает location-часть перед
записью, providers/cian/serp.py _format_address skip_types={"location",...}).
Без города такой адрес при геокодинге считался "город не назван" и коллизировал
с одноимённой екатеринбургской улицей (Ленина/Победы/Тенистая — сотни совпадений
в ЕКБ-реестрах) → объявление получало координаты Екатеринбурга.

Fix: отдельная колонка listings.city (196_listings_city.sql), проставляется из
sweep-контекста через save_listings(..., city=...) — НЕ парсингом/дописыванием
в address. Раздельная колонка не портит исходный текст адреса: downstream
text-парсеры (geocoder._parse_street_house/_names_non_ekb_city, estimator
house-matching) продолжают работать на исходном сыром тексте неизменёнными —
дописывание города в address ломало бы bare-form адреса без street-маркера
("Дружинина, 33" без "ул.") в этих же парсерах.

Симметрия: EKB-варианты city-sweep функций (city_slug=None) тоже получают
city="Екатеринбург" — resolve_city_name(None) даёт тот же ЕКБ-дефолт, что и
get_city_location/get_city_anchors. Проставлено во всех продовых write-путях:
run_avito_city_sweep/run_yandex_city_sweep/run_cian_city_sweep (city_slug-aware),
run_avito_newbuilding_sweep/run_cian_full_load/run_yandex_full_load/
run_avito_full_load (подтверждённо EKB-only по докстрингам), run_domclick_city_sweep
(EKB city_id, oblast B2 ещё не wired — честный None для неизвестного city_id).

Scope: только write-path для НОВЫХ листингов. Бэкфилл накопленных строк и
консультация city в geocode_missing_listings/backfill_coords_from_geoportal
(gate там пока text-only, _names_non_ekb_city) — geocoder.py намеренно не
тронут (#2582/#2580) — отдельные follow-up задачи.
2026-07-31 22:14:47 +03:00
bot-backend
cf35e632db fix(tradein/tasks): городской гейт в ночном бэкфилле координат (#2583)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 13s
CI / changes (pull_request) Successful in 14s
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 3m8s
backfill_coords_from_geoportal брал все listings с lat IS NULL, парсил street+house
и матчил напрямую против EKB-only ekb_geoportal_buildings, минуя geocoder.geocode()
и его городской гейт (_names_non_ekb_city). Улица+дом могут буквально совпасть между
Екатеринбургом и другим городом области ("проспект Ленина 1" есть и в ЕКБ, и в Нижнем
Тагиле) — такие адреса получали екатеринбургские координаты и портили радиусные
выборки аналогов на этой улице в ЕКБ, а сами исчезали из выборки своего города.

Фикс: _names_non_ekb_city(address) перед вызовом _geoportal_house_match — тот же гейт,
что уже используется в geocode(). Прямой вызов geoportal-матчера (а не полноценный
geocode()) сохранён намеренно — pure local-DB операция без внешнего HTTP, полноценный
geocode() добавил бы Nominatim/Yandex вызов на каждый non-EKB адрес backlog'а (лишняя
нагрузка на ограниченный Nominatim, Yandex сейчас 403 — #2585).

geo_precision оставлен NULL для house-level матчей этого тира — по конвенции
089_listings_geo_precision.sql/geocode_missing.py NULL означает "не coarse", то же
значение что geo_precision=None для precise-адресов в geocode_missing_listings;
исключать из radius-аналогов нужно только 'city'-fallback.

Порядок окон (05:00 geoportal → 06:00 geocode_missing_listings) не менялся: гонка была
безвредна для корректно заматченных EKB-адресов, вредна только из-за отсутствия гейта —
теперь non-EKB адреса здесь не матчатся вообще и просто ждут oblast-aware провайдеров
в следующем окне.

Поправлен ложный комментарий в migration 171 ("не-ЕКБ адреса не матчатся — корректно").

Ущерб на проде (SELECT-only, без изменений): 2040 листингов с координатами внутри
EKB-bbox (56.65-56.95, 60.40-60.85) при адресе, называющем другой город области
(1941 после исключения мкр/р-н/жк-омонимов вроде ЖК "Заречный" внутри ЕКБ). Только
~31 из них совпадают по координатам с ekb_geoportal_buildings/gendesign_cad_buildings —
основной массив, вероятно, из других источников координат (не только этот таск).
Чистка — отдельный шаг.
2026-07-31 18:38:39 +03:00
bot-backend
f44ed4043c feat(tradein/db): авто-refresh ценовых бэндов по городам + порог для малых городов (#2576)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 12s
CI / changes (pull_request) Successful in 13s
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 2m21s
Миграция 178 засеяла deal_city_price_bands разово (N>=30 сделок), без scheduler'а
на refresh. Город без строки падал на глобальный DEAL_MIN_PPM2=50000 (ЕКБ-калибровка)
— для дешёвых городов области это не anti-outlier guard, а cut-off легитимного рынка
(Североуральск median ~21.7k). Замер по прод-данным: 289 из 369 не-ЕКБ городов
(1265 сделок) не имели строки и падали на ЕКБ-порог.

- 194_deal_city_price_bands_tiers.sql — трёхуровневая схема (tier колонка):
  full (N>=30, own p1/p99, unchanged) / rough (N 10-29, own p1 floor + фикс.
  ceiling 800000) / region_fallback (N 1-9, pooled областной p1=15263 вместо
  ЕКБ-порога). Екатеринбург по-прежнему не в таблице — estimator fallback
  byte-identical.
- 195_scrape_schedules_seed_deal_city_price_bands_refresh.sql — scrape_schedules
  row, окно 07:00-08:00 UTC (после rosreestr_dkp_import + asking_to_sold_ratio_refresh).
- app/tasks/deal_city_price_bands_refresh.py — периодический re-derive (kit-scheduler,
  byte-identical 194 derivation), без DELETE (множество городов монотонно растёт).
- app/services/product_handlers.py — регистрация Handler для нового source.

Валидация: scratch-БД (syntax_check) в прод-контейнере, synthetic данные на
границах тиров (N=9/10/29/30) + Екатеринбург/non-rosreestr/NULL exclusion, оба
файла применены дважды (идемпотентность подтверждена), scratch-БД удалена.
2026-07-31 16:33:20 +03:00
bot-backend
f915e1ee4d fix(tradein/db): не затирать manager_id при повторном прогоне сида (#2557)
All checks were successful
CI / changes (pull_request) Successful in 12s
CI Trade-In / 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 2m0s
Deep-review #2564: manager_id синкался из EXCLUDED безусловно — повторный прогон сида
тихо обнулял связь сотрудник->менеджер, назначенную через team-API (#2563), сотрудник
выпадал из _LIST_EMPLOYEES_BY_MANAGER_SQL. Защищён COALESCE, как остальные UI-managed
поля.

is_active убран из ON CONFLICT DO UPDATE SET вовсе (не COALESCE — колонка NOT NULL
DEFAULT true делала бы COALESCE-ветку недостижимой, мёртвый код вводил в заблуждение
симметрией с реально работающими COALESCE-полями). Open/close доступа — решение
владельца продукта через UI (#2556), не повторный прогон seed-файла.
2026-07-30 22:08:14 +03:00
bot-backend
e7a658c7fa feat(tradein/db): seed существующих юзеров + ASCII-CHECK на username (#2557)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
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 1m56s
Migration 193: переносит org-карту (admin/kopylov/praktika/user1-10), утверждённую
владельцем продукта, из auth/roles.yaml в tradein_users (Foundation — миграция 192).
password_hash=NULL для всех — пароли админ проставит вручную через team-UI (#2556).
ASCII-CHECK на username (deep-review #2561): rbac кодирует session-username через
encode("latin-1","replace"), кириллические логины одинаковой длины схлопываются
в общий downstream-identity (IDOR) — constraint запрещает это fail-closed.

ON CONFLICT DO UPDATE защищает password_hash/is_active/display_name/org_name/email
через COALESCE — повторный прогон (recovery / staging без _schema_migrations
tracking) не затирает то, что менеджер поменял через UI.
2026-07-30 21:56:16 +03:00
bot-backend
0c5246913a feat(tradein/db): tradein_users + tradein_sessions schema (#2551)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
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 1m10s
2026-07-30 09:40:05 +03:00
5f3579b8c2 feat(tradein/auth): восстановить доступ praktika + безлимит через данные вместо хардкода (#2544)
All checks were successful
Deploy Trade-In / changes (push) Successful in 13s
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 1m2s
Deploy Trade-In / build-backend (push) Successful in 5m35s
Deploy Trade-In / deploy (push) Successful in 1m14s
2026-07-26 23:29:53 +00:00
bcb903cfa2 fix(tradein/devops): проверка всего стека после деплоя + запас на остановку (#2540)
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-browser (push) Successful in 35s
Deploy Trade-In / build-frontend (push) Successful in 37s
Deploy Trade-In / test (push) Successful in 4m55s
Deploy Trade-In / build-backend (push) Successful in 29s
Deploy Trade-In / deploy (push) Successful in 1m12s
2026-07-26 22:57:41 +00:00
a450aed71b fix(tradein/sql): цена в подписи дедупликации «доли квартир дома в продаже» (#2539)
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 4m53s
Deploy Trade-In / build-backend (push) Successful in 28s
Deploy Trade-In / deploy (push) Successful in 1m6s
2026-07-26 22:50:39 +00:00
dce2cd2040 fix(tradein): откат транзакции в backfill + неотрицательный счётчик квот (#2538)
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m52s
Deploy Trade-In / build-backend (push) Successful in 1m0s
Deploy Trade-In / deploy (push) Successful in 6m14s
2026-07-26 22:21:08 +00:00
ca1015bd4e feat(tradein/support): веб-чат поддержки — серверная часть поверх Telegram-моста (#2532)
All checks were successful
Deploy Trade-In / changes (push) Successful in 14s
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 4m59s
Deploy Trade-In / build-backend (push) Successful in 1m10s
Deploy Trade-In / deploy (push) Successful in 1m17s
2026-07-26 20:43:47 +00:00
bot-backend
b579fa4ced feat(tradein/tgbot): Telegram support-мост @MERAsupport_bot
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
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 5m4s
Клиент пишет боту в личку → воркер зеркалит сообщение через copyMessage
в топик супергруппы-форума → оператор отвечает реплаем на зеркало → бот
доставляет ответ клиенту. Полный лог переписки в Postgres.

Отдельный контейнер на long-polling, а не webhook в tradein-backend:
не нужно пробивать дырку в auth-middleware (_PUBLIC_PATHS, #2213) и
маршрут в Caddy, нулевая внешняя поверхность, падение бота не задевает API.
Без aiogram — httpx уже в зависимостях, нужны только getUpdates/copyMessage.

Маршрутизация ответа — по topic_message_id: message_id в Telegram уникален
в пределах чата сквозь все топики, а все зеркала лежат в одном support-чате,
поэтому спутать адресата нельзя. Реплай на шапку/на ответ другого оператора
не резолвится (у direction='out' topic_message_id IS NULL) → тихий игнор.

Безопасность (найдено ревью, воспроизведено эмпирически):
- токен Telegram живёт в PATH URL, поэтому sanitize_url его не режет;
  утекал в GlitchTip через locals стек-фреймов (include_local_variables
  по умолчанию True) и через span data HttpxIntegration. Закрыто
  include_local_variables=False + regex-редактор в before_send (обе формы:
  /bot<id>:<secret> и голая <id>:<secret>), поверх существующего PII-scrub.
- httpx-логгер печатает полный URL на INFO → боевой токен уходил бы в
  docker logs каждые 30с. Приглушён до WARNING.

Надёжность:
- kill-switch при пустом токене — idle-блокировка, не exit(0): при
  restart: unless-stopped выход с любым кодом даёт рестарт-луп.
  unless-stopped выбран сознательно — только он гарантирует автозапуск
  после ребута VPS.
- stop_grace_period: 120s — дефолтные 10с убивали бы контейнер раньше,
  чем докрутится long-poll (30с) и отработает drain (100с).
- сбой SQL теперь ловится отдельно и делает rollback перед сдвигом offset:
  иначе сессия в failed-transaction не давала сохранить offset, апдейт
  переигрывался и зеркалился в топик по кругу.

152-ФЗ: переписка — ПДн, ON DELETE CASCADE по chat_id, удаление клиента
одним DELETE. Ретенция — follow-up.

Бот не включается автоматически: TELEGRAM_* задаются в runtime-env на VPS,
без них воркер штатно висит в idle. Порядок — в DEPLOY.md.

Тесты: 51 passed (маршрутизация обоих направлений, дедуп, 403→is_blocked,
throttle-окно шапки, redaction токена во всех формах event).
2026-07-16 16:58:53 +03:00
d530602fbd fix(tradein/quota): per-user quota override + no-burn on empty estimate (#2517)
Some checks failed
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Has been cancelled
Co-authored-by: bot-backend <bot-backend@gendsgn.local>
Co-committed-by: bot-backend <bot-backend@gendsgn.local>
2026-07-13 20:18:42 +00:00
5647c2b40e feat(tradein/db): user_events table — unified login/IP audit + behavior event log (#2515)
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 48s
Deploy Trade-In / build-backend (push) Successful in 29s
Deploy Trade-In / deploy (push) Successful in 52s
Append-only user_events table (event_type, username, ip_address inet, user_agent, path, method, estimate_id, payload jsonb, created_at) + 4 indexes. Foundation for login/IP audit + behavior analytics. Resurrects dropped audit_log design.
2026-07-13 19:47:26 +00:00
60c21ae2a5 feat(tradein/lead): persist 152-ФЗ proof-of-consent (LOW audit R2 #9) (#2511)
All checks were successful
Deploy Trade-In / changes (push) Successful in 15s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 32s
Deploy Trade-In / test (push) Successful in 58s
Deploy Trade-In / build-backend (push) Successful in 1m30s
Deploy Trade-In / deploy (push) Successful in 1m20s
2026-07-12 22:13:36 +00:00
3bd0873230 fix(tradein/scheduler): re-enable deactivate_stale_domklik now that domclick sweeps run (LOW audit R2 #10) (#2510)
Some checks failed
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 15s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 1m1s
Deploy Trade-In / build-frontend (push) Has been cancelled
Deploy Trade-In / build-backend (push) Has been cancelled
2026-07-12 22:12:12 +00:00
2cd24ecfbc fix(tradein/scrapers): clamp implausible listing/publish dates (LOW audit R2 #6) (#2507)
Some checks failed
Deploy Trade-In / test (push) Blocked by required conditions
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / build-frontend (push) Blocked by required conditions
Deploy Trade-In / build-browser (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / changes (push) Has been cancelled
2026-07-12 22:11:06 +00:00
7fa52bb3ad fix(tradein/estimator): honest repair-note + ratio_basis label; doc EKB-calibrated constants (LOW audit R2 #1-4) (#2505)
Some checks failed
Deploy Trade-In / test (push) Blocked by required conditions
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / build-frontend (push) Blocked by required conditions
Deploy Trade-In / build-browser (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / changes (push) Has been cancelled
2026-07-12 22:11:03 +00:00
bot-backend
07275c3c97 feat(tradein): монитор свежести СберИндекса (audit п.1)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / 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 / backend-tests (pull_request) Successful in 51s
Добавляет sber_freshness_monitor по образцу deals_freshness_monitor:
staleness данных СберИндекса теперь видна на MONITOR-частоте, а не тонет
в per-estimate warning'ах estimator._load_sber_index_series (#audit-5a).

- app/tasks/sber_freshness_monitor.py: чистая evaluate_sber_freshness()
  (frozen-now, без БД) + check_sber_freshness() (один SELECT
  max(period_month) вторичного сегмента по региону, #R2-H1 фильтр как в
  эстиматоре; WARNING при stale, mark_done при алерте — это монитор, не
  сбой; mark_failed только при пустой таблице).
- app/services/product_handlers.py: _job_sber_freshness_monitor + Handler
  в build_product_handlers (run_in_executor, как deals-монитор).
- data/sql/180_seed_sber_freshness_monitor.sql: seed scrape_schedules
  (enabled, daily 09:00-10:00 UTC, lag_allowance_days=25).
- tests/test_sber_freshness_monitor.py: frozen-now (fresh/stale/границы) +
  FakeDB (fresh/stale/empty/кастомный lag) + свойства миграции + registry.

Порог алерта: sber_index_max_age_days (35) + lag_allowance (25) = 60д.
+25 — запас на инхерентный лаг публикации источника (1-2 мес), чтобы не
шуметь на штатном отставании. Прод 2026-07-12: max=2026-05-01, age=72д >
60 → alert=1.
2026-07-12 23:27:35 +03:00
bot-backend
cf91ac6b24 feat(tradein/scraper): per-city sweep capability для 5 городов области (schedules disabled)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
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 52s
B1: avito/cian/yandex city-sweep по городам Свердловской обл. (Н.Тагил/Каменск/Первоуральск/В.Пышма/Серов). Schedules засеяны DISABLED — rollout вручную по одному городу (proxy = 1 живой прокси).

- pipeline.py: CITY_ANCHORS (5) + get_city_anchors(slug) (None→EKB fallback).
- scheduler.py: sweep-jobs читают params.city → анкоры; wildcard-хендлеры *_city_sweep_* (source UNIQUE → per-city source, как deactivate_stale_*).
- geo.py: OBLAST66_BBOX + is_within_oblast66_bbox; avito/detail.py ingest-guard EKB→oblast66.
- price_brackets.py: 5 city-seed (reuse EKB). migration 179: 15 rows enabled=false, staggered, idempotent.
- +6 тестов. domclick (B2) не тронут.
2026-07-12 20:06:46 +03:00
bot-backend
e2502a0134 fix(tradein/estimator): per-city ценовой band ДКП-сделок для всей обл. 66
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
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 50s
Глобальный DEAL_MIN_PPM2=50k резал 46.6% не-ЕКБ сделок (легит дешёвые города: Североуральск p50=21.7k, Новоуральск 36k, Асбест 41k). Band стал per-city.

- migration 178: deal_city_price_bands (city PK, ppm2_min/max, n_deals), populate из deals (source=rosreestr, city<>Екатеринбург, count>=30), min=GREATEST(p1,8000) max=LEAST(p99,800000), idempotent upsert; HAVING-guard p99>=8000 (airtight против inverted band).
- estimator.py оба enforcement-пойнта city-aware с fallback на глобаль:
  - _fetch_dkp_corridor SQL: LEFT JOIN deal_city_price_bands + COALESCE(band, :global).
  - _is_plausible_deal(city, bands) + _fetch_deals: _load_city_price_bands (TTL-cache), SELECT city.
- ЕКБ исключён из таблицы → fallback на 50k/800k → EKB-сделки byte-identical (deals спеллит EKB только 'Екатеринбург', 49791).

EKB deal-level инвариант airtight; interior-EKB оценки идентичны. Регресс-гейт байт-зелёный. Follow-ups: periodic refresh band-таблицы; city-scoping _fetch_dkp_corridor (→ C2).
2026-07-12 19:07:05 +03:00
bot-backend
752cb1044b fix(tradein/ingest): раз-хардкод rosreestr DKP импорта на всю обл. 66
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / 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 / backend-tests (pull_request) Successful in 51s
- scheduler.py import_rosreestr_dkp: снят фильтр city ILIKE '%катеринбург%', address из реального city источника, deals.region_code + новая deals.city заполняются (было: хардкод 'Екатеринбург,' + region_code NULL)
- migration 177: deals.city + индекс + бэкфилл существующих EKB-строк region_code=66/city
- guard city IS NOT NULL → address не NULL для ~15 null-city строк источника
- sync deploy/import-rosreestr.sh (ops-fallback) под тот же oblast-scope
- split dedup-теста: 077 (историческая) хранит EKB-фильтр, живой импорт — нет

Открывает +47183 не-ЕКБ сделок region 66, уже сидящих в источнике, ранее резавшихся на импорте.
2026-07-12 18:11:47 +03:00
cc231946c2 feat(tradein): fill houses.year_built/material_walls from ДОМ.РФ КР1.1/1.2 + zhkh_year, propagate to listings (#2013) (#2476)
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Successful in 45s
Deploy Trade-In / build-backend (push) Successful in 1m1s
Deploy Trade-In / deploy (push) Successful in 2m46s
2026-07-12 13:12:07 +00:00
f93ef5374d feat(tradein/domclick): production Layer B detail-backfill orchestrator (#2000) (#2436)
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 47s
Deploy Trade-In / build-backend (push) Successful in 1m30s
Deploy Trade-In / deploy (push) Successful in 1m32s
2026-07-04 19:49:07 +00:00
ced616d150 feat(tradein/domclick): cookie-injection MVP для обхода QRATOR-блока (#2000) (#2433)
All checks were successful
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Successful in 49s
Deploy Trade-In / build-browser (push) Successful in 2m13s
Deploy Trade-In / build-backend (push) Successful in 1m32s
Deploy Trade-In / deploy (push) Successful in 7m16s
2026-07-04 17:49:40 +00:00
bot-backend
b238d3ef50 chore(tradein/proxy-pool): добавить domclick в provider_affinity CHECK (residential ASocks для #2000)
All checks were successful
CI / changes (pull_request) Successful in 9s
CI Trade-In / 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 49s
2026-07-04 17:21:07 +03:00
2538fc02e2 feat(tradein/lead): POST /api/v1/trade-in/lead — persist contact leads (#2376) (#2390)
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 1m36s
Deploy Trade-In / build-backend (push) Successful in 54s
Deploy Trade-In / deploy (push) Successful in 48s
2026-07-04 07:12:22 +00:00
5fa505b809 fix(tradein/scrapers): подключить geoportal coords backfill в scheduler (#1967) (#2346)
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 1m30s
Deploy Trade-In / build-backend (push) Successful in 52s
Deploy Trade-In / deploy (push) Successful in 1m48s
2026-07-03 23:25:47 +00:00
bot-backend
90731da537 feat(tradein): location-coef MVP через FDW-мост к OSM POI Птицы (#2045)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 8s
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 1m36s
Финальный PR issue #2045 (BE-3): GET /api/v1/trade-in/location-coef для
LocationDrawer. FDW foreign table -> локальное зеркало osm_poi_ekb_local
(TRUNCATE+INSERT, тот же паттерн что cad_buildings_local/cadastral_geo_match,
избегает ~1.16s/row FDW round-trip) -> straight-line POI-скоринг, портированный
из Site Finder poi_score.py::compute_poi_weighted_top7 (CATEGORY_WEIGHTS as-is,
радиус 1200м для квартир вместо Ptica 2000м для участков). score->coef -
новая MVP-эвристика (0.95..1.05, не откалибрована на реальных дельтах).

Graceful fallback (не 500, не фабрикуем факторы): пустая/не отрефрешенная
osm_poi_ekb_local или отсутствие lat/lon у оценки -> coef=1.0, factors=[],
geo_source="unavailable".

Scheduler: source=osm_poi_ekb_refresh, daily, зарегистрирован и в боевом
dispatch (scheduler.py), и в kit-registry (product_handlers.py) - иначе
test_kit_registry_completeness падает на ship-dark инварианте (#2192).

Frontend wiring (mappers.ts/LocationDrawer.tsx) - вне scope, отдельная задача
после проверки endpoint'а curl'ом на деплое.
2026-07-03 23:44:55 +03:00
bot-backend
6f170504ba fix(tradein/pii): удалить телефоны продавцов (NULL) + PII клиента (DROP) (#2217, #1969)
All checks were successful
CI / changes (pull_request) Successful in 9s
CI Trade-In / changes (pull_request) Successful in 10s
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Successful in 1m21s
CI Trade-In / backend-tests (pull_request) Successful in 1m46s
- listings.phones: миграция 166 обнуляет данные; cian.py + scraper-kit
  serp.py (golden-parity зеркало) перестают читать offer.get("phones") на
  входе — write-path выключен у источника, колонка/поле в модели остаются.
  Мёртвая запись "phones" в LISTING_FIELD_PRIORITY убрана.
- trade_in_estimates.client_name/client_phone: миграция 167 дропает колонки;
  синхронно убраны из TradeInEstimateInput, обоих INSERT в estimator.py
  (основной путь + _empty_estimate), frontend TradeInEstimateInput и
  CRM-блока EstimateForm (поля "Имя клиента"/"Телефон").
- sentry_scrub.py и management_companies.phones не тронуты — вне scope.
2026-07-03 23:00:42 +03:00
bot-backend
dd050af16d chore(tradein): полное выключение источника n1 — миграция 165 + вычистка backend/ops (#2204) 2026-07-03 10:08:56 +03:00
bot-backend
dd8b5769e5 fix(tradein/migrations): дописать 162 в _manifest_applied — stale manifest из #2228 (#2216)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
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 1m44s
2026-07-03 09:41:44 +03:00
bot-backend
33e3503f97 fix(tradein/data): yandex url-дубли — дедуп-миграция + инвариант + корень (#2235) 2026-07-03 09:39:29 +03:00
bot-backend
bbd47ae536 fix(tradein): оставить domklik активным — отключить его TTL-деактивацию (#2204)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
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 1m39s
Продуктовое решение (user, 2026-07-03): domklik остаётся активным источником;
TTL-деактивация по scraped_at (заведена миграцией 160 этой ночью) отключается
до возобновления регулярных domclick-свипов. deactivate_stale_n1 не тронут
(n1 мёртв — деактивация остаётся). Миграция 163 идемпотентна (enabled=false).

Refs #2204
2026-07-03 08:52:56 +03:00
0793571c4a fix(tradein/deploy): миграции ДО подъёма app-контейнеров + manifest-инвариант (#2216) (#2228)
All checks were successful
Deploy Trade-In / changes (push) Successful in 20s
Deploy Trade-In / build-frontend (push) Successful in 30s
Deploy Trade-In / build-browser (push) Successful in 29s
Deploy Trade-In / test (push) Successful in 1m35s
Deploy Trade-In / build-backend (push) Successful in 29s
Deploy Trade-In / deploy (push) Successful in 49s
2026-07-02 21:03:37 +00:00
ac19c4f7b3 feat(tradein/monitor): алерт на staleness данных deals по max(deal_date) (#2212) (#2226)
All checks were successful
Deploy Trade-In / changes (push) Successful in 8s
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 1m35s
Deploy Trade-In / build-backend (push) Successful in 56s
Deploy Trade-In / deploy (push) Successful in 1m2s
2026-07-02 20:50:37 +00:00
47f45fc3d2 fix(tradein/scrapers): bump scraped_at при ре-подтверждении живым + бэкфилл (#2206) (#2224)
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 1m29s
Deploy Trade-In / build-backend (push) Successful in 1m25s
Deploy Trade-In / deploy (push) Successful in 1m48s
2026-07-02 20:30:01 +00:00
f5b0076e6f fix(tradein): deactivate_stale для domklik/n1 + честная freshness по scraped_at (#2204) (#2221)
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 1m30s
Deploy Trade-In / build-backend (push) Successful in 58s
Deploy Trade-In / deploy (push) Successful in 2m57s
2026-07-02 20:10:34 +00:00
bot-backend
67a852caf1 feat(tradein/matching): fias_exact tier + estimate fias persist + suggest fias_id contract
All checks were successful
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
2026-07-02 20:49:04 +03:00
d7f4873a53 feat(proxy-pool): pool service (acquire/release/health) + healthcheck scheduler task (#2162)
Some checks failed
Deploy Trade-In / changes (push) Successful in 16s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 33s
Deploy Trade-In / test (push) Failing after 1m36s
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / deploy (push) Has been skipped
2026-07-02 16:41:39 +00:00
90e4a741f7 feat(proxy-pool): scrape_proxies table + admin bulk-load/list endpoints (#2161)
Some checks failed
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 33s
Deploy Trade-In / test (push) Failing after 1m32s
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / deploy (push) Has been skipped
2026-07-02 16:31:21 +00:00
abef3e6e02 feat(market): revoke gendesign_reader direct SELECT on raw tables (#2138 step 3/3)
All checks were successful
Deploy Trade-In / changes (push) Successful in 13s
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 1m31s
Deploy Trade-In / build-backend (push) Successful in 30s
Deploy Trade-In / deploy (push) Successful in 54s
2026-07-02 15:11:55 +00:00
03b3cb9fe4 feat(market): grant gendesign_reader SELECT on contract views (#2138 step 1/3)
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 1m30s
Deploy Trade-In / build-backend (push) Successful in 29s
Deploy Trade-In / deploy (push) Successful in 50s
2026-07-02 14:47:49 +00:00