Commit graph

2901 commits

Author SHA1 Message Date
bot-backend
efce189c8b fix(tradein/avito): починить чтение параметров, добавить metro/is_homeowner/days_on_market
Разобрались по эталонной разметке (2 живые detail-карточки, 2026-08-21), что реально
парсится и что нет:

- sale_type: item-view/item-params — Avito отдаёт "О квартире" и "О доме" как ДВА
  ОТДЕЛЬНЫХ <div> с ОДНИМ И ТЕМ ЖЕ маркером, а не один div с двумя <ul>, как
  предполагал старый код. css_first брал только ПЕРВЫЙ div — sale_type это не
  задевало (он в первом блоке), но house_type/total_floors_house/лифты из второго
  блока терялись молча через мёртвую ветку `len(ul_els) >= 2`. Теперь читаем <ul>
  из ВСЕХ блоков с этим маркером — устойчиво к порядку блоков.
- metro_stations: раньше только эвристика по тексту описания (28 строк из 54 855).
  Основной источник теперь — структурная разметка (#item-view-address, иконка
  "Пешком до метро"), покрывает станции без ограничения на суффикс имени
  (METRO_RE ловил только "-ская"/"-инская"). Описание — фолбэк.
- is_homeowner: не парсился вовсе. [data-marker='seller-info/label'] — "Частное
  лицо" -> True, "Агентство" -> False. Подтверждено разными значениями на двух
  эталонах.
- days_on_market: на странице явно нет, но есть publish_date, из которого честно
  выводится. Заодно чинит сам publish_date — искали дату ВНУТРИ item-id-блока, а
  она в СОСЕДНЕМ [data-marker='item-view/item-date'] ("сегодня в HH:MM").
- cadastral_number: подтверждено отсутствие на странице (не парсим, не выдумываем).

