@gotza02/sequential-thinking 2026.2.39 → 2026.2.41

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.
Files changed (38) hide show
  1. package/README.md +7 -4
  2. package/SYSTEM_INSTRUCTION.md +25 -50
  3. package/dist/tools/sports/core/base.d.ts +169 -0
  4. package/dist/tools/sports/core/base.js +289 -0
  5. package/dist/tools/sports/core/cache.d.ts +106 -0
  6. package/dist/tools/sports/core/cache.js +305 -0
  7. package/dist/tools/sports/core/constants.d.ts +179 -0
  8. package/dist/tools/sports/core/constants.js +149 -0
  9. package/dist/tools/sports/core/types.d.ts +379 -0
  10. package/dist/tools/sports/core/types.js +5 -0
  11. package/dist/tools/sports/index.d.ts +34 -0
  12. package/dist/tools/sports/index.js +50 -0
  13. package/dist/tools/sports/providers/api.d.ts +73 -0
  14. package/dist/tools/sports/providers/api.js +517 -0
  15. package/dist/tools/sports/providers/scraper.d.ts +66 -0
  16. package/dist/tools/sports/providers/scraper.js +186 -0
  17. package/dist/tools/sports/providers/search.d.ts +54 -0
  18. package/dist/tools/sports/providers/search.js +224 -0
  19. package/dist/tools/sports/tools/betting.d.ts +6 -0
  20. package/dist/tools/sports/tools/betting.js +251 -0
  21. package/dist/tools/sports/tools/league.d.ts +11 -0
  22. package/dist/tools/sports/tools/league.js +12 -0
  23. package/dist/tools/sports/tools/live.d.ts +9 -0
  24. package/dist/tools/sports/tools/live.js +235 -0
  25. package/dist/tools/sports/tools/match.d.ts +6 -0
  26. package/dist/tools/sports/tools/match.js +323 -0
  27. package/dist/tools/sports/tools/player.d.ts +6 -0
  28. package/dist/tools/sports/tools/player.js +152 -0
  29. package/dist/tools/sports/tools/team.d.ts +6 -0
  30. package/dist/tools/sports/tools/team.js +370 -0
  31. package/dist/tools/sports/utils/calculator.d.ts +69 -0
  32. package/dist/tools/sports/utils/calculator.js +156 -0
  33. package/dist/tools/sports/utils/formatter.d.ts +57 -0
  34. package/dist/tools/sports/utils/formatter.js +206 -0
  35. package/dist/tools/sports.d.ts +7 -0
  36. package/dist/tools/sports.js +27 -6
  37. package/package.json +1 -1
  38. package/system_instruction.md +155 -0
