@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/README.md.bak +197 -0
- package/dist/.gemini_graph_cache.json.bak +1641 -0
- package/dist/graph.d.ts +7 -0
- package/dist/graph.js +136 -113
- package/dist/intelligent-code.d.ts +8 -0
- package/dist/intelligent-code.js +152 -125
- package/dist/intelligent-code.test.d.ts +1 -0
- package/dist/intelligent-code.test.js +104 -0
- package/dist/lib/formatters.d.ts +9 -0
- package/dist/lib/formatters.js +119 -0
- package/dist/lib/validators.d.ts +45 -0
- package/dist/lib/validators.js +232 -0
- package/dist/lib.js +23 -265
- package/dist/tools/sports/tools/match-calculations.d.ts +51 -0
- package/dist/tools/sports/tools/match-calculations.js +171 -0
- package/dist/tools/sports/tools/match-helpers.d.ts +21 -0
- package/dist/tools/sports/tools/match-helpers.js +57 -0
- package/dist/tools/sports/tools/match.js +19 -122
- package/dist/tools/sports.js +3 -3
- package/dist/utils.d.ts +111 -44
- package/dist/utils.js +510 -305
- package/dist/utils.test.js +3 -3
- package/package.json +1 -1
package/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
|
-
|
|
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
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
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
|
-
|
|
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
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
}
|