Перейти к основному содержимому

Мультикарточный портфель — аудит ВСЕХ SKU клиента

Как провести полный аудит портфеля продавца, рассчитать потенциал ROI для каждой карточки и сформировать пакетное предложение, которое в 4-6 раз выше обычного чека за одну карточку.

Проблема

У большинства продавцов на WB/Ozon десятки или сотни SKU. Когда такой селлер обращается в Fotofactor, он просит обновить одну карточку. Какую? Ту, которая ему нравится больше всего, или ту, что и так хорошо продаётся. Но это ошибка приоритизации:

  • «Обновите мой бестселлер» — у бестселлера и так хорошие фото, 12 картинок и видео. Улучшение даст +5% конверсии
  • «Этот товар мне нравится» — но он приносит 3% выручки. Даже идеальный контент не изменит картину
  • «Самый дорогой товар» — высокая цена != высокий потенциал ROI на контент

Максимальный ROI от контент-обновления получает карточка с наибольшим разрывом между текущим контентом и потенциалом продаж. Это карточка с высокой выручкой, но плохими фото. Или с огромной упущенной выручкой (lost_profit), которую можно вернуть хорошим контентом.

Без анализа всего портфеля селлер тратит 80 000 рублей на обновление не той карточки. А карточка с потенциалом ROI x10 лежит забытая на 5-м экране кабинета.

Самая дорогая ошибка селлера

73% селлеров обновляют контент «по ощущениям» — выбирают карточку интуитивно. В результате:

  • 40% бюджета уходит на карточки, которые и так работают нормально (ROI < 1.5x)
  • Карточки с потенциалом ROI 5-10x остаются без внимания годами
  • Селлер не видит результата → разочаровывается в контент-услугах → уходит

Мультикарточный аудит решает эту проблему: мы показываем где именно каждый рубль даст максимальную отдачу.

Пайплайн данных

graph LR
A["MPStats API\nВсе SKU продавца"] --> B["Скоринг каждого SKU\ncontent_gap x revenue"]
B --> C["Ранжирование\nпо потенциалу ROI"]
C --> D["Pareto-анализ\n20% SKU = 80% эффекта"]
D --> E["Портфельная матрица\nс приоритетами"]
E --> F["Пакетное предложение\nТОП-10 с дисконтом"]

Источники данных

ЭндпоинтМетодКлючевые поляЗачем
POST /wb/get/categoryPOSTid, revenue, sales, picscount, hasvideo, lost_profit, rating, comments, category_positionПолучить ВСЕ товары конкретного продавца (фильтр по supplier_id)
GET /wb/get/item/{sku}/by_keywordsGETkeyword, position, frequencySEO-профиль для ТОП-SKU (опционально, для детального анализа)
Как получить все SKU продавца

MPStats не имеет отдельного эндпоинта «все товары продавца». Вместо этого используем POST /wb/get/category с фильтром filterModel по supplier_id. Запрос возвращает все товары в выбранной категории для конкретного поставщика. Если селлер работает в нескольких категориях — делаем несколько запросов и объединяем результаты.

Альтернатива: если у селлера публичный магазин на WB — можно собрать все SKU через страницу продавца и затем обогатить данными из MPStats.

Ключевые поля для скоринга

ПолеТипДля чего
supplier_idstringФильтр — выбрать только товары конкретного продавца
revenuenumberТекущая выручка — вес приоритета (больше выручка = важнее карточка)
lost_profitnumberУпущенная выручка — индикатор потенциала роста
picscountnumberКоличество фото — основной gap-индикатор контента
hasvideobooleanНаличие видео — сильный gap-индикатор
category_positionnumberПозиция в категории — room for improvement
ratingnumberРейтинг — коррелирует с качеством контента
commentsnumberКоличество отзывов — социальное доказательство

Анализ

Шаг 1. Загрузка всех SKU продавца

import httpx
from urllib.parse import quote

MPSTATS_TOKEN = "YOUR_TOKEN"
BASE_URL = "https://mpstats.io/api"
HEADERS = {
"X-Mpstats-TOKEN": MPSTATS_TOKEN,
"Content-Type": "application/json",
}


def fetch_seller_products(
category: str, supplier_id: str, d1: str, d2: str
) -> list:
"""
Загружает все товары конкретного продавца в категории.
Если продавец работает в нескольких категориях —
вызвать для каждой и объединить результаты.
"""
all_products = []
start_row = 0
batch_size = 100

