# Challenge Service - Аналитика и метрики для фронтенда Краткое руководство по ключевым метрикам и аналитике для интеграции на фронтенде. ## 📊 Ключевые метрики для отслеживания ### 1. Метрики производительности ```typescript // Метрики для мониторинга 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. Метрики пользовательского поведения ```typescript 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. Метрики успешности ```typescript 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 (для студента) ```typescript 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 { 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 (для преподавателя) ```typescript 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 { 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 (круговая диаграмма) ```typescript 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 (
{/* Реализация круговой диаграммы */}
✅ Завершено: {data.completed}
🔄 В процессе: {data.inProgress}
❌ Доработка: {data.needsRevision}
⚪ Не начато: {data.notStarted}
) } ``` ### 2. Timeline Chart (время проверки) ```typescript 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 (
{/* Реализация bar chart */}
) } ``` ### 3. Heatmap (активность по дням) ```typescript interface HeatmapData { dates: Array<{ date: string // YYYY-MM-DD submissions: number successRate: number }> } // Визуализация активности пользователя function ActivityHeatmap({ data }: { data: HeatmapData }) { return (
{data.dates.map(day => (
50 ? 'green' : 'red' }} title={`${day.date}: ${day.submissions} попыток, ${day.successRate}% успех`} /> ))}
) } ``` ## 🔔 Real-time уведомления ### События для отслеживания ```typescript 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 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) }) ``` ## 📱 Адаптивная аналитика ### Мобильная версия дашборда ```typescript 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 ```typescript interface StatCardProps { title: string value: number | string change?: number // % изменение trend?: 'up' | 'down' icon?: string } function StatCard({ title, value, change, trend, icon }: StatCardProps) { return (
{icon} {title}
{value}
{change && (
{trend === 'up' ? '↑' : '↓'} {Math.abs(change)}%
)}
) } // Использование ``` ## 🔍 A/B Testing ### Метрики для тестирования ```typescript 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 ```typescript async function exportUserProgress(userId: string): Promise { 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 интерфейса!