# Retention Curve и Product-Market Fit: как AI помогает увидеть сигнал в данных

> Как распознать product-market fit по retention curve: SQL для когортного анализа, определение flattening, промпты для AI-интерпретации кривых удержания.
> Author: Roman Belov · Published: 2026-06-15 · Source: https://futurecraft.pro/ru/blog/retention-curve-pmf/

В 2014 году Алекс Шульц, VP Growth в Facebook, сформулировал один из самых цитируемых критериев product-market fit в лекции Stanford CS183B: если retention curve выходит на плато параллельно оси X, у вас есть PMF. Не NPS, не revenue growth, не количество регистраций. Форма кривой удержания.

Проблема в том, что на практике retention curves зашумлены. Маленькие когорты дают статистические выбросы, сезонность искажает тренд, а граница между "кривая стабилизируется" и "кривая медленно падает к нулю" визуально неочевидна. Здесь AI-анализ даёт преимущество: модели находят паттерны flattening, которые человеческий глаз пропускает.

Статья о том, как построить retention curves от сырых событий до формального вывода о PMF. SQL-запросы для когортного анализа, статистические тесты на flattening, промпты для AI-интерпретации.

## Retention curve как индикатор PMF

Retention curve показывает, какая доля пользователей из определённой когорты возвращается в продукт через N дней/недель/месяцев после первого визита. Day 0 всегда 100%. Day 1 обычно резко падает. Дальше форма кривой говорит о продукте больше, чем любой другой показатель.

Три характерных паттерна:

**Плато (PMF-сигнал).** Кривая стабилизируется на определённом уровне. Пример: Day 1 — 40%, Day 7 — 25%, Day 14 — 20%, Day 30 — 18%, Day 60 — 17%, Day 90 — 17%. Кривая нашла дно и перестала падать. Часть пользователей остаётся навсегда. Это и есть PMF.

**Медленное затухание (слабый PMF или нет PMF).** Кривая снижается без явной стабилизации: Day 30 — 15%, Day 60 — 12%, Day 90 — 9%, Day 120 — 7%. На первый взгляд похоже на плато, но каждый месяц теряется 2-3 процентных пункта. При таком темпе через год retention стремится к нулю.

**Обрыв (отсутствие PMF).** Day 1 — 15%, Day 7 — 3%, Day 30 — 0.5%. Пользователи пробуют продукт и уходят. Никакого намёка на стабилизацию.

Ключевой вопрос: как отличить первый паттерн от второго? Визуально разница между "плато на 17%" и "медленное снижение с 18% до 12% за полгода" может быть неразличима на графике с 90-дневным окном. Нужен формальный анализ.

## SQL-запросы для построения retention curves

Базовая структура данных: таблица событий с `user_id`, `event_timestamp` и `event_name`. Минимальная схема:

```sql
CREATE TABLE events (
    user_id       TEXT NOT NULL,
    event_name    TEXT NOT NULL,
    event_timestamp TIMESTAMP NOT NULL
);
```

### Определение когорт

Когорта — группа пользователей, зарегистрированных (или впервые появившихся) в один и тот же период. Первый шаг — вычислить дату первого визита каждого пользователя:

```sql
CREATE MATERIALIZED VIEW user_first_seen AS
SELECT
    user_id,
    DATE_TRUNC('week', MIN(event_timestamp)) AS cohort_week
FROM events
GROUP BY user_id;
```

`DATE_TRUNC('week', ...)` группирует пользователей по неделям. Для продуктов с ежедневным использованием (мессенджеры, соцсети) лучше `'day'`. Для SaaS с месячным циклом — `'month'`.

### Расчёт retention по когортам

```sql
WITH activity AS (
    SELECT DISTINCT
        e.user_id,
        u.cohort_week,
        DATE_TRUNC('week', e.event_timestamp) AS activity_week
    FROM events e
    JOIN user_first_seen u USING (user_id)
),
retention_raw AS (
    SELECT
        cohort_week,
        EXTRACT(DAYS FROM activity_week - cohort_week)::INT / 7 AS week_number,
        COUNT(DISTINCT user_id) AS active_users
    FROM activity
    GROUP BY cohort_week, week_number
),
cohort_sizes AS (
    SELECT
        cohort_week,
        COUNT(DISTINCT user_id) AS cohort_size
    FROM user_first_seen
    GROUP BY cohort_week
)
SELECT
    r.cohort_week,
    r.week_number,
    r.active_users,
    c.cohort_size,
    ROUND(100.0 * r.active_users / c.cohort_size, 2) AS retention_pct
FROM retention_raw r
JOIN cohort_sizes c USING (cohort_week)
WHERE r.week_number >= 0
ORDER BY r.cohort_week, r.week_number;
```

