@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.
Files changed (35) hide show
  1. package/README.md.bak +197 -0
  2. package/dist/.gemini_graph_cache.json.bak +1641 -0
  3. package/dist/graph.d.ts +7 -0
  4. package/dist/graph.js +136 -113
  5. package/dist/intelligent-code.d.ts +8 -0
  6. package/dist/intelligent-code.js +152 -125
  7. package/dist/intelligent-code.test.d.ts +1 -0
  8. package/dist/intelligent-code.test.js +104 -0
  9. package/dist/lib/formatters.d.ts +9 -0
  10. package/dist/lib/formatters.js +119 -0
  11. package/dist/lib/validators.d.ts +45 -0
  12. package/dist/lib/validators.js +232 -0
  13. package/dist/lib.js +23 -265
  14. package/dist/tools/sports/core/base.d.ts +3 -2
  15. package/dist/tools/sports/core/base.js +12 -10
  16. package/dist/tools/sports/core/cache.d.ts +9 -0
  17. package/dist/tools/sports/core/cache.js +25 -3
  18. package/dist/tools/sports/core/types.d.ts +6 -2
  19. package/dist/tools/sports/providers/api.d.ts +4 -0
  20. package/dist/tools/sports/providers/api.js +110 -27
  21. package/dist/tools/sports/tools/betting.js +16 -16
  22. package/dist/tools/sports/tools/league.d.ts +2 -7
  23. package/dist/tools/sports/tools/league.js +198 -8
  24. package/dist/tools/sports/tools/live.js +80 -38
  25. package/dist/tools/sports/tools/match-calculations.d.ts +51 -0
  26. package/dist/tools/sports/tools/match-calculations.js +171 -0
  27. package/dist/tools/sports/tools/match-helpers.d.ts +21 -0
  28. package/dist/tools/sports/tools/match-helpers.js +57 -0
  29. package/dist/tools/sports/tools/match.js +227 -125
  30. package/dist/tools/sports.js +3 -3
  31. package/dist/utils.d.ts +111 -44
  32. package/dist/utils.js +510 -305
  33. package/dist/utils.test.js +3 -3
  34. package/package.json +1 -1
  35. 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
- // In-memory watchlist storage (initialized from file)
12
- let watchlist = [];
13
- // Load watchlist from file
14
- async function loadWatchlist() {
15
- try {
16
- const filePath = path.resolve(WATCHLIST_FILE);
17
- const data = await fs.readFile(filePath, 'utf-8');
18
- watchlist = JSON.parse(data);
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
- catch (error) {
21
- // File doesn't exist or is invalid, start with empty list
22
- watchlist = [];
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
- // Save watchlist to file
26
- async function saveWatchlist() {
27
- try {
28
- const filePath = path.resolve(WATCHLIST_FILE);
29
- await fs.writeFile(filePath, JSON.stringify(watchlist, null, 2), 'utf-8');
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
- catch (error) {
32
- console.error(`Failed to save watchlist to ${WATCHLIST_FILE}:`, error);
62
+ get length() {
63
+ return this.items.length;
33
64
  }
34
65
  }
35
- // Initialize watchlist
36
- loadWatchlist();
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
- watchlist.push(item);
92
- await saveWatchlist();
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: ${watchlist.length}`;
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
- if (watchlist.length === 0) {
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 (${watchlist.length} items)\n\n`;
119
- for (const item of watchlist) {
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
- const index = watchlist.findIndex(item => item.id === id);
140
- if (index === -1) {
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
- const removed = watchlist.splice(index, 1)[0];
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: ${watchlist.length}`;
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
- const count = watchlist.length;
163
- watchlist = []; // Reset array
164
- await saveWatchlist();
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
- if (watchlist.length === 0) {
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 watchlist) {
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 saveWatchlist();
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
+ }