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

Контент-усталость -- детектор устаревания карточки

Даже лучшие фотографии «протухают». Конкуренты обновляются, алгоритмы меняются, ожидания покупателей растут. Карточка, которая была на 10-й позиции полгода назад, теперь на 30-й -- не потому что товар стал хуже, а потому что контент не обновлялся. Большинство селлеров замечают это, только когда продажи уже критически упали.

Этот кейс показывает, как автоматически обнаруживать «уставший» контент и проактивно предлагать клиенту обновление -- до того, как он потеряет позиции безвозвратно.

Проблема

У контента есть срок годности. Это не очевидно, потому что фотографии физически не меняются -- но всё вокруг них меняется:

  • Конкуренты обновились: новый игрок залил качественную инфографику, ваша карточка на его фоне выглядит устаревшей
  • Алгоритмы WB/Ozon эволюционировали: площадка стала учитывать видео, 360-ракурсы, rich-контент -- а у вас по-прежнему 5 фото на белом фоне
  • Ожидания покупателей выросли: в 2024 хватало фото товара на манекене, в 2026 покупатель ждёт lifestyle-съёмку, размерную сетку в инфографике, видео-обзор
  • Сезонный контекст ушёл: зимняя инфографика в мае выглядит нелепо, но селлер забыл обновить

Типичный сценарий деградации:

МесяцПозицияПродажи/деньЧто происходит
Январь#10120 штКонтент свежий, всё работает
Март#14105 штКонкурент обновил карточку, начал обгонять
Май#2285 штЕщё 2 конкурента обновились, позиция падает
Июль#3060 штСеллер замечает: «Что-то продажи упали...»
Август#3545 штСрочный заказ на обновление контента (паника)
Сентябрь#2870 штНовый контент загружен, но 4 месяца и 50% продаж потеряны

Главная боль: между «контент начал устаревать» и «селлер заметил проблему» проходит 3-5 месяцев. За это время теряется 30-50% продаж и 10-20 позиций. Если бы детектор сработал в марте, а не в июле -- потери были бы минимальны.

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

graph LR
A["MPStats API\nitem/sales\n90 дней продаж"] --> B["Тренд-анализ\nлинейная регрессия"]
B --> C{"Продажи\nпадают?"}
C -->|"Нет"| D["OK\nмониторинг"]
C -->|"Да"| E["Проверка:\nкатегория тоже\nпадает?"]
E -->|"Да, рынок падает"| F["Рыночный спад\nне контент"]
E -->|"Нет, рынок стабилен"| G["Дата последнего\nобновления контента"]
G --> H["Расчёт\nFatigue Score"]
H --> I["Алерт клиенту\n+ рекомендации"]

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

ЭндпоинтМетодДанныеЗачем
/wb/get/item/{sku}/salesGETЕжедневные продажи SKU за 90+ днейДетектировать тренд: растёт, падает или стабильно
/wb/get/category/trendsGETТренды категории (помесячно)Изолировать причину: рынок или контент?
Page version history--Дата последнего обновления контента карточкиОпределить «возраст» текущего контента
Методология: как изолировать контент-усталость от рыночного спада

Падение продаж конкретного SKU может быть вызвано тремя причинами:

  1. Рыночный спад -- вся категория падает (сезонность, макроэкономика). Это НЕ контент-усталость.
  2. Проблема с товаром -- рейтинг упал, негативные отзывы, рост возвратов. Это НЕ контент-усталость.
  3. Контент-усталость -- категория стабильна или растёт, товар в порядке, но позиции и продажи падают. Конкуренты обновились, а ваша карточка -- нет.

Алгоритм изоляции:

  • Если тренд_категории < -5% -- рыночный спад, не алертим
  • Если рейтинг_SKU < 4.0 или доля_негативных_отзывов > 15% -- проблема товара, не контент
  • Если тренд_категории >= 0% И тренд_SKU < -10% И дней_без_обновления > 90 -- контент-усталость

Чем сильнее расхождение между трендом категории (рост) и трендом SKU (падение), тем выше вероятность именно контент-усталости.

Анализ

Формула Content Fatigue Score

Fatigue Score объединяет три фактора в один показатель:

fatigue_score = days_since_update * abs(decline_rate) * (1 + category_growth_rate)

Логика каждого множителя:

  • days_since_update -- чем дольше не обновлялся контент, тем выше усталость
  • abs(decline_rate) -- чем быстрее падают продажи, тем острее проблема
  • (1 + category_growth_rate) -- если категория растёт, а SKU падает, ситуация ещё хуже (конкуренты забирают вашу долю)
