ДЕТАЛИЗАЦИЯ ТЕХНИЧЕСКОГО ЗАДАНИЯ
B2B-платформа инвестиций в бизнес
—
БЛОК 1: ПОЛЬЗОВАТЕЛЬСКИЕ РОЛИ И ПРАВА ДОСТУПА
1.1. Детализация ролей и сценариев использования
Раздел ТЗ: Роли пользователей
Суть проблемы:
Недостаточно описаны конкретные возможности каждой роли, не определены сценарии переключения между ролями, отсутствует матрица прав доступа.
Предлагаемое решение:
1.1.1. Матрица ролей и прав доступа
| Функционал | Инвестор (Free) | Инвестор (Premium) | Предприниматель (Free) | Предприниматель (Premium) | Администратор | Модератор |
|---|---|---|---|---|---|---|
| Просмотр объявлений | ✓ (10/день) | ✓ (безлимит) | ✓ (5/день) | ✓ (безлимит) | ✓ | ✓ |
| Создание объявлений | — | — | ✓ (1 активное) | ✓ (5 активных) | — | — |
| Отклик на объявления | ✓ (3/месяц) | ✓ (безлимит) | — | — | — | — |
| Просмотр контактов | — | ✓ | По запросу | ✓ (после одобрения) | ✓ | — |
| Внутренний чат | ✓ (базовый) | ✓ (расширенный) | ✓ (базовый) | ✓ (расширенный) | ✓ | ✓ |
| Продвижение объявлений | — | — | ✓ (платно) | ✓ (включено) | — | — |
| Аналитика | Базовая | Расширенная | Базовая | Расширенная | Полная | Частичная |
| Верификация | Опционально | Включена | Опционально | Включена | — | Проверка док-тов |
| Избранное | ✓ (20 макс) | ✓ (безлимит) | ✓ (20 макс) | ✓ (безлимит) | — | — |
| Экспорт данных | — | ✓ (CSV/Excel) | — | ✓ (CSV/Excel) | ✓ | — |
1.1.2. Механизм переключения между ролями
Бизнес-логика:
- Один аккаунт = множественные роли
- Пользователь может быть одновременно инвестором и предпринимателем
- Переключение через dropdown в шапке профиля
- Отдельные балансы, настройки и история для каждой роли
- Единый email и телефон для всех ролей
- Правила переключения:
IF пользователь имеет обе роли THEN - Показывать переключатель ролей в header - При переходе в раздел автоматически определять нужную роль - Сохранять последнюю активную роль в сессии IF пользователь хочет добавить роль THEN - Кнопка "Добавить роль" в настройках профиля - Заполнение профиля для новой роли (отдельная форма) - Модерация новой роли (если требуется верификация) - UI/UX переключения:
- Визуальная индикация активной роли (цветовая схема: синий = инвестор, зеленый = предприниматель)
- Уведомление при первом переключении с подсказками
- Быстрый доступ к функциям другой роли через контекстное меню
Технические требования:
// Структура данных пользователя
{
userId: "UUID",
email: "user@example.com",
roles: [
{
roleType: "investor",
tier: "premium",
profileId: "INV-UUID",
isActive: true,
settings: {...},
balance: 0,
statistics: {...}
},
{
roleType: "entrepreneur",
tier: "free",
profileId: "ENT-UUID",
isActive: false,
settings: {...},
balance: 0,
statistics: {...}
}
],
currentRole: "investor",
lastRoleSwitch: "2024-01-15T10:30:00Z"
}
Примеры use-cases:
UC-1.1: Инвестор хочет стать предпринимателем
1. Пользователь заходит в "Настройки профиля"
2. Нажимает "Добавить роль предпринимателя"
3. Заполняет форму с данными компании (ИНН, ОГРН, описание)
4. Загружает документы (устав, выписка из ЕГРЮЛ)
5. Отправляет на модерацию
6. Получает уведомление о результате проверки (24-48 часов)
7. После одобрения видит переключатель ролей в header
UC-1.2: Переключение между ролями
1. Пользователь кликает на dropdown с текущей ролью в header
2. Видит список доступных ролей с иконками
3. Выбирает нужную роль
4. Страница обновляется с контентом для выбранной роли
5. Навигация и доступные функции меняются соответственно
Приоритет: [CRITICAL] — MVP
Обоснование решения:
- Гибкость: Пользователь может быть и инвестором, и предпринимателем, что увеличивает engagement
- Монетизация: Возможность продавать Premium для каждой роли отдельно
- Безопасность: Разделение данных по ролям предотвращает конфликты интересов
- UX: Единый аккаунт удобнее, чем создание нескольких профилей
Риски, которые закрывает:
- Конфликт интересов (пользователь видит данные в разных контекстах)
- Путаница в интерфейсе (четкое разделение функционала)
- Проблемы с биллингом (отдельные подписки для каждой роли)
Влияние на метрики:
- Увеличение LTV на 40-60% (пользователь платит за обе роли)
- Снижение CAC (один пользователь = два сегмента)
- Рост engagement на 30-40%
Связанные изменения:
- Раздел «Биллинг и оплата» — добавить управление подписками по ролям
- Раздел «Уведомления» — разделить настройки по ролям
- Раздел «Профиль» — создать вкладки для каждой роли
—
1.2. Детализация тарифных планов
Раздел ТЗ: Монетизация
Суть проблемы:
Не определены конкретные лимиты, не описаны механизмы ограничений, отсутствует логика перехода между тарифами.
Предлагаемое решение:
1.2.1. Тарифные планы для инвесторов
| Параметр | Free | Premium (₽4,990/мес) | Enterprise (по запросу) |
|---|---|---|---|
| Просмотр объявлений | 10/день | Безлимит | Безлимит |
| Откликов на объявления | 3/месяц | Безлимит | Безлимит |
| Доступ к контактам | — | ✓ | ✓ + приоритет |
| Сохраненные объявления | 20 | Безлимит | Безлимит |
| Расширенный поиск | Базовый | ✓ | ✓ + AI-рекомендации |
| Аналитика рынка | — | Базовая | Расширенная + отчеты |
| Верификация профиля | Опционально (₽2,000) | Включена | Включена |
| Приоритет в откликах | — | ✓ | ✓ + выделение |
| Экспорт данных | — | CSV | CSV + API |
| Техподдержка | Email (48ч) | Email (24ч) + чат | Персональный менеджер |
| Скрытие от конкурентов | — | — | ✓ |
| Ранний доступ к объявлениям | — | +24 часа | +48 часов |
1.2.2. Тарифные планы для предпринимателей
| Параметр | Free | Premium (₽7,990/мес) | Premium+ (₽14,990/мес) |
|---|---|---|---|
| Активных объявлений | 1 | 5 | 15 |
| Продвижение объявлений | ₽500/неделя | 2 бесплатно/мес | 5 бесплатно/мес |
| Позиция в выдаче | Стандартная | Топ-10 | Топ-3 |
| Аналитика объявлений | Базовая | Расширенная | Полная + A/B тесты |
| Верификация компании | Опционально (₽5,000) | Включена | Включена + бейдж |
| Ответов на отклики | 10/месяц | Безлимит | Безлимит |
| Шаблоны ответов | — | 5 | 20 |
| Автоответчик | — | — | ✓ |
| CRM-интеграция | — | — | ✓ |
| Мультиаккаунт (сотрудники) | — | 2 пользователя | 10 пользователей |
| Брендирование профиля | — | Логотип | Полное оформление |
| Приоритетная модерация | — | ✓ (4 часа) | ✓ (2 часа) |
1.2.3. Бизнес-логика ограничений
Механизм подсчета лимитов:
# Псевдокод системы лимитов
class RateLimiter:
def check_limit(user_id, action_type, role):
# Получаем тарифный план пользователя
plan = get_user_plan(user_id, role)
# Проверяем лимит для действия
limit = LIMITS[plan][action_type]
current_usage = get_usage(user_id, action_type, period)
if limit == "unlimited":
return True
if current_usage >= limit:
return False, {
"error": "limit_reached",
"current": current_usage,
"max": limit,
"reset_at": get_reset_time(period),
"upgrade_url": "/pricing"
}
return True
def increment_usage(user_id, action_type):
# Увеличиваем счетчик использования
redis.incr(f"usage:{user_id}:{action_type}:{period}")
redis.expire(f"usage:{user_id}:{action_type}:{period}", TTL)
Правила сброса лимитов:
- Ежедневные лимиты (просмотры объявлений):
- Сброс в 00:00 по московскому времени
- Уведомление за 2 часа до сброса, если лимит исчерпан
- Ежемесячные лимиты (отклики, объявления):
- Сброс в день оплаты подписки (billing cycle)
- Уведомление за 3 дня до сброса с статистикой использования
- Одновременные лимиты (активные объявления):
- Проверка при создании нового объявления
- Возможность архивировать старое для публикации нового
Поведение при достижении лимита:
IF пользователь достиг лимита THEN
1. Показать модальное окно с информацией:
- "Вы использовали X из Y доступных [действий]"
- Дата следующего сброса лимита
- Кнопка "Перейти на Premium" (с указанием выгод)
- Кнопка "Напомнить позже"
2. Отправить email с предложением апгрейда:
- Персонализированное предложение
- Скидка 20% при оплате в течение 24 часов
- Калькулятор выгоды от Premium
3. Логировать событие для аналитики:
- Тип лимита
- Частота достижения
- Конверсия в upgrade
1.2.4. Механизм перехода между тарифами
Апгрейд (Free → Premium):
1. Пользователь нажимает "Перейти на Premium"
2. Показывается страница с выбором тарифа:
- Помесячная оплата (полная стоимость)
- Годовая оплата (скидка 20%)
- Квартальная оплата (скидка 10%)
3. Выбор способа оплаты:
- Банковская карта (Stripe/CloudPayments)
- Счет для юрлиц (выставление счета)
- СБП (для РФ)
4. Оплата
5. Мгновенная активация Premium
6. Email-подтверждение с чеком и деталями подписки
7. Onboarding для новых функций Premium
Даунгрейд (Premium → Free):
1. Пользователь отменяет подписку в настройках
2. Показывается опрос:
- Причина отмены (обязательно)
- Предложение скидки 30% на следующий месяц
- Предложение паузы подписки на 1 месяц
3. Если пользователь подтверждает отмену:
- Premium действует до конца оплаченного периода
- За 7 дней до окончания - напоминание с предложением продлить
- За 1 день до окончания - финальное напоминание
- После окончания - переход на Free с сохранением данных
4. Ограничения после даунгрейда:
- Лишние объявления переходят в архив (можно выбрать, какие оставить)
- История откликов сохраняется, но доступна только для чтения
- Аналитика доступна только за последние 30 дней
Технические требования:
-- Таблица подписок
CREATE TABLE subscriptions (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
role_type VARCHAR(20), -- 'investor' или 'entrepreneur'
plan_type VARCHAR(20), -- 'free', 'premium', 'enterprise'
status VARCHAR(20), -- 'active', 'cancelled', 'expired', 'paused'
started_at TIMESTAMP,
expires_at TIMESTAMP,
billing_cycle VARCHAR(20), -- 'monthly', 'quarterly', 'yearly'
amount DECIMAL(10,2),
currency VARCHAR(3),
payment_method VARCHAR(50),
auto_renew BOOLEAN DEFAULT true,
cancelled_at TIMESTAMP,
cancellation_reason TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- Таблица использования лимитов
CREATE TABLE usage_limits (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
role_type VARCHAR(20),
action_type VARCHAR(50), -- 'view_listing', 'create_response', etc.
period_type VARCHAR(20), -- 'daily', 'monthly'
period_start TIMESTAMP,
period_end TIMESTAMP,
usage_count INTEGER DEFAULT 0,
limit_value INTEGER,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
Примеры use-cases:
UC-1.3: Инвестор исчерпал дневной лимит просмотров
1. Пользователь просмотрел 10 объявлений (лимит Free-плана)
2. При попытке открыть 11-е объявление видит модальное окно:
"Вы достигли дневного лимита просмотров (10/10)
Лимит обновится через 8 часов 23 минуты
[Перейти на Premium] - безлимитный просмотр за ₽4,990/мес
[Напомнить завтра]"
3. Если выбирает Premium:
- Переход на страницу оплаты
- После оплаты мгновенный доступ к просмотру
4. Если выбирает "Напомнить":
- Push-уведомление в момент сброса лимита
- Email с подборкой новых объявлений
UC-1.4: Предприниматель хочет опубликовать второе объявление на Free
1. Пользователь нажимает "Создать объявление"
2. Система проверяет: уже есть 1 активное объявление (лимит Free)
3. Показывается модальное окно:
"У вас уже есть активное объявление
Варианты:
[Архивировать старое объявление] - бесплатно
[Перейти на Premium] - до 5 активных объявлений за ₽7,990/мес
[Отменить]"
4. Если выбирает архивацию:
- Показывается список активных объявлений
- Пользователь выбирает, какое архивировать
- Новое объявление публикуется
5. Если выбирает Premium:
- Переход на страницу оплаты
- После оплаты возврат к созданию объявления
Приоритет: [CRITICAL] — MVP
Обоснование решения:
- Монетизация: Четкие лимиты стимулируют переход на платные планы
- Прозрачность: Пользователь всегда знает свой статус и ограничения
- Гибкость: Возможность выбора между архивацией и апгрейдом
- Retention: Мягкие ограничения не отталкивают пользователей
Риски, которые закрывает:
- Злоупотребление бесплатным тарифом
- Неясность ценности Premium
- Фрустрация от неожиданных ограничений
- Потеря revenue из-за нечетких границ тарифов
Влияние на метрики:
- Конверсия Free → Premium: 8-12% (бенчмарк SaaS)
- ARPU увеличится на 300-400%
- Churn rate Premium: <5% при правильной коммуникации
- Trial-to-paid: 25-30% при наличии триального периода
Связанные изменения:
- Раздел «Биллинг» — добавить управление подписками
- Раздел «Уведомления» — добавить алерты о лимитах
- Раздел «Аналитика» — добавить метрики по использованию лимитов
- Раздел «Админ-панель» — добавить управление тарифами
—
БЛОК 2: ВЕРИФИКАЦИЯ И ДОВЕРИЕ
2.1. Процедуры KYC и верификации
Раздел ТЗ: Верификация пользователей
Суть проблемы:
Не описан процесс верификации, не определены требуемые документы, отсутствует логика проверки и критерии одобрения.
Предлагаемое решение:
2.1.1. Уровни верификации
Трехуровневая система:
| Уровень | Требования | Доступные возможности | Бейдж |
|---|---|---|---|
| Базовый | Email + телефон | Базовый функционал | — |
| Подтвержденный | + паспорт/ID | Увеличенные лимиты, доверие +30% | ✓ Verified |
| Премиум | + финансовые документы + видеозвонок | Полный доступ, доверие +70% | ⭐ Premium Verified |
2.1.2. Процесс верификации для инвесторов
Шаг 1: Email и телефон (обязательно для всех)
Процесс:
1. Регистрация с email
2. Отправка кода подтверждения на email (6 цифр, TTL 15 минут)
3. Ввод номера телефона
4. Отправка SMS с кодом (4 цифры, TTL 10 минут)
5. Подтверждение обоих каналов
6. Активация аккаунта
Технические требования:
- Сервис отправки email: SendGrid/AWS SES
- Сервис SMS: Twilio/SMSC
- Rate limiting: 3 попытки в час на email/телефон
- Защита от ботов: reCAPTCHA v3
Шаг 2: Верификация личности (опционально, ₽2,000 для Free, включено в Premium)
Требуемые документы:
1. Паспорт РФ (разворот с фото) или ID другой страны
2. Селфи с паспортом и листком бумаги с текущей датой
3. ИНН (для РФ)
Процесс:
1. Пользователь загружает документы через защищенную форму
2. Автоматическая проверка качества фото (AI):
- Читаемость текста
- Отсутствие размытия
- Соответствие формата
3. Автоматическое распознавание данных (OCR):
- Извлечение ФИО, даты рождения, номера документа
- Проверка контрольных сумм
4. Проверка в базах данных:
- Черные списки
- Санкционные списки (OFAC, EU, UN)
- База недействительных паспортов МВД РФ (API)
5. Ручная модерация (если автопроверка не прошла):
- Модератор проверяет документы (SLA: 24 часа)
- Может запросить дополнительные документы
- Принимает решение: одобрить/отклонить/запросить уточнения
6. Уведомление пользователя о результате
7. При одобрении: активация бейджа "Verified"
Критерии одобрения:
✓ Документ действителен
✓ Фото четкое и читаемое
✓ Селфи соответствует фото в документе (liveness detection)
✓ Данные не в черных списках
✓ Возраст 18+ (для инвесторов 21+)
Критерии отклонения:
✗ Поддельный документ
✗ Несоответствие селфи и фото в документе
✗ Документ в черном списке
✗ Недостаточное качество фото
✗ Возраст менее 18 лет
Шаг 3: Премиум-верификация (только для Enterprise-плана)
Дополнительные требования:
1. Справка о доходах (2-НДФЛ или выписка со счета)
2. Подтверждение источника средств для инвестиций
3. Видеозвонок с сотрудником платформы (15-20 минут)
Процесс видеозвонка:
1. Запись на удобное время через календарь
2. Проверка документов в реальном времени
3. Верификация liveness (пользователь выполняет действия: повернуть голову, моргнуть)
4. Краткое интервью:
- Цели инвестирования
- Опыт инвестиций
- Источник средств
5. Запись звонка сохраняется (с согласия пользователя)
6. Решение принимается в течение 2 часов после звонка
Результат:
- Бейдж "Premium Verified" с галочкой и звездой
- Приоритет в откликах
- Доступ к закрытым сделкам (если предприниматель требует верификацию)
2.1.3. Процесс верификации для предпринимателей
Базовая верификация компании (обязательно для публикации объявлений)
Требуемые документы (для ООО/АО):
1. Выписка из ЕГРЮЛ (не старше 30 дней)
2. Устав компании
3. Свидетельство о регистрации (ОГРН)
4. ИНН компании
5. Паспорт директора/учредителя
6. Решение о назначении директора (если применимо)
Для ИП:
1. Выписка из ЕГРИП (не старше 30 дней)
2. Свидетельство о регистрации (ОГРНИП)
3. ИНН
4. Паспорт ИП
Процесс:
1. Загрузка документов через форму
2. Автоматическая проверка:
- Валидация ИНН/ОГРН через API ФНС
- Проверка статуса компании (действующая/ликвидирована)
- Проверка в реестре дисквалифицированных лиц
- Проверка задолженностей по налогам (API ФНС)
3. Проверка финансового состояния:
- Выручка за последний год (из ЕГРЮЛ)
- Наличие исполнительных производств
- Кредитная история компании (опционально, через бюро)
4. Ручная модерация документов (SLA: 48 часов):
- Проверка соответствия документов
- Проверка полномочий загрузившего документы
- Звонок на официальный телефон компании для подтверждения
5. Решение о верификации
6. Активация бейджа "Verified Business"
Критерии одобрения:
✓ Компания действующая
✓ Нет задолженностей по налогам >100,000 руб
✓ Директор не дисквалифицирован
✓ Документы актуальные и соответствуют друг другу
✓ Подтверждение по телефону пройдено
Критерии отклонения:
✗ Компания ликвидирована или в процессе ликвидации
✗ Массовый адрес регистрации (признак "однодневки")
✗ Директор-массовый руководитель (>10 компаний)
✗ Задолженность по налогам >500,000 руб
✗ Исполнительные производства на сумму >1 млн руб
✗ Компания моложе 3 месяцев (для Free-плана)
Расширенная верификация (для Premium)
Дополнительные документы:
1. Финансовая отчетность за последний год (баланс, ОПУ)
2. Бизнес-план (для стартапов)
3. Презентация проекта
4. Рекомендательные письма (опционально)
Дополнительные проверки:
1. Анализ финансовых показателей:
- Рентабельность
- Долговая нагрузка
- Динамика выручки
2. Проверка репутации:
- Отзывы в интернете
- Судебные дела
- Упоминания в СМИ
3. Видеозвонок с основателем/директором:
- Презентация компании
- Обсуждение планов развития
- Проверка компетенций команды
Результат:
- Бейдж "Premium Verified Business" со звездой
- Приоритет в выдаче
- Увеличенное доверие инвесторов (+70% к откликам)
- Доступ к премиум-инвесторам
2.1.4. Техническая реализация
API для проверки документов:
// Интеграции
const verificationServices = {
// OCR для распознавания документов
ocr: {
provider: "Google Cloud Vision API / ABBYY",
confidence_threshold: 0.85,
supported_documents: ["passport_rf", "inn", "snils", "driver_license"]
},
// Проверка в государственных реестрах
government_apis: {
fns: {
endpoint: "https://api.nalog.ru/",
methods: ["check_inn", "check_ogrn", "get_company_info"],
rate_limit: "100 requests/hour"
},
egrul: {
endpoint: "https://egrul.nalog.ru/",
methods: ["get_extract", "check_status"],
cache_ttl: "24 hours"
},
fssp: {
endpoint: "https://api.fssp.gov.ru/",
methods: ["check_enforcement_proceedings"],
rate_limit: "50 requests/hour"
}
},
// Проверка в санкционных списках
sanctions: {
providers: ["Dow Jones", "World-Check", "ComplyAdvantage"],
update_frequency: "daily",
lists: ["OFAC", "EU", "UN", "UK", "INTERPOL"]
},
// Liveness detection для селфи
liveness: {
provider: "Onfido / Sumsub",
checks: ["face_match", "document_authenticity", "liveness"],
confidence_threshold: 0.90
},
// Проверка телефона
phone_verification: {
provider: "Twilio Lookup",
checks: ["validity", "carrier", "country", "line_type"],
fraud_score_threshold: 0.7
}
};
// Пример workflow верификации
async function verifyUser(userId, documents) {
const results = {
status: "pending",
checks: {},
score: 0,
issues: []
};
// 1. OCR документов
const ocrResults = await ocr.recognize(documents.passport);
results.checks.ocr = {
success: ocrResults.confidence > 0.85,
data: ocrResults.extracted_data
};
// 2. Проверка в черных списках
const sanctionsCheck = await sanctions.check(ocrResults.extracted_data.full_name);
results.checks.sanctions = {
success: !sanctionsCheck.found,
lists_checked: sanctionsCheck.lists
};
// 3. Liveness detection
const livenessCheck = await liveness.verify(documents.selfie, documents.passport);
results.checks.liveness = {
success: livenessCheck.match_score > 0.90,
score: livenessCheck.match_score
};
// 4. Проверка документа в базе МВД
const passportCheck = await government.checkPassport(ocrResults.extracted_data.passport_number);
results.checks.passport_validity = {
success: passportCheck.valid && !passportCheck.invalid,
status: passportCheck.status
};
// 5. Расчет общего скора
results.score = calculateVerificationScore(results.checks);
// 6. Автоматическое решение
if (results.score >= 0.95) {
results.status = "approved";
await approveVerification(userId);
} else if (results.score < 0.70) {
results.status = "rejected";
results.issues = identifyIssues(results.checks);
await rejectVerification(userId, results.issues);
} else {
results.status = "manual_review";
await sendToModerator(userId, results);
}
return results;
}
// Расчет скора верификации
function calculateVerificationScore(checks) {
const weights = {
ocr: 0.15,
sanctions: 0.30,
liveness: 0.25,
passport_validity: 0.30
};
let score = 0;
for (const [check, weight] of Object.entries(weights)) {
if (checks[check]?.success) {
score += weight;
}
}
return score;
}
База данных для хранения верификаций:
CREATE TABLE verifications (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id),
role_type VARCHAR(20), -- 'investor' или 'entrepreneur'
verification_level VARCHAR(20), -- 'basic', 'verified', 'premium'
status VARCHAR(20), -- 'pending', 'approved', 'rejected', 'expired'
-- Документы
documents JSONB, -- {passport: {url, type, uploaded_at}, selfie: {...}, ...}
-- Результаты проверок
checks_results JSONB, -- {ocr: {...}, sanctions: {...}, liveness: {...}}
verification_score DECIMAL(3,2),
-- Данные из документов
extracted_data JSONB, -- {full_name, birth_date, passport_number, inn, ...}
-- Модерация
moderator_id UUID REFERENCES users(id),
moderation_notes TEXT,
moderation_completed_at TIMESTAMP,
-- Временные метки
submitted_at TIMESTAMP DEFAULT NOW(),
approved_at TIMESTAMP,
rejected_at TIMESTAMP,
expires_at TIMESTAMP, -- верификация действительна 1 год
-- Причина отклонения
rejection_reason TEXT,
rejection_code VARCHAR(50),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Индексы
CREATE INDEX idx_verifications_user_id ON verifications(user_id);
CREATE INDEX idx_verifications_status ON verifications(status);
CREATE INDEX idx_verifications_moderator ON verifications(moderator_id) WHERE status = 'manual_review';
-- Таблица для логов проверок
CREATE TABLE verification_checks_log (
id UUID PRIMARY KEY,
verification_id UUID REFERENCES verifications(id),
check_type VARCHAR(50), -- 'ocr', 'sanctions', 'liveness', etc.
provider VARCHAR(100),
request_data JSONB,
response_data JSONB,
success BOOLEAN,
error_message TEXT,
duration_ms INTEGER,
created_at TIMESTAMP DEFAULT NOW()
);
Примеры use-cases:
UC-2.1: Инвестор проходит базовую верификацию
1. Пользователь заходит в "Настройки" → "Верификация"
2. Видит преимущества верификации:
- Увеличение доверия на 30%
- Доступ к верифицированным предпринимателям
- Приоритет в откликах
3. Нажимает "Начать верификацию"
4. Загружает паспорт (фото разворота с фото):
- Система проверяет качество фото в реальном времени
- Показывает подсказки: "Убедитесь, что текст читаемый"
- Автоматически обрезает и выравнивает фото
5. Загружает селфи с паспортом:
- Камера открывается с наложением контура лица
- Система просит выполнить действие: "Поверните голову направо"
- Делает фото автоматически при правильном положении
6. Система автоматически проверяет документы (30-60 секунд):
- Показывается прогресс-бар с этапами проверки
- "Распознаем данные..." → "Проверяем в базах..." → "Сравниваем фото..."
7. Результат:
- Если автопроверка успешна (score > 0.95):
* Мгновенное одобрение
* Бейдж "Verified" активируется
* Поздравительное сообщение
- Если требуется ручная проверка (0.70 < score < 0.95):
* "Ваши документы отправлены на проверку"
* "Мы свяжемся с вами в течение 24 часов"
* Email с подтверждением получения
- Если отклонено (score < 0.70):
* "К сожалению, верификация не пройдена"
* Причина отклонения (например, "Фото паспорта нечеткое")
* Возможность повторной попытки через 24 часа
UC-2.2: Предприниматель проходит верификацию компании
1. Пользователь создает профиль предпринимателя
2. Для публикации объявления требуется верификация компании
3. Заполняет форму с данными компании:
- ИНН (автозаполнение остальных полей через API ФНС)
- ОГРН
- Юридический адрес
- Фактический адрес
- Телефон компании
- Email компании
4. Загружает документы:
- Выписка из ЕГРЮЛ (PDF, не старше 30 дней)
* Система проверяет дату выписки автоматически
- Устав компании (PDF)
- Паспорт директора
5. Автоматическая проверка (2-3 минуты):
- Валидация ИНН/ОГРН через API ФНС
- Проверка статуса компании
- Проверка директора в реестре дисквалифицированных лиц
- Проверка задолженностей по налогам
6. Результаты автопроверки:
- Показываются в виде чеклиста:
✓ Компания действующая
✓ Нет задолженностей по налогам
✓ Директор не дисквалифицирован
⏳ Документы отправлены на проверку модератору
7. Модератор проверяет документы (в течение 48 часов):
- Звонит на официальный телефон компании
- Проверяет соответствие документов
- Принимает решение
8. Уведомление о результате:
- Email + push-уведомление
- Если одобрено:
* Бейдж "Verified Business" активируется
* Возможность публиковать объявления
- Если отклонено:
* Причина отклонения
* Рекомендации по исправлению
* Возможность повторной подачи
UC-2.3: Модератор проверяет документы
1. Модератор заходит в админ-панель → "Очередь верификаций"
2. Видит список заявок, отсортированных по приоритету:
- Premium-пользователи (SLA 4 часа)
- Обычные пользователи (SLA 24-48 часов)
3. Открывает заявку:
- Слева: загруженные документы (с возможностью увеличения)
- Справа: результаты автоматических проверок
- Внизу: поле для заметок и кнопки действий
4. Проверяет документы:
- Сравнивает фото в паспорте с селфи
- Проверяет читаемость и подлинность документов
- Проверяет соответствие данных
5. Если нужны уточнения:
- Нажимает "Запросить дополнительные документы"
- Выбирает из списка или пишет свободный текст
- Пользователь получает уведомление с запросом
- Заявка переходит в статус "Ожидание документов"
6. Принимает решение:
- [Одобрить] - верификация активируется мгновенно
- [Отклонить] - выбирает причину из списка + комментарий
- [Заблокировать] - если обнаружено мошенничество
7. После решения:
- Пользователь получает уведомление
- Заявка архивируется
- Статистика модератора обновляется
Приоритет: [CRITICAL] — MVP (базовая верификация), [HIGH] — v1.1 (премиум-верификация)
Обоснование решения:
- Доверие: Верификация критична для B2B-платформы, где речь идет о больших суммах
- Безопасность: Многоуровневая проверка минимизирует риск мошенничества
- Автоматизация: 70-80% заявок проходят автоматически, снижая нагрузку на модераторов
- Compliance: Соответствие требованиям 115-ФЗ (противодействие отмыванию денег)
Риски, которые закрывает:
- Мошенничество и фейковые профили
- Репутационные риски платформы
- Юридические риски (платформа может быть привлечена к ответственности)
- Недоверие между пользователями
Влияние на метрики:
- Конверсия в отклики: +40-50% для верифицированных профилей
- Успешность сделок: +60% при обоюдной верификации
- Churn rate: -25% (верифицированные пользователи более лояльны)
- Стоимость модерации: -70% за счет автоматизации
Связанные изменения:
- Раздел «Профиль» — добавить отображение статуса верификации
- Раздел «Поиск» — добавить фильтр по верифицированным пользователям
- Раздел «Уведомления» — добавить алерты о статусе верификации
- Раздел «Админ-панель» — добавить интерфейс модерации
- Раздел «Безопасность» — добавить хранение документов с шифрованием
—
2.2. Система рейтингов и отзывов
Раздел ТЗ: Доверие и репутация
Суть проблемы:
Не описана механика оценок, не определено, кто может оставлять отзывы, отсутствует защита от накрутки рейтинга.
Предлагаемое решение:
2.2.1. Модель рейтинга
Многофакторный рейтинг:
Общий рейтинг пользователя = взвешенная сумма:
Для инвесторов:
- Отзывы от предпринимателей (40%)
- Активность на платформе (20%)
- Статус верификации (20%)
- Завершенные сделки (15%)
- Время отклика (5%)
Для предпринимателей:
- Отзывы от инвесторов (35%)
- Качество объявлений (25%)
- Статус верификации (20%)
- Прозрачность информации (10%)
- Время отклика (10%)
Шкала: от 1.0 до 5.0 (с точностью до 0.1)
Отображение рейтинга:
Визуализация:
★★★★☆ 4.7 (23 отзыва)
Детализация при наведении:
Коммуникация: ★★★★★ 4.9
Надежность: ★★★★☆ 4.6
Профессионализм: ★★★★☆ 4.5
Скорость ответа: ★★★★★ 5.0
2.2.2. Правила оставления отзывов
Кто может оставить отзыв:
Условия для возможности оставить отзыв:
1. Должно быть взаимодействие между пользователями:
- Инвестор откликнулся на объявление предпринимателя
- Предприниматель одобрил отклик
- Состоялась переписка (минимум 5 сообщений от каждой стороны)
- Прошло минимум 7 дней с момента первого контакта
2. Отзыв можно оставить только один раз на каждое взаимодействие
3. Отзыв можно оставить в течение 90 дней после взаимодействия
4. Нельзя оставить отзыв самому себе (очевидно)
5. Нельзя оставить отзыв, если пользователь заблокирован
6. Для Premium-пользователей: можно запросить отзыв через платформу
(отправляется приглашение оставить отзыв)
Процесс оставления отзыва:
1. Система автоматически предлагает оставить отзыв:
- Через 14 дней после начала взаимодействия
- После завершения сделки (если пользователи отметили это)
- Напоминание через 30 дней, если отзыв не оставлен
2. Форма отзыва:
[Оцените взаимодействие с [Имя пользователя]]
Общая оценка: ☆☆☆☆☆ (обязательно)
Детальные оценки:
- Коммуникация: ☆☆☆☆☆
- Надежность: ☆☆☆☆☆
- Профессионализм: ☆☆☆☆☆
- Скорость ответа: ☆☆☆☆☆
Текст отзыва: (необязательно, 50-1000 символов)
[Расскажите о вашем опыте взаимодействия...]
Рекомендации:
☐ Я рекомендую этого пользователя другим
[Опубликовать отзыв] [Отменить]
Примечание: Отзыв будет виден публично. Пожалуйста, будьте объективны
и соблюдайте правила платформы.
3. Модерация отзыва:
- Автоматическая проверка на запрещенные слова (мат, оскорбления)
- Проверка на спам (одинаковые отзывы, подозрительные паттерны)
- Если отзыв негативный (1-2 звезды), отправляется на ручную модерацию
- SLA модерации: 24 часа
4. Публикация:
- После модерации отзыв публикуется в профиле
- Пользователь получает уведомление о новом отзыве
- Рейтинг пересчитывается автоматически
2.2.3. Ответ на отзыв
Возможности:
1. Пользователь может ответить на отзыв один раз:
- Ответ публикуется под отзывом
- Максимум 500 символов
- Модерируется так же, как отзыв
2. Пользователь может оспорить отзыв:
- Если считает отзыв несправедливым или ложным
- Заполняет форму с обоснованием
- Модератор рассматривает спор (SLA: 48 часов)
- Если отзыв признан нарушающим правила, он удаляется
- Если отзыв справедлив, он остается, но добавляется пометка "Оспорен"
3. Пользователь может пожаловаться на отзыв:
- Причины: оскорбления, спам, ложная информация, нарушение правил
- Отзыв отправляется на проверку модератору
- Решение принимается в течение 24 часов
2.2.4. Защита от накрутки рейтинга
Антифрод-механизмы:
# Псевдокод системы антифрода для отзывов
class ReviewAntiFraud:
def check_review(review):
fraud_score = 0
flags = []
# 1. Проверка на массовые отзывы
recent_reviews = get_user_reviews(review.author_id, days=7)
if len(recent_reviews) > 10:
fraud_score += 30
flags.append("mass_reviews")
# 2. Проверка на отзывы без взаимодействия
interaction = check_interaction(review.author_id, review.target_id)
if not interaction or interaction.messages_count < 5:
fraud_score += 50
flags.append("no_interaction")
# 3. Проверка на подозрительные паттерны текста
similar_reviews = find_similar_reviews(review.text, threshold=0.8)
if len(similar_reviews) > 3:
fraud_score += 40
flags.append("duplicate_text")
# 4. Проверка на связь между аккаунтами
if check_accounts_connection(review.author_id, review.target_id):
fraud_score += 60
flags.append("connected_accounts")
# Связь определяется по: одинаковый IP, одинаковое устройство,
# паттерны активности, платежные данные
# 5. Проверка на новые аккаунты
author_age = get_account_age(review.author_id)
if author_age < 7: # дней
fraud_score += 20
flags.append("new_account")
# 6. Проверка на аномальную активность
if check_abnormal_activity(review.author_id):
fraud_score += 30
flags.append("abnormal_activity")
# Аномалии: только положительные/отрицательные отзывы,
# отзывы только одному пользователю, и т.д.
# 7. Проверка времени между взаимодействием и отзывом
if interaction:
time_diff = review.created_at - interaction.started_at
if time_diff < timedelta(hours=1):
fraud_score += 25
flags.append("too_fast")
# Решение
if fraud_score >= 100:
return {
"action": "block",
"reason": "High fraud probability",
"flags": flags,
"score": fraud_score
}
elif fraud_score >= 60:
return {
"action": "manual_review",
"reason": "Suspicious activity",
"flags": flags,
"score": fraud_score
}
else:
return {
"action": "approve",
"flags": flags,
"score": fraud_score
}
def check_accounts_connection(user1_id, user2_id):
# Проверка связи между аккаунтами
# 1. Одинаковый IP-адрес
user1_ips = get_user_ips(user1_id, days=30)
user2_ips = get_user_ips(user2_id, days=30)
if len(set(user1_ips) & set(user2_ips)) > 0:
return True
# 2. Одинаковое устройство (device fingerprint)
user1_devices = get_user_devices(user1_id)
user2_devices = get_user_devices(user2_id)
if len(set(user1_devices) & set(user2_devices)) > 0:
return True
# 3. Одинаковые платежные данные
user1_payments = get_payment_methods(user1_id)
user2_payments = get_payment_methods(user2_id)
if check_payment_similarity(user1_payments, user2_payments):
return True
# 4. Паттерны активности (одновременный онлайн)
if check_activity_correlation(user1_id, user2_id) > 0.8:
return True
return False
def check_abnormal_activity(user_id):
reviews = get_all_user_reviews(user_id)
# Только положительные отзывы (5 звезд)
if all(r.rating == 5 for r in reviews) and len(reviews) > 5:
return True
# Только отрицательные отзывы (1-2 звезды)
if all(r.rating <= 2 for r in reviews) and len(reviews) > 5:
return True
# Отзывы только одному пользователю или компании
target_ids = [r.target_id for r in reviews]
if len(set(target_ids)) == 1 and len(reviews) > 3:
return True
return False
Дополнительные меры защиты:
1. Ограничения на отзывы:
- Максимум 5 отзывов в день от одного пользователя
- Максимум 20 отзывов в месяц от одного пользователя
- Нельзя оставить более 1 отзыва одному пользователю
2. Верификация для оставления отзывов:
- Для Free-пользователей: только после верификации email и телефона
- Для Premium: без ограничений
3. Вес отзывов:
- Отзывы от верифицированных пользователей имеют вес 1.0
- Отзывы от неверифицированных пользователей имеют вес 0.5
- Отзывы от Premium-пользователей имеют вес 1.2
- Отзывы от новых аккаунтов (<30 дней) имеют вес 0.7
4. Мониторинг и алерты:
- Автоматические алерты модераторам при подозрительной активности
- Еженедельные отчеты по фроду
- Машинное обучение для улучшения детекции
5. Санкции за накрутку:
- Первое нарушение: предупреждение + удаление фейковых отзывов
- Второе нарушение: временная блокировка (30 дней)
- Третье нарушение: перманентная блокировка аккаунта
- Для организаторов накрутки: блокировка всех связанных аккаунтов
2.2.5. Техническая реализация
База данных:
CREATE TABLE reviews (
id UUID PRIMARY KEY,
author_id UUID REFERENCES users(id),
target_id UUID REFERENCES users(id),
interaction_id UUID REFERENCES interactions(id), -- связь с взаимодействием
-- Оценки
overall_rating DECIMAL(2,1) CHECK (overall_rating BETWEEN 1.0 AND 5.0),
communication_rating DECIMAL(2,1),
reliability_rating DECIMAL(2,1),
professionalism_rating DECIMAL(2,1),
response_time_rating DECIMAL(2,1),
-- Текст
review_text TEXT CHECK (char_length(review_text) BETWEEN 50 AND 1000),
is_recommended BOOLEAN DEFAULT false,
-- Статус
status VARCHAR(20) DEFAULT 'pending', -- 'pending', 'approved', 'rejected', 'disputed'
moderation_notes TEXT,
moderator_id UUID REFERENCES users(id),
-- Антифрод
fraud_score INTEGER DEFAULT 0,
fraud_flags JSONB, -- массив флагов подозрительной активности
-- Ответ на отзыв
response_text TEXT,
response_at TIMESTAMP,
-- Споры
is_disputed BOOLEAN DEFAULT false,
dispute_reason TEXT,
dispute_status VARCHAR(20), -- 'pending', 'resolved_kept', 'resolved_removed'
-- Временные метки
created_at TIMESTAMP DEFAULT NOW(),
published_at TIMESTAMP,
updated_at TIMESTAMP DEFAULT NOW(),
-- Ограничения
UNIQUE(author_id, target_id, interaction_id), -- один отзыв на взаимодействие
CHECK(author_id != target_id) -- нельзя оставить отзыв самому себе
);
-- Индексы
CREATE INDEX idx_reviews_target ON reviews(target_id, status, published_at DESC);
CREATE INDEX idx_reviews_author ON reviews(author_id);
CREATE INDEX idx_reviews_moderation ON reviews(status) WHERE status = 'pending';
CREATE INDEX idx_reviews_fraud ON reviews(fraud_score) WHERE fraud_score > 60;
-- Таблица для хранения агрегированных рейтингов
CREATE TABLE user_ratings (
user_id UUID PRIMARY KEY REFERENCES users(id),
role_type VARCHAR(20), -- 'investor' или 'entrepreneur'
-- Средние оценки
overall_rating DECIMAL(3,2) DEFAULT 0,
communication_rating DECIMAL(3,2) DEFAULT 0,
reliability_rating DECIMAL(3,2) DEFAULT 0,
professionalism_rating DECIMAL(3,2) DEFAULT 0,
response_time_rating DECIMAL(3,2) DEFAULT 0,
-- Статистика
total_reviews INTEGER DEFAULT 0,
positive_reviews INTEGER DEFAULT 0, -- 4-5 звезд
neutral_reviews INTEGER DEFAULT 0, -- 3 звезды
negative_reviews INTEGER DEFAULT 0, -- 1-2 звезды
-- Распределение оценок
rating_distribution JSONB, -- {1: 2, 2: 1, 3: 5, 4: 10, 5: 15}
-- Дополнительные факторы
verification_bonus DECIMAL(3,2) DEFAULT 0,
activity_bonus DECIMAL(3,2) DEFAULT 0,
updated_at TIMESTAMP DEFAULT NOW()
);
-- Триггер для автоматического пересчета рейтинга
CREATE OR REPLACE FUNCTION recalculate_rating()
RETURNS TRIGGER AS $$
BEGIN
-- Пересчитываем рейтинг целевого пользователя
UPDATE user_ratings
SET
overall_rating = (
SELECT AVG(overall_rating)
FROM reviews
WHERE target_id = NEW.target_id
AND status = 'approved'
),
total_reviews = (
SELECT COUNT(*)
FROM reviews
WHERE target_id = NEW.target_id
AND status = 'approved'
),
-- ... остальные поля
updated_at = NOW()
WHERE user_id = NEW.target_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_recalculate_rating
AFTER INSERT OR UPDATE ON reviews
FOR EACH ROW
WHEN (NEW.status = 'approved')
EXECUTE FUNCTION recalculate_rating();
API endpoints:
// API для работы с отзывами
// Получить отзывы пользователя
GET /api/v1/users/:userId/reviews
Query params:
- page: номер страницы (default: 1)
- limit: количество на странице (default: 10, max: 50)
- sort: сортировка (recent, rating_high, rating_low)
- filter: фильтр по рейтингу (positive, neutral, negative, all)
Response:
{
"data": [
{
"id": "uuid",
"author": {
"id": "uuid",
"name": "Иван Иванов",
"avatar": "url",
"isVerified": true
},
"ratings": {
"overall": 4.5,
"communication": 5.0,
"reliability": 4.0,
"professionalism": 4.5,
"responseTime": 5.0
},
"text": "Отличный инвестор, быстро отвечает...",
"isRecommended": true,
"createdAt": "2024-01-15T10:30:00Z",
"response": {
"text": "Спасибо за отзыв!",
"createdAt": "2024-01-16T09:00:00Z"
}
}
],
"meta": {
"total": 23,
"page": 1,
"totalPages": 3
},
"summary": {
"overallRating": 4.7,
"totalReviews": 23,
"ratingDistribution": {
"5": 15,
"4": 5,
"3": 2,
"2": 1,
"1": 0
}
}
}
// Создать отзыв
POST /api/v1/reviews
Body:
{
"targetUserId": "uuid",
"interactionId": "uuid",
"ratings": {
"overall": 4.5,
"communication": 5.0,
"reliability": 4.0,
"professionalism": 4.5,
"responseTime": 5.0
},
"text": "Отличный инвестор...",
"isRecommended": true
}
Response:
{
"id": "uuid",
"status": "pending", // или "approved" если прошел автомодерацию
"message": "Отзыв отправлен на модерацию"
}
// Ответить на отзыв
POST /api/v1/reviews/:reviewId/response
Body:
{
"text": "Спасибо за отзыв!"
}
// Оспорить отзыв
POST /api/v1/reviews/:reviewId/dispute
Body:
{
"reason": "Отзыв содержит ложную информацию...",
"evidence": ["url1", "url2"] // ссылки на доказательства
}
// Пожаловаться на отзыв
POST /api/v1/reviews/:reviewId/report
Body:
{
"reason": "spam", // или "offensive", "false_info", "other"
"details": "Подробности жалобы..."
}
Примеры use-cases:
UC-2.4: Инвестор оставляет отзыв предпринимателю
1. После 14 дней взаимодействия инвестор получает уведомление:
"Вы общались с [Имя предпринимателя]. Оставьте отзыв о сотрудничестве"
2. Нажимает на уведомление, открывается форма отзыва:
- Видит краткую информацию о взаимодействии (дата начала, количество сообщений)
- Заполняет оценки по категориям (звездочки)
- Пишет текстовый отзыв (опционально)
- Ставит галочку "Рекомендую" (опционально)
3. Нажимает "Опубликовать отзыв"
4. Система проверяет отзыв:
- Автоматическая проверка на спам и запрещенные слова
- Проверка антифрод-системой (fraud_score = 15, низкий риск)
- Отзыв одобряется автоматически
5. Отзыв публикуется мгновенно:
- Появляется в профиле предпринимателя
- Рейтинг пересчитывается автоматически
- Предприниматель получает уведомление
6. Предприниматель видит новый отзыв:
- Читает текст отзыва
- Может ответить на отзыв
- Может поблагодарить инвестора (кнопка "Спасибо")
UC-2.5: Предприниматель оспаривает негативный отзыв
1. Предприниматель получает уведомление о новом отзыве (2 звезды)
2. Читает отзыв и считает его несправедливым:
"Не вышли на связь после первого сообщения"
3. Нажимает "Оспорить отзыв"
4. Заполняет форму оспаривания:
"Инвестор написал одно сообщение и больше не отвечал на наши вопросы.
Мы отправили 5 сообщений с уточнениями, но не получили ответа.
Прикладываю скриншоты переписки."
5. Загружает доказательства (скриншоты переписки)
6. Отправляет на рассмотрение модератору
7. Модератор проверяет спор (в течение 48 часов):
- Читает оба аргумента
- Проверяет историю переписки в системе
- Видит, что действительно предприниматель отправил 5 сообщений
- Инвестор не отвечал после первого сообщения
8. Модератор принимает решение:
- Отзыв признается необоснованным
- Отзыв удаляется
- Инвестор получает предупреждение
- Рейтинг предпринимателя пересчитывается
9. Оба пользователя получают уведомления о решении
UC-2.6: Модератор проверяет подозрительные отзывы
1. Модератор заходит в админ-панель → "Отзывы на модерации"
2. Видит список отзывов, отсортированных по fraud_score:
- Красные (score > 80): высокий риск
- Желтые (score 60-80): средний риск
- Зеленые (score < 60): низкий риск (автоодобренные)
3. Открывает отзыв с высоким fraud_score (85):
Флаги:
- connected_accounts (оба пользователя заходили с одного IP)
- new_account (автор зарегистрирован 3 дня назад)
- no_interaction (всего 2 сообщения в переписке)
4. Проверяет детали:
- Смотрит историю IP-адресов обоих пользователей
- Проверяет переписку (действительно, всего 2 коротких сообщения)
- Смотрит другие отзывы автора (все 5 звезд, все одному пользователю)
5. Принимает решение: "Блокировать отзыв"
- Отзыв удаляется
- Автор получает предупреждение
- Оба аккаунта помечаются как подозрительные
- Если обнаружится еще одно нарушение, аккаунты будут заблокированы
6. Документирует решение в заметках модератора
7. Переходит к следующему отзыву в очереди
Приоритет: [HIGH] — v1.1
Обоснование решения:
- Доверие: Система отзывов критична для B2B-платформы
- Прозрачность: Многофакторный рейтинг дает полную картину
- Защита: Антифрод-система минимизирует накрутку
- Справедливость: Механизм оспаривания защищает от несправедливых отзывов
Риски, которые закрывает:
- Накрутка рейтинга (фейковые отзывы)
- Недоверие к платформе из-за необъективных оценок
- Репутационные атаки (массовые негативные отзывы)
- Злоупотребление системой отзывов
Влияние на метрики:
- Конверсия в отклики: +35% для пользователей с рейтингом >4.5
- Успешность сделок: +50% при высоких рейтингах обеих сторон
- Доверие к платформе: +40% (по опросам пользователей)
- Retention: +20% (пользователи с хорошим рейтингом более лояльны)
Связанные изменения:
- Раздел «Профиль» — добавить отображение рейтинга и отзывов
- Раздел «Поиск» — добавить фильтр и сортировку по рейтингу
- Раздел «Уведомления» — добавить алерты о новых отзывах
- Раздел «Админ-панель» — добавить интерфейс модерации отзывов
- Раздел «Аналитика» — добавить метрики по отзывам и рейтингам
—
БЛОК 3: МАТЧМЕЙКИНГ И РЕКОМЕНДАЦИИ
3.1. Алгоритм подбора и рекомендаций
Раздел ТЗ: Поиск и фильтрация
Суть проблемы:
Не описан алгоритм подбора релевантных объявлений для инвесторов и инвесторов для предпринимателей. Отсутствует персонализация.
Предлагаемое решение:
3.1.1. Алгоритм ранжирования объявлений для инвесторов
Формула релевантности:
Relevance Score =
Profile Match (40%) +
Engagement Signals (25%) +
Freshness (15%) +
Quality Score (10%) +
Premium Boost (10%)
Где:
Profile Match = взвешенная сумма совпадений:
- Отрасль (вес 30%)
- Сумма инвестиций (вес 25%)
- География (вес 20%)
- Стадия бизнеса (вес 15%)
- Форма участия (вес 10%)
Engagement Signals:
- CTR объявления (вес 40%)
- Количество откликов (вес 30%)
- Время на странице (вес 20%)
- Добавления в избранное (вес 10%)
Freshness:
- Новые объявления (<24ч): коэффициент 1.5
- Объявления 1-7 дней: коэффициент 1.2
- Объявления 7-30 дней: коэффициент 1.0
- Объявления >30 дней: коэффициент 0.8
Quality Score:
- Полнота профиля предпринимателя (30%)
- Верификация (30%)
- Рейтинг (20%)
- Качество объявления (20%)
Premium Boost:
- Продвигаемые объявления: +50% к итоговому скору
- Premium-предприниматели: +20% к итоговому скору
Псевдокод алгоритма:
def calculate_relevance_score(listing, investor_profile):
# 1. Profile Match (40%)
profile_match = 0
# Отрасль
if listing.industry in investor_profile.preferred_industries:
profile_match += 0.30 * 0.40
elif listing.industry in investor_profile.secondary_industries:
profile_match += 0.15 * 0.40
# Сумма инвестиций
investment_match = calculate_investment_match(
listing.investment_amount,
investor_profile.min_investment,
investor_profile.max_investment
)
profile_match += investment_match * 0.25 * 0.40
# География
if listing.location in investor_profile.preferred_locations:
profile_match += 0.20 * 0.40
elif listing.location.region in investor_profile.preferred_regions:
profile_match += 0.10 * 0.40
# Стадия бизнеса
if listing.business_stage in investor_profile.preferred_stages:
profile_match += 0.15 * 0.40
# Форма участия
if listing.participation_form in investor_profile.preferred_forms:
profile_match += 0.10 * 0.40
# 2. Engagement Signals (25%)
engagement = 0
# CTR (Click-Through Rate)
listing_ctr = listing.clicks / listing.impressions if listing.impressions > 0 else 0
avg_ctr = get_average_ctr()
ctr_score = min(listing_ctr / avg_ctr, 2.0) # cap at 2x average
engagement += ctr_score * 0.40 * 0.25
# Количество откликов
response_rate = listing.responses / listing.views if listing.views > 0 else 0
avg_response_rate = get_average_response_rate()
response_score = min(response_rate / avg_response_rate, 2.0)
engagement += response_score * 0.30 * 0.25
# Время на странице
avg_time = listing.total_time_spent / listing.views if listing.views > 0 else 0
platform_avg_time = get_platform_average_time()
time_score = min(avg_time / platform_avg_time, 2.0)
engagement += time_score * 0.20 * 0.25
# Добавления в избранное
favorite_rate = listing.favorites / listing.views if listing.views > 0 else 0
avg_favorite_rate = get_average_favorite_rate()
favorite_score = min(favorite_rate / avg_favorite_rate, 2.0)
engagement += favorite_score * 0.10 * 0.25
# 3. Freshness (15%)
age_hours = (now() - listing.created_at).total_seconds() / 3600
if age_hours < 24:
freshness = 1.5 * 0.15
elif age_hours < 168: # 7 days
freshness = 1.2 * 0.15
elif age_hours < 720: # 30 days
freshness = 1.0 * 0.15
else:
freshness = 0.8 * 0.15
# 4. Quality Score (10%)
quality = 0
# Полнота профиля
profile_completeness = calculate_profile_completeness(listing.entrepreneur)
quality += profile_completeness * 0.30 * 0.10
# Верификация
if listing.entrepreneur.verification_level == "premium":
quality += 1.0 * 0.30 * 0.10
elif listing.entrepreneur.verification_level == "verified":
quality += 0.7 * 0.30 * 0.10
# Рейтинг
rating_score = listing.entrepreneur.rating / 5.0
quality += rating_score * 0.20 * 0.10
# Качество объявления
listing_quality = calculate_listing_quality(listing)
quality += listing_quality * 0.20 * 0.10
# 5. Premium Boost (10%)
premium_boost = 0
if listing.is_promoted:
premium_boost += 0.50 * 0.10
if listing.entrepreneur.tier == "premium":
premium_boost += 0.20 * 0.10
# Итоговый скор
total_score = profile_match + engagement + freshness + quality + premium_boost
# Персональные корректировки
total_score = apply_personal_adjustments(total_score, listing, investor_profile)
return total_score
def calculate_investment_match(amount, min_inv, max_inv):
"""Рассчитывает совпадение по сумме инвестиций"""
if min_inv <= amount <= max_inv:
return 1.0
elif amount < min_inv:
# Чем ближе к минимуму, тем выше скор
ratio = amount / min_inv
return max(0, ratio)
else: # amount > max_inv
# Чем дальше от максимума, тем ниже скор
ratio = max_inv / amount
return max(0, ratio)
def apply_personal_adjustments(score, listing, investor_profile):
"""Применяет персональные корректировки на основе истории"""
# Если инвестор уже взаимодействовал с похожими объявлениями
similar_interactions = get_similar_interactions(investor_profile, listing)
if similar_interactions:
# Положительные взаимодействия (отклики, добавление в избранное)
positive_count = sum(1 for i in similar_interactions if i.is_positive)
# Отрицательные (скрытие, жалобы)
negative_count = sum(1 for i in similar_interactions if i.is_negative)
adjustment = (positive_count - negative_count) * 0.05
score *= (1 + adjustment)
# Если инвестор скрывал похожие объявления
if check_hidden_similar(investor_profile, listing):
score *= 0.5
# Если инвестор добавлял в избранное похожие объявления
if check_favorited_similar(investor_profile, listing):
score *= 1.3
return score
def calculate_listing_quality(listing):
"""Оценивает качество объявления"""
quality = 0
# Наличие фото/видео
if listing.has_photos:
quality += 0.2
if listing.has_video:
quality += 0.1
# Длина описания
description_length = len(listing.description)
if description_length >= 500:
quality += 0.2
elif description_length >= 200:
quality += 0.1
# Наличие финансовых показателей
if listing.has_financial_data:
quality += 0.2
# Наличие бизнес-плана
if listing.has_business_plan:
quality += 0.15
# Наличие презентации
if listing.has_presentation:
quality += 0.15
return min(quality, 1.0)
3.1.2. Персонализация рекомендаций
Машинное обучение для улучшения рекомендаций:
# Collaborative Filtering для рекомендаций
class RecommendationEngine:
def __init__(self):
self.user_item_matrix = None # матрица взаимодействий пользователь-объявление
self.model = None # модель машинного обучения
def train_model(self):
"""Обучает модель на исторических данных"""
# 1. Собираем данные о взаимодействиях
interactions = get_all_interactions()
# Формат: user_id, listing_id, interaction_type, timestamp
# 2. Создаем матрицу пользователь-объявление
self.user_item_matrix = create_sparse_matrix(interactions)
# 3. Обучаем модель (например, Matrix Factorization)
self.model = AlternatingLeastSquares(
factors=50,
regularization=0.01,
iterations=15
)
self.model.fit(self.user_item_matrix)
def get_recommendations(self, user_id, n=20):
"""Получает персонализированные рекомендации для пользователя"""
# 1. Получаем рекомендации от модели
ml_recommendations = self.model.recommend(
user_id,
self.user_item_matrix[user_id],
N=n*2 # берем в 2 раза больше для дальнейшей фильтрации
)
# 2. Получаем профиль пользователя
user_profile = get_user_profile(user_id)
# 3. Фильтруем по базовым критериям
filtered = []
for listing_id, score in ml_recommendations:
listing = get_listing(listing_id)
# Пропускаем, если не соответствует базовым критериям
if not matches_basic_criteria(listing, user_profile):
continue
# Пропускаем, если пользователь уже взаимодействовал
if has_interacted(user_id, listing_id):
continue
# Пропускаем скрытые объявления
if is_hidden_by_user(user_id, listing_id):
continue
filtered.append((listing_id, score))
# 4. Ре-ранжируем с учетом дополнительных факторов
reranked = []
for listing_id, ml_score in filtered[:n*1.5]:
listing = get_listing(listing_id)
# Рассчитываем итоговый скор
relevance_score = calculate_relevance_score(listing, user_profile)
# Комбинируем ML-скор и скор релевантности
final_score = 0.6 * ml_score + 0.4 * relevance_score
reranked.append((listing_id, final_score))
# 5. Сортируем и возвращаем топ-N
reranked.sort(key=lambda x: x[1], reverse=True)
return reranked[:n]
def get_similar_listings(self, listing_id, n=10):
"""Получает похожие объявления (для блока "Похожие предложения")"""
# Используем item-to-item similarity
similar = self.model.similar_items(listing_id, N=n+1)
# Исключаем само объявление
return [lid for lid, score in similar if lid != listing_id][:n]
def update_online(self, user_id, listing_id, interaction_type):
"""Онлайн-обновление модели при новом взаимодействии"""
# Добавляем взаимодействие в матрицу
self.user_item_matrix[user_id, listing_id] = get_interaction_weight(interaction_type)
# Частичное переобучение модели (incremental update)
self.model.partial_fit(user_id, listing_id)
def get_interaction_weight(interaction_type):
"""Возвращает вес взаимодействия для модели"""
weights = {
"view": 1.0,
"click": 2.0,
"favorite": 5.0,
"response": 10.0,
"contact_request": 15.0,
"hide": -5.0,
"report": -10.0
}
return weights.get(interaction_type, 0)
Гибридный подход (Content-Based + Collaborative Filtering):
def get_hybrid_recommendations(user_id, n=20):
"""Гибридные рекомендации, комбинирующие разные подходы"""
# 1. Collaborative Filtering (50%)
cf_recommendations = recommendation_engine.get_recommendations(user_id, n=n)
# 2. Content-Based (30%)
user_profile = get_user_profile(user_id)
cb_recommendations = get_content_based_recommendations(user_profile, n=n)
# 3. Trending (10%)
trending = get_trending_listings(n=n//2)
# 4. Diversity (10%)
diverse = get_diverse_recommendations(user_profile, n=n//2)
# Комбинируем рекомендации
combined = {}
for listing_id, score in cf_recommendations:
combined[listing_id] = combined.get(listing_id, 0) + score * 0.50
for listing_id, score in cb_recommendations:
combined[listing_id] = combined.get(listing_id, 0) + score * 0.30
for listing_id, score in trending:
combined[listing_id] = combined.get(listing_id, 0) + score * 0.10
for listing_id, score in diverse:
combined[listing_id] = combined.get(listing_id, 0) + score * 0.10
# Сортируем и возвращаем топ-N
sorted_recommendations = sorted(
combined.items(),
key=lambda x: x[1],
reverse=True
)
return sorted_recommendations[:n]
def get_content_based_recommendations(user_profile, n=20):
"""Рекомендации на основе профиля пользователя"""
# Получаем все активные объявления
all_listings = get_active_listings()
# Рассчитываем релевантность для каждого
scored_listings = []
for listing in all_listings:
score = calculate_relevance_score(listing, user_profile)
scored_listings.append((listing.id, score))
# Сортируем и возвращаем топ-N
scored_listings.sort(key=lambda x: x[1], reverse=True)
return scored_listings[:n]
def get_trending_listings(n=10):
"""Получает трендовые объявления (популярные за последние 24 часа)"""
# Получаем объявления с высоким engagement за последние 24 часа
trending = db.query("""
SELECT
listing_id,
COUNT(DISTINCT user_id) as unique_views,
SUM(CASE WHEN interaction_type = 'favorite' THEN 5
WHEN interaction_type = 'response' THEN 10
WHEN interaction_type = 'click' THEN 2
ELSE 1 END) as engagement_score
FROM interactions
WHERE created_at >= NOW() - INTERVAL '24 hours'
GROUP BY listing_id
ORDER BY engagement_score DESC
LIMIT :n
""", n=n)
return [(row['listing_id'], row['engagement_score']) for row in trending]
def get_diverse_recommendations(user_profile, n=10):
"""Получает разнообразные рекомендации (из разных отраслей/регионов)"""
# Получаем отрасли, с которыми пользователь еще не взаимодействовал
unexplored_industries = get_unexplored_industries(user_profile)
recommendations = []
per_industry = max(1, n // len(unexplored_industries))
for industry in unexplored_industries:
# Получаем лучшие объявления из этой отрасли
top_in_industry = get_top_listings_in_industry(industry, per_industry)
recommendations.extend(top_in_industry)
return recommendations[:n]
3.1.3. Реализация на фронтенде
Блоки рекомендаций:
// Компонент главной страницы для инвестора
function InvestorDashboard() {
return (
<div className="dashboard">
{/* 1. Персонализированные рекомендации */}
<RecommendationsBlock
title="Рекомендуем для вас"
subtitle="На основе ваших предпочтений и активности"
endpoint="/api/v1/recommendations/personalized"
icon="✨"
/>
{/* 2. Новые объявления */}
<RecommendationsBlock
title="Новые предложения"
subtitle="Опубликованы за последние 24 часа"
endpoint="/api/v1/listings/recent"
icon="🆕"
/>
{/* 3. Трендовые */}
<RecommendationsBlock
title="Популярные сейчас"
subtitle="Объявления с высоким интересом"
endpoint="/api/v1/listings/trending"
icon="🔥"
/>
{/* 4. Из избранных отраслей */}
<RecommendationsBlock
title="IT и технологии"
subtitle="Ваша любимая отрасль"
endpoint="/api/v1/listings/by-industry/it"
icon="💻"
/>
{/* 5. Похожие на избранные */}
<RecommendationsBlock
title="Похожие на избранные"
subtitle="На основе объявлений в вашем избранном"
endpoint="/api/v1/recommendations/similar-to-favorites"
icon="⭐"
/>
{/* 6. Разнообразие */}
<RecommendationsBlock
title="Откройте новое"
subtitle="Объявления из других отраслей"
endpoint="/api/v1/recommendations/diverse"
icon="🌍"
/>
</div>
);
}
// Компонент блока рекомендаций
function RecommendationsBlock({ title, subtitle, endpoint, icon }) {
const [listings, setListings] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchRecommendations();
}, [endpoint]);
const fetchRecommendations = async () => {
try {
const response = await api.get(endpoint);
setListings(response.data);
} catch (error) {
console.error('Error fetching recommendations:', error);
} finally {
setLoading(false);
}
};
if (loading) {
return <SkeletonLoader />;
}
return (
<div className="recommendations-block">
<div className="block-header">
<span className="icon">{icon}</span>
<div>
<h2>{title}</h2>
<p className="subtitle">{subtitle}</p>
</div>
<a href={`/listings?source=${endpoint}`} className="view-all">
Смотреть все →
</a>
</div>
<div className="listings-carousel">
{listings.map(listing => (
<ListingCard
key={listing.id}
listing={listing}
onView={() => trackView(listing.id)}
onFavorite={() => trackFavorite(listing.id)}
/>
))}
</div>
</div>
);
}
// Отслеживание взаимодействий для улучшения рекомендаций
function trackView(listingId) {
api.post('/api/v1/interactions', {
listingId,
type: 'view',
timestamp: new Date().toISOString()
});
// Онлайн-обновление модели рекомендаций
recommendation_engine.update_online(currentUserId, listingId, 'view');
}
function trackFavorite(listingId) {
api.post('/api/v1/interactions', {
listingId,
type: 'favorite',
timestamp: new Date().toISOString()
});
recommendation_engine.update_online(currentUserId, listingId, 'favorite');
}
Примеры use-cases:
UC-3.1: Инвестор заходит на главную страницу
1. Пользователь авторизуется и попадает на дашборд
2. Система загружает персонализированные рекомендации:
- Запрос к recommendation engine
- Получение топ-20 релевантных объявлений
- Группировка по блокам (персональные, новые, трендовые, etc.)
3. Отображаются блоки рекомендаций:
✨ Рекомендуем для вас
[Карточка 1] [Карточка 2] [Карточка 3] [Карточка 4] [Смотреть все →]
🆕 Новые предложения
[Карточка 5] [Карточка 6] [Карточка 7] [Карточка 8] [Смотреть все →]
🔥 Популярные сейчас
[Карточка 9] [Карточка 10] [Карточка 11] [Карточка 12] [Смотреть все →]
4. Пользователь взаимодействует с объявлениями:
- Кликает на карточку → открывается детальная страница
- Добавляет в избранное → система учитывает для будущих рекомендаций
- Скрывает объявление → система исключает похожие из рекомендаций
5. Система отслеживает взаимодействия:
- Логирует просмотры, клики, избранное
- Обновляет модель рекомендаций в реальном времени
- Корректирует будущие рекомендации на основе поведения
UC-3.2: Инвестор просматривает объявление
1. Пользователь открывает детальную страницу объявления
2. Внизу страницы отображается блок "Похожие предложения":
📊 Похожие предложения
[Карточка 1] [Карточка 2] [Карточка 3] [Карточка 4]
Критерии подбора:
- Та же отрасль (IT)
- Похожая сумма инвестиций (±30%)
- Та же стадия бизнеса (рост)
- Похожая форма участия (доля в бизнесе)
3. Пользователь кликает на одно из похожих объявлений
4. Система логирует:
- "Пользователь перешел с объявления A на объявление B"
- Увеличивает связь между этими объявлениями в модели
- Учитывает для будущих рекомендаций
5. Если пользователь откликается на похожее объявление:
- Система понимает, что рекомендация была успешной
- Увеличивает вес этого типа рекомендаций для пользователя
UC-3.3: Предприниматель получает рекомендации инвесторов
1. Предприниматель публикует объявление
2. Система анализирует объявление:
- Извлекает ключевые параметры (отрасль, сумма, стадия, etc.)
- Ищет инвесторов с подходящими предпочтениями
- Ранжирует инвесторов по релевантности
3. Предприниматель видит блок "Подходящие инвесторы":
💼 Инвесторы, которым может быть интересно ваше предложение
[Карточка инвестора 1]
Иван Иванов
★★★★★ 4.9 (15 отзывов)
✓ Verified
Интересы: IT, E-commerce
Сумма инвестиций: от 5 до 50 млн ₽
[Пригласить к диалогу]
[Карточка инвестора 2]
...
4. Предприниматель может:
- Просмотреть профиль инвестора
- Отправить приглашение к диалогу (если Premium)
- Добавить в избранное для отслеживания
5. Система отправляет уведомления инвесторам:
"Новое объявление, которое может вас заинтересовать"
- Персонализированное сообщение
- Ссылка на объявление
- Краткое описание, почему это релевантно
Приоритет: [HIGH] — v1.1 (базовый алгоритм), [MEDIUM] — v1.2 (ML-рекомендации)
Обоснование решения:
- Релевантность: Персонализированные рекомендации увеличивают вероятность успешного матчинга
- Engagement: Пользователи проводят больше времени на платформе, просматривая релевантный контент
- Конверсия: Правильные рекомендации увеличивают количество откликов и сделок
- Retention: Пользователи возвращаются, зная, что найдут подходящие предложения
Риски, которые закрывает:
- Низкая конверсия из-за нерелевантных объявлений
- Информационная перегрузка (слишком много объявлений)
- Холодный старт для новых пользователей
- Эхо-камера (пользователь видит только одно и то же)
Влияние на метрики:
- CTR (Click-Through Rate): +40-60% для персонализированных рекомендаций
- Конверсия в отклики: +30-50%
- Время на платформе: +35%
- Retention (D7): +25%
- Успешность матчинга: +45%
Связанные изменения:
- Раздел «Главная страница» — добавить блоки рекомендаций
- Раздел «Детальная страница объявления» — добавить «Похожие предложения»
- Раздел «Поиск» — добавить персонализированную сортировку
- Раздел «Аналитика» — добавить метрики по рекомендациям
- Раздел «Инфраструктура» — добавить ML-сервис для рекомендаций
—