Результат — таблица, где каждая строка содержит когорту, номер недели и процент вернувшихся пользователей. Это сырые данные для retention curve.

### Агрегированная retention curve

Для анализа PMF нужна не россыпь отдельных когорт, а средняя кривая по всем когортам (или по достаточно зрелым):

```sql
WITH mature_cohorts AS (
    -- Берём когорты возрастом 12+ недель
    SELECT cohort_week
    FROM user_first_seen
    GROUP BY cohort_week
    HAVING MAX(CURRENT_DATE - cohort_week::DATE) >= 84
),
retention_data AS (
    SELECT
        r.week_number,
        AVG(r.retention_pct) AS avg_retention,
        STDDEV(r.retention_pct) AS std_retention,
        COUNT(*) AS num_cohorts
    FROM retention_raw r
    JOIN mature_cohorts mc USING (cohort_week)
    WHERE r.week_number BETWEEN 1 AND 12
    GROUP BY r.week_number
)
SELECT
    week_number,
    ROUND(avg_retention, 2) AS avg_retention_pct,
    ROUND(std_retention, 2) AS std_dev,
    num_cohorts,
    ROUND(std_retention / SQRT(num_cohorts), 2) AS standard_error
FROM retention_data
ORDER BY week_number;
```

`standard_error` показывает, насколько надёжно среднее. Если SE больше 3-4 процентных пунктов, данных недостаточно для выводов.

## Обнаружение flattening: статистический подход

Визуальная оценка "кривая вышла на плато" субъективна. Формализация требует определения: flattening — это ситуация, когда скорость снижения retention статистически не отличается от нуля.

### Метод скользящего наклона

Вычислить наклон (slope) retention curve в окне последних N точек. Если slope не отличается от нуля с заданной значимостью, кривая стабилизировалась.

```sql
WITH weekly_retention AS (
    -- Используем данные из предыдущего запроса
    SELECT week_number, avg_retention_pct
    FROM retention_data
    ORDER BY week_number
),
slope_calc AS (
    SELECT
        week_number,
        avg_retention_pct,
        -- Наклон за последние 4 недели (линейная регрессия)
        REGR_SLOPE(avg_retention_pct, week_number)
            OVER (ORDER BY week_number ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
            AS rolling_slope,
        -- R² для оценки линейности
        REGR_R2(avg_retention_pct, week_number)
            OVER (ORDER BY week_number ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
            AS rolling_r2,
        -- Количество точек в окне
        COUNT(*) OVER (ORDER BY week_number ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
            AS window_size
    FROM weekly_retention
)
SELECT
    week_number,
    avg_retention_pct,
    ROUND(rolling_slope, 3) AS slope_per_week,
    ROUND(rolling_r2, 3) AS r_squared,
    CASE
        WHEN window_size >= 4 AND ABS(rolling_slope) < 0.5 THEN 'PLATEAU'
        WHEN window_size >= 4 AND rolling_slope < -0.5 THEN 'DECLINING'
        ELSE 'INSUFFICIENT_DATA'
    END AS curve_status
FROM slope_calc
WHERE window_size >= 4
ORDER BY week_number;
```

Порог `0.5` означает: если retention падает менее чем на 0.5 п.п. в неделю, кривая считается стабилизировавшейся. Для месячных когорт аналогичный порог — 1-2 п.п. в месяц.

### Метод сравнения периодов

Альтернативный подход: сравнить retention за две последовательные половины наблюдаемого периода с помощью t-теста.

```sql
WITH period_comparison AS (
    SELECT
        CASE
            WHEN week_number BETWEEN 5 AND 8 THEN 'period_1'
            WHEN week_number BETWEEN 9 AND 12 THEN 'period_2'
        END AS period,
        avg_retention_pct
    FROM retention_data
    WHERE week_number BETWEEN 5 AND 12
)
SELECT
    period,
    ROUND(AVG(avg_retention_pct), 2) AS mean_retention,
    ROUND(STDDEV(avg_retention_pct), 2) AS std_retention,
    COUNT(*) AS n_weeks
FROM period_comparison
GROUP BY period
ORDER BY period;
```

Если разница средних между `period_1` и `period_2` статистически незначима (p > 0.05), кривая стабилизировалась. Расчёт p-value удобнее вынести в Python или передать AI для интерпретации.

## AI-анализ retention curves

SQL даёт числа. AI превращает числа в выводы. Три промпта для последовательного анализа.

### Промпт 1: Диагностика формы кривой

```
Ты — product data analyst. Получи данные retention curve
и определи паттерн.

Данные (формат: week_number | avg_retention_pct | std_dev):
[ВСТАВИТЬ РЕЗУЛЬТАТ SQL-ЗАПРОСА]

Задачи:
1. Классифицируй форму кривой: plateau / slow_decay / cliff
2. Если plateau — укажи, на какой неделе начинается
   стабилизация и на каком уровне
3. Рассчитай slope кривой за последние 4 точки
4. Оцени статистическую значимость: достаточно ли данных
   для вывода? Какой доверительный интервал?
5. Дай однозначный вердикт: есть PMF-сигнал или нет

Формат ответа: JSON с полями curve_type, plateau_start_week,
plateau_level, final_slope, confidence, pmf_signal (boolean),
reasoning.
```

Важно запрашивать JSON. Структурированный вывод позволяет автоматизировать пайплайн: запрос → SQL → AI-анализ → алерт в Slack.

### Промпт 2: Сравнительный анализ когорт

```
Ты — product data analyst. Сравни retention curves
нескольких когорт и найди тренд.

Данные retention по когортам:
[ВСТАВИТЬ ТАБЛИЦУ С КОГОРТАМИ]

Задачи:
1. Сравни уровень стабилизации (plateau level)
   между ранними и поздними когортами
2. Растёт ли plateau level от когорты к когорте?
   (Это сигнал улучшения PMF)
3. Меняется ли скорость первоначального падения
   (week 0 → week 4)?
4. Есть ли аномальные когорты? Если да, выдвини гипотезу
   почему
5. Спрогнозируй plateau level для следующих 3 когорт

Формат: JSON с полями trend_direction (improving/stable/
degrading), plateau_levels_by_cohort, anomalies,
forecast, confidence.
```

Этот промпт ловит то, что статический анализ одной кривой пропускает: динамику PMF. Продукт может иметь плато на 15%, но если три месяца назад плато было на 10%, это сильный положительный сигнал.

### Промпт 3: Сегментированный анализ

```
Ты — product data analyst. Проанализируй retention curves
для разных сегментов пользователей.

Сегмент A (органический трафик):
[ДАННЫЕ]

Сегмент B (платный трафик):
[ДАННЫЕ]

Сегмент C (реферальный трафик):
[ДАННЫЕ]

Задачи:
1. Для каждого сегмента: определи форму кривой
   и уровень стабилизации
2. Какой сегмент показывает лучший PMF-сигнал?
3. Есть ли сегменты, где PMF отсутствует полностью?
4. Рассчитай разницу в LTV-потенциале между сегментами
   (исходя из retention levels)
5. Рекомендации: на какие каналы увеличить расход,
   какие сократить

Формат: JSON с полями segments (массив с curve_type,
plateau_level для каждого), best_segment, worst_segment,
ltv_multiplier, recommendations.
```

Сегментация вскрывает ситуации, когда общая кривая маскирует проблему. Агрегированный retention 18% может означать 30% для органики и 8% для платного трафика. PMF есть, но только для определённой аудитории.

## Полный pipeline: от событий до PMF-вердикта

Практическая реализация объединяет SQL и AI в автоматизированный процесс.

### Шаг 1: Извлечение данных

```sql
-- Полный запрос: retention по неделям, зрелые когорты,
-- с доверительными интервалами
WITH user_first_seen AS (
    SELECT
        user_id,
        DATE_TRUNC('week', MIN(event_timestamp)) AS cohort_week
    FROM events
    WHERE event_name = 'session_start'
    GROUP BY user_id
),
weekly_activity AS (
    SELECT DISTINCT
        e.user_id,
        u.cohort_week,
        DATE_TRUNC('week', e.event_timestamp) AS activity_week
    FROM events e
    JOIN user_first_seen u USING (user_id)
    WHERE e.event_name = 'session_start'
),
retention_by_cohort AS (
    SELECT
        w.cohort_week,
        EXTRACT(DAYS FROM w.activity_week - w.cohort_week)::INT / 7
            AS week_number,
        COUNT(DISTINCT w.user_id) AS active_users,
        c.cohort_size,
        ROUND(100.0 * COUNT(DISTINCT w.user_id) / c.cohort_size, 2)
            AS retention_pct
    FROM weekly_activity w
    JOIN (
        SELECT cohort_week, COUNT(*) AS cohort_size
        FROM user_first_seen
        GROUP BY cohort_week
    ) c USING (cohort_week)
    GROUP BY w.cohort_week, week_number, c.cohort_size
),
aggregated AS (
    SELECT
        week_number,
        ROUND(AVG(retention_pct), 2) AS avg_retention,
        ROUND(STDDEV(retention_pct), 2) AS std_dev,
        COUNT(DISTINCT cohort_week) AS n_cohorts,
        ROUND(STDDEV(retention_pct) / SQRT(COUNT(DISTINCT cohort_week)), 2)
            AS se
    FROM retention_by_cohort
    WHERE week_number BETWEEN 0 AND 12
      AND cohort_week <= CURRENT_DATE - INTERVAL '84 days'
    GROUP BY week_number
    HAVING COUNT(DISTINCT cohort_week) >= 4
)
SELECT * FROM aggregated ORDER BY week_number;
```

### Шаг 2: Вычисление slope и flattening-метрик

```sql
WITH slopes AS (
    SELECT
        week_number,
        avg_retention,
        avg_retention - LAG(avg_retention) OVER (ORDER BY week_number)
            AS week_over_week_change,
        REGR_SLOPE(avg_retention, week_number)
            OVER (ORDER BY week_number ROWS BETWEEN 3 PRECEDING AND CURRENT ROW)
            AS rolling_slope_4w
    FROM aggregated
)
SELECT
    week_number,
    avg_retention,
    ROUND(week_over_week_change, 2) AS wow_change,
    ROUND(rolling_slope_4w, 3) AS slope_4w,
    CASE
        WHEN week_number >= 4 AND ABS(rolling_slope_4w) < 0.5
            THEN 'FLATTENING'
        WHEN week_number >= 4 AND rolling_slope_4w < -0.5
            THEN 'DECLINING'
        ELSE 'EARLY_STAGE'
    END AS status
FROM slopes
ORDER BY week_number;
```

### Шаг 3: AI-интерпретация

Передать результаты обоих запросов в промпт 1 (диагностика формы). Если вердикт — plateau, запустить промпт 2 (динамика по когортам) для подтверждения.

### Шаг 4: Формальные критерии PMF

Набор условий, при выполнении которых PMF считается подтверждённым:

| Критерий | Порог | Вес |
|----------|-------|-----|
| Slope за последние 4 недели | \|slope\| < 0.5 п.п./нед | Обязательный |
| Уровень плато (retention) | > 15% для B2C, > 50% для SaaS | Обязательный |
| Количество зрелых когорт | >= 6 | Обязательный |
| Стандартная ошибка среднего | < 3 п.п. | Рекомендуемый |
| Тренд plateau level по когортам | Не снижается | Рекомендуемый |
| AI-вердикт (промпт 1) | pmf_signal: true | Подтверждающий |

Все три обязательных критерия должны выполняться одновременно. PMF — не один показатель, а совокупность сигналов.

## Бенчмарки retention по типам продуктов

Уровень плато сам по себе ничего не значит без контекста индустрии. Ориентиры:

**B2C мобильные приложения.** Day 1 retention: 25-40% (хорошо), Day 30: 10-15%. Плато выше 15% на Day 90 — сильный PMF-сигнал. Для социальных приложений и мессенджеров бенчмарк выше: 20-25% на Day 90.

**SaaS (B2B).** Month 1 retention: 80-90%, Month 12: 60-75%. Плато выше 70% на Month 12 — здоровый продукт. Ниже 50% — проблема с value delivery.

**E-commerce.** Repeat purchase rate Month 3: 20-30%. Плато выше 25% указывает на сформированную привычку покупать в конкретном магазине.

**Контентные платформы.** Week 4 retention: 15-25%. Плато выше 20% на Week 12 — контент удерживает аудиторию. Ниже 10% — пользователь получил ценность и ушёл (one-shot usage).

Сравнивать retention B2C-приложения с SaaS-бенчмарками бессмысленно. 20% Day 90 retention для мобильной игры означает top-10 перцентиль. Те же 20% Month 3 retention для SaaS с подпиской означают, что 80% платящих клиентов уходят за квартал.

## Ложные сигналы PMF

Retention curve может показывать плато при отсутствии реального PMF. Четыре распространённых ловушки.

**Survivorship bias в когортах.** Если когорты включают только пользователей, совершивших определённое действие (оплата, завершение онбординга), retention будет искусственно завышен. Правильный подход: когорта включает всех, кто зарегистрировался, без фильтрации.

**Малые когорты.** 50 пользователей в когорте — это шум, не сигнал. Один активный пользователь из 50 даёт 2% retention. Два — уже 4%. Разница в 100% вызвана одним человеком. Минимальный размер когорты для статистически значимых выводов — 200-500 пользователей.

**Сезонность.** Когорты декабря (предновогодний период) и июля (каникулы) могут вести себя принципиально по-разному. Агрегация без учёта сезонности смешивает сигнал с шумом. Решение: анализировать когорты одного сезона отдельно или добавить сезонную поправку.

**Push-notifications и реактивация.** Агрессивные push-уведомления искусственно поднимают retention. Пользователь открывает приложение не потому, что нуждается в продукте, а потому что получил уведомление. Такой retention не конвертируется в [монетизацию](/ru/blog/unit-economics-calculator-ai/) и рассыпается при отключении пушей.

SQL-запрос для проверки survivorship bias:

```sql
-- Сравнить retention "всех зарегистрированных" vs
-- "завершивших онбординг"
WITH all_users AS (
    SELECT user_id, cohort_week
    FROM user_first_seen
),
onboarded_users AS (
    SELECT DISTINCT e.user_id, u.cohort_week
    FROM events e
    JOIN user_first_seen u USING (user_id)
    WHERE e.event_name = 'onboarding_complete'
),
retention_all AS (
    SELECT
        'all_users' AS segment,
        week_number,
        ROUND(AVG(retention_pct), 2) AS avg_retention
    FROM retention_by_cohort
    GROUP BY week_number
),
retention_onboarded AS (
    -- retention_by_cohort_onboarded здесь не определён:
    -- постройте его так же, как retention_by_cohort выше,
    -- только от onboarded_users вместо всех пользователей
    SELECT
        'onboarded' AS segment,
        week_number,
        ROUND(AVG(retention_pct), 2) AS avg_retention
    FROM retention_by_cohort_onboarded
    GROUP BY week_number
)
SELECT * FROM retention_all
UNION ALL
SELECT * FROM retention_onboarded
ORDER BY segment, week_number;
```

Если разница между двумя сегментами больше 10 п.п. на Week 8+, retention "завышен" за счёт фильтрации. Реальный PMF должен просматриваться на когорте всех зарегистрированных.

## Автоматизация: еженедельный PMF-монитор

Вместо ручного анализа — автоматический пайплайн, который каждую неделю выдаёт PMF-статус.

### Архитектура

```
Cron (понедельник, 09:00)
    │
    ▼
SQL-запросы к базе событий
    │
    ├─ Агрегированная retention curve
    ├─ Slopes и flattening-метрики
    └─ Retention по когортам
    │
    ▼
AI-анализ (промпт 1 + промпт 2)
    │
    ▼
Структурированный JSON
    │
    ├─ pmf_signal: true/false
    ├─ plateau_level: X%
    ├─ trend: improving/stable/degrading
    └─ confidence: high/medium/low
    │
    ▼
Алерт в Slack / Dashboard update
```

### Python-скрипт для оркестрации

```python
import json
from datetime import datetime

def run_pmf_monitor(db_conn, llm_client):
    # 1. Выполнить SQL-запросы
    retention_data = db_conn.execute(RETENTION_QUERY).fetchall()
    slope_data = db_conn.execute(SLOPE_QUERY).fetchall()

    # 2. Форматировать данные для промпта
    data_str = format_retention_table(retention_data)
    slope_str = format_slope_table(slope_data)

    # 3. AI-анализ
    prompt = DIAGNOSTIC_PROMPT.format(
        retention_data=data_str,
        slope_data=slope_str
    )
    response = llm_client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=2048,
        messages=[{"role": "user", "content": prompt}]
    )
    result = json.loads(response.content[0].text)

    # 4. Проверка формальных критериев
    formal_check = {
        "slope_ok": abs(result["final_slope"]) < 0.5,
        "plateau_ok": result["plateau_level"] > 15,
        "cohorts_ok": len(retention_data) >= 6,
        "ai_signal": result["pmf_signal"]
    }
    pmf_confirmed = all([
        formal_check["slope_ok"],
        formal_check["plateau_ok"],
        formal_check["cohorts_ok"]
    ])

    return {
        "date": datetime.now().isoformat(),
        "pmf_confirmed": pmf_confirmed,
        "details": result,
        "formal_check": formal_check
    }
```

JSON-формат ответа здесь обеспечивается инструкцией в промпте (промпт 1 явно требует JSON) и парсингом; если нужна гарантия схемы на уровне API, у Claude API есть structured outputs — параметр `output_format` с JSON-схемой. Модель `claude-sonnet-4-6` выбрана осознанно: для табличных данных и числового анализа Sonnet даёт результат, сопоставимый с Opus, при меньшей стоимости. Для задач с неструктурированным контекстом (анализ причин аномалий, стратегические рекомендации) стоит использовать Opus.

## Что делать после обнаружения PMF

Три сценария в зависимости от результата анализа.

**PMF подтверждён (plateau > порога, slope ~ 0, тренд стабильный/растущий).** Фокус смещается с product-development на growth. Retention показывает, что продукт решает задачу. Следующие вопросы: как масштабировать привлечение, какие каналы дают пользователей с лучшим retention (промпт 3), как увеличить plateau level через улучшение онбординга.

**PMF не подтверждён (кривая падает, slope значимо отрицательный).** Масштабировать привлечение на этом этапе — тратить деньги впустую. Возврат к product-development: интервью с пользователями, анализ момента оттока (на какой неделе происходит максимальное падение), поиск [magic number](/ru/blog/unit-economics-calculator-ai/) — действия, коррелирующего с retention.

**Неопределённость (slope на границе, данных мало, когорты маленькие).** Увеличить горизонт наблюдения. Подождать ещё 4-6 недель для формирования зрелых когорт. Увеличить размер когорт через таргетированное привлечение. Повторить анализ.

## Ограничения метода

Retention curve — необходимый, но недостаточный критерий PMF.

Продукт может показывать плато retention при низкой готовности платить. Пользователи возвращаются, но переход на платный план не происходит. Формально retention стабилен, но бизнес-модель не работает.

Retention не учитывает глубину использования. 17% пользователей возвращаются, но если каждый визит длится 30 секунд вместо 10 минут, engagement снижается при стабильном retention.

Для полной картины retention curves дополняются метриками: revenue retention (NRR), depth of engagement (DAU/MAU ratio), willingness to pay, Net Promoter Score. Retention — фундамент, остальные метрики — этажи.

## Итог

Retention curve, выходящая на плато, остаётся одним из самых надёжных количественных сигналов PMF. SQL-запросы для когортного анализа строят кривую. Статистические тесты на flattening формализуют субъективную оценку. AI-промпты автоматизируют интерпретацию и ловят паттерны, которые ускользают при ручном анализе.

Минимальный набор для старта: таблица событий с user_id и timestamp, запрос для расчёта retention по когортам, один промпт для диагностики формы кривой. Всё остальное — слои, добавляющие точность: сегментация, сравнение когорт, автоматический мониторинг. Каждый слой сокращает неопределённость, но базовый ответ на вопрос "есть ли PMF" доступен уже на первом.

---

*Нужна помощь с анализом retention? Я помогаю стартапам внедрять AI-решения и строить продукты — [belov.works](https://belov.works).*
