sshkey.txt содержит открытым текстом GHCR-PAT, пароли БД, ADMIN_TOKEN,
пароль Obsidian. git rm --cached снимает файл с отслеживания (локальная
копия остаётся) + добавлен в .gitignore.
Частично закрывает #391. Остаётся (требует ручных действий):
- ротация всех засвеченных секретов;
- чистка истории git (filter-repo) — секреты остаются в старых коммитах.
cron запускается с пустым окружением — ADMIN_TOKEN отсутствует, и все
вызовы admin/scrape и geocode-missing получают 401 (#2 admin-auth).
Парсинг новых объявлений и сделок не работал с ~21.05.
cron-scrape.sh теперь сам читает tradein-mvp/.env.runtime
(resolve по BASH_SOURCE → ../.env.runtime, source с set -a).
Closes#390
Второй баг из #385 (#388 закрыл только алиас distance_m). В
_fetch_analogs ORDER BY использует :target_year и :target_house_type.
Когда год постройки / тип дома не заданы, они приходят NULL — psycopg
шлёт их без типа, PostgreSQL не может определить тип параметра →
psycopg.errors.AmbiguousParameter ($8) → 500 на каждый estimate.
Фикс: явный CAST(:target_year AS integer) и CAST(:target_house_type
AS text). Проверено на прод-БД через psycopg3: без CAST падает с
AmbiguousParameter, с CAST — успешно (7 строк).
Регрессия #385 (#6 house-match): ORDER BY использует SELECT-алиас
distance_m внутри арифметического выражения. PostgreSQL подставляет
алиасы в ORDER BY только голым термом — внутри выражения имя ищется
как колонка listings.distance_m, которой нет → "column does not
exist" → 500 на каждый геокодящийся адрес. Инлайнили ST_Distance()
вместо алиаса.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
#6: аналоги сортируются по relevance — расстояние + близость года
постройки + совпадение типа дома (соразмерные квартиры из встречи Птицы).
#7: медиана корректируется коэффициентом по состоянию ремонта
(требует ремонта 0.92 ... евроремонт 1.08).
deploy/import-rosreestr.sh — ETL ЕКБ-квартир из gendesign rosreestr_deals
(6.8М строк) в tradein deals. rooms выводится из площади. Дедуп по
md5(rosreestr.id). 54k реальных сделок вместо 60 синтетических.
КРИТИЧНЫЙ БАГ: 003_seed_listings.sql содержал TRUNCATE listings,deals,
а deploy-tradein.yml гоняет весь data/sql/*.sql на каждом деплое →
каждый деплой стирал реальные данные парсеров и подсовывал 120 синтетики.
Удалён 003_seed_listings.sql. Вместо него 003_seed_deals.sql —
идемпотентный (INSERT ... WHERE NOT EXISTS), сидит ТОЛЬКО сделки и
ТОЛЬКО когда таблица deals пуста. listings больше не трогается seed-ом.
Cian/N1 ставили anchor+jitter координаты — фейковые, вокруг центрального
якоря. Для адресов вдали от центра ST_DWithin не находил аналоги → 0.
Теперь Cian/N1 оставляют lat/lon=None, geocode-missing проставляет реальные
координаты по адресу (geom пересчитывается триггером). cron вызывает
geocode-missing после скрейпа. Адрес чистится от суффикса · р-н.
Добавлен 4-й источник N1.ru (ekaterinburg.n1.ru) — региональный портал,
хорошо представлен на Урале. DOM-парсинг карточек data-test=offers-list-item
через curl_cffi. Multi-room сегментация 1/2/3/4к.
cron-scrape.sh переписан под прод: запрос к backend через docker exec
(без публичного admin/scrape), все 4 источника (avito/cian/yandex/n1).
502 fix: Next.js standalone server.js биндился на HOSTNAME=<container-id>
вместо 0.0.0.0 → Caddy не мог достучаться до tradein-frontend:3000.
Теперь HOSTNAME=0.0.0.0 и в Dockerfile (runner ENV), и в compose.
API routing fix: фронт собирался без NEXT_PUBLIC_API_BASE_URL, fetch шёл
на same-origin /api/* (главный gendesign-backend) вместо /trade-in/api/*.
Добавлен build-arg в Dockerfile + workflow.
handle_path /trade-in/api/* стрипал /trade-in/api целиком — FastAPI
получал /v1/trade-in/estimate и возвращал 404. FastAPI router на
/api/v1/trade-in/*, нужен strip только префикса basePath /trade-in.
Без префикса /trade-in клиентский fetch шёл на gendsgn.ru/api/v1/...
который Caddy маршрутизировал в main gendesign backend (не tradein).
Теперь NEXT_PUBLIC_API_BASE_URL fallback на NEXT_PUBLIC_BASE_PATH —
build с BASE_PATH=/trade-in корректно префиксит запросы.
handle /trade-in/* НЕ матчил /trade-in (без слеша) — request попадал в
универсальный handle и проксировался на основной gendesign frontend,
который для unknown path отдавал 200 OK с пустым body.
Заменил на named matcher `@tradein path /trade-in /trade-in/*` — ловит
оба случая. Также убрал redirect-handle /trade-in → /trade-in/ т.к.
trailingSlash=false у Next.js и сам редиректит обратно.
Dockerfile делает COPY --from=builder /app/public ./public — без папки билд падает 'public: not found' (gitignore прячет tradein.html, других файлов нет → git не сохранил пустую папку).
Allow self-signup на errors.gendsgn.ru чтобы добавить нового user'а
(evileyecreation@gmail.com). После регистрации — revert PR вернёт
ENABLE_USER_REGISTRATION=false.
Workflow:
1. Этот PR merge → deploy ~5 мин → recreate glitchtip-web с новым env
2. User регистрируется на https://errors.gendsgn.ru/sign-up
3. Revert PR (otdельный) → deploy → registration снова закрыта
Issue: invite через UI требует existing user (GlitchTip отличается от Sentry).
ENABLE_USER_REGISTRATION env hardcoded в compose, не в .env — поэтому
через git/deploy.
User: 'плюс проверь какие данн чтобы было то что хочет макс хотя бы похожи
что не старше года иначе жк не конкурент'.
Анализ /analyze для 66:41:0204016:10:
Total competitors: 20
Строящиеся: 6
Сданные: 14 (12 из них ready_dt >1y → стейл)
Maxim spec: ЖК старше года не конкурент. Активные = строящиеся
ИЛИ ready_dt в будущем ИЛИ сданы ≤1 года назад.
MarketTab фильтрует data.competitors в relevantCompetitors:
- site_status === 'Строящиеся' → активен
- ready_dt в будущем → активен
- ready_dt в пределах последнего года → активен
- иначе скрыт
activeCount + CompetitorTable теперь используют отфильтрованный список.
Section label показывает 'N из M — фильтр' для прозрачности.
После deploy для тестового cad: activeCount упадёт с 20 → 9
(6 строящихся + 1 future ≤1y + 2 past ≤1y).