File size: 6,740 Bytes
6f7e932
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
"""
Enhanced Caching System for Predictions

Provides intelligent caching with:
- Redis-compatible in-memory cache
- TTL-based expiration
- Automatic cache invalidation
- Performance metrics
"""

import time
import hashlib
import json
from datetime import datetime, timedelta
from typing import Any, Optional, Dict, Callable
from functools import wraps
import threading


class PredictionCache:
    """High-performance prediction cache with TTL support"""
    
    def __init__(self, default_ttl: int = 300):  # 5 minutes default
        self._cache: Dict[str, Dict] = {}
        self._lock = threading.RLock()
        self.default_ttl = default_ttl
        self.hits = 0
        self.misses = 0
        
    def _generate_key(self, *args, **kwargs) -> str:
        """Generate cache key from args"""
        key_data = json.dumps({'args': args, 'kwargs': kwargs}, sort_keys=True)
        return hashlib.md5(key_data.encode()).hexdigest()
    
    def get(self, key: str) -> Optional[Any]:
        """Get value from cache"""
        with self._lock:
            if key in self._cache:
                entry = self._cache[key]
                if entry['expires'] > time.time():
                    self.hits += 1
                    return entry['value']
                else:
                    del self._cache[key]
            self.misses += 1
            return None
    
    def set(self, key: str, value: Any, ttl: Optional[int] = None) -> None:
        """Set value in cache with TTL"""
        with self._lock:
            self._cache[key] = {
                'value': value,
                'expires': time.time() + (ttl or self.default_ttl),
                'created': time.time()
            }
    
    def delete(self, key: str) -> bool:
        """Delete key from cache"""
        with self._lock:
            if key in self._cache:
                del self._cache[key]
                return True
            return False
    
    def clear(self) -> int:
        """Clear all cache entries"""
        with self._lock:
            count = len(self._cache)
            self._cache.clear()
            return count
    
    def cleanup_expired(self) -> int:
        """Remove expired entries"""
        with self._lock:
            now = time.time()
            expired = [k for k, v in self._cache.items() if v['expires'] <= now]
            for key in expired:
                del self._cache[key]
            return len(expired)
    
    def get_stats(self) -> Dict:
        """Get cache statistics"""
        with self._lock:
            total_requests = self.hits + self.misses
            hit_rate = (self.hits / total_requests * 100) if total_requests > 0 else 0
            return {
                'entries': len(self._cache),
                'hits': self.hits,
                'misses': self.misses,
                'hit_rate': round(hit_rate, 2),
                'memory_usage': self._estimate_memory()
            }
    
    def _estimate_memory(self) -> str:
        """Estimate memory usage"""
        try:
            import sys
            size = sys.getsizeof(self._cache)
            for v in self._cache.values():
                size += sys.getsizeof(v)
            if size < 1024:
                return f"{size} B"
            elif size < 1024 * 1024:
                return f"{size / 1024:.1f} KB"
            else:
                return f"{size / (1024 * 1024):.1f} MB"
        except:
            return "Unknown"


# Global cache instances
prediction_cache = PredictionCache(default_ttl=300)  # 5 min for predictions
fixtures_cache = PredictionCache(default_ttl=600)    # 10 min for fixtures
odds_cache = PredictionCache(default_ttl=60)         # 1 min for odds


def cache_prediction(ttl: int = 300):
    """Decorator to cache prediction results"""
    def decorator(func: Callable):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # Generate cache key
            key = prediction_cache._generate_key(func.__name__, *args, **kwargs)
            
            # Check cache
            cached = prediction_cache.get(key)
            if cached is not None:
                cached['from_cache'] = True
                return cached
            
            # Call function
            result = func(*args, **kwargs)
            
            # Cache result
            if result:
                prediction_cache.set(key, result, ttl)
            
            return result
        return wrapper
    return decorator


def cache_fixtures(ttl: int = 600):
    """Decorator to cache fixture results"""
    def decorator(func: Callable):
        @wraps(func)
        def wrapper(*args, **kwargs):
            key = fixtures_cache._generate_key(func.__name__, *args, **kwargs)
            cached = fixtures_cache.get(key)
            if cached is not None:
                return cached
            result = func(*args, **kwargs)
            if result:
                fixtures_cache.set(key, result, ttl)
            return result
        return wrapper
    return decorator


def invalidate_prediction_cache(home: str = None, away: str = None, league: str = None):
    """Invalidate cache entries matching criteria"""
    # For simplicity, clear all if specific criteria
    if home or away or league:
        prediction_cache.clear()
    return True


class RealTimeUpdater:
    """Real-time update manager for live data"""
    
    def __init__(self):
        self.subscribers: Dict[str, list] = {}
        self.last_updates: Dict[str, float] = {}
        
    def subscribe(self, channel: str, callback: Callable):
        """Subscribe to a channel"""
        if channel not in self.subscribers:
            self.subscribers[channel] = []
        self.subscribers[channel].append(callback)
        
    def publish(self, channel: str, data: Any):
        """Publish data to channel subscribers"""
        self.last_updates[channel] = time.time()
        if channel in self.subscribers:
            for callback in self.subscribers[channel]:
                try:
                    callback(data)
                except Exception as e:
                    print(f"Subscriber error: {e}")
                    
    def get_channels(self) -> list:
        """Get list of active channels"""
        return list(self.subscribers.keys())


# Global real-time updater
realtime = RealTimeUpdater()


def get_cache_stats() -> Dict:
    """Get combined cache statistics"""
    return {
        'predictions': prediction_cache.get_stats(),
        'fixtures': fixtures_cache.get_stats(),
        'odds': odds_cache.get_stats(),
        'total_entries': (
            len(prediction_cache._cache) + 
            len(fixtures_cache._cache) + 
            len(odds_cache._cache)
        )
    }