Тесты на реальной разметке (backend/tests/test_avito_detail_fields_3086.py) на
обеих эталонных фикстурах (большие embedded JS-блобы вырезаны из фикстур —
не используются DOM-based парсером, экономят место). estimator.py не тронут —
ни одно поле не влияет на цену.
2026-08-21 23:36:19 +03:00
e80c9e08df Merge pull request 'ops(backup): missed-run detection + Forgejo code/DB backup to S3' (#3025) from feat/2203-backup-staleness-and-forgejo into main
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy Trade-In / changes (push) Successful in 12s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy Trade-In / build-browser (push) Successful in 49s
Deploy / deploy (push) Successful in 1m45s
Deploy / deploy-status (push) Successful in 2s
Deploy / perimeter-smoke (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Successful in 2m44s
Deploy Trade-In / test (push) Successful in 3m59s
Deploy Trade-In / build-backend (push) Successful in 32s
Deploy Trade-In / deploy (push) Successful in 6m2s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 9s
2026-08-21 17:12:03 +00:00
1202804268 Merge pull request 'fix(ops): дрилл восстановления ждал БД по сокету и умирал на старте настоящего сервера' (#3026) from fix/2203-restore-drill-readiness into main
Some checks failed
Deploy / build-backend (push) Blocked by required conditions
Deploy / build-worker (push) Blocked by required conditions
Deploy / build-frontend (push) Blocked by required conditions
Deploy / deploy (push) Blocked by required conditions
Deploy / deploy-caddy (push) Blocked by required conditions
Deploy / perimeter-smoke (push) Blocked by required conditions
Deploy / deploy-status (push) Blocked by required conditions
Deploy / changes (push) Has been cancelled
2026-08-21 17:11:59 +00:00
1d479d0150 Merge pull request 'fix(tradein/browser): дожидаться QRATOR PoW-челленджа Авито вместо тихой заглушки' (#3046) from fix/3045-browser-pow-challenge-wait into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Successful in 2m18s
Deploy Trade-In / deploy (push) Successful in 2m6s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 9s
Reviewed-on: #3046
2026-08-21 17:01:24 +00:00
5c0fd78c1f fix(tradein/browser): цикл ожидания челленджа падал ровно на успешном исходе
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-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 / browser-tests (pull_request) Successful in 53s
Найдено при ревью ветки. `_wait_out_pow_challenge` опрашивал `page.content()`
без защиты, а челлендж перезагружает страницу САМ (`window.location =
location.href`). Вызов content(), попавший в момент этой перезагрузки, кидает
«Execution context was destroyed, most likely because of a navigation» — то
есть цикл ронял фетч ровно тогда, когда проверка успешно пройдена и мы
дождались того, ради чего ждали.

На моках дефект не воспроизводился: поддельная page навигацию не рвёт.

Добавлено:
  - `_content_during_navigation()` — content(), возвращающий None вместо
    исключения, если текст ошибки указывает на гонку с навигацией. Различаем
    по тексту, а не по типу: сервис не импортирует playwright, page приходит
    готовым. Всё прочее (закрытая страница, упавший браузер) пробрасывается.
  - Цикл трактует None как «ещё не устоялось, опроси снова».
  - Финальная догидрация тоже защищена: один короткий добор, затем внятная
    ошибка вместо падения на гонке.
  - Поддельная page в тестах умеет поднимать исключение из content();
    три теста на гонку — прохождение, вечная навигация, посторонняя ошибка.

Фальсификация: без правки два новых теста падают именно с
`Execution context was destroyed`. Полный сьют сервиса — 123 passed.

Refs #3045
2026-08-21 19:57:51 +03:00
bot-backend
0f6523f852 fix(tradein/browser): дожидаться QRATOR PoW-челленджа Авито вместо тихой заглушки
Живой замер 2026-08-21 (#3045): фиксированной паузы BROWSER_WAIT_MS (6с) не
хватает на цепочку «PoW-расчёт в JS → таймер 3с → self-reload → гидрация» —
4 из 6 карточек с органической навигацией отдавали 7891-байтную challenge-
страницу вместо контента (не бан, "проверка безопасности"). caller считал её
валидным HTML — парсер либо падал, либо молча ничего не находил.

_fetch_once теперь опрашивает page.content() (шаг ~1с, бюджет
BROWSER_CHALLENGE_WAIT_MS=30000) пока маркеры челленджа (startPow / "проверка
безопасности") не исчезнут, затем догидрируется тем же BROWSER_WAIT_MS. По
истечении бюджета — ChallengeTimeoutError вместо заглушки. wait_for_url не
годится: страница перезагружает саму себя, URL не меняется.

Бан-страница ("проблема с IP") распознаётся отдельно и падает сразу
(BanPageDetectedError), без траты бюджета ожидания — это не то же самое, что
челлендж, и ждать там нечего.

Провайдер-агностично по форме: включается только по факту маркеров в HTML,
cian/yandex/generic их никогда не отдают.
2026-08-21 19:52:00 +03:00
bebeb0c8f0 feat(tradein/avito): путь вторички для якорного и citywide-обхода (#3033, follow-up) (#3042)
All checks were successful
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 3m41s
Deploy Trade-In / build-backend (push) Successful in 1m40s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 9s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / deploy (push) Successful in 2m21s
2026-08-21 15:33:22 +00:00
17a6dae3a4 feat(tradein/avito): house-поля с детальной страницы доезжают в houses (#3036) (#3040)
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 3m37s
Deploy Trade-In / build-backend (push) Successful in 2m55s
Deploy Trade-In / deploy (push) Successful in 3m20s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
2026-08-21 14:48:36 +00:00
28f5e8079e Merge pull request 'fix(tradein/avito): страница SERP отдаёт 60 карточек, а не 50 — лишние 17% запросов' (#3041) from fix/avito-offers-per-page into main
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 / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
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) Has been cancelled
2026-08-21 14:46:32 +00:00
6ffcd78d35 fix(tradein/avito): страница SERP отдаёт 60 карточек, а не 50
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 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 / backend-tests (pull_request) Successful in 4m27s
Замер живьём 2026-08-21 через tradein-browser (camoufox, JS исполняется):
59-60 уникальных `data-item-id` на странице выдачи. «Лишние» сверх 50 —
обычные объявления с платным продвижением (`vas-icon_type-promoted`), они
лежат в том же списке под `page-title/count`, а не отдельным рекламным
блоком, и собираются наравне с остальными.

Направление эффекта важно понимать правильно. Константа участвует ТОЛЬКО
в `ceil(total / PAGE)`, поэтому занижение размера страницы ЗАВЫШАЛО
расчётное число страниц, а не занижало:

  - запрашивали примерно на 17 % страниц больше, чем нужно; при доле банов
    37-83 % по avito-заданиям лишние запросы — основная цена ошибки;
  - `tail_loss` считался как `total - cap * 50` и завышал потерю;
  - флаг `complete` в пагинации листа чаще ложно показывал «неполно».

Тихой потери данных НЕ было: условия «страница вернула меньше PAGE
карточек, значит последняя» в коде нет, пагинация ограничена только
`max_pages`. Тест закрепляет и значение, и направление арифметики, чтобы
неверная трактовка не вернулась при следующем рефакторинге.

Найдено при разборе Авито сверкой живого браузера со скраппером; полный
разбор — в волте `research/Avito_Live_Browser_Recon_0821.md`.

Refs #3033
2026-08-21 17:38:55 +03:00
7382ae123d feat(tradein/avito): полный обход качает вторичку родным фильтром пути (#3033) (#3039)
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 3m42s
Deploy Trade-In / build-backend (push) Successful in 1m48s
Deploy Trade-In / deploy (push) Successful in 2m28s
Deploy Trade-In / deploy-status (push) Successful in 2s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
2026-08-21 14:22:48 +00:00
e31c537205 Merge pull request 'fix(db/ci): чистый старт БД не падает на 077 и не может уехать на пустой схеме' (#3011) from fix/2990-clean-start-initdb into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-browser (push) Successful in 44s
Deploy Trade-In / build-frontend (push) Successful in 2m25s
Deploy Trade-In / test (push) Successful in 3m36s
Deploy Trade-In / build-backend (push) Successful in 49s
Deploy Trade-In / deploy (push) Successful in 1m53s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
2026-08-21 12:55:48 +00:00
b588278923 fix(ops): дрилл восстановления ждал БД по сокету и умирал на старте настоящего сервера
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
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
`ops/restore-drill.sh` проверял готовность через `pg_isready -U postgres`, то
есть по unix-сокету. Образ postgres во время инициализации поднимает временный
сервер, который на сокете уже отвечает "accepting connections". Проба зеленела
посреди initdb, восстановление начиналось в этот временный сервер и погибало,
как только entrypoint гасил его ради настоящего:

    [12:26:22Z] Restoring dump into 'drill' (ON_ERROR_STOP=1 ...)
    FATAL:  terminating connection due to administrator command
    server closed the connection unexpectedly

Воспроизведено на проде 2026-08-21 на живом дампе `tradein-20260821-013001`:
падение через 6 секунд после старта. С `-h 127.0.0.1` тот же дамп проходит
восстановление и доходит до сборки индексов — временный сервер TCP не слушает,
поэтому по TCP проба зеленеет только на настоящем сервере.

Ровно та же проба и ровно по той же причине уже стоит в
`.forgejo/workflows/ci-tradein.yml:137-141`. Тот же дефект живёт и в
`.forgejo/workflows/deploy-tradein.yml:718-721` — там его чинят в PR #3011,
здесь не трогаю.

Refs #2203, #2989
2026-08-21 15:42:36 +03:00
bot-backend
c6b408e483 fix(ops): restore +x on new ops/*.sh (exec bit lost in prior commit, Windows core.filemode=false)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 13s
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
2026-08-21 15:38:31 +03:00
bot-backend
83be088263 feat(ops): missed-run detection for backups + Forgejo code/DB backup to S3
Deliverable 1: ops/backup.sh and tradein-mvp/deploy/backup-tradein-db.sh now
write a sentinel file on every verified-good run. A new ops/check-backup-
staleness.sh (separate cron entry, hourly) alerts via the existing Telegram
channel (same TELEGRAM_BOT_TOKEN/TELEGRAM_CHAT_ID idiom as ops/uptime-
healthcheck.sh, transition-tracked so it doesn't spam) if a sentinel goes
stale. Shared logic (notify/sentinel/state) factored into ops/lib-backup.sh
so it isn't triplicated; the two existing scripts' own hardening (integrity
checks, retention, etc.) is untouched.

Deliverable 2: ops/backup-forgejo.sh — Forgejo (git.gendsgn.ru) has no backup
today. Dumps the shared-postgres `forgejo` DB + tars the bare-repo tree, both
off-box to s3://gendsgn-backups/forgejo/ under a SEPARATE, narrower S3 key
(root-of-bucket writer key stays out of this). The key doesn't exist yet —
the script refuses to run and exits non-zero, loudly, until the four
FORGEJO_S3_* vars are filled in (see the example env file and PR description
for the exact bucket policy JSON to create it with).

Refs #2203, #2989
2026-08-21 15:37:19 +03:00
bot-backend
4cb8f32dcc Merge remote-tracking branch 'forgejo/main' into fix/2990-clean-start-initdb
All checks were successful
CI / changes (pull_request) Successful in 10s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Successful in 57s
CI Trade-In / frontend-checks (pull_request) Successful in 1m40s
CI / openapi-codegen-check (pull_request) Successful in 2m45s
CI Trade-In / backend-tests (pull_request) Successful in 5m15s
CI / backend-tests (pull_request) Successful in 18m13s
2026-08-21 15:35:32 +03:00
bot-backend
a3ccbbd045 fix(db/ci): 077 гардится по USER MAPPING, деплой ждёт готовности БД по TCP
Deep-review BLOCK на PR #3011: обе правки чинили заявленный симптом только
частично.

077: гард считал pending-строки по source='rosreestr' AND dedup_hash ~
md5-паттерн и пропускал backfill, только если таких строк 0. На чистой БД
они есть — 003_seed_deals.sql сеет синтетические сделки с тем же паттерном,
значит pending > 0 уже на пустом томе, и миграция всё равно падала на
"user mapping not found" (воспроизведено в CI run 8257). Первичный гард
теперь проверяет напрямую наличие USER MAPPING для gendesign_remote
(идиома из app/core/fdw.py:57-62), счётчик pending оставлен вторым —
экономит обращение к FDW, когда мигрировать уже нечего.

deploy-tradein.yml: цикл ожидания готовности postgres ходил по unix-сокету
(pg_isready без -h). На пустом томе временный init-сервер отвечает на
сокете, пока docker-entrypoint-initdb.d ещё прогоняет цепочку миграций —
проба зеленела посреди initdb. Добавлен -h 127.0.0.1 (тот же приём уже
есть в ci-tradein.yml:157) — TCP открывается только после полного
завершения initdb.d.

Отдельно ужесточён sentinel baseline-детекции: раньше «схема уже накачена»
проверялась одной таблицей listings (миграция 002, почти голова цепочки).
Если бы гонка готовности когда-нибудь вернулась, listings был бы уже
создан, а хвост цепочки — ещё нет, и baseline тихо пометил бы недостающие
миграции применёнными без прогона. Теперь проверяются оба конца — listings
(голова) и houses_geog_gist_idx, индекс из миграции 270 (хвост); при
несовпадении (ровно один конец на месте) деплой падает громко с explicit
ошибкой вместо угадывания.
2026-08-21 15:35:26 +03:00
4ae14055ec feat(tradein/v2): позиция квартиры на рынке — плашка на карточке рекомендованной цены (#2899) (#3024)
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / build-frontend (push) Successful in 2m33s
Deploy Trade-In / deploy (push) Successful in 1m46s
Deploy Trade-In / deploy-status (push) Successful in 2s
Deploy Trade-In / perimeter-smoke (push) Successful in 18s
2026-08-21 12:34:07 +00:00
f2945b7157 fix(ci): деплой падает громко, если :latest отстаёт от головы по компоненту (#2950) (#3023)
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy Trade-In / changes (push) Successful in 15s
Deploy / deploy-caddy (push) Has been skipped
Deploy Trade-In / build-browser (push) Successful in 57s
Deploy / build-backend (push) Successful in 1m2s
Deploy / build-frontend (push) Successful in 1m8s
Deploy / build-worker (push) Successful in 1m9s
Deploy / deploy (push) Successful in 1m58s
Deploy / deploy-status (push) Successful in 3s
Deploy Trade-In / build-frontend (push) Successful in 3m14s
Deploy / perimeter-smoke (push) Successful in 14s
Deploy Trade-In / test (push) Successful in 4m12s
Deploy Trade-In / build-backend (push) Successful in 33s
Deploy Trade-In / deploy (push) Successful in 2m16s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 9s
2026-08-21 12:19:07 +00:00
cdf493f345 chore(format): нормализация под ruff 0.15.20 — 161 файл, только формат (#2864) (#3022)
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy Trade-In / changes (push) Successful in 13s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy / build-backend (push) Successful in 2m23s
Deploy Trade-In / test (push) Successful in 3m56s
Deploy / build-worker (push) Successful in 4m16s
Deploy Trade-In / build-backend (push) Successful in 1m19s
Deploy / deploy (push) Successful in 1m49s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 12s
Deploy Trade-In / deploy (push) Successful in 2m25s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
2026-08-21 12:01:52 +00:00
1782dae0e2 chore(tooling): pre-commit, pyproject и uv.lock — один ruff 0.15.20 (#2864) (#3021)
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy Trade-In / changes (push) Successful in 11s
Deploy / deploy-caddy (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy / build-backend (push) Successful in 3m57s
Deploy Trade-In / test (push) Successful in 4m5s
Deploy / build-worker (push) Successful in 4m59s
Deploy Trade-In / build-backend (push) Successful in 1m53s
Deploy / deploy (push) Successful in 1m58s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 11s
Deploy Trade-In / deploy (push) Successful in 2m27s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 9s
2026-08-21 11:37:23 +00:00
95db3f44c8 fix(ops): бэкапы — +x на deploy-скриптах при деплое tradein, тихий s3 cp (#3005) (#3019)
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy Trade-In / changes (push) Successful in 13s
Deploy Trade-In / build-browser (push) Successful in 41s
Deploy / deploy (push) Successful in 1m38s
Deploy / deploy-status (push) Successful in 2s
Deploy / perimeter-smoke (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Successful in 2m38s
Deploy Trade-In / test (push) Successful in 3m47s
Deploy Trade-In / build-backend (push) Successful in 34s
Deploy Trade-In / deploy (push) Successful in 1m38s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 9s
2026-08-21 10:35:10 +00:00
ccf84b4adc fix(best-layouts): avg_area_m2 = NULL вместо 0 при пустом окне сделок (#2867) (#3018)
Some checks failed
Deploy / perimeter-smoke (push) Blocked by required conditions
Deploy / deploy-status (push) Blocked by required conditions
Deploy / changes (push) Successful in 11s
Deploy / build-backend (push) Successful in 2m48s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-worker (push) Successful in 5m23s
Deploy / build-frontend (push) Successful in 4m18s
Deploy / deploy (push) Has been cancelled
2026-08-21 10:27:02 +00:00
fa32db299d feat(db): GiST по (geom::geography) на houses — матчинг идёт по индексу (#2997) (#3020)
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 3m54s
Deploy Trade-In / build-backend (push) Successful in 43s
Deploy Trade-In / deploy (push) Successful in 2m35s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
2026-08-21 10:24:35 +00:00
bb74a0e5e1 fix(tradein): неудача резолва yandex_jk_slug помечает дом — очередь не упирается в одни и те же пять (#2924)
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 3m27s
Deploy Trade-In / build-backend (push) Successful in 1m9s
Deploy Trade-In / deploy (push) Successful in 2m28s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 8s
Закрывает #2924. Миграция 269 применится штатным деплоем.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:51:27 +00:00
bf119de7bb fix(db): миграция 269 — SET LOCAL lock_timeout внутри BEGIN/COMMIT (#2752)
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 Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 11s
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m24s
Вне транзакции SET LOCAL молча ничего не делает — гейт это ловит, и он
поймал меня второй раз подряд, теперь локально, до CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 14:46:17 +05:00
8de9831e25 fix(db): миграция 269 — SET LOCAL lock_timeout перед ALTER TABLE houses (#2752)
Some checks failed
CI Trade-In / 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 / changes (pull_request) Failing after 10s
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 4m27s
Гейт CI «блокирующий DDL без lock_timeout» покраснел по делу: ALTER TABLE
берёт ACCESS EXCLUSIVE на houses и при живом писателе ждал бы бесконечно,
копя очередь. 5с — миграция падает честно и деплой перезапускается.
Предыдущий пустой коммит «перезапуск — флуктуация раннера» был ошибкой
диагноза: я не дочитал лог changes до ::error и приписал падение
checkout'у. Единственный источник истины — строка ::error в логе самого
changes; «Job 'changes' failed» у зависимых джобов и «'runs-on' key not
defined» в act_runner v6.3.1 — шум.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 14:45:29 +05:00
ebb26b9382 ci: перезапуск — CI/changes упал на checkout (git clone: some refs were not updated) в 09:38 UTC, одновременно с деплоем tradein
Some checks failed
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Failing after 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 / openapi-codegen-check (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m30s
Единственный реально упавший джоб — changes (задача 22320): лог обрывается на
'Setting up auth' checkout'а после 'Non-terminating error while running git clone:
some refs were not updated'. Три зависимых джоба помечены «Job 'changes' failed»
как пропущенные, не упавшие. Строка «'runs-on' key not defined in CI/changes»
в логах act_runner v6.3.1 стоит у десятков успешных задач за день — шум при
разборе needs, не причина. YAML головы PR байт-идентичен origin/main.
Код не меняется.
2026-08-21 14:43:58 +05:00
507c8e0611 fix(tradein): неудача резолва yandex_jk_slug помечает дом — очередь не упирается в одни и те же пять (#2924)
Some checks failed
CI Trade-In / changes (pull_request) Successful in 8s
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 / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m25s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
(полное описание — в PR)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 14:38:22 +05:00
95d348f3c3 fix(tradein): апсерт не переписывает неизменившуюся строку — гейт IS DISTINCT FROM + МСК-день (#2992)
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 3m26s
Deploy Trade-In / build-backend (push) Successful in 1m37s
Deploy Trade-In / deploy (push) Successful in 1m48s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 9s
Часть #2992. Замер «после» — 22.08 ~09:00 UTC.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 09:08:05 +00:00
6cdf56820d fix(tradein): апсерт не переписывает неизменившуюся строку — гейт IS DISTINCT FROM + МСК-день (#2992)
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 4m15s
(полное описание — в PR)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:57:09 +05:00
ce466c6d03 fix(data): партиции rosreestr_deals на Q2–Q4 2026 + загрузчик доходит до файла (#2998)
All checks were successful
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy / changes (push) Successful in 8s
Deploy / build-backend (push) Successful in 38s
Deploy / build-worker (push) Successful in 39s
Deploy / deploy (push) Successful in 1m15s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 9s
Закрывает корень #2998 (партиции rosreestr_deals). Данные Q2 уже на проде.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 08:56:25 +00:00
6b4800c81d feat(tradein/estimator): штраф за неизвестный год/тип дома — флагом, default OFF (#2936)
All checks were successful
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Successful in 3m25s
Deploy Trade-In / build-backend (push) Successful in 1m10s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / changes (push) Successful in 9s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / deploy (push) Successful in 1m59s
Deploy Trade-In / perimeter-smoke (push) Successful in 9s
Часть #2936. Флаг default OFF — поведение прода не меняется.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 08:36:25 +00:00
9b54d64bd8 test(#2998): сторож партиций — герметично в схеме-песочнице, а не по живому проду
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
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 / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m59s
CI / backend-tests (pull_request) Successful in 17m6s
(см. описание в PR #3014)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:33:57 +05:00
865e64b783 feat(tradein/estimator): штраф за неизвестный год/тип дома — флагом, default OFF (#2936)
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 4m14s
В SQL-формуле relevance_score кандидат без year_built получает штраф 0 —
столько же, сколько точное попадание в год, и лучше, чем кандидат с
известным годом, отличающимся на 24 (2.0). То же с house_type. Отсутствие
данных выигрывает у знания и возвышает источник с худшей полнотой.

Флаг estimate_unknown_attr_penalty_enabled (default OFF): кандидат с NULL
получает МЕДИАННЫЙ по пулу штраф того же признака среди тех, у кого он
известен — не наказание и не награда; считается по тем же термам, что
SQL (abs(Δyear)/12.0, 1.5 за несовпадение типа). Лежит в Python-слое
после SQL, рядом с kitchen/ceiling (#2012), по тому же контракту:
включать — только по бэктесту.

Без чего флаг был бы мёртв (и был в первой редакции): _ANALOG_SELECT_COLS
не выбирал year_built/house_type — SQL считал по ним CASE, но в словарь
кандидата колонки не попадали, Python-слой видел None у ВСЕХ и не штрафовал
никого по построению. Probe-лог в прод-оверлее: pool=30 null_year=30.
Добавлены в _ANALOG_SELECT_COLS, во внешние SELECT тиров H/W и во
внутренний base Tier W (он строится явным списком). Контроль: флаг OFF с
колонками и без — метрики бэктеста идентичны до сотых. На этот инвариант
стоит тест по исходнику запросов.

Живой A/B (бэктест в прод-контейнере, оверлей /tmp/ab, 300 сделок ЕКБ
Q2 2026, одна и та же выборка в обоих прогонах — проверено по deal_id):
  состав топ-50: сменилось 96 слотов из 5 915 (1.6 %), 27 сделок из 300
  источники: avito 55.7→55.3 %, cian 24.5→24.8 %, yandex 12.7→12.6 %
  цена: MAPE 16.70→16.70, bias −4.52→−4.52, coverage 84.46→84.46 —
  идентично до сотых по всем срезам (сегменты, комнатность).
Нижний Тагил (300 сделок, пулы 21/p90 40): 0 из 5 666 слотов сменилось.

Почему эффект в разы меньше замера задачи (×0.20 avito): тот замер шёл
по SQL тира H без стратификации. В боевом пути 54.9 % слотов топ-50 —
гарантированная квота MIN_ANALOGS_PER_SOURCE=5, раздаётся ДО сортировки
остатка; и у avito в топ-50 NULL-год лишь у 25.7 % (задача мерила 56 %
по всем активным объявлениям). Обе величины измерены по фикстурам A/B.

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

pytest tradein-mvp/backend: test_2936 7 passed; гейт фикстуры и
roundtrip 8 passed; -k "estimat or analog" 735 passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 13:27:18 +05:00
ebb7b7d32d ci: перезапуск backend-tests — джоб завис с 07:49 без обновлений (#2998)
Some checks failed
CI Trade-In / changes (pull_request) Successful in 8s
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 Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m6s
CI / backend-tests (pull_request) Failing after 17m16s
Остальные семь чеков прошли за минуты, backend-tests стоял 1ч30м без
единого обновления статуса. Actions API на этом Forgejo недоступен
(/actions/tasks обрывает соединение), перезапуск — только новым
коммитом. Код не меняется.
2026-08-21 13:04:35 +05:00
e6e5bd962c fix(data): партиции rosreestr_deals на Q2–Q4 2026 + загрузчик доходит до файла (#2998)
Some checks failed
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / 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 / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m55s
CI / backend-tests (pull_request) Failing after 17m20s
Задача формулировала «импорт каждый день рапортует done с total_seen=0».
Проверка на проде показала другое: импорт исправен — ежедневно вычитывает
все 96 974 строки FDW-источника и честно их пропускает (rows_fetched =
rows_skipped = 96974), а `total_seen` — поле админ-витрины, не счётчик
импорта. Источник gendesign.rosreestr_deals стоял на Q1 2026 (загружен
30.04), хотя Q2 2026 опубликован Росреестром 10.07 и poll заметил его
14.08 (available=1). Оба сторожа — poll и deals_freshness_monitor —
сработали и семь событий ушли в GlitchTip, где 0 правил / 0 адресатов /
0 отправок.

Корень, которого в задаче не было: rosreestr_deals партиционирована по
period_start_date, партиции созданы списком в 01_schema «2024 Q3 — 2026
Q1», и ничто новые не создаёт. Загрузка Q2 21.08 упала:

  ERROR: no partition of relation "rosreestr_deals" found for row
  DETAIL: (period_start_date) = (2026-04-01)

То есть даже оператор, запустив загрузчик по подсказке poll, получил бы
отказ. Это и объясняет, почему poll сделан «только сообщить».

Что сделано:
• миграция 193 — партиции Q2, Q3, Q4 2026 с запасом, идемпотентно, с
  lock_timeout; индексы наследуются от родителя (проверено: 4 на 2026q2);
• JOBS загрузчика — 2026Q2–Q4 (квартал без CSV честно SKIP);
• ловушка set -e в загрузчике: resolve_csv сигналит «файла нет» кодом 1,
  и первый же квартал без CSV молча ронял ВЕСЬ прогон до строки SKIP — на
  проде с одним Q2-файлом скрипт завершался rc=0, не напечатав ни строки.
  `|| true` на вызове; после правки боевой прогон на VPS: 12 кварталов,
  2026Q2 «уже загружен (741874 строк)», остальные SKIP, rc=0;
• тест-сторож горизонта: партиция обязана существовать на последний
  публикуемый квартал (+20 дней лага после конца квартала; Q2 2026 вышел
  10.07) и на следующий — чтобы предупреждение приходило за квартал до
  отказа, а не в день публикации. Читает pg_inherits живого Postgres.
  Красная сторона воспроизводима на проде, где миграция уже применена:
  DETACH партиции в откатываемой транзакции → головная краснеет по
  значению («нет партиции на квартал 2026-04-01»), откат возвращает
  партицию (проверено: 12 партиций после теста). Без БД — skip с
  причиной, в allowlist; календарный тест идёт везде.

Сам Q2 загружен на прод по штатному пути: 741 874 строки в
rosreestr_deals (ЕКБ-фильтр 13 654), import-rosreestr.sh → tradein.deals
+11 649 сделок, max(deal_date) 2026-01-01 → 2026-04-01.
deals_freshness_monitor на следующем тике: alert 0, latest_quarter 2.

pytest backend/tests/sql — 55 passed (через туннель к проду).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 12:48:28 +05:00
a003fdd37c feat(tradein): ручка «дома, уехавшие от собственных объявлений» (#2996)
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 3m27s
Deploy Trade-In / build-backend (push) Successful in 1m4s
Deploy Trade-In / deploy (push) Successful in 2m10s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
Часть Находки 3 из #2996.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 05:54:33 +00:00
63bc576c40 Merge pull request 'fix(ops): проверка трейлера дампа падала на grep — ведущие -- принимались за опции (#2203)' (#3013) from fix/backup-trailer-grep-dashdash into main
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy Trade-In / changes (push) Successful in 11s
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 41s
Deploy / build-worker (push) Successful in 42s
Deploy Trade-In / build-browser (push) Successful in 39s
Deploy / deploy (push) Successful in 1m56s
Deploy / deploy-status (push) Successful in 1s
Deploy / perimeter-smoke (push) Successful in 9s
Deploy Trade-In / build-frontend (push) Successful in 2m51s
Deploy Trade-In / test (push) Successful in 3m45s
Deploy Trade-In / build-backend (push) Successful in 33s
Deploy Trade-In / deploy (push) Successful in 6m31s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 9s
2026-08-20 20:57:31 +00:00
1da1ba084d Merge pull request 'chore(db): Postgres tradein уходит со стоковых настроек — конфиг в compose' (#3012) from chore/2991-postgres-config into main
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 / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / build-browser (push) Successful in 40s
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Successful in 2m31s
Deploy Trade-In / test (push) Has been cancelled
2026-08-20 20:53:54 +00:00
bot-backend
70f3c0a88a fix(ops): проверка трейлера дампа падала на grep — ведущие -- принимались за опции (#2203)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
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 / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m59s
CI / backend-tests (pull_request) Successful in 17m7s
verify_dump_integrity() в ops/backup.sh и tradein-mvp/deploy/backup-tradein-db.sh
делала `grep -qF "$trailer"`, где $trailer = "-- PostgreSQL database dump
complete". Ведущие -- в аргументе grep трактует как конец опций/флаг, без
разделителя команда падает: `grep: unrecognized option '-- PostgreSQL...'`.

Проверка из-за этого ВСЕГДА возвращала "трейлера нет" — не потому что дамп
оборван, а потому что сама проверка не могла выполниться. Вызывающий код
удалял только что созданный валидный дамп и завершался с ошибкой; ретеншен
не успевал отработать (ранний exit) — свежие бэкапы не создавались никогда,
старые копии оставались молча.

Воспроизведено вручную на проде: bash
/opt/gendesign/tradein-mvp/deploy/backup-tradein-db.sh удалил свежий дамп с
сообщением "дамп оборван?".

Фикс: `grep -qF -- "$trailer"` — `--` явно завершает список опций grep.

Регрессионный тест (backend/tests/ops/test_2203_backup_trailer_grep_dashdash.py)
исполняет РЕАЛЬНУЮ verify_dump_integrity() из обоих скриптов на настоящем
gzip-потоке через gunzip|tail|grep — не читает исходник текстом. Проверено
локально: падает на добаговой версии с тем же "unrecognized option", зелен
на исправленной.
2026-08-20 23:38:45 +03:00
bot-backend
d4692a6ff1 chore(db): Postgres tradein уходит со стоковых настроек — конфиг в compose
All checks were successful
CI Trade-In / changes (pull_request) Successful in 13s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 16s
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
Блокер переезда (#2991). Контейнер шёл на ПОЛНОСТЬЮ стоковой конфигурации:
ни command:, ни смонтированного postgresql.conf. Замер прода 2026-08-20 —
169 млрд blks_read за 91,75 сут ≈ 175 МБ/с мимо кеша при shared_buffers 128 МБ.

Поправка к диагнозу из issue: 288 чекпойнтов в сутки — это ровно 24ч/288 =
5 минут, то есть дефолтный checkpoint_timeout, а НЕ max_wal_size. При WAL
7 ГБ/сут лимит в 1 ГБ дал бы 7 чекпойнтов, а не 288. Поэтому поднят именно
timeout до 30min — правка одного max_wal_size не изменила бы ничего.
Реже чекпойнты → реже full-page images, а они сейчас 55-86% объёма WAL.

Значения подобраны под ТЕКУЩИЙ сервер и не выходят за mem_limit=3g:
shared_buffers 768MB (6× от стоковых), work_mem 16MB, maintenance_work_mem
256MB (autovacuum по listings идёт 193 раза в сутки).

max_wal_size 4GB, а не 8GB: на диске 38 ГБ свободно, а pg_wal растёт до этого
значения. При timeout=30min между чекпойнтами копится ~0,15 ГБ — запас большой.

wal_compression=zstd — самая дешёвая победа при такой доле FPI.
random_page_cost 1.1 вместо стоковых 4: диск NVMe, а 4 — настройка под HDD,
из-за неё планировщик недооценивал индексные сканы.

pg_stat_statements подключён через shared_preload_libraries — расширение было
в образе, но не активировано, и прошлый разбор пришлось вести по косвенным
признакам.

shm_size 512m: дефолтные 64 МБ /dev/shm ронял параллельные воркеры на тяжёлых
PostGIS-сортировках.

mem_limit оставлен 3g. В комментарии зафиксировано, что при переезде его надо
поднимать ДО правки shared_buffers: лимит контейнера срабатывает раньше
postgresql.conf, и shared_buffers=16GB при mem_limit=3g даёт OOM на старте.

Refs #2991, #2989
2026-08-20 23:34:40 +03:00
bot-backend
efc965a257 fix(db/ci): чистый старт БД больше не падает на 077 и не может уехать на пустой схеме
Some checks failed
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Failing after 55s
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 1m3s
CI Trade-In / frontend-checks (pull_request) Successful in 1m47s
CI / openapi-codegen-check (pull_request) Successful in 2m26s
CI / backend-tests (pull_request) Successful in 17m51s
Блокер переезда (#2990). Чистый старт на пустом томе падал: 077 читает foreign
table gendesign_rosreestr_deals, а USER MAPPING создаёт бэкенд при старте
(app/core/fdw.py), то есть ПОСЛЕ docker-entrypoint-initdb.d. Контейнер не
поднимался вообще. Путь «пустой том» на реальном железе не исполнялся ни разу,
а CI этот файл явно пропускал — гейт, который должен был поймать, был ослаблен.

Проверено по всем 14 миграциям, упоминающим FDW-таблицы: читает ровно одна —
077. Остальные только CREATE/DROP FOREIGN TABLE и COMMENT, им ни USER MAPPING,
ни связь с чужой БД не нужны.

077 не удалена, а сделана самозащитной: гард считает строки в md5-форме и
выходит раньше обращения к FDW, если мигрировать нечего. На чистой БД таких
строк нет по определению. Удаление файла было бы неверным — прод помнит
миграции по bare-filename в _schema_migrations, и
test_applied_migration_is_not_renamed_or_deleted падает на удалении.

Исключение в ci-tradein.yml снято: теперь цепочка применяется целиком, то есть
CI сам стал репетицией чистого старта.

Отдельно закрыт тихий отказ в deploy-tradein.yml. Ветка baseline срабатывала по
одному лишь отсутствию _schema_migrations, а это состояние неоднозначно: так
выглядит и наполненный прод до внедрения tracking, и пустая БД нового сервера.
Во втором случае baseline пометил бы все миграции применёнными, ни одной не
прогнав, и деплой уехал бы зелёным на пустой схеме. Добавлен sentinel по
listings: пусто → baseline пропускается, цепочка применяется с нуля.

Refs #2990, #2989
2026-08-20 23:31:46 +03:00
172a36a202 feat(tradein): secondary_only — параметр расписания, выброшенное считается (#1781)
All checks were successful
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / changes (push) Successful in 14s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Successful in 3m58s
Deploy Trade-In / build-backend (push) Successful in 3m23s
Deploy Trade-In / deploy (push) Successful in 8m12s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
Готовит решение по #1781 / #2994. Дефолт не меняется.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 20:30:00 +00:00
fab0bc84c1 feat(tradein): ручка «дома, уехавшие от собственных объявлений» (#2996)
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 4m49s
Находка 3 задачи («23 дома лежат вне области 66, гарда на приёме нет»)
подтвердилась, и к ней добавились масштаб и причина.

Масштаб: к этим домам привязано 591 объявление. Адреса екатеринбургские
(«Ул. 8 Марта», «Крауля», «Амундсена»), координаты — Варшава, Белград,
Таллин, Владивосток, Ижевск.

Причина: 22 из 23 несут в raw_payload след разового бэкфилла
`full_backfill_2026-05-27`, который звал Yandex-геокодер напрямую, без
резолва города. Живой путь при этом ЧИСТ — 0 записей вне региона в
geocode_cache из 10 448 и 2 из 88 544 у listings; скрипта в репозитории
нет. Это исторический осадок, а не текущая утечка, и гард нужен не
столько живому пути, сколько следующему разовому скрипту.

17 из 22 помечены геокодером `precision: "exact"`. Точность отвечает на
«нашёлся ли номер дома», а не «в том ли городе», и критерием приёмки быть
не может — поэтому проверяется ПРИНАДЛЕЖНОСТЬ.

Инвариант нарочно не географический: «дом рядом со своими объявлениями»,
а не «дом внутри рамки области». Рамка сломалась бы при выходе в Москву —
ровно то, ради чего заведена #2996. Он же строго сильнее: ловит 25 домов
против 23 у рамки, и оба лишних проверены («Ул. Белинского/Фурманова» за
207 км, 34 объявления).

Порог 100 км не подобран на глаз. Замер по 9 052 домам: ближе 1 км —
8 914 (98.5 %), 25-100 км — 9 настоящих пригородов (Сарапулка, Кедровка,
Чусовское Озеро, Ревда, Первоуральск; самый дальний 46.7 км), дальше
100 км — 25 (ближайший 119.2, максимум 5 079). Между 46.7 и 119.2 км нет
НИ ОДНОГО дома: порог лежит в середине пустого промежутка.

Первая редакция клала счётчик полем в /scraper/data-quality. Замер это
остановил: запрос стоит ~445 мс на тёплом кэше, а обе существующие
выборки той ручки вместе — 27 мс, при опросе фронтом каждые 120 с. То
есть 17-кратное удорожание ради числа, которое меняется раз в месяцы.
Проверка вынесена в отдельную ручку по требованию, и на возврат в горячий
путь поставлен контроль-тест.

Ручка отдаёт не только счётчик, но и масштаб (список домов + сколько
объявлений привязано) — по одному числу «25» решение об очистке не
принять.

Двусторонне: против origin/main три теста красные, краснота везде по
значению — ни одного ImportError/AttributeError. Контроль
test_check_stays_out_of_the_polled_endpoint зелёный с обеих сторон.

pytest tradein-mvp/backend — 4642 passed, 23 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:28:50 +05:00
1663f22795 Merge pull request 'fix(ops): выгрузка бэкапа в S3 не проходила TLS — у aws-cli свой CA-бандл (#2203)' (#3009) from fix/backup-s3-ca-bundle into main
All checks were successful
Deploy / changes (push) Successful in 9s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-browser (push) Successful in 40s
Deploy / deploy (push) Successful in 1m19s
Deploy / deploy-status (push) Successful in 2s
Deploy / perimeter-smoke (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Successful in 2m59s
Deploy Trade-In / test (push) Successful in 3m38s
Deploy Trade-In / build-backend (push) Successful in 33s
Deploy Trade-In / deploy (push) Successful in 2m16s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
2026-08-20 20:14:16 +00:00
bot-backend
74d503fe36 fix(ops): выгрузка бэкапа в S3 не проходила TLS — у aws-cli свой CA-бандл (#2203)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI / changes (pull_request) Successful in 13s
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 / openapi-codegen-check (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
aws-cli v2 (образ amazon/aws-cli:latest) использует собственный набор
корневых сертификатов из botocore, а не системное хранилище контейнера.
В нём нет корня, которым подписан сертификат Selectel S3
(s3.ru-1.storage.selcloud.ru), поэтому docker run падал на
SSL validation failed / CERTIFICATE_VERIFY_FAILED: self-signed
certificate in certificate chain — и ночной S3-upload не проходил
каждый раз, хотя ключи и bucket были верны.

Проверено на проде: без AWS_CA_BUNDLE — SSL validation failed; с
AWS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt (системное хранилище
контейнера, содержит нужный GlobalSign-корень) — upload 11 МБ проходит,
код возврата 0, объект подтверждён чтением вторым ключом.

Правка — одна переменная окружения в docker run в обоих скриптах:
- ops/backup.sh (main DB backup)
- tradein-mvp/deploy/backup-tradein-db.sh (tradein DB backup, #3004)
2026-08-20 23:09:28 +03:00
4a728b3a68 fix(ptica): «геологический риск» больше не выводится из шума (#2934)
Some checks failed
Deploy / deploy (push) Blocked by required conditions
Deploy / perimeter-smoke (push) Blocked by required conditions
Deploy / deploy-status (push) Blocked by required conditions
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Has been skipped
Deploy / deploy-caddy (push) Has been skipped
Deploy / build-backend (push) Successful in 3m39s
Deploy / build-worker (push) Has been cancelled
Пункт 2 порядка починки #2934.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 20:09:19 +00:00
b9cfdaa040 feat(tradein): secondary_only — параметр расписания, выброшенное считается (#1781)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
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 / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m27s
`run_cian_full_load` передавал `secondary_only=True` жёстко, поэтому
включить новостройки в полный обход можно было только деплоем. Теперь это
параметр с ТЕМ ЖЕ дефолтом `True` — поведение прода не меняется ни на
строку, но решение становится правкой одной ячейки
`scrape_schedules.default_params`, а не выкаткой кода. Откат — тем же
движением.

Почему это важно именно здесь. Новостройки НЕ пропускаются при запросе:
они скачиваются, разбираются и выбрасываются последним шагом
(`cian/serp.py`), потому что SERP-параметр `object_type=1` у Cian
ненадёжен (~5 % выдачи) и фильтруют по authoritative `listing_segment`
после парсинга. Проба бакета берёт `totalOffers` из Redux-состояния SERP
и считает `pages_needed = ceil(totalOffers / offers_per_page)`, а
`totalOffers` включает ОБЕ категории — то есть страницы с новостройками
уже скачаны, лимит страниц и антибан-бюджет за них уже заплачены.
Включение стоит ноль дополнительных запросов.

Заодно `dropped_novostroyki` сохраняется в counters прогона. Счётчик
логировался (`dropped_nb=`), но не персистился, и ответить «сколько
инвентаря выбрасывает полный обход» задним числом было нечем: логи за
17.08 уже ротировались — `docker logs --since 120h` не находит ни строки
«cian:» ни в одном контейнере. Тот же довод, по которому рядом заведён
`partial_buckets`. Копится в атрибуте инстанса, а не аргументом
`on_bucket`: у колбэка есть внешние реализации, менять его сигнатуру
ради счётчика нельзя. Сброс на каждый прогон — инстанс переиспользуется.

Замер, ради которого это делается (прод 21.08): месячный охват свипа
cian/novostroyki — 11.7 % против 100 % у cian/vtorichka и
avito/novostroyki; 11 993 активные строки, медианный возраст 81 сутки,
10 585 старше 30 суток. Подробности и оговорки — в #1781 и #2994.

Двусторонне: против origin/main пять тестов красные, и краснота везде по
значению, а не по отсутствию символа — ни одного KeyError. Сообщения
перечисляют фактическое состояние («параметра нет в сигнатуре; параметры:
[...]», «поля нет в запросе; поля: [...]»).

Контроли зелёные с обеих сторон: дефолт остаётся True (иначе правка тихо
включила бы сбор новостроек на проде — это отдельное решение с замером);
фильтр при `secondary_only=True` остаётся на месте и по-прежнему зависит
от флага.

pytest tradein-mvp/backend — 4644 passed, 23 skipped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 01:08:11 +05:00
9b72e50d18 Merge pull request 'fix(tradein/search): снять STORED-generated с listings.tsv, не пересчитывать на каждом UPDATE' (#3007) from fix/listings-tsv-drop-generated into main
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 3m30s
Deploy Trade-In / build-backend (push) Successful in 36s
Deploy Trade-In / deploy (push) Successful in 1m43s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 9s
2026-08-20 19:59:15 +00:00