@gotza02/sequential-thinking 10000.1.6 → 10000.1.7
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/CLAUDE.md +31 -6
- package/README.md +20 -1
- package/dist/dashboard/server.js +97 -0
- package/dist/index.js +2 -0
- package/dist/intelligent-code.d.ts +157 -0
- package/dist/intelligent-code.js +779 -0
- package/dist/tools/intelligent-code.d.ts +7 -0
- package/dist/tools/intelligent-code.js +387 -0
- package/dist/tools/sports/tools/match.js +221 -166
- package/package.json +1 -1
|
@@ -211,6 +211,153 @@ function optimizeQueries(coverage, baseQueries) {
|
|
|
211
211
|
}
|
|
212
212
|
});
|
|
213
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Build search queries for match analysis
|
|
216
|
+
*/
|
|
217
|
+
function buildSearchQueries(homeTeam, awayTeam, league, context) {
|
|
218
|
+
const leagueStr = league ? `${league} ` : '';
|
|
219
|
+
const baseQuery = `${leagueStr}${homeTeam} vs ${awayTeam}`;
|
|
220
|
+
const dateQuery = getDateContext();
|
|
221
|
+
const queries = [
|
|
222
|
+
{ type: 'general', query: `${baseQuery} match prediction stats${dateQuery}`, title: 'General & Prediction' },
|
|
223
|
+
{ type: 'h2h', query: `${baseQuery} head to head history`, title: 'Head to Head' },
|
|
224
|
+
{ type: 'form', query: `${homeTeam} ${awayTeam} recent form last 5 matches${dateQuery}`, title: 'Recent Form' },
|
|
225
|
+
{ type: 'news', query: `${baseQuery} team news injuries lineups${dateQuery}`, title: 'Team News & Lineups' },
|
|
226
|
+
{ type: 'stats', query: `${baseQuery} xG expected goals stats${dateQuery}`, title: 'Advanced Metrics' },
|
|
227
|
+
{ type: 'odds', query: `${baseQuery} Asian Handicap odds prediction today${dateQuery}`, title: 'Latest Handicap Odds' },
|
|
228
|
+
{ type: 'fatigue', query: `${homeTeam} ${awayTeam} days rest fixture congestion${dateQuery}`, title: 'Fatigue & Schedule' },
|
|
229
|
+
{ type: 'setpieces', query: `${baseQuery} set pieces corners aerial duels${dateQuery}`, title: 'Set Pieces' },
|
|
230
|
+
];
|
|
231
|
+
if (context) {
|
|
232
|
+
queries.push({ type: 'general', query: `${baseQuery} ${context}${dateQuery}`, title: 'Specific Context' });
|
|
233
|
+
}
|
|
234
|
+
return queries;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Format H2H data for display
|
|
238
|
+
*/
|
|
239
|
+
function formatH2HData(h2h, homeTeam, awayTeam) {
|
|
240
|
+
return `### Head-to-Head Record\n` +
|
|
241
|
+
`- Total Matches: ${h2h.totalMatches}\n` +
|
|
242
|
+
`- ${homeTeam} Wins: ${h2h.homeWins}\n` +
|
|
243
|
+
`- Draws: ${h2h.draws}\n` +
|
|
244
|
+
`- ${awayTeam} Wins: ${h2h.awayWins}\n` +
|
|
245
|
+
`- Goals: ${h2h.homeGoals}-${h2h.awayGoals}\n\n`;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Get match result character (W/D/L) for a team
|
|
249
|
+
*/
|
|
250
|
+
function getMatchResultChar(match, teamName) {
|
|
251
|
+
const isHome = match.homeTeam.name.includes(teamName);
|
|
252
|
+
const homeScore = match.score?.home ?? 0;
|
|
253
|
+
const awayScore = match.score?.away ?? 0;
|
|
254
|
+
if (isHome) {
|
|
255
|
+
return homeScore > awayScore ? 'W' : homeScore === awayScore ? 'D' : 'L';
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
return awayScore > homeScore ? 'W' : homeScore === awayScore ? 'D' : 'L';
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Format team form data for display
|
|
263
|
+
*/
|
|
264
|
+
function formatTeamForm(form, teamName, isHomeTeam) {
|
|
265
|
+
const label = isHomeTeam ? 'Home' : 'Away';
|
|
266
|
+
let output = `### ${teamName} Recent Form (Last ${form.length})\n`;
|
|
267
|
+
output += form.map(m => {
|
|
268
|
+
const opponent = isHomeTeam ? m.awayTeam.name : m.homeTeam.name;
|
|
269
|
+
const score = m.score ? `${m.score.home}-${m.score.away}` : '?';
|
|
270
|
+
const result = getMatchResultChar(m, teamName);
|
|
271
|
+
return `- ${result} vs ${opponent} (${score})`;
|
|
272
|
+
}).join('\n');
|
|
273
|
+
return output + '\n\n';
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Format API data section for output
|
|
277
|
+
*/
|
|
278
|
+
function formatAPIData(apiData, homeTeam, awayTeam) {
|
|
279
|
+
let output = '--- API DATA (Reliable Source) ---\n\n';
|
|
280
|
+
if (apiData.h2h) {
|
|
281
|
+
output += formatH2HData(apiData.h2h, homeTeam, awayTeam);
|
|
282
|
+
}
|
|
283
|
+
if (apiData.homeForm?.length) {
|
|
284
|
+
output += formatTeamForm(apiData.homeForm, homeTeam, true);
|
|
285
|
+
}
|
|
286
|
+
if (apiData.awayForm?.length) {
|
|
287
|
+
output += formatTeamForm(apiData.awayForm, awayTeam, false);
|
|
288
|
+
}
|
|
289
|
+
if (apiData.odds) {
|
|
290
|
+
output += `### Current Odds (from API)\n`;
|
|
291
|
+
output += `- Home Win: ${apiData.odds.homeWin.toFixed(2)}\n`;
|
|
292
|
+
output += `- Draw: ${apiData.odds.draw.toFixed(2)}\n`;
|
|
293
|
+
output += `- Away Win: ${apiData.odds.awayWin.toFixed(2)}\n`;
|
|
294
|
+
if (apiData.odds.asianHandicap) {
|
|
295
|
+
output += `- Asian Handicap: ${apiData.odds.asianHandicap.line}\n`;
|
|
296
|
+
}
|
|
297
|
+
output += '\n';
|
|
298
|
+
}
|
|
299
|
+
return output + '--- END API DATA ---\n\n';
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Execute search queries in batches
|
|
303
|
+
*/
|
|
304
|
+
async function executeSearches(queries, searchProvider, candidateUrls) {
|
|
305
|
+
const BATCH_SIZE = 4;
|
|
306
|
+
let output = '';
|
|
307
|
+
for (let i = 0; i < queries.length; i += BATCH_SIZE) {
|
|
308
|
+
const batch = queries.slice(i, i + BATCH_SIZE);
|
|
309
|
+
const batchPromises = batch.map(async (q) => {
|
|
310
|
+
try {
|
|
311
|
+
const results = await searchProvider.search(q.query, undefined, 4);
|
|
312
|
+
results.forEach(r => candidateUrls.push(r.url));
|
|
313
|
+
const items = results.map(r => `- [${r.title}](${r.url}): ${r.snippet}`).join('\n');
|
|
314
|
+
return `### ${q.title}\n${items}\n`;
|
|
315
|
+
}
|
|
316
|
+
catch (error) {
|
|
317
|
+
return `### ${q.title} (Failed)\nError: ${error instanceof Error ? error.message : String(error)}\n`;
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
const batchResults = await Promise.all(batchPromises);
|
|
321
|
+
output += batchResults.join('\n');
|
|
322
|
+
}
|
|
323
|
+
return output;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Scrape content from URL with error handling
|
|
327
|
+
*/
|
|
328
|
+
async function scrapeContentWithErrorHandling(url, type) {
|
|
329
|
+
try {
|
|
330
|
+
const scrapedContent = await scrapeMatchContent(url);
|
|
331
|
+
if (scrapedContent.success) {
|
|
332
|
+
if (type === 'news' && scrapedContent.data) {
|
|
333
|
+
// Truncate news to avoid blowing up context
|
|
334
|
+
const content = scrapedContent.data.substring(0, 3000);
|
|
335
|
+
return content + (scrapedContent.data.length > 3000 ? '\n...(truncated)' : '');
|
|
336
|
+
}
|
|
337
|
+
return scrapedContent.data || '';
|
|
338
|
+
}
|
|
339
|
+
else {
|
|
340
|
+
return `(${type} scrape failed: ${scrapedContent.error})`;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
catch (scrapeError) {
|
|
344
|
+
const errorMsg = scrapeError instanceof Error ? scrapeError.message : String(scrapeError);
|
|
345
|
+
return `(${type} scrape failed: ${errorMsg})`;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Get analysis framework instructions
|
|
350
|
+
*/
|
|
351
|
+
function getAnalysisInstructions() {
|
|
352
|
+
return `INSTRUCTIONS: Act as a World-Class Football Analysis Panel. Provide a deep, non-obvious analysis using this framework:\n\n` +
|
|
353
|
+
`1. 📊 THE DATA SCIENTIST (Quantitative):\n - Analyze xG trends & Possession stats.\n - Assess Home/Away variance.\n\n` +
|
|
354
|
+
`2. 🧠 THE TACTICAL SCOUT (Qualitative):\n - Stylistic Matchup (Press vs Block).\n - Key Battles.\n\n` +
|
|
355
|
+
`3. 🚑 THE PHYSIO (Physical Condition):\n - FATIGUE CHECK: Days rest? Travel distance?\n - Squad Depth: Who has the better bench?\n\n` +
|
|
356
|
+
`4. 🎯 SET PIECE ANALYST:\n - Corners/Free Kicks: Strong vs Weak?\n - Who is most likely to score from a header?\n\n` +
|
|
357
|
+
`5. 💎 THE INSIDER (External Factors):\n - Market Movements (Odds dropping?).\n - Referee & Weather impact.\n\n` +
|
|
358
|
+
`6. 🕵️ THE SKEPTIC & SCENARIOS:\n - Why might the favorite LOSE?\n - Game Script: "If Team A scores first..."\n\n` +
|
|
359
|
+
`🏆 FINAL VERDICT:\n - Asian Handicap Leans\n - Goal Line (Over/Under)\n - The "Value Pick"`;
|
|
360
|
+
}
|
|
214
361
|
/**
|
|
215
362
|
* Calculate adaptive TTL based on data type and match timing
|
|
216
363
|
*/
|
|
@@ -261,162 +408,63 @@ function calculateAdaptiveTTL(dataType, matchDate) {
|
|
|
261
408
|
* Perform comprehensive match analysis using web search
|
|
262
409
|
*/
|
|
263
410
|
export async function performMatchAnalysis(homeTeam, awayTeam, league, context) {
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
const searchProvider = getSearchProvider();
|
|
268
|
-
const queries = [
|
|
269
|
-
{ type: 'general', query: `${baseQuery} match prediction stats${dateQuery}`, title: 'General & Prediction' },
|
|
270
|
-
{ type: 'h2h', query: `${baseQuery} head to head history`, title: 'Head to Head' },
|
|
271
|
-
{ type: 'form', query: `${homeTeam} ${awayTeam} recent form last 5 matches${dateQuery}`, title: 'Recent Form' },
|
|
272
|
-
{ type: 'news', query: `${baseQuery} team news injuries lineups${dateQuery}`, title: 'Team News & Lineups' },
|
|
273
|
-
{ type: 'stats', query: `${baseQuery} xG expected goals stats${dateQuery}`, title: 'Advanced Metrics' },
|
|
274
|
-
{ type: 'odds', query: `${baseQuery} Asian Handicap odds prediction today${dateQuery}`, title: 'Latest Handicap Odds' },
|
|
275
|
-
{ type: 'fatigue', query: `${homeTeam} ${awayTeam} days rest fixture congestion${dateQuery}`, title: 'Fatigue & Schedule' },
|
|
276
|
-
{ type: 'setpieces', query: `${baseQuery} set pieces corners aerial duels${dateQuery}`, title: 'Set Pieces' },
|
|
277
|
-
];
|
|
278
|
-
if (context) {
|
|
279
|
-
queries.push({ type: 'general', query: `${baseQuery} ${context}${dateQuery}`, title: 'Specific Context' });
|
|
280
|
-
}
|
|
281
|
-
// Try to get API data first
|
|
411
|
+
// Step 1: Build search queries
|
|
412
|
+
const queries = buildSearchQueries(homeTeam, awayTeam, league, context);
|
|
413
|
+
// Step 2: Fetch API data
|
|
282
414
|
const api = createAPIProvider();
|
|
283
415
|
const matchId = await findMatchId(homeTeam, awayTeam, league);
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
apiData = await fetchAPIMatchData(api, matchId, homeTeam, awayTeam);
|
|
287
|
-
}
|
|
288
|
-
// Assess data coverage and optimize queries
|
|
416
|
+
const apiData = matchId ? await fetchAPIMatchData(api, matchId, homeTeam, awayTeam) : null;
|
|
417
|
+
// Step 3: Assess coverage and optimize queries
|
|
289
418
|
const coverage = assessDataCoverage(apiData);
|
|
290
419
|
const queriesToExecute = optimizeQueries(coverage, queries);
|
|
420
|
+
// Step 4: Build header
|
|
291
421
|
let combinedResults = `--- FOOTBALL MATCH DATA: ${homeTeam} vs ${awayTeam} ---\n`;
|
|
292
422
|
if (matchId)
|
|
293
423
|
combinedResults += `Resolved API Match ID: ${matchId}\n`;
|
|
294
424
|
combinedResults += `Data Quality Score: ${coverage.score}/100\n`;
|
|
295
425
|
combinedResults += `Match Context Date: ${new Date().toLocaleDateString()}\n\n`;
|
|
296
|
-
// Add API
|
|
426
|
+
// Step 5: Add API data section
|
|
297
427
|
if (apiData && coverage.score > 0) {
|
|
298
|
-
combinedResults +=
|
|
299
|
-
if (apiData.h2h) {
|
|
300
|
-
combinedResults += `### Head-to-Head Record\n`;
|
|
301
|
-
combinedResults += `- Total Matches: ${apiData.h2h.totalMatches}\n`;
|
|
302
|
-
combinedResults += `- ${homeTeam} Wins: ${apiData.h2h.homeWins}\n`;
|
|
303
|
-
combinedResults += `- Draws: ${apiData.h2h.draws}\n`;
|
|
304
|
-
combinedResults += `- ${awayTeam} Wins: ${apiData.h2h.awayWins}\n`;
|
|
305
|
-
combinedResults += `- Goals: ${apiData.h2h.homeGoals}-${apiData.h2h.awayGoals}\n\n`;
|
|
306
|
-
}
|
|
307
|
-
if (apiData.homeForm?.length) {
|
|
308
|
-
combinedResults += `### ${homeTeam} Recent Form (Last ${apiData.homeForm.length})\n`;
|
|
309
|
-
combinedResults += apiData.homeForm.map(m => {
|
|
310
|
-
const isHome = m.homeTeam.name.includes(homeTeam);
|
|
311
|
-
const score = m.score ? `${m.score.home}-${m.score.away}` : '?';
|
|
312
|
-
const opponent = isHome ? m.awayTeam.name : m.homeTeam.name;
|
|
313
|
-
const result = isHome
|
|
314
|
-
? (m.score?.home ?? 0) > (m.score?.away ?? 0) ? 'W' : (m.score?.home === m.score?.away ? 'D' : 'L')
|
|
315
|
-
: (m.score?.away ?? 0) > (m.score?.home ?? 0) ? 'W' : (m.score?.home === m.score?.away ? 'D' : 'L');
|
|
316
|
-
return `- ${result} vs ${opponent} (${score})`;
|
|
317
|
-
}).join('\n');
|
|
318
|
-
combinedResults += '\n\n';
|
|
319
|
-
}
|
|
320
|
-
if (apiData.awayForm?.length) {
|
|
321
|
-
combinedResults += `### ${awayTeam} Recent Form (Last ${apiData.awayForm.length})\n`;
|
|
322
|
-
combinedResults += apiData.awayForm.map(m => {
|
|
323
|
-
const isAway = m.awayTeam.name.includes(awayTeam);
|
|
324
|
-
const score = m.score ? `${m.score.home}-${m.score.away}` : '?';
|
|
325
|
-
const opponent = isAway ? m.homeTeam.name : m.awayTeam.name;
|
|
326
|
-
const result = isAway
|
|
327
|
-
? (m.score?.away ?? 0) > (m.score?.home ?? 0) ? 'W' : (m.score?.home === m.score?.away ? 'D' : 'L')
|
|
328
|
-
: (m.score?.home ?? 0) > (m.score?.away ?? 0) ? 'W' : (m.score?.home === m.score?.away ? 'D' : 'L');
|
|
329
|
-
return `- ${result} vs ${opponent} (${score})`;
|
|
330
|
-
}).join('\n');
|
|
331
|
-
combinedResults += '\n\n';
|
|
332
|
-
}
|
|
333
|
-
if (apiData.odds) {
|
|
334
|
-
combinedResults += `### Current Odds (from API)\n`;
|
|
335
|
-
combinedResults += `- Home Win: ${apiData.odds.homeWin.toFixed(2)}\n`;
|
|
336
|
-
combinedResults += `- Draw: ${apiData.odds.draw.toFixed(2)}\n`;
|
|
337
|
-
combinedResults += `- Away Win: ${apiData.odds.awayWin.toFixed(2)}\n`;
|
|
338
|
-
if (apiData.odds.asianHandicap) {
|
|
339
|
-
combinedResults += `- Asian Handicap: ${apiData.odds.asianHandicap.line}\n`;
|
|
340
|
-
}
|
|
341
|
-
combinedResults += '\n';
|
|
342
|
-
}
|
|
343
|
-
combinedResults += `--- END API DATA ---\n\n`;
|
|
428
|
+
combinedResults += formatAPIData(apiData, homeTeam, awayTeam);
|
|
344
429
|
}
|
|
430
|
+
// Step 6: Add coverage notes
|
|
345
431
|
if (coverage.factors.missingDataPoints.length > 0) {
|
|
346
432
|
combinedResults += `*Note: Using web search to supplement: ${coverage.factors.missingDataPoints.join(', ')}*\n\n`;
|
|
347
433
|
}
|
|
434
|
+
// Step 7: Execute searches
|
|
348
435
|
const candidateUrls = [];
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
try {
|
|
355
|
-
const results = await searchProvider.search(q.query, undefined, 4);
|
|
356
|
-
results.forEach(r => candidateUrls.push(r.url));
|
|
357
|
-
const items = results.map(r => `- [${r.title}](${r.url}): ${r.snippet}`).join('\n');
|
|
358
|
-
return `### ${q.title}\n${items}\n`;
|
|
359
|
-
}
|
|
360
|
-
catch (error) {
|
|
361
|
-
return `### ${q.title} (Failed)\nError: ${error instanceof Error ? error.message : String(error)}\n`;
|
|
362
|
-
}
|
|
363
|
-
});
|
|
364
|
-
const batchResults = await Promise.all(batchPromises);
|
|
365
|
-
combinedResults += batchResults.join('\n');
|
|
366
|
-
}
|
|
367
|
-
// Deep dive: Scrape the best URLs (Dual-Source Strategy)
|
|
368
|
-
if (candidateUrls.length > 0) {
|
|
369
|
-
const bestStatsUrl = findBestMatchUrl(candidateUrls);
|
|
370
|
-
const bestNewsUrl = findBestNewsUrl(candidateUrls);
|
|
371
|
-
// 1. Scrape Stats Source (Priority)
|
|
372
|
-
if (bestStatsUrl) {
|
|
373
|
-
combinedResults += `\n--- DEEP DIVE ANALYSIS: STATS & DATA (${bestStatsUrl}) ---\n`;
|
|
374
|
-
try {
|
|
375
|
-
const scrapedContent = await scrapeMatchContent(bestStatsUrl);
|
|
376
|
-
if (scrapedContent.success) {
|
|
377
|
-
combinedResults += scrapedContent.data || '';
|
|
378
|
-
}
|
|
379
|
-
else {
|
|
380
|
-
combinedResults += `(Stats scrape failed: ${scrapedContent.error})`;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
catch (scrapeError) {
|
|
384
|
-
combinedResults += `(Stats scrape failed: ${scrapeError instanceof Error ? scrapeError.message : String(scrapeError)})`;
|
|
385
|
-
}
|
|
386
|
-
combinedResults += `\n--- END STATS ---\n`;
|
|
387
|
-
}
|
|
388
|
-
// 2. Scrape News Source (if different from stats)
|
|
389
|
-
if (bestNewsUrl && bestNewsUrl !== bestStatsUrl) {
|
|
390
|
-
combinedResults += `\n--- DEEP DIVE ANALYSIS: NEWS & LINEUPS (${bestNewsUrl}) ---\n`;
|
|
391
|
-
try {
|
|
392
|
-
const scrapedContent = await scrapeMatchContent(bestNewsUrl);
|
|
393
|
-
if (scrapedContent.success) {
|
|
394
|
-
// Truncate news to avoid blowing up context, keep first 3000 chars
|
|
395
|
-
const newsContent = scrapedContent.data?.substring(0, 3000) || '';
|
|
396
|
-
combinedResults += newsContent + (scrapedContent.data && scrapedContent.data.length > 3000 ? '\n...(truncated)' : '');
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
combinedResults += `(News scrape failed: ${scrapedContent.error})`;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
catch (scrapeError) {
|
|
403
|
-
combinedResults += `(News scrape failed: ${scrapeError instanceof Error ? scrapeError.message : String(scrapeError)})`;
|
|
404
|
-
}
|
|
405
|
-
combinedResults += `\n--- END NEWS ---\n`;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
436
|
+
const searchProvider = getSearchProvider();
|
|
437
|
+
combinedResults += await executeSearches(queriesToExecute, searchProvider, candidateUrls);
|
|
438
|
+
// Step 8: Scrape deep dive sources
|
|
439
|
+
combinedResults += await scrapeDeepDiveSources(candidateUrls);
|
|
440
|
+
// Step 9: Add instructions
|
|
408
441
|
combinedResults += `\n--- END DATA ---\n\n`;
|
|
409
|
-
|
|
410
|
-
combinedResults += `INSTRUCTIONS: Act as a World-Class Football Analysis Panel. Provide a deep, non-obvious analysis using this framework:\n\n`;
|
|
411
|
-
combinedResults += `1. 📊 THE DATA SCIENTIST (Quantitative):\n - Analyze xG trends & Possession stats.\n - Assess Home/Away variance.\n\n`;
|
|
412
|
-
combinedResults += `2. 🧠 THE TACTICAL SCOUT (Qualitative):\n - Stylistic Matchup (Press vs Block).\n - Key Battles.\n\n`;
|
|
413
|
-
combinedResults += `3. 🚑 THE PHYSIO (Physical Condition):\n - FATIGUE CHECK: Days rest? Travel distance?\n - Squad Depth: Who has the better bench?\n\n`;
|
|
414
|
-
combinedResults += `4. 🎯 SET PIECE ANALYST:\n - Corners/Free Kicks: Strong vs Weak?\n - Who is most likely to score from a header?\n\n`;
|
|
415
|
-
combinedResults += `5. 💎 THE INSIDER (External Factors):\n - Market Movements (Odds dropping?).\n - Referee & Weather impact.\n\n`;
|
|
416
|
-
combinedResults += `6. 🕵️ THE SKEPTIC & SCENARIOS:\n - Why might the favorite LOSE?\n - Game Script: "If Team A scores first..."\n\n`;
|
|
417
|
-
combinedResults += `🏆 FINAL VERDICT:\n - Asian Handicap Leans\n - Goal Line (Over/Under)\n - The "Value Pick"`;
|
|
442
|
+
combinedResults += getAnalysisInstructions();
|
|
418
443
|
return combinedResults;
|
|
419
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* Scrape deep dive sources for additional context
|
|
447
|
+
*/
|
|
448
|
+
async function scrapeDeepDiveSources(candidateUrls) {
|
|
449
|
+
if (candidateUrls.length === 0)
|
|
450
|
+
return '';
|
|
451
|
+
let output = '';
|
|
452
|
+
const bestStatsUrl = findBestMatchUrl(candidateUrls);
|
|
453
|
+
const bestNewsUrl = findBestNewsUrl(candidateUrls);
|
|
454
|
+
// Scrape Stats Source
|
|
455
|
+
if (bestStatsUrl) {
|
|
456
|
+
output += `\n--- DEEP DIVE ANALYSIS: STATS & DATA (${bestStatsUrl}) ---\n`;
|
|
457
|
+
output += await scrapeContentWithErrorHandling(bestStatsUrl, 'stats');
|
|
458
|
+
output += `\n--- END STATS ---\n`;
|
|
459
|
+
}
|
|
460
|
+
// Scrape News Source (if different)
|
|
461
|
+
if (bestNewsUrl && bestNewsUrl !== bestStatsUrl) {
|
|
462
|
+
output += `\n--- DEEP DIVE ANALYSIS: NEWS & LINEUPS (${bestNewsUrl}) ---\n`;
|
|
463
|
+
output += await scrapeContentWithErrorHandling(bestNewsUrl, 'news');
|
|
464
|
+
output += `\n--- END NEWS ---\n`;
|
|
465
|
+
}
|
|
466
|
+
return output;
|
|
467
|
+
}
|
|
420
468
|
/**
|
|
421
469
|
* Calculate probability range with uncertainty quantification
|
|
422
470
|
*/
|
|
@@ -491,6 +539,37 @@ async function detectValueBets(apiData, homeTeam, awayTeam) {
|
|
|
491
539
|
}
|
|
492
540
|
return valueBets.sort((a, b) => b.value - a.value);
|
|
493
541
|
}
|
|
542
|
+
/**
|
|
543
|
+
* Calculate points from a team's form matches
|
|
544
|
+
*/
|
|
545
|
+
function calculateFormPoints(form, teamName) {
|
|
546
|
+
return form.reduce((sum, m) => {
|
|
547
|
+
const isHome = m.homeTeam.name.includes(teamName);
|
|
548
|
+
const score = m.score;
|
|
549
|
+
if (!score)
|
|
550
|
+
return sum;
|
|
551
|
+
const teamScore = isHome ? score.home : score.away;
|
|
552
|
+
const opponentScore = isHome ? score.away : score.home;
|
|
553
|
+
if (teamScore > opponentScore)
|
|
554
|
+
return sum + 3;
|
|
555
|
+
if (teamScore === opponentScore)
|
|
556
|
+
return sum + 1;
|
|
557
|
+
return sum;
|
|
558
|
+
}, 0);
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Calculate probabilities from form data
|
|
562
|
+
*/
|
|
563
|
+
function calculateProbabilitiesFromForm(homeForm, awayForm, homeTeam, awayTeam) {
|
|
564
|
+
const homePoints = calculateFormPoints(homeForm, homeTeam);
|
|
565
|
+
const awayPoints = calculateFormPoints(awayForm, awayTeam);
|
|
566
|
+
const totalPoints = homePoints + awayPoints + 5; // +5 for draw possibility
|
|
567
|
+
return {
|
|
568
|
+
homeWin: (homePoints + 1.5) / totalPoints,
|
|
569
|
+
draw: 5 / totalPoints,
|
|
570
|
+
awayWin: (awayPoints + 1.5) / totalPoints,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
494
573
|
/**
|
|
495
574
|
* Build structured analysis result from API and web data
|
|
496
575
|
*/
|
|
@@ -501,34 +580,10 @@ async function buildStructuredAnalysis(homeTeam, awayTeam, league, apiData, cove
|
|
|
501
580
|
let awayWinProb = 0.35;
|
|
502
581
|
// Adjust based on form if available
|
|
503
582
|
if (apiData?.homeForm && apiData?.awayForm) {
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
return sum;
|
|
509
|
-
if (isHome) {
|
|
510
|
-
return sum + (score.home > score.away ? 3 : score.home === score.away ? 1 : 0);
|
|
511
|
-
}
|
|
512
|
-
else {
|
|
513
|
-
return sum + (score.away > score.home ? 3 : score.away === score.home ? 1 : 0);
|
|
514
|
-
}
|
|
515
|
-
}, 0);
|
|
516
|
-
const awayPoints = apiData.awayForm.reduce((sum, m) => {
|
|
517
|
-
const isAway = m.awayTeam.name.includes(awayTeam);
|
|
518
|
-
const score = m.score;
|
|
519
|
-
if (!score)
|
|
520
|
-
return sum;
|
|
521
|
-
if (isAway) {
|
|
522
|
-
return sum + (score.away > score.home ? 3 : score.away === score.home ? 1 : 0);
|
|
523
|
-
}
|
|
524
|
-
else {
|
|
525
|
-
return sum + (score.home > score.away ? 3 : score.home === score.away ? 1 : 0);
|
|
526
|
-
}
|
|
527
|
-
}, 0);
|
|
528
|
-
const totalPoints = homePoints + awayPoints + 5; // +5 for draw possibility
|
|
529
|
-
homeWinProb = (homePoints + 1.5) / totalPoints;
|
|
530
|
-
awayWinProb = (awayPoints + 1.5) / totalPoints;
|
|
531
|
-
drawProb = 1 - homeWinProb - awayWinProb;
|
|
583
|
+
const probs = calculateProbabilitiesFromForm(apiData.homeForm, apiData.awayForm, homeTeam, awayTeam);
|
|
584
|
+
homeWinProb = probs.homeWin;
|
|
585
|
+
drawProb = probs.draw;
|
|
586
|
+
awayWinProb = probs.awayWin;
|
|
532
587
|
}
|
|
533
588
|
// Calculate probability ranges
|
|
534
589
|
const predictions = {
|