challenge-pl/memory bank/frontend/CHALLENGE_ANALYTICS_SUMMARY.md
Primakov Alexandr Alexandrovich 3a65307fd0
All checks were successful
platform/bro-js/challenge-pl/pipeline/head This commit looks good
init brojs
2025-11-02 17:44:37 +03:00

18 KiB
Raw Permalink Blame History

Challenge Service - Аналитика и метрики для фронтенда

Краткое руководство по ключевым метрикам и аналитике для интеграции на фронтенде.

📊 Ключевые метрики для отслеживания

1. Метрики производительности

// Метрики для мониторинга
interface PerformanceMetrics {
  // Время от отправки до получения результата
  timeToFeedback: number // миллисекунды
  
  // Время ожидания в очереди
  queueWaitTime: number // миллисекунды
  
  // Время непосредственной проверки
  checkTime: number // миллисекунды
  
  // Позиция в очереди при добавлении
  initialQueuePosition: number
  
  // Количество проверок статуса до завершения
  pollsBeforeComplete: number
}

// Пример сбора метрик
class MetricsCollector {
  private startTime: number = 0
  private pollCount: number = 0
  
  startTracking() {
    this.startTime = Date.now()
    this.pollCount = 0
  }
  
  incrementPoll() {
    this.pollCount++
  }
  
  getMetrics(submission: ChallengeSubmission): PerformanceMetrics {
    return {
      timeToFeedback: Date.now() - this.startTime,
      queueWaitTime: submission.checkedAt 
        ? new Date(submission.checkedAt).getTime() - new Date(submission.submittedAt).getTime()
        : 0,
      checkTime: submission.checkedAt 
        ? new Date(submission.checkedAt).getTime() - new Date(submission.submittedAt).getTime()
        : 0,
      initialQueuePosition: 0, // Сохранить из первого ответа
      pollsBeforeComplete: this.pollCount
    }
  }
}

2. Метрики пользовательского поведения

interface UserBehaviorMetrics {
  // Время, проведенное на задании
  timeSpentOnTask: number // секунды
  
  // Количество символов в решении
  solutionLength: number
  
  // Количество редактирований текста
  editCount: number
  
  // Использовал ли черновик
  usedDraft: boolean
  
  // Время от загрузки до отправки
  timeToSubmit: number // секунды
}

// Трекинг поведения
class BehaviorTracker {
  private taskStartTime: number = Date.now()
  private editCount: number = 0
  private lastValue: string = ''
  
  onTextChange(newValue: string) {
    if (newValue !== this.lastValue) {
      this.editCount++
      this.lastValue = newValue
    }
  }
  
  getMetrics(result: string, usedDraft: boolean): UserBehaviorMetrics {
    return {
      timeSpentOnTask: Math.floor((Date.now() - this.taskStartTime) / 1000),
      solutionLength: result.length,
      editCount: this.editCount,
      usedDraft,
      timeToSubmit: Math.floor((Date.now() - this.taskStartTime) / 1000)
    }
  }
}

3. Метрики успешности

interface SuccessMetrics {
  // Процент принятых заданий с первой попытки
  firstAttemptSuccessRate: number // 0-100
  
  // Среднее количество попыток до успеха
  averageAttemptsToSuccess: number
  
  // Процент завершенных цепочек
  chainCompletionRate: number // 0-100
  
  // Время до первого успешного задания
  timeToFirstSuccess: number // минуты
}

// Расчет метрик успешности
function calculateSuccessMetrics(stats: UserStats): SuccessMetrics {
  const taskStats = stats.taskStats
  
  const firstAttemptSuccess = taskStats.filter(
    t => t.status === 'completed' && t.totalAttempts === 1
  ).length
  
  const completedTasks = taskStats.filter(t => t.status === 'completed')
  const totalAttempts = completedTasks.reduce((sum, t) => sum + t.totalAttempts, 0)
  
  return {
    firstAttemptSuccessRate: (firstAttemptSuccess / taskStats.length) * 100,
    averageAttemptsToSuccess: completedTasks.length > 0 
      ? totalAttempts / completedTasks.length 
      : 0,
    chainCompletionRate: (stats.chainStats.filter(c => c.progress === 100).length / stats.chainStats.length) * 100,
    timeToFirstSuccess: 0 // Требует дополнительных данных
  }
}