while True:
resp = httpx.post(
f"{BASE_URL}/wb/get/category",
headers=HEADERS,
json={
"path": quote(category, safe=""),
"d1": d1,
"d2": d2,
"startRow": start_row,
"endRow": start_row + batch_size,
"filterModel": {
"supplier_id": {
"filterType": "text",
"type": "equals",
"filter": supplier_id,
}
},
"sortModel": [{"colId": "revenue", "sort": "desc"}],
},
timeout=30,
)
resp.raise_for_status()
data = resp.json().get("data", [])

if not data:
break

all_products.extend(data)
start_row += batch_size

if len(data) < batch_size:
break

return all_products


# --- Загрузка ---
categories = [
"Красота/Ароматы для дома",
"Красота/Аксессуары для ароматерапии",
]

supplier_id = "ООО Ароматный мир" # Имя поставщика из MPStats
all_skus = []

for cat in categories:
products = fetch_seller_products(cat, supplier_id, "2025-01-01", "2025-01-31")
all_skus.extend(products)
print(f"Категория '{cat}': {len(products)} SKU")

print(f"\nВсего SKU продавца: {len(all_skus)}")

Шаг 2. Скоринг каждого SKU по потенциалу ROI

Для каждой карточки рассчитываем improvement_potential — комплексный балл, учитывающий:

  • Насколько плох текущий контент (content_gap)
  • Насколько велика выручка (revenue_weight)
  • Насколько велика упущенная выручка (lost_profit_ratio)
def calculate_portfolio_scores(products: list, benchmark_photos: int = 12) -> list:
"""
Рассчитывает потенциал ROI для каждого SKU.

Формула:
content_gap = (benchmark_photos - picscount) + (1 - hasvideo) * 20
revenue_weight = revenue / max_revenue
lost_profit_ratio = lost_profit / (revenue + 1)
improvement_potential = content_gap * revenue_weight * (1 + lost_profit_ratio)

Чем выше improvement_potential — тем больше ROI от обновления контента.
"""
if not products:
return []

max_revenue = max(p.get("revenue", 0) for p in products) or 1

scored = []
for p in products:
revenue = p.get("revenue", 0)
lost_profit = p.get("lost_profit", 0)
picscount = p.get("picscount", 0)
hasvideo = p.get("hasvideo", 0)
rating = p.get("rating", 0)
position = p.get("category_position", 999)

# Контент-разрыв: сколько "единиц контента" не хватает
photo_gap = max(0, benchmark_photos - picscount)
video_gap = (1 - hasvideo) * 20 # Отсутствие видео = 20 баллов gap
content_gap = photo_gap + video_gap

# Весовой коэффициент выручки (0..1)
revenue_weight = revenue / max_revenue

# Коэффициент упущенной выручки
lost_profit_ratio = lost_profit / (revenue + 1)

# Итоговый потенциал
potential = round(
content_gap * revenue_weight * (1 + lost_profit_ratio), 1
)

# Классификация приоритета
if potential >= 60:
priority = "urgent"
priority_label = "Срочно"
emoji = "🔴"
elif potential >= 30:
priority = "high"
priority_label = "Высокий"
emoji = "🟡"
elif potential >= 10:
priority = "medium"
priority_label = "Средний"
emoji = "🟢"
else:
priority = "low"
priority_label = "Низкий"
emoji = "⚪"

scored.append({
"sku": p["id"],
"name": p.get("name", ""),
"revenue": revenue,
"lost_profit": lost_profit,
"picscount": picscount,
"hasvideo": hasvideo,
"rating": rating,
"position": position,
"content_gap": content_gap,
"revenue_weight": round(revenue_weight, 3),
"lost_profit_ratio": round(lost_profit_ratio, 3),
"improvement_potential": potential,
"priority": priority,
"priority_label": priority_label,
"emoji": emoji,
})

# Сортировка: наибольший потенциал наверху
scored.sort(key=lambda x: x["improvement_potential"], reverse=True)

# Нумерация
for i, item in enumerate(scored, 1):
item["rank"] = i

return scored


# --- Скоринг ---
portfolio = calculate_portfolio_scores(all_skus)

print("=== ТОП-10 SKU ПО ПОТЕНЦИАЛУ ROI ===\n")
for item in portfolio[:10]:
print(
f" #{item['rank']} {item['emoji']} SKU {item['sku']}"
f" | Выручка: {item['revenue']:,.0f} руб"
f" | Фото: {item['picscount']}"
f" | Видео: {'Да' if item['hasvideo'] else 'Нет'}"
f" | Lost Profit: {item['lost_profit']:,.0f} руб"
f" | Потенциал: {item['improvement_potential']}"
)

