@gotza02/sequential-thinking 10000.2.0 → 10000.2.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/README.md.bak +197 -0
- package/dist/.gemini_graph_cache.json.bak +1641 -0
- package/dist/graph.d.ts +7 -0
- package/dist/graph.js +136 -113
- package/dist/intelligent-code.d.ts +8 -0
- package/dist/intelligent-code.js +152 -125
- package/dist/intelligent-code.test.d.ts +1 -0
- package/dist/intelligent-code.test.js +104 -0
- package/dist/lib/formatters.d.ts +9 -0
- package/dist/lib/formatters.js +119 -0
- package/dist/lib/validators.d.ts +45 -0
- package/dist/lib/validators.js +232 -0
- package/dist/lib.js +23 -265
- package/dist/tools/sports/core/base.d.ts +3 -2
- package/dist/tools/sports/core/base.js +12 -10
- package/dist/tools/sports/core/cache.d.ts +9 -0
- package/dist/tools/sports/core/cache.js +25 -3
- package/dist/tools/sports/core/types.d.ts +6 -2
- package/dist/tools/sports/providers/api.d.ts +4 -0
- package/dist/tools/sports/providers/api.js +110 -27
- package/dist/tools/sports/tools/betting.js +16 -16
- package/dist/tools/sports/tools/league.d.ts +2 -7
- package/dist/tools/sports/tools/league.js +198 -8
- package/dist/tools/sports/tools/live.js +80 -38
- package/dist/tools/sports/tools/match-calculations.d.ts +51 -0
- package/dist/tools/sports/tools/match-calculations.js +171 -0
- package/dist/tools/sports/tools/match-helpers.d.ts +21 -0
- package/dist/tools/sports/tools/match-helpers.js +57 -0
- package/dist/tools/sports/tools/match.js +227 -125
- package/dist/tools/sports.js +3 -3
- package/dist/utils.d.ts +111 -44
- package/dist/utils.js +510 -305
- package/dist/utils.test.js +3 -3
- package/package.json +1 -1
- package/CLAUDE.md +0 -231
|
@@ -6,34 +6,65 @@ import { z } from 'zod';
|
|
|
6
6
|
import * as fs from 'fs/promises';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import { getSearchProvider } from '../providers/search.js';
|
|
9
|
+
import { logger } from '../../../utils.js';
|
|
9
10
|
// Configuration for watchlist persistence
|
|
10
11
|
const WATCHLIST_FILE = process.env.SPORTS_WATCHLIST_PATH || '.sports_watchlist.json';
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
// Watchlist storage with proper initialization
|
|
13
|
+
class WatchlistStorage {
|
|
14
|
+
items = [];
|
|
15
|
+
initialized = false;
|
|
16
|
+
initPromise = null;
|
|
17
|
+
async initialize() {
|
|
18
|
+
if (this.initialized)
|
|
19
|
+
return;
|
|
20
|
+
// Prevent concurrent initialization
|
|
21
|
+
if (this.initPromise)
|
|
22
|
+
return this.initPromise;
|
|
23
|
+
this.initPromise = this.loadFromFile();
|
|
24
|
+
await this.initPromise;
|
|
25
|
+
this.initialized = true;
|
|
19
26
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
async loadFromFile() {
|
|
28
|
+
try {
|
|
29
|
+
const filePath = path.resolve(WATCHLIST_FILE);
|
|
30
|
+
const data = await fs.readFile(filePath, 'utf-8');
|
|
31
|
+
this.items = JSON.parse(data);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
// File doesn't exist or is invalid, start with empty list
|
|
35
|
+
this.items = [];
|
|
36
|
+
}
|
|
23
37
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
async save() {
|
|
39
|
+
try {
|
|
40
|
+
const filePath = path.resolve(WATCHLIST_FILE);
|
|
41
|
+
await fs.writeFile(filePath, JSON.stringify(this.items, null, 2), 'utf-8');
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
logger.error(`Failed to save watchlist to ${WATCHLIST_FILE}:`, error);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
getItems() {
|
|
48
|
+
return this.items;
|
|
49
|
+
}
|
|
50
|
+
add(item) {
|
|
51
|
+
this.items.push(item);
|
|
52
|
+
}
|
|
53
|
+
remove(id) {
|
|
54
|
+
const index = this.items.findIndex(item => item.id === id);
|
|
55
|
+
if (index === -1)
|
|
56
|
+
return null;
|
|
57
|
+
return this.items.splice(index, 1)[0];
|
|
58
|
+
}
|
|
59
|
+
clear() {
|
|
60
|
+
this.items = [];
|
|
30
61
|
}
|
|
31
|
-
|
|
32
|
-
|
|
62
|
+
get length() {
|
|
63
|
+
return this.items.length;
|
|
33
64
|
}
|
|
34
65
|
}
|
|
35
|
-
//
|
|
36
|
-
|
|
66
|
+
// Singleton instance
|
|
67
|
+
const watchlistStorage = new WatchlistStorage();
|
|
37
68
|
/**
|
|
38
69
|
* Register live and alert tools
|
|
39
70
|
*/
|
|
@@ -58,6 +89,8 @@ Can be called with:
|
|
|
58
89
|
awayTeam: z.string().optional().describe('Away team name'),
|
|
59
90
|
alertCondition: z.string().optional().describe('Alert condition for match'),
|
|
60
91
|
}, async ({ type, target, condition, matchId, homeTeam, awayTeam, alertCondition }) => {
|
|
92
|
+
// Ensure watchlist is initialized before use
|
|
93
|
+
await watchlistStorage.initialize();
|
|
61
94
|
const id = `watch_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
62
95
|
// Handle match mode (matchId, homeTeam, awayTeam provided)
|
|
63
96
|
let itemType = type;
|
|
@@ -88,8 +121,8 @@ Can be called with:
|
|
|
88
121
|
createdAt: new Date().toISOString(),
|
|
89
122
|
triggered: false,
|
|
90
123
|
};
|
|
91
|
-
|
|
92
|
-
await
|
|
124
|
+
watchlistStorage.add(item);
|
|
125
|
+
await watchlistStorage.save();
|
|
93
126
|
let output = `## 📋 Added to Watchlist\n\n`;
|
|
94
127
|
output += `**ID:** ${id}\n`;
|
|
95
128
|
output += `**Type:** ${itemType}\n`;
|
|
@@ -100,7 +133,7 @@ Can be called with:
|
|
|
100
133
|
output += `**Match:** ${homeTeam} vs ${awayTeam}\n`;
|
|
101
134
|
}
|
|
102
135
|
output += `**Created:** ${item.createdAt}\n`;
|
|
103
|
-
output += `\n\nTotal watchlist items: ${
|
|
136
|
+
output += `\n\nTotal watchlist items: ${watchlistStorage.length}`;
|
|
104
137
|
return {
|
|
105
138
|
content: [{ type: 'text', text: output }],
|
|
106
139
|
};
|
|
@@ -110,13 +143,16 @@ Can be called with:
|
|
|
110
143
|
* List all items in the watchlist
|
|
111
144
|
*/
|
|
112
145
|
server.tool('watchlist_list', `List all items in the sports watchlist.`, {}, async () => {
|
|
113
|
-
|
|
146
|
+
// Ensure watchlist is initialized
|
|
147
|
+
await watchlistStorage.initialize();
|
|
148
|
+
const items = watchlistStorage.getItems();
|
|
149
|
+
if (items.length === 0) {
|
|
114
150
|
return {
|
|
115
151
|
content: [{ type: 'text', text: '## 📋 Watchlist\n\nNo items in watchlist.' }],
|
|
116
152
|
};
|
|
117
153
|
}
|
|
118
|
-
let output = `## 📋 Watchlist (${
|
|
119
|
-
for (const item of
|
|
154
|
+
let output = `## 📋 Watchlist (${items.length} items)\n\n`;
|
|
155
|
+
for (const item of items) {
|
|
120
156
|
const status = item.triggered ? '✅' : '👀';
|
|
121
157
|
output += `${status} **${item.id}**\n`;
|
|
122
158
|
output += `- Type: ${item.type}\n`;
|
|
@@ -136,20 +172,21 @@ Can be called with:
|
|
|
136
172
|
server.tool('watchlist_remove', `Remove an item from the sports watchlist.`, {
|
|
137
173
|
id: z.string().describe('Watchlist item ID to remove'),
|
|
138
174
|
}, async ({ id }) => {
|
|
139
|
-
|
|
140
|
-
|
|
175
|
+
// Ensure watchlist is initialized
|
|
176
|
+
await watchlistStorage.initialize();
|
|
177
|
+
const removed = watchlistStorage.remove(id);
|
|
178
|
+
if (!removed) {
|
|
141
179
|
return {
|
|
142
180
|
content: [{ type: 'text', text: `Error: Watchlist item "${id}" not found.` }],
|
|
143
181
|
isError: true,
|
|
144
182
|
};
|
|
145
183
|
}
|
|
146
|
-
|
|
147
|
-
await saveWatchlist();
|
|
184
|
+
await watchlistStorage.save();
|
|
148
185
|
let output = `## 📋 Removed from Watchlist\n\n`;
|
|
149
186
|
output += `**ID:** ${removed.id}\n`;
|
|
150
187
|
output += `**Type:** ${removed.type}\n`;
|
|
151
188
|
output += `**Target:** ${removed.target}\n`;
|
|
152
|
-
output += `\n\nRemaining items: ${
|
|
189
|
+
output += `\n\nRemaining items: ${watchlistStorage.length}`;
|
|
153
190
|
return {
|
|
154
191
|
content: [{ type: 'text', text: output }],
|
|
155
192
|
};
|
|
@@ -159,9 +196,11 @@ Can be called with:
|
|
|
159
196
|
* Clear all items from the watchlist
|
|
160
197
|
*/
|
|
161
198
|
server.tool('watchlist_clear', `Clear all items from the sports watchlist.`, {}, async () => {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
199
|
+
// Ensure watchlist is initialized
|
|
200
|
+
await watchlistStorage.initialize();
|
|
201
|
+
const count = watchlistStorage.length;
|
|
202
|
+
watchlistStorage.clear();
|
|
203
|
+
await watchlistStorage.save();
|
|
165
204
|
return {
|
|
166
205
|
content: [{ type: 'text', text: `## 📋 Watchlist Cleared\n\nRemoved ${count} item(s).` }],
|
|
167
206
|
};
|
|
@@ -171,8 +210,11 @@ Can be called with:
|
|
|
171
210
|
* Check watchlist for triggered alerts
|
|
172
211
|
*/
|
|
173
212
|
server.tool('check_alerts', `Check watchlist for triggered alerts based on current data.`, {}, async () => {
|
|
213
|
+
// Ensure watchlist is initialized
|
|
214
|
+
await watchlistStorage.initialize();
|
|
174
215
|
let output = `## 🔔 Alert Check Results\n\n`;
|
|
175
|
-
|
|
216
|
+
const items = watchlistStorage.getItems();
|
|
217
|
+
if (items.length === 0) {
|
|
176
218
|
output += 'No items in watchlist.';
|
|
177
219
|
return {
|
|
178
220
|
content: [{ type: 'text', text: output }],
|
|
@@ -181,7 +223,7 @@ Can be called with:
|
|
|
181
223
|
const searchProvider = getSearchProvider();
|
|
182
224
|
let triggeredCount = 0;
|
|
183
225
|
let saveNeeded = false;
|
|
184
|
-
for (const item of
|
|
226
|
+
for (const item of items) {
|
|
185
227
|
let triggered = false;
|
|
186
228
|
let message = '';
|
|
187
229
|
// Check based on type
|
|
@@ -217,7 +259,7 @@ Can be called with:
|
|
|
217
259
|
}
|
|
218
260
|
}
|
|
219
261
|
if (saveNeeded) {
|
|
220
|
-
await
|
|
262
|
+
await watchlistStorage.save();
|
|
221
263
|
}
|
|
222
264
|
if (triggeredCount === 0) {
|
|
223
265
|
output += 'No new alerts. All conditions normal.';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPORTS MODULE - Match Calculations
|
|
3
|
+
* Probability and statistical calculation functions
|
|
4
|
+
*/
|
|
5
|
+
import { Match, ProbabilityRange } from '../core/types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Calculate adaptive TTL based on data type and match timing
|
|
8
|
+
*/
|
|
9
|
+
export declare function calculateAdaptiveTTL(dataType: 'match' | 'lineups' | 'odds' | 'news' | 'stats' | 'h2h' | 'form', matchDate?: Date): number;
|
|
10
|
+
/**
|
|
11
|
+
* Calculate probability range with uncertainty quantification
|
|
12
|
+
*/
|
|
13
|
+
export declare function calculateProbabilityRange(baseProbability: number, dataQuality: {
|
|
14
|
+
score: number;
|
|
15
|
+
}): {
|
|
16
|
+
low: number;
|
|
17
|
+
mid: number;
|
|
18
|
+
high: number;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Calculate points from a team's form matches
|
|
22
|
+
*/
|
|
23
|
+
export declare function calculateFormPoints(form: Match[], teamName: string): number;
|
|
24
|
+
/**
|
|
25
|
+
* Calculate expected goals from form data
|
|
26
|
+
*/
|
|
27
|
+
export declare function calculateExpectedGoals(form: Match[], teamName: string, isHomeTeam: boolean): {
|
|
28
|
+
xG: number;
|
|
29
|
+
attackStrength: number;
|
|
30
|
+
defenseWeakness: number;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Calculate probabilities from form data using proper normalization
|
|
34
|
+
*/
|
|
35
|
+
export declare function calculateProbabilitiesFromForm(homeForm: Match[], awayForm: Match[], homeTeamName: string, awayTeamName: string, dataQuality: number): {
|
|
36
|
+
homeWin: ProbabilityRange;
|
|
37
|
+
draw: ProbabilityRange;
|
|
38
|
+
awayWin: ProbabilityRange;
|
|
39
|
+
expectedGoals: {
|
|
40
|
+
home: number;
|
|
41
|
+
away: number;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Calculate probability from betting odds
|
|
46
|
+
*/
|
|
47
|
+
export declare function calculateProbabilityFromOdds(odds: number): number;
|
|
48
|
+
/**
|
|
49
|
+
* Calculate Kelly Criterion stake
|
|
50
|
+
*/
|
|
51
|
+
export declare function calculateKellyStake(bankroll: number, probability: number, odds: number, fraction?: number): number;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPORTS MODULE - Match Calculations
|
|
3
|
+
* Probability and statistical calculation functions
|
|
4
|
+
*/
|
|
5
|
+
import { normalizeTeamName } from './match-helpers.js';
|
|
6
|
+
/**
|
|
7
|
+
* Calculate adaptive TTL based on data type and match timing
|
|
8
|
+
*/
|
|
9
|
+
export function calculateAdaptiveTTL(dataType, matchDate) {
|
|
10
|
+
const now = Date.now();
|
|
11
|
+
const hoursUntilMatch = matchDate
|
|
12
|
+
? (matchDate.getTime() - now) / (1000 * 60 * 60)
|
|
13
|
+
: Infinity;
|
|
14
|
+
switch (dataType) {
|
|
15
|
+
case 'odds':
|
|
16
|
+
if (hoursUntilMatch < 1)
|
|
17
|
+
return 60 * 1000;
|
|
18
|
+
if (hoursUntilMatch < 24)
|
|
19
|
+
return 5 * 60 * 1000;
|
|
20
|
+
return 15 * 60 * 1000;
|
|
21
|
+
case 'lineups':
|
|
22
|
+
if (hoursUntilMatch < 2)
|
|
23
|
+
return 5 * 60 * 1000;
|
|
24
|
+
return 60 * 60 * 1000;
|
|
25
|
+
case 'news':
|
|
26
|
+
if (hoursUntilMatch < 24)
|
|
27
|
+
return 15 * 60 * 1000;
|
|
28
|
+
return 60 * 60 * 1000;
|
|
29
|
+
case 'stats':
|
|
30
|
+
case 'h2h':
|
|
31
|
+
return 24 * 60 * 60 * 1000;
|
|
32
|
+
case 'form':
|
|
33
|
+
if (hoursUntilMatch < 0)
|
|
34
|
+
return 60 * 1000;
|
|
35
|
+
return 6 * 60 * 60 * 1000;
|
|
36
|
+
case 'match':
|
|
37
|
+
if (hoursUntilMatch < 0)
|
|
38
|
+
return 60 * 1000;
|
|
39
|
+
if (hoursUntilMatch < 1)
|
|
40
|
+
return 5 * 60 * 1000;
|
|
41
|
+
return 30 * 60 * 1000;
|
|
42
|
+
default:
|
|
43
|
+
return 60 * 60 * 1000;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Calculate probability range with uncertainty quantification
|
|
48
|
+
*/
|
|
49
|
+
export function calculateProbabilityRange(baseProbability, dataQuality) {
|
|
50
|
+
// Higher data quality = narrower range
|
|
51
|
+
const qualityFactor = dataQuality.score / 100;
|
|
52
|
+
const baseUncertainty = 0.15; // 15% base uncertainty
|
|
53
|
+
// Adjust uncertainty based on data quality (better quality = smaller range)
|
|
54
|
+
const adjustedUncertainty = baseUncertainty * (1 - qualityFactor * 0.7);
|
|
55
|
+
// Calculate range using standard error formula
|
|
56
|
+
const mid = baseProbability;
|
|
57
|
+
// Margin decreases with better data quality
|
|
58
|
+
const margin = adjustedUncertainty * Math.sqrt(mid * (1 - mid) + 0.1);
|
|
59
|
+
return {
|
|
60
|
+
low: Math.max(0, mid - margin * 1.96),
|
|
61
|
+
mid: Math.min(1, Math.max(0, mid)),
|
|
62
|
+
high: Math.min(1, mid + margin * 1.96),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Calculate points from a team's form matches
|
|
67
|
+
*/
|
|
68
|
+
export function calculateFormPoints(form, teamName) {
|
|
69
|
+
let points = 0;
|
|
70
|
+
for (const match of form) {
|
|
71
|
+
const isHome = normalizeTeamName(match.homeTeam.name).includes(normalizeTeamName(teamName));
|
|
72
|
+
const homeScore = match.score?.home ?? 0;
|
|
73
|
+
const awayScore = match.score?.away ?? 0;
|
|
74
|
+
const teamScore = isHome ? homeScore : awayScore;
|
|
75
|
+
const opponentScore = isHome ? awayScore : homeScore;
|
|
76
|
+
if (teamScore > opponentScore)
|
|
77
|
+
points += 3;
|
|
78
|
+
else if (teamScore === opponentScore)
|
|
79
|
+
points += 1;
|
|
80
|
+
}
|
|
81
|
+
return points;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Calculate expected goals from form data
|
|
85
|
+
*/
|
|
86
|
+
export function calculateExpectedGoals(form, teamName, isHomeTeam) {
|
|
87
|
+
let totalGoalsScored = 0;
|
|
88
|
+
let totalGoalsConceded = 0;
|
|
89
|
+
let matchesWithData = 0;
|
|
90
|
+
for (const match of form) {
|
|
91
|
+
if (!match.score)
|
|
92
|
+
continue;
|
|
93
|
+
const isHome = normalizeTeamName(match.homeTeam.name).includes(normalizeTeamName(teamName));
|
|
94
|
+
const homeScore = match.score.home ?? 0;
|
|
95
|
+
const awayScore = match.score.away ?? 0;
|
|
96
|
+
const teamScore = isHome ? homeScore : awayScore;
|
|
97
|
+
const opponentScore = isHome ? awayScore : homeScore;
|
|
98
|
+
totalGoalsScored += teamScore;
|
|
99
|
+
totalGoalsConceded += opponentScore;
|
|
100
|
+
matchesWithData++;
|
|
101
|
+
}
|
|
102
|
+
if (matchesWithData === 0) {
|
|
103
|
+
return { xG: 1.3, attackStrength: 1.0, defenseWeakness: 1.0 };
|
|
104
|
+
}
|
|
105
|
+
const avgGoalsScored = totalGoalsScored / matchesWithData;
|
|
106
|
+
const avgGoalsConceded = totalGoalsConceded / matchesWithData;
|
|
107
|
+
// League average is approximately 1.3 goals per match
|
|
108
|
+
const leagueAvgGoals = 1.3;
|
|
109
|
+
const attackStrength = avgGoalsScored / leagueAvgGoals;
|
|
110
|
+
const defenseWeakness = avgGoalsConceded / leagueAvgGoals;
|
|
111
|
+
// xG estimation
|
|
112
|
+
const xG = avgGoalsScored * (isHomeTeam ? 1.2 : 0.9);
|
|
113
|
+
return {
|
|
114
|
+
xG: Math.round(xG * 10) / 10,
|
|
115
|
+
attackStrength: Math.round(attackStrength * 100) / 100,
|
|
116
|
+
defenseWeakness: Math.round(defenseWeakness * 100) / 100
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Calculate probabilities from form data using proper normalization
|
|
121
|
+
*/
|
|
122
|
+
export function calculateProbabilitiesFromForm(homeForm, awayForm, homeTeamName, awayTeamName, dataQuality) {
|
|
123
|
+
// Calculate form metrics
|
|
124
|
+
const homeXGData = calculateExpectedGoals(homeForm, homeTeamName, true);
|
|
125
|
+
const awayXGData = calculateExpectedGoals(awayForm, awayTeamName, false);
|
|
126
|
+
// Base probabilities from xG
|
|
127
|
+
const homeAdvantage = 1.2;
|
|
128
|
+
const adjustedHomeXg = homeXGData.xG * homeAdvantage;
|
|
129
|
+
const adjustedAwayXg = awayXGData.xG;
|
|
130
|
+
const totalXg = adjustedHomeXg + adjustedAwayXg;
|
|
131
|
+
const drawFactor = 0.25;
|
|
132
|
+
let homeProb = adjustedHomeXg / totalXg * (1 - drawFactor);
|
|
133
|
+
let awayProb = adjustedAwayXg / totalXg * (1 - drawFactor);
|
|
134
|
+
let drawProb = drawFactor;
|
|
135
|
+
// Normalize
|
|
136
|
+
const total = homeProb + awayProb + drawProb;
|
|
137
|
+
homeProb /= total;
|
|
138
|
+
awayProb /= total;
|
|
139
|
+
drawProb /= total;
|
|
140
|
+
// Determine confidence based on data quality and sample size
|
|
141
|
+
const sampleSize = Math.min(homeForm.length, awayForm.length);
|
|
142
|
+
let confidence = 'low';
|
|
143
|
+
if (sampleSize >= 5 && dataQuality >= 70)
|
|
144
|
+
confidence = 'high';
|
|
145
|
+
else if (sampleSize >= 3 && dataQuality >= 50)
|
|
146
|
+
confidence = 'medium';
|
|
147
|
+
return {
|
|
148
|
+
homeWin: calculateProbabilityRange(homeProb, confidence, dataQuality),
|
|
149
|
+
draw: calculateProbabilityRange(drawProb, confidence, dataQuality),
|
|
150
|
+
awayWin: calculateProbabilityRange(awayProb, confidence, dataQuality),
|
|
151
|
+
expectedGoals: {
|
|
152
|
+
home: homeXGData.xG,
|
|
153
|
+
away: awayXGData.xG
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Calculate probability from betting odds
|
|
159
|
+
*/
|
|
160
|
+
export function calculateProbabilityFromOdds(odds) {
|
|
161
|
+
if (odds <= 1)
|
|
162
|
+
return 0.5;
|
|
163
|
+
return 1 / odds;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Calculate Kelly Criterion stake
|
|
167
|
+
*/
|
|
168
|
+
export function calculateKellyStake(bankroll, probability, odds, fraction = 0.5) {
|
|
169
|
+
const edge = (probability * odds - 1) / (odds - 1);
|
|
170
|
+
return Math.max(0, bankroll * edge * fraction);
|
|
171
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPORTS MODULE - Match Helpers
|
|
3
|
+
* Utility functions for match analysis
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Get date context for search queries
|
|
7
|
+
*/
|
|
8
|
+
export declare function getDateContext(): string;
|
|
9
|
+
/**
|
|
10
|
+
* Normalize team names for better matching
|
|
11
|
+
*/
|
|
12
|
+
export declare function normalizeTeamName(name: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Check if a team name matches exactly or is contained within another name
|
|
15
|
+
* Uses more strict matching to avoid false positives
|
|
16
|
+
*/
|
|
17
|
+
export declare function isTeamMatch(teamName: string, matchName: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Get analysis framework instructions
|
|
20
|
+
*/
|
|
21
|
+
export declare function getAnalysisInstructions(): string;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPORTS MODULE - Match Helpers
|
|
3
|
+
* Utility functions for match analysis
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Get date context for search queries
|
|
7
|
+
*/
|
|
8
|
+
export function getDateContext() {
|
|
9
|
+
const now = new Date();
|
|
10
|
+
const month = now.toLocaleDateString('en-US', { month: 'long' });
|
|
11
|
+
const year = now.getFullYear();
|
|
12
|
+
return ` ${month} ${year}`;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Normalize team names for better matching
|
|
16
|
+
*/
|
|
17
|
+
export function normalizeTeamName(name) {
|
|
18
|
+
return name
|
|
19
|
+
.toLowerCase()
|
|
20
|
+
.replace(/\b(fc|afc|sc|cf|united|utd|city|town|athletic|albion|rovers|wanderers|olympic|real)\b/g, '')
|
|
21
|
+
.replace(/\s+/g, ' ')
|
|
22
|
+
.trim();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Check if a team name matches exactly or is contained within another name
|
|
26
|
+
* Uses more strict matching to avoid false positives
|
|
27
|
+
*/
|
|
28
|
+
export function isTeamMatch(teamName, matchName) {
|
|
29
|
+
const normalizedSearch = normalizeTeamName(teamName);
|
|
30
|
+
const normalizedMatch = normalizeTeamName(matchName);
|
|
31
|
+
// Exact match
|
|
32
|
+
if (normalizedSearch === normalizedMatch)
|
|
33
|
+
return true;
|
|
34
|
+
// Check if search is contained as a word in match
|
|
35
|
+
const searchWords = normalizedSearch.split(/\s+/).filter(w => w.length > 2);
|
|
36
|
+
const matchWords = normalizedMatch.split(/\s+/).filter(w => w.length > 2);
|
|
37
|
+
// Must have at least one significant word in common
|
|
38
|
+
const commonWords = searchWords.filter(w => matchWords.includes(w));
|
|
39
|
+
// For longer names, require more matching words
|
|
40
|
+
if (searchWords.length >= 2) {
|
|
41
|
+
return commonWords.length >= Math.min(2, searchWords.length);
|
|
42
|
+
}
|
|
43
|
+
return commonWords.length > 0;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get analysis framework instructions
|
|
47
|
+
*/
|
|
48
|
+
export function getAnalysisInstructions() {
|
|
49
|
+
return `INSTRUCTIONS: Act as a World-Class Football Analysis Panel. Provide a deep, non-obvious analysis using this framework:\n\n` +
|
|
50
|
+
`1. 📊 THE DATA SCIENTIST (Quantitative):\n - Analyze xG trends & Possession stats.\n - Assess Home/Away variance.\n\n` +
|
|
51
|
+
`2. 🧠 THE TACTICAL SCOUT (Qualitative):\n - Stylistic Matchup (Press vs Block).\n - Key Battles.\n\n` +
|
|
52
|
+
`3. 🚑 THE PHYSIO (Physical Condition):\n - FATIGUE CHECK: Days rest? Travel distance?\n - Squad Depth: Who has the better bench?\n\n` +
|
|
53
|
+
`4. 🎯 SET PIECE ANALYST:\n - Corners/Free Kicks: Strong vs Weak?\n - Who is most likely to score from a header?\n\n` +
|
|
54
|
+
`5. 💎 THE INSIDER (External Factors):\n - Market Movements (Odds dropping?).\n - Referee & Weather impact.\n\n` +
|
|
55
|
+
`6. 🕵️ THE SKEPTIC & SCENARIOS:\n - Why might the favorite LOSE?\n - Game Script: "If Team A scores first..."\n\n` +
|
|
56
|
+
`🏆 FINAL VERDICT:\n - Asian Handicap Leans\n - Goal Line (Over/Under)\n - The "Value Pick"`;
|
|
57
|
+
}
|