До/После — трекер эффекта обновления контента
Автоматическая система доказательства ROI: измеряем влияние обновлённого контента на продажи в 5 контрольных точках и превращаем цифры в аргумент для удержания клиента.
Проблема
Самая болезненная точка контент-агентства — невозможность доказать результат. Клиент платит 80 000 рублей за фотосъёмку и через месяц спрашивает: «А оно вообще окупилось?» Без системного трекинга ответить нечего.
Последствия отсутствия измерений:
- Клиент не видит ценности — «заплатил за фото, а продажи вроде те же». Даже если продажи выросли, без цифр клиент не связывает рост с обновлением контента
- Отток 60% — клиенты не прод левают сотрудничество, потому что не могут обосновать расходы перед собственным руководством или партнёрами
- Возражение на старте — новые клиенты спрашивают «а у вас есть кейсы с цифрами?», и студии нечего показать, кроме портфолио
- Нет базы для upsell — без данных невозможно предложить обновление ещё 5 карточек и обосновать бюджет
- Конкуренты дешевле — если нет доказательства ROI, клиент выбирает по цене, а не по результату
Fotofactor делает качественный контент. Но «качественный» — субъективная оценка. Клиенту нужна объективная: «Ваши продажи выросли на 67%, позиция поднялась с #45 на #22, ROI = 340%». Именно эта цифра превращает разовую съёмку в долгосрочное сотрудничество.
Пайплайн данных
Ключевой эндпоинт MPStats
Основной источник данных — ежедневная статистика продаж по артикулу:
GET /wb/get/item/{sku}/sales
Возвращает массив ежедневных данных: продажи, выручка, остатки, цена. Дополнительно используем:
| Эндпоинт | Данные | Зачем |
|---|---|---|
GET /wb/get/item/{sku}/sales | Продажи, выручка, остатки по дням | Базовые метрики до/после обновления |
GET /wb/get/item/{sku}/by_category | Позиция в категории по дням | Отслеживание роста позиции |
GET /wb/get/item/{sku}/by_keywords | Позиции по ключевым словам | SEO-эффект обновления контента |
POST /wb/get/category | Средние показатели категории | Сравнение роста с рынком (клиент vs тренд) |
Протокол 5 контрольных точек
Каждое обновление контента отслеживается в 5 временных точках — от baseline до долгосрочного эффекта.
gantt
title Before/After Timeline -- 5 точек замера
dateFormat YYYY-MM-DD
axisFormat %d.%m
section Baseline
T-30d Baseline (30 дней до) :done, baseline, 2026-02-01, 30d
section Update
Обновление контента :crit, update, 2026-03-03, 1d
section Tracking
T+7d Первый эффект :milestone, t1, 2026-03-10, 0d
T+14d Стабилизация :milestone, t2, 2026-03-17, 0d
T+30d Месячный результат :milestone, t3, 2026-04-02, 0d
T+90d Долгосрочный эффект :milestone, t4, 2026-06-01, 0d
| Точка замера | Период | Что измеряем | Ожидаемый паттерн |
|---|---|---|---|
| T-30d | За 30 дней до обновления | Базовые метрики (benchmark) | Начальные значения — точка отсчёта |
| T+7d | Через 7 дней после | Первый эффект — индексация, первые реакции | Возможен кратковременный спад (переиндексация WB) |
| T+14d | Через 14 дней | Стабилизация — алгоритм WB адаптировался | Начало устойчивого роста позиции |
| T+30d | Через 30 дней | Месячный результат — устоявшиеся метрики | Основной рост продаж и позиции |
| T+90d | Через 90 дней | Долгосрочный эффект — устойчивость результата | Стабильный уровень или продолжение роста |
Две точки (до и после) не дают полной картины. На T+7d позиция может упасть — WB переиндексирует карточку, и клиент в панике звонит: «Стало хуже!» Если заранее предупредить про 5-точечный протокол, клиент спокойно ждёт T+30d. А если замерить только T+30d — можно пропустить быстрый рост на T+14d, который покажет, что контент работает даже лучше, чем ожидалось.
Детальное описание каждой точки замера и статистической значимости результатов: Before/After трекер.
Анализ
Алгоритм сбора и расчёта метрик
import httpx
from datetime import datetime, timedelta
from dataclasses import dataclass
MPSTATS_TOKEN = "YOUR_TOKEN"
BASE_URL = "https://mpstats.io/api"
HEADERS = {
"X-Mpstats-TOKEN": MPSTATS_TOKEN,
"Content-Type": "application/json",
}
@dataclass
class MeasurementPoint:
"""Снапшот метрик товара в контрольной точке."""
label: str # t_baseline, t_7d, t_14d, t_30d, t_90d
period_start: str # YYYY-MM-DD
period_end: str # YYYY-MM-DD
avg_daily_sales: float # Средние продажи в день
total_revenue: float # Суммарная выручка за период
avg_position: float | None # Средняя позиция в категории
avg_price: float # Средняя цена
def fetch_sales_data(sku: int, d1: str, d2: str) -> list[dict]:
"""Загружает ежедневные продажи по SKU за период."""
resp = httpx.get(
f"{BASE_URL}/wb/get/item/{sku}/sales",
headers=HEADERS,
params={"d1": d1, "d2": d2},
timeout=30,
)
resp.raise_for_status()
return resp.json()
def fetch_category_position(sku: int, d1: str, d2: str) -> list[dict]:
"""Загружает позицию в категории по дням."""
resp = httpx.get(
f"{BASE_URL}/wb/get/item/{sku}/by_category",
headers=HEADERS,
params={"d1": d1, "d2": d2},
timeout=30,
)
resp.raise_for_status()
return resp.json()
def measure_point(
sku: int, label: str, period_start: str, period_end: str
) -> MeasurementPoint:
"""
Замеряет метрики в одной контрольной точке.
Собирает данные о продажах и позиции за указанный период.
"""
# Продажи
sales_data = fetch_sales_data(sku, period_start, period_end)
days = len(sales_data) or 1
total_sales = sum(day.get("sales", 0) for day in sales_data)
total_revenue = sum(day.get("revenue", 0) for day in sales_data)
avg_price = (
sum(day.get("price", 0) for day in sales_data) / days
if sales_data else 0
)
# Позиция в категории
position_data = fetch_category_position(sku, period_start, period_end)
positions = [
day.get("position")
for day in position_data
if day.get("position") is not None
]
avg_position = sum(positions) / len(positions) if positions else None
return MeasurementPoint(
label=label,
period_start=period_start,
period_end=period_end,
avg_daily_sales=round(total_sales / days, 1),
total_revenue=round(total_revenue, 0),
avg_position=round(avg_position, 0) if avg_position else None,
avg_price=round(avg_price, 0),
)
def run_before_after(sku: int, content_update_date: str) -> list[MeasurementPoint]:
"""
Запускает 5-точечный протокол замеров.
Args:
sku: Артикул товара на Wildberries
content_update_date: Дата обновления контента (YYYY-MM-DD)
Returns:
Список из 5 MeasurementPoint
"""
update = datetime.strptime(content_update_date, "%Y-%m-%d")
# Определяем периоды для каждой точки
points_config = [
("T-30d (baseline)", update - timedelta(days=30), update - timedelta(days=1)),
("T+7d", update + timedelta(days=1), update + timedelta(days=7)),
("T+14d", update + timedelta(days=1), update + timedelta(days=14)),
("T+30d", update + timedelta(days=1), update + timedelta(days=30)),
("T+90d", update + timedelta(days=1), update + timedelta(days=90)),
]
results = []
for label, start, end in points_config:
point = measure_point(
sku, label,
start.strftime("%Y-%m-%d"),
end.strftime("%Y-%m-%d"),
)
results.append(point)
return results
def calculate_deltas(points: list[MeasurementPoint]) -> list[dict]:
"""
Рассчитывает delta (%) для каждой точки относительно baseline.
"""
baseline = points[0]
deltas = []
for point in points:
delta_sales = (
(point.avg_daily_sales - baseline.avg_daily_sales)
/ baseline.avg_daily_sales * 100
if baseline.avg_daily_sales > 0 else 0
)
delta_revenue = (
(point.total_revenue - baseline.total_revenue)
/ baseline.total_revenue * 100
if baseline.total_revenue > 0 else 0
)
delta_position = None
if point.avg_position and baseline.avg_position:
# Позиция: снижение = улучшение (с #45 на #22 = рост)
delta_position = (
(baseline.avg_position - point.avg_position)
/ baseline.avg_position * 100
)
deltas.append({
"label": point.label,
"avg_daily_sales": point.avg_daily_sales,
"delta_sales_pct": round(delta_sales, 1),
"total_revenue": point.total_revenue,
"delta_revenue_pct": round(delta_revenue, 1),
"avg_position": point.avg_position,
"delta_position_pct": round(delta_position, 1) if delta_position else None,
})
return deltas
def calculate_roi(
delta_revenue_30d: float,
baseline_revenue_30d: float,
fotofactor_cost: float,
) -> float:
"""
ROI = (дополнительная выручка - стоимость контента) / стоимость контента * 100%
delta_revenue_30d: выручка за 30 дней ПОСЛЕ обновления
baseline_revenue_30d: выручка за 30 дней ДО обновления
fotofactor_cost: стоимость услуг Fotofactor
"""
extra_revenue = delta_revenue_30d - baseline_revenue_30d
roi = (extra_revenue - fotofactor_cost) / fotofactor_cost * 100
return round(roi, 1)
ROI = (Дополнительная выручка − Стоимость контента) / Стоимость контента × 100%
Где:
- Дополнительная выручка = Выручка за период ПОСЛЕ обновления минус Выручка за аналогичный период ДО обновления
- Стоимость контента = Полная стоимость услуг Fotofactor (съёмка + обработка + загрузка)
Пример: Выручка ДО = 1 350 000 руб/мес, выручка ПОСЛЕ = 2 250 000 руб/мес, стоимость съёмки = 80 000 руб.
ROI = (2 250 000 − 1 350 000 − 80 000) / 80 000 × 100% = 1025%
Каждый вложенный рубль принёс 10.25 руб дополнительной выручки.
Пример отчёта: 5 точек замера
Реальный сценарий — женская кожаная сумка, SKU 12345678, стоимость съёмки 80 000 руб:
| Метрика | До (T-30d) | T+7d | T+14d | T+30d | T+90d |
|---|---|---|---|---|---|
| Продажи/день | 15 шт | 18 шт (+20%) | 22 шт (+47%) | 25 шт (+67%) | 28 шт (+87%) |
| Выручка/день | 45 000 руб | 54 000 руб (+20%) | 66 000 руб (+47%) | 75 000 руб (+67%) | 84 000 руб (+87%) |
| Позиция в категории | #45 | #38 (+16%) | #28 (+38%) | #22 (+51%) | #18 (+60%) |
| ContentScore | 42/100 | 78/100 | 78/100 | 80/100 | 80/100 |
| ROI контента | -- | -- | -- | +125% | +340% |
Типичная кривая роста после обновления контента
graph LR
A["T-30d<br/>Baseline<br/>15 продаж/день<br/>Позиция #45"] --> B["T+7d<br/>Первый эффект<br/>18 продаж/день (+20%)<br/>Позиция #38"]
B --> C["T+14d<br/>Стабилизация<br/>22 продажи/день (+47%)<br/>Позиция #28"]
C --> D["T+30d<br/>Основной рост<br/>25 продаж/день (+67%)<br/>ROI +125%"]
D --> E["T+90d<br/>Долгосрочный<br/>28 продаж/день (+87%)<br/>ROI +340%"]
style A fill:#ff6b6b,color:#fff
style B fill:#ffd93d,color:#333
style C fill:#ffd93d,color:#333
style D fill:#6bcb77,color:#fff
style E fill:#4d96ff,color:#fff
Сравнение с рынком — изоляция эффекта контента
Ключевой вопрос скептичного клиента: «А может, вся категория выросла, и вы тут ни при чём?» Для этого сравниваем рост клиента с ростом категории:
def compare_with_market(
client_deltas: list[dict],
category_path: str,
d1: str,
d2: str,
) -> dict:
"""
Сравнивает рост клиента со средним ростом категории.
Если клиент вырос на 67%, а категория на 10% -- чистый эффект контента = 57%.
"""
# Средние продажи категории за тот же период
resp = httpx.post(
f"{BASE_URL}/wb/get/category",
headers=HEADERS,
json={
"path": category_path,
"d1": d1,
"d2": d2,
"startRow": 0,
"endRow": 100,
},
timeout=30,
)
resp.raise_for_status()
category_data = resp.json().get("data", [])
avg_category_growth = sum(
item.get("sales_growth_percent", 0) for item in category_data
) / len(category_data) if category_data else 0
# Чистый эффект = рост клиента - рост рынка
client_growth = client_deltas[-1]["delta_sales_pct"] # T+90d
net_effect = client_growth - avg_category_growth
return {
"client_growth_pct": client_growth,
"market_growth_pct": round(avg_category_growth, 1),
"net_content_effect_pct": round(net_effect, 1),
"attribution": "content" if net_effect > 10 else "mixed",
}
Пример сравнения:
| Показатель | Клиент | Категория в среднем | Чистый эффект контента |
|---|---|---|---|
| Рост продаж за 90 дней | +87% | +12% | +75% |
| Рост позиции | +60% | +5% | +55% |
| Вывод | -- | -- | Контент = основной драйвер |
Действие Fotofactor
Автоматическая рассылка ROI-отчётов
Система автоматически отправляет клиенту красивый отчёт в каждой контрольной точке:
graph TD
A["Обнов ление контента<br/>фиксируем SKU + дату"] --> B["Cron-задача<br/>проверяет наступление<br/>контрольных точек"]
B --> C{"Наступила<br/>контрольная<br/>точка?"}
C -->|"Нет"| B
C -->|"Да"| D["Сбор метрик<br/>MPStats API"]
D --> E["Расчёт delta<br/>+ ROI + сравнение<br/>с рынком"]
E --> F{"Метрики<br/>упали?"}
F -->|"Нет"| G["Позитивный отчёт<br/>рост, ROI, графики"]
F -->|"Да"| H["Алерт менеджеру<br/>+ диагностика причин"]
G --> I["Отправка клиенту<br/>email / Telegram"]
H --> J["Проактивная реакция<br/>разбор + план действий"]
Структура отчёта для клиента
Каждый отчёт содержит:
- Заголовок: «Отчёт по эффекту обновления контента — T+30d»
- Ключевые цифры: продажи, выручка, позиция — до и после (delta %)
- График роста: визуальная кривая по 5 точкам
- Сравнение с рынком: «Категория выросла на 12%, вы — на 67%. Чистый эффект контента: +55%»
- ROI: «Ваш ROI за 30 дней: +125%. Каждый вложенный рубль принёс 2.25 руб»
- Рекомендация: upsell или подтверждение стратегии
Цитата из T+90d отчёта:
Ваш ROI за 90 дней: 340%. Каждый вложенный рубль в обновление контента принёс 3.4 рубля дополнительной выручки. Категория за тот же период выросла на 12% — ваш рост в 7 раз выше рынка.