This workflow corresponds to n8n.io template #10890 — we link there as the canonical source.
This workflow follows the Agent → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "av2MtlrQsNETUN9T",
"name": "Singapore Lottery Predictive Analytics and Pattern Mining System",
"tags": [],
"nodes": [
{
"id": "1a46beb0-fdef-494a-9215-81d898265029",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
2192,
3376
],
"parameters": {
"rule": {
"interval": [
{
"triggerAtHour": 9
}
]
}
},
"typeVersion": 1.2
},
{
"id": "58e89b70-6412-4978-a158-f757256fe5b4",
"name": "Workflow Configuration",
"type": "n8n-nodes-base.set",
"position": [
2416,
3376
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "id-1",
"name": "totoApiUrl",
"type": "string",
"value": "<__PLACEHOLDER_VALUE__TOTO API endpoint URL__>"
},
{
"id": "id-2",
"name": "fourDApiUrl",
"type": "string",
"value": "<__PLACEHOLDER_VALUE__4D API endpoint URL__>"
},
{
"id": "id-3",
"name": "totoHistoricalUrl",
"type": "string",
"value": "<__PLACEHOLDER_VALUE__Historical TOTO dataset URL__>"
},
{
"id": "id-4",
"name": "fourDHistoricalUrl",
"type": "string",
"value": "<__PLACEHOLDER_VALUE__Historical 4D dataset URL__>"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "6277a621-1239-46dc-bd64-3d7680f17ac7",
"name": "Fetch TOTO Draw Data",
"type": "n8n-nodes-base.httpRequest",
"position": [
2656,
3120
],
"parameters": {
"url": "={{ $('Workflow Configuration').first().json.totoApiUrl }}",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"typeVersion": 4.3
},
{
"id": "ea37ca70-7097-4543-a9cf-0da2cf19074b",
"name": "Fetch 4D Draw Data",
"type": "n8n-nodes-base.httpRequest",
"position": [
2640,
3280
],
"parameters": {
"url": "={{ $('Workflow Configuration').first().json.fourDApiUrl }}",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"typeVersion": 4.3
},
{
"id": "d91c2336-0e53-423d-ae3e-f3ffab35cb24",
"name": "Fetch Historical TOTO Dataset",
"type": "n8n-nodes-base.httpRequest",
"position": [
2640,
3472
],
"parameters": {
"url": "={{ $('Workflow Configuration').first().json.totoHistoricalUrl }}",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"typeVersion": 4.3
},
{
"id": "5f41f489-6d50-4f1f-875f-7ed12d1740d1",
"name": "Fetch Historical 4D Dataset",
"type": "n8n-nodes-base.httpRequest",
"position": [
2640,
3664
],
"parameters": {
"url": "={{ $('Workflow Configuration').first().json.fourDHistoricalUrl }}",
"options": {
"response": {
"response": {
"responseFormat": "json"
}
}
}
},
"typeVersion": 4.3
},
{
"id": "d48f1333-3ca0-4f40-b651-4aa95219f0a5",
"name": "Merge TOTO Data",
"type": "n8n-nodes-base.aggregate",
"position": [
2912,
3232
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData",
"destinationFieldName": "allDraws"
},
"typeVersion": 1
},
{
"id": "c25d981b-7a87-4f97-81e5-3e9f1bdd1684",
"name": "Merge 4D Data",
"type": "n8n-nodes-base.aggregate",
"position": [
2912,
3472
],
"parameters": {
"options": {},
"aggregate": "aggregateAllItemData",
"destinationFieldName": "allDraws"
},
"typeVersion": 1
},
{
"id": "256f413f-bf6c-4de0-b97f-da647b4a0648",
"name": "Statistical Pattern Mining - TOTO",
"type": "n8n-nodes-base.code",
"position": [
3312,
3120
],
"parameters": {
"jsCode": "// Statistical Pattern Mining for TOTO Data\n// Comprehensive analysis of lottery patterns with uncertainty quantification\n\nconst items = $input.all();\nconst allDraws = [];\n\n// Extract all draw data\nfor (const item of items) {\n if (item.json.draws && Array.isArray(item.json.draws)) {\n allDraws.push(...item.json.draws);\n } else if (item.json.numbers) {\n allDraws.push(item.json);\n }\n}\n\nconsole.log(`Processing ${allDraws.length} TOTO draws for pattern mining`);\n\n// Initialize analysis structures\nconst numberFrequency = {};\nconst consecutivePatterns = {};\nconst sumRanges = {};\nconst oddEvenRatios = {};\nconst numberGaps = {};\nconst temporalTrends = {};\n\n// Process each draw\nfor (const draw of allDraws) {\n const numbers = draw.numbers || draw.winningNumbers || [];\n const drawDate = draw.date || draw.drawDate;\n \n if (!numbers || numbers.length === 0) continue;\n \n // 1. Frequency Analysis\n for (const num of numbers) {\n numberFrequency[num] = (numberFrequency[num] || 0) + 1;\n }\n \n // 2. Consecutive Number Patterns\n const sortedNumbers = [...numbers].sort((a, b) => a - b);\n let consecutiveCount = 1;\n for (let i = 1; i < sortedNumbers.length; i++) {\n if (sortedNumbers[i] === sortedNumbers[i-1] + 1) {\n consecutiveCount++;\n }\n }\n consecutivePatterns[consecutiveCount] = (consecutivePatterns[consecutiveCount] || 0) + 1;\n \n // 3. Sum Range Analysis\n const sum = numbers.reduce((acc, num) => acc + num, 0);\n const sumRange = Math.floor(sum / 50) * 50;\n sumRanges[sumRange] = (sumRanges[sumRange] || 0) + 1;\n \n // 4. Odd/Even Ratio\n const oddCount = numbers.filter(n => n % 2 !== 0).length;\n const evenCount = numbers.length - oddCount;\n const ratio = `${oddCount}:${evenCount}`;\n oddEvenRatios[ratio] = (oddEvenRatios[ratio] || 0) + 1;\n \n // 5. Number Gap Analysis (distance between consecutive numbers)\n const gaps = [];\n for (let i = 1; i < sortedNumbers.length; i++) {\n gaps.push(sortedNumbers[i] - sortedNumbers[i-1]);\n }\n const avgGap = gaps.length > 0 ? (gaps.reduce((a, b) => a + b, 0) / gaps.length).toFixed(2) : 0;\n numberGaps[avgGap] = (numberGaps[avgGap] || 0) + 1;\n \n // 6. Temporal Trend Detection\n if (drawDate) {\n const year = new Date(drawDate).getFullYear();\n if (!temporalTrends[year]) {\n temporalTrends[year] = { draws: 0, totalSum: 0, numbers: [] };\n }\n temporalTrends[year].draws++;\n temporalTrends[year].totalSum += sum;\n temporalTrends[year].numbers.push(...numbers);\n }\n}\n\n// Utility function: Calculate standard deviation\nfunction calculateStdDev(values) {\n const mean = values.reduce((a, b) => a + b, 0) / values.length;\n const variance = values.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / values.length;\n return { mean, variance, stdDev: Math.sqrt(variance) };\n}\n\n// Utility function: Calculate 95% confidence interval\nfunction calculateConfidenceInterval(proportion, sampleSize) {\n const z = 1.96; // 95% confidence level\n const se = Math.sqrt((proportion * (1 - proportion)) / sampleSize);\n return {\n lower: Math.max(0, proportion - z * se),\n upper: Math.min(1, proportion + z * se),\n standardError: se\n };\n}\n\n// Utility function: Chi-square test for statistical significance\nfunction chiSquareTest(observed, expected) {\n let chiSquare = 0;\n for (let i = 0; i < observed.length; i++) {\n chiSquare += Math.pow(observed[i] - expected[i], 2) / expected[i];\n }\n // Degrees of freedom = n - 1\n const df = observed.length - 1;\n // Critical value for 95% confidence (df varies, using approximation)\n const criticalValue = 3.841; // for df=1, increases with df\n return {\n chiSquare: chiSquare.toFixed(4),\n degreesOfFreedom: df,\n isSignificant: chiSquare > criticalValue,\n pValue: chiSquare > criticalValue ? '< 0.05' : '>= 0.05'\n };\n}\n\n// Identify Hot and Cold Numbers\nconst sortedByFrequency = Object.entries(numberFrequency)\n .sort((a, b) => b[1] - a[1]);\n\nconst hotNumbers = sortedByFrequency.slice(0, 10).map(([num, freq]) => {\n const proportion = freq / allDraws.length;\n const ci = calculateConfidenceInterval(proportion, allDraws.length);\n return {\n number: parseInt(num),\n frequency: freq,\n percentage: ((freq / allDraws.length) * 100).toFixed(2),\n confidenceInterval: {\n lower: (ci.lower * 100).toFixed(2) + '%',\n upper: (ci.upper * 100).toFixed(2) + '%'\n },\n standardError: ci.standardError.toFixed(4)\n };\n});\n\nconst coldNumbers = sortedByFrequency.slice(-10).map(([num, freq]) => {\n const proportion = freq / allDraws.length;\n const ci = calculateConfidenceInterval(proportion, allDraws.length);\n return {\n number: parseInt(num),\n frequency: freq,\n percentage: ((freq / allDraws.length) * 100).toFixed(2),\n confidenceInterval: {\n lower: (ci.lower * 100).toFixed(2) + '%',\n upper: (ci.upper * 100).toFixed(2) + '%'\n },\n standardError: ci.standardError.toFixed(4)\n };\n});\n\n// Calculate frequency distribution statistics\nconst frequencies = Object.values(numberFrequency);\nconst freqStats = calculateStdDev(frequencies);\n\n// Calculate temporal trends\nconst temporalAnalysis = Object.entries(temporalTrends).map(([year, data]) => {\n const uniqueNumbers = [...new Set(data.numbers)];\n const avgSum = (data.totalSum / data.draws).toFixed(2);\n return {\n year: parseInt(year),\n totalDraws: data.draws,\n averageSum: parseFloat(avgSum),\n uniqueNumbersDrawn: uniqueNumbers.length\n };\n});\n\n// Chi-square test for frequency distribution uniformity\nconst totalNumbers = Object.keys(numberFrequency).length;\nconst expectedFreq = allDraws.length / totalNumbers;\nconst observedFreqs = Object.values(numberFrequency);\nconst expectedFreqs = new Array(observedFreqs.length).fill(expectedFreq);\nconst chiSquareResult = chiSquareTest(observedFreqs, expectedFreqs);\n\n// Prepare output with uncertainty quantification\nconst analysis = {\n metadata: {\n totalDrawsAnalyzed: allDraws.length,\n analysisDate: new Date().toISOString(),\n lotteryType: 'TOTO'\n },\n frequencyAnalysis: {\n hotNumbers,\n coldNumbers,\n totalUniqueNumbers: Object.keys(numberFrequency).length,\n uncertaintyMetrics: {\n meanFrequency: freqStats.mean.toFixed(2),\n variance: freqStats.variance.toFixed(2),\n standardDeviation: freqStats.stdDev.toFixed(2),\n coefficientOfVariation: ((freqStats.stdDev / freqStats.mean) * 100).toFixed(2) + '%'\n },\n statisticalSignificance: {\n ...chiSquareResult,\n interpretation: chiSquareResult.isSignificant \n ? 'Frequency distribution is significantly non-uniform (rejects null hypothesis)'\n : 'Frequency distribution does not significantly differ from uniform distribution'\n }\n },\n consecutivePatterns: Object.entries(consecutivePatterns)\n .map(([count, freq]) => {\n const proportion = freq / allDraws.length;\n const ci = calculateConfidenceInterval(proportion, allDraws.length);\n return {\n consecutiveCount: parseInt(count),\n occurrences: freq,\n percentage: ((freq / allDraws.length) * 100).toFixed(2),\n confidenceInterval: {\n lower: (ci.lower * 100).toFixed(2) + '%',\n upper: (ci.upper * 100).toFixed(2) + '%'\n }\n };\n })\n .sort((a, b) => b.occurrences - a.occurrences),\n sumRangeDistribution: Object.entries(sumRanges)\n .map(([range, freq]) => {\n const proportion = freq / allDraws.length;\n const ci = calculateConfidenceInterval(proportion, allDraws.length);\n return {\n sumRange: `${range}-${parseInt(range) + 49}`,\n occurrences: freq,\n percentage: ((freq / allDraws.length) * 100).toFixed(2),\n confidenceInterval: {\n lower: (ci.lower * 100).toFixed(2) + '%',\n upper: (ci.upper * 100).toFixed(2) + '%'\n }\n };\n })\n .sort((a, b) => b.occurrences - a.occurrences),\n oddEvenPatterns: Object.entries(oddEvenRatios)\n .map(([ratio, freq]) => {\n const proportion = freq / allDraws.length;\n const ci = calculateConfidenceInterval(proportion, allDraws.length);\n return {\n ratio,\n occurrences: freq,\n percentage: ((freq / allDraws.length) * 100).toFixed(2),\n confidenceInterval: {\n lower: (ci.lower * 100).toFixed(2) + '%',\n upper: (ci.upper * 100).toFixed(2) + '%'\n }\n };\n })\n .sort((a, b) => b.occurrences - a.occurrences),\n numberGapAnalysis: Object.entries(numberGaps)\n .map(([gap, freq]) => {\n const proportion = freq / allDraws.length;\n const ci = calculateConfidenceInterval(proportion, allDraws.length);\n return {\n averageGap: parseFloat(gap),\n occurrences: freq,\n percentage: ((freq / allDraws.length) * 100).toFixed(2),\n confidenceInterval: {\n lower: (ci.lower * 100).toFixed(2) + '%',\n upper: (ci.upper * 100).toFixed(2) + '%'\n }\n };\n })\n .sort((a, b) => b.occurrences - a.occurrences)\n .slice(0, 15),\n temporalTrends: temporalAnalysis.sort((a, b) => b.year - a.year),\n insights: {\n mostFrequentNumber: hotNumbers[0]?.number,\n leastFrequentNumber: coldNumbers[0]?.number,\n mostCommonOddEvenRatio: Object.entries(oddEvenRatios).sort((a, b) => b[1] - a[1])[0]?.[0],\n mostCommonConsecutivePattern: Object.entries(consecutivePatterns).sort((a, b) => b[1] - a[1])[0]?.[0]\n }\n};\n\nreturn [{ json: analysis }];"
},
"typeVersion": 2
},
{
"id": "b8791609-cf27-4c6d-bb4c-bc1d8529b061",
"name": "Statistical Pattern Mining - 4D",
"type": "n8n-nodes-base.code",
"position": [
3312,
3472
],
"parameters": {
"jsCode": "// Statistical Pattern Mining for 4D Data\n// Analyzes historical 4D lottery data to identify patterns and trends\n// Enhanced with uncertainty quantification and statistical significance\n\nconst items = $input.all();\nconst results = [];\n\n// Extract all 4D numbers from the data\nconst fourDNumbers = [];\nconst drawDates = [];\n\nfor (const item of items) {\n const data = item.json;\n \n // Handle different data structures\n if (data.drawNumber) {\n fourDNumbers.push(data.drawNumber.toString().padStart(4, '0'));\n drawDates.push(data.drawDate || data.date);\n } else if (data.number) {\n fourDNumbers.push(data.number.toString().padStart(4, '0'));\n drawDates.push(data.date);\n } else if (Array.isArray(data)) {\n for (const entry of data) {\n if (entry.drawNumber || entry.number) {\n fourDNumbers.push((entry.drawNumber || entry.number).toString().padStart(4, '0'));\n drawDates.push(entry.drawDate || entry.date);\n }\n }\n }\n}\n\n// Statistical Helper Functions\nfunction calculateMean(values) {\n return values.reduce((a, b) => a + b, 0) / values.length;\n}\n\nfunction calculateStandardDeviation(values, mean) {\n const squaredDiffs = values.map(v => Math.pow(v - mean, 2));\n const variance = squaredDiffs.reduce((a, b) => a + b, 0) / values.length;\n return Math.sqrt(variance);\n}\n\nfunction calculateVariance(values, mean) {\n const squaredDiffs = values.map(v => Math.pow(v - mean, 2));\n return squaredDiffs.reduce((a, b) => a + b, 0) / values.length;\n}\n\nfunction calculateConfidenceInterval(proportion, sampleSize, confidenceLevel = 0.95) {\n // Using Wilson score interval for better accuracy with proportions\n const z = confidenceLevel === 0.95 ? 1.96 : (confidenceLevel === 0.99 ? 2.576 : 1.645);\n const denominator = 1 + (z * z) / sampleSize;\n const center = (proportion + (z * z) / (2 * sampleSize)) / denominator;\n const margin = (z * Math.sqrt((proportion * (1 - proportion) / sampleSize) + (z * z) / (4 * sampleSize * sampleSize))) / denominator;\n \n return {\n lower: Math.max(0, center - margin),\n upper: Math.min(1, center + margin),\n center: center\n };\n}\n\nfunction calculateChiSquare(observed, expected) {\n let chiSquare = 0;\n for (let i = 0; i < observed.length; i++) {\n chiSquare += Math.pow(observed[i] - expected[i], 2) / expected[i];\n }\n return chiSquare;\n}\n\nfunction isStatisticallySignificant(chiSquare, degreesOfFreedom, alpha = 0.05) {\n // Critical values for chi-square test at alpha = 0.05\n const criticalValues = {\n 1: 3.841, 2: 5.991, 3: 7.815, 4: 9.488, 5: 11.070,\n 6: 12.592, 7: 14.067, 8: 15.507, 9: 16.919, 10: 18.307\n };\n const critical = criticalValues[degreesOfFreedom] || 18.307;\n return chiSquare > critical;\n}\n\n// 1. DIGIT FREQUENCY ANALYSIS BY POSITION WITH UNCERTAINTY\nconst positionFrequency = [\n {}, // Position 1 (thousands)\n {}, // Position 2 (hundreds)\n {}, // Position 3 (tens)\n {} // Position 4 (ones)\n];\n\nfor (const num of fourDNumbers) {\n for (let i = 0; i < 4; i++) {\n const digit = num[i];\n positionFrequency[i][digit] = (positionFrequency[i][digit] || 0) + 1;\n }\n}\n\n// Convert to sorted arrays with statistical measures\nconst positionAnalysis = positionFrequency.map((freq, pos) => {\n const counts = Object.values(freq);\n const mean = calculateMean(counts);\n const stdDev = calculateStandardDeviation(counts, mean);\n const variance = calculateVariance(counts, mean);\n \n // Chi-square test for uniformity\n const expectedFreq = fourDNumbers.length / 10; // Expected frequency for uniform distribution\n const observed = Array(10).fill(0);\n for (let i = 0; i < 10; i++) {\n observed[i] = freq[i.toString()] || 0;\n }\n const expected = Array(10).fill(expectedFreq);\n const chiSquare = calculateChiSquare(observed, expected);\n const isSignificant = isStatisticallySignificant(chiSquare, 9);\n \n const sorted = Object.entries(freq)\n .map(([digit, count]) => {\n const proportion = count / fourDNumbers.length;\n const ci = calculateConfidenceInterval(proportion, fourDNumbers.length);\n return {\n digit,\n count,\n percentage: (proportion * 100).toFixed(2),\n confidenceInterval95: {\n lower: (ci.lower * 100).toFixed(2) + '%',\n upper: (ci.upper * 100).toFixed(2) + '%'\n }\n };\n })\n .sort((a, b) => b.count - a.count);\n \n return {\n position: pos + 1,\n topDigits: sorted.slice(0, 5),\n leastFrequent: sorted.slice(-3),\n statistics: {\n mean: mean.toFixed(2),\n standardDeviation: stdDev.toFixed(2),\n variance: variance.toFixed(2),\n coefficientOfVariation: ((stdDev / mean) * 100).toFixed(2) + '%'\n },\n significanceTest: {\n chiSquare: chiSquare.toFixed(2),\n degreesOfFreedom: 9,\n isSignificant: isSignificant,\n interpretation: isSignificant ? 'Non-uniform distribution detected' : 'Distribution appears uniform'\n }\n };\n});\n\n// 2. REPEATING DIGIT PATTERNS WITH UNCERTAINTY\nconst repeatingPatterns = {\n allSame: 0, // e.g., 1111\n threeSame: 0, // e.g., 1112\n twoSame: 0, // e.g., 1122\n twoPairs: 0, // e.g., 1122\n noRepeats: 0 // e.g., 1234\n};\n\nfor (const num of fourDNumbers) {\n const digitCounts = {};\n for (const digit of num) {\n digitCounts[digit] = (digitCounts[digit] || 0) + 1;\n }\n \n const counts = Object.values(digitCounts).sort((a, b) => b - a);\n \n if (counts[0] === 4) repeatingPatterns.allSame++;\n else if (counts[0] === 3) repeatingPatterns.threeSame++;\n else if (counts[0] === 2 && counts[1] === 2) repeatingPatterns.twoPairs++;\n else if (counts[0] === 2) repeatingPatterns.twoSame++;\n else repeatingPatterns.noRepeats++;\n}\n\nconst repeatingPatternsWithUncertainty = {};\nfor (const [pattern, count] of Object.entries(repeatingPatterns)) {\n const proportion = count / fourDNumbers.length;\n const ci = calculateConfidenceInterval(proportion, fourDNumbers.length);\n repeatingPatternsWithUncertainty[pattern] = {\n count: count,\n percentage: (proportion * 100).toFixed(2) + '%',\n confidenceInterval95: {\n lower: (ci.lower * 100).toFixed(2) + '%',\n upper: (ci.upper * 100).toFixed(2) + '%'\n },\n standardError: (Math.sqrt(proportion * (1 - proportion) / fourDNumbers.length) * 100).toFixed(2) + '%'\n };\n}\n\n// 3. SUM PATTERNS WITH UNCERTAINTY\nconst sumDistribution = {};\nconst sumStats = [];\n\nfor (const num of fourDNumbers) {\n const sum = num.split('').reduce((acc, digit) => acc + parseInt(digit), 0);\n sumDistribution[sum] = (sumDistribution[sum] || 0) + 1;\n sumStats.push(sum);\n}\n\nconst avgSum = calculateMean(sumStats);\nconst sumStdDev = calculateStandardDeviation(sumStats, avgSum);\nconst sumVariance = calculateVariance(sumStats, avgSum);\n\nconst mostCommonSums = Object.entries(sumDistribution)\n .map(([sum, count]) => {\n const proportion = count / fourDNumbers.length;\n const ci = calculateConfidenceInterval(proportion, fourDNumbers.length);\n return {\n sum: parseInt(sum),\n count,\n percentage: (proportion * 100).toFixed(2),\n confidenceInterval95: {\n lower: (ci.lower * 100).toFixed(2) + '%',\n upper: (ci.upper * 100).toFixed(2) + '%'\n }\n };\n })\n .sort((a, b) => b.count - a.count)\n .slice(0, 10);\n\n// 4. SEQUENTIAL PATTERNS WITH UNCERTAINTY\nconst sequentialPatterns = {\n ascending: 0, // e.g., 1234\n descending: 0, // e.g., 4321\n partialAscending: 0,\n partialDescending: 0\n};\n\nfor (const num of fourDNumbers) {\n const digits = num.split('').map(d => parseInt(d));\n \n let ascending = 0;\n let descending = 0;\n \n for (let i = 0; i < 3; i++) {\n if (digits[i + 1] === digits[i] + 1) ascending++;\n if (digits[i + 1] === digits[i] - 1) descending++;\n }\n \n if (ascending === 3) sequentialPatterns.ascending++;\n else if (ascending >= 2) sequentialPatterns.partialAscending++;\n \n if (descending === 3) sequentialPatterns.descending++;\n else if (descending >= 2) sequentialPatterns.partialDescending++;\n}\n\nconst sequentialPatternsWithUncertainty = {};\nfor (const [pattern, count] of Object.entries(sequentialPatterns)) {\n const proportion = count / fourDNumbers.length;\n const ci = calculateConfidenceInterval(proportion, fourDNumbers.length);\n sequentialPatternsWithUncertainty[pattern] = {\n count: count,\n percentage: (proportion * 100).toFixed(2) + '%',\n confidenceInterval95: {\n lower: (ci.lower * 100).toFixed(2) + '%',\n upper: (ci.upper * 100).toFixed(2) + '%'\n },\n standardError: (Math.sqrt(proportion * (1 - proportion) / fourDNumbers.length) * 100).toFixed(2) + '%'\n };\n}\n\n// 5. MIRROR PATTERNS WITH UNCERTAINTY\nconst mirrorPatterns = {\n fullMirror: 0, // e.g., 1221\n outerMirror: 0, // e.g., 1xx1\n innerMirror: 0 // e.g., x11x\n};\n\nfor (const num of fourDNumbers) {\n if (num[0] === num[3] && num[1] === num[2]) mirrorPatterns.fullMirror++;\n else if (num[0] === num[3]) mirrorPatterns.outerMirror++;\n else if (num[1] === num[2]) mirrorPatterns.innerMirror++;\n}\n\nconst mirrorPatternsWithUncertainty = {};\nfor (const [pattern, count] of Object.entries(mirrorPatterns)) {\n const proportion = count / fourDNumbers.length;\n const ci = calculateConfidenceInterval(proportion, fourDNumbers.length);\n mirrorPatternsWithUncertainty[pattern] = {\n count: count,\n percentage: (proportion * 100).toFixed(2) + '%',\n confidenceInterval95: {\n lower: (ci.lower * 100).toFixed(2) + '%',\n upper: (ci.upper * 100).toFixed(2) + '%'\n },\n standardError: (Math.sqrt(proportion * (1 - proportion) / fourDNumbers.length) * 100).toFixed(2) + '%'\n };\n}\n\n// 6. TEMPORAL DISTRIBUTION ANALYSIS\nconst recentNumbers = fourDNumbers.slice(-20);\nconst recentDigitFreq = {};\n\nfor (const num of recentNumbers) {\n for (const digit of num) {\n recentDigitFreq[digit] = (recentDigitFreq[digit] || 0) + 1;\n }\n}\n\nconst hotDigits = Object.entries(recentDigitFreq)\n .map(([digit, count]) => ({ digit, count }))\n .sort((a, b) => b.count - a.count)\n .slice(0, 5);\n\nconst coldDigits = Object.entries(recentDigitFreq)\n .map(([digit, count]) => ({ digit, count }))\n .sort((a, b) => a.count - b.count)\n .slice(0, 5);\n\n// Compile comprehensive analysis with uncertainty quantification\nconst analysis = {\n datasetSize: fourDNumbers.length,\n analysisDate: new Date().toISOString(),\n \n uncertaintyMetrics: {\n sampleSize: fourDNumbers.length,\n confidenceLevel: '95%',\n description: 'All confidence intervals calculated using Wilson score method for proportions',\n statisticalSignificance: 'Chi-square tests performed at alpha = 0.05 significance level'\n },\n \n positionAnalysis: positionAnalysis,\n \n repeatingPatterns: repeatingPatternsWithUncertainty,\n \n sumPatterns: {\n averageSum: avgSum.toFixed(2),\n standardDeviation: sumStdDev.toFixed(2),\n variance: sumVariance.toFixed(2),\n coefficientOfVariation: ((sumStdDev / avgSum) * 100).toFixed(2) + '%',\n mostCommonSums: mostCommonSums,\n sumRange: {\n min: Math.min(...sumStats),\n max: Math.max(...sumStats)\n },\n confidenceInterval95: {\n lower: (avgSum - 1.96 * sumStdDev / Math.sqrt(fourDNumbers.length)).toFixed(2),\n upper: (avgSum + 1.96 * sumStdDev / Math.sqrt(fourDNumbers.length)).toFixed(2)\n }\n },\n \n sequentialPatterns: sequentialPatternsWithUncertainty,\n \n mirrorPatterns: mirrorPatternsWithUncertainty,\n \n temporalAnalysis: {\n recentDrawsAnalyzed: recentNumbers.length,\n hotDigits: hotDigits,\n coldDigits: coldDigits\n },\n \n rawData: {\n allNumbers: fourDNumbers,\n recentNumbers: recentNumbers\n }\n};\n\nreturn [{ json: analysis }];"
},
"typeVersion": 2
},
{
"id": "09ece952-5a3f-400f-be6e-533b131706a5",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
3984,
3424
],
"parameters": {
"model": {
"__rl": true,
"mode": "id",
"value": "gpt-4o"
},
"options": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "092efe72-c58e-45cd-a8e9-c6be0617821c",
"name": "Probabilistic Modeling Tool",
"type": "@n8n/n8n-nodes-langchain.toolCode",
"position": [
4112,
3424
],
"parameters": {
"jsCode": "// Advanced Probabilistic Modeling and Monte Carlo Simulation for Lottery Predictions\n// Enhanced with comprehensive uncertainty metrics\n\n// Parse the query input (expecting JSON string with patterns and parameters)\nlet input;\ntry {\n input = typeof query === 'string' ? JSON.parse(query) : query;\n} catch (e) {\n return JSON.stringify({ error: 'Invalid input format. Expected JSON string.' });\n}\n\nconst { patterns, numberRange = [1, 49], sampleSize = 10000, topN = 6 } = input;\n\nif (!patterns || !Array.isArray(patterns)) {\n return JSON.stringify({ error: 'Patterns array is required' });\n}\n\n// Monte Carlo Simulation Function\nfunction monteCarloSimulation(patterns, iterations, range) {\n const [min, max] = range;\n const frequencyMap = new Map();\n \n for (let i = 0; i < iterations; i++) {\n // Weighted random selection based on historical patterns\n const selectedNumbers = weightedRandomSelection(patterns, min, max, topN);\n \n selectedNumbers.forEach(num => {\n frequencyMap.set(num, (frequencyMap.get(num) || 0) + 1);\n });\n }\n \n return frequencyMap;\n}\n\n// Weighted Random Selection based on historical frequency\nfunction weightedRandomSelection(patterns, min, max, count) {\n const weights = new Map();\n \n // Calculate weights from patterns\n patterns.forEach(pattern => {\n if (pattern.number && pattern.frequency) {\n weights.set(pattern.number, pattern.frequency);\n }\n });\n \n // Normalize weights\n const totalWeight = Array.from(weights.values()).reduce((a, b) => a + b, 0);\n const normalizedWeights = new Map();\n weights.forEach((weight, num) => {\n normalizedWeights.set(num, weight / totalWeight);\n });\n \n // Select numbers based on weights\n const selected = new Set();\n while (selected.size < count) {\n let random = Math.random();\n let cumulative = 0;\n \n for (const [num, weight] of normalizedWeights) {\n cumulative += weight;\n if (random <= cumulative && !selected.has(num)) {\n selected.add(num);\n break;\n }\n }\n \n // Fallback: add random number if selection fails\n if (selected.size < count && Array.from(selected).length === selected.size) {\n const randomNum = Math.floor(Math.random() * (max - min + 1)) + min;\n if (!selected.has(randomNum)) {\n selected.add(randomNum);\n }\n }\n }\n \n return Array.from(selected);\n}\n\n// Bayesian Probability Calculation\nfunction calculateBayesianProbability(number, patterns, priorProbability = 0.02) {\n const pattern = patterns.find(p => p.number === number);\n \n if (!pattern) {\n return priorProbability;\n }\n \n // Likelihood based on frequency\n const totalDraws = patterns.reduce((sum, p) => sum + (p.frequency || 0), 0);\n const likelihood = pattern.frequency / totalDraws;\n \n // Bayesian update: P(A|B) = P(B|A) * P(A) / P(B)\n const posterior = (likelihood * priorProbability) / (likelihood * priorProbability + (1 - likelihood) * (1 - priorProbability));\n \n return posterior;\n}\n\n// Confidence Interval Calculation (95% CI)\nfunction calculateConfidenceInterval(frequency, totalSamples) {\n const proportion = frequency / totalSamples;\n const standardError = Math.sqrt((proportion * (1 - proportion)) / totalSamples);\n const marginOfError = 1.96 * standardError; // 95% confidence\n \n return {\n lower: Math.max(0, proportion - marginOfError),\n upper: Math.min(1, proportion + marginOfError),\n proportion: proportion,\n standardError: standardError\n };\n}\n\n// Calculate Standard Error for prediction\nfunction calculateStandardError(proportion, sampleSize) {\n return Math.sqrt((proportion * (1 - proportion)) / sampleSize);\n}\n\n// Calculate Prediction Interval (95% PI)\nfunction calculatePredictionInterval(proportion, sampleSize) {\n const standardError = calculateStandardError(proportion, sampleSize);\n // Prediction interval is wider than confidence interval\n const predictionError = standardError * Math.sqrt(1 + (1 / sampleSize));\n const marginOfError = 1.96 * predictionError;\n \n return {\n lower: Math.max(0, proportion - marginOfError),\n upper: Math.min(1, proportion + marginOfError),\n predictionError: predictionError\n };\n}\n\n// Calculate Variance Estimate\nfunction calculateVariance(frequency, totalSamples) {\n const proportion = frequency / totalSamples;\n return proportion * (1 - proportion);\n}\n\n// Calculate Uncertainty Score (0-100, where 100 is highest uncertainty)\nfunction calculateUncertaintyScore(proportion, sampleSize, variance) {\n const standardError = calculateStandardError(proportion, sampleSize);\n const coefficientOfVariation = proportion > 0 ? (Math.sqrt(variance) / proportion) : 1;\n \n // Combine multiple uncertainty factors\n const uncertaintyFactors = [\n standardError * 100, // Standard error contribution\n coefficientOfVariation * 20, // Coefficient of variation contribution\n (1 - proportion) * 50 // Inverse proportion contribution\n ];\n \n const rawScore = uncertaintyFactors.reduce((a, b) => a + b, 0) / uncertaintyFactors.length;\n return Math.min(100, Math.max(0, rawScore));\n}\n\n// Run Monte Carlo Simulation\nconst simulationResults = monteCarloSimulation(patterns, sampleSize, numberRange);\n\n// Calculate likelihood scores, confidence intervals, and uncertainty metrics\nconst results = [];\nsimulationResults.forEach((frequency, number) => {\n const bayesianProb = calculateBayesianProbability(number, patterns);\n const confidenceInterval = calculateConfidenceInterval(frequency, sampleSize);\n const predictionInterval = calculatePredictionInterval(frequency / sampleSize, sampleSize);\n const variance = calculateVariance(frequency, sampleSize);\n const standardError = confidenceInterval.standardError;\n const uncertaintyScore = calculateUncertaintyScore(frequency / sampleSize, sampleSize, variance);\n \n results.push({\n number: number,\n frequency: frequency,\n likelihoodScore: (frequency / sampleSize) * 100,\n bayesianProbability: bayesianProb,\n confidenceInterval: {\n lower: (confidenceInterval.lower * 100).toFixed(2) + '%',\n upper: (confidenceInterval.upper * 100).toFixed(2) + '%'\n },\n uncertaintyMetrics: {\n standardError: standardError.toFixed(6),\n standardErrorPercentage: (standardError * 100).toFixed(4) + '%',\n predictionInterval: {\n lower: (predictionInterval.lower * 100).toFixed(2) + '%',\n upper: (predictionInterval.upper * 100).toFixed(2) + '%',\n width: ((predictionInterval.upper - predictionInterval.lower) * 100).toFixed(2) + '%'\n },\n variance: variance.toFixed(6),\n standardDeviation: Math.sqrt(variance).toFixed(6),\n uncertaintyScore: uncertaintyScore.toFixed(2),\n uncertaintyLevel: uncertaintyScore < 30 ? 'Low' : uncertaintyScore < 60 ? 'Moderate' : 'High'\n }\n });\n});\n\n// Sort by likelihood score\nresults.sort((a, b) => b.likelihoodScore - a.likelihoodScore);\n\n// Return top predictions\nconst topPredictions = results.slice(0, topN);\n\n// Calculate aggregate uncertainty metrics\nconst avgUncertaintyScore = topPredictions.reduce((sum, r) => sum + parseFloat(r.uncertaintyMetrics.uncertaintyScore), 0) / topPredictions.length;\nconst avgStandardError = topPredictions.reduce((sum, r) => sum + parseFloat(r.uncertaintyMetrics.standardError), 0) / topPredictions.length;\nconst avgVariance = topPredictions.reduce((sum, r) => sum + parseFloat(r.uncertaintyMetrics.variance), 0) / topPredictions.length;\n\nreturn JSON.stringify({\n predictions: topPredictions,\n simulationParameters: {\n iterations: sampleSize,\n numberRange: numberRange,\n topN: topN\n },\n summary: {\n totalNumbersAnalyzed: results.length,\n averageLikelihood: (results.reduce((sum, r) => sum + r.likelihoodScore, 0) / results.length).toFixed(2) + '%',\n aggregateUncertaintyMetrics: {\n averageUncertaintyScore: avgUncertaintyScore.toFixed(2),\n averageStandardError: avgStandardError.toFixed(6),\n averageVariance: avgVariance.toFixed(6),\n overallUncertaintyLevel: avgUncertaintyScore < 30 ? 'Low' : avgUncertaintyScore < 60 ? 'Moderate' : 'High'\n }\n }\n}, null, 2);\n",
"description": "Performs advanced probabilistic modeling and Monte Carlo simulations on lottery data patterns to generate likelihood scores for number combinations with comprehensive uncertainty metrics"
},
"typeVersion": 1.3
},
{
"id": "9a45cf48-b01d-4002-a498-419c288f13da",
"name": "AI Predictive Analysis Agent",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
4144,
3232
],
"parameters": {
"text": "You are an expert lottery data analyst specializing in Singapore TOTO and 4D games with expertise in ensemble modeling and advanced statistical methods. You have access to multiple analysis streams: statistical pattern mining, Markov chain analysis, time series forecasting, and probabilistic modeling. Use the ensemble_aggregator tool to combine predictions from all models, the cross_validation_scorer to assess model reliability, the probabilistic_modeling tool for Monte Carlo simulations, and the calculator for complex computations. Provide: 1) Ensemble predictions for top 5 TOTO combinations with model agreement scores and uncertainty ranges, 2) Ensemble predictions for top 5 4D numbers with cross-model validation and uncertainty metrics, 3) Model performance comparison and reliability scores, 4) Markov chain transition insights and time series trends, 5) Comprehensive uncertainty assessment across all models with confidence intervals, standard errors, and prediction intervals. CRITICAL: Emphasize that ensemble methods reduce but do not eliminate the inherently HIGH uncertainty in lottery predictions. Clearly communicate that all models show significant uncertainty and past patterns provide no guarantee of future outcomes.",
"options": {},
"promptType": "define"
},
"typeVersion": 3
},
{
"id": "cc73c226-9b93-4c25-861d-bcfd8b041dbd",
"name": "Format Predictions Output",
"type": "n8n-nodes-base.set",
"position": [
4672,
3232
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "id-1",
"name": "analysisDate",
"type": "string",
"value": "={{ $now.format('yyyy-MM-dd HH:mm:ss') }}"
},
{
"id": "id-2",
"name": "predictionType",
"type": "string",
"value": "AI-Driven Statistical Pattern Analysis"
},
{
"id": "id-3",
"name": "disclaimer",
"type": "string",
"value": "These predictions are based on statistical pattern mining and probabilistic modeling with quantified uncertainty metrics. Lottery outcomes are fundamentally random and past patterns do not guarantee future results. All predictions include confidence intervals and uncertainty ranges to reflect the high degree of unpredictability in lottery draws."
},
{
"id": "id-4",
"name": "uncertaintyLevel",
"type": "string",
"value": "HIGH - Lottery outcomes are inherently random with significant uncertainty"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "80783413-ae3e-4ed1-a6c4-a086f48babe2",
"name": "Advanced Markov Chain Analysis - TOTO",
"type": "n8n-nodes-base.code",
"position": [
3552,
3056
],
"parameters": {
"jsCode": "// Advanced Markov Chain Analysis for TOTO Lottery Data\n// Implements transition probability matrices, steady-state distributions, and eigenvalue analysis\n\nconst items = $input.all();\nconst allDraws = [];\n\n// Extract all draw data from statistical pattern mining output\nfor (const item of items) {\n if (item.json.metadata && item.json.metadata.lotteryType === 'TOTO') {\n // This is the pattern mining output, we need to get the raw draws\n // We'll need to access the original data from previous nodes\n continue;\n }\n if (item.json.draws && Array.isArray(item.json.draws)) {\n allDraws.push(...item.json.draws);\n } else if (item.json.numbers) {\n allDraws.push(item.json);\n }\n}\n\n// If we don't have draws, try to get them from the merged data\nif (allDraws.length === 0) {\n const mergedData = $('Merge TOTO Data').all();\n for (const item of mergedData) {\n if (item.json.draws && Array.isArray(item.json.draws)) {\n allDraws.push(...item.json.draws);\n } else if (item.json.numbers) {\n allDraws.push(item.json);\n }\n }\n}\n\nconsole.log(`Processing ${allDraws.length} TOTO draws for Markov Chain analysis`);\n\nif (allDraws.length === 0) {\n return [{ json: { error: 'No draw data available for Markov Chain analysis' } }];\n}\n\n// Sort draws by date to ensure chronological order\nallDraws.sort((a, b) => {\n const dateA = new Date(a.date || a.drawDate || 0);\n const dateB = new Date(b.date || b.drawDate || 0);\n return dateA - dateB;\n});\n\n// Initialize Markov Chain structures\nconst numberRange = 49; // TOTO numbers range from 1 to 49\nconst transitionMatrix = {};\nconst stateFrequency = {};\n\n// Build transition probability matrix\n// State: a number that appeared in draw N\n// Transition: probability that number X appears in draw N+1 given number Y appeared in draw N\n\nfor (let i = 0; i < allDraws.length - 1; i++) {\n const currentDraw = allDraws[i].numbers || allDraws[i].winningNumbers || [];\n const nextDraw = allDraws[i + 1].numbers || allDraws[i + 1].winningNumbers || [];\n \n if (!currentDraw || !nextDraw || currentDraw.length === 0 || nextDraw.length === 0) continue;\n \n // For each number in current draw, track what numbers appear in next draw\n for (const currentNum of currentDraw) {\n if (!transitionMatrix[currentNum]) {\n transitionMatrix[currentNum] = {};\n }\n if (!stateFrequency[currentNum]) {\n stateFrequency[currentNum] = 0;\n }\n stateFrequency[currentNum]++;\n \n for (const nextNum of nextDraw) {\n if (!transitionMatrix[currentNum][nextNum]) {\n transitionMatrix[currentNum][nextNum] = 0;\n }\n transitionMatrix[currentNum][nextNum]++;\n }\n }\n}\n\n// Normalize transition matrix to get probabilities\nconst transitionProbabilities = {};\nfor (const [fromState, transitions] of Object.entries(transitionMatrix)) {\n transitionProbabilities[fromState] = {};\n const totalTransitions = Object.values(transitions).reduce((sum, count) => sum + count, 0);\n \n for (const [toState, count] of Object.entries(transitions)) {\n transitionProbabilities[fromState][toState] = count / totalTransitions;\n }\n}\n\n// Calculate steady-state distribution using power iteration method\nfunction calculateSteadyState(transitionProbs, maxIterations = 1000, tolerance = 1e-6) {\n // Get all unique states\n const states = new Set();\n for (const from of Object.keys(transitionProbs)) {\n states.add(parseInt(from));\n for (const to of Object.keys(transitionProbs[from])) {\n states.add(parseInt(to));\n }\n }\n const stateArray = Array.from(states).sort((a, b) => a - b);\n const n = stateArray.length;\n \n // Initialize uniform distribution\n let distribution = new Array(n).fill(1 / n);\n \n // Power iteration\n for (let iter = 0; iter < maxIterations; iter++) {\n const newDistribution = new Array(n).fill(0);\n \n for (let i = 0; i < n; i++) {\n for (let j = 0; j < n; j++) {\n const fromState = stateArray[j];\n const toState = stateArray[i];\n const prob = transitionProbs[fromState]?.[toState] || 0;\n newDistribution[i] += distribution[j] * prob;\n }\n }\n \n // Check convergence\n const diff = newDistribution.reduce((sum, val, idx) => sum + Math.abs(val - distribution[idx]), 0);\n distribution = newDistribution;\n \n if (diff < tolerance) {\n console.log(`Steady state converged after ${iter + 1} iterations`);\n break;\n }\n }\n \n // Map back to states\n const steadyState = {};\n stateArray.forEach((state, idx) => {\n steadyState[state] = distribution[idx];\n });\n \n return steadyState;\n}\n\nconst steadyStateDistribution = calculateSteadyState(transitionProbabilities);\n\n// Identify most likely state transitions\nconst topTransitions = [];\nfor (const [fromState, transitions] of Object.entries(transitionProbabilities)) {\n for (const [toState, probability] of Object.entries(transitions)) {\n topTransitions.push({\n from: parseInt(fromState),\n to: parseInt(toState),\n probability: probability,\n percentageChance: (probability * 100).toFixed(2) + '%'\n });\n }\n}\ntopTransitions.sort((a, b) => b.probability - a.probability);\n\n// Calculate eigenvalues for convergence analysis (simplified approach)\n// For a full eigenvalue decomposition, we'd need a matrix library\n// Here we approximate using the dominant eigenvalue from power iteration\nfunction estimateDominantEigenvalue(transitionProbs, iterations = 100) {\n const states = Object.keys(transitionProbs).map(s => parseInt(s)).sort((a, b) => a - b);\n const n = states.length;\n \n // Start with random vector\n let vector = new Array(n).fill(1 / Math.sqrt(n));\n let eigenvalue = 0;\n \n for (let iter = 0; iter < iterations; iter++) {\n const newVector = new Array(n).fill(0);\n \n // Matrix-vector multiplication\n for (let i = 0; i < n; i++) {\n for (let j = 0; j < n; j++) {\n const fromState = states[j];\n const toState = states[i];\n const prob = transitionProbs[fromState]?.[toState] || 0;\n newVector[i] += vector[j] * prob;\n }\n }\n \n // Calculate eigenvalue (Rayleigh quotient)\n const numerator = newVector.reduce((sum, val, idx) => sum + val * vector[idx], 0);\n const denominator = vector.reduce((sum, val) => sum + val * val, 0);\n eigenvalue = numerator / denominator;\n \n // Normalize vector\n const norm = Math.sqrt(newVector.reduce((sum, val) => sum + val * val, 0));\n vector = newVector.map(v => v / norm);\n }\n \n return eigenvalue;\n}\n\nconst dominantEigenvalue = estimateDominantEigenvalue(transitionProbabilities);\n\n// Generate next-state predictions based on most recent draw\nconst mostRecentDraw = allDraws[allDraws.length - 1];\nconst recentNumbers = mostRecentDraw.numbers || mostRecentDraw.winningNumbers || [];\n\nconst nextStatePredictions = [];\nconst predictionScores = {};\n\n// For each number in recent draw, get transition probabilities\nfor (const num of recentNumbers) {\n if (transitionProbabilities[num]) {\n for (const [nextNum, prob] of Object.entries(transitionProbabilities[num])) {\n if (!predictionScores[nextNum]) {\n predictionScores[nextNum] = [];\n }\n predictionScores[nextNum].push(prob);\n }\n }\n}\n\n// Average the probabilities for each predicted number\nfor (const [num, probs] of Object.entries(predictionScores)) {\n const avgProb = probs.reduce((sum, p) => sum + p, 0) / probs.length;\n nextStatePredictions.push({\n number: parseInt(num),\n transitionProbability: avgProb,\n percentageChance: (avgProb * 100).toFixed(2) + '%',\n confidenceLevel: avgProb > 0.15 ? 'High' : avgProb > 0.08 ? 'Medium' : 'Low',\n basedOnTransitions: probs.length\n });\n}\n\nnextStatePredictions.sort((a, b) => b.transitionProbability - a.transitionProbability);\n\n// Calculate convergence metrics\nconst convergenceRate = Math.abs(1 - dominantEigenvalue);\nconst mixingTime = convergenceRate > 0 ? Math.log(0.01) / Math.log(dominantEigenvalue) : Infinity;\n\n// Prepare comprehensive output\nconst analysis = {\n metadata: {\n analysisType: 'Markov Chain Analysis',\n lotteryType: 'TOTO',\n totalDrawsAnalyzed: allDraws.length,\n transitionPairsAnalyzed: allDraws.length - 1,\n analysisDate: new Date().toISOString(),\n mostRecentDrawDate: mostRecentDraw.date || mostRecentDraw.drawDate\n },\n transitionMatrix: {\n totalStates: Object.keys(transitionProbabilities).length,\n totalTransitions: topTransitions.length,\n description: 'Probability of transitioning from one number state to another between consecutive draws'\n },\n steadyStateDistribution: {\n description: 'Long-term equilibrium probabilities for each number',\n topNumbers: Object.entries(steadyStateDistribution)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 15)\n .map(([num, prob]) => ({\n number: parseInt(num),\n steadyStateProbability: prob,\n percentageChance: (prob * 100).toFixed(4) + '%'\n }))\n },\n mostLikelyTransitions: {\n description: 'Highest probability state transitions between consecutive draws',\n top20Transitions: topTransitions.slice(0, 20)\n },\n eigenvalueAnalysis: {\n dominantEigenvalue: dominantEigenvalue.toFixed(6),\n convergenceRate: convergenceRate.toFixed(6),\n estimatedMixingTime: mixingTime === Infinity ? 'Does not converge' : mixingTime.toFixed(2) + ' iterations',\n interpretation: dominantEigenvalue > 0.95 \n ? 'Chain converges slowly to steady state' \n : 'Chain converges relatively quickly to steady state'\n },\n nextStatePredictions: {\n description: 'Predicted numbers for next draw based on Markov transitions from most recent draw',\n recentDrawNumbers: recentNumbers,\n top15Predictions: nextStatePredictions.slice(0, 15)\n },\n statisticalInsights: {\n averageTransitionProbability: (topTransitions.reduce((sum, t) => sum + t.probability, 0) / topTransitions.length).toFixed(6),\n maxTransitionProbability: topTransitions[0]?.probability.toFixed(6),\n minTransitionProbability: topTransitions[topTransitions.length - 1]?.probability.toFixed(6),\n stateSpaceSize: Object.keys(transitionProbabilities).length\n },\n uncertaintyMetrics: {\n modelType: 'First-order Markov Chain',\n assumptions: [\n 'Memoryless property: next state depends only on current state',\n 'Stationary transition probabilities',\n 'Time-homogeneous process'\n ],\n limitations: [\n 'Lottery draws are fundamentally random and independent',\n 'Historical patterns may not predict future outcomes',\n 'Transition probabilities are estimates with sampling uncertainty'\n ],\n confidenceNote: 'Predictions are probabilistic estimates based on historical transition patterns and should not be interpreted as guarantees'\n }\n};\n\nreturn [{ json: analysis }];"
},
"typeVersion": 2
},
{
"id": "f7f08654-afc4-4228-a1bb-1a37710beb44",
"name": "Advanced Markov Chain Analysis - 4D",
"type": "n8n-nodes-base.code",
"position": [
3536,
3440
],
"parameters": {
"jsCode": "// Advanced Markov Chain Analysis for 4D Lottery Data\n// Builds transition matrices, calculates stationary distributions, and predicts next digits\n\nconst items = $input.all();\nconst allDraws = [];\n\n// Extract all draw data\nfor (const item of items) {\n if (item.json.draws && Array.isArray(item.json.draws)) {\n allDraws.push(...item.json.draws);\n } else if (item.json.number || item.json.winningNumber) {\n allDraws.push(item.json);\n }\n}\n\nconsole.log(`Processing ${allDraws.length} 4D draws for Markov Chain analysis`);\n\n// Initialize transition matrices for each digit position (0-3)\nconst transitionMatrices = [\n {}, // Position 0 (first digit)\n {}, // Position 1 (second digit)\n {}, // Position 2 (third digit)\n {} // Position 3 (fourth digit)\n];\n\n// Initialize transition counts\nfor (let pos = 0; pos < 4; pos++) {\n for (let i = 0; i <= 9; i++) {\n transitionMatrices[pos][i] = {};\n for (let j = 0; j <= 9; j++) {\n transitionMatrices[pos][i][j] = 0;\n }\n }\n}\n\n// Build transition matrices by analyzing sequential draws\nfor (let i = 0; i < allDraws.length - 1; i++) {\n const currentDraw = allDraws[i];\n const nextDraw = allDraws[i + 1];\n \n const currentNumber = (currentDraw.number || currentDraw.winningNumber || '').toString().padStart(4, '0');\n const nextNumber = (nextDraw.number || nextDraw.winningNumber || '').toString().padStart(4, '0');\n \n if (currentNumber.length === 4 && nextNumber.length === 4) {\n // Track transitions for each digit position\n for (let pos = 0; pos < 4; pos++) {\n const currentDigit = parseInt(currentNumber[pos]);\n const nextDigit = parseInt(nextNumber[pos]);\n \n if (!isNaN(currentDigit) && !isNaN(nextDigit)) {\n transitionMatrices[pos][currentDigit][nextDigit]++;\n }\n }\n }\n}\n\n// Convert counts to probabilities\nconst transitionProbabilities = [];\nfor (let pos = 0; pos < 4; pos++) {\n const probMatrix = {};\n \n for (let i = 0; i <= 9; i++) {\n probMatrix[i] = {};\n const rowSum = Object.values(transitionMatrices[pos][i]).reduce((a, b) => a + b, 0);\n \n for (let j = 0; j <= 9; j++) {\n probMatrix[i][j] = rowSum > 0 ? transitionMatrices[pos][i][j] / rowSum : 0.1; // Uniform prior if no data\n }\n }\n \n transitionProbabilities.push(probMatrix);\n}\n\n// Calculate stationary distribution using power iteration method\nfunction calculateStationaryDistribution(transitionMatrix, maxIterations = 1000, tolerance = 1e-6) {\n // Initialize with uniform distribution\n let distribution = {};\n for (let i = 0; i <= 9; i++) {\n distribution[i] = 0.1;\n }\n \n for (let iter = 0; iter < maxIterations; iter++) {\n const newDistribution = {};\n \n // Matrix multiplication: \u03c0 = \u03c0 * P\n for (let j = 0; j <= 9; j++) {\n newDistribution[j] = 0;\n for (let i = 0; i <= 9; i++) {\n newDistribution[j] += distribution[i] * transitionMatrix[i][j];\n }\n }\n \n // Check convergence\n let maxDiff = 0;\n for (let i = 0; i <= 9; i++) {\n maxDiff = Math.max(maxDiff, Math.abs(newDistribution[i] - distribution[i]));\n }\n \n distribution = newDistribution;\n \n if (maxDiff < tolerance) {\n return { distribution, converged: true, iterations: iter + 1 };\n }\n }\n \n return { distribution, converged: false, iterations: maxIterations };\n}\n\n// Calculate stationary distributions for each position\nconst stationaryDistributions = [];\nfor (let pos = 0; pos < 4; pos++) {\n const result = calculateStationaryDistribution(transitionProbabilities[pos]);\n stationaryDistributions.push(result);\n}\n\n// Identify high-probability transitions (top 5 for each position)\nconst highProbabilityTransitions = [];\nfor (let pos = 0; pos < 4; pos++) {\n const transitions = [];\n \n for (let i = 0; i <= 9; i++) {\n for (let j = 0; j <= 9; j++) {\n transitions.push({\n from: i,\n to: j,\n probability: transitionProbabilities[pos][i][j],\n count: transitionMatrices[pos][i][j]\n });\n }\n }\n \n transitions.sort((a, b) => b.probability - a.probability);\n highProbabilityTransitions.push(transitions.slice(0, 10));\n}\n\n// Calculate convergence metrics\nfunction calculateConvergenceMetrics(transitionMatrix) {\n // Calculate eigenvalues (simplified: use power method for dominant eigenvalue)\n let vector = {};\n for (let i = 0; i <= 9; i++) {\n vector[i] = 1;\n }\n \n for (let iter = 0; iter < 100; iter++) {\n const newVector = {};\n for (let j = 0; j <= 9; j++) {\n newVector[j] = 0;\n for (let i = 0; i <= 9; i++) {\n newVector[j] += vector[i] * transitionMatrix[i][j];\n }\n }\n \n // Normalize\n const norm = Math.sqrt(Object.values(newVector).reduce((sum, val) => sum + val * val, 0));\n for (let i = 0; i <= 9; i++) {\n vector[i] = newVector[i] / norm;\n }\n }\n \n // Estimate mixing time (simplified)\n const mixingTime = Math.ceil(Math.log(0.01) / Math.log(0.9)); // Rough estimate\n \n return {\n estimatedMixingTime: mixingTime,\n isErgodic: true // Simplified assumption for lottery data\n };\n}\n\nconst convergenceMetrics = [];\nfor (let pos = 0; pos < 4; pos++) {\n convergenceMetrics.push(calculateConvergenceMetrics(transitionProbabilities[pos]));\n}\n\n// Predict next digits based on transition probabilities\nfunction predictNextDigit(transitionMatrix, currentDigit) {\n const probabilities = transitionMatrix[currentDigit];\n const predictions = [];\n \n for (let digit = 0; digit <= 9; digit++) {\n predictions.push({\n digit: digit,\n probability: probabilities[digit],\n percentage: (probabilities[digit] * 100).toFixed(2) + '%'\n });\n }\n \n predictions.sort((a, b) => b.probability - a.probability);\n return predictions;\n}\n\n// Get the most recent draw for prediction\nconst mostRecentDraw = allDraws[allDraws.length - 1];\nconst mostRecentNumber = (mostRecentDraw.number || mostRecentDraw.winningNumber || '').toString().padStart(4, '0');\n\nconst nextDigitPredictions = [];\nif (mostRecentNumber.length === 4) {\n for (let pos = 0; pos < 4; pos++) {\n const currentDigit = parseInt(mostRecentNumber[pos]);\n if (!isNaN(currentDigit)) {\n nextDigitPredictions.push({\n position: pos,\n currentDigit: currentDigit,\n predictions: predictNextDigit(transitionProbabilities[pos], currentDigit).slice(0, 5)\n });\n }\n }\n}\n\n// Generate complete 4D number predictions based on highest probabilities\nconst complete4DPredictions = [];\nfor (let i = 0; i < 5; i++) {\n let predictedNumber = '';\n let combinedProbability = 1;\n \n for (let pos = 0; pos < 4; pos++) {\n const currentDigit = mostRecentNumber.length === 4 ? parseInt(mostRecentNumber[pos]) : 0;\n const predictions = predictNextDigit(transitionProbabilities[pos], currentDigit);\n \n // Select i-th most probable digit for variety\n const selectedPrediction = predictions[i] || predictions[0];\n predictedNumber += selectedPrediction.digit;\n combinedProbability *= selectedPrediction.probability;\n }\n \n complete4DPredictions.push({\n number: predictedNumber,\n combinedProbability: combinedProbability,\n probabilityPercentage: (combinedProbability * 100).toFixed(6) + '%',\n confidenceScore: (combinedProbability * 100).toFixed(2)\n });\n}\n\n// Prepare output\nconst analysis = {\n metadata: {\n totalDrawsAnalyzed: allDraws.length,\n analysisDate: new Date().toISOString(),\n lotteryType: '4D',\n mostRecentDraw: mostRecentNumber\n },\n transitionMatrices: {\n description: 'Transition probability matrices for each digit position',\n positions: transitionProbabilities.map((matrix, pos) => ({\n position: pos,\n matrix: matrix\n }))\n },\n stationaryDistributions: stationaryDistributions.map((result, pos) => ({\n position: pos,\n distribution: Object.entries(result.distribution)\n .map(([digit, prob]) => ({\n digit: parseInt(digit),\n probability: prob,\n percentage: (prob * 100).toFixed(2) + '%'\n }))\n .sort((a, b) => b.probability - a.probability),\n converged: result.converged,\n iterations: result.iterations\n })),\n highProbabilityTransitions: highProbabilityTransitions.map((transitions, pos) => ({\n position: pos,\n topTransitions: transitions.map(t => ({\n from: t.from,\n to: t.to,\n probability: t.probability,\n percentage: (t.probability * 100).toFixed(2) + '%',\n observedCount: t.count\n }))\n })),\n convergenceMetrics: convergenceMetrics.map((metrics, pos) => ({\n position: pos,\n ...metrics\n })),\n nextDigitPredictions: nextDigitPredictions,\n complete4DPredictions: complete4DPredictions,\n insights: {\n mostStablePosition: stationaryDistributions.reduce((min, curr, idx) => \n curr.iterations < stationaryDistributions[min].iterations ? idx : min, 0),\n averageConvergenceIterations: (stationaryDistributions.reduce((sum, r) => sum + r.iterations, 0) / 4).toFixed(2),\n topPredictedNumber: complete4DPredictions[0]?.number,\n predictionConfidence: complete4DPredictions[0]?.confidenceScore\n }\n};\n\nreturn [{ json: analysis }];"
},
"typeVersion": 2
},
{
"id": "61b5bf26-7eee-4697-9acc-b3869bf1313b",
"name": "Time Series Forecasting - TOTO",
"type": "n8n-nodes-base.cod
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
openAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
A scheduled trigger initiates automated retrieval of TOTO/4D data, including both current and historical records. The datasets are merged and validated to ensure structural consistency before branching into parallel analytical pipelines. One track performs pattern mining and…
Source: https://n8n.io/workflows/10890/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow automates end-to-end ESG (Environmental, Social, and Governance) sustainability reporting for enterprise sustainability teams, compliance officers, and green governance leads. It solves
This workflow automates energy portfolio governance for energy managers, sustainability teams, and policy compliance officers. It eliminates the manual effort of aggregating multi-source energy data,
The workflow runs on a monthly trigger to collect both current-year and multi-year historical HDB data. Once fetched, all datasets are merged with aligned fields to produce a unified table. The system
This workflow automates weekly capital expenditure (CAPEX) forecasting for property portfolios using a multi-agent AI architecture. It targets property managers, asset managers, and facilities finance
Intelligent workforce analytics and talent strategy report automation