DEV Community

Long Phan
Long Phan

Posted on

Real-Time Donation Leaderboard with AI Predictions: Powered by Redis 8

Redis AI Challenge: Real-Time AI Innovators

This is a submission for the Redis AI Challenge: Real-Time AI Innovators.

What I Built

I created a Real-Time Donation Leaderboard that combines the power of Redis 8 with OpenAI to deliver instant donation tracking, live leaderboards, and AI-powered predictions. This full-stack TypeScript application demonstrates how Redis can serve as the backbone for real-time applications while enabling intelligent insights through AI integration.

Key Features:

  • Real-time donation tracking with instant leaderboard updates
  • 🏆 Live leaderboard with automatic polling every 3 seconds
  • 🤖 AI-powered predictions for rising donors and daily totals
  • 📊 Comprehensive analytics with donation patterns and trends
  • 🎨 Modern responsive UI with smooth animations
  • 🔒 Production-ready with security, error handling, and health checks

Demo

🔗 Links

📱 Screenshots

  • Main leaderboard interface

Image 1

  • Donation form in action

Image 2

  • AI predictions panel

Image 3

  • Mobile responsive design

Image 4

  • Real-time updates in progress

Image 5

How I Used Redis 8

Redis 8 serves as the complete real-time data layer for this application, leveraging multiple Redis data structures and features:

🏗️ Core Redis Architecture

1. Sorted Sets for Leaderboard Rankings

// Real-time leaderboard updates using Redis Sorted Sets
// Increment donor's total amount atomically
multi.zIncrBy('leaderboard', donation.amount, donation.donorId);

// Get top donors with scores (descending order)
const results = await this.client.zRangeWithScores('leaderboard', 0, limit - 1, {
  REV: true,
});
Enter fullscreen mode Exit fullscreen mode

2. Streams for Event Sourcing

// Track all donation events with Redis Streams
multi.xAdd('donations:stream', '*', {
  donorId: donation.donorId,
  donorName: donation.donorName,
  amount: donation.amount.toString(),
  message: donation.message || '',
  timestamp: donation.timestamp.toString(),
});

// Retrieve recent donations for AI analysis
const streamData = await this.client.xRange('donations:stream', '-', '+');
Enter fullscreen mode Exit fullscreen mode

3. Hash Maps for Donor Information

// Store donor name mapping efficiently
multi.hSet('donor:names', donation.donorId, donation.donorName);

// Get donor names for leaderboard display
const donorIds = results.map(item => item.value);
const names = await this.client.hmGet('donor:names', donorIds);
Enter fullscreen mode Exit fullscreen mode

4. Atomic Multi-Operations

// Use Redis multi for atomic operations
const multi = this.client.multi();

// Add to streams for event history
multi.xAdd('donations:stream', '*', donationData);

// Update leaderboard (sorted set)
multi.zIncrBy('leaderboard', donation.amount, donation.donorId);

// Store donor name mapping
multi.hSet('donor:names', donation.donorId, donation.donorName);

// Execute all commands atomically
await multi.exec();
Enter fullscreen mode Exit fullscreen mode

🚀 Real-Time Performance Benefits

Connection Management with Retry Logic:

this.client = createClient({
  username: process.env.REDIS_USERNAME || 'default',
  password: process.env.REDIS_PASSWORD || '',
  socket: {
    host: process.env.REDIS_URL || 'localhost',
    port: parseInt(process.env.REDIS_PORT || '6379'),
    reconnectStrategy: retries => Math.min(retries * 50, 500),
  },
});
Enter fullscreen mode Exit fullscreen mode

Health Monitoring:

async ping(): Promise<boolean> {
  try {
    await this.ensureConnection();
    const result = await this.client.ping();
    return result === 'PONG';
  } catch (error) {
    console.error('Redis ping failed:', error);
    return false;
  }
}
Enter fullscreen mode Exit fullscreen mode

🤖 AI Integration with Redis

Redis serves as the intelligent data pipeline for AI predictions:

async generatePredictions(): Promise<PredictionData> {
  // Get current leaderboard and recent donations from Redis
  const [leaderboard, recentDonations, totalDonations] = await Promise.all([
    redisService.getLeaderboard(20),
    redisService.getRecentDonations(24),
    redisService.getTotalDonations(),
  ]);

  // Prepare data for AI analysis
  const donationTrends = this.analyzeDonationTrends(recentDonations);
  const leaderboardData = leaderboard.slice(0, 10);

  // Generate AI predictions using OpenAI
  const completion = await this.openai.chat.completions.create({
    model: 'gpt-3.5-turbo',
    messages: [
      {
        role: 'system',
        content: 'You are an AI analyst specializing in donation pattern analysis and predictions.',
      },
      {
        role: 'user',
        content: this.buildPredictionPrompt(leaderboardData, donationTrends, totalDonations),
      },
    ],
    temperature: 0.3,
    max_tokens: 1000,
  });
}
Enter fullscreen mode Exit fullscreen mode

📊 Data Analysis from Redis Streams

private analyzeDonationTrends(donations: Donation[]): any {
  const hourlyTrends: { [hour: string]: number } = {};
  const donorActivity: { [donorId: string]: { count: number; total: number; recent: number } } = {};

  const now = Date.now();
  const oneHour = 60 * 60 * 1000;

  donations.forEach(donation => {
    const hour = Math.floor(donation.timestamp / oneHour);
    hourlyTrends[hour] = (hourlyTrends[hour] || 0) + donation.amount;

    if (!donorActivity[donation.donorId]) {
      donorActivity[donation.donorId] = { count: 0, total: 0, recent: 0 };
    }

    donorActivity[donation.donorId].count++;
    donorActivity[donation.donorId].total += donation.amount;

    // Count recent activity (last 6 hours)
    if (now - donation.timestamp < 6 * oneHour) {
      donorActivity[donation.donorId].recent += donation.amount;
    }
  });

  return { hourlyTrends, donorActivity };
}
Enter fullscreen mode Exit fullscreen mode

Technical Architecture

🏗️ Full Stack Implementation

Backend: Node.js + Express + TypeScript

  • RESTful API with comprehensive error handling
  • Redis integration with connection pooling
  • OpenAI API integration for predictions
  • Security middleware (Helmet, CORS)
  • Health monitoring and graceful shutdown

Frontend: React 18 + TypeScript + Vite

  • Real-time polling every 3 seconds
  • Responsive design with SCSS
  • Smooth animations and loading states
  • Error boundaries and user feedback
  • Accessibility-compliant interface

Database: Redis as primary data store

  • Sorted Sets for rankings
  • Streams for event history
  • Hash Maps for metadata
  • Atomic operations for consistency

🔧 Key Technical Decisions

Why Redis Over Traditional Databases?

  1. Performance: In-memory operations for instant leaderboard updates
  2. Real-time: Native support for live data structures
  3. Simplicity: Single data store for multiple use cases
  4. AI-Ready: Fast data aggregation for ML pipelines

Actual Redis Data Model

leaderboard (Sorted Set)
├── john_doe → 1500.00
├── jane_smith → 1200.50
└── bob_johnson → 950.25

donations:stream (Stream)
├── 1691234567890-0: {donorId: "john_doe", donorName: "John Doe", amount: "25.00", ...}
├── 1691234567891-0: {donorId: "jane_smith", donorName: "Jane Smith", amount: "50.00", ...}
└── 1691234567892-0: {donorId: "john_doe", donorName: "John Doe", amount: "75.00", ...}

donor:names (Hash)
├── "john_doe" → "John Doe"
├── "jane_smith" → "Jane Smith"
└── "bob_johnson" → "Bob Johnson"
Enter fullscreen mode Exit fullscreen mode

🚀 Performance Optimizations

Connection Management with Retry Logic

this.client = createClient({
  username: process.env.REDIS_USERNAME || 'default',
  password: process.env.REDIS_PASSWORD || '',
  socket: {
    host: process.env.REDIS_URL || 'localhost',
    port: parseInt(process.env.REDIS_PORT || '6379'),
    reconnectStrategy: retries => Math.min(retries * 50, 500),
  },
});
Enter fullscreen mode Exit fullscreen mode

Atomic Multi-Operations

// Use Redis multi for atomic operations
const multi = this.client.multi();

// Add to streams for event history
multi.xAdd('donations:stream', '*', {
  donorId: donation.donorId,
  donorName: donation.donorName,
  amount: donation.amount.toString(),
  message: donation.message || '',
  timestamp: donation.timestamp.toString(),
});

// Update leaderboard (sorted set)
multi.zIncrBy('leaderboard', donation.amount, donation.donorId);

// Store donor name mapping
multi.hSet('donor:names', donation.donorId, donation.donorName);

// Execute all commands atomically
await multi.exec();
Enter fullscreen mode Exit fullscreen mode

Efficient Data Retrieval

// Get leaderboard with scores in one operation
const results = await this.client.zRangeWithScores('leaderboard', 0, limit - 1, {
  REV: true,
});

// Batch get donor names
const donorIds = results.map(item => item.value);
const names = await this.client.hmGet('donor:names', donorIds);
Enter fullscreen mode Exit fullscreen mode

AI-Powered Features

🤖 Intelligent Predictions

The application uses OpenAI GPT-3.5-turbo to analyze donation patterns and generate predictions:

Real AI Implementation

async generatePredictions(): Promise<PredictionData> {
  try {
    // Get current leaderboard and recent donations from Redis
    const [leaderboard, recentDonations, totalDonations] = await Promise.all([
      redisService.getLeaderboard(20),
      redisService.getRecentDonations(24),
      redisService.getTotalDonations(),
    ]);

    // Prepare data for AI analysis
    const donationTrends = this.analyzeDonationTrends(recentDonations);
    const leaderboardData = leaderboard.slice(0, 10);

    // Generate AI predictions using OpenAI
    const completion = await this.openai.chat.completions.create({
      model: 'gpt-3.5-turbo',
      messages: [
        {
          role: 'system',
          content: 'You are an AI analyst specializing in donation pattern analysis and predictions.',
        },
        {
          role: 'user',
          content: this.buildPredictionPrompt(leaderboardData, donationTrends, totalDonations),
        },
      ],
      temperature: 0.3,
      max_tokens: 1000,
    });

    const response = completion.choices[0]?.message?.content;
    const predictions = JSON.parse(response);

    return this.validateAndEnhancePredictions(predictions, leaderboard, totalDonations);
  } catch (error) {
    console.error('AI prediction error:', error);
    return this.generateFallbackPredictions();
  }
}
Enter fullscreen mode Exit fullscreen mode

Data Analysis Pipeline

private analyzeDonationTrends(donations: Donation[]): any {
  const hourlyTrends: { [hour: string]: number } = {};
  const donorActivity: { [donorId: string]: { count: number; total: number; recent: number } } = {};

  const now = Date.now();
  const oneHour = 60 * 60 * 1000;

  donations.forEach(donation => {
    const hour = Math.floor(donation.timestamp / oneHour);
    hourlyTrends[hour] = (hourlyTrends[hour] || 0) + donation.amount;

    if (!donorActivity[donation.donorId]) {
      donorActivity[donation.donorId] = { count: 0, total: 0, recent: 0 };
    }

    donorActivity[donation.donorId].count++;
    donorActivity[donation.donorId].total += donation.amount;

    // Count recent activity (last 6 hours)
    if (now - donation.timestamp < 6 * oneHour) {
      donorActivity[donation.donorId].recent += donation.amount;
    }
  });

  return {
    hourlyTrends,
    donorActivity,
    totalDonations: donations.length,
    totalAmount: donations.reduce((sum, d) => sum + d.amount, 0),
  };
}
Enter fullscreen mode Exit fullscreen mode

AI Prompt Engineering

private buildPredictionPrompt(leaderboard: LeaderboardEntry[], trends: any, totalDonations: number): string {
  return `
Analyze the following donation data and provide predictions in JSON format:

CURRENT LEADERBOARD:
${leaderboard.map(entry => `${entry.rank}. ${entry.donorName}: $${entry.totalAmount}`).join('\n')}

DONATION TRENDS:
- Total donations today: $${totalDonations}
- Recent donor activity: ${JSON.stringify(trends.donorActivity, null, 2)}
- Hourly trends: ${JSON.stringify(trends.hourlyTrends, null, 2)}

Please provide predictions in this EXACT JSON format:
{
  "risingStars": [
    {
      "donorId": "donor_id",
      "donorName": "Donor Name", 
      "currentAmount": 100,
      "predictedGrowth": 25.5,
      "confidence": 0.75
    }
  ],
  "dailyTotal": {
    "currentTotal": ${totalDonations},
    "predictedTotal": estimated_end_of_day_total,
    "confidence": 0.8,
    "timeRemaining": "X hours remaining"
  }
}
`;
}
Enter fullscreen mode Exit fullscreen mode

🧠 Fallback Intelligence

When OpenAI is unavailable, the system uses Redis-based heuristics:

private async generateFallbackPredictions(): Promise<PredictionData> {
  const [leaderboard, totalDonations] = await Promise.all([
    redisService.getLeaderboard(10),
    redisService.getTotalDonations(),
  ]);

  // Simple heuristic-based predictions
  const risingStars = leaderboard
    .slice(3, 8) // Pick middle-ranking donors as potential rising stars
    .map(entry => ({
      donorId: entry.donorId,
      donorName: entry.donorName,
      currentAmount: entry.totalAmount,
      predictedGrowth: Math.random() * 50 + 10, // Random growth between 10-60
      confidence: 0.5,
    }));

  const currentHour = new Date().getHours();
  const hoursRemaining = 24 - currentHour;
  const growthFactor = 1 + (Math.random() * 0.4 + 0.1); // 10-50% growth

  return {
    risingStars,
    dailyTotal: {
      currentTotal: totalDonations,
      predictedTotal: Math.round(totalDonations * growthFactor),
      confidence: 0.6,
      timeRemaining: `${hoursRemaining} hours remaining`,
    },
  };
}
Enter fullscreen mode Exit fullscreen mode

Prediction Validation

private validateAndEnhancePredictions(
  predictions: any,
  leaderboard: LeaderboardEntry[],
  totalDonations: number,
): PredictionData {
  // Ensure rising stars exist and are valid
  const risingStars = (predictions.risingStars || [])
    .filter((star: any) => star.donorId && star.donorName)
    .slice(0, 5)
    .map((star: any) => ({
      donorId: star.donorId,
      donorName: star.donorName,
      currentAmount: Math.max(0, star.currentAmount || 0),
      predictedGrowth: Math.max(0, Math.min(1000, star.predictedGrowth || 0)),
      confidence: Math.max(0.1, Math.min(0.9, star.confidence || 0.5)),
    }));

  // Validate daily total prediction
  const currentHour = new Date().getHours();
  const hoursRemaining = 24 - currentHour;

  const dailyTotal = {
    currentTotal: totalDonations,
    predictedTotal: Math.max(totalDonations, predictions.dailyTotal?.predictedTotal || totalDonations * 1.2),
    confidence: Math.max(0.1, Math.min(0.9, predictions.dailyTotal?.confidence || 0.6)),
    timeRemaining: `${hoursRemaining} hours remaining`,
  };

  return { risingStars, dailyTotal };
}
Enter fullscreen mode Exit fullscreen mode

Real-World Impact

📈 Technical Benefits

  • Response Time: Fast in-memory Redis operations
  • Data Consistency: Atomic multi-operations ensure data integrity
  • Scalability: Redis can handle high concurrent loads
  • Memory Efficiency: Optimized data structures for leaderboard use case
  • Real-time Updates: 3-second polling provides near real-time experience

🎯 Use Cases

This architecture is perfect for:

  • Charity Fundraising: Real-time donation tracking and leaderboards
  • Gaming Leaderboards: Live player rankings and achievements
  • Sales Competitions: Team performance tracking and motivation
  • Social Media: Trending content and engagement rankings
  • E-commerce: Real-time inventory and sales tracking

🔮 Future Enhancements

  1. WebSocket Integration: Replace polling with real-time push notifications
  2. Redis Pub/Sub: Instant updates to all connected clients
  3. Advanced Analytics: More sophisticated trend analysis
  4. Machine Learning: Custom prediction models trained on historical data
  5. Geographic Analytics: Location-based donation insights
  6. Mobile App: Native mobile applications with push notifications

Getting Started

🚀 Quick Setup

# Clone the repository
git clone [your-repo-url]
cd leaderboard-donation

# Run automated setup
./setup.sh

# Configure environment variables
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env

# Start the application
# Terminal 1: Backend
cd backend && npm run dev

# Terminal 2: Frontend  
cd frontend && npm run dev

# Optional: Seed test data
cd backend && npm run seed
Enter fullscreen mode Exit fullscreen mode

🔧 Configuration

Backend (.env):

PORT=3001
REDIS_URL=redis://your-redis-host:6379
REDIS_PASSWORD=your-redis-password
OPENAI_API_KEY=your-openai-api-key
Enter fullscreen mode Exit fullscreen mode

Frontend (.env):

VITE_API_URL=http://localhost:3001
Enter fullscreen mode Exit fullscreen mode

📚 API Implementation

Donation Submission

async submitDonation(req: Request, res: Response): Promise<void> {
  try {
    const { donorName, amount, message }: DonationRequest = req.body;

    // Validation
    if (!donorName || typeof donorName !== 'string' || donorName.trim().length === 0) {
      res.status(400).json({ error: 'Donor name is required' });
      return;
    }

    if (!amount || typeof amount !== 'number' || amount <= 0) {
      res.status(400).json({ error: 'Valid donation amount is required' });
      return;
    }

    if (amount > 10000) {
      res.status(400).json({ error: 'Donation amount cannot exceed $10,000' });
      return;
    }

    // Create donation object
    const donation: Donation = {
      id: uuidv4(),
      donorId: donorName.toLowerCase().replace(/\s+/g, '_'),
      donorName: donorName.trim(),
      amount: Math.round(amount * 100) / 100, // Round to 2 decimal places
      message: message?.trim(),
      timestamp: Date.now(),
    };

    // Save to Redis
    await redisService.addDonation(donation);

    res.status(201).json({
      success: true,
      donation: {
        id: donation.id,
        donorName: donation.donorName,
        amount: donation.amount,
        message: donation.message,
        timestamp: donation.timestamp,
      },
    });
  } catch (error) {
    console.error('Error submitting donation:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
}
Enter fullscreen mode Exit fullscreen mode

Leaderboard Retrieval

async getLeaderboard(req: Request, res: Response): Promise<void> {
  try {
    const limit = parseInt(req.query.limit as string) || 10;

    if (limit > 50) {
      res.status(400).json({ error: 'Limit cannot exceed 50' });
      return;
    }

    const leaderboard = await redisService.getLeaderboard(limit);

    res.json({
      success: true,
      leaderboard,
      timestamp: Date.now(),
    });
  } catch (error) {
    console.error('Error fetching leaderboard:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
}
Enter fullscreen mode Exit fullscreen mode

Statistics Calculation

async getStats(req: Request, res: Response): Promise<void> {
  try {
    const [totalDonations, recentDonations] = await Promise.all([
      redisService.getTotalDonations(),
      redisService.getRecentDonations(24),
    ]);

    const stats = {
      totalDonations,
      donationsToday: recentDonations.length,
      averageDonation:
        recentDonations.length > 0
          ? recentDonations.reduce((sum, d) => sum + d.amount, 0) / recentDonations.length
          : 0,
    };

    res.json({
      success: true,
      stats,
      timestamp: Date.now(),
    });
  } catch (error) {
    console.error('Error fetching stats:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

This project demonstrates how Redis 8 can serve as a complete real-time data platform, enabling both high-performance applications and intelligent AI integrations. By leveraging Redis's diverse data structures and combining them with OpenAI's predictive capabilities, we've created a system that's not just fast and reliable, but also intelligent and insightful.

The combination of Redis's sub-millisecond performance with AI-powered predictions creates a new category of applications that are both reactive and predictive - responding instantly to user actions while providing intelligent insights about future trends.

🏆 Why This Matters

In today's real-time world, users expect instant feedback and intelligent insights. This project shows how Redis 8 makes it possible to deliver both, creating applications that are not just fast, but smart.

Redis isn't just a cache anymore - it's an intelligent real-time data platform.


Built with ❤️ using Redis 8, OpenAI, TypeScript, React, and Node.js

Top comments (0)