Шаг 3. Pareto-анализ портфеля

def pareto_analysis(scored_portfolio: list) -> dict:
"""
Pareto-анализ: какие 20% SKU дают 80% потенциала.
Помогает определить минимальный пакет обновлений
для максимального эффекта.
"""
total_potential = sum(s["improvement_potential"] for s in scored_portfolio)
total_revenue = sum(s["revenue"] for s in scored_portfolio)

cumulative = 0
pareto_threshold = total_potential * 0.8
pareto_skus = []

for item in scored_portfolio:
cumulative += item["improvement_potential"]
pareto_skus.append(item)
if cumulative >= pareto_threshold:
break

return {
"total_skus": len(scored_portfolio),
"pareto_skus": len(pareto_skus),
"pareto_pct": round(len(pareto_skus) / len(scored_portfolio) * 100, 1),
"total_potential": round(total_potential, 1),
"pareto_potential": round(cumulative, 1),
"total_revenue": total_revenue,
"pareto_revenue": sum(s["revenue"] for s in pareto_skus),
"items": pareto_skus,
}


pareto = pareto_analysis(portfolio)

print(f"=== PARETO-АНАЛИЗ ===")
print(f"Всего SKU: {pareto['total_skus']}")
print(f"SKU для обновления (80% эффекта): {pareto['pareto_skus']} "
f"({pareto['pareto_pct']}%)")
print(f"Суммарная выручка этих SKU: {pareto['pareto_revenue']:,.0f} руб")

Портфельная матрица

Результат анализа для реального клиента (производитель ароматов для дома, 47 SKU):

#SKUНазваниеВыручкаФотоВидеоLost ProfitПотенциалПриоритет
1183749201Набор аромат. свечей «Лаванда»1.2M4Нет300K92🔴 Срочно
2183749215Автоосвежитель «Хвоя»800K6Нет150K71🟡 Высокий
3183749228Аромадиффузор палочки650K5Нет180K65🟡 Высокий
4183749234Саше для белья420K3Нет90K58🟡 Высокий
5183749240Набор эфирных масел380K7Нет70K42🟡 Высокий
6183749251Аромалампа керамика290K8Нет45K31🟡 Высокий
7183749267Спрей для дома «Цитрус»210K6Нет35K24🟢 Средний
8183749273Свеча соевая «Ваниль»180K9Нет20K18🟢 Средний
9183749285Ароматизатор в машину150K5Нет25K16🟢 Средний
10183749290Диффузор «Морской бриз»2.1M12Да50K35🟢 Низкий
......... остальные 37 SKU ...............<10⚪ Низкий

Pareto-результат: 6 из 47 SKU (13%) покрывают 80% потенциала ROI.

Дерево решений

graph TD
A["Получить все SKU\nпродавца через MPStats"] --> B["Рассчитать content_gap\nдля каждого SKU"]
B --> C["Рассчитать revenue_weight\n+ lost_profit_ratio"]
C --> D["improvement_potential =\ncontent_gap x revenue_weight\nx (1 + lost_profit_ratio)"]
D --> E{"Потенциал SKU?"}
E -->|">= 60 (Срочно)"| F["Фаза 1\nНемедленное обновление\nПолная пересъёмка"]
E -->|"30-59 (Высокий)"| G["Фаза 2\nОбновление в течение\n1-2 месяцев"]
E -->|"10-29 (Средний)"| H["Фаза 3\nОбновление при\nналичии ресурсов"]
E -->|"< 10 (Низкий)"| I["Пропустить\nROI не оправдывает\nзатрат"]
F --> J["Пакетное предложение\nТОП-10 со скидкой -30%"]
G --> J
H --> K["Отдельное предложение\nпо запросу"]

Действие Fotofactor

Формирование пакетного предложения

На основе портфельного аудита Fotofactor предлагает клиенту пакетное обновление вместо одиночных карточек:

  1. Портфельный отчёт — PDF на 10-15 страниц с полной матрицей SKU, потенциалом ROI и Pareto-анализом
  2. Пакет ТОП-10 — обновление 10 приоритетных карточек с дисконтом за объём
  3. Фазовый подход — разбивка на 3 фазы для управления бюджетом
Стратегия пакетного ценообразования

Пакет «Портфель ТОП-10» — обновление 10 приоритетных карточек:

Поштучная цена: 10 x 80 000 рублей = 800 000 рублей

Пакетная цена: 560 000 рублей (скидка 30%)

Разбивка по фазам:

  • Фаза 1 (ТОП-5, срочные): 5 карточек = 250 000 рублей — старт немедленно
  • Фаза 2 (следующие 5): 5 карточек = 220 000 рублей — через 3-4 недели
  • Фаза 3 (остальные по запросу): индивидуально, 70 000 рублей/карточка

Почему скидка 30% выгодна Fotofactor:

  • Объём: 10 карточек одного клиента дешевле в продакшне, чем 10 клиентов по 1 карточке (единый стиль, один бренд, одна съёмка)
  • Cash flow: предоплата 50% = 280 000 рублей за раз
  • Retention: клиент «привязан» на 2-3 месяца проекта
  • Upsell: после фазы 1 показываем ROI — фаза 2 утверждается автоматически

Предпродажная коммуникация

Шаблон outreach для продавца:

Добрый день! Провели портфельный аудит ваших карточек на WB.

📊 Результаты анализа 47 SKU:
- 6 карточек с критическим разрывом контента (потенциал ROI > x5)
- Суммарная упущенная выручка: ~890 000 руб/мес
- У 38 из 47 карточек нет видео (81%)
- Среднее количество фото: 6.2 (benchmark категории: 12)

🔥 ТОП-3 по приоритету:
1. "Набор аромат. свечей" — 1.2M выручка, всего 4 фото, нет видео
2. "Автоосвежитель Хвоя" — 800K выручка, 6 фото, нет видео
3. "Аромадиффузор палочки" — 650K выручка, 5 фото, нет видео

💡 Предлагаем: пакетное обновление ТОП-10 карточек
— Скидка 30% при пакетном заказе (560K вместо 800K)
— Фазовый подход: сначала 5 самых приоритетных
— После Фазы 1 — покажем ROI и согласуем Фазу 2

📅 Подготовили полный портфельный отчёт с матрицей приоритетов — отправить?

Экономическое обоснование для клиента

Для каждой карточки из ТОП-10 рассчитываем прогнозируемый ROI:

def project_roi(scored_item: dict, update_cost: float = 56_000) -> dict:
"""
Прогнозируемый ROI от обновления контента карточки.

update_cost: стоимость обновления одной карточки в пакете
(после скидки 30%: 80K * 0.7 = 56K)

Допущения:
- Улучшение контента возвращает 30-50% lost_profit
- Новое видео добавляет +10-15% к конверсии
- Оптимальное кол-во фото: +5-8% к конверсии за каждое
"""
lost_profit = scored_item["lost_profit"]
has_video = scored_item["hasvideo"]
picscount = scored_item["picscount"]

# Прогноз дополнительной выручки в месяц
recovered_lost = lost_profit * 0.35 # 35% от lost_profit
video_uplift = scored_item["revenue"] * 0.12 if not has_video else 0
photo_uplift = scored_item["revenue"] * 0.05 * max(0, 12 - picscount) / 12

monthly_uplift = recovered_lost + video_uplift + photo_uplift
annual_uplift = monthly_uplift * 12

roi = annual_uplift / update_cost if update_cost > 0 else 0

return {
"sku": scored_item["sku"],
"update_cost": update_cost,
"monthly_uplift": round(monthly_uplift),
"annual_uplift": round(annual_uplift),
"roi_x": round(roi, 1),
"payback_months": round(update_cost / monthly_uplift, 1) if monthly_uplift > 0 else 99,
}


# --- Прогноз ROI для ТОП-10 ---
print("=== ПРОГНОЗ ROI ПО ПАКЕТУ ТОП-10 ===\n")
total_cost = 0
total_annual_uplift = 0

for item in portfolio[:10]:
roi = project_roi(item)
total_cost += roi["update_cost"]
total_annual_uplift += roi["annual_uplift"]
print(
f" SKU {roi['sku']}: "
f"затраты {roi['update_cost']:,.0f} руб → "
f"+{roi['monthly_uplift']:,.0f} руб/мес → "
f"ROI {roi['roi_x']}x за год, "
f"окупаемость {roi['payback_months']} мес"
)

print(f"\nИТОГО ПАКЕТ:")
print(f" Затраты: {total_cost:,.0f} руб")
print(f" Доп. выручка: +{total_annual_uplift:,.0f} руб/год")
print(f" ROI пакета: {total_annual_uplift / total_cost:.1f}x")

