@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.
@@ -0,0 +1,80 @@
1
+ /**
2
+ * DATA QUALITY VALIDATOR
3
+ * Validates and scores data quality for football matches
4
+ */
5
+ import type { Match, Team, MatchOdds } from './types.js';
6
+ export interface DataQualityReport {
7
+ overall: number;
8
+ completeness: number;
9
+ accuracy: number;
10
+ freshness: number;
11
+ consistency: number;
12
+ issues: DataQualityIssue[];
13
+ warnings: DataQualityIssue[];
14
+ suggestions: string[];
15
+ }
16
+ export interface DataQualityIssue {
17
+ field: string;
18
+ severity: 'error' | 'warning' | 'info';
19
+ message: string;
20
+ suggestion?: string;
21
+ value?: any;
22
+ }
23
+ export declare class DataQualityValidator {
24
+ private knownTeams;
25
+ private knownLeagues;
26
+ constructor();
27
+ /**
28
+ * Validate match data and return quality report
29
+ */
30
+ validateMatchData(data: Partial<Match>): DataQualityReport;
31
+ /**
32
+ * Validate team data
33
+ */
34
+ validateTeamData(data: Partial<Team>): DataQualityReport;
35
+ /**
36
+ * Validate odds data
37
+ */
38
+ validateOddsData(odds: MatchOdds): DataQualityReport;
39
+ /**
40
+ * Check data completeness
41
+ */
42
+ private checkCompleteness;
43
+ /**
44
+ * Check data accuracy
45
+ */
46
+ private checkAccuracy;
47
+ /**
48
+ * Check data freshness
49
+ */
50
+ private checkFreshness;
51
+ /**
52
+ * Check data consistency
53
+ */
54
+ private checkConsistency;
55
+ /**
56
+ * Validate team stats
57
+ */
58
+ private validateTeamStats;
59
+ /**
60
+ * Generate improvement suggestions
61
+ */
62
+ private generateSuggestions;
63
+ /**
64
+ * Calculate freshness score from timestamp
65
+ */
66
+ private calculateFreshness;
67
+ /**
68
+ * Calculate overall score from issues and warnings
69
+ */
70
+ private calculateScore;
71
+ /**
72
+ * Quick validation - returns true if data passes basic checks
73
+ */
74
+ isValid(data: Partial<Match>): boolean;
75
+ /**
76
+ * Get quality grade (A-F)
77
+ */
78
+ getGrade(score: number): string;
79
+ }
80
+ export declare function getDataQualityValidator(): DataQualityValidator;
@@ -0,0 +1,460 @@
1
+ /**
2
+ * DATA QUALITY VALIDATOR
3
+ * Validates and scores data quality for football matches
4
+ */
5
+ import { logger } from '../../../utils.js';
6
+ export class DataQualityValidator {
7
+ knownTeams = new Set();
8
+ knownLeagues = new Set();
9
+ constructor() {
10
+ // Initialize known leagues
11
+ this.knownLeagues.add('Premier League');
12
+ this.knownLeagues.add('La Liga');
13
+ this.knownLeagues.add('Bundesliga');
14
+ this.knownLeagues.add('Serie A');
15
+ this.knownLeagues.add('Ligue 1');
16
+ this.knownLeagues.add('Champions League');
17
+ this.knownLeagues.add('Europa League');
18
+ }
19
+ /**
20
+ * Validate match data and return quality report
21
+ */
22
+ validateMatchData(data) {
23
+ const issues = [];
24
+ const warnings = [];
25
+ const suggestions = [];
26
+ // Check completeness
27
+ const completeness = this.checkCompleteness(data, issues);
28
+ // Check accuracy
29
+ const accuracy = this.checkAccuracy(data, issues, warnings);
30
+ // Check freshness
31
+ const freshness = this.checkFreshness(data, warnings);
32
+ // Check consistency
33
+ const consistency = this.checkConsistency(data, issues, warnings);
34
+ // Generate suggestions
35
+ this.generateSuggestions(data, suggestions);
36
+ // Calculate overall score
37
+ const overall = Math.round((completeness + accuracy + freshness + consistency) / 4);
38
+ const report = {
39
+ overall,
40
+ completeness,
41
+ accuracy,
42
+ freshness,
43
+ consistency,
44
+ issues: issues.filter(i => i.severity === 'error'),
45
+ warnings: [...issues.filter(i => i.severity === 'warning'), ...warnings],
46
+ suggestions,
47
+ };
48
+ logger.debug(`[DataQuality] Match ${data.id}: Overall=${overall}, Completeness=${completeness}, Accuracy=${accuracy}`);
49
+ return report;
50
+ }
51
+ /**
52
+ * Validate team data
53
+ */
54
+ validateTeamData(data) {
55
+ const issues = [];
56
+ const warnings = [];
57
+ const suggestions = [];
58
+ // Check required fields
59
+ if (!data.name) {
60
+ issues.push({
61
+ field: 'name',
62
+ severity: 'error',
63
+ message: 'Team name is required',
64
+ });
65
+ }
66
+ if (!data.country) {
67
+ warnings.push({
68
+ field: 'country',
69
+ severity: 'warning',
70
+ message: 'Team country is missing',
71
+ suggestion: 'Add country information for better filtering',
72
+ });
73
+ }
74
+ // Validate stats if present
75
+ if (data.stats) {
76
+ this.validateTeamStats(data.stats, issues, warnings);
77
+ }
78
+ const completeness = this.calculateScore(issues, warnings, 3);
79
+ const accuracy = 100; // Simplified
80
+ const freshness = 100;
81
+ const consistency = 100;
82
+ return {
83
+ overall: Math.round((completeness + accuracy + freshness + consistency) / 4),
84
+ completeness,
85
+ accuracy,
86
+ freshness,
87
+ consistency,
88
+ issues: issues.filter(i => i.severity === 'error'),
89
+ warnings: [...issues.filter(i => i.severity === 'warning'), ...warnings],
90
+ suggestions,
91
+ };
92
+ }
93
+ /**
94
+ * Validate odds data
95
+ */
96
+ validateOddsData(odds) {
97
+ const issues = [];
98
+ const warnings = [];
99
+ // Check required fields
100
+ if (!odds.homeWin || odds.homeWin <= 1) {
101
+ issues.push({
102
+ field: 'homeWin',
103
+ severity: 'error',
104
+ message: `Invalid home win odds: ${odds.homeWin}`,
105
+ value: odds.homeWin,
106
+ });
107
+ }
108
+ if (!odds.draw || odds.draw <= 1) {
109
+ issues.push({
110
+ field: 'draw',
111
+ severity: 'error',
112
+ message: `Invalid draw odds: ${odds.draw}`,
113
+ value: odds.draw,
114
+ });
115
+ }
116
+ if (!odds.awayWin || odds.awayWin <= 1) {
117
+ issues.push({
118
+ field: 'awayWin',
119
+ severity: 'error',
120
+ message: `Invalid away win odds: ${odds.awayWin}`,
121
+ value: odds.awayWin,
122
+ });
123
+ }
124
+ // Check implied probability (overround)
125
+ const impliedProb = (1 / odds.homeWin) + (1 / odds.draw) + (1 / odds.awayWin);
126
+ if (impliedProb < 1 || impliedProb > 1.5) {
127
+ warnings.push({
128
+ field: 'odds',
129
+ severity: 'warning',
130
+ message: `Unusual implied probability: ${(impliedProb * 100).toFixed(1)}%`,
131
+ suggestion: 'Normal range is 100-115%',
132
+ value: impliedProb,
133
+ });
134
+ }
135
+ // Check for arbitrage opportunity (unlikely but possible error)
136
+ if (impliedProb < 0.95) {
137
+ issues.push({
138
+ field: 'odds',
139
+ severity: 'error',
140
+ message: 'Possible arbitrage opportunity detected - verify odds accuracy',
141
+ value: impliedProb,
142
+ });
143
+ }
144
+ const completeness = this.calculateScore(issues, warnings, 3);
145
+ return {
146
+ overall: completeness,
147
+ completeness,
148
+ accuracy: completeness,
149
+ freshness: odds.timestamp ? this.calculateFreshness(odds.timestamp) : 50,
150
+ consistency: 100,
151
+ issues: issues.filter(i => i.severity === 'error'),
152
+ warnings: [...issues.filter(i => i.severity === 'warning'), ...warnings],
153
+ suggestions: [],
154
+ };
155
+ }
156
+ /**
157
+ * Check data completeness
158
+ */
159
+ checkCompleteness(data, issues) {
160
+ const requiredFields = [
161
+ { key: 'id', weight: 15 },
162
+ { key: 'homeTeam', weight: 15 },
163
+ { key: 'awayTeam', weight: 15 },
164
+ { key: 'date', weight: 15 },
165
+ { key: 'status', weight: 10 },
166
+ { key: 'league', weight: 10 },
167
+ ];
168
+ const optionalFields = [
169
+ { key: 'score', weight: 5 },
170
+ { key: 'odds', weight: 5 },
171
+ { key: 'lineups', weight: 3 },
172
+ { key: 'events', weight: 3 },
173
+ { key: 'venue', weight: 2 },
174
+ { key: 'referee', weight: 2 },
175
+ ];
176
+ let score = 0;
177
+ requiredFields.forEach(({ key, weight }) => {
178
+ const value = data[key];
179
+ if (value !== undefined && value !== null) {
180
+ score += weight;
181
+ }
182
+ else {
183
+ issues.push({
184
+ field: key,
185
+ severity: 'error',
186
+ message: `Required field '${key}' is missing`,
187
+ });
188
+ }
189
+ });
190
+ optionalFields.forEach(({ key, weight }) => {
191
+ const value = data[key];
192
+ if (value !== undefined && value !== null) {
193
+ score += weight;
194
+ }
195
+ });
196
+ return score;
197
+ }
198
+ /**
199
+ * Check data accuracy
200
+ */
201
+ checkAccuracy(data, issues, warnings) {
202
+ let score = 100;
203
+ // Check score consistency with status
204
+ if (data.score && data.status === 'scheduled') {
205
+ score -= 15;
206
+ warnings.push({
207
+ field: 'score',
208
+ severity: 'warning',
209
+ message: 'Match has score but status is "scheduled"',
210
+ suggestion: 'Update status to "live" or "finished"',
211
+ });
212
+ }
213
+ // Check for negative scores
214
+ if (data.score) {
215
+ if (data.score.home < 0 || data.score.away < 0) {
216
+ score -= 20;
217
+ issues.push({
218
+ field: 'score',
219
+ severity: 'error',
220
+ message: 'Negative score detected',
221
+ value: data.score,
222
+ });
223
+ }
224
+ // Check for unrealistic scores
225
+ if (data.score.home > 15 || data.score.away > 15) {
226
+ score -= 10;
227
+ warnings.push({
228
+ field: 'score',
229
+ severity: 'warning',
230
+ message: 'Unusually high score detected',
231
+ value: `${data.score.home}-${data.score.away}`,
232
+ });
233
+ }
234
+ }
235
+ // Check date validity
236
+ if (data.date) {
237
+ const matchDate = new Date(data.date);
238
+ const now = new Date();
239
+ const oneYearAgo = new Date(now.getFullYear() - 1, now.getMonth(), now.getDate());
240
+ const oneYearFromNow = new Date(now.getFullYear() + 1, now.getMonth(), now.getDate());
241
+ if (matchDate < oneYearAgo || matchDate > oneYearFromNow) {
242
+ score -= 10;
243
+ warnings.push({
244
+ field: 'date',
245
+ severity: 'warning',
246
+ message: 'Match date is outside expected range',
247
+ value: data.date,
248
+ });
249
+ }
250
+ }
251
+ // Check team names
252
+ if (data.homeTeam && data.awayTeam) {
253
+ if (data.homeTeam.name === data.awayTeam.name) {
254
+ score -= 20;
255
+ issues.push({
256
+ field: 'teams',
257
+ severity: 'error',
258
+ message: 'Home and away teams have the same name',
259
+ value: data.homeTeam.name,
260
+ });
261
+ }
262
+ }
263
+ return Math.max(0, score);
264
+ }
265
+ /**
266
+ * Check data freshness
267
+ */
268
+ checkFreshness(data, warnings) {
269
+ if (!data.date)
270
+ return 50;
271
+ const matchDate = new Date(data.date).getTime();
272
+ const now = Date.now();
273
+ const age = now - matchDate;
274
+ // For live matches, data should be very fresh
275
+ if (data.status === 'live') {
276
+ // Live data older than 2 minutes is stale
277
+ if (age > 2 * 60 * 1000) {
278
+ warnings.push({
279
+ field: 'freshness',
280
+ severity: 'warning',
281
+ message: 'Live match data is stale (>2 minutes old)',
282
+ suggestion: 'Refresh data from source',
283
+ });
284
+ return 50;
285
+ }
286
+ return 100;
287
+ }
288
+ // For scheduled matches, freshness is less critical
289
+ if (data.status === 'scheduled') {
290
+ return 100;
291
+ }
292
+ // For finished matches, check if recently completed
293
+ if (data.status === 'finished') {
294
+ // Match finished within last hour
295
+ if (age < 60 * 60 * 1000) {
296
+ return 100;
297
+ }
298
+ // Match finished within last day
299
+ if (age < 24 * 60 * 60 * 1000) {
300
+ return 80;
301
+ }
302
+ return 60;
303
+ }
304
+ return 70;
305
+ }
306
+ /**
307
+ * Check data consistency
308
+ */
309
+ checkConsistency(data, issues, warnings) {
310
+ let score = 100;
311
+ // Check team stats consistency
312
+ if (data.homeTeam?.stats && data.awayTeam?.stats) {
313
+ const homeStats = data.homeTeam.stats;
314
+ const awayStats = data.awayTeam.stats;
315
+ // Check if matches played is consistent
316
+ if (homeStats.matchesPlayed !== awayStats.matchesPlayed &&
317
+ data.league?.name !== 'Champions League' &&
318
+ data.league?.name !== 'Europa League') {
319
+ // This might be OK for cup competitions
320
+ warnings.push({
321
+ field: 'stats',
322
+ severity: 'info',
323
+ message: 'Teams have different number of matches played',
324
+ });
325
+ }
326
+ }
327
+ // Check score consistency
328
+ if (data.score && data.status === 'finished') {
329
+ const totalGoals = data.score.home + data.score.away;
330
+ // Very high scoring match - verify
331
+ if (totalGoals > 10) {
332
+ score -= 5;
333
+ warnings.push({
334
+ field: 'score',
335
+ severity: 'info',
336
+ message: `High scoring match: ${totalGoals} total goals`,
337
+ });
338
+ }
339
+ }
340
+ return Math.max(0, score);
341
+ }
342
+ /**
343
+ * Validate team stats
344
+ */
345
+ validateTeamStats(stats, issues, warnings) {
346
+ // Check matches played
347
+ if (stats.matchesPlayed < 0) {
348
+ issues.push({
349
+ field: 'matchesPlayed',
350
+ severity: 'error',
351
+ message: 'Matches played cannot be negative',
352
+ value: stats.matchesPlayed,
353
+ });
354
+ }
355
+ // Check wins + draws + losses = matches played
356
+ const totalResults = stats.wins + stats.draws + stats.losses;
357
+ if (totalResults !== stats.matchesPlayed && stats.matchesPlayed > 0) {
358
+ warnings.push({
359
+ field: 'stats',
360
+ severity: 'warning',
361
+ message: `Wins+Draws+Losses (${totalResults}) != Matches Played (${stats.matchesPlayed})`,
362
+ });
363
+ }
364
+ // Check goal difference
365
+ const calculatedGD = stats.goalsFor - stats.goalsAgainst;
366
+ if (stats.goalDifference !== undefined && stats.goalDifference !== calculatedGD) {
367
+ warnings.push({
368
+ field: 'goalDifference',
369
+ severity: 'warning',
370
+ message: `Goal difference mismatch: ${stats.goalDifference} vs calculated ${calculatedGD}`,
371
+ });
372
+ }
373
+ // Check points
374
+ const calculatedPoints = (stats.wins * 3) + stats.draws;
375
+ if (stats.points !== undefined && stats.points !== calculatedPoints) {
376
+ warnings.push({
377
+ field: 'points',
378
+ severity: 'warning',
379
+ message: `Points mismatch: ${stats.points} vs calculated ${calculatedPoints}`,
380
+ });
381
+ }
382
+ }
383
+ /**
384
+ * Generate improvement suggestions
385
+ */
386
+ generateSuggestions(data, suggestions) {
387
+ if (!data.odds) {
388
+ suggestions.push('Add betting odds for value analysis');
389
+ }
390
+ if (!data.homeLineup && !data.awayLineup) {
391
+ suggestions.push('Add team lineups for better predictions');
392
+ }
393
+ if (!data.homeTeam?.stats?.xG && !data.awayTeam?.stats?.xG) {
394
+ suggestions.push('Add expected goals (xG) data for advanced analysis');
395
+ }
396
+ if (!data.referee) {
397
+ suggestions.push('Add referee information for bias analysis');
398
+ }
399
+ if (!data.venue) {
400
+ suggestions.push('Add venue information for home advantage analysis');
401
+ }
402
+ }
403
+ /**
404
+ * Calculate freshness score from timestamp
405
+ */
406
+ calculateFreshness(timestamp) {
407
+ const ts = timestamp instanceof Date ? timestamp.getTime() : timestamp;
408
+ const age = Date.now() - ts;
409
+ if (age < 60000)
410
+ return 100; // < 1 minute
411
+ if (age < 300000)
412
+ return 90; // < 5 minutes
413
+ if (age < 600000)
414
+ return 80; // < 10 minutes
415
+ if (age < 1800000)
416
+ return 60; // < 30 minutes
417
+ if (age < 3600000)
418
+ return 40; // < 1 hour
419
+ return 20;
420
+ }
421
+ /**
422
+ * Calculate overall score from issues and warnings
423
+ */
424
+ calculateScore(errors, warnings, maxFields) {
425
+ const errorWeight = 20;
426
+ const warningWeight = 5;
427
+ const errorDeduction = errors.filter(i => i.severity === 'error').length * errorWeight;
428
+ const warningDeduction = warnings.length * warningWeight;
429
+ return Math.max(0, 100 - errorDeduction - warningDeduction);
430
+ }
431
+ /**
432
+ * Quick validation - returns true if data passes basic checks
433
+ */
434
+ isValid(data) {
435
+ const report = this.validateMatchData(data);
436
+ return report.issues.length === 0 && report.overall >= 70;
437
+ }
438
+ /**
439
+ * Get quality grade (A-F)
440
+ */
441
+ getGrade(score) {
442
+ if (score >= 90)
443
+ return 'A';
444
+ if (score >= 80)
445
+ return 'B';
446
+ if (score >= 70)
447
+ return 'C';
448
+ if (score >= 60)
449
+ return 'D';
450
+ return 'F';
451
+ }
452
+ }
453
+ // Singleton instance
454
+ let globalValidator = null;
455
+ export function getDataQualityValidator() {
456
+ if (!globalValidator) {
457
+ globalValidator = new DataQualityValidator();
458
+ }
459
+ return globalValidator;
460
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * HISTORICAL ANALYZER
3
+ * Analyzes historical data for patterns and trends
4
+ */
5
+ import type { Match, HeadToHead, HistoricalPattern, TeamHistoricalPerformance } from './types.js';
6
+ export interface HistoricalQuery {
7
+ team?: string;
8
+ league?: string;
9
+ dateFrom?: Date;
10
+ dateTo?: Date;
11
+ opponent?: string;
12
+ venue?: 'home' | 'away' | 'neutral';
13
+ minOdds?: number;
14
+ maxOdds?: number;
15
+ }
16
+ export interface TrendAnalysis {
17
+ direction: 'up' | 'down' | 'stable';
18
+ strength: number;
19
+ confidence: number;
20
+ dataPoints: number;
21
+ description: string;
22
+ }
23
+ export interface PerformanceMetrics {
24
+ winRate: number;
25
+ drawRate: number;
26
+ lossRate: number;
27
+ avgGoalsFor: number;
28
+ avgGoalsAgainst: number;
29
+ cleanSheetRate: number;
30
+ bothTeamsScoreRate: number;
31
+ over25Rate: number;
32
+ }
33
+ /**
34
+ * Historical Analyzer
35
+ * Analyzes past matches for patterns and predictive insights
36
+ */
37
+ export declare class HistoricalAnalyzer {
38
+ private matchHistory;
39
+ private patterns;
40
+ constructor();
41
+ /**
42
+ * Analyze team performance over time
43
+ */
44
+ analyzeTeamPerformance(teamId: string, query?: HistoricalQuery): TeamHistoricalPerformance;
45
+ /**
46
+ * Analyze head-to-head history
47
+ */
48
+ analyzeHeadToHead(teamAId: string, teamBId: string, limit?: number): HeadToHead;
49
+ /**
50
+ * Analyze trends for a team
51
+ */
52
+ analyzeTrends(teamId: string, windowSize?: number): TrendAnalysis;
53
+ /**
54
+ * Calculate performance metrics
55
+ */
56
+ calculateMetrics(teamId: string, query?: HistoricalQuery): PerformanceMetrics;
57
+ /**
58
+ * Find betting patterns
59
+ */
60
+ findBettingPatterns(query: HistoricalQuery): HistoricalPattern[];
61
+ /**
62
+ * Analyze referee bias
63
+ */
64
+ analyzeRefereeBias(refereeName: string, teamId?: string): {
65
+ referee: string;
66
+ matches: number;
67
+ avgCards: number;
68
+ homeWinRate: number;
69
+ teamBias?: number;
70
+ };
71
+ /**
72
+ * Analyze weather impact
73
+ */
74
+ analyzeWeatherImpact(condition: string, teamId?: string): {
75
+ condition: string;
76
+ matches: number;
77
+ avgGoals: number;
78
+ homeWinRate: number;
79
+ teamPerformance?: number;
80
+ };
81
+ /**
82
+ * Get similar matches
83
+ */
84
+ findSimilarMatches(match: Match, limit?: number): Match[];
85
+ /**
86
+ * Calculate similarity between two matches
87
+ */
88
+ private calculateSimilarity;
89
+ private getTeamMatches;
90
+ private getH2HMatches;
91
+ private isWin;
92
+ private isDraw;
93
+ private isLoss;
94
+ private getGoalsFor;
95
+ private getGoalsAgainst;
96
+ private getMatchPoints;
97
+ private calculateHomeAdvantage;
98
+ private findPatterns;
99
+ private calculateProfit;
100
+ private queryMatches;
101
+ private createEmptyPerformance;
102
+ private loadHistoricalData;
103
+ /**
104
+ * Add match to history
105
+ */
106
+ addMatch(match: Match): void;
107
+ }
108
+ export declare function getHistoricalAnalyzer(): HistoricalAnalyzer;