@@ -0,0 +1,370 @@
1
+ /**
2
+ * SPORTS MODULE TOOLS - Team Tools
3
+ * Tools for team analysis, comparison, and league standings
4
+ */
5
+ import { z } from 'zod';
6
+ import { createAPIProvider } from '../providers/api.js';
7
+ import { getSearchProvider } from '../providers/search.js';
8
+ import { getGlobalCache, CacheService } from '../core/cache.js';
9
+ import { formatStandingsTable, formatComparisonResult } from '../utils/formatter.js';
10
+ import { CACHE_CONFIG, LEAGUES } from '../core/constants.js';
11
+ // ============= Helper Functions =============
12
+ /**
13
+ * Get league ID from league name
14
+ */
15
+ function getLeagueId(leagueName) {
16
+ const normalized = leagueName.toLowerCase();
17
+ for (const [key, value] of Object.entries(LEAGUES)) {
18
+ if (value.name.toLowerCase().includes(normalized) ||
19
+ normalized.includes(value.name.toLowerCase()) ||
20
+ value.code?.toLowerCase() === normalized.replace(' ', '')) {
21
+ return value.id;
22
+ }
23
+ }
24
+ // Try direct name match
25
+ for (const [key, value] of Object.entries(LEAGUES)) {
26
+ if (value.name.toLowerCase() === normalized) {
27
+ return value.id;
28
+ }
29
+ }
30
+ return undefined;
31
+ }
32
+ /**
33
+ * Search for team by name using web search
34
+ */
35
+ async function searchTeamByName(teamName) {
36
+ try {
37
+ const searchProvider = getSearchProvider();
38
+ const results = await searchProvider.search(`${teamName} football team stats`, undefined, 3);
39
+ if (results.length > 0) {
40
+ // Return a basic team object from search results
41
+ return {
42
+ id: `search_${Date.now()}`,
43
+ name: teamName,
44
+ country: 'Unknown',
45
+ stats: {
46
+ matchesPlayed: 0,
47
+ wins: 0,
48
+ draws: 0,
49
+ losses: 0,
50
+ goalsFor: 0,
51
+ goalsAgainst: 0,
52
+ goalDifference: 0,
53
+ },
54
+ };
55
+ }
56
+ }
57
+ catch {
58
+ // Ignore search errors
59
+ }
60
+ return null;
61
+ }
62
+ /**
63
+ * Compare two teams and generate analysis
64
+ */
65
+ async function compareTeamsAnalysis(teamAName, teamBName) {
66
+ const metrics = [];
67
+ const errors = [];
68
+ try {
69
+ const api = createAPIProvider();
70
+ // Try to get team data from API
71
+ const [teamAResult, teamBResult] = await Promise.all([
72
+ api.searchTeams(teamAName),
73
+ api.searchTeams(teamBName),
74
+ ]);
75
+ const teamA = teamAResult.success && teamAResult.data && teamAResult.data.length > 0
76
+ ? teamAResult.data[0]
77
+ : { id: 'a', name: teamAName, country: '', stats: undefined };
78
+ const teamB = teamBResult.success && teamBResult.data && teamBResult.data.length > 0
79
+ ? teamBResult.data[0]
80
+ : { id: 'b', name: teamBName, country: '', stats: undefined };
81
+ // Generate comparison metrics
82
+ // Note: These would be populated with real API data
83
+ metrics.push({
84
+ name: 'Team Name',
85
+ teamAValue: teamA.name,
86
+ teamBValue: teamB.name,
87
+ importance: 'low',
88
+ });
89
+ if (teamA.stats && teamB.stats) {
90
+ metrics.push({
91
+ name: 'League Position',
92
+ teamAValue: teamA.stats.position || 'N/A',
93
+ teamBValue: teamB.stats.position || 'N/A',
94
+ winner: teamA.stats.position && teamB.stats.position
95
+ ? teamA.stats.position < teamB.stats.position ? 'A' : teamA.stats.position > teamB.stats.position ? 'B' : 'draw'
96
+ : undefined,
97
+ importance: 'high',
98
+ });
99
+ metrics.push({
100
+ name: 'Form (Last 5)',
101
+ teamAValue: teamA.stats.form
102
+ ? Array.isArray(teamA.stats.form)
103
+ ? teamA.stats.form.join('')
104
+ : teamA.stats.form
105
+ : 'N/A',
106
+ teamBValue: teamB.stats.form
107
+ ? Array.isArray(teamB.stats.form)
108
+ ? teamB.stats.form.join('')
109
+ : teamB.stats.form
110
+ : 'N/A',
111
+ importance: 'medium',
112
+ });
113
+ metrics.push({
114
+ name: 'Goals Per Game',
115
+ teamAValue: teamA.stats.matchesPlayed > 0
116
+ ? (teamA.stats.goalsFor / teamA.stats.matchesPlayed).toFixed(2)
117
+ : 'N/A',
118
+ teamBValue: teamB.stats.matchesPlayed > 0
119
+ ? (teamB.stats.goalsFor / teamB.stats.matchesPlayed).toFixed(2)
120
+ : 'N/A',
121
+ winner: teamA.stats.matchesPlayed > 0 && teamB.stats.matchesPlayed > 0
122
+ ? (teamA.stats.goalsFor / teamA.stats.matchesPlayed) > (teamB.stats.goalsFor / teamB.stats.matchesPlayed) ? 'A' : 'B'
123
+ : undefined,
124
+ importance: 'high',
125
+ });
126
+ }
127
+ // Generate summary
128
+ let summary = `Comparison between **${teamA.name}** and **${teamB.name}**.\n\n`;
129
+ if (teamA.stats && teamB.stats) {
130
+ const aPoints = teamA.stats.points || 0;
131
+ const bPoints = teamB.stats.points || 0;
132
+ if (aPoints > bPoints + 10) {
133
+ summary += `${teamA.name} has a significantly better league position. `;
134
+ }
135
+ else if (bPoints > aPoints + 10) {
136
+ summary += `${teamB.name} has a significantly better league position. `;
137
+ }
138
+ else {
139
+ summary += `The teams are relatively evenly matched in terms of league standing. `;
140
+ }
141
+ }
142
+ else {
143
+ summary += `Detailed statistics not available via API. `;
144
+ }
145
+ summary += `\n\n**Note:** Full comparison requires API provider configuration.`;
146
+ return {
147
+ teamA,
148
+ teamB,
149
+ metrics,
150
+ summary,
151
+ };
152
+ }
153
+ catch (error) {
154
+ errors.push(error instanceof Error ? error.message : String(error));
155
+ // Fallback to basic comparison
156
+ return {
157
+ teamA: { id: 'a', name: teamAName, country: '' },
158
+ teamB: { id: 'b', name: teamBName, country: '' },
159
+ metrics: [
160
+ {
161
+ name: 'Team Name',
162
+ teamAValue: teamAName,
163
+ teamBValue: teamBName,
164
+ importance: 'low',
165
+ },
166
+ ],
167
+ summary: `Basic comparison between ${teamAName} and ${teamBName}. API providers not configured.\n\nErrors: ${errors.join(', ')}`,
168
+ };
169
+ }
170
+ }
171
+ // ============= Tool Registration =============
172
+ export function registerTeamTools(server) {
173
+ /**
174
+ * Tool 1: compare_teams
175
+ * Compare two teams across multiple metrics
176
+ */
177
+ server.tool('compare_teams', `Compare two football teams across multiple metrics.
178
+ Analyzes: form, head-to-head, xG, possession, goals, defense, home/away records.`, {
179
+ teamA: z.string().describe('First team name'),
180
+ teamB: z.string().describe('Second team name'),
181
+ metrics: z.array(z.enum(['form', 'h2h', 'xg', 'possession', 'goals', 'defense', 'home-away']))
182
+ .optional().default(['form', 'h2h', 'goals'])
183
+ .describe('Metrics to compare'),
184
+ }, async ({ teamA, teamB, metrics }) => {
185
+ const result = await compareTeamsAnalysis(teamA, teamB);
186
+ const output = formatComparisonResult(result);
187
+ return {
188
+ content: [{ type: 'text', text: output }],
189
+ };
190
+ });
191
+ /**
192
+ * Tool 2: get_league_standings
193
+ * Get current league standings table
194
+ */
195
+ server.tool('get_league_standings', `Get current league standings table.
196
+ Includes: position, played, won, drawn, lost, goals for/against, goal difference, points.
197
+ Supports home/away splits.`, {
198
+ league: z.string().describe('League name (e.g., "Premier League", "La Liga")'),
199
+ season: z.string().optional().describe('Season year (default: current)'),
200
+ }, async ({ league, season }) => {
201
+ const cache = getGlobalCache();
202
+ const currentYear = new Date().getFullYear();
203
+ const seasonYear = season || currentYear.toString();
204
+ const cacheKey = CacheService.generateKey('standings', league, seasonYear);
205
+ // Check cache first (30min TTL)
206
+ const cached = cache.get(cacheKey);
207
+ if (cached) {
208
+ return {
209
+ content: [{ type: 'text', text: cached + '\n\n*(cached data)*' }],
210
+ };
211
+ }
212
+ const leagueId = getLeagueId(league);
213
+ let output = '';
214
+ const errors = [];
215
+ if (!leagueId) {
216
+ errors.push(`League "${league}" not found. Available leagues: ${Object.values(LEAGUES).map(l => l.name).join(', ')}`);
217
+ }
218
+ if (leagueId) {
219
+ try {
220
+ const api = createAPIProvider();
221
+ const result = await api.getStandings(leagueId.toString(), seasonYear);
222
+ if (result.success && result.data) {
223
+ output = `## 🏆 ${league} - Standings ${seasonYear}\n\n`;
224
+ output += formatStandingsTable(result.data);
225
+ // Cache for 30 minutes
226
+ cache.set(cacheKey, output, CACHE_CONFIG.TTL.STANDINGS);
227
+ }
228
+ else {
229
+ errors.push(result.error || 'Unknown API error');
230
+ }
231
+ }
232
+ catch (error) {
233
+ errors.push(error instanceof Error ? error.message : String(error));
234
+ }
235
+ }
236
+ // Fallback to web search if API failed
237
+ if (!output && errors.length > 0) {
238
+ try {
239
+ const searchQuery = `${league} standings table ${seasonYear}`;
240
+ const searchProvider = getSearchProvider();
241
+ const searchResults = await searchProvider.search(searchQuery, undefined, 3);
242
+ const fallbackText = searchResults.map(r => `- [${r.title}](${r.url}): ${r.snippet}`).join('\n');
243
+ output = `## ${league} - Standings ${seasonYear}\n\n`;
244
+ output += `*API providers not available. Click links for standings:*\n\n${fallbackText}`;
245
+ }
246
+ catch (searchError) {
247
+ output = `Error: ${errors.join(', ')}`;
248
+ }
249
+ }
250
+ return {
251
+ content: [{ type: 'text', text: output }],
252
+ };
253
+ });
254
+ /**
255
+ * Tool 3: get_team_info
256
+ * Get detailed information about a team
257
+ */
258
+ server.tool('get_team_info', `Get detailed team information including stats, squad, and recent form.`, {
259
+ team: z.string().describe('Team name'),
260
+ }, async ({ team }) => {
261
+ const cache = getGlobalCache();
262
+ const cacheKey = CacheService.generateKey('team_info', team);
263
+ // Check cache first (1 hour TTL)
264
+ const cached = cache.get(cacheKey);
265
+ if (cached) {
266
+ return {
267
+ content: [{ type: 'text', text: cached + '\n\n*(cached data)*' }],
268
+ };
269
+ }
270
+ let output = '';
271
+ const errors = [];
272
+ try {
273
+ const api = createAPIProvider();
274
+ const searchResult = await api.searchTeams(team);
275
+ if (searchResult.success && searchResult.data && searchResult.data.length > 0) {
276
+ const teamData = searchResult.data[0];
277
+ output = `## ⚽ ${teamData.name}\n\n`;
278
+ output += `**Country:** ${teamData.country}\n`;
279
+ if (teamData.founded)
280
+ output += `**Founded:** ${teamData.founded}\n`;
281
+ if (teamData.venue)
282
+ output += `**Stadium:** ${teamData.venue}\n`;
283
+ if (teamData.logo)
284
+ output += `**Logo:** ${teamData.logo}\n`;
285
+ if (teamData.stats) {
286
+ output += `\n### Season Stats\n`;
287
+ output += `| Metric | Value |\n|--------|-------|\n`;
288
+ output += `| Played | ${teamData.stats.matchesPlayed} |\n`;
289
+ output += `| Wins | ${teamData.stats.wins} |\n`;
290
+ output += `| Draws | ${teamData.stats.draws} |\n`;
291
+ output += `| Losses | ${teamData.stats.losses} |\n`;
292
+ output += `| Goals For/Against | ${teamData.stats.goalsFor}/${teamData.stats.goalsAgainst} |\n`;
293
+ output += `| Goal Difference | ${teamData.stats.goalDifference} |\n`;
294
+ if (teamData.stats.points !== undefined)
295
+ output += `| Points | ${teamData.stats.points} |\n`;
296
+ }
297
+ // Cache for 1 hour
298
+ cache.set(cacheKey, output, CACHE_CONFIG.TTL.TEAM_STATS);
299
+ }
300
+ else {
301
+ errors.push('Team not found via API');
302
+ }
303
+ }
304
+ catch (error) {
305
+ errors.push(error instanceof Error ? error.message : String(error));
306
+ }
307
+ // Fallback to web search
308
+ if (!output) {
309
+ try {
310
+ const searchQuery = `${team} football team stats squad`;
311
+ const searchProvider = getSearchProvider();
312
+ const searchResults = await searchProvider.search(searchQuery, undefined, 3);
313
+ const fallbackText = searchResults.map(r => `- [${r.title}](${r.url}): ${r.snippet}`).join('\n');
314
+ output = `## ⚽ ${team}\n\n`;
315
+ output += `*API data not available. Click links for team info:*\n\n${fallbackText}`;
316
+ }
317
+ catch (searchError) {
318
+ output = `Error: ${errors.join(', ')}`;
319
+ }
320
+ }
321
+ return {
322
+ content: [{ type: 'text', text: output }],
323
+ };
324
+ });
325
+ /**
326
+ * Tool 4: get_top_scorers
327
+ * Get top scorers and assists for a league
328
+ */
329
+ server.tool('get_top_scorers', `Get top scorers and assists for a league.`, {
330
+ league: z.string().describe('League name'),
331
+ type: z.enum(['scorers', 'assists', 'both']).optional().default('both').describe('Statistics type'),
332
+ limit: z.number().optional().default(20).describe('Number of players to return'),
333
+ }, async ({ league, type, limit }) => {
334
+ const leagueId = getLeagueId(league);
335
+ let output = '';
336
+ const errors = [];
337
+ if (!leagueId) {
338
+ errors.push(`League "${league}" not found`);
339
+ }
340
+ // Try API first
341
+ if (leagueId) {
342
+ try {
343
+ const api = createAPIProvider();
344
+ // Note: This would require a dedicated API endpoint for top scorers
345
+ // For now, use web search as fallback
346
+ }
347
+ catch (error) {
348
+ errors.push(error instanceof Error ? error.message : String(error));
349
+ }
350
+ }
351
+ // Use web search
352
+ try {
353
+ const searchQuery = `${league} top ${type === 'assists' ? 'assists' : 'scorers'} ${new Date().getFullYear()}`;
354
+ const searchProvider = getSearchProvider();
355
+ const searchResults = await searchProvider.search(searchQuery, undefined, limit);
356
+ output = `## 🥇 ${league} - Top ${type === 'assists' ? 'Assists' : 'Scorers'}\n\n`;
357
+ const items = searchResults.map((r, i) => `${i + 1}. [${r.title}](${r.url}): ${r.snippet}`).join('\n');
358
+ output += items;
359
+ if (searchResults.length === 0) {
360
+ output += `\nNo results found.`;
361
+ }
362
+ }
363
+ catch (error) {
364
+ output = `Error: ${errors.join(', ')}`;
365
+ }
366
+ return {
367
+ content: [{ type: 'text', text: output }],
368
+ };
369
+ });
370
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * SPORTS MODULE UTILS - Calculator
3
+ * Betting calculators and mathematical utilities
4
+ */
5
+ import { BetType, KellyVariant, ValueBet } from '../core/types.js';
6
+ /**
7
+ * Calculate value of a bet
8
+ * @param odds - Decimal odds
9
+ * @param probability - Estimated probability (0-1)
10
+ * @returns Value as percentage (e.g., 0.15 = 15%)
11
+ */
12
+ export declare function calculateValue(odds: number, probability: number): number;
13
+ /**
14
+ * Calculate Kelly Criterion stake
15
+ * @param odds - Decimal odds
16
+ * @param probability - Estimated probability (0-1)
17
+ * @param variant - Kelly variant (full, half, quarter)
18
+ * @returns Fraction of bankroll to stake (0-1)
19
+ */
20
+ export declare function calculateKelly(odds: number, probability: number, variant?: KellyVariant): number;
21
+ /**
22
+ * Calculate recommended stake based on bankroll
23
+ * @param odds - Decimal odds
24
+ * @param probability - Estimated probability (0-1)
25
+ * @param bankroll - Total bankroll
26
+ * @param kellyVariant - Kelly variant
27
+ * @param maxStakePct - Maximum stake as percentage of bankroll
28
+ * @returns Recommended stake amount
29
+ */
30
+ export declare function calculateRecommendedStake(odds: number, probability: number, bankroll?: number, kellyVariant?: KellyVariant, maxStakePct?: number): number;
31
+ /**
32
+ * Create a value bet analysis
33
+ * @param odds - Decimal odds
34
+ * @param probability - Estimated probability (0-1)
35
+ * @param betType - Type of bet
36
+ * @param bankroll - Total bankroll
37
+ * @returns Value bet analysis
38
+ */
39
+ export declare function analyzeValueBet(odds: number, probability: number, betType: BetType, bankroll?: number): Omit<ValueBet, 'match' | 'reasoning'>;
40
+ /**
41
+ * Convert fractional odds to decimal
42
+ */
43
+ export declare function fractionalToDecimal(fractional: string): number;
44
+ /**
45
+ * Convert decimal odds to fractional
46
+ */
47
+ export declare function decimalToFractional(decimal: number): string;
48
+ /**
49
+ * Convert American odds to decimal
50
+ */
51
+ export declare function americanToDecimal(american: number): number;
52
+ /**
53
+ * Convert decimal odds to American
54
+ */
55
+ export declare function decimalToAmerican(decimal: number): number;
56
+ /**
57
+ * Calculate implied probability from decimal odds
58
+ */
59
+ export declare function oddsToProbability(odds: number): number;
60
+ /**
61
+ * Calculate overround (bookmaker margin)
62
+ * @param odds - Array of decimal odds for all outcomes
63
+ * @returns Overround as percentage
64
+ */
65
+ export declare function calculateOverround(odds: number[]): number;
66
+ /**
67
+ * Calculate true probability after removing overround
68
+ */
69
+ export declare function removeOverround(odds: number[]): number[];
@@ -0,0 +1,156 @@
1
+ /**
2
+ * SPORTS MODULE UTILS - Calculator
3
+ * Betting calculators and mathematical utilities
4
+ */
5
+ import { BETTING } from '../core/constants.js';
6
+ /**
7
+ * Calculate value of a bet
8
+ * @param odds - Decimal odds
9
+ * @param probability - Estimated probability (0-1)
10
+ * @returns Value as percentage (e.g., 0.15 = 15%)
11
+ */
12
+ export function calculateValue(odds, probability) {
13
+ return (odds * probability) - 1;
14
+ }
15
+ /**
16
+ * Calculate Kelly Criterion stake
17
+ * @param odds - Decimal odds
18
+ * @param probability - Estimated probability (0-1)
19
+ * @param variant - Kelly variant (full, half, quarter)
20
+ * @returns Fraction of bankroll to stake (0-1)
21
+ */
22
+ export function calculateKelly(odds, probability, variant = 'half') {
23
+ const kelly = (probability * odds - 1) / (odds - 1);
24
+ // Kelly can be negative (no value), return 0 in that case
25
+ if (kelly < 0)
26
+ return 0;
27
+ switch (variant) {
28
+ case 'full':
29
+ return Math.min(kelly, 1);
30
+ case 'half':
31
+ return Math.min(kelly / 2, 1);
32
+ case 'quarter':
33
+ return Math.min(kelly / 4, 1);
34
+ default:
35
+ return Math.min(kelly / 2, 1);
36
+ }
37
+ }
38
+ /**
39
+ * Calculate recommended stake based on bankroll
40
+ * @param odds - Decimal odds
41
+ * @param probability - Estimated probability (0-1)
42
+ * @param bankroll - Total bankroll
43
+ * @param kellyVariant - Kelly variant
44
+ * @param maxStakePct - Maximum stake as percentage of bankroll
45
+ * @returns Recommended stake amount
46
+ */
47
+ export function calculateRecommendedStake(odds, probability, bankroll = BETTING.DEFAULT_BANKROLL, kellyVariant = 'half', maxStakePct = BETTING.MAX_STAKE_PERCENTAGE) {
48
+ const kelly = calculateKelly(odds, probability, kellyVariant);
49
+ const kellyStake = bankroll * kelly;
50
+ const maxStake = bankroll * maxStakePct;
51
+ return Math.min(kellyStake, maxStake);
52
+ }
53
+ /**
54
+ * Create a value bet analysis
55
+ * @param odds - Decimal odds
56
+ * @param probability - Estimated probability (0-1)
57
+ * @param betType - Type of bet
58
+ * @param bankroll - Total bankroll
59
+ * @returns Value bet analysis
60
+ */
61
+ export function analyzeValueBet(odds, probability, betType, bankroll = BETTING.DEFAULT_BANKROLL) {
62
+ const value = calculateValue(odds, probability);
63
+ const kellyFraction = calculateKelly(odds, probability, 'half');
64
+ const recommendedStake = calculateRecommendedStake(odds, probability, bankroll);
65
+ // Determine confidence based on value and probability
66
+ let confidence;
67
+ if (value >= BETTING.HIGH_VALUE_THRESHOLD && probability >= 0.5) {
68
+ confidence = BETTING.CONFIDENCE.VERY_HIGH;
69
+ }
70
+ else if (value >= BETTING.VALUE_THRESHOLD && probability >= 0.4) {
71
+ confidence = BETTING.CONFIDENCE.HIGH;
72
+ }
73
+ else if (value >= 0 && probability >= 0.3) {
74
+ confidence = BETTING.CONFIDENCE.MEDIUM;
75
+ }
76
+ else {
77
+ confidence = BETTING.CONFIDENCE.LOW;
78
+ }
79
+ return {
80
+ betType,
81
+ fairOdds: 1 / probability,
82
+ marketOdds: odds,
83
+ value,
84
+ confidence,
85
+ kellyFraction,
86
+ recommendedStake,
87
+ };
88
+ }
89
+ /**
90
+ * Convert fractional odds to decimal
91
+ */
92
+ export function fractionalToDecimal(fractional) {
93
+ const [numerator, denominator] = fractional.split('/').map(Number);
94
+ if (denominator === 0)
95
+ return 0;
96
+ return (numerator / denominator) + 1;
97
+ }
98
+ /**
99
+ * Convert decimal odds to fractional
100
+ */
101
+ export function decimalToFractional(decimal) {
102
+ const numerator = decimal - 1;
103
+ const denominator = 1;
104
+ // Find common denominator
105
+ const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
106
+ const num = Math.round(numerator * 100);
107
+ const den = 100;
108
+ const divisor = gcd(num, den);
109
+ return `${num / divisor}/${den / divisor}`;
110
+ }
111
+ /**
112
+ * Convert American odds to decimal
113
+ */
114
+ export function americanToDecimal(american) {
115
+ if (american > 0) {
116
+ return (american / 100) + 1;
117
+ }
118
+ else {
119
+ return (100 / Math.abs(american)) + 1;
120
+ }
121
+ }
122
+ /**
123
+ * Convert decimal odds to American
124
+ */
125
+ export function decimalToAmerican(decimal) {
126
+ if (decimal >= 2) {
127
+ return Math.round((decimal - 1) * 100);
128
+ }
129
+ else {
130
+ return Math.round(-100 / (decimal - 1));
131
+ }
132
+ }
133
+ /**
134
+ * Calculate implied probability from decimal odds
135
+ */
136
+ export function oddsToProbability(odds) {
137
+ return 1 / odds;
138
+ }
139
+ /**
140
+ * Calculate overround (bookmaker margin)
141
+ * @param odds - Array of decimal odds for all outcomes
142
+ * @returns Overround as percentage
143
+ */
144
+ export function calculateOverround(odds) {
145
+ const impliedProbability = odds.reduce((sum, odd) => sum + oddsToProbability(odd), 0);
146
+ return impliedProbability - 1;
147
+ }
148
+ /**
149
+ * Calculate true probability after removing overround
150
+ */
151
+ export function removeOverround(odds) {
152
+ const overround = calculateOverround(odds);
153
+ const probabilities = odds.map(o => oddsToProbability(o));
154
+ const total = probabilities.reduce((sum, p) => sum + p, 0);
155
+ return probabilities.map(p => p / total);
156
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * SPORTS MODULE UTILS - Formatter
3
+ * Output formatting utilities for sports data
4
+ */
5
+ import { Match, TableEntry, PlayerStats, ComparisonResult, HeadToHead } from '../core/types.js';
6
+ /**
7
+ * Format a match as markdown table row
8
+ */
9
+ export declare function formatMatchRow(match: Match): string;
10
+ /**
11
+ * Format standings as markdown table
12
+ */
13
+ export declare function formatStandingsTable(standings: TableEntry[]): string;
14
+ /**
15
+ * Format player stats as markdown table
16
+ */
17
+ export declare function formatPlayerStats(stats: PlayerStats, playerName: string): string;
18
+ /**
19
+ * Format comparison result as markdown
20
+ */
21
+ export declare function formatComparisonResult(result: ComparisonResult): string;
22
+ /**
23
+ * Format head to head record as markdown
24
+ */
25
+ export declare function formatHeadToHead(h2h: HeadToHead): string;
26
+ /**
27
+ * Format match status for display
28
+ */
29
+ export declare function formatMatchStatus(status: Match['status']): string;
30
+ /**
31
+ * Format score with context
32
+ */
33
+ export declare function formatScore(match: Match): string;
34
+ /**
35
+ * Format odds as decimal/fractional
36
+ */
37
+ export declare function formatOdds(decimalOdds: number): string;
38
+ /**
39
+ * Format probability as percentage
40
+ */
41
+ export declare function formatProbability(probability: number): string;
42
+ /**
43
+ * Format value bet recommendation
44
+ */
45
+ export declare function formatValueBet(match: string, odds: number, probability: number, value: number, confidence: number): string;
46
+ /**
47
+ * Format a list of matches as markdown table
48
+ */
49
+ export declare function formatMatchesTable(matches: Match[], title: string): string;
50
+ /**
51
+ * Truncate text to max length with ellipsis
52
+ */
53
+ export declare function truncate(text: string, maxLength?: number): string;
54
+ /**
55
+ * Format date relative to now
56
+ */
57
+ export declare function formatRelativeDate(date: Date): string;