Результат для клиента

Что получает клиент

  • Data-driven приоритизация — не «обновите любимую карточку», а «вот 6 карточек, где каждый вложенный рубль даёт максимальную отдачу»
  • Экономия на пакете — 30% скидка при обновлении ТОП-10 вместо поштучного заказа
  • Прогноз ROI по каждому SKU — конкретные цифры: «вложили 56 000 рублей → получили +105 000 рублей/мес»
  • Фазовый подход — не нужно тратить 560 000 рублей сразу, можно начать с Фазы 1 за 250 000 рублей и увидеть результат
  • Портфельная стратегия — план обновления всех карточек на 3-6 месяцев вперёд

Эффект пакетного подхода

МетрикаОдна карточкаПакет ТОП-10Разница
Стоимость для клиента80 000 руб560 000 руб (56 000/шт)Скидка 30%
Количество обновлений1 SKU10 SKUx10 охват
Прогнозируемый uplift+50 000-100 000 руб/мес+400 000-800 000 руб/месx5-10 эффект
Окупаемость1-2 месяца1-2 месяцаОдинаково
Привязка клиентаРазовый заказ2-3 месяца проектRetention

Экономика для Fotofactor

МетрикаБез портфельного аудитаС портфельным аудитом
Средний чек80 000 руб (1 карточка)300 000-560 000 руб (пакет)
Конверсия из лида20-30%50-65% (аудит продаёт сам себя)
LTV клиента80 000-160 000 руб700 000-1 200 000 руб (3 фазы + мониторинг)
Маржинальность40-50%55-65% (экономия на продакшне пакета)
Загрузка студииНепредсказуемаяПлановая (проект на 2-3 месяца)
Методология приоритизации: почему формула работает

Формула improvement_potential = content_gap x revenue_weight x (1 + lost_profit_ratio) учитывает три фактора:

  1. content_gap — насколько плох текущий контент. Карточка с 4 фото и без видео получает высокий gap. Карточка с 12 фото и видео — низкий.
  2. revenue_weight — нормализованная выручка. Карточка с 1.2M руб/мес важнее, чем карточка с 50K руб/мес, даже если у обеих одинаковый content_gap.
  3. lost_profit_ratio — доля упущенной выручки. Если MPStats показывает lost_profit = 300K при revenue = 1.2M, это 25% упущенных продаж — серьёзный сигнал.

Произведение трёх факторов выделяет карточки, где одновременно плохой контент, высокая выручка и большой потенциал роста. Это именно те карточки, где обновление контента даст максимальный ROI.

Воронка мультикарточного аудита

flowchart TD
A["Клиент обращается:\n«Обновите одну карточку»"] --> B["Fotofactor: «Давайте сначала\nпроверим весь портфель.\nАудит — бесплатно»"]
B --> C["Загрузка всех SKU\nчерез MPStats API"]
C --> D["Скоринг + Pareto-анализ\n20 минут автоматизации"]
D --> E["PDF-отчёт с матрицей\nприоритетов по ROI"]
E --> F["Презентация клиенту:\n«Вы хотели обновить\nне ту карточку»"]
F --> G{"Реакция клиента?"}
G -->|"«Вау, дайте пакет»\n(50-65%)"| H["Пакет ТОП-10\n560 000 руб"]
G -->|"«Начнём с ТОП-5»\n(20-25%)"| I["Фаза 1\n250 000 руб"]
G -->|"«Пока одну»\n(10-15%)"| J["1 карточка\n80 000 руб\n+ follow-up через\n1 месяц с ROI"]
I --> K["Показать ROI\nпосле Фазы 1"]
K --> L["Фаза 2 утверждается\nавтоматически (85%)"]
J --> M["Follow-up:\nROI одной карточки\n→ Upsell на пакет"]

Unit-экономика

Вход:  Имя продавца + категория (1-3 API-запроса)

Анализ: Скоринг 47 SKU + Pareto (~20 минут)

Отчёт: PDF 10-15 страниц с матрицей приоритетов

Пакет ТОП-10: 560 000 руб (скидка 30%)

Фаза 1 (ТОП-5): 250 000 руб → показать ROI
Фаза 2 (ТОП 6-10): 220 000 руб → утверждение 85%
Фаза 3 (остальные): по запросу

Мониторинг: 25 000 руб/мес (ежемесячный отчёт)

LTV: 700 000 - 1 200 000 руб/год на клиента

Что дальше