# Trade-In MVP Локальный standalone-форк фичи **Trade-In Estimator** из проекта gendesign — оценка выкупной стоимости квартиры на вторичном рынке по аналогам и реальным сделкам. Layout повторяет PDF-отчёт «Брусника.Обмен» (см. [docs/](docs/)). ## Что это и откуда взято | Источник | Что | Где | |---|---|---| | `gendesign/main` PR [#316](https://git.gendsgn.ru/lekss361/gendesign/pulls/316) TI-1 | mock endpoint + Pydantic + SQL migration | `backend/` | | `gendesign/main` PR [#317](https://git.gendsgn.ru/lekss361/gendesign/pulls/317) TI-3 | Next.js страница + 5 компонентов + hooks | `frontend/` | | `gendesign/main` PR [#319](https://git.gendsgn.ru/lekss361/gendesign/pulls/319) TI-2 | PDF export 4 страницы (как у Брусники) | `backend/app/services/exporters/` | | `gendesign/main` PR [#283](https://git.gendsgn.ru/lekss361/gendesign/pulls/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` | ## Быстрый старт ```bash make up # build + up весь стек (caddy + frontend + backend + postgres) open http://localhost:8080 ``` Откроется `/` → автоматически редирект на `/trade-in`. Заполняешь форму (адрес/площадь/комнаты/этаж/...), нажимаешь «Оценить» — backend возвращает mock-оценку, фронт показывает median + диапазон цен + список аналогов. Проверка backend напрямую: ```bash 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 документация: Для сравнения макет 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` — оценить квартиру. Запрос: ```json { "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 } ``` Ответ: ```json { "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](docs/PTITSA_MEETING_2026-05-19.pdf). ## Как это связано с прод gendesign | Аспект | Прод (gendsgn.ru) | Этот MVP | |---|---|---| | URL | | | | 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).