@gotza02/sequential-thinking 10000.2.1 → 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/dist/lib.js CHANGED
@@ -1,10 +1,11 @@
1
- import chalk from 'chalk';
2
1
  import * as fs from 'fs/promises';
3
2
  import { existsSync, readFileSync } from 'fs';
4
3
  import * as path from 'path';
5
4
  import { AsyncMutex } from './utils.js';
6
5
  import { ContextManager } from './core/ContextManager.js';
7
6
  import { RuleManager } from './core/RuleManager.js';
7
+ import { formatThought, generateMermaid } from './lib/formatters.js';
8
+ import { detectLoopsAndStalling, validateSolution, checkAntiInsanity, checkConfidenceCritical, calculateConfidenceScore } from './lib/validators.js';
8
9
  export class SequentialThinkingServer {
9
10
  thoughtHistory = [];
10
11
  blocks = [];
@@ -390,79 +391,7 @@ export class SequentialThinkingServer {
390
391
  }
391
392
  }
392
393
  formatThought(thoughtData) {
393
- const { thoughtNumber, totalThoughts, thought, isRevision, revisesThought, branchFromThought, branchId, thoughtType, score, options, selectedOption, blockId, relatedToolCall, complexity } = thoughtData;
394
- const typeEmoji = {
395
- 'analysis': 'šŸ”',
396
- 'planning': 'šŸ“‹',
397
- 'critique': '😈',
398
- 'execution': '⚔',
399
- 'observation': 'šŸ‘ļø',
400
- 'hypothesis': 'šŸ’”',
401
- 'reflexion': 'šŸ”„',
402
- 'solution': 'āœ…',
403
- 'generation': 'šŸ’­',
404
- 'evaluation': 'āš–ļø',
405
- 'selection': 'šŸŽÆ'
406
- };
407
- let prefix = '';
408
- let context = '';
409
- const type = thoughtType || 'analysis';
410
- const emoji = typeEmoji[type] || 'šŸ’­';
411
- if (type === 'reflexion' || isRevision) {
412
- prefix = chalk.yellow(`${emoji} Reflexion`);
413
- if (revisesThought)
414
- context += ` (revising #${revisesThought})`;
415
- }
416
- else if (type === 'critique') {
417
- prefix = chalk.redBright(`${emoji} Critique`);
418
- }
419
- else if (type === 'execution') {
420
- prefix = chalk.magenta(`${emoji} Execution`);
421
- if (relatedToolCall)
422
- context += ` [Tool: ${relatedToolCall}]`;
423
- }
424
- else if (type === 'observation') {
425
- prefix = chalk.cyan(`${emoji} Observation`);
426
- }
427
- else if (type === 'planning') {
428
- prefix = chalk.blue(`${emoji} Planning`);
429
- }
430
- else if (type === 'solution') {
431
- prefix = chalk.green(`${emoji} Solution`);
432
- }
433
- else if (branchFromThought) {
434
- prefix = chalk.green(`🌿 Branch`);
435
- context = ` (from #${branchFromThought}, ID: ${branchId})`;
436
- }
437
- else {
438
- prefix = chalk.blue(`${emoji} ${type.charAt(0).toUpperCase() + type.slice(1)}`);
439
- }
440
- if (blockId && blockId !== 'default' && blockId !== 'legacy-default') {
441
- context += ` [Block: ${blockId}]`;
442
- }
443
- if (complexity)
444
- context += ` [${complexity.toUpperCase()}]`;
445
- if (score)
446
- context += ` (Score: ${score})`;
447
- if (selectedOption)
448
- context += ` (Selected: ${selectedOption})`;
449
- const header = `${prefix} ${thoughtNumber}/${totalThoughts}${context}`;
450
- const borderLength = Math.max(header.length, Math.min(thought.length, 80)) + 4;
451
- const border = '─'.repeat(borderLength);
452
- let extraContent = '';
453
- if (options && options.length > 0) {
454
- extraContent += `\n│ Options:\n` + options.map(o => `│ - ${o}`).join('\n');
455
- }
456
- // Wrap long thoughts
457
- const wrappedThought = thought.length > 76
458
- ? thought.match(/.{1,76}/g)?.map(line => `│ ${line.padEnd(borderLength - 2)} │`).join('\n') || thought
459
- : `│ ${thought.padEnd(borderLength - 2)} │`;
460
- return `
461
- ā”Œ${border}┐
462
- │ ${header.padEnd(borderLength - 2)} │
463
- ā”œ${border}┤
464
- ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrappedThought : `│ ${thought.padEnd(borderLength - 2)} │`}${extraContent}
465
- ā””${border}ā”˜`;
394
+ return formatThought(thoughtData);
466
395
  }