📈 Дашборды для фронтенда

1. Personal Dashboard (для студента)

interface PersonalDashboard {
  // Общий прогресс
  overview: {
    tasksCompleted: number
    totalTasks: number
    completionPercentage: number
    currentStreak: number // дней подряд
  }
  
  // Текущие цепочки
  activeChains: Array<{
    chainId: string
    name: string
    progress: number
    nextTask: ChallengeTask | null
    estimatedTimeToComplete: number // минуты
  }>
  
  // Последние достижения
  recentAchievements: Array<{
    type: 'task_completed' | 'chain_completed' | 'first_try_success'
    taskTitle: string
    timestamp: string
  }>
  
  // Статистика по попыткам
  attemptsStats: {
    totalAttempts: number
    successfulAttempts: number
    successRate: number
  }
  
  // Рекомендации
  recommendations: Array<{
    type: 'retry' | 'continue' | 'new_chain'
    message: string
    actionLink: string
  }>
}

// Генерация dashboard
async function generatePersonalDashboard(userId: string): Promise<PersonalDashboard> {
  const stats = await challengeAPI.getUserStats(userId)
  const chains = await challengeAPI.getChains()
  
  return {
    overview: {
      tasksCompleted: stats.completedTasks,
      totalTasks: stats.totalTasksAttempted,
      completionPercentage: (stats.completedTasks / stats.totalTasksAttempted) * 100,
      currentStreak: 0 // Требует дополнительной логики
    },
    activeChains: stats.chainStats
      .filter(c => c.progress > 0 && c.progress < 100)
      .map(c => {
        const chain = chains.find(ch => ch.id === c.chainId)
        const completedCount = c.completedTasks
        const nextTask = chain?.tasks[completedCount] || null
        
        return {
          chainId: c.chainId,
          name: c.chainName,
          progress: c.progress,
          nextTask,
          estimatedTimeToComplete: (c.totalTasks - c.completedTasks) * 10 // 10 мин на задание
        }
      }),
    recentAchievements: [], // Требует истории
    attemptsStats: {
      totalAttempts: stats.totalSubmissions,
      successfulAttempts: stats.completedTasks,
      successRate: (stats.completedTasks / stats.totalSubmissions) * 100
    },
    recommendations: generateRecommendations(stats)
  }
}

function generateRecommendations(stats: UserStats): Array<{type: string, message: string, actionLink: string}> {
  const recommendations = []
  
  // Если есть задания требующие доработки
  if (stats.needsRevisionTasks > 0) {
    recommendations.push({
      type: 'retry',
      message: `У вас ${stats.needsRevisionTasks} заданий требуют доработки`,
      actionLink: '/tasks?status=needs_revision'
    })
  }
  
  // Если есть начатые цепочки
  const inProgressChains = stats.chainStats.filter(c => c.progress > 0 && c.progress < 100)
  if (inProgressChains.length > 0) {
    recommendations.push({
      type: 'continue',
      message: `Продолжите цепочку "${inProgressChains[0].chainName}"`,
      actionLink: `/chain/${inProgressChains[0].chainId}`
    })
  }
  
  return recommendations
}

2. Admin Dashboard (для преподавателя)

interface AdminDashboard {
  // Системные метрики
  system: {
    totalUsers: number
    activeUsers24h: number
    totalTasks: number
    totalChains: number
    queueStatus: {
      length: number
      processing: number
      avgWaitTime: number
    }
  }
  
  // Метрики заданий
  taskMetrics: Array<{
    taskId: string
    title: string
    attemptsCount: number
    successRate: number
    avgAttempts: number
    avgTimeToComplete: number
    difficulty: 'easy' | 'medium' | 'hard' // на основе метрик
  }>
  
  // Активность пользователей
  userActivity: {
    registrationsToday: number
    submissionsToday: number
    peakHours: Array<{ hour: number, count: number }>
  }
  
  // Проблемные области
  issues: Array<{
    type: 'low_success_rate' | 'high_attempts' | 'long_queue'
    severity: 'low' | 'medium' | 'high'
    message: string
    affectedEntity: string
  }>
}

