Платформа анализа аукционных сделок · 2026
CarFlip
Детерминированная платформа анализа прибыли для авто с аукционов salvage / auction в США — полная стоимость, оценка перепродажи, прибыль, ROI и Deal Score от 0 до 100 для любого лота.
Инженерный обзор
- ~12.5k Lines of TypeScript
- 12 Prisma Models
- 9 Deal-Score Factors
- 3 Business Engines
- ~88% API Test Coverage
- 17 Build Phases
Технологии
- npm workspaces
- packages/shared
- packages/api
- packages/web
- NestJS 10
- TypeScript
- Prisma 5
- PostgreSQL 16
- JWT + Passport
- argon2
- Helmet
- Per-IP rate limiting
- Next.js 14 (App Router)
- React 18
- Tailwind CSS
- TanStack Query
- Recharts
- Framework-free TypeScript
- Profit
- Deal Score
- Market Price
- VIN decoder
- Jest
- Unit + e2e
- Real PostgreSQL test DB
- ~88% API coverage
- Docker (multi-stage)
- Docker Compose
- nginx / Caddy
- Let's Encrypt
- Hetzner VPS
Задача
Дилер видит битую машину на аукционе в США — Copart, IAA — но не может понять, принесёт ли прибыль её покупка, доставка, ремонт и перепродажа.
Слишком много переменных затрат, неясная цена перепродажи, а риск трудно прочитать по коду тайтла и фото повреждений. Решение обычно принимается по интуиции.
Решение
CarFlip превращает каждый аукционный лот в понятное «да / нет».
Для любого лота он считает полную стоимость вложений (ставка + сборы + доставка + ремонт + дополнительно), оценку цены перепродажи по похожим продажам, прогноз прибыли и ROI, а также Deal Score от 0 до 100 с рейтингом риска из девяти взвешенных факторов — тип тайтла, тип повреждений, пробег, возраст, спрос на рынке и другие.
Каждый расчёт детерминирован и объясним. Одинаковый вход всегда даёт одинаковый результат, и за каждым числом стоит его обоснование — без чёрного ящика AI.
Моя роль
Я построил CarFlip в одиночку — от структуры монорепозитория до продакшена.
Это включало NestJS API и его схему Prisma, три свободных от фреймворка бизнес-движка, полный фронтенд на Next.js — лендинг, аутентификацию, фильтруемые списки, страницу анализа с визуализацией Deal Score и графиками, калькулятор what-if в реальном времени, дашборд, избранное и админ-панель — сид-данные, многоэтапные Docker-сборки, два деплой-стека и развёртывание на общий Hetzner VPS.
Архитектура
Монорепозиторий на TypeScript (npm workspaces) с тремя пакетами: shared, api и web.
Backend — NestJS 10 + Prisma 5 + PostgreSQL 16: глобальный validation pipe с белым списком DTO, глобальный exception filter, CORS, trust-proxy и лимиты размера тела. Вся бизнес-логика живёт в packages/shared как чистый, свободный от фреймворка TypeScript — легко тестируется и переиспользуется и API, и отдельным сид-скриптом.
Анализ рыночной цены дорогой, поэтому он кэшируется по хешу входа + TTL и инвалидируется автоматически при импорте новых похожих данных.
- Клиент Next.js 14 · App Router
- Транспорт REST /api/* · один origin
- Приложение NestJS 10 · 11 модулей
- Движки ProfitDeal ScoreMarket Price
- Данные PostgreSQL 16 · Prisma 5Кэш hash + TTL
Медиана со взвешиванием по похожести и отсевом выбросов
Цена перепродажи оценивается по похожим продажам, а не одной догадкой. Каждый comparable взвешивается по тому, насколько он похож на целевую машину; оценка — взвешенная медиана этого набора.
Выбросы отсеиваются через MAD (Median Absolute Deviation) до взятия медианы, и каждый результат несёт уровень уверенности — пользователь знает, насколько ему доверять.
- 01 Медиана со взвешиванием по похожести
- 02 Отсев выбросов через MAD
- 03 5 уровней уверенности — VERY_LOW → VERY_HIGH
- 04 Кэш по хешу входа + TTL
В сид-данных 558 рыночных comparable; оценка деградирует плавно — меньше / слабее comparable снижают уверенность, а не ломают число.
Девять взвешенных факторов, 0–100
Deal Score объединяет девять взвешенных факторов — тип тайтла, тип повреждений, пробег, возраст машины, спрос на рынке и другие — в единый балл от 0 до 100 плюс рейтинг.
Это простая взвешенная сумма, а не модель. Вклад каждого фактора виден — низкий балл всегда приходит с причиной.
Детерминированный и объяснимый — один и тот же лот всегда получает один балл, и балл никогда не чёрный ящик.
В ядре нет AI
Каждая цена, балл и цифра прибыли выдаётся чистой функцией. Нет ML-модели и нет «зависит» — одинаковый вход даёт одинаковый выход, и каждый выход идёт с входами, которые его породили.
У AI есть место в рабочем процессе; числа, на которые дилер ставит деньги — не оно.
Инженерные решения
Деплой на один origin — вообще без CORS
Браузер общается только с одним доменом. /api/* реверс-проксируется на NestJS-backend за тем же origin, что и фронтенд на Next.js.
Это полностью убирает CORS — нет preflight, нет жонглирования заголовками, нет краевых случаев с доменом cookie — и сокращает поверхность, которую нужно защищать.
Принцип Самая простая архитектура — та, в которой меньше всего частей, которые нужно защищать.
Бизнес-логика вне фреймворка
Движки profit, Deal-Score и рыночной цены — это обычный TypeScript в packages/shared без единого импорта NestJS. Их вызывает API; вызывает сид-скрипт; вызывают напрямую и юнит-тесты.
В правилах нет ничего, привязанного к тому, как именно пришёл запрос.
Принцип Держи правила там, куда фреймворк не дотянется.
Сложности
- 01
Один путь кода для живых и сид-данных
Пайплайны импорта — чистые функции, принимающие
PrismaClient. Эндпоинт импорта API и отдельный сид-скрипт вызывают идентичную логику, поэтому нет расхождения «работает в сидере, ломается в продакшене». - 02
Инвалидация кэша при импорте
Анализ рыночной цены кэшируется по хешу входа + TTL, потому что он дорогой. Импорт новых comparable автоматически инвалидирует затронутые записи — устаревшая оценка после свежих данных не отдаётся никогда.
- 03
Без скрейпинга
У Copart и IAA агрессивная анти-бот-защита. Вместо борьбы с ней CarFlip читает через mock-провайдер аукциона за интерфейсом провайдера — подключить реальный источник данных позже это ограниченное изменение, а не переписывание.
- 04
Доверие через объяснение
Детерминированный движок полезен, только если пользователь ему верит. Каждый балл и цена показываются с их входами и обоснованием, а не только итоговой цифрой — именно это делает результат применимым.
Результат
CarFlip — работающий full-stack продукт: NestJS API, три протестированных бизнес-движка и фронтенд на Next.js с калькулятором what-if в реальном времени и админ-панелью, на PostgreSQL.
Он заполнен 152 машинами, 558 рыночными comparable и 120 mock-импортами аукциона — каждый автоматически оценён по цене и по Deal Score. Около 12 500 строк TypeScript, за 17 фаз, с отдельной документацией движков (docs/market-price-engine.md, docs/auction-data.md).
Развёрнут на общий Hetzner VPS за общим реверс-прокси как изолированный Docker-стек — non-root контейнер, healthcheck-и, миграция при загрузке и сид, который никогда не удаляет заполненную базу.
- Аукционный лот
- Разбивка затрат
- Похожие продажи
- Рыночная цена
- Deal Score
- Прибыль + ROI
- Купить / пропустить
Ключевые функции
- Разбивка полных вложений
- Оценка перепродажи по comparable
- Прибыль и ROI
- Deal Score (0–100)
- Рейтинг риска
- Калькулятор what-if в реальном времени
- Фильтруемый список (состояние в URL)
- Страница анализа
- Визуализация Deal Score
- Графики (Recharts)
- Регистрация / вход / профиль
- JWT + argon2
- Ролевые guard-ы (USER / ADMIN)
- Rate limiting по IP
- Helmet · валидация DTO
- Добавление машины
- Импорт аукциона
- Управление рыночными данными
- Дашборд
- Избранное
- Движок Profit
- Движок Deal Score
- Движок Market Price
- VIN-декодер
Overview
CarFlip is a profit-analysis platform for US salvage / auction cars (Copart / IAA style). For any auction lot it computes the total investment, an estimated resale price from comparable sales, projected profit and ROI, and a 0–100 Deal Score with a risk rating — all deterministic and explainable, no AI black box.
It is built as a TypeScript monorepo: a NestJS + Prisma + PostgreSQL backend, framework-free business engines with full unit-test coverage, and a Next.js 14 frontend with a real-time what-if calculator and an admin panel.