gendesign/tradein-mvp
bot-backend a9b87aa311
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 1m36s
chore(tradein/scraper-kit): удалить 5 orphaned legacy-модулей (#2277 финальный шаг, частичный)
Удалены (0 runtime importers в app/, scripts/, packages/; проверено grep'ом,
включая intra-legacy импорты в оставшихся 21 legacy-модуле scrapers/):
- backend/app/services/scrapers/avito_imv.py
- backend/app/services/scrapers/domclick_detail.py
- backend/app/services/scrapers/ekb_geoportal_client.py
- backend/app/services/scrapers/yandex_detail.py
- backend/app/services/scrapers/yandex_valuation.py

Тесты — удалены (pure legacy-vs-kit parity, kit-only coverage уже есть в другом
месте):
- tests/scrapers/test_ekb_geoportal_client_kit_parity.py
- tests/scrapers/test_domclick_detail_kit_parity.py

Тесты — конвертированы в kit-only (импорт легаси заменён на scraper_kit.*,
покрытие сохранено без потерь):
- tests/scrapers/test_avito_imv_kit_parity.py (parity-тесты убраны, config-footgun
  regression на kit-стороне оставлен)
- tests/scrapers/test_avito_imv_browser_transport.py
- tests/scrapers/test_yandex_valuation_kit_migration.py (parity убрана, footgun-
  регрессия mandatory config/delay_provider оставлена)
- tests/scrapers/test_domclick_detail.py (+ exceptions переведены на
  scraper_kit.domclick_exceptions, т.к. kit parse_detail_html поднимает их)
- tests/test_avito_imv_parse.py
- tests/test_ekb_geoportal_ingest.py (client-часть; ingest/geocoder-часть не
  трогали — не зависят от удалённого модуля)
- tests/test_yandex_detail.py
- tests/test_yandex_detail_structural.py
- tests/test_yandex_valuation.py (YandexValuationScraper() → config=_KIT_CONFIG,
  kit конструктор требует обязательный config)
- tests/test_yandex_valuation_save.py
- tests/test_extval_house_id_write_path.py (только yandex_valuation-часть;
  cian_valuation остался нетронутым — живой легаси-модуль)
- tests/test_yandex_history_area_filter.py

Тесты — частично отредактированы (убраны только части про удалённые модули,
живые легаси-модули/их parity не тронуты):
- tests/scrapers/test_admin_domclick_ingest_kit_parity.py (base.py/ScrapedLot
  parity остался)
- tests/scrapers/test_admin_yandex_kit_parity.py (yandex_realty parity остался)
- tests/scrapers/test_admin_avito_kit_parity.py (avito.py/avito_houses.py parity
  остался; _parse_price parity убран)
- tests/scrapers/test_avito_unix_date_tz_consistency.py (IMV-ветка переведена на
  kit avito.imv, легаси avito/avito_houses/avito_shared/avito_detail не тронуты)
- tests/tasks/test_yandex_detail_backfill.py (subject — kit-задача, уже вызывает
  scraper_kit.providers.yandex.detail; save_detail_enrichment coverage-тесты
  внизу файла переведены на kit)
- tests/test_scraper_kit_yandex_golden_parity.py (detail/valuation parity убраны;
  serp/newbuilding/house_type_normalizer/build_url для yandex_realty/
  yandex_newbuilding не тронуты)
- tests/test_scraper_proxy.py (YandexValuation proxy-тесты переведены на kit
  config= DI; test_avito_imv_own_session_receives_proxies удалён — дублирует
  test_avito_imv_kit_parity.py footgun-тесты)
- tests/test_yandex_scrapers_delay_wiring.py (yandex_detail/yandex_valuation →
  kit delay_provider=; yandex_realty/yandex_newbuilding не тронуты)
- tests/test_scraper_kit_group_c_backfill_kit_parity.py (docstring уточнение,
  без функциональных изменений)

Полный backend-suite зелёный (3253 passed, 6 skipped) кроме известного
pre-existing flake test_search_api.py::test_search_cache_hit (#2208, не
регрессия этого PR). ruff check + ruff format — чисто на всех изменённых файлах.
2026-07-04 10:41:46 +03:00
..
backend chore(tradein/scraper-kit): удалить 5 orphaned legacy-модулей (#2277 финальный шаг, частичный) 2026-07-04 10:41:46 +03:00
browser feat(proxy-pool): P4 — browser dynamic pool-proxy (acquire→/fetch→relaunch) + orchestration wiring, ship-dark (#2164) 2026-07-02 19:20:29 +00:00
deploy chore(tradein): полное выключение источника n1 — миграция 165 + вычистка backend/ops (#2204) 2026-07-03 10:08:56 +03:00
docs feat(tradein): канонический матч ГАР↔houses (0%→41% yield) + tradein_canon_addr + --match-only (#2063) 2026-06-28 15:50:07 +00:00
frontend fix(tradein/v2): mobile layout — remove overflow floor + honest stacked mobile block (#2275) (#2389) 2026-07-04 07:02:56 +00:00
ops fix(sf): psql stdin-bootstrap пароля gendesign_reader + COALESCE-protect внешних id (review fixup, #976) 2026-06-12 11:26:40 +03:00
packages/scraper-kit refactor(tradein/scraper-kit): yandex providers on shared _base.py (#2363) 2026-07-04 09:38:43 +03:00
scripts chore(tradein): полное выключение источника n1 — миграция 165 + вычистка backend/ops (#2204) 2026-07-03 10:08:56 +03:00
.dockerignore chore(scraper-kit): docker-context wiring + restore backend workspace dep (#2137) 2026-07-02 14:21:38 +00:00
.env.example feat(tradein-imv): route IMV backfill через BrowserFetcher.fetch_json за dormant-флагом (#915 Stage 3) (#1916) 2026-06-26 09:54:28 +00:00
.gitignore feat: add tradein-mvp subproject (Trade-In Estimator под /trade-in) 2026-05-21 00:25:39 +03:00
DEPLOY.md fix(tradein): SSRF host-allowlist на admin scrape + удалён dead ADMIN_TOKEN (Refs #756) (#778) 2026-05-30 16:27:19 +00:00
docker-compose.prod.yml fix(tradein/deploy): atomic recreate + graceful scraper drain + startup-reap (#1951) (#2388) 2026-07-04 06:49:06 +00:00
docker-compose.yml chore(scraper-kit): docker-context wiring + restore backend workspace dep (#2137) 2026-07-02 14:21:38 +00:00
Makefile feat: add tradein-mvp subproject (Trade-In Estimator под /trade-in) 2026-05-21 00:25:39 +03:00
pyproject.toml chore(scraper-kit): scaffold internal package + uv workspace, build-safe (#2128) 2026-07-02 14:06:07 +00:00
README.md feat: add tradein-mvp subproject (Trade-In Estimator под /trade-in) 2026-05-21 00:25:39 +03:00
uv.lock ci(tradein): pre-merge гейт — backend pytest + frontend type-check/lint (#2208) (#2220) 2026-07-02 19:56:22 +00:00

Trade-In MVP

Локальный standalone-форк фичи Trade-In Estimator из проекта gendesign — оценка выкупной стоимости квартиры на вторичном рынке по аналогам и реальным сделкам. Layout повторяет PDF-отчёт «Брусника.Обмен» (см. docs/).

Что это и откуда взято

Источник Что Где
gendesign/main PR #316 TI-1 mock endpoint + Pydantic + SQL migration backend/
gendesign/main PR #317 TI-3 Next.js страница + 5 компонентов + hooks frontend/
gendesign/main PR #319 TI-2 PDF export 4 страницы (как у Брусники) backend/app/services/exporters/
gendesign/main PR #283 статичный tradein.html mockup (для Геныча) frontend/public/tradein.html
Встреча 19.05.2026 («Птица») требования к MVP оценки вторички docs/PTITSA_MEETING_2026-05-19.pdf
PDF Брусники EКБ-2485 референс layout-а отчёта docs/BRUSNIKA_REFERENCE_EKB-2485.pdf

Быстрый старт

make up              # build + up весь стек (caddy + frontend + backend + postgres)
open http://localhost:8080

Откроется / → автоматически редирект на /trade-in. Заполняешь форму (адрес/площадь/комнаты/этаж/...), нажимаешь «Оценить» — backend возвращает mock-оценку, фронт показывает median + диапазон цен + список аналогов.

Проверка backend напрямую:

make test-estimate
# или вручную:
curl -sS -X POST http://localhost:8080/api/v1/trade-in/estimate \
  -H 'Content-Type: application/json' \
  -d '{"address":"ул. Малышева, 1","area_m2":54,"rooms":2,"floor":5,"total_floors":17}' \
  | python3 -m json.tool

OpenAPI документация: http://localhost:8000/docs

Для сравнения макет vs реальная фича:

Структура

tradein-mvp/
├── docker-compose.yml         # caddy + frontend + backend + postgres
├── Makefile                   # удобные команды (up/down/logs/test-estimate)
├── deploy/
│   └── Caddyfile              # local reverse-proxy на http://localhost:8080
├── backend/                   # FastAPI + WeasyPrint
│   ├── Dockerfile
│   ├── pyproject.toml
│   ├── app/
│   │   ├── main.py            # FastAPI entry — только trade-in router
│   │   ├── core/
│   │   │   ├── config.py      # минимальный pydantic-settings
│   │   │   └── db.py          # SQLAlchemy engine + get_db
│   │   ├── api/v1/
│   │   │   └── trade_in.py    # 3 endpoint'а: POST /estimate, GET /estimate/{id}, GET /estimate/{id}/pdf
│   │   ├── schemas/
│   │   │   └── trade_in.py    # Pydantic: TradeInEstimateInput / AnalogLot / AggregatedEstimate
│   │   └── services/exporters/
│   │       └── trade_in_pdf.py  # WeasyPrint → 4-страничный PDF (cover/listings/deals/offer)
│   └── data/sql/
│       └── 001_trade_in_estimates.sql  # CREATE TABLE; применяется при первом старте postgres
├── frontend/                  # Next.js 15 + React 19 + TanStack Query
│   ├── Dockerfile
│   ├── package.json
│   ├── next.config.ts         # rewrites /api/* → backend
│   ├── tsconfig.json
│   ├── src/
│   │   ├── app/
│   │   │   ├── layout.tsx
│   │   │   ├── page.tsx       # redirect → /trade-in
│   │   │   ├── globals.css
│   │   │   ├── providers.tsx  # QueryClientProvider
│   │   │   └── trade-in/
│   │   │       └── page.tsx
│   │   ├── components/trade-in/
│   │   │   ├── EstimateForm.tsx       # форма ввода (sticky 360px)
│   │   │   ├── EstimateProgress.tsx   # индикатор «Парсим Циан → Авито → ...»
│   │   │   ├── EstimateResult.tsx     # карточка результата
│   │   │   ├── PriceRangeBar.tsx      # визуализация диапазона цен (как у Брусники)
│   │   │   └── AnalogsTable.tsx       # таблица аналогов
│   │   ├── lib/
│   │   │   ├── api.ts                 # apiFetch + HTTPError
│   │   │   ├── sessionId.ts           # X-Session-Id из localStorage
│   │   │   └── trade-in-api.ts        # useEstimateMutation + useEstimate hooks
│   │   └── types/
│   │       └── trade-in.ts            # TS типы зеркалят Pydantic schemas
│   └── public/
│       └── tradein.html              # статичный mockup от 17.05 (для side-by-side review)
└── docs/
    ├── BRUSNIKA_REFERENCE_EKB-2485.pdf  # эталон layout-а
    └── PTITSA_MEETING_2026-05-19.pdf    # AI-протокол встречи с требованиями

API

POST /api/v1/trade-in/estimate — оценить квартиру.

Запрос:

{
  "address": "ул. Малышева, 1, кв. 5, Екатеринбург",
  "area_m2": 54.0,
  "rooms": 2,
  "floor": 5,
  "total_floors": 17,
  "year_built": 1985,
  "house_type": "panel",
  "repair_state": "good",
  "has_balcony": true
}

Ответ:

{
  "estimate_id": "...uuid...",
  "median_price_rub": 13125000,
  "range_low_rub": 11550000,
  "range_high_rub": 14700000,
  "median_price_per_m2": 243056,
  "confidence": "high",
  "n_analogs": 8,
  "period_months": 24,
  "analogs": [ {"address": "...", "area_m2": 56, "price_rub": 12700000, ...} ],
  "actual_deals": [ ... ],
  "expires_at": "2026-05-20T22:48:00Z"
}

GET /api/v1/trade-in/estimate/{id} — получить сохранённую оценку (TTL 24ч) GET /api/v1/trade-in/estimate/{id}/pdf — скачать 4-страничный PDF (cover / listings / deals / offer)

Что внутри _mock_estimate() (текущая реализация)

Формула:

price = base_price_by_rooms × floor_factor × repair_factor
Поле Значения
Базовая цена ЕКБ 2026 студия 6.5M (260K/м²) · 1к 9.0M (225K/м²) · 2к 12.5M (208K/м²) · 3к 17.0M (213K/м²)
floor_factor 1-й этаж = ×0.95, последний = ×0.97, остальные = ×1.00
repair_factor needs_repair = ×0.90, standard = ×1.00, good = ×1.05, excellent = ×1.10
confidence 1-3 комнаты = high, остальные = medium
Улицы аналогов реальные центральные ЕКБ (Малышева, Куйбышева, 8 Марта, Белинского, пр. Ленина, Толмачёва, Радищева, Мамина-Сибиряка, Луначарского, Первомайская)

Каждая оценка сохраняется в trade_in_estimates с TTL 24 часа — UUID можно использовать для shareable links и PDF-экспорта.

Roadmap — что доделать

Phase 1 — заменить mock на реальные данные (TODO TI-1b из gendesign)

Сейчас _mock_estimate() возвращает хардкод. На встрече Птица 19.05 решили:

  • источники: Циан, Авито, Дом.Клик, Я.Недвижимость, Н1, Дом РФ
  • Объектив НЕ использовать на вторичке (он про первичку/ДДУ)
  • Росреестр для исторических сделок (квартал глубины)
  • картография ЕКБ для проверки этажности/года/планировок

Phase 2 — то что обсуждали на встрече

Задача Из протокола Птицы
Парольный вход + учёт пользователей + аналитика 0:23:44, 0:25:54
Доступ только Геныч / Загайнов / Паша (НЕ Рожкова) 0:25:50, 0:22:35
PDF-отчёт под паролем 0:08:39
Real-time парсинг ≥1/час чтобы ловить быстрые продажи 0:40:19
MVP к понедельнику 25.05.2026 0:26:12
Демо для девелопера в четверг 28.05.2026 0:18:08

Phase 3 — следующие продукты (упоминалось на встрече)

  1. Птица — анализ участков + расселение домов (≥20% квартир дома в продаже → подсветить можно расселять)
  2. Расселение как сервис — следствие #1 и Птицы

См. полный протокол: docs/PTITSA_MEETING_2026-05-19.pdf.

Как это связано с прод gendesign

Аспект Прод (gendsgn.ru) Этот MVP
URL https://gendsgn.ru/trade-in http://localhost:8080/trade-in
Backend shared FastAPI /api/v1/trade-in/* то же самое, standalone
Frontend Next.js 15 в большом monorepo тот же код, standalone
Postgres 84 таблицы, 6.83M ДДУ partitioned только trade_in_estimates (1 таблица)
Caddy TLS + 5 доменов + reverse-proxy local :8080 без TLS
Что отрезано site-finder, analytics, generative, scraper, OSM, NSPD, sentry, celery, redis, playwright всё это — кроме trade-in

Важно: эти два инстанса полностью изолированы. Локальный backend пишет в свой Postgres контейнер (порт 5433), не трогает прод. Можно сломать локально что угодно — прод не пострадает.

Лицензия

Internal use only. Forked from gendesign monorepo (private).