// Анализ сложности задания
function analyzeDifficulty(
  successRate: number,
  avgAttempts: number
): 'easy' | 'medium' | 'hard' {
  if (successRate > 70 && avgAttempts < 2) return 'easy'
  if (successRate > 40 && avgAttempts < 3) return 'medium'
  return 'hard'
}

// Определение проблем
function detectIssues(stats: SystemStats): Array<any> {
  const issues = []
  
  // Длинная очередь
  if (stats.queue.queueLength > 50) {
    issues.push({
      type: 'long_queue',
      severity: 'high',
      message: `Очередь содержит ${stats.queue.queueLength} заданий`,
      affectedEntity: 'system'
    })
  }
  
  // Низкий success rate системы
  const systemSuccessRate = (stats.submissions.accepted / stats.submissions.total) * 100
  if (systemSuccessRate < 30) {
    issues.push({
      type: 'low_success_rate',
      severity: 'medium',
      message: `Общий процент принятых заданий всего ${systemSuccessRate.toFixed(1)}%`,
      affectedEntity: 'system'
    })
  }
  
  return issues
}

🎯 Визуализация метрик

1. Progress Chart (круговая диаграмма)

interface ProgressChartData {
  completed: number
  inProgress: number
  needsRevision: number
  notStarted: number
}

// Компонент для отображения (концепт)
function ProgressChart({ data }: { data: ProgressChartData }) {
  const total = Object.values(data).reduce((a, b) => a + b, 0)
  
  return (
    <div className="progress-chart">
      <svg viewBox="0 0 100 100">
        {/* Реализация круговой диаграммы */}
      </svg>
      <div className="legend">
        <div> Завершено: {data.completed}</div>
        <div>🔄 В процессе: {data.inProgress}</div>
        <div> Доработка: {data.needsRevision}</div>
        <div> Не начато: {data.notStarted}</div>
      </div>
    </div>
  )
}

2. Timeline Chart (время проверки)

interface TimelineData {
  submissions: Array<{
    timestamp: string
    checkTime: number
    status: 'accepted' | 'needs_revision'
  }>
}

// График времени проверки по времени суток
function TimelineChart({ data }: { data: TimelineData }) {
  const hourlyData = new Array(24).fill(0).map((_, hour) => {
    const submissions = data.submissions.filter(s => 
      new Date(s.timestamp).getHours() === hour
    )
    
    return {
      hour,
      count: submissions.length,
      avgCheckTime: submissions.length > 0
        ? submissions.reduce((sum, s) => sum + s.checkTime, 0) / submissions.length
        : 0
    }
  })
  
  return (
    <div className="timeline-chart">
      {/* Реализация bar chart */}
    </div>
  )
}

3. Heatmap (активность по дням)

interface HeatmapData {
  dates: Array<{
    date: string // YYYY-MM-DD
    submissions: number
    successRate: number
  }>
}

// Визуализация активности пользователя
function ActivityHeatmap({ data }: { data: HeatmapData }) {
  return (
    <div className="activity-heatmap">
      {data.dates.map(day => (
        <div 
          key={day.date}
          className="heatmap-cell"
          style={{
            opacity: day.submissions / 10, // Интенсивность цвета
            backgroundColor: day.successRate > 50 ? 'green' : 'red'
          }}
          title={`${day.date}: ${day.submissions} попыток, ${day.successRate}% успех`}
        />
      ))}
    </div>
  )
}

🔔 Real-time уведомления

События для отслеживания

enum ChallengeEventType {
  SUBMISSION_QUEUED = 'submission_queued',
  SUBMISSION_CHECKING = 'submission_checking',
  SUBMISSION_COMPLETED = 'submission_completed',
  TASK_COMPLETED = 'task_completed',
  CHAIN_COMPLETED = 'chain_completed',
  ACHIEVEMENT_UNLOCKED = 'achievement_unlocked'
}

interface ChallengeEvent {
  type: ChallengeEventType
  timestamp: string
  userId: string
  data: any
}

// Event emitter для уведомлений
class ChallengeEventEmitter {
  private listeners: Map<ChallengeEventType, Array<(event: ChallengeEvent) => void>> = new Map()
  
  on(type: ChallengeEventType, callback: (event: ChallengeEvent) => void) {
    if (!this.listeners.has(type)) {
      this.listeners.set(type, [])
    }
    this.listeners.get(type)!.push(callback)
  }
  