Пороги Fatigue Score
Fatigue ScoreУровеньДействие
> 80CRITICALНемедленное обновление. Карточка теряет позиции каждый день. Срочная съёмка + инфографика.
50-80WARNINGЗапланировать обновление в ближайшие 2-3 недели. Начать с инфографики и SEO.
30-50WATCHМониторить еженедельно. Если тренд не развернётся -- обновить через месяц.
< 30OKКонтент в порядке. Стандартный мониторинг.

Пример: SKU с decline_rate = -29%, обновление 8 месяцев назад, категория растёт на 5%: fatigue_score = 240 * 0.29 * 1.05 = 73 -- WARNING, нужно обновление.

Расчёт тренда и Fatigue Score

import httpx
import numpy as np
from datetime import datetime, timedelta

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


def fetch_sales_history(sku: int, days: int = 90) -> list[dict]:
"""
Загрузить историю продаж SKU за N дней.
Возвращает [{date, sales}, ...] отсортированный по дате.
"""
date_from = (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
date_to = datetime.now().strftime("%Y-%m-%d")

resp = httpx.get(
f"{BASE_URL}/wb/get/item/{sku}/sales",
headers=HEADERS,
params={"d1": date_from, "d2": date_to},
timeout=30,
)
resp.raise_for_status()
return resp.json()


def calculate_decline_rate(sales_data: list[dict]) -> float:
"""
Рассчитать скорость падения продаж через линейную регрессию.

Возвращает:
- Отрицательное значение = падение (напр. -0.29 = -29%)
- Положительное = рост
- Около 0 = стабильно
"""
if len(sales_data) < 14:
return 0.0

daily_sales = [day.get("sales", 0) for day in sales_data]
x = np.arange(len(daily_sales))
y = np.array(daily_sales, dtype=float)

# Линейная регрессия: y = slope * x + intercept
slope, intercept = np.polyfit(x, y, 1)

# Нормализуем: процентное изменение от среднего
avg_sales = np.mean(y)
if avg_sales == 0:
return 0.0

# Изменение за весь период как % от среднего
total_change = slope * len(daily_sales)
decline_rate = total_change / avg_sales

return round(decline_rate, 3)


def fetch_category_trend(category_path: str) -> float:
"""
Получить текущий тренд категории (последние 3 месяца vs предыдущие 3).
Возвращает процентное изменение: +0.05 = рост 5%, -0.10 = падение 10%.
"""
resp = httpx.get(
f"{BASE_URL}/wb/get/category/trends",
headers=HEADERS,
params={"path": category_path},
timeout=30,
)
resp.raise_for_status()
trends = resp.json() # 72 значения (6 лет помесячно)

if len(trends) < 6:
return 0.0

# Сравниваем последние 3 месяца с предыдущими 3
recent = sum(trends[-3:])
previous = sum(trends[-6:-3])

if previous == 0:
return 0.0

return round((recent - previous) / previous, 3)


def calculate_fatigue_score(
days_since_update: int,
decline_rate: float,
category_growth_rate: float,
) -> dict:
"""
Рассчитать Content Fatigue Score.

Формула:
fatigue_score = days_since_update * abs(decline_rate) * (1 + category_growth_rate)

Если категория тоже падает -- понижаем скор (не контент виноват).
"""
# Если продажи растут -- нет усталости
if decline_rate >= 0:
return {
"score": 0,
"level": "OK",
"reason": "Продажи стабильны или растут",
}

# Если категория падает сильнее SKU -- рыночный спад
if category_growth_rate < decline_rate:
return {
"score": 0,
"level": "MARKET_DECLINE",
"reason": f"Рынок падает ({category_growth_rate:+.1%}), "
f"не контент-усталость",
}

# Основной расчёт
amplifier = max(1 + category_growth_rate, 1.0)
score = round(days_since_update * abs(decline_rate) * amplifier, 1)

# Классификация
if score > 80:
level = "CRITICAL"
elif score > 50:
level = "WARNING"
elif score > 30:
level = "WATCH"
else:
level = "OK"

return {
"score": score,
"level": level,
"components": {
"days_since_update": days_since_update,
"decline_rate": f"{decline_rate:+.1%}",
"category_growth": f"{category_growth_rate:+.1%}",
"amplifier": round(amplifier, 2),
},
}


def detect_content_fatigue(
sku: int,
category_path: str,
last_content_update: str, # формат "YYYY-MM-DD"
) -> dict:
"""
Полный пайплайн детекции контент-усталости для одного SKU.
"""
# 1. Загружаем продажи
sales = fetch_sales_history(sku, days=90)

# 2. Рассчитываем тренд SKU
decline_rate = calculate_decline_rate(sales)

# 3. Рассчитываем тренд категории
category_trend = fetch_category_trend(category_path)

# 4. Считаем дни с последнего обновления
update_date = datetime.strptime(last_content_update, "%Y-%m-%d")
days_since = (datetime.now() - update_date).days

# 5. Fatigue Score
fatigue = calculate_fatigue_score(days_since, decline_rate, category_trend)

# 6. Средние продажи для отчёта
recent_30 = sales[-30:] if len(sales) >= 30 else sales
older_30 = sales[-90:-60] if len(sales) >= 90 else sales[:30]

avg_recent = sum(d.get("sales", 0) for d in recent_30) / max(len(recent_30), 1)
avg_older = sum(d.get("sales", 0) for d in older_30) / max(len(older_30), 1)

return {
"sku": sku,
"category": category_path,
"avg_sales_90d_ago": round(avg_older, 1),
"avg_sales_30d": round(avg_recent, 1),
"decline_rate": f"{decline_rate:+.1%}",
"category_trend": f"{category_trend:+.1%}",
"last_update": last_content_update,
"days_since_update": days_since,
"fatigue": fatigue,
}


# --- Пример использования ---

results = []
skus_to_check = [
{
"sku": 331320399,
"category": "Одежда/Женская одежда/Платья",
"last_update": "2025-06-15",
},
{
"sku": 445566778,
"category": "Одежда/Женская одежда/Платья",
"last_update": "2025-12-10",
},
{
"sku": 998877665,
"category": "Обувь/Женская обувь/Кроссовки",
"last_update": "2025-03-01",
},
]

for item in skus_to_check:
result = detect_content_fatigue(
sku=item["sku"],
category_path=item["category"],
last_content_update=item["last_update"],
)
results.append(result)

level = result["fatigue"]["level"]
score = result["fatigue"]["score"]
emoji = {"CRITICAL": "!!!", "WARNING": "!", "WATCH": "~", "OK": ""}.get(level, "")

print(
f"SKU {result['sku']}: "
f"score={score} [{level}] {emoji} | "
f"sales: {result['avg_sales_90d_ago']} -> {result['avg_sales_30d']}/day | "
f"last update: {result['days_since_update']} days ago"
)

Пример детекции

SKUПродажи 90д назадПродажи 30дТренд SKUТренд категорииПоследнее обновлениеFatigue ScoreУровень
331320399120/день85/день-29%+5%8 мес назад (240 дней)73.1WARNING
44556677850/день48/день-4%-3%2 мес назад (70 дней)2.8OK
998877665200/день110/день-45%+8%12 мес назад (360 дней)174.9CRITICAL

Разбор результатов:

  • SKU 331320399 (score 73, WARNING): продажи падают на 29%, а категория растёт на 5%. Контент не обновлялся 8 месяцев. Конкуренты обновились и забирают долю. Рекомендация: запланировать обновление в ближайшие 2-3 недели.

  • SKU 445566778 (score 2.8, OK): минимальное падение (-4%) при падающей категории (-3%). Спад рыночный, контент не виноват. Обновлён недавно. Продолжаем мониторинг.

  • SKU 998877665 (score 174.9, CRITICAL): катастрофическое падение -45% при растущей категории +8%. Контент не обновлялся год. Немедленная пересъёмка, каждый день теряются продажи.

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

graph TD
A["90 дней продаж SKU\n(item/sales)"] --> B["Линейная регрессия\nslope < -0.1?"]
B -->|"Нет, стабильно"| C["OK\nМониторинг"]
B -->|"Да, падение"| D["Тренд категории\n(category/trends)"]
D --> E{"Категория тоже\nпадает?"}
E -->|"Да, рынок падает"| F["Рыночный спад\nНе контент"]
E -->|"Нет, рынок стабилен\nили растёт"| G["Проверить дату\nобновления контента"]
G --> H{"Дней без\nобновления?"}
H -->|"< 60 дней"| I["Недавно обновлено\nИскать другую причину"]
H -->|"60-180 дней"| J["Fatigue Score\n= days * rate * amp"]
H -->|"> 180 дней"| K["Высокий риск\nFatigue Score\n= days * rate * amp"]
J --> L{"Score?"}
K --> L
L -->|"> 80"| M["CRITICAL\nСрочная пересъёмка"]
L -->|"50-80"| N["WARNING\nОбновить за 2-3 нед"]
L -->|"30-50"| O["WATCH\nМониторить еженедельно"]
L -->|"< 30"| C

Действие Fotofactor

Проактивный алерт клиенту

Когда детектор обнаруживает контент-усталость (score > 50), Fotofactor отправляет клиенту проактивное сообщение:

Шаблон проактивного алерта
Добрый день! Мониторинг MPStats выявил снижение продаж по вашей карточке:

📊 Карточка: {product_name} (SKU {sku})
📉 Продажи: {avg_old}/день -> {avg_new}/день ({decline_rate})
📈 При этом ваша категория: {category_trend} (растёт!)
📅 Последнее обновление контента: {last_update} ({days} дней назад)

⚠️ Диагноз: контент-усталость (Fatigue Score: {score}/100)

Ваши конкуренты обновили карточки за последние 3 месяца:
- {competitor_1}: обновил инфографику, продажи +{comp1_growth}%
- {competitor_2}: добавил видео-обзор, продажи +{comp2_growth}%
- {competitor_3}: новая lifestyle-съёмка, вышел в топ-5

💡 Рекомендация: обновление контента в ближайшие 2 недели
вернёт карточку на прежние позиции.

Предлагаем:
1. Новая инфографика под текущие тренды категории -- 3 дня
2. Обновлённое главное фото (A/B тест) -- 2 дня
3. Видео-обзор 15 сек (если нет) -- 5 дней

📞 Готовы обсудить? Забронировать фотосъёмку на ближайшую неделю?

Конкретные рекомендации по обновлению

На основе анализа изменений в категории с момента последнего обновления клиентской карточки:

Что изменилось в категорииРекомендация для клиентаСрочность
Конкуренты добавили видеоСнять видео-обзор 15-30 секВысокая
Появилась инфографика с размерными сеткамиОбновить инфографикуВысокая
Тренд на lifestyle-фото вместо предметнойПересъёмка в lifestyle-стилеСредняя
Сезон изменился (зимний контент в мае)Сезонное обновлениеКритическая
Новый формат rich-контент на WBАдаптировать под новый форматСредняя

Услуга «Экстренное обновление»

Для карточек с Fatigue Score > 80 -- ускоренный процесс:

  • Срок: 3-5 рабочих дней (вместо стандартных 10-14)
  • Стоимость: 40 000--80 000 рублей (надбавка за срочность 30-50%)
  • Включает: пересъёмка главного фото + обновление инфографики + SEO-правки описания
  • Приоритет: сдвигаем другие заказы, карточка в CRITICAL теряет деньги каждый день

Связь с конкурентным контекстом

Показать клиенту карточки конкурентов, которые обновились недавно и растут:

«Ваш прямой конкурент (SKU 112233445) обновил карточку 3 недели назад: новая инфографика + видео. Его продажи выросли на 35%, а ваши упали на 29%. Разрыв увеличивается каждый день.»

Это создаёт urgency -- клиент видит не абстрактные цифры, а конкретного конкурента, который забирает его продажи.

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

Раннее обнаружение -- главная ценность

МетрикаБез детектораС детекторомВыигрыш
Время обнаружения проблемы3-5 месяцев (когда уже заметно)3-4 недели (автоматический алерт)Экономия 2-4 месяца
Потерянные продажи до обновления30-50% от пикового уровня5-10% (пока контент обновляется)Сохраняется 80-90% продаж
Позиции в поискеПадение на 15-25 позицийПадение на 3-5 позицийЛегче восстановить
Стоимость восстановленияВысокая (нужна агрессивная реклама + контент)Низкая (только обновление контента)Экономия 50-70% бюджета

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

Проактивный outreach по детектору контент-усталости создаёт тёплые лиды -- клиент уже видит падение продаж и готов к диалогу:

  • Конверсия проактивного алерта: 25-35% (vs 3-5% холодного outreach)
  • Средний чек экстренного обновления: 40 000--80 000 рублей
  • Время от алерта до заказа: 2-5 дней (клиент мотивирован)
  • Upsell в годовую подписку: 40% клиентов после экстренного обновления подписываются на мониторинг

Связь с другими кейсами

Детектор контент-усталости является частью системы проактивного управления контентом:

Что дальше