467
396
  // --- Extracted Helper Methods for processThought ---
468
397
  /**
@@ -515,102 +444,24 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
515
444
  * Detect loops, stalling, and other thinking pattern issues
516
445
  */
517
446
  detectLoopsAndStalling(input, blockThoughts, recentInBlock, lastThought, complexity) {
518
- const warnings = [];
519
- let isStallingOrLooping = false;
520
- const thinkingTypes = ['analysis', 'planning', 'hypothesis', 'generation', 'critique'];
521
- // Rule 1: Stalling Detection (Adaptive)
522
- if (complexity !== 'low') {
523
- if (recentInBlock.length >= SequentialThinkingServer.STALLING_THRESHOLD &&
524
- recentInBlock.every(t => thinkingTypes.includes(t.thoughtType || 'analysis')) &&
525
- thinkingTypes.includes(input.thoughtType || 'analysis')) {
526
- warnings.push(`āš ļø STALLING DETECTED: You have been thinking for 4 consecutive steps without execution. Consider changing thoughtType to 'execution' and calling a tool to make progress.`);
527
- isStallingOrLooping = true;
528
- }
529
- }
530
- // Rule 2: Repeating Action Detection
531
- if (input.thoughtType === 'execution') {
532
- // Check Immune System Rules
533
- const ruleWarnings = this.ruleManager.checkRules(input.thought);
534
- ruleWarnings.forEach(w => warnings.push(w));
535
- if (recentInBlock.some(t => t.thoughtType === 'execution' && t.thought === input.thought)) {
536
- warnings.push(`šŸ›‘ LOOP DETECTED: You are attempting to execute the exact same action again. You MUST change your strategy or create a branch with a different approach.`);
537
- isStallingOrLooping = true;
538
- }
539
- }
540
- // Rule 3: Missing Observation
541
- if (lastThought &&
542
- lastThought.thoughtType === 'execution' &&
543
- input.thoughtType !== 'observation' &&
544
- !input.branchFromThought) {
545
- warnings.push(`šŸ’” INTERLEAVED HINT: Your last step was 'execution'. The next step SHOULD be 'observation' to record and analyze the tool result before continuing.`);
546
- }
547
- // Rule 4: Skipping Planning (Adaptive)
548
- if (complexity !== 'low') {
549
- const hasPlanning = blockThoughts.some(t => t.thoughtType === 'planning');
550
- if (!hasPlanning &&
551
- blockThoughts.length >= 2 &&
552
- input.thoughtType === 'execution') {
553
- warnings.push(`šŸ“‹ PLANNING HINT: You're about to execute without a planning step. Consider adding a 'planning' thought first to outline your approach.`);
554
- }
555
- }
556
- // Rule 5: Devil's Advocate (Critique Phase)
557
- if (complexity === 'high' && input.thoughtType === 'execution') {
558
- const hasCritique = blockThoughts.some(t => t.thoughtType === 'critique');
559
- if (!hasCritique) {
560
- warnings.push(`šŸ›‘ CRITIQUE REQUIRED: High complexity task requires a 'critique' step before execution. Analyze risks and flaws in your plan first.`);
561
- }
562
- }
563
- // Rule 6: Ending without Verification
564
- if (input.nextThoughtNeeded === false) {
565
- const hasVerification = this.thoughtHistory.some(t => t.thought.toLowerCase().includes('test') ||
566
- t.thought.toLowerCase().includes('verify') ||
567
- t.thought.toLowerCase().includes('check') ||
568
- t.thoughtType === 'observation');
569
- if (!hasVerification) {
570
- warnings.push(`🚨 CRITICAL: You are ending without explicit verification/testing. Consider adding an 'observation' or verification step.`);
571
- }
572
- }
573
- // Rule 7: Same type loop
574
- if (complexity !== 'low' && recentInBlock.length >= SequentialThinkingServer.STALLING_THRESHOLD &&
575
- recentInBlock.every(t => t.thoughtType === input.thoughtType)) {
576
- warnings.push(`āš ļø TYPE LOOP: You've used '${input.thoughtType}' for 4 consecutive steps. Consider using a different thought type to progress.`);
577
- }
578
- // Rule 8: Struggle Detection
579
- const executionCount = blockThoughts.filter(t => t.thoughtType === 'execution').length;
580
- if (executionCount >= SequentialThinkingServer.EXECUTION_STRUGGLE_THRESHOLD && input.thoughtType === 'execution') {
581
- warnings.push(`šŸ›‘ STRUGGLE DETECTED: You have executed ${SequentialThinkingServer.EXECUTION_STRUGGLE_THRESHOLD}+ commands in this block without reaching a solution. If you are fixing a bug and it's not working, STOP. Do not try a 4th time linearly. Use 'branchFromThought' to try a completely different approach.`);
582
- }
583
- // Rule 9: Premature Solution Detection
584
- const hasObservation = blockThoughts.some(t => t.thoughtType === 'observation');
585
- if (input.thoughtType === 'solution' && !hasObservation && blockThoughts.length < 4 && complexity !== 'low') {
586
- warnings.push(`šŸ¤” PREMATURE SOLUTION: You are concluding this block very quickly without any 'observation'. Are you hallucinating? Please verify with a tool first.`);
587
- }
588
- return { isStallingOrLooping, warnings };
447
+ return detectLoopsAndStalling(input, {
448
+ thoughtHistory: this.thoughtHistory,
449
+ blockThoughts,
450
+ recentInBlock,
451
+ lastThought,
452
+ complexity
453
+ }, (thought) => this.ruleManager.checkRules(thought));
589
454
  }
590
455
  /**
591
456
  * Validate solution before accepting
592
457
  */
593
458
  async validateSolution(input, blockThoughts, complexity, warnings) {
594
- const observations = blockThoughts.filter(t => t.thoughtType === 'observation');
595
- if (observations.length === 0) {
596
- warnings.push(`🚫 UNVERIFIED SOLUTION: You are attempting to solve without ANY observation/tool output in this block.`);
597
- }
598
- else {
599
- const lastObservation = observations[observations.length - 1];
600
- const failureKeywords = ['error', 'fail', 'exception', 'undefined', 'not found', 'enoent'];
601
- const hasFailure = failureKeywords.some(kw => lastObservation.toolResult?.toLowerCase().includes(kw));
602
- if (hasFailure) {
603
- warnings.push(`āš ļø FAILURE DETECTED: The last observation contains failure signals ("${lastObservation.toolResult?.substring(0, 50)}..."). Fix the error first.`);
604
- }
605
- // Smart Search Verification
606
- const searchTools = ['web_search', 'google_web_search', 'read_webpage', 'search_file_content', 'google_search'];
607
- const isSearch = lastObservation.relatedToolCall && searchTools.some(tool => lastObservation.relatedToolCall.includes(tool));
608
- if (isSearch) {
609
- warnings.push(`šŸ•µļø DATA INTEGRITY CHECK: You are concluding a search task. Verify: Did you actually find the specific answer?`);
610
- }
611
- // Double Verification for High Complexity
612
- if (complexity === 'high' && observations.length < 2) {
613
- warnings.push(`āš–ļø DOUBLE VERIFICATION REQUIRED: High complexity tasks require at least 2 distinct observations/verifications before conclusion.`);
459
+ const validationWarnings = validateSolution(input, blockThoughts, complexity);
460
+ warnings.push(...validationWarnings);
461
+ // Handle high complexity double verification penalty
462
+ if (complexity === 'high') {
463
+ const observations = blockThoughts.filter(t => t.thoughtType === 'observation');
464
+ if (observations.length < 2 && validationWarnings.some(w => w.includes('DOUBLE VERIFICATION'))) {
614
465
  this.confidenceScore -= 10;
615
466
  }
616
467
  }
@@ -628,23 +479,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
628
479
  * Update confidence score based on thought type and warnings
629
480
  */
630
481
  updateConfidenceScore(input, warnings) {
631
- if (warnings.length > 0) {
632
- this.confidenceScore = Math.max(0, this.confidenceScore - (5 * warnings.length));
633
- }
634
- if (input.thoughtType === 'reflexion') {
635
- this.confidenceScore = Math.min(100, this.confidenceScore + 10);
636
- }
637
- else if (input.thoughtType === 'observation') {
638
- const isError = input.toolResult?.toLowerCase().includes('error');
639
- if (isError)
640
- this.confidenceScore -= 10;
641
- else
642
- this.confidenceScore = Math.min(100, this.confidenceScore + 10);
643
- }
644
- else if (input.thoughtType === 'solution' && warnings.length === 0) {
645
- // Bonus for clean solution
646
- this.confidenceScore = Math.min(100, this.confidenceScore + 20);
647
- }
482
+ this.confidenceScore = calculateConfidenceScore(this.confidenceScore, input, warnings.length);
648
483
  }
649
484
  // --- Helper Methods for processThought ---
650
485
  /**
@@ -680,27 +515,10 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
680
515
  * Check anti-insanity - prevent repeating failed executions
681
516
  */
682
517
  checkAntiInsanity(input, historyForCheck) {
683
- const warnings = [];
684
- if (input.thoughtType === 'execution') {
685
- const failedExecutions = historyForCheck.filter(t => {
686
- if (t.thoughtType !== 'execution')
687
- return false;
688
- // Find the observation immediately following this execution
689
- const nextThought = this.thoughtHistory.find(h => h.thoughtNumber === t.thoughtNumber + 1);
690
- if (nextThought && nextThought.thoughtType === 'observation') {
691
- const result = nextThought.toolResult?.toLowerCase() || "";
692
- // Check for failure signals
693
- return result.includes("error") || result.includes("fail") ||
694
- result.includes("exception") || result.includes("enoent");
695
- }
696
- return false;
697
- });
698
- const isRepeatedFailure = failedExecutions.some(t => t.thought.trim() === input.thought.trim());
699
- if (isRepeatedFailure) {
700
- const failedItem = failedExecutions.find(t => t.thought.trim() === input.thought.trim());
701
- warnings.push(`ā›” INSANITY CHECK: You are trying to run a command that ALREADY FAILED in thought #${failedItem?.thoughtNumber}. STOP. Do not repeat mistakes. Try a different parameter or tool.`);
702
- this.confidenceScore -= 15;
703
- }
518
+ const warnings = checkAntiInsanity(input, historyForCheck, this.thoughtHistory);
519
+ // Apply confidence penalty for repeated failures
520
+ if (warnings.some(w => w.includes('INSANITY CHECK'))) {
521
+ this.confidenceScore -= 15;
704
522
  }
705
523
  return warnings;
706
524
  }
@@ -708,36 +526,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
708
526
  * Check if confidence is critical and return critical stop response if needed
709
527
  */
710
528
  checkConfidenceCritical(input, historyForCheck) {
711
- if (this.confidenceScore >= SequentialThinkingServer.CONFIDENCE_CRITICAL_THRESHOLD ||
712
- input.thoughtType === 'reflexion' ||
713
- input.thoughtType === 'analysis') {
714
- return null;
715
- }
716
- // Smart Branching: Find the last valid planning point
717
- const lastGoodPlan = historyForCheck
718
- .slice()
719
- .reverse()
720
- .find(t => t.thoughtType === 'planning' || t.thoughtType === 'hypothesis');
721
- const recoveryAdvice = lastGoodPlan
722
- ? `RECOMMENDATION: Branch from thought #${lastGoodPlan.thoughtNumber} to try a new approach.`
723
- : `RECOMMENDATION: Use 'reflexion' to analyze why current attempts are failing.`;
724
- return {
725
- content: [{
726
- type: "text",
727
- text: JSON.stringify({
728
- status: "CRITICAL_STOP",
729
- feedback: [
730
- "🚨 CONFIDENCE CRITICAL (<50). Execution blocked to prevent further damage.",
731
- "šŸ›‘ STOP: You are likely in a loop or making repeated errors.",
732
- "šŸ‘‰ REQUIRED ACTION: You must switch thoughtType to 'reflexion' to critique your errors.",
733
- "āš ļø REMEMBER: You MUST provide a 'thought' string explaining your reflection. Do not leave it empty.",
734
- recoveryAdvice
735
- ],
736
- confidenceScore: this.confidenceScore
737
- }, null, 2)
738
- }],
739
- isError: true
740
- };
529
+ return checkConfidenceCritical(this.confidenceScore, input, historyForCheck);
741
530
  }
742
531
  // --- 2. The New Brain: Process Thought with Interleaved Logic (Opus 4.5 Style) ---
743
532
  async processThought(input) {
@@ -847,38 +636,7 @@ ${typeof wrappedThought === 'string' && wrappedThought.startsWith('│') ? wrapp
847
636
  return 'THINKING';
848
637
  }
849
638
  generateMermaid() {
850
- let diagram = 'graph TD\n';
851
- const recentThoughts = this.thoughtHistory.slice(-15); // Last 15 for readability
852
- recentThoughts.forEach((t, i) => {
853
- const id = `T${t.thoughtNumber}`;
854
- const typeIcon = {
855
- 'analysis': 'šŸ”',
856
- 'planning': 'šŸ“‹',
857
- 'critique': '😈',
858
- 'execution': '⚔',
859
- 'observation': 'šŸ‘ļø',
860
- 'hypothesis': 'šŸ’”',
861
- 'reflexion': 'šŸ”„',
862
- 'solution': 'āœ…',
863
- 'generation': 'šŸ’­',
864
- 'evaluation': 'āš–ļø',
865
- 'selection': 'šŸŽÆ'
866
- }[t.thoughtType || 'analysis'] || 'šŸ’­';
867
- const label = `${typeIcon} ${t.thoughtNumber}`;
868
- diagram += ` ${id}("${label}")\n`;
869
- if (i > 0) {
870
- const prevT = recentThoughts[i - 1];
871
- const prevId = `T${prevT.thoughtNumber}`;
872
- if (t.branchFromThought) {
873
- diagram += ` T${t.branchFromThought} -->|branch: ${t.branchId}| ${id}\n`;
874
- }
875
- else {
876
- const edgeLabel = t.thoughtType === 'observation' ? '|result|' : '';
877
- diagram += ` ${prevId} -->${edgeLabel} ${id}\n`;
878
- }
879
- }
880
- });
881
- return diagram;
639
+ return generateMermaid(this.thoughtHistory);
882
640
  }
883
641
  // Public method to start a new block explicitly
884
642
  startNewBlock(blockId, topic) {
@@ -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
+ }