  emit(event: ChallengeEvent) {
    const callbacks = this.listeners.get(event.type) || []
    callbacks.forEach(cb => cb(event))
  }
}

// Использование
const events = new ChallengeEventEmitter()

events.on(ChallengeEventType.TASK_COMPLETED, (event) => {
  // Показать toast уведомление
  showNotification('✅ Задание выполнено!', 'success')
  
  // Обновить статистику
  refreshStats()
  
  // Отправить аналитику
  analytics.track('task_completed', event.data)
})

📱 Адаптивная аналитика

Мобильная версия дашборда

interface MobileDashboard {
  // Упрощенные метрики для мобильных
  quickStats: {
    completedToday: number
    currentStreak: number
    nextTask: string
  }
  
  // Минимальные графики
  weekProgress: number[] // 7 последних дней
  
  // Быстрые действия
  quickActions: Array<{
    label: string
    action: () => void
    icon: string
  }>
}

🎨 UI Components для метрик

Stat Card Component

interface StatCardProps {
  title: string
  value: number | string
  change?: number // % изменение
  trend?: 'up' | 'down'
  icon?: string
}

function StatCard({ title, value, change, trend, icon }: StatCardProps) {
  return (
    <div className="stat-card">
      <div className="stat-header">
        <span className="stat-icon">{icon}</span>
        <span className="stat-title">{title}</span>
      </div>
      <div className="stat-value">{value}</div>
      {change && (
        <div className={`stat-change ${trend}`}>
          {trend === 'up' ? '↑' : '↓'} {Math.abs(change)}%
        </div>
      )}
    </div>
  )
}

// Использование
<StatCard 
  title="Задания завершено"
  value={42}
  change={15}
  trend="up"
  icon="✅"
/>

🔍 A/B Testing

Метрики для тестирования

interface ABTestMetrics {
  variant: 'A' | 'B'
  
  // Конверсионные метрики
  submissionRate: number // % пользователей, отправивших хотя бы одно задание
  completionRate: number // % завершенных заданий
  retryRate: number // % повторных попыток
  
  // Временные метрики
  timeToFirstSubmission: number
  sessionDuration: number
  
  // Качественные метрики
  satisfactionScore?: number // если есть опрос
}

// Сравнение вариантов
function compareVariants(variantA: ABTestMetrics, variantB: ABTestMetrics) {
  return {
    submissionRateDiff: ((variantB.submissionRate - variantA.submissionRate) / variantA.submissionRate) * 100,
    completionRateDiff: ((variantB.completionRate - variantA.completionRate) / variantA.completionRate) * 100,
    winner: variantB.completionRate > variantA.completionRate ? 'B' : 'A'
  }
}

📊 Экспорт данных

CSV Export

async function exportUserProgress(userId: string): Promise<string> {
  const stats = await challengeAPI.getUserStats(userId)
  const submissions = await challengeAPI.getSubmissions(userId)
  
  let csv = 'Task,Status,Attempts,Last Attempt,Feedback\n'
  
  stats.taskStats.forEach(task => {
    csv += `"${task.taskTitle}","${task.status}",${task.totalAttempts},"${task.lastAttemptAt || 'N/A'}",""\n`
  })
  
  return csv
}

// Скачивание файла
function downloadCSV(csv: string, filename: string) {
  const blob = new Blob([csv], { type: 'text/csv' })
  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = filename
  link.click()
  URL.revokeObjectURL(url)
}

Чек-лист для фронтенд-разработчика

  • Интегрировать API клиент
  • Настроить Context для state management
  • Реализовать polling механизм
  • Добавить Personal Dashboard
  • Создать визуализации прогресса
  • Настроить event tracking
  • Добавить offline support
  • Реализовать экспорт данных
  • Добавить A/B тестирование
  • Настроить мониторинг ошибок
  • Оптимизировать для мобильных
  • Добавить accessibility features

📚 Полезные ресурсы

  • API документация: CHALLENGE_API_README.md
  • Архитектура: CHALLENGE_ARCHITECTURE.md
  • React пример: CHALLENGE_REACT_EXAMPLE.md
  • Быстрый старт: CHALLENGE_QUICK_START.md

Используйте эти метрики и компоненты для создания информативного и user-friendly интерфейса!