@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.
- package/README.md +249 -39
- package/dist/dashboard/server.d.ts +79 -2
- package/dist/dashboard/server.js +466 -61
- package/dist/index.js +1 -4
- package/dist/tools/sports/core/alert-manager.d.ts +145 -0
- package/dist/tools/sports/core/alert-manager.js +380 -0
- package/dist/tools/sports/core/cache.d.ts +19 -8
- package/dist/tools/sports/core/cache.js +95 -38
- package/dist/tools/sports/core/circuit-breaker.d.ts +40 -0
- package/dist/tools/sports/core/circuit-breaker.js +99 -0
- package/dist/tools/sports/core/constants.d.ts +63 -4
- package/dist/tools/sports/core/constants.js +86 -11
- package/dist/tools/sports/core/data-quality.d.ts +80 -0
- package/dist/tools/sports/core/data-quality.js +460 -0
- package/dist/tools/sports/core/historical-analyzer.d.ts +108 -0
- package/dist/tools/sports/core/historical-analyzer.js +461 -0
- package/dist/tools/sports/core/index.d.ts +13 -0
- package/dist/tools/sports/core/index.js +16 -0
- package/dist/tools/sports/core/ml-prediction.d.ts +134 -0
- package/dist/tools/sports/core/ml-prediction.js +402 -0
- package/dist/tools/sports/core/realtime-manager.d.ts +102 -0
- package/dist/tools/sports/core/realtime-manager.js +331 -0
- package/dist/tools/sports/core/retry.d.ts +29 -0
- package/dist/tools/sports/core/retry.js +77 -0
- package/dist/tools/sports/core/types.d.ts +40 -1
- package/package.json +1 -1
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REALTIME DATA MANAGER
|
|
3
|
+
* WebSocket/SSE based real-time data streaming with fallback polling
|
|
4
|
+
*/
|
|
5
|
+
import { EventEmitter } from 'events';
|
|
6
|
+
import { CacheService } from './cache.js';
|
|
7
|
+
import { logger } from '../../../utils.js';
|
|
8
|
+
import { REALTIME_CONFIG, CACHE_CONFIG } from './constants.js';
|
|
9
|
+
export class RealtimeDataManager extends EventEmitter {
|
|
10
|
+
cache;
|
|
11
|
+
wsConnections = new Map();
|
|
12
|
+
pollingIntervals = new Map();
|
|
13
|
+
reconnectAttempts = new Map();
|
|
14
|
+
isRunning = false;
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.cache = new CacheService();
|
|
18
|
+
this.setMaxListeners(100);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Start the realtime manager
|
|
22
|
+
*/
|
|
23
|
+
async start() {
|
|
24
|
+
if (this.isRunning)
|
|
25
|
+
return;
|
|
26
|
+
this.isRunning = true;
|
|
27
|
+
logger.info('[RealtimeManager] Started');
|
|
28
|
+
// Start polling for live data
|
|
29
|
+
this.startLiveScoresPolling();
|
|
30
|
+
this.startOddsPolling();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Stop the realtime manager
|
|
34
|
+
*/
|
|
35
|
+
stop() {
|
|
36
|
+
this.isRunning = false;
|
|
37
|
+
// Close all WebSocket connections
|
|
38
|
+
for (const [matchId, ws] of this.wsConnections.entries()) {
|
|
39
|
+
ws.close();
|
|
40
|
+
}
|
|
41
|
+
this.wsConnections.clear();
|
|
42
|
+
// Clear all polling intervals
|
|
43
|
+
for (const [key, interval] of this.pollingIntervals.entries()) {
|
|
44
|
+
clearInterval(interval);
|
|
45
|
+
}
|
|
46
|
+
this.pollingIntervals.clear();
|
|
47
|
+
logger.info('[RealtimeManager] Stopped');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Subscribe to a specific match's events
|
|
51
|
+
*/
|
|
52
|
+
subscribeToMatch(matchId, callback) {
|
|
53
|
+
const eventName = `match:${matchId}`;
|
|
54
|
+
this.on(eventName, callback);
|
|
55
|
+
// Try to establish WebSocket connection if available
|
|
56
|
+
this.connectToMatchStream(matchId);
|
|
57
|
+
// Return unsubscribe function
|
|
58
|
+
return () => {
|
|
59
|
+
this.off(eventName, callback);
|
|
60
|
+
this.unsubscribeFromMatch(matchId);
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Subscribe to odds changes for a match
|
|
65
|
+
*/
|
|
66
|
+
subscribeToOdds(matchId, callback) {
|
|
67
|
+
const eventName = `odds:${matchId}`;
|
|
68
|
+
this.on(eventName, callback);
|
|
69
|
+
return () => {
|
|
70
|
+
this.off(eventName, callback);
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Subscribe to all live events
|
|
75
|
+
*/
|
|
76
|
+
subscribeToAllEvents(callback) {
|
|
77
|
+
this.on('event', callback);
|
|
78
|
+
return () => this.off('event', callback);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Process and broadcast an event
|
|
82
|
+
*/
|
|
83
|
+
processEvent(event) {
|
|
84
|
+
// Update cache
|
|
85
|
+
this.updateCache(event);
|
|
86
|
+
// Emit to specific match listeners
|
|
87
|
+
this.emit(`match:${event.matchId}`, event);
|
|
88
|
+
// Emit to odds listeners if it's an odds change
|
|
89
|
+
if (event.type === 'odds_change') {
|
|
90
|
+
this.emit(`odds:${event.matchId}`, event);
|
|
91
|
+
}
|
|
92
|
+
// Emit to global listeners
|
|
93
|
+
this.emit('event', event);
|
|
94
|
+
// Log event
|
|
95
|
+
logger.debug(`[Realtime] Event: ${event.type} | Match: ${event.matchId} | Min: ${event.minute}`);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get current live matches
|
|
99
|
+
*/
|
|
100
|
+
getLiveMatches() {
|
|
101
|
+
const cached = this.cache.get('live:all');
|
|
102
|
+
return cached || [];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get match events history
|
|
106
|
+
*/
|
|
107
|
+
getMatchEvents(matchId) {
|
|
108
|
+
const cached = this.cache.get(`events:${matchId}`);
|
|
109
|
+
return cached || [];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Connect to match stream (WebSocket or polling)
|
|
113
|
+
*/
|
|
114
|
+
async connectToMatchStream(matchId) {
|
|
115
|
+
// For now, use polling fallback
|
|
116
|
+
// In production, this would try WebSocket first
|
|
117
|
+
this.startMatchEventsPolling(matchId);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Unsubscribe from a match
|
|
121
|
+
*/
|
|
122
|
+
unsubscribeFromMatch(matchId) {
|
|
123
|
+
// Close WebSocket if exists
|
|
124
|
+
const ws = this.wsConnections.get(matchId);
|
|
125
|
+
if (ws) {
|
|
126
|
+
ws.close();
|
|
127
|
+
this.wsConnections.delete(matchId);
|
|
128
|
+
}
|
|
129
|
+
// Clear polling if no more listeners
|
|
130
|
+
const listenerCount = this.listenerCount(`match:${matchId}`);
|
|
131
|
+
if (listenerCount === 0) {
|
|
132
|
+
const interval = this.pollingIntervals.get(`events:${matchId}`);
|
|
133
|
+
if (interval) {
|
|
134
|
+
clearInterval(interval);
|
|
135
|
+
this.pollingIntervals.delete(`events:${matchId}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Start polling for live scores
|
|
141
|
+
*/
|
|
142
|
+
startLiveScoresPolling() {
|
|
143
|
+
const interval = setInterval(async () => {
|
|
144
|
+
if (!this.isRunning)
|
|
145
|
+
return;
|
|
146
|
+
try {
|
|
147
|
+
// Fetch live scores from API
|
|
148
|
+
// This would call your API provider
|
|
149
|
+
const liveScores = await this.fetchLiveScores();
|
|
150
|
+
// Update cache
|
|
151
|
+
this.cache.set('live:all', liveScores, CACHE_CONFIG.TTL.LIVE_SCORES);
|
|
152
|
+
// Emit update
|
|
153
|
+
this.emit('live_scores', liveScores);
|
|
154
|
+
// Check for score changes and emit events
|
|
155
|
+
this.detectScoreChanges(liveScores);
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
logger.error('[Realtime] Failed to fetch live scores:', error);
|
|
159
|
+
}
|
|
160
|
+
}, REALTIME_CONFIG.POLLING_INTERVALS.LIVE_SCORES);
|
|
161
|
+
this.pollingIntervals.set('live_scores', interval);
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Start polling for odds
|
|
165
|
+
*/
|
|
166
|
+
startOddsPolling() {
|
|
167
|
+
const interval = setInterval(async () => {
|
|
168
|
+
if (!this.isRunning)
|
|
169
|
+
return;
|
|
170
|
+
try {
|
|
171
|
+
const liveMatches = this.getLiveMatches();
|
|
172
|
+
for (const match of liveMatches) {
|
|
173
|
+
const odds = await this.fetchOdds(match.id);
|
|
174
|
+
if (odds) {
|
|
175
|
+
const cachedOdds = this.cache.get(`odds:${match.id}`);
|
|
176
|
+
if (cachedOdds) {
|
|
177
|
+
// Detect changes
|
|
178
|
+
this.detectOddsChanges(match.id, cachedOdds, odds);
|
|
179
|
+
}
|
|
180
|
+
this.cache.set(`odds:${match.id}`, odds, CACHE_CONFIG.TTL.ODDS);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
logger.error('[Realtime] Failed to fetch odds:', error);
|
|
186
|
+
}
|
|
187
|
+
}, REALTIME_CONFIG.POLLING_INTERVALS.ODDS);
|
|
188
|
+
this.pollingIntervals.set('odds', interval);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Start polling for specific match events
|
|
192
|
+
*/
|
|
193
|
+
startMatchEventsPolling(matchId) {
|
|
194
|
+
if (this.pollingIntervals.has(`events:${matchId}`))
|
|
195
|
+
return;
|
|
196
|
+
const interval = setInterval(async () => {
|
|
197
|
+
if (!this.isRunning)
|
|
198
|
+
return;
|
|
199
|
+
try {
|
|
200
|
+
const events = await this.fetchMatchEvents(matchId);
|
|
201
|
+
const cachedEvents = this.getMatchEvents(matchId);
|
|
202
|
+
// Find new events
|
|
203
|
+
const newEvents = events.filter(e => !cachedEvents.some(ce => ce.timestamp === e.timestamp && ce.type === e.type));
|
|
204
|
+
for (const event of newEvents) {
|
|
205
|
+
this.processEvent(event);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
logger.error(`[Realtime] Failed to fetch events for match ${matchId}:`, error);
|
|
210
|
+
}
|
|
211
|
+
}, REALTIME_CONFIG.POLLING_INTERVALS.MATCH_EVENTS);
|
|
212
|
+
this.pollingIntervals.set(`events:${matchId}`, interval);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Detect score changes and emit goal events
|
|
216
|
+
*/
|
|
217
|
+
detectScoreChanges(matches) {
|
|
218
|
+
for (const match of matches) {
|
|
219
|
+
const cachedMatch = this.cache.get(`match:${match.id}`);
|
|
220
|
+
if (cachedMatch && cachedMatch.score && match.score) {
|
|
221
|
+
// Check for home team goal
|
|
222
|
+
if (match.score.home > cachedMatch.score.home) {
|
|
223
|
+
this.processEvent({
|
|
224
|
+
type: 'goal',
|
|
225
|
+
matchId: match.id,
|
|
226
|
+
timestamp: Date.now(),
|
|
227
|
+
minute: match.minute,
|
|
228
|
+
data: {
|
|
229
|
+
team: 'home',
|
|
230
|
+
score: match.score,
|
|
231
|
+
previousScore: cachedMatch.score,
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
// Check for away team goal
|
|
236
|
+
if (match.score.away > cachedMatch.score.away) {
|
|
237
|
+
this.processEvent({
|
|
238
|
+
type: 'goal',
|
|
239
|
+
matchId: match.id,
|
|
240
|
+
timestamp: Date.now(),
|
|
241
|
+
minute: match.minute,
|
|
242
|
+
data: {
|
|
243
|
+
team: 'away',
|
|
244
|
+
score: match.score,
|
|
245
|
+
previousScore: cachedMatch.score,
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Update match cache
|
|
251
|
+
this.cache.set(`match:${match.id}`, match, CACHE_CONFIG.TTL.MATCH_DETAILS);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Detect odds changes and emit events
|
|
256
|
+
*/
|
|
257
|
+
detectOddsChanges(matchId, oldOdds, newOdds) {
|
|
258
|
+
const markets = [
|
|
259
|
+
{ key: 'homeWin', name: 'Home Win' },
|
|
260
|
+
{ key: 'draw', name: 'Draw' },
|
|
261
|
+
{ key: 'awayWin', name: 'Away Win' },
|
|
262
|
+
];
|
|
263
|
+
for (const market of markets) {
|
|
264
|
+
const oldValue = oldOdds[market.key];
|
|
265
|
+
const newValue = newOdds[market.key];
|
|
266
|
+
if (oldValue && newValue && oldValue !== newValue) {
|
|
267
|
+
const change = ((newValue - oldValue) / oldValue) * 100;
|
|
268
|
+
// Only emit if change is significant (>5%)
|
|
269
|
+
if (Math.abs(change) > 5) {
|
|
270
|
+
this.processEvent({
|
|
271
|
+
type: 'odds_change',
|
|
272
|
+
matchId,
|
|
273
|
+
timestamp: Date.now(),
|
|
274
|
+
data: {
|
|
275
|
+
bookmaker: newOdds.provider,
|
|
276
|
+
market: market.name,
|
|
277
|
+
selection: market.key,
|
|
278
|
+
oldOdds: oldValue,
|
|
279
|
+
newOdds: newValue,
|
|
280
|
+
change,
|
|
281
|
+
timestamp: Date.now(),
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Update cache with event
|
|
290
|
+
*/
|
|
291
|
+
updateCache(event) {
|
|
292
|
+
// Invalidate related cache entries
|
|
293
|
+
this.cache.invalidatePattern(`match:${event.matchId}*`);
|
|
294
|
+
// Store event in history
|
|
295
|
+
const eventsKey = `events:${event.matchId}`;
|
|
296
|
+
const existingEvents = this.cache.get(eventsKey) || [];
|
|
297
|
+
existingEvents.push(event);
|
|
298
|
+
// Keep only last 100 events
|
|
299
|
+
if (existingEvents.length > 100) {
|
|
300
|
+
existingEvents.shift();
|
|
301
|
+
}
|
|
302
|
+
this.cache.set(eventsKey, existingEvents, CACHE_CONFIG.TTL.LIVE_EVENTS);
|
|
303
|
+
}
|
|
304
|
+
// Placeholder methods - implement with actual API calls
|
|
305
|
+
async fetchLiveScores() {
|
|
306
|
+
// Implement with your API provider
|
|
307
|
+
return [];
|
|
308
|
+
}
|
|
309
|
+
async fetchOdds(matchId) {
|
|
310
|
+
// Implement with your API provider
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
async fetchMatchEvents(matchId) {
|
|
314
|
+
// Implement with your API provider
|
|
315
|
+
return [];
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
// Singleton instance
|
|
319
|
+
let globalRealtimeManager = null;
|
|
320
|
+
export function getRealtimeManager() {
|
|
321
|
+
if (!globalRealtimeManager) {
|
|
322
|
+
globalRealtimeManager = new RealtimeDataManager();
|
|
323
|
+
}
|
|
324
|
+
return globalRealtimeManager;
|
|
325
|
+
}
|
|
326
|
+
export function resetRealtimeManager() {
|
|
327
|
+
if (globalRealtimeManager) {
|
|
328
|
+
globalRealtimeManager.stop();
|
|
329
|
+
}
|
|
330
|
+
globalRealtimeManager = null;
|
|
331
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RETRY MECHANISM
|
|
3
|
+
* Exponential backoff with jitter for resilient API calls
|
|
4
|
+
*/
|
|
5
|
+
export interface RetryConfig {
|
|
6
|
+
maxAttempts: number;
|
|
7
|
+
initialDelay: number;
|
|
8
|
+
maxDelay: number;
|
|
9
|
+
backoffMultiplier: number;
|
|
10
|
+
retryableErrors?: string[];
|
|
11
|
+
onRetry?: (attempt: number, error: Error, delay: number) => void;
|
|
12
|
+
}
|
|
13
|
+
export declare const DEFAULT_RETRY_CONFIG: RetryConfig;
|
|
14
|
+
export declare const AGGRESSIVE_RETRY_CONFIG: RetryConfig;
|
|
15
|
+
export declare const LIVE_DATA_RETRY_CONFIG: RetryConfig;
|
|
16
|
+
/**
|
|
17
|
+
* Execute a function with retry logic
|
|
18
|
+
*/
|
|
19
|
+
export declare function withRetry<T>(fn: () => Promise<T>, config?: Partial<RetryConfig>, context?: string): Promise<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Retry with circuit breaker combination
|
|
22
|
+
*/
|
|
23
|
+
export declare function withRetryAndCircuitBreaker<T>(fn: () => Promise<T>, circuitBreaker: {
|
|
24
|
+
execute: <U>(fn: () => Promise<U>) => Promise<U>;
|
|
25
|
+
}, config?: Partial<RetryConfig>, context?: string): Promise<T>;
|
|
26
|
+
/**
|
|
27
|
+
* Create a retryable wrapper for a function
|
|
28
|
+
*/
|
|
29
|
+
export declare function createRetryable<T extends (...args: any[]) => Promise<any>>(fn: T, config?: Partial<RetryConfig>, context?: string): T;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RETRY MECHANISM
|
|
3
|
+
* Exponential backoff with jitter for resilient API calls
|
|
4
|
+
*/
|
|
5
|
+
import { logger } from '../../../utils.js';
|
|
6
|
+
export const DEFAULT_RETRY_CONFIG = {
|
|
7
|
+
maxAttempts: 3,
|
|
8
|
+
initialDelay: 1000,
|
|
9
|
+
maxDelay: 30000,
|
|
10
|
+
backoffMultiplier: 2,
|
|
11
|
+
retryableErrors: ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED', '503', '502', '504', '429'],
|
|
12
|
+
};
|
|
13
|
+
export const AGGRESSIVE_RETRY_CONFIG = {
|
|
14
|
+
maxAttempts: 5,
|
|
15
|
+
initialDelay: 500,
|
|
16
|
+
maxDelay: 60000,
|
|
17
|
+
backoffMultiplier: 2,
|
|
18
|
+
retryableErrors: ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED', '503', '502', '504', '429', '500'],
|
|
19
|
+
};
|
|
20
|
+
export const LIVE_DATA_RETRY_CONFIG = {
|
|
21
|
+
maxAttempts: 3,
|
|
22
|
+
initialDelay: 200,
|
|
23
|
+
maxDelay: 5000,
|
|
24
|
+
backoffMultiplier: 1.5,
|
|
25
|
+
retryableErrors: ['ECONNRESET', 'ETIMEDOUT', '503', '504'],
|
|
26
|
+
};
|
|
27
|
+
function sleep(ms) {
|
|
28
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Execute a function with retry logic
|
|
32
|
+
*/
|
|
33
|
+
export async function withRetry(fn, config = {}, context) {
|
|
34
|
+
const fullConfig = { ...DEFAULT_RETRY_CONFIG, ...config };
|
|
35
|
+
let lastError = null;
|
|
36
|
+
for (let attempt = 1; attempt <= fullConfig.maxAttempts; attempt++) {
|
|
37
|
+
try {
|
|
38
|
+
return await fn();
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
42
|
+
// Check if error is retryable
|
|
43
|
+
if (fullConfig.retryableErrors && !fullConfig.retryableErrors.some(e => lastError.message.includes(e) ||
|
|
44
|
+
lastError.code?.includes(e))) {
|
|
45
|
+
throw lastError;
|
|
46
|
+
}
|
|
47
|
+
if (attempt === fullConfig.maxAttempts) {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
// Calculate delay with exponential backoff and jitter
|
|
51
|
+
const delay = Math.min(fullConfig.initialDelay * Math.pow(fullConfig.backoffMultiplier, attempt - 1), fullConfig.maxDelay);
|
|
52
|
+
const jitteredDelay = delay * (0.8 + Math.random() * 0.4); // ±20% jitter
|
|
53
|
+
const contextStr = context ? `[${context}]` : '';
|
|
54
|
+
logger.warn(`[Retry${contextStr}] Attempt ${attempt}/${fullConfig.maxAttempts} failed: ${lastError.message}. Retrying in ${Math.round(jitteredDelay)}ms...`);
|
|
55
|
+
if (fullConfig.onRetry) {
|
|
56
|
+
fullConfig.onRetry(attempt, lastError, jitteredDelay);
|
|
57
|
+
}
|
|
58
|
+
await sleep(jitteredDelay);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const contextStr = context ? `[${context}]` : '';
|
|
62
|
+
throw new Error(`[Retry${contextStr}] All ${fullConfig.maxAttempts} attempts failed. Last error: ${lastError?.message}`);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Retry with circuit breaker combination
|
|
66
|
+
*/
|
|
67
|
+
export async function withRetryAndCircuitBreaker(fn, circuitBreaker, config, context) {
|
|
68
|
+
return circuitBreaker.execute(() => withRetry(fn, config, context));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Create a retryable wrapper for a function
|
|
72
|
+
*/
|
|
73
|
+
export function createRetryable(fn, config, context) {
|
|
74
|
+
return (async (...args) => {
|
|
75
|
+
return withRetry(() => fn(...args), config, context);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -7,7 +7,7 @@ export type MatchStatus = 'scheduled' | 'live' | 'finished' | 'postponed' | 'can
|
|
|
7
7
|
export type BetType = 'home' | 'draw' | 'away' | 'over' | 'under' | 'btts' | 'handicap';
|
|
8
8
|
export type KellyVariant = 'full' | 'half' | 'quarter';
|
|
9
9
|
export type QueryType = 'general' | 'h2h' | 'form' | 'news' | 'stats' | 'odds' | 'referee' | 'weather' | 'fatigue' | 'setpieces';
|
|
10
|
-
export type ProviderType = 'api-football' | 'football-data' | 'sports-db' | 'scraper';
|
|
10
|
+
export type ProviderType = 'api-football' | 'football-data' | 'sports-db' | 'odds-api' | 'sportradar' | 'scraper';
|
|
11
11
|
export interface SearchQuery {
|
|
12
12
|
type: QueryType;
|
|
13
13
|
query: string;
|
|
@@ -175,6 +175,8 @@ export interface Match {
|
|
|
175
175
|
homeSubstitutes?: Player[];
|
|
176
176
|
awaySubstitutes?: Player[];
|
|
177
177
|
events?: MatchEvent[];
|
|
178
|
+
dataQuality?: number;
|
|
179
|
+
lastUpdated?: Date;
|
|
178
180
|
}
|
|
179
181
|
export interface MatchOdds {
|
|
180
182
|
homeWin: number;
|
|
@@ -344,6 +346,7 @@ export interface APIResponse<T = any> {
|
|
|
344
346
|
provider?: ProviderType;
|
|
345
347
|
cached?: boolean;
|
|
346
348
|
rateLimited?: boolean;
|
|
349
|
+
quality?: number;
|
|
347
350
|
}
|
|
348
351
|
export interface ProviderStatus {
|
|
349
352
|
name: ProviderType;
|
|
@@ -352,6 +355,7 @@ export interface ProviderStatus {
|
|
|
352
355
|
quotaLimit: number;
|
|
353
356
|
rateLimitUntil?: Date;
|
|
354
357
|
lastCall?: Date;
|
|
358
|
+
circuitBreakerState?: 'closed' | 'open' | 'half-open';
|
|
355
359
|
}
|
|
356
360
|
export interface ComparisonResult {
|
|
357
361
|
teamA: Team;
|
|
@@ -377,3 +381,38 @@ export interface LiveScoreUpdate {
|
|
|
377
381
|
events: MatchEvent[];
|
|
378
382
|
timestamp: Date;
|
|
379
383
|
}
|
|
384
|
+
export interface MLPrediction {
|
|
385
|
+
matchId: string;
|
|
386
|
+
homeWinProbability: number;
|
|
387
|
+
drawProbability: number;
|
|
388
|
+
awayWinProbability: number;
|
|
389
|
+
over25Probability?: number;
|
|
390
|
+
bttsProbability?: number;
|
|
391
|
+
confidence: number;
|
|
392
|
+
factors: PredictionFactor[];
|
|
393
|
+
timestamp: Date;
|
|
394
|
+
}
|
|
395
|
+
export interface PredictionFactor {
|
|
396
|
+
name: string;
|
|
397
|
+
weight: number;
|
|
398
|
+
impact: 'positive' | 'negative' | 'neutral';
|
|
399
|
+
description: string;
|
|
400
|
+
}
|
|
401
|
+
export interface HistoricalPattern {
|
|
402
|
+
pattern: string;
|
|
403
|
+
occurrence: number;
|
|
404
|
+
successRate: number;
|
|
405
|
+
avgOdds: number;
|
|
406
|
+
profit: number;
|
|
407
|
+
}
|
|
408
|
+
export interface TeamHistoricalPerformance {
|
|
409
|
+
team: Team;
|
|
410
|
+
totalMatches: number;
|
|
411
|
+
wins: number;
|
|
412
|
+
draws: number;
|
|
413
|
+
losses: number;
|
|
414
|
+
avgGoalsFor: number;
|
|
415
|
+
avgGoalsAgainst: number;
|
|
416
|
+
homeAdvantage: number;
|
|
417
|
+
patterns: HistoricalPattern[];
|
|
418
|
+
}
|