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