@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,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ML PREDICTION ENGINE
|
|
3
|
+
* Machine learning based match outcome prediction
|
|
4
|
+
*/
|
|
5
|
+
import { logger } from '../../../utils.js';
|
|
6
|
+
import { ML_CONFIG } from './constants.js';
|
|
7
|
+
/**
|
|
8
|
+
* ML Prediction Engine
|
|
9
|
+
* Uses weighted factors and historical patterns for predictions
|
|
10
|
+
*/
|
|
11
|
+
export class MLPredictionEngine {
|
|
12
|
+
weights = ML_CONFIG.FEATURE_WEIGHTS;
|
|
13
|
+
historicalData = new Map();
|
|
14
|
+
constructor() {
|
|
15
|
+
this.loadHistoricalData();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Predict match outcome
|
|
19
|
+
*/
|
|
20
|
+
async predict(input) {
|
|
21
|
+
const factors = [];
|
|
22
|
+
// Calculate form score
|
|
23
|
+
const formScore = this.calculateFormScore(input);
|
|
24
|
+
factors.push({
|
|
25
|
+
name: 'Recent Form',
|
|
26
|
+
weight: this.weights.form,
|
|
27
|
+
impact: formScore > 0 ? 'positive' : formScore < 0 ? 'negative' : 'neutral',
|
|
28
|
+
description: this.describeFormImpact(input),
|
|
29
|
+
});
|
|
30
|
+
// Calculate H2H score
|
|
31
|
+
const h2hScore = this.calculateH2HScore(input);
|
|
32
|
+
factors.push({
|
|
33
|
+
name: 'Head-to-Head',
|
|
34
|
+
weight: this.weights.h2h,
|
|
35
|
+
impact: h2hScore > 0 ? 'positive' : h2hScore < 0 ? 'negative' : 'neutral',
|
|
36
|
+
description: this.describeH2HImpact(input),
|
|
37
|
+
});
|
|
38
|
+
// Calculate home advantage
|
|
39
|
+
const homeAdvantageScore = this.calculateHomeAdvantage(input);
|
|
40
|
+
factors.push({
|
|
41
|
+
name: 'Home Advantage',
|
|
42
|
+
weight: this.weights.home_advantage,
|
|
43
|
+
impact: homeAdvantageScore > 0 ? 'positive' : 'neutral',
|
|
44
|
+
description: `Home advantage factor: ${(homeAdvantageScore * 100).toFixed(1)}%`,
|
|
45
|
+
});
|
|
46
|
+
// Calculate xG score
|
|
47
|
+
const xgScore = this.calculateXgScore(input);
|
|
48
|
+
factors.push({
|
|
49
|
+
name: 'Expected Goals (xG)',
|
|
50
|
+
weight: this.weights.xg,
|
|
51
|
+
impact: xgScore > 0 ? 'positive' : xgScore < 0 ? 'negative' : 'neutral',
|
|
52
|
+
description: `xG differential: ${(xgScore * 100).toFixed(1)}%`,
|
|
53
|
+
});
|
|
54
|
+
// Calculate injury impact
|
|
55
|
+
const injuryScore = this.calculateInjuryImpact(input);
|
|
56
|
+
factors.push({
|
|
57
|
+
name: 'Injuries',
|
|
58
|
+
weight: this.weights.injuries,
|
|
59
|
+
impact: injuryScore > 0 ? 'positive' : injuryScore < 0 ? 'negative' : 'neutral',
|
|
60
|
+
description: this.describeInjuryImpact(input),
|
|
61
|
+
});
|
|
62
|
+
// Calculate fatigue impact
|
|
63
|
+
const fatigueScore = this.calculateFatigueImpact(input);
|
|
64
|
+
factors.push({
|
|
65
|
+
name: 'Fatigue',
|
|
66
|
+
weight: this.weights.fatigue,
|
|
67
|
+
impact: fatigueScore > 0 ? 'positive' : fatigueScore < 0 ? 'negative' : 'neutral',
|
|
68
|
+
description: `Fatigue differential: ${(fatigueScore * 100).toFixed(1)}%`,
|
|
69
|
+
});
|
|
70
|
+
// Weather impact
|
|
71
|
+
const weatherScore = this.calculateWeatherImpact(input);
|
|
72
|
+
factors.push({
|
|
73
|
+
name: 'Weather',
|
|
74
|
+
weight: this.weights.weather,
|
|
75
|
+
impact: weatherScore !== 0 ? (weatherScore > 0 ? 'positive' : 'negative') : 'neutral',
|
|
76
|
+
description: this.describeWeatherImpact(input),
|
|
77
|
+
});
|
|
78
|
+
// Combine all factors
|
|
79
|
+
const combinedScore = this.combineScores([
|
|
80
|
+
{ score: formScore, weight: this.weights.form },
|
|
81
|
+
{ score: h2hScore, weight: this.weights.h2h },
|
|
82
|
+
{ score: homeAdvantageScore, weight: this.weights.home_advantage },
|
|
83
|
+
{ score: xgScore, weight: this.weights.xg },
|
|
84
|
+
{ score: injuryScore, weight: this.weights.injuries },
|
|
85
|
+
{ score: fatigueScore, weight: this.weights.fatigue },
|
|
86
|
+
{ score: weatherScore, weight: this.weights.weather },
|
|
87
|
+
]);
|
|
88
|
+
// Calculate probabilities
|
|
89
|
+
const probabilities = this.scoreToProbabilities(combinedScore);
|
|
90
|
+
// Calculate confidence
|
|
91
|
+
const confidence = this.calculateConfidence(factors, input);
|
|
92
|
+
// Calculate additional markets
|
|
93
|
+
const over25Prob = this.predictOver25(input);
|
|
94
|
+
const bttsProb = this.predictBTTS(input);
|
|
95
|
+
logger.info(`[MLPrediction] ${input.match.homeTeam.name} vs ${input.match.awayTeam.name}: ` +
|
|
96
|
+
`H:${(probabilities.home * 100).toFixed(1)}% D:${(probabilities.draw * 100).toFixed(1)}% A:${(probabilities.away * 100).toFixed(1)}% ` +
|
|
97
|
+
`Confidence: ${confidence.toFixed(1)}%`);
|
|
98
|
+
return {
|
|
99
|
+
homeWin: probabilities.home,
|
|
100
|
+
draw: probabilities.draw,
|
|
101
|
+
awayWin: probabilities.away,
|
|
102
|
+
over25: over25Prob,
|
|
103
|
+
btts: bttsProb,
|
|
104
|
+
confidence,
|
|
105
|
+
factors,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Calculate form score (-1 to 1)
|
|
110
|
+
*/
|
|
111
|
+
calculateFormScore(input) {
|
|
112
|
+
const homePoints = this.formToPoints(input.homeForm.last5);
|
|
113
|
+
const awayPoints = this.formToPoints(input.awayForm.last5);
|
|
114
|
+
// Normalize to -1 to 1
|
|
115
|
+
const maxPoints = 15; // 5 wins = 15 points
|
|
116
|
+
const homeNormalized = (homePoints / maxPoints) * 2 - 1;
|
|
117
|
+
const awayNormalized = (awayPoints / maxPoints) * 2 - 1;
|
|
118
|
+
return homeNormalized - awayNormalized;
|
|
119
|
+
}
|
|
120
|
+
formToPoints(form) {
|
|
121
|
+
return form.reduce((sum, result) => {
|
|
122
|
+
if (result === 'W')
|
|
123
|
+
return sum + 3;
|
|
124
|
+
if (result === 'D')
|
|
125
|
+
return sum + 1;
|
|
126
|
+
return sum;
|
|
127
|
+
}, 0);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Calculate H2H score (-1 to 1)
|
|
131
|
+
*/
|
|
132
|
+
calculateH2HScore(input) {
|
|
133
|
+
const h2h = input.h2h;
|
|
134
|
+
const total = h2h.totalMatches;
|
|
135
|
+
if (total === 0)
|
|
136
|
+
return 0;
|
|
137
|
+
const homeWinRate = h2h.homeWins / total;
|
|
138
|
+
const awayWinRate = h2h.awayWins / total;
|
|
139
|
+
// Weight recent matches more heavily
|
|
140
|
+
const recentMatches = h2h.recentMatches.slice(0, 5);
|
|
141
|
+
let recentScore = 0;
|
|
142
|
+
recentMatches.forEach((match, index) => {
|
|
143
|
+
const weight = (5 - index) / 5; // More recent = higher weight
|
|
144
|
+
if (match.score) {
|
|
145
|
+
if (match.score.home > match.score.away)
|
|
146
|
+
recentScore += weight;
|
|
147
|
+
else if (match.score.home < match.score.away)
|
|
148
|
+
recentScore -= weight;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
// Combine historical and recent
|
|
152
|
+
const historicalScore = homeWinRate - awayWinRate;
|
|
153
|
+
const combinedScore = (historicalScore * 0.4) + (recentScore / 5 * 0.6);
|
|
154
|
+
return Math.max(-1, Math.min(1, combinedScore));
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Calculate home advantage score (0 to 1)
|
|
158
|
+
*/
|
|
159
|
+
calculateHomeAdvantage(input) {
|
|
160
|
+
const homeStats = input.homeStats;
|
|
161
|
+
const awayStats = input.awayStats;
|
|
162
|
+
if (!homeStats?.homeStats || !awayStats?.awayStats) {
|
|
163
|
+
return 0.1; // Default home advantage
|
|
164
|
+
}
|
|
165
|
+
const homeWinRate = homeStats.homeStats.wins / (homeStats.homeStats.matchesPlayed || 1);
|
|
166
|
+
const awayWinRate = awayStats.awayStats.wins / (awayStats.awayStats.matchesPlayed || 1);
|
|
167
|
+
return Math.max(0, homeWinRate - awayWinRate);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Calculate xG score (-1 to 1)
|
|
171
|
+
*/
|
|
172
|
+
calculateXgScore(input) {
|
|
173
|
+
const homeXg = input.homeForm.xG;
|
|
174
|
+
const homeXga = input.homeForm.xGA;
|
|
175
|
+
const awayXg = input.awayForm.xG;
|
|
176
|
+
const awayXga = input.awayForm.xGA;
|
|
177
|
+
// xG differential (attack - defense)
|
|
178
|
+
const homeXgDiff = homeXg - homeXga;
|
|
179
|
+
const awayXgDiff = awayXg - awayXga;
|
|
180
|
+
// Normalize (typical range is -1 to 1 per game)
|
|
181
|
+
const normalized = (homeXgDiff - awayXgDiff) / 2;
|
|
182
|
+
return Math.max(-1, Math.min(1, normalized));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Calculate injury impact (-1 to 1)
|
|
186
|
+
*/
|
|
187
|
+
calculateInjuryImpact(input) {
|
|
188
|
+
const homeKeyInjuries = input.injuries.home.keyPlayers;
|
|
189
|
+
const awayKeyInjuries = input.injuries.away.keyPlayers;
|
|
190
|
+
// Each key player injury is worth ~0.1
|
|
191
|
+
const impact = (awayKeyInjuries - homeKeyInjuries) * 0.1;
|
|
192
|
+
return Math.max(-1, Math.min(1, impact));
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Calculate fatigue impact (-1 to 1)
|
|
196
|
+
*/
|
|
197
|
+
calculateFatigueImpact(input) {
|
|
198
|
+
// This would need actual fatigue data
|
|
199
|
+
// For now, use matches played as proxy
|
|
200
|
+
const homeMatches = input.homeStats.matchesPlayed;
|
|
201
|
+
const awayMatches = input.awayStats.matchesPlayed;
|
|
202
|
+
// More matches = more fatigue
|
|
203
|
+
const diff = awayMatches - homeMatches;
|
|
204
|
+
const impact = diff * 0.02; // Each match difference = 2%
|
|
205
|
+
return Math.max(-1, Math.min(1, impact));
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Calculate weather impact (-0.5 to 0.5)
|
|
209
|
+
*/
|
|
210
|
+
calculateWeatherImpact(input) {
|
|
211
|
+
if (!input.weather)
|
|
212
|
+
return 0;
|
|
213
|
+
const weather = input.weather;
|
|
214
|
+
let impact = 0;
|
|
215
|
+
// Rain favors defensive teams
|
|
216
|
+
if (weather.precipitation) {
|
|
217
|
+
impact -= 0.1;
|
|
218
|
+
}
|
|
219
|
+
// High wind affects passing teams
|
|
220
|
+
if (weather.windSpeed > 20) {
|
|
221
|
+
impact -= 0.1;
|
|
222
|
+
}
|
|
223
|
+
// Extreme temperatures
|
|
224
|
+
if (weather.temperature > 30 || weather.temperature < 0) {
|
|
225
|
+
impact -= 0.05;
|
|
226
|
+
}
|
|
227
|
+
return impact;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Combine weighted scores
|
|
231
|
+
*/
|
|
232
|
+
combineScores(scores) {
|
|
233
|
+
const totalWeight = scores.reduce((sum, s) => sum + s.weight, 0);
|
|
234
|
+
const weightedSum = scores.reduce((sum, s) => sum + (s.score * s.weight), 0);
|
|
235
|
+
return weightedSum / totalWeight;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Convert score to probabilities
|
|
239
|
+
*/
|
|
240
|
+
scoreToProbabilities(score) {
|
|
241
|
+
// Score is from -1 (away advantage) to 1 (home advantage)
|
|
242
|
+
// Convert to probabilities using softmax-like function
|
|
243
|
+
const homeExp = Math.exp(score * 2);
|
|
244
|
+
const drawExp = Math.exp(0); // Neutral
|
|
245
|
+
const awayExp = Math.exp(-score * 2);
|
|
246
|
+
const total = homeExp + drawExp + awayExp;
|
|
247
|
+
return {
|
|
248
|
+
home: homeExp / total,
|
|
249
|
+
draw: drawExp / total,
|
|
250
|
+
away: awayExp / total,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Calculate confidence level
|
|
255
|
+
*/
|
|
256
|
+
calculateConfidence(factors, input) {
|
|
257
|
+
let confidence = 50; // Base confidence
|
|
258
|
+
// More data = higher confidence
|
|
259
|
+
if (input.homeForm.last5.length >= 5)
|
|
260
|
+
confidence += 10;
|
|
261
|
+
if (input.awayForm.last5.length >= 5)
|
|
262
|
+
confidence += 10;
|
|
263
|
+
if (input.h2h.totalMatches >= 3)
|
|
264
|
+
confidence += 10;
|
|
265
|
+
// High impact factors increase confidence
|
|
266
|
+
const highImpactFactors = factors.filter(f => Math.abs(f.weight) > 0.15 && f.impact !== 'neutral').length;
|
|
267
|
+
confidence += highImpactFactors * 5;
|
|
268
|
+
// Cap at 95%
|
|
269
|
+
return Math.min(95, confidence);
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Predict Over 2.5 goals
|
|
273
|
+
*/
|
|
274
|
+
predictOver25(input) {
|
|
275
|
+
const homeXg = input.homeForm.xG / 5; // per game
|
|
276
|
+
const awayXg = input.awayForm.xG / 5;
|
|
277
|
+
const homeXga = input.homeForm.xGA / 5;
|
|
278
|
+
const awayXga = input.awayForm.xGA / 5;
|
|
279
|
+
const expectedGoals = (homeXg + awayXga + awayXg + homeXga) / 2;
|
|
280
|
+
// Convert to probability
|
|
281
|
+
return Math.min(0.9, Math.max(0.1, expectedGoals / 3));
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Predict Both Teams to Score
|
|
285
|
+
*/
|
|
286
|
+
predictBTTS(input) {
|
|
287
|
+
const homeScoring = input.homeForm.goalsFor / 5;
|
|
288
|
+
const awayScoring = input.awayForm.goalsFor / 5;
|
|
289
|
+
const homeConceding = input.homeForm.goalsAgainst / 5;
|
|
290
|
+
const awayConceding = input.awayForm.goalsAgainst / 5;
|
|
291
|
+
const homeLikelyToScore = homeScoring > 1 ? 0.7 : homeScoring > 0.5 ? 0.5 : 0.3;
|
|
292
|
+
const awayLikelyToScore = awayScoring > 1 ? 0.7 : awayScoring > 0.5 ? 0.5 : 0.3;
|
|
293
|
+
return homeLikelyToScore * awayLikelyToScore;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Describe form impact
|
|
297
|
+
*/
|
|
298
|
+
describeFormImpact(input) {
|
|
299
|
+
const homePoints = this.formToPoints(input.homeForm.last5);
|
|
300
|
+
const awayPoints = this.formToPoints(input.awayForm.last5);
|
|
301
|
+
if (homePoints > awayPoints + 4) {
|
|
302
|
+
return `${input.match.homeTeam.name} in significantly better form`;
|
|
303
|
+
}
|
|
304
|
+
else if (awayPoints > homePoints + 4) {
|
|
305
|
+
return `${input.match.awayTeam.name} in significantly better form`;
|
|
306
|
+
}
|
|
307
|
+
else if (Math.abs(homePoints - awayPoints) <= 2) {
|
|
308
|
+
return 'Teams in similar form';
|
|
309
|
+
}
|
|
310
|
+
return 'Form advantage detected';
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Describe H2H impact
|
|
314
|
+
*/
|
|
315
|
+
describeH2HImpact(input) {
|
|
316
|
+
const h2h = input.h2h;
|
|
317
|
+
if (h2h.totalMatches === 0) {
|
|
318
|
+
return 'No recent head-to-head data';
|
|
319
|
+
}
|
|
320
|
+
const homeWinRate = (h2h.homeWins / h2h.totalMatches * 100).toFixed(0);
|
|
321
|
+
return `${input.match.homeTeam.name} won ${homeWinRate}% of ${h2h.totalMatches} meetings`;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Describe injury impact
|
|
325
|
+
*/
|
|
326
|
+
describeInjuryImpact(input) {
|
|
327
|
+
const homeKey = input.injuries.home.keyPlayers;
|
|
328
|
+
const awayKey = input.injuries.away.keyPlayers;
|
|
329
|
+
if (homeKey === 0 && awayKey === 0) {
|
|
330
|
+
return 'No key injuries for either team';
|
|
331
|
+
}
|
|
332
|
+
else if (homeKey > awayKey) {
|
|
333
|
+
return `${input.match.homeTeam.name} has ${homeKey} key player(s) injured`;
|
|
334
|
+
}
|
|
335
|
+
else if (awayKey > homeKey) {
|
|
336
|
+
return `${input.match.awayTeam.name} has ${awayKey} key player(s) injured`;
|
|
337
|
+
}
|
|
338
|
+
return 'Both teams have similar injury concerns';
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Describe weather impact
|
|
342
|
+
*/
|
|
343
|
+
describeWeatherImpact(input) {
|
|
344
|
+
if (!input.weather)
|
|
345
|
+
return 'Weather data not available';
|
|
346
|
+
const conditions = [];
|
|
347
|
+
if (input.weather.precipitation)
|
|
348
|
+
conditions.push('rain');
|
|
349
|
+
if (input.weather.windSpeed > 20)
|
|
350
|
+
conditions.push('high wind');
|
|
351
|
+
if (input.weather.temperature > 30)
|
|
352
|
+
conditions.push('hot');
|
|
353
|
+
if (input.weather.temperature < 5)
|
|
354
|
+
conditions.push('cold');
|
|
355
|
+
if (conditions.length === 0) {
|
|
356
|
+
return 'Good weather conditions';
|
|
357
|
+
}
|
|
358
|
+
return `Adverse conditions: ${conditions.join(', ')}`;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Load historical data
|
|
362
|
+
*/
|
|
363
|
+
loadHistoricalData() {
|
|
364
|
+
// Would load from database or file
|
|
365
|
+
// For now, start empty
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Learn from past predictions
|
|
369
|
+
*/
|
|
370
|
+
learnFromResult(prediction, actualResult, odds) {
|
|
371
|
+
// Update weights based on accuracy
|
|
372
|
+
// This is a simplified version - real ML would use gradient descent
|
|
373
|
+
const predictedOutcome = prediction.homeWin > prediction.draw && prediction.homeWin > prediction.awayWin
|
|
374
|
+
? 'home'
|
|
375
|
+
: prediction.awayWin > prediction.draw
|
|
376
|
+
? 'away'
|
|
377
|
+
: 'draw';
|
|
378
|
+
const wasCorrect = predictedOutcome === actualResult;
|
|
379
|
+
if (wasCorrect) {
|
|
380
|
+
logger.info(`[MLPrediction] Correct prediction at ${odds}x`);
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
logger.info(`[MLPrediction] Incorrect prediction. Predicted: ${predictedOutcome}, Actual: ${actualResult}`);
|
|
384
|
+
}
|
|
385
|
+
// Store for analysis
|
|
386
|
+
this.historicalData.set(`result-${Date.now()}`, {
|
|
387
|
+
prediction,
|
|
388
|
+
actualResult,
|
|
389
|
+
odds,
|
|
390
|
+
wasCorrect,
|
|
391
|
+
timestamp: Date.now(),
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
// Singleton instance
|
|
396
|
+
let globalPredictionEngine = null;
|
|
397
|
+
export function getPredictionEngine() {
|
|
398
|
+
if (!globalPredictionEngine) {
|
|
399
|
+
globalPredictionEngine = new MLPredictionEngine();
|
|
400
|
+
}
|
|
401
|
+
return globalPredictionEngine;
|
|
402
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REALTIME DATA MANAGER
|
|
3
|
+
* WebSocket/SSE based real-time data streaming with fallback polling
|
|
4
|
+
*/
|
|
5
|
+
import { EventEmitter } from 'events';
|
|
6
|
+
import type { Match } from './types.js';
|
|
7
|
+
export interface LiveEvent {
|
|
8
|
+
type: 'goal' | 'card' | 'substitution' | 'var' | 'injury' | 'whistle' | 'odds_change' | 'status_change';
|
|
9
|
+
matchId: string;
|
|
10
|
+
timestamp: number;
|
|
11
|
+
minute?: number;
|
|
12
|
+
data: any;
|
|
13
|
+
}
|
|
14
|
+
export interface OddsChangeEvent extends LiveEvent {
|
|
15
|
+
type: 'odds_change';
|
|
16
|
+
data: {
|
|
17
|
+
bookmaker: string;
|
|
18
|
+
market: string;
|
|
19
|
+
selection: string;
|
|
20
|
+
oldOdds: number;
|
|
21
|
+
newOdds: number;
|
|
22
|
+
change: number;
|
|
23
|
+
timestamp: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export declare class RealtimeDataManager extends EventEmitter {
|
|
27
|
+
private cache;
|
|
28
|
+
private wsConnections;
|
|
29
|
+
private pollingIntervals;
|
|
30
|
+
private reconnectAttempts;
|
|
31
|
+
private isRunning;
|
|
32
|
+
constructor();
|
|
33
|
+
/**
|
|
34
|
+
* Start the realtime manager
|
|
35
|
+
*/
|
|
36
|
+
start(): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Stop the realtime manager
|
|
39
|
+
*/
|
|
40
|
+
stop(): void;
|
|
41
|
+
/**
|
|
42
|
+
* Subscribe to a specific match's events
|
|
43
|
+
*/
|
|
44
|
+
subscribeToMatch(matchId: string, callback: (event: LiveEvent) => void): () => void;
|
|
45
|
+
/**
|
|
46
|
+
* Subscribe to odds changes for a match
|
|
47
|
+
*/
|
|
48
|
+
subscribeToOdds(matchId: string, callback: (event: OddsChangeEvent) => void): () => void;
|
|
49
|
+
/**
|
|
50
|
+
* Subscribe to all live events
|
|
51
|
+
*/
|
|
52
|
+
subscribeToAllEvents(callback: (event: LiveEvent) => void): () => void;
|
|
53
|
+
/**
|
|
54
|
+
* Process and broadcast an event
|
|
55
|
+
*/
|
|
56
|
+
processEvent(event: LiveEvent): void;
|
|
57
|
+
/**
|
|
58
|
+
* Get current live matches
|
|
59
|
+
*/
|
|
60
|
+
getLiveMatches(): Match[];
|
|
61
|
+
/**
|
|
62
|
+
* Get match events history
|
|
63
|
+
*/
|
|
64
|
+
getMatchEvents(matchId: string): LiveEvent[];
|
|
65
|
+
/**
|
|
66
|
+
* Connect to match stream (WebSocket or polling)
|
|
67
|
+
*/
|
|
68
|
+
private connectToMatchStream;
|
|
69
|
+
/**
|
|
70
|
+
* Unsubscribe from a match
|
|
71
|
+
*/
|
|
72
|
+
private unsubscribeFromMatch;
|
|
73
|
+
/**
|
|
74
|
+
* Start polling for live scores
|
|
75
|
+
*/
|
|
76
|
+
private startLiveScoresPolling;
|
|
77
|
+
/**
|
|
78
|
+
* Start polling for odds
|
|
79
|
+
*/
|
|
80
|
+
private startOddsPolling;
|
|
81
|
+
/**
|
|
82
|
+
* Start polling for specific match events
|
|
83
|
+
*/
|
|
84
|
+
private startMatchEventsPolling;
|
|
85
|
+
/**
|
|
86
|
+
* Detect score changes and emit goal events
|
|
87
|
+
*/
|
|
88
|
+
private detectScoreChanges;
|
|
89
|
+
/**
|
|
90
|
+
* Detect odds changes and emit events
|
|
91
|
+
*/
|
|
92
|
+
private detectOddsChanges;
|
|
93
|
+
/**
|
|
94
|
+
* Update cache with event
|
|
95
|
+
*/
|
|
96
|
+
private updateCache;
|
|
97
|
+
private fetchLiveScores;
|
|
98
|
+
private fetchOdds;
|
|
99
|
+
private fetchMatchEvents;
|
|
100
|
+
}
|
|
101
|
+
export declare function getRealtimeManager(): RealtimeDataManager;
|
|
102
|
+
export declare function resetRealtimeManager(): void;
|