# ✅ Реализация Lazy Impression для Unleash AB тестов

## 🎯 Цель

Отправлять impression события в Google Analytics **только когда пользователь реально видит экран** с AB тестом, но при этом **загружать все флаги заранее** для быстрых переходов.

---

## 🏗️ Архитектура решения

```
┌─────────────────────────────────────────────────────────────┐
│  FunnelUnleashWrapper                                       │
│  ✅ Загружает ВСЕ флаги из воронки заранее                  │
│  ✅ Сохраняет активные варианты в UnleashContext            │
│  ❌ НЕ отправляет impression автоматически                  │
└─────────────────────────────────────────────────────────────┘
                           ↓
┌─────────────────────────────────────────────────────────────┐
│  UnleashContext                                             │
│  activeVariants: { "trial-test": "v1", "flow-test": "v2" } │
└─────────────────────────────────────────────────────────────┘
                           ↓
┌─────────────────────────────────────────────────────────────┐
│  FunnelRuntime                                              │
│  1. Собирает флаги для ТЕКУЩЕГО экрана                     │
│  2. Получает варианты из activeVariants                     │
│  3. Отправляет impression через sendUnleashImpression()     │
│  4. sessionStorage предотвращает дубликаты                  │
└─────────────────────────────────────────────────────────────┘
```

---

## 📝 Реализованные изменения

### 1. Убрана автоматическая отправка из `useUnleash.ts`

**Было:**
```typescript
// useUnleash автоматически подписывался на события impression
useEffect(() => {
  unleashClient.on("impression", handleImpression);
  // Отправлял события сразу при загрузке флагов
}, [unleashClient]);
```

**Стало:**
```typescript
// useUnleash теперь только получает вариант, не отправляет события
export function useUnleash({ flag }: UseUnleashProps) {
  const variant = useVariant(flag);
  return { variant: variant?.name };
}
```

### 2. Создан `sendImpression.ts` - ручная отправка

```typescript
export function sendUnleashImpression(flag: string, variant: string | undefined) {
  // Проверки валидности
  if (!variant || variant === "disabled") return;
  if (typeof window === "undefined") return;

  // ✅ Защита от дубликатов через sessionStorage
  const storageKey = `unleash_impression_${flag}_${variant}`;
  if (sessionStorage.getItem(storageKey)) return;

  // Отправка в GA
  if (window.gtag) {
    window.gtag("event", "experiment_impression", {
      app_name: "witlab-funnel",
      feature: flag,
      treatment: variant,
    });
    
    sessionStorage.setItem(storageKey, "true");
  }
}
```

**Особенности:**
- ✅ sessionStorage - предотвращает дубликаты при перезагрузке
- ✅ Graceful degradation - не падает если GA не установлена
- ✅ Debug логи в development режиме

### 3. Добавлена логика в `FunnelRuntime.tsx`

```typescript
// Собираем флаги для ТЕКУЩЕГО экрана
const currentScreenFlags = useMemo(() => {
  const flags = new Set<string>();

  // Из вариантов экрана
  currentScreen.variants?.forEach((variant) => {
    variant.conditions.forEach((condition) => {
      if (condition.conditionType === "unleash" && condition.unleashFlag) {
        flags.add(condition.unleashFlag);
      }
    });
  });

  // Из правил навигации
  currentScreen.navigation?.rules?.forEach((rule) => {
    rule.conditions.forEach((condition) => {
      if (condition.conditionType === "unleash" && condition.unleashFlag) {
        flags.add(condition.unleashFlag);
      }
    });
  });

  return Array.from(flags);
}, [currentScreen]);

// Отправляем impression когда экран меняется
useEffect(() => {
  if (currentScreenFlags.length === 0) return;

  currentScreenFlags.forEach((flag) => {
    const variant = activeVariants[flag];
    sendUnleashImpression(flag, variant);
  });
}, [currentScreenFlags, activeVariants]);
```

**Ключевые моменты:**
- Собираем флаги **только для текущего экрана**
- Получаем варианты из `activeVariants` (уже загружены)
- Отправляем impression **при рендере экрана**
- `sessionStorage` не дает отправить дважды

---

## ✅ Преимущества решения

| Критерий | Результат |
|----------|-----------|
| **Точность аналитики** | ✅ Impression = пользователь увидел экран |
| **Нет дубликатов** | ✅ sessionStorage предотвращает |
| **Скорость загрузки** | ✅ Флаги загружаются заранее (preload) |
| **Плавность переходов** | ✅ Нет задержек - все уже готово |
| **Обратная совместимость** | ✅ Существующая логика не затронута |

---

## 📊 Примеры работы

### Сценарий 1: Пользователь проходит воронку

```
T+0ms:    Открывает /soulmate/onboarding
          → FunnelUnleashWrapper загружает все флаги
          → activeVariants: { "trial-test": "v1", "flow-test": "short" }

T+500ms:  Экран onboarding рендерится
          → FunnelRuntime собирает флаги для onboarding: []
          → Impression не отправляется (нет AB тестов)

T+10s:    Переходит на /soulmate/payment
          → Экран payment рендерится
          → FunnelRuntime собирает флаги: ["trial-test"]
          → ✅ Отправка: experiment_impression { feature: "trial-test", treatment: "v1" }
          → sessionStorage: "unleash_impression_trial-test_v1" = "true"

T+20s:    Переходит на /soulmate/gender
          → Экран gender рендерится
          → FunnelRuntime собирает флаги: ["flow-test"]
          → ✅ Отправка: experiment_impression { feature: "flow-test", treatment: "short" }
```

### Сценарий 2: Пользователь перезагружает страницу

```
T+0ms:    На экране payment
          → FunnelRuntime пытается отправить impression для "trial-test"
          → sessionStorage.getItem("unleash_impression_trial-test_v1") = "true"
          → ❌ Отправка пропущена (уже было)
          → Console: [Unleash Impression] Skipped (already sent)
```

### Сценарий 3: Без Google Analytics

```
T+0ms:    GA не установлена
          → window.gtag = undefined
          → sendUnleashImpression() проверяет window.gtag
          → ❌ Отправка пропущена (gracefully)
          → Console: [Unleash Impression] Google Analytics not available
          → ✅ AB тесты продолжают работать нормально
```

---

## 🔍 Тестирование

### 1. Проверка impression событий

```bash
# 1. Запустить dev сервер
npm run dev:full

# 2. Открыть консоль браузера
# 3. Увидеть логи:
[Unleash Impression] Sent: { feature: "trial-test", variant: "v1" }

# 4. Перезагрузить страницу (F5)
# 5. Увидеть:
[Unleash Impression] Skipped (already sent): { feature: "trial-test", variant: "v1" }
```

### 2. Проверка sessionStorage

```javascript
// В консоли браузера:
Object.keys(sessionStorage)
  .filter(key => key.startsWith('unleash_impression_'))
  .forEach(key => console.log(key, sessionStorage.getItem(key)));

// Output:
// unleash_impression_trial-test_v1 "true"
// unleash_impression_flow-test_short "true"
```

### 3. Проверка в Google Analytics

```
1. Откройте GA → Realtime → Events
2. Найдите событие: experiment_impression
3. Параметры должны быть:
   - app_name: "witlab-funnel"
   - feature: "trial-test"
   - treatment: "v1"
```

### 4. Очистка для повторного тестирования

```javascript
// В консоли браузера:
Object.keys(sessionStorage)
  .filter(key => key.startsWith('unleash_impression_'))
  .forEach(key => sessionStorage.removeItem(key));
```

---

## 🐛 Debug и мониторинг

### Development логи

```javascript
// При отправке нового impression:
[Unleash Impression] Sent: {
  feature: "trial-button-test",
  variant: "v1"
}

// При попытке отправить дубликат:
[Unleash Impression] Skipped (already sent): {
  feature: "trial-button-test",
  variant: "v1"
}

// Если GA не доступна:
[Unleash Impression] Google Analytics not available
```

### Production мониторинг

```javascript
// Логи отключены в production
// Для мониторинга используйте:
// 1. Google Analytics DebugView
// 2. Network tab (запросы к google-analytics.com)
// 3. Sentry/другие error tracking сервисы
```

---

## 📦 Файлы изменений

| Файл | Изменение | Статус |
|------|-----------|--------|
| `src/lib/funnel/unleash/useUnleash.ts` | Убрана автоотправка impression | ✅ Изменен |
| `src/lib/funnel/unleash/sendImpression.ts` | Новый файл для ручной отправки | ✅ Создан |
| `src/lib/funnel/unleash/index.ts` | Экспорт sendUnleashImpression | ✅ Обновлен |
| `src/components/funnel/FunnelRuntime.tsx` | Логика отправки по экранам | ✅ Изменен |
| `src/lib/funnel/unleash/useScreenUnleash.ts` | Альтернативный подход (не используется) | ℹ️ Создан |

---

## 🎯 Результаты

### ✅ Решенные проблемы:

1. **Преждевременная отправка** - ИСПРАВЛЕНО
   - Было: события для всех экранов сразу
   - Стало: события только для видимых экранов

2. **Дубликаты при перезагрузке** - ИСПРАВЛЕНО
   - Было: повторная отправка при F5
   - Стало: sessionStorage блокирует дубликаты

3. **Завышенные метрики** - ИСПРАВЛЕНО
   - Было: 100% impression vs 20% достигших
   - Стало: impression = реальная видимость

### ✅ Сохраненные преимущества:

1. **Быстрые переходы** - флаги загружаются заранее
2. **Нет задержек** - все готово к моменту рендера
3. **Graceful degradation** - работает без GA
4. **Обратная совместимость** - не ломает существующий код

---

## 🔄 Миграция (если нужно откатить)

Если понадобится вернуться к старой логике:

```typescript
// В useUnleash.ts восстановить:
useEffect(() => {
  unleashClient.on("impression", handleImpression);
  return () => unleashClient.off("impression", handleImpression);
}, [unleashClient]);

// В FunnelRuntime.tsx удалить:
// - currentScreenFlags
// - useEffect с sendUnleashImpression
```

---

## 📚 Связанные документы

- `UNLEASH_ANALYTICS_FLOW.md` - Как работает отправка в GA
- `UNLEASH_ANALYTICS_FIX.md` - Анализ проблемы и варианты решения
- `AB_TESTING_GUIDE.md` - Общее руководство по AB тестам

---

**Дата:** 2025-01-20  
**Статус:** ✅ Реализовано и протестировано  
**Версия:** 1.0
