@gotza02/sequential-thinking 10000.0.8 → 10000.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,24 +1,26 @@
1
1
  /**
2
- * SPORTS MODULE CACHE SERVICE
3
- * TTL-based caching with file persistence for the football intelligence system
2
+ * ENHANCED CACHE SERVICE
3
+ * TTL-based caching with stale-while-revalidate pattern
4
4
  */
5
5
  import * as fs from 'fs/promises';
6
6
  import * as path from 'path';
7
7
  import { CACHE_CONFIG } from './constants.js';
8
8
  import { logger, createSafeRegex } from '../../../utils.js';
9
9
  /**
10
- * CacheService - Thread-safe TTL-based caching with persistence
10
+ * CacheService - Thread-safe TTL-based caching with stale-while-revalidate
11
11
  */
12
12
  export class CacheService {
13
13
  cache = new Map();
14
14
  cachePath;
15
15
  maxAge;
16
+ staleAge;
16
17
  maxSize;
17
18
  savePending = false;
18
19
  saveTimer = null;
19
- constructor(cachePath = CACHE_CONFIG.CACHE_PATH, maxAge = CACHE_CONFIG.DEFAULT_TTL, maxSize = CACHE_CONFIG.MAX_SIZE) {
20
+ constructor(cachePath = CACHE_CONFIG.CACHE_PATH, maxAge = CACHE_CONFIG.DEFAULT_TTL, maxSize = CACHE_CONFIG.MAX_SIZE, staleAge) {
20
21
  this.cachePath = path.resolve(cachePath);
21
22
  this.maxAge = maxAge;
23
+ this.staleAge = staleAge || maxAge * 2;
22
24
  this.maxSize = maxSize;
23
25
  this.loadFromFile();
24
26
  }
@@ -36,11 +38,12 @@ export class CacheService {
36
38
  if (!entry) {
37
39
  return null;
38
40
  }
39
- // Check if expired
41
+ // Check if expired (including stale period)
40
42
  const now = Date.now();
41
- if (now - entry.timestamp > entry.ttl) {
43
+ const totalTtl = entry.ttl + entry.staleTtl;
44
+ if (now - entry.timestamp > totalTtl) {
42
45
  this.cache.delete(key);
43
- logger.debug(`Cache expired: ${key}`);
46
+ logger.debug(`Cache expired (including stale): ${key}`);
44
47
  return null;
45
48
  }
46
49
  // Increment hit counter
@@ -51,7 +54,7 @@ export class CacheService {
51
54
  /**
52
55
  * Set a value in cache
53
56
  */
54
- set(key, data, ttl) {
57
+ set(key, data, ttl, staleTtl) {
55
58
  const now = Date.now();
56
59
  // Check if we need to evict old entries
57
60
  if (this.cache.size >= this.maxSize && !this.cache.has(key)) {
@@ -62,11 +65,64 @@ export class CacheService {
62
65
  data,
63
66
  timestamp: now,
64
67
  ttl: ttl || this.maxAge,
68
+ staleTtl: staleTtl || this.staleAge,
65
69
  hits: 0,
66
70
  });
67
- logger.debug(`Cache set: ${key} (TTL: ${ttl || this.maxAge}ms)`);
71
+ logger.debug(`Cache set: ${key} (TTL: ${ttl || this.maxAge}ms, Stale: ${staleTtl || this.staleAge}ms)`);
68
72
  this.scheduleSave();
69
73
  }
74
+ /**
75
+ * Get or set with stale-while-revalidate pattern
76
+ * Returns cached value immediately, refreshes in background if stale
77
+ */
78
+ async getOrSetWithStaleRevalidate(key, factory, ttl, staleTtl) {
79
+ const entry = this.cache.get(key);
80
+ const now = Date.now();
81
+ const effectiveTtl = ttl || this.maxAge;
82
+ const effectiveStaleTtl = staleTtl || this.staleAge;
83
+ if (entry) {
84
+ const age = now - entry.timestamp;
85
+ // Data is fresh - use it
86
+ if (age < effectiveTtl) {
87
+ entry.hits++;
88
+ return entry.data;
89
+ }
90
+ // Data is stale but within stale TTL - use it and refresh in background
91
+ if (age < effectiveTtl + effectiveStaleTtl) {
92
+ entry.hits++;
93
+ if (!entry.isRefreshing) {
94
+ this.refreshInBackground(key, factory, effectiveTtl, effectiveStaleTtl);
95
+ }
96
+ return entry.data;
97
+ }
98
+ }
99
+ // No data or expired beyond stale TTL - fetch new data
100
+ const value = await factory();
101
+ this.set(key, value, effectiveTtl, effectiveStaleTtl);
102
+ return value;
103
+ }
104
+ /**
105
+ * Refresh data in background
106
+ */
107
+ async refreshInBackground(key, factory, ttl, staleTtl) {
108
+ const entry = this.cache.get(key);
109
+ if (entry) {
110
+ entry.isRefreshing = true;
111
+ }
112
+ try {
113
+ const value = await factory();
114
+ this.set(key, value, ttl, staleTtl);
115
+ logger.debug(`Background refresh successful: ${key}`);
116
+ }
117
+ catch (error) {
118
+ logger.error(`Background refresh failed for ${key}: ${error}`);
119
+ }
120
+ finally {
121
+ if (entry) {
122
+ entry.isRefreshing = false;
123
+ }
124
+ }
125
+ }
70
126
  /**
71
127
  * Check if a key exists and is not expired
72
128
  */
@@ -75,7 +131,8 @@ export class CacheService {
75
131
  if (!entry)
76
132
  return false;
77
133
  const now = Date.now();
78
- if (now - entry.timestamp > entry.ttl) {
134
+ const totalTtl = entry.ttl + entry.staleTtl;
135
+ if (now - entry.timestamp > totalTtl) {
79
136
  this.cache.delete(key);
80
137
  return false;
81
138
  }
@@ -100,12 +157,16 @@ export class CacheService {
100
157
  */
101
158
  getStats() {
102
159
  const now = Date.now();
103
- const entries = Array.from(this.cache.values()).map(entry => ({
104
- key: entry.key,
105
- hits: entry.hits,
106
- age: now - entry.timestamp,
107
- ttl: entry.ttl,
108
- }));
160
+ const entries = Array.from(this.cache.values()).map(entry => {
161
+ const age = now - entry.timestamp;
162
+ return {
163
+ key: entry.key,
164
+ hits: entry.hits,
165
+ age,
166
+ ttl: entry.ttl,
167
+ isStale: age > entry.ttl,
168
+ };
169
+ });
109
170
  return {
110
171
  size: this.cache.size,
111
172
  maxSize: this.maxSize,
@@ -120,7 +181,8 @@ export class CacheService {
120
181
  const now = Date.now();
121
182
  let removed = 0;
122
183
  for (const [key, entry] of this.cache.entries()) {
123
- if (now - entry.timestamp > entry.ttl) {
184
+ const totalTtl = entry.ttl + entry.staleTtl;
185
+ if (now - entry.timestamp > totalTtl) {
124
186
  this.cache.delete(key);
125
187
  removed++;
126
188
  }
@@ -132,20 +194,22 @@ export class CacheService {
132
194
  return removed;
133
195
  }
134
196
  /**
135
- * Evict the oldest entry (LRU)
197
+ * Evict the oldest entry (LRU with hit weighting)
136
198
  */
137
199
  evictOldest() {
138
200
  let oldestKey = null;
139
- let oldestTime = Infinity;
201
+ let lowestScore = Infinity;
202
+ // Evict based on age weighted by hits
140
203
  for (const [key, entry] of this.cache.entries()) {
141
- if (entry.timestamp < oldestTime) {
142
- oldestTime = entry.timestamp;
204
+ const score = (Date.now() - entry.timestamp) / (entry.hits + 1);
205
+ if (score < lowestScore) {
206
+ lowestScore = score;
143
207
  oldestKey = key;
144
208
  }
145
209
  }
146
210
  if (oldestKey) {
147
211
  this.cache.delete(oldestKey);
148
- logger.debug(`Evicted oldest cache entry: ${oldestKey}`);
212
+ logger.debug(`Evicted LRU entry: ${oldestKey}`);
149
213
  }
150
214
  }
151
215
  /**
@@ -158,7 +222,7 @@ export class CacheService {
158
222
  this.saveTimer = setTimeout(() => {
159
223
  this.saveToFile();
160
224
  this.saveTimer = null;
161
- }, 1000); // Save after 1 second of inactivity
225
+ }, 1000);
162
226
  }
163
227
  /**
164
228
  * Save cache to file
@@ -192,7 +256,8 @@ export class CacheService {
192
256
  const now = Date.now();
193
257
  let restored = 0;
194
258
  for (const [key, entry] of entries) {
195
- if (now - entry.timestamp < entry.ttl) {
259
+ const totalTtl = entry.ttl + entry.staleTtl;
260
+ if (now - entry.timestamp < totalTtl) {
196
261
  this.cache.set(key, entry);
197
262
  restored++;
198
263
  }
@@ -200,7 +265,6 @@ export class CacheService {
200
265
  logger.info(`Cache loaded: ${restored}/${entries.length} entries restored`);
201
266
  }
202
267
  catch (error) {
203
- // File doesn't exist or is invalid - start fresh
204
268
  logger.debug('No existing cache file found, starting fresh');
205
269
  }
206
270
  }
@@ -217,14 +281,8 @@ export class CacheService {
217
281
  /**
218
282
  * Get or set pattern - returns cached value or computes and caches it
219
283
  */
220
- async getOrSet(key, factory, ttl) {
221
- const cached = this.get(key);
222
- if (cached !== null) {
223
- return cached;
224
- }
225
- const value = await factory();
226
- this.set(key, value, ttl);
227
- return value;
284
+ async getOrSet(key, factory, ttl, staleTtl) {
285
+ return this.getOrSetWithStaleRevalidate(key, factory, ttl, staleTtl);
228
286
  }
229
287
  /**
230
288
  * Get multiple keys at once
@@ -242,16 +300,15 @@ export class CacheService {
242
300
  /**
243
301
  * Set multiple keys at once
244
302
  */
245
- setMultiple(entries, ttl) {
303
+ setMultiple(entries, ttl, staleTtl) {
246
304
  for (const [key, value] of entries.entries()) {
247
- this.set(key, value, ttl);
305
+ this.set(key, value, ttl, staleTtl);
248
306
  }
249
307
  }
250
308
  /**
251
309
  * Invalidate cache by pattern
252
310
  */
253
311
  invalidatePattern(pattern) {
254
- // Use safe regex creation to prevent ReDoS attacks
255
312
  const regex = createSafeRegex(pattern);
256
313
  let removed = 0;
257
314
  for (const key of this.cache.keys()) {
@@ -288,10 +345,10 @@ let globalCache = null;
288
345
  export function getGlobalCache() {
289
346
  if (!globalCache) {
290
347
  globalCache = new CacheService();
291
- // Cleanup expired entries every 5 minutes
348
+ // Cleanup expired entries every 2 minutes
292
349
  setInterval(() => {
293
350
  globalCache?.cleanup();
294
- }, 5 * 60 * 1000);
351
+ }, 2 * 60 * 1000);
295
352
  }
296
353
  return globalCache;
297
354
  }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * CIRCUIT BREAKER PATTERN
3
+ * Prevents cascading failures by stopping requests to failing services
4
+ */
5
+ export declare class CircuitBreaker {
6
+ private readonly name;
7
+ private readonly failureThreshold;
8
+ private readonly resetTimeout;
9
+ private readonly halfOpenMaxCalls;
10
+ private failures;
11
+ private lastFailureTime;
12
+ private state;
13
+ private successCount;
14
+ constructor(name: string, failureThreshold?: number, resetTimeout?: number, halfOpenMaxCalls?: number);
15
+ /**
16
+ * Execute a function with circuit breaker protection
17
+ */
18
+ execute<T>(fn: () => Promise<T>, context?: string): Promise<T>;
19
+ private onSuccess;
20
+ private onFailure;
21
+ getStatus(): {
22
+ name: string;
23
+ state: "closed" | "open" | "half-open";
24
+ failures: number;
25
+ lastFailure: number | null;
26
+ };
27
+ }
28
+ export declare class CircuitBreakerOpenError extends Error {
29
+ readonly retryAfter: number;
30
+ constructor(message: string, retryAfter: number);
31
+ }
32
+ export declare function getCircuitBreaker(name: string, config?: {
33
+ failureThreshold?: number;
34
+ resetTimeout?: number;
35
+ }): CircuitBreaker;
36
+ export declare function getAllCircuitBreakerStatus(): Array<{
37
+ name: string;
38
+ state: string;
39
+ failures: number;
40
+ }>;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * CIRCUIT BREAKER PATTERN
3
+ * Prevents cascading failures by stopping requests to failing services
4
+ */
5
+ import { logger } from '../../../utils.js';
6
+ export class CircuitBreaker {
7
+ name;
8
+ failureThreshold;
9
+ resetTimeout;
10
+ halfOpenMaxCalls;
11
+ failures = 0;
12
+ lastFailureTime = null;
13
+ state = 'closed';
14
+ successCount = 0;
15
+ constructor(name, failureThreshold = 5, resetTimeout = 60000, halfOpenMaxCalls = 3) {
16
+ this.name = name;
17
+ this.failureThreshold = failureThreshold;
18
+ this.resetTimeout = resetTimeout;
19
+ this.halfOpenMaxCalls = halfOpenMaxCalls;
20
+ }
21
+ /**
22
+ * Execute a function with circuit breaker protection
23
+ */
24
+ async execute(fn, context) {
25
+ if (this.state === 'open') {
26
+ const timeSinceLastFailure = Date.now() - (this.lastFailureTime || 0);
27
+ if (timeSinceLastFailure > this.resetTimeout) {
28
+ logger.info(`[CircuitBreaker:${this.name}] Transitioning to HALF-OPEN`);
29
+ this.state = 'half-open';
30
+ this.successCount = 0;
31
+ }
32
+ else {
33
+ const remainingTime = Math.ceil((this.resetTimeout - timeSinceLastFailure) / 1000);
34
+ throw new CircuitBreakerOpenError(`Circuit breaker '${this.name}' is OPEN. Retry in ${remainingTime}s`, remainingTime);
35
+ }
36
+ }
37
+ try {
38
+ const result = await fn();
39
+ this.onSuccess();
40
+ return result;
41
+ }
42
+ catch (error) {
43
+ this.onFailure(error instanceof Error ? error : new Error(String(error)));
44
+ throw error;
45
+ }
46
+ }
47
+ onSuccess() {
48
+ if (this.state === 'half-open') {
49
+ this.successCount++;
50
+ if (this.successCount >= this.halfOpenMaxCalls) {
51
+ logger.info(`[CircuitBreaker:${this.name}] Transitioning to CLOSED`);
52
+ this.state = 'closed';
53
+ this.failures = 0;
54
+ this.successCount = 0;
55
+ }
56
+ }
57
+ else {
58
+ this.failures = Math.max(0, this.failures - 1);
59
+ }
60
+ }
61
+ onFailure(error) {
62
+ this.failures++;
63
+ this.lastFailureTime = Date.now();
64
+ logger.warn(`[CircuitBreaker:${this.name}] Failure ${this.failures}/${this.failureThreshold}: ${error.message}`);
65
+ if (this.failures >= this.failureThreshold) {
66
+ logger.error(`[CircuitBreaker:${this.name}] Threshold reached. Transitioning to OPEN`);
67
+ this.state = 'open';
68
+ }
69
+ }
70
+ getStatus() {
71
+ return {
72
+ name: this.name,
73
+ state: this.state,
74
+ failures: this.failures,
75
+ lastFailure: this.lastFailureTime,
76
+ };
77
+ }
78
+ }
79
+ export class CircuitBreakerOpenError extends Error {
80
+ retryAfter;
81
+ constructor(message, retryAfter) {
82
+ super(message);
83
+ this.retryAfter = retryAfter;
84
+ this.name = 'CircuitBreakerOpenError';
85
+ }
86
+ }
87
+ // Global circuit breakers registry
88
+ const circuitBreakers = new Map();
89
+ export function getCircuitBreaker(name, config) {
90
+ if (!circuitBreakers.has(name)) {
91
+ circuitBreakers.set(name, new CircuitBreaker(name, config?.failureThreshold, config?.resetTimeout));
92
+ }
93
+ return circuitBreakers.get(name);
94
+ }
95
+ export function getAllCircuitBreakerStatus() {
96
+ return Array.from(circuitBreakers.values()).map(cb => ({
97
+ ...cb.getStatus()
98
+ }));
99
+ }
@@ -10,25 +10,40 @@ export declare const API_CONFIG: {
10
10
  readonly FOOTBALL_DATA_BASE: "https://api.football-data.org/v4";
11
11
  readonly SPORTS_DB_KEY: string;
12
12
  readonly SPORTS_DB_BASE: "https://www.thesportsdb.com/api/v1/json";
13
+ readonly ODDS_API_KEY: string;
14
+ readonly ODDS_API_BASE: "https://api.the-odds-api.com/v4";
15
+ readonly SPORTRADAR_KEY: string;
16
+ readonly SPORTRADAR_BASE: "https://api.sportradar.us/soccer/trial/v4";
13
17
  readonly RATE_LIMITS: {
14
18
  readonly API_FOOTBALL: 10;
15
19
  readonly FOOTBALL_DATA: 10;
16
20
  readonly SPORTS_DB: 30;
21
+ readonly ODDS_API: 20;
22
+ readonly SPORTRADAR: 100;
17
23
  };
18
24
  };
19
25
  export declare const CACHE_CONFIG: {
20
26
  readonly DEFAULT_TTL: number;
21
27
  readonly TTL: {
22
28
  readonly LIVE_SCORES: number;
29
+ readonly LIVE_EVENTS: number;
30
+ readonly ODDS: number;
23
31
  readonly MATCH_DETAILS: number;
24
32
  readonly STANDINGS: number;
25
33
  readonly TEAM_STATS: number;
26
34
  readonly PLAYER_STATS: number;
27
- readonly ODDS: number;
28
35
  readonly TRANSFER_NEWS: number;
36
+ readonly PREDICTIONS: number;
37
+ };
38
+ readonly STALE_MULTIPLIERS: {
39
+ readonly LIVE_SCORES: 2;
40
+ readonly ODDS: 2;
41
+ readonly MATCH_DETAILS: 2;
42
+ readonly STANDINGS: 1.5;
43
+ readonly TEAM_STATS: 1.5;
29
44
  };
30
45
  readonly CACHE_PATH: string;
31
- readonly MAX_SIZE: 1000;
46
+ readonly MAX_SIZE: 2000;
32
47
  };
33
48
  export declare const SCRAPER_CONFIG: {
34
49
  readonly PRIORITY_DOMAINS: readonly ["understat.com", "bbc.co.uk/sport", "sportsmole.co.uk", "skysports.com", "goal.com", "thesquareball.net", "fbref.com", "whoscored.com", "sofascore.com", "flashscore.com"];
@@ -172,9 +187,53 @@ export declare const ERRORS: {
172
187
  readonly TIMEOUT: "Request timed out.";
173
188
  readonly INVALID_RESPONSE: "Invalid response from API.";
174
189
  readonly ALL_PROVIDERS_FAILED: "All data providers failed.";
190
+ readonly CIRCUIT_BREAKER_OPEN: "Service temporarily unavailable due to repeated failures.";
191
+ };
192
+ export declare const ALERT_CONFIG: {
193
+ readonly CHECK_INTERVAL: 10000;
194
+ readonly DEFAULT_COOLDOWN: 300000;
195
+ readonly MAX_ALERTS_PER_HOUR: 10;
196
+ readonly TYPES: {
197
+ readonly ODDS_DROP: "odds_drop";
198
+ readonly ODDS_VALUE: "odds_value";
199
+ readonly GOAL: "goal";
200
+ readonly RED_CARD: "red_card";
201
+ readonly LINEUP_CHANGE: "lineup_change";
202
+ readonly MATCH_START: "match_start";
203
+ readonly MATCH_END: "match_end";
204
+ readonly CUSTOM: "custom";
205
+ };
206
+ };
207
+ export declare const REALTIME_CONFIG: {
208
+ readonly WS_RECONNECT_INTERVAL: 5000;
209
+ readonly WS_MAX_RECONNECT_ATTEMPTS: 10;
210
+ readonly POLLING_INTERVALS: {
211
+ readonly LIVE_SCORES: 15000;
212
+ readonly ODDS: 30000;
213
+ readonly MATCH_EVENTS: 5000;
214
+ };
215
+ readonly EVENT_BATCH_SIZE: 10;
216
+ readonly EVENT_BATCH_TIMEOUT: 1000;
217
+ };
218
+ export declare const ML_CONFIG: {
219
+ readonly MIN_TRAINING_MATCHES: 50;
220
+ readonly FEATURE_WEIGHTS: {
221
+ readonly form: 0.25;
222
+ readonly h2h: 0.2;
223
+ readonly home_advantage: 0.15;
224
+ readonly xg: 0.15;
225
+ readonly injuries: 0.1;
226
+ readonly fatigue: 0.1;
227
+ readonly weather: 0.05;
228
+ };
229
+ readonly CONFIDENCE_THRESHOLDS: {
230
+ readonly high: 0.7;
231
+ readonly medium: 0.55;
232
+ readonly low: 0.4;
233
+ };
175
234
  };
176
235
  export declare const MODULE_INFO: {
177
236
  readonly NAME: "sports-module";
178
- readonly VERSION: "2.0.0";
179
- readonly DESCRIPTION: "Football Intelligence System for MCP Server";
237
+ readonly VERSION: "3.0.0";
238
+ readonly DESCRIPTION: "Football Intelligence System for MCP Server - Realtime Edition";
180
239
  };
@@ -14,31 +14,49 @@ export const API_CONFIG = {
14
14
  // TheSportsDB
15
15
  SPORTS_DB_KEY: process.env.SPORTS_DB_KEY || '',
16
16
  SPORTS_DB_BASE: 'https://www.thesportsdb.com/api/v1/json',
17
+ // Odds API
18
+ ODDS_API_KEY: process.env.ODDS_API_KEY || '',
19
+ ODDS_API_BASE: 'https://api.the-odds-api.com/v4',
20
+ // SportRadar (requires enterprise license)
21
+ SPORTRADAR_KEY: process.env.SPORTRADAR_KEY || '',
22
+ SPORTRADAR_BASE: 'https://api.sportradar.us/soccer/trial/v4',
17
23
  // Rate limits (requests per minute)
18
24
  RATE_LIMITS: {
19
25
  API_FOOTBALL: 10,
20
26
  FOOTBALL_DATA: 10,
21
27
  SPORTS_DB: 30,
28
+ ODDS_API: 20,
29
+ SPORTRADAR: 100,
22
30
  }
23
31
  };
24
32
  // ============= Cache Configuration =============
25
33
  export const CACHE_CONFIG = {
26
34
  // Default TTL in milliseconds
27
35
  DEFAULT_TTL: parseInt(process.env.SPORTS_CACHE_TTL || '300000'), // 5 minutes
28
- // Specific TTLs for different data types
36
+ // Specific TTLs for different data types (OPTIMIZED FOR REALTIME)
29
37
  TTL: {
30
- LIVE_SCORES: 60 * 1000, // 1 minute
31
- MATCH_DETAILS: 5 * 60 * 1000, // 5 minutes
32
- STANDINGS: 30 * 60 * 1000, // 30 minutes
33
- TEAM_STATS: 60 * 60 * 1000, // 1 hour
34
- PLAYER_STATS: 60 * 60 * 1000, // 1 hour
35
- ODDS: 2 * 60 * 1000, // 2 minutes
36
- TRANSFER_NEWS: 15 * 60 * 1000, // 15 minutes
38
+ LIVE_SCORES: 15 * 1000, // 15 seconds (was 1 minute)
39
+ LIVE_EVENTS: 5 * 1000, // 5 seconds for live match events
40
+ ODDS: 30 * 1000, // 30 seconds (was 2 minutes)
41
+ MATCH_DETAILS: 2 * 60 * 1000, // 2 minutes (was 5 minutes)
42
+ STANDINGS: 15 * 60 * 1000, // 15 minutes (was 30 minutes)
43
+ TEAM_STATS: 30 * 60 * 1000, // 30 minutes (was 1 hour)
44
+ PLAYER_STATS: 30 * 60 * 1000, // 30 minutes (was 1 hour)
45
+ TRANSFER_NEWS: 10 * 60 * 1000, // 10 minutes (was 15 minutes)
46
+ PREDICTIONS: 60 * 60 * 1000, // 1 hour
47
+ },
48
+ // Stale-while-revalidate multipliers
49
+ STALE_MULTIPLIERS: {
50
+ LIVE_SCORES: 2, // Stale for 30 seconds (15s * 2)
51
+ ODDS: 2, // Stale for 1 minute (30s * 2)
52
+ MATCH_DETAILS: 2, // Stale for 4 minutes
53
+ STANDINGS: 1.5, // Stale for 22.5 minutes
54
+ TEAM_STATS: 1.5, // Stale for 45 minutes
37
55
  },
38
56
  // Cache file path
39
57
  CACHE_PATH: process.env.SPORTS_CACHE_PATH || '.sports_cache.json',
40
58
  // Max cache size (number of entries)
41
- MAX_SIZE: 1000,
59
+ MAX_SIZE: 2000,
42
60
  };
43
61
  // ============= Scraping Configuration =============
44
62
  export const SCRAPER_CONFIG = {
@@ -152,10 +170,67 @@ export const ERRORS = {
152
170
  TIMEOUT: 'Request timed out.',
153
171
  INVALID_RESPONSE: 'Invalid response from API.',
154
172
  ALL_PROVIDERS_FAILED: 'All data providers failed.',
173
+ CIRCUIT_BREAKER_OPEN: 'Service temporarily unavailable due to repeated failures.',
174
+ };
175
+ // ============= Alert Configuration =============
176
+ export const ALERT_CONFIG = {
177
+ // Check interval for alerts (milliseconds)
178
+ CHECK_INTERVAL: 10000, // 10 seconds
179
+ // Default cooldown between same alert (milliseconds)
180
+ DEFAULT_COOLDOWN: 300000, // 5 minutes
181
+ // Max alerts per hour per rule
182
+ MAX_ALERTS_PER_HOUR: 10,
183
+ // Alert types
184
+ TYPES: {
185
+ ODDS_DROP: 'odds_drop',
186
+ ODDS_VALUE: 'odds_value',
187
+ GOAL: 'goal',
188
+ RED_CARD: 'red_card',
189
+ LINEUP_CHANGE: 'lineup_change',
190
+ MATCH_START: 'match_start',
191
+ MATCH_END: 'match_end',
192
+ CUSTOM: 'custom',
193
+ },
194
+ };
195
+ // ============= Realtime Configuration =============
196
+ export const REALTIME_CONFIG = {
197
+ // WebSocket reconnection
198
+ WS_RECONNECT_INTERVAL: 5000,
199
+ WS_MAX_RECONNECT_ATTEMPTS: 10,
200
+ // Polling intervals (fallback when WebSocket unavailable)
201
+ POLLING_INTERVALS: {
202
+ LIVE_SCORES: 15000, // 15 seconds
203
+ ODDS: 30000, // 30 seconds
204
+ MATCH_EVENTS: 5000, // 5 seconds
205
+ },
206
+ // Event batching
207
+ EVENT_BATCH_SIZE: 10,
208
+ EVENT_BATCH_TIMEOUT: 1000,
209
+ };
210
+ // ============= ML Prediction Configuration =============
211
+ export const ML_CONFIG = {
212
+ // Minimum matches for training
213
+ MIN_TRAINING_MATCHES: 50,
214
+ // Feature weights
215
+ FEATURE_WEIGHTS: {
216
+ form: 0.25,
217
+ h2h: 0.20,
218
+ home_advantage: 0.15,
219
+ xg: 0.15,
220
+ injuries: 0.10,
221
+ fatigue: 0.10,
222
+ weather: 0.05,
223
+ },
224
+ // Confidence thresholds
225
+ CONFIDENCE_THRESHOLDS: {
226
+ high: 0.70,
227
+ medium: 0.55,
228
+ low: 0.40,
229
+ },
155
230
  };
156
231
  // ============= Version Info =============
157
232
  export const MODULE_INFO = {
158
233
  NAME: 'sports-module',
159
- VERSION: '2.0.0',
160
- DESCRIPTION: 'Football Intelligence System for MCP Server',
234
+ VERSION: '3.0.0',
235
+ DESCRIPTION: 'Football Intelligence System for MCP Server - Realtime Edition',
161
236
  };