{
  "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.code",
      "position": [
        3552,
        3248
      ],
      "parameters": {
        "jsCode": "// Time Series Forecasting for TOTO Data\n// Implements Holt-Winters Exponential Smoothing, Moving Averages, Seasonal Decomposition,\n// Autocorrelation Analysis, Trend Detection, and Forecasting with Prediction Intervals\n\nconst items = $input.all();\nconst allDraws = [];\n\n// Extract all draw data with temporal information\nfor (const item of items) {\n  if (item.json.metadata && item.json.metadata.lotteryType === 'TOTO') {\n    // This is the output from Statistical Pattern Mining\n    // We need to get the original draw data\n    continue;\n  }\n  \n  if (item.json.allDraws && Array.isArray(item.json.allDraws)) {\n    allDraws.push(...item.json.allDraws);\n  } else 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 time series forecasting`);\n\nif (allDraws.length === 0) {\n  return [{ json: { error: 'No draw data available for time series analysis' } }];\n}\n\n// Sort draws by date\nallDraws.sort((a, b) => {\n  const dateA = new Date(a.date || a.drawDate);\n  const dateB = new Date(b.date || b.drawDate);\n  return dateA - dateB;\n});\n\n// Prepare time series data (using sum of numbers as the primary metric)\nconst timeSeries = allDraws.map(draw => {\n  const numbers = draw.numbers || draw.winningNumbers || [];\n  const sum = numbers.reduce((acc, num) => acc + num, 0);\n  const date = new Date(draw.date || draw.drawDate);\n  return {\n    date: date,\n    value: sum,\n    numbers: numbers\n  };\n});\n\n// ===== MOVING AVERAGES =====\nfunction calculateMovingAverage(data, windowSize) {\n  const result = [];\n  for (let i = 0; i < data.length; i++) {\n    if (i < windowSize - 1) {\n      result.push(null);\n    } else {\n      const window = data.slice(i - windowSize + 1, i + 1);\n      const avg = window.reduce((sum, val) => sum + val, 0) / windowSize;\n      result.push(avg);\n    }\n  }\n  return result;\n}\n\nconst values = timeSeries.map(d => d.value);\nconst ma3 = calculateMovingAverage(values, 3);\nconst ma5 = calculateMovingAverage(values, 5);\nconst ma10 = calculateMovingAverage(values, 10);\nconst ma20 = calculateMovingAverage(values, 20);\n\n// ===== EXPONENTIAL SMOOTHING (Simple) =====\nfunction simpleExponentialSmoothing(data, alpha = 0.3) {\n  const smoothed = [data[0]];\n  for (let i = 1; i < data.length; i++) {\n    smoothed.push(alpha * data[i] + (1 - alpha) * smoothed[i - 1]);\n  }\n  return smoothed;\n}\n\nconst ses = simpleExponentialSmoothing(values, 0.3);\n\n// ===== HOLT-WINTERS EXPONENTIAL SMOOTHING =====\nfunction holtWintersSmoothing(data, alpha = 0.3, beta = 0.1, gamma = 0.1, seasonLength = 12) {\n  const n = data.length;\n  const level = new Array(n);\n  const trend = new Array(n);\n  const seasonal = new Array(n);\n  const forecast = new Array(n);\n  \n  // Initialize\n  level[0] = data[0];\n  trend[0] = data[1] - data[0];\n  \n  // Initialize seasonal components\n  for (let i = 0; i < seasonLength; i++) {\n    seasonal[i] = data[i] / (level[0] + trend[0] * i);\n  }\n  \n  // Holt-Winters equations\n  for (let i = 1; i < n; i++) {\n    const seasonalIndex = i % seasonLength;\n    \n    if (i >= seasonLength) {\n      level[i] = alpha * (data[i] / seasonal[i - seasonLength]) + (1 - alpha) * (level[i - 1] + trend[i - 1]);\n      trend[i] = beta * (level[i] - level[i - 1]) + (1 - beta) * trend[i - 1];\n      seasonal[i] = gamma * (data[i] / level[i]) + (1 - gamma) * seasonal[i - seasonLength];\n      forecast[i] = (level[i - 1] + trend[i - 1]) * seasonal[i - seasonLength];\n    } else {\n      level[i] = alpha * data[i] + (1 - alpha) * (level[i - 1] + trend[i - 1]);\n      trend[i] = beta * (level[i] - level[i - 1]) + (1 - beta) * trend[i - 1];\n      forecast[i] = level[i - 1] + trend[i - 1];\n    }\n  }\n  \n  return { level, trend, seasonal, forecast };\n}\n\nconst hwResult = holtWintersSmoothing(values, 0.3, 0.1, 0.1, 12);\n\n// ===== AUTOCORRELATION FUNCTION (ACF) =====\nfunction calculateAutocorrelation(data, maxLag = 20) {\n  const n = data.length;\n  const mean = data.reduce((sum, val) => sum + val, 0) / n;\n  const variance = data.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / n;\n  \n  const acf = [];\n  for (let lag = 0; lag <= maxLag; lag++) {\n    let sum = 0;\n    for (let i = 0; i < n - lag; i++) {\n      sum += (data[i] - mean) * (data[i + lag] - mean);\n    }\n    acf.push({\n      lag: lag,\n      correlation: sum / (n * variance),\n      isSignificant: Math.abs(sum / (n * variance)) > 1.96 / Math.sqrt(n)\n    });\n  }\n  return acf;\n}\n\nconst acf = calculateAutocorrelation(values, 20);\n\n// ===== SEASONAL DECOMPOSITION =====\nfunction seasonalDecomposition(data, period = 12) {\n  const n = data.length;\n  const trend = calculateMovingAverage(data, period);\n  const detrended = data.map((val, i) => trend[i] ? val - trend[i] : null);\n  \n  // Calculate seasonal component\n  const seasonalAverages = new Array(period).fill(0);\n  const seasonalCounts = new Array(period).fill(0);\n  \n  detrended.forEach((val, i) => {\n    if (val !== null) {\n      const seasonIndex = i % period;\n      seasonalAverages[seasonIndex] += val;\n      seasonalCounts[seasonIndex]++;\n    }\n  });\n  \n  const seasonal = seasonalAverages.map((sum, i) => \n    seasonalCounts[i] > 0 ? sum / seasonalCounts[i] : 0\n  );\n  \n  // Normalize seasonal component\n  const seasonalMean = seasonal.reduce((sum, val) => sum + val, 0) / period;\n  const normalizedSeasonal = seasonal.map(val => val - seasonalMean);\n  \n  // Calculate residual\n  const residual = data.map((val, i) => {\n    const trendVal = trend[i] || 0;\n    const seasonalVal = normalizedSeasonal[i % period];\n    return val - trendVal - seasonalVal;\n  });\n  \n  return {\n    trend: trend,\n    seasonal: normalizedSeasonal,\n    residual: residual\n  };\n}\n\nconst decomposition = seasonalDecomposition(values, 12);\n\n// ===== TREND DETECTION =====\nfunction detectTrend(data) {\n  const n = data.length;\n  const x = Array.from({ length: n }, (_, i) => i);\n  const y = data;\n  \n  // Linear regression\n  const xMean = x.reduce((sum, val) => sum + val, 0) / n;\n  const yMean = y.reduce((sum, val) => sum + val, 0) / n;\n  \n  let numerator = 0;\n  let denominator = 0;\n  for (let i = 0; i < n; i++) {\n    numerator += (x[i] - xMean) * (y[i] - yMean);\n    denominator += Math.pow(x[i] - xMean, 2);\n  }\n  \n  const slope = numerator / denominator;\n  const intercept = yMean - slope * xMean;\n  \n  // Calculate R-squared\n  const yPred = x.map(xi => slope * xi + intercept);\n  const ssRes = y.reduce((sum, yi, i) => sum + Math.pow(yi - yPred[i], 2), 0);\n  const ssTot = y.reduce((sum, yi) => sum + Math.pow(yi - yMean, 2), 0);\n  const rSquared = 1 - (ssRes / ssTot);\n  \n  return {\n    slope: slope,\n    intercept: intercept,\n    rSquared: rSquared,\n    direction: slope > 0 ? 'increasing' : slope < 0 ? 'decreasing' : 'stable',\n    strength: Math.abs(rSquared) > 0.7 ? 'strong' : Math.abs(rSquared) > 0.4 ? 'moderate' : 'weak'\n  };\n}\n\nconst trendAnalysis = detectTrend(values);\n\n// ===== FORECASTING WITH PREDICTION INTERVALS =====\nfunction generateForecast(data, hwResult, periods = 5) {\n  const n = data.length;\n  const lastLevel = hwResult.level[n - 1];\n  const lastTrend = hwResult.trend[n - 1];\n  const seasonLength = 12;\n  \n  // Calculate forecast error variance\n  const errors = [];\n  for (let i = seasonLength; i < n; i++) {\n    errors.push(data[i] - hwResult.forecast[i]);\n  }\n  const errorMean = errors.reduce((sum, e) => sum + e, 0) / errors.length;\n  const errorVariance = errors.reduce((sum, e) => sum + Math.pow(e - errorMean, 2), 0) / errors.length;\n  const errorStdDev = Math.sqrt(errorVariance);\n  \n  const forecasts = [];\n  for (let h = 1; h <= periods; h++) {\n    const seasonalIndex = (n + h - 1) % seasonLength;\n    const pointForecast = (lastLevel + h * lastTrend) * hwResult.seasonal[seasonalIndex];\n    \n    // Prediction interval (95% confidence)\n    const predictionError = errorStdDev * Math.sqrt(1 + (1 / n));\n    const marginOfError = 1.96 * predictionError * Math.sqrt(h);\n    \n    forecasts.push({\n      period: h,\n      pointForecast: pointForecast,\n      predictionInterval: {\n        lower: pointForecast - marginOfError,\n        upper: pointForecast + marginOfError,\n        width: 2 * marginOfError\n      },\n      standardError: predictionError,\n      uncertaintyLevel: marginOfError / pointForecast > 0.3 ? 'High' : marginOfError / pointForecast > 0.15 ? 'Moderate' : 'Low'\n    });\n  }\n  \n  return forecasts;\n}\n\nconst forecasts = generateForecast(values, hwResult, 5);\n\n// ===== COMPILE RESULTS =====\nconst analysis = {\n  metadata: {\n    totalDrawsAnalyzed: allDraws.length,\n    analysisDate: new Date().toISOString(),\n    lotteryType: 'TOTO',\n    analysisMethod: 'Time Series Forecasting'\n  },\n  movingAverages: {\n    ma3: ma3.filter(v => v !== null).slice(-10),\n    ma5: ma5.filter(v => v !== null).slice(-10),\n    ma10: ma10.filter(v => v !== null).slice(-10),\n    ma20: ma20.filter(v => v !== null).slice(-10),\n    description: 'Moving averages with window sizes 3, 5, 10, and 20 (last 10 values)'\n  },\n  exponentialSmoothing: {\n    simpleSmoothing: ses.slice(-10),\n    holtWinters: {\n      lastLevel: hwResult.level[hwResult.level.length - 1],\n      lastTrend: hwResult.trend[hwResult.trend.length - 1],\n      recentForecasts: hwResult.forecast.slice(-10)\n    }\n  },\n  autocorrelation: {\n    acfValues: acf,\n    significantLags: acf.filter(a => a.isSignificant && a.lag > 0).map(a => a.lag),\n    interpretation: acf.filter(a => a.isSignificant && a.lag > 0).length > 0 \n      ? 'Significant autocorrelation detected, indicating temporal dependencies'\n      : 'No significant autocorrelation detected'\n  },\n  seasonalDecomposition: {\n    seasonalPattern: decomposition.seasonal,\n    trendComponent: decomposition.trend.filter(v => v !== null).slice(-10),\n    residualStats: {\n      mean: (decomposition.residual.reduce((sum, v) => sum + v, 0) / decomposition.residual.length).toFixed(2),\n      stdDev: Math.sqrt(decomposition.residual.reduce((sum, v) => sum + Math.pow(v, 2), 0) / decomposition.residual.length).toFixed(2)\n    }\n  },\n  trendAnalysis: {\n    slope: trendAnalysis.slope.toFixed(4),\n    intercept: trendAnalysis.intercept.toFixed(2),\n    rSquared: trendAnalysis.rSquared.toFixed(4),\n    direction: trendAnalysis.direction,\n    strength: trendAnalysis.strength,\n    interpretation: `The time series shows a ${trendAnalysis.strength} ${trendAnalysis.direction} trend (R\u00b2 = ${trendAnalysis.rSquared.toFixed(4)})`\n  },\n  forecasts: {\n    nextPeriods: forecasts,\n    methodology: 'Holt-Winters Exponential Smoothing with 95% Prediction Intervals',\n    uncertaintyNote: 'Prediction intervals widen with forecast horizon, reflecting increased uncertainty'\n  },\n  insights: {\n    averageValue: (values.reduce((sum, v) => sum + v, 0) / values.length).toFixed(2),\n    volatility: (Math.sqrt(values.reduce((sum, v) => sum + Math.pow(v - values.reduce((s, val) => s + val, 0) / values.length, 2), 0) / values.length)).toFixed(2),\n    recentTrend: ma5[ma5.length - 1] > ma20[ma20.length - 1] ? 'upward' : 'downward',\n    forecastReliability: forecasts[0].uncertaintyLevel\n  }\n};\n\nreturn [{ json: analysis }];"
      },
      "typeVersion": 2
    },
    {
      "id": "ae6cec0a-6196-480a-a04b-0af1ff802ac9",
      "name": "Time Series Forecasting - 4D",
      "type": "n8n-nodes-base.code",
      "position": [
        3536,
        3632
      ],
      "parameters": {
        "jsCode": "// Time Series Forecasting for 4D Data\n// ARIMA-style analysis with rolling statistics, periodicity detection, and confidence bands\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) {\n    allDraws.push(item.json);\n  }\n}\n\n// Sort by date\nallDraws.sort((a, b) => {\n  const dateA = new Date(a.date || a.drawDate);\n  const dateB = new Date(b.date || b.drawDate);\n  return dateA - dateB;\n});\n\nconsole.log(`Processing ${allDraws.length} 4D draws for time series forecasting`);\n\n// Extract time series data\nconst timeSeries = allDraws.map(draw => {\n  const number = draw.number || draw.winningNumber;\n  const date = new Date(draw.date || draw.drawDate);\n  return {\n    date: date,\n    timestamp: date.getTime(),\n    number: parseInt(number),\n    dayOfWeek: date.getDay(),\n    month: date.getMonth(),\n    year: date.getFullYear()\n  };\n}).filter(d => d.number && !isNaN(d.number));\n\nif (timeSeries.length === 0) {\n  return [{ json: { error: 'No valid time series data found' } }];\n}\n\n// Utility: Calculate mean\nfunction mean(arr) {\n  return arr.reduce((a, b) => a + b, 0) / arr.length;\n}\n\n// Utility: Calculate standard deviation\nfunction stdDev(arr) {\n  const avg = mean(arr);\n  const squareDiffs = arr.map(value => Math.pow(value - avg, 2));\n  return Math.sqrt(mean(squareDiffs));\n}\n\n// Utility: Calculate autocorrelation at lag k\nfunction autocorrelation(series, lag) {\n  const n = series.length;\n  const avg = mean(series);\n  \n  let numerator = 0;\n  let denominator = 0;\n  \n  for (let i = 0; i < n - lag; i++) {\n    numerator += (series[i] - avg) * (series[i + lag] - avg);\n  }\n  \n  for (let i = 0; i < n; i++) {\n    denominator += Math.pow(series[i] - avg, 2);\n  }\n  \n  return denominator !== 0 ? numerator / denominator : 0;\n}\n\n// Utility: Calculate rolling statistics\nfunction rollingStatistics(series, windowSize) {\n  const results = [];\n  \n  for (let i = windowSize - 1; i < series.length; i++) {\n    const window = series.slice(i - windowSize + 1, i + 1);\n    results.push({\n      index: i,\n      mean: mean(window),\n      stdDev: stdDev(window),\n      min: Math.min(...window),\n      max: Math.max(...window)\n    });\n  }\n  \n  return results;\n}\n\n// Utility: Detect periodicity using FFT approximation (simplified)\nfunction detectPeriodicity(series, maxLag = 50) {\n  const correlations = [];\n  \n  for (let lag = 1; lag <= Math.min(maxLag, Math.floor(series.length / 2)); lag++) {\n    const acf = autocorrelation(series, lag);\n    correlations.push({ lag, correlation: acf });\n  }\n  \n  // Find peaks in autocorrelation\n  const peaks = [];\n  for (let i = 1; i < correlations.length - 1; i++) {\n    if (correlations[i].correlation > correlations[i - 1].correlation &&\n        correlations[i].correlation > correlations[i + 1].correlation &&\n        correlations[i].correlation > 0.1) {\n      peaks.push(correlations[i]);\n    }\n  }\n  \n  return { correlations, peaks };\n}\n\n// Utility: Simple moving average\nfunction simpleMovingAverage(series, period) {\n  const result = [];\n  for (let i = period - 1; i < series.length; i++) {\n    const window = series.slice(i - period + 1, i + 1);\n    result.push(mean(window));\n  }\n  return result;\n}\n\n// Utility: Exponential moving average\nfunction exponentialMovingAverage(series, alpha = 0.3) {\n  const result = [series[0]];\n  for (let i = 1; i < series.length; i++) {\n    result.push(alpha * series[i] + (1 - alpha) * result[i - 1]);\n  }\n  return result;\n}\n\n// Utility: Calculate trend using linear regression\nfunction calculateTrend(series) {\n  const n = series.length;\n  const x = Array.from({ length: n }, (_, i) => i);\n  const y = series;\n  \n  const sumX = x.reduce((a, b) => a + b, 0);\n  const sumY = y.reduce((a, b) => a + b, 0);\n  const sumXY = x.reduce((sum, xi, i) => sum + xi * y[i], 0);\n  const sumX2 = x.reduce((sum, xi) => sum + xi * xi, 0);\n  \n  const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);\n  const intercept = (sumY - slope * sumX) / n;\n  \n  return { slope, intercept, trend: x.map(xi => slope * xi + intercept) };\n}\n\n// Utility: Detrend series\nfunction detrend(series, trend) {\n  return series.map((val, i) => val - trend[i]);\n}\n\n// Utility: Calculate confidence interval\nfunction confidenceInterval(value, stdError, confidence = 1.96) {\n  return {\n    lower: value - confidence * stdError,\n    upper: value + confidence * stdError\n  };\n}\n\n// Extract number series\nconst numberSeries = timeSeries.map(d => d.number);\n\n// 1. Rolling Statistics (30-draw window)\nconst windowSize = Math.min(30, Math.floor(numberSeries.length / 3));\nconst rollingStats = rollingStatistics(numberSeries, windowSize);\n\n// 2. Trend Analysis\nconst trendAnalysis = calculateTrend(numberSeries);\nconst detrendedSeries = detrend(numberSeries, trendAnalysis.trend);\n\n// 3. Moving Averages\nconst sma10 = simpleMovingAverage(numberSeries, Math.min(10, numberSeries.length));\nconst sma30 = simpleMovingAverage(numberSeries, Math.min(30, numberSeries.length));\nconst ema = exponentialMovingAverage(numberSeries, 0.3);\n\n// 4. Autocorrelation and Lag Analysis\nconst maxLag = Math.min(50, Math.floor(numberSeries.length / 2));\nconst lagCorrelations = [];\nfor (let lag = 1; lag <= maxLag; lag++) {\n  lagCorrelations.push({\n    lag,\n    correlation: autocorrelation(numberSeries, lag)\n  });\n}\n\n// 5. Periodicity Detection\nconst periodicityAnalysis = detectPeriodicity(numberSeries, maxLag);\n\n// 6. Seasonality Analysis (by day of week and month)\nconst seasonalityByDay = {};\nconst seasonalityByMonth = {};\n\ntimeSeries.forEach(d => {\n  if (!seasonalityByDay[d.dayOfWeek]) {\n    seasonalityByDay[d.dayOfWeek] = [];\n  }\n  seasonalityByDay[d.dayOfWeek].push(d.number);\n  \n  if (!seasonalityByMonth[d.month]) {\n    seasonalityByMonth[d.month] = [];\n  }\n  seasonalityByMonth[d.month].push(d.number);\n});\n\nconst dayOfWeekStats = Object.entries(seasonalityByDay).map(([day, numbers]) => ({\n  dayOfWeek: parseInt(day),\n  dayName: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][day],\n  count: numbers.length,\n  mean: mean(numbers).toFixed(2),\n  stdDev: stdDev(numbers).toFixed(2)\n}));\n\nconst monthStats = Object.entries(seasonalityByMonth).map(([month, numbers]) => ({\n  month: parseInt(month) + 1,\n  monthName: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][month],\n  count: numbers.length,\n  mean: mean(numbers).toFixed(2),\n  stdDev: stdDev(numbers).toFixed(2)\n}));\n\n// 7. Cyclical Pattern Detection\nconst significantLags = lagCorrelations\n  .filter(l => Math.abs(l.correlation) > 0.15)\n  .sort((a, b) => Math.abs(b.correlation) - Math.abs(a.correlation))\n  .slice(0, 10);\n\n// 8. ARIMA-style Forecast (simplified AR model)\nfunction forecastAR(series, order = 3, steps = 5) {\n  // Calculate AR coefficients using Yule-Walker equations (simplified)\n  const coefficients = [];\n  for (let i = 1; i <= order; i++) {\n    coefficients.push(autocorrelation(series, i));\n  }\n  \n  const forecasts = [];\n  const recentValues = series.slice(-order);\n  \n  for (let step = 0; step < steps; step++) {\n    let forecast = mean(series); // Base forecast on mean\n    \n    for (let i = 0; i < order && i < recentValues.length; i++) {\n      forecast += coefficients[i] * (recentValues[recentValues.length - 1 - i] - mean(series));\n    }\n    \n    forecasts.push(forecast);\n    recentValues.push(forecast);\n    recentValues.shift();\n  }\n  \n  return forecasts;\n}\n\nconst forecastSteps = 5;\nconst arOrder = Math.min(3, Math.floor(numberSeries.length / 10));\nconst forecasts = forecastAR(numberSeries, arOrder, forecastSteps);\n\n// Calculate forecast confidence bands\nconst forecastStdError = stdDev(numberSeries) / Math.sqrt(numberSeries.length);\nconst forecastsWithConfidence = forecasts.map((forecast, i) => {\n  const adjustedStdError = forecastStdError * Math.sqrt(i + 1); // Uncertainty increases with forecast horizon\n  const ci = confidenceInterval(forecast, adjustedStdError);\n  return {\n    step: i + 1,\n    forecast: Math.round(forecast),\n    confidenceInterval: {\n      lower: Math.max(0, Math.round(ci.lower)),\n      upper: Math.min(9999, Math.round(ci.upper))\n    },\n    standardError: adjustedStdError.toFixed(4),\n    uncertaintyLevel: i < 2 ? 'Moderate' : 'High'\n  };\n});\n\n// 9. Recent Trend Analysis\nconst recentWindow = Math.min(20, numberSeries.length);\nconst recentSeries = numberSeries.slice(-recentWindow);\nconst recentTrend = calculateTrend(recentSeries);\nconst trendDirection = recentTrend.slope > 0 ? 'Increasing' : recentTrend.slope < 0 ? 'Decreasing' : 'Stable';\n\n// Prepare output\nconst analysis = {\n  metadata: {\n    totalDrawsAnalyzed: timeSeries.length,\n    dateRange: {\n      start: timeSeries[0].date.toISOString().split('T')[0],\n      end: timeSeries[timeSeries.length - 1].date.toISOString().split('T')[0]\n    },\n    analysisDate: new Date().toISOString(),\n    lotteryType: '4D'\n  },\n  rollingStatistics: {\n    windowSize,\n    latest: rollingStats.slice(-5).map(r => ({\n      index: r.index,\n      mean: parseFloat(r.mean.toFixed(2)),\n      stdDev: parseFloat(r.stdDev.toFixed(2)),\n      range: `${r.min}-${r.max}`\n    })),\n    summary: {\n      averageMean: mean(rollingStats.map(r => r.mean)).toFixed(2),\n      averageStdDev: mean(rollingStats.map(r => r.stdDev)).toFixed(2)\n    }\n  },\n  trendAnalysis: {\n    overallTrend: {\n      slope: trendAnalysis.slope.toFixed(6),\n      intercept: trendAnalysis.intercept.toFixed(2),\n      direction: trendAnalysis.slope > 0 ? 'Increasing' : trendAnalysis.slope < 0 ? 'Decreasing' : 'Stable'\n    },\n    recentTrend: {\n      windowSize: recentWindow,\n      slope: recentTrend.slope.toFixed(6),\n      direction: trendDirection,\n      interpretation: `Recent ${recentWindow} draws show ${trendDirection.toLowerCase()} trend`\n    }\n  },\n  movingAverages: {\n    sma10: {\n      latest: sma10.slice(-5).map(v => Math.round(v)),\n      current: Math.round(sma10[sma10.length - 1])\n    },\n    sma30: {\n      latest: sma30.slice(-5).map(v => Math.round(v)),\n      current: Math.round(sma30[sma30.length - 1])\n    },\n    ema: {\n      latest: ema.slice(-5).map(v => Math.round(v)),\n      current: Math.round(ema[ema.length - 1])\n    }\n  },\n  autocorrelation: {\n    significantLags: significantLags.map(l => ({\n      lag: l.lag,\n      correlation: l.correlation.toFixed(4),\n      strength: Math.abs(l.correlation) > 0.3 ? 'Strong' : Math.abs(l.correlation) > 0.15 ? 'Moderate' : 'Weak'\n    })),\n    interpretation: significantLags.length > 0 \n      ? `Found ${significantLags.length} significant lag correlations, suggesting temporal dependencies`\n      : 'No strong temporal dependencies detected'\n  },\n  periodicityAnalysis: {\n    detectedPeaks: periodicityAnalysis.peaks.slice(0, 5).map(p => ({\n      period: p.lag,\n      correlation: p.correlation.toFixed(4),\n      interpretation: `Potential ${p.lag}-draw cycle detected`\n    })),\n    hasPeriodicity: periodicityAnalysis.peaks.length > 0\n  },\n  seasonality: {\n    byDayOfWeek: dayOfWeekStats.sort((a, b) => b.count - a.count),\n    byMonth: monthStats.sort((a, b) => b.count - a.count),\n    interpretation: 'Seasonal patterns analyzed by day of week and month'\n  },\n  cyclicalPatterns: {\n    detected: significantLags.length > 0,\n    cycles: significantLags.map(l => ({\n      cycleLength: l.lag,\n      strength: l.correlation.toFixed(4)\n    })),\n    interpretation: significantLags.length > 0\n      ? 'Cyclical patterns detected in the time series'\n      : 'No strong cyclical patterns identified'\n  },\n  forecasts: {\n    method: 'Autoregressive (AR) Model',\n    order: arOrder,\n    predictions: forecastsWithConfidence,\n    confidenceLevel: '95%',\n    interpretation: 'Forecasts based on historical autocorrelation patterns with expanding confidence intervals',\n    uncertainty: {\n      baseStandardError: forecastStdError.toFixed(4),\n      note: 'Uncertainty increases with forecast horizon. These are statistical estimates with high uncertainty.'\n    }\n  },\n  insights: {\n    overallTrendDirection: trendAnalysis.slope > 0 ? 'Increasing' : trendAnalysis.slope < 0 ? 'Decreasing' : 'Stable',\n    recentTrendDirection: trendDirection,\n    hasStrongAutocorrelation: significantLags.length > 0,\n    hasPeriodicity: periodicityAnalysis.peaks.length > 0,\n    mostActiveDayOfWeek: dayOfWeekStats.sort((a, b) => b.count - a.count)[0]?.dayName,\n    mostActiveMonth: monthStats.sort((a, b) => b.count - a.count)[0]?.monthName\n  }\n};\n\nreturn [{ json: analysis }];"
      },
      "typeVersion": 2
    },
    {
      "id": "27e5f986-59e1-40c3-ba89-8066a1276d2d",
      "name": "Ensemble Prediction Aggregator Tool",
      "type": "@n8n/n8n-nodes-langchain.toolCode",
      "position": [
        4240,
        3424
      ],
      "parameters": {
        "jsCode": "// Ensemble Prediction Aggregator\n// Combines multiple prediction models using advanced ensemble techniques\n\n// Parse the query input (expecting JSON string with predictions from multiple models)\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 with model predictions.' });\n}\n\nconst { models, weights, ensembleMethod = 'weighted_average' } = input;\n\nif (!models || !Array.isArray(models) || models.length === 0) {\n  return JSON.stringify({ error: 'Models array is required with at least one model prediction' });\n}\n\n// Default weights if not provided (equal weighting)\nconst modelWeights = weights || models.map(() => 1 / models.length);\n\n// Normalize weights to sum to 1\nconst weightSum = modelWeights.reduce((a, b) => a + b, 0);\nconst normalizedWeights = modelWeights.map(w => w / weightSum);\n\n// Ensemble Method 1: Weighted Average\nfunction weightedAverage(models, weights) {\n  const aggregatedPredictions = new Map();\n  \n  models.forEach((model, idx) => {\n    const weight = weights[idx];\n    const predictions = model.predictions || [];\n    \n    predictions.forEach(pred => {\n      const number = pred.number;\n      const score = pred.likelihoodScore || pred.score || 0;\n      \n      if (!aggregatedPredictions.has(number)) {\n        aggregatedPredictions.set(number, { totalScore: 0, count: 0, models: [] });\n      }\n      \n      const current = aggregatedPredictions.get(number);\n      current.totalScore += score * weight;\n      current.count++;\n      current.models.push(model.modelName || `Model ${idx + 1}`);\n    });\n  });\n  \n  const results = [];\n  aggregatedPredictions.forEach((data, number) => {\n    results.push({\n      number: number,\n      ensembleScore: data.totalScore,\n      modelConsensus: data.count,\n      contributingModels: data.models\n    });\n  });\n  \n  return results.sort((a, b) => b.ensembleScore - a.ensembleScore);\n}\n\n// Ensemble Method 2: Majority Voting\nfunction majorityVoting(models) {\n  const voteCounts = new Map();\n  \n  models.forEach(model => {\n    const predictions = model.predictions || [];\n    predictions.forEach(pred => {\n      const number = pred.number;\n      voteCounts.set(number, (voteCounts.get(number) || 0) + 1);\n    });\n  });\n  \n  const results = [];\n  voteCounts.forEach((votes, number) => {\n    results.push({\n      number: number,\n      votes: votes,\n      votePercentage: (votes / models.length * 100).toFixed(2) + '%'\n    });\n  });\n  \n  return results.sort((a, b) => b.votes - a.votes);\n}\n\n// Ensemble Method 3: Confidence-Weighted Aggregation\nfunction confidenceWeightedAggregation(models, weights) {\n  const aggregatedPredictions = new Map();\n  \n  models.forEach((model, idx) => {\n    const weight = weights[idx];\n    const predictions = model.predictions || [];\n    \n    predictions.forEach(pred => {\n      const number = pred.number;\n      const score = pred.likelihoodScore || pred.score || 0;\n      const confidence = pred.confidence || pred.bayesianProbability || 0.5;\n      \n      if (!aggregatedPredictions.has(number)) {\n        aggregatedPredictions.set(number, { \n          totalScore: 0, \n          totalConfidence: 0, \n          count: 0 \n        });\n      }\n      \n      const current = aggregatedPredictions.get(number);\n      current.totalScore += score * weight * confidence;\n      current.totalConfidence += confidence;\n      current.count++;\n    });\n  });\n  \n  const results = [];\n  aggregatedPredictions.forEach((data, number) => {\n    results.push({\n      number: number,\n      ensembleScore: data.totalScore,\n      averageConfidence: (data.totalConfidence / data.count).toFixed(4),\n      modelConsensus: data.count\n    });\n  });\n  \n  return results.sort((a, b) => b.ensembleScore - a.ensembleScore);\n}\n\n// Ensemble Method 4: Stacking with Meta-Learner\nfunction stackingMetaLearner(models, weights) {\n  // Simple meta-learner: combines predictions with learned weights\n  const metaPredictions = new Map();\n  \n  models.forEach((model, idx) => {\n    const weight = weights[idx];\n    const predictions = model.predictions || [];\n    const modelAccuracy = model.historicalAccuracy || 0.5;\n    \n    predictions.forEach(pred => {\n      const number = pred.number;\n      const score = pred.likelihoodScore || pred.score || 0;\n      \n      // Meta-learning adjustment: weight by historical accuracy\n      const adjustedScore = score * weight * modelAccuracy;\n      \n      if (!metaPredictions.has(number)) {\n        metaPredictions.set(number, { \n          metaScore: 0, \n          rawScore: 0,\n          count: 0 \n        });\n      }\n      \n      const current = metaPredictions.get(number);\n      current.metaScore += adjustedScore;\n      current.rawScore += score;\n      current.count++;\n    });\n  });\n  \n  const results = [];\n  metaPredictions.forEach((data, number) => {\n    results.push({\n      number: number,\n      metaScore: data.metaScore,\n      rawScore: data.rawScore,\n      modelConsensus: data.count\n    });\n  });\n  \n  return results.sort((a, b) => b.metaScore - a.metaScore);\n}\n\n// Execute ensemble method\nlet ensembleResults;\nswitch (ensembleMethod) {\n  case 'weighted_average':\n    ensembleResults = weightedAverage(models, normalizedWeights);\n    break;\n  case 'majority_voting':\n    ensembleResults = majorityVoting(models);\n    break;\n  case 'confidence_weighted':\n    ensembleResults = confidenceWeightedAggregation(models, normalizedWeights);\n    break;\n  case 'stacking':\n    ensembleResults = stackingMetaLearner(models, normalizedWeights);\n    break;\n  default:\n    ensembleResults = weightedAverage(models, normalizedWeights);\n}\n\n// Calculate ensemble statistics\nconst topPredictions = ensembleResults.slice(0, 10);\nconst avgConsensus = topPredictions.reduce((sum, p) => sum + (p.modelConsensus || 0), 0) / topPredictions.length;\n\nreturn JSON.stringify({\n  ensembleMethod: ensembleMethod,\n  topPredictions: topPredictions,\n  ensembleStatistics: {\n    totalModels: models.length,\n    averageConsensus: avgConsensus.toFixed(2),\n    modelWeights: normalizedWeights.map((w, i) => ({\n      model: models[i].modelName || `Model ${i + 1}`,\n      weight: w.toFixed(4)\n    }))\n  },\n  metadata: {\n    timestamp: new Date().toISOString(),\n    totalNumbersAnalyzed: ensembleResults.length\n  }\n}, null, 2);",
        "description": "Aggregates predictions from multiple models (statistical, Markov, time series, probabilistic) using weighted voting, stacking, and meta-learning techniques to produce ensemble predictions with improved accuracy"
      },
      "typeVersion": 1.3
    },
    {
      "id": "cfb0c857-db3c-4b3c-8b57-aa6c84da47e1",
      "name": "Cross-Validation Scoring Tool",
      "type": "@n8n/n8n-nodes-langchain.toolCode",
      "position": [
        4368,
        3424
      ],
      "parameters": {
        "jsCode": "// Cross-Validation Scoring Tool for Lottery Prediction Models\n// Implements k-fold cross-validation with comprehensive performance metrics\n\n// Parse the query input (expecting JSON string with model predictions and historical data)\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 with predictions and historicalData.' });\n}\n\nconst { predictions, historicalData, k = 5, modelType = 'classification' } = input;\n\nif (!predictions || !Array.isArray(predictions)) {\n  return JSON.stringify({ error: 'Predictions array is required' });\n}\n\nif (!historicalData || !Array.isArray(historicalData)) {\n  return JSON.stringify({ error: 'Historical data array is required for validation' });\n}\n\n// K-Fold Cross-Validation Implementation\nfunction kFoldSplit(data, k) {\n  const foldSize = Math.floor(data.length / k);\n  const folds = [];\n  \n  for (let i = 0; i < k; i++) {\n    const start = i * foldSize;\n    const end = i === k - 1 ? data.length : (i + 1) * foldSize;\n    folds.push(data.slice(start, end));\n  }\n  \n  return folds;\n}\n\n// Calculate Performance Metrics\nfunction calculateMetrics(predicted, actual) {\n  let truePositives = 0;\n  let falsePositives = 0;\n  let trueNegatives = 0;\n  let falseNegatives = 0;\n  let correctPredictions = 0;\n  \n  for (let i = 0; i < predicted.length; i++) {\n    const pred = predicted[i];\n    const act = actual[i];\n    \n    if (pred === act) {\n      correctPredictions++;\n      if (pred === 1 || pred === true) truePositives++;\n      else trueNegatives++;\n    } else {\n      if (pred === 1 || pred === true) falsePositives++;\n      else falseNegatives++;\n    }\n  }\n  \n  const accuracy = predicted.length > 0 ? correctPredictions / predicted.length : 0;\n  const precision = (truePositives + falsePositives) > 0 ? truePositives / (truePositives + falsePositives) : 0;\n  const recall = (truePositives + falseNegatives) > 0 ? truePositives / (truePositives + falseNegatives) : 0;\n  const f1Score = (precision + recall) > 0 ? 2 * (precision * recall) / (precision + recall) : 0;\n  \n  return {\n    accuracy: accuracy.toFixed(4),\n    precision: precision.toFixed(4),\n    recall: recall.toFixed(4),\n    f1Score: f1Score.toFixed(4),\n    truePositives,\n    falsePositives,\n    trueNegatives,\n    falseNegatives\n  };\n}\n\n// Calculate Mean Absolute Error (for regression-like scoring)\nfunction calculateMAE(predicted, actual) {\n  let totalError = 0;\n  for (let i = 0; i < predicted.length; i++) {\n    totalError += Math.abs(predicted[i] - actual[i]);\n  }\n  return predicted.length > 0 ? totalError / predicted.length : 0;\n}\n\n// Calculate Root Mean Squared Error\nfunction calculateRMSE(predicted, actual) {\n  let totalSquaredError = 0;\n  for (let i = 0; i < predicted.length; i++) {\n    totalSquaredError += Math.pow(predicted[i] - actual[i], 2);\n  }\n  return predicted.length > 0 ? Math.sqrt(totalSquaredError / predicted.length) : 0;\n}\n\n// Detect Overfitting (compare training vs validation performance)\nfunction detectOverfitting(trainMetrics, validationMetrics) {\n  const accuracyDiff = Math.abs(parseFloat(trainMetrics.accuracy) - parseFloat(validationMetrics.accuracy));\n  const f1Diff = Math.abs(parseFloat(trainMetrics.f1Score) - parseFloat(validationMetrics.f1Score));\n  \n  const isOverfitting = accuracyDiff > 0.15 || f1Diff > 0.15;\n  const overfittingScore = ((accuracyDiff + f1Diff) / 2) * 100;\n  \n  return {\n    isOverfitting,\n    overfittingScore: overfittingScore.toFixed(2),\n    accuracyDifference: accuracyDiff.toFixed(4),\n    f1Difference: f1Diff.toFixed(4),\n    severity: overfittingScore < 10 ? 'Low' : overfittingScore < 20 ? 'Moderate' : 'High'\n  };\n}\n\n// Perform K-Fold Cross-Validation\nconst folds = kFoldSplit(historicalData, k);\nconst foldResults = [];\n\nfor (let i = 0; i < k; i++) {\n  // Use fold i as validation set, rest as training\n  const validationSet = folds[i];\n  const trainingSet = folds.filter((_, idx) => idx !== i).flat();\n  \n  // Simulate predictions on validation set (using provided predictions as baseline)\n  const validationPredictions = validationSet.map((item, idx) => {\n    // Match predictions to validation data\n    const matchingPrediction = predictions.find(p => p.number === item.number || p.combination === item.combination);\n    return matchingPrediction ? matchingPrediction.predicted : Math.random() > 0.5 ? 1 : 0;\n  });\n  \n  const validationActual = validationSet.map(item => item.actual || (Math.random() > 0.7 ? 1 : 0));\n  \n  // Calculate metrics for this fold\n  const foldMetrics = calculateMetrics(validationPredictions, validationActual);\n  const mae = calculateMAE(validationPredictions, validationActual);\n  const rmse = calculateRMSE(validationPredictions, validationActual);\n  \n  foldResults.push({\n    fold: i + 1,\n    metrics: foldMetrics,\n    mae: mae.toFixed(4),\n    rmse: rmse.toFixed(4),\n    validationSize: validationSet.length,\n    trainingSize: trainingSet.length\n  });\n}\n\n// Calculate average metrics across all folds\nconst avgAccuracy = foldResults.reduce((sum, f) => sum + parseFloat(f.metrics.accuracy), 0) / k;\nconst avgPrecision = foldResults.reduce((sum, f) => sum + parseFloat(f.metrics.precision), 0) / k;\nconst avgRecall = foldResults.reduce((sum, f) => sum + parseFloat(f.metrics.recall), 0) / k;\nconst avgF1Score = foldResults.reduce((sum, f) => sum + parseFloat(f.metrics.f1Score), 0) / k;\nconst avgMAE = foldResults.reduce((sum, f) => sum + parseFloat(f.mae), 0) / k;\nconst avgRMSE = foldResults.reduce((sum, f) => sum + parseFloat(f.rmse), 0) / k;\n\n// Calculate standard deviation of metrics (measure of stability)\nconst accuracyStdDev = Math.sqrt(foldResults.reduce((sum, f) => sum + Math.pow(parseFloat(f.metrics.accuracy) - avgAccuracy, 2), 0) / k);\nconst f1StdDev = Math.sqrt(foldResults.reduce((sum, f) => sum + Math.pow(parseFloat(f.metrics.f1Score) - avgF1Score, 2), 0) / k);\n\n// Calculate out-of-sample error rate\nconst outOfSampleErrorRate = 1 - avgAccuracy;\n\n// Model reliability score (0-100, higher is better)\nconst reliabilityScore = Math.max(0, Math.min(100, (\n  (avgAccuracy * 40) +\n  (avgF1Score * 30) +\n  ((1 - accuracyStdDev) * 20) +\n  ((1 - outOfSampleErrorRate) * 10)\n) * 100));\n\n// Overfitting detection (compare first fold as \"training\" vs average of rest as \"validation\")\nconst trainingMetrics = foldResults[0].metrics;\nconst validationMetrics = {\n  accuracy: (foldResults.slice(1).reduce((sum, f) => sum + parseFloat(f.metrics.accuracy), 0) / (k - 1)).toFixed(4),\n  f1Score: (foldResults.slice(1).reduce((sum, f) => sum + parseFloat(f.metrics.f1Score), 0) / (k - 1)).toFixed(4)\n};\nconst overfittingAnalysis = detectOverfitting(trainingMetrics, validationMetrics);\n\n// Generate final report\nconst report = {\n  crossValidationResults: {\n    kFolds: k,\n    totalSamples: historicalData.length,\n    modelType: modelType,\n    foldResults: foldResults\n  },\n  aggregateMetrics: {\n    averageAccuracy: avgAccuracy.toFixed(4),\n    averagePrecision: avgPrecision.toFixed(4),\n    averageRecall: avgRecall.toFixed(4),\n    averageF1Score: avgF1Score.toFixed(4),\n    averageMAE: avgMAE.toFixed(4),\n    averageRMSE: avgRMSE.toFixed(4)\n  },\n  stabilityMetrics: {\n    accuracyStandardDeviation: accuracyStdDev.toFixed(4),\n    f1StandardDeviation: f1StdDev.toFixed(4),\n    coefficientOfVariation: avgAccuracy > 0 ? (accuracyStdDev / avgAccuracy).toFixed(4) : 'N/A'\n  },\n  errorAnalysis: {\n    outOfSampleErrorRate: outOfSampleErrorRate.toFixed(4),\n    outOfSampleErrorPercentage: (outOfSampleErrorRate * 100).toFixed(2) + '%'\n  },\n  overfittingDetection: overfittingAnalysis,\n  modelReliability: {\n    reliabilityScore: reliabilityScore.toFixed(2),\n    reliabilityLevel: reliabilityScore > 80 ? 'High' : reliabilityScore > 60 ? 'Moderate' : 'Low',\n    recommendation: reliabilityScore > 80 \n      ? 'Model shows good generalization capability' \n      : reliabilityScore > 60 \n      ? 'Model has moderate reliability, consider additional validation' \n      : 'Model shows poor generalization, significant improvement needed'\n  },\n  summary: {\n    bestFold: foldResults.reduce((best, current) => \n      parseFloat(current.metrics.f1Score) > parseFloat(best.metrics.f1Score) ? current : best\n    ).fold,\n    worstFold: foldResults.reduce((worst, current) => \n      parseFloat(current.metrics.f1Score) < parseFloat(worst.metrics.f1Score) ? current : worst\n    ).fold,\n    performanceConsistency: accuracyStdDev < 0.05 ? 'High' : accuracyStdDev < 0.1 ? 'Moderate' : 'Low'\n  }\n};\n\nreturn JSON.stringify(report, null, 2);",
        "description": "Performs k-fold cross-validation on prediction models, calculates performance metrics (accuracy, precision, recall, F1-score), computes out-of-sample error rates, and provides model reliability scores"
      },
      "typeVersion": 1.3
    },
    {
      "id": "e8321169-4a5a-4dd3-9a14-0c0bc30e6744",
      "name": "Calculator Tool",
      "type": "@n8n/n8n-nodes-langchain.toolCalculator",
      "position": [
        4496,
        3424
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "9d937482-155c-47dd-9f61-bbb0246fce6d",
      "name": "Merge All Analysis Results",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        3760,
        3056
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData",
        "destinationFieldName": "combinedAnalysis"
      },
      "typeVersion": 1
    },
    {
      "id": "e2d803bf-7254-4066-9f5b-48321ef7209c",
      "name": "Check Data Quality",
      "type": "n8n-nodes-base.if",
      "position": [
        3088,
        3296
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "id-1",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.allDraws ? $json.allDraws.length : 0 }}",
              "rightValue": "100"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "514e392c-0b67-4019-a17e-020b7760cb00",
      "name": "Generate Quality Report",
      "type": "n8n-nodes-base.code",
      "position": [
        3312,
        3280
      ],
      "parameters": {
        "jsCode": "// Data Quality Report Generator\n// Evaluates data sufficiency and provides recommendations\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 || item.json.winningNumbers) {\n    allDraws.push(item.json);\n  }\n}\n\nconst totalDraws = allDraws.length;\n\n// Define minimum thresholds for reliable analysis\nconst MINIMUM_DRAWS_REQUIRED = 100;\nconst RECOMMENDED_DRAWS = 500;\nconst OPTIMAL_DRAWS = 1000;\n\n// Calculate data quality metrics\nconst dataQualityScore = Math.min(100, (totalDraws / OPTIMAL_DRAWS) * 100);\nconst sufficientData = totalDraws >= MINIMUM_DRAWS_REQUIRED;\n\n// Determine quality level\nlet qualityLevel;\nlet qualityColor;\nif (totalDraws >= OPTIMAL_DRAWS) {\n  qualityLevel = 'OPTIMAL';\n  qualityColor = 'green';\n} else if (totalDraws >= RECOMMENDED_DRAWS) {\n  qualityLevel = 'GOOD';\n  qualityColor = 'blue';\n} else if (totalDraws >= MINIMUM_DRAWS_REQUIRED) {\n  qualityLevel = 'ACCEPTABLE';\n  qualityColor = 'yellow';\n} else {\n  qualityLevel = 'INSUFFICIENT';\n  qualityColor = 'red';\n}\n\n// Generate recommendations\nconst recommendations = [];\n\nif (totalDraws < MINIMUM_DRAWS_REQUIRED) {\n  recommendations.push({\n    priority: 'CRITICAL',\n    message: `Insufficient data for reliable analysis. Current: ${totalDraws} draws, Required: ${MINIMUM_DRAWS_REQUIRED} draws`,\n    action: 'Collect at least ' + (MINIMUM_DRAWS_REQUIRED - totalDraws) + ' more historical draws before proceeding'\n  });\n  recommendations.push({\n    priority: 'HIGH',\n    message: 'Statistical patterns require substantial historical data to be meaningful',\n    action: 'Consider using official lottery archives or historical datasets'\n  });\n} else if (totalDraws < RECOMMENDED_DRAWS) {\n  recommendations.push({\n    priority: 'MEDIUM',\n    message: `Data is acceptable but below recommended threshold. Current: ${totalDraws} draws, Recommended: ${RECOMMENDED_DRAWS} draws`,\n    action: 'Collect ' + (RECOMMENDED_DRAWS - totalDraws) + ' more draws for improved prediction accuracy'\n  });\n} else if (totalDraws < OPTIMAL_DRAWS) {\n  recommendations.push({\n    priority: 'LOW',\n    message: `Good data volume. Current: ${totalDraws} draws, Optimal: ${OPTIMAL_DRAWS} draws`,\n    action: 'Consider collecting ' + (OPTIMAL_DRAWS - totalDraws) + ' more draws for optimal statistical confidence'\n  });\n} else {\n  recommendations.push({\n    priority: 'INFO',\n    message: 'Optimal data volume achieved for comprehensive analysis',\n    action: 'Continue monitoring and updating with new draws'\n  });\n}\n\n// Check for data completeness\nconst incompleteDraws = allDraws.filter(draw => {\n  const numbers = draw.numbers || draw.winningNumbers || [];\n  return !numbers || numbers.length === 0 || !draw.date;\n}).length;\n\nif (incompleteDraws > 0) {\n  recommendations.push({\n    priority: 'MEDIUM',\n    message: `Found ${incompleteDraws} incomplete draw records (${((incompleteDraws / totalDraws) * 100).toFixed(2)}%)`,\n    action: 'Review and complete missing data fields for better analysis accuracy'\n  });\n}\n\n// Generate quality report\nconst qualityReport = {\n  reportDate: new Date().toISOString(),\n  dataQualityAssessment: {\n    totalDrawsAnalyzed: totalDraws,\n    qualityLevel: qualityLevel,\n    qualityScore: dataQualityScore.toFixed(2) + '%',\n    sufficientForAnalysis: sufficientData,\n    incompleteRecords: incompleteDraws,\n    completenessRate: (((totalDraws - incompleteDraws) / totalDraws) * 100).toFixed(2) + '%'\n  },\n  thresholds: {\n    minimum: MINIMUM_DRAWS_REQUIRED,\n    recommended: RECOMMENDED_DRAWS,\n    optimal: OPTIMAL_DRAWS,\n    currentProgress: {\n      toMinimum: totalDraws >= MINIMUM_DRAWS_REQUIRED ? '\u2713 Achieved' : `${totalDraws}/${MINIMUM_DRAWS_REQUIRED} (${((totalDraws / MINIMUM_DRAWS_REQUIRED) * 100).toFixed(1)}%)`,\n      toRecommended: totalDraws >= RECOMMENDED_DRAWS ? '\u2713 Achieved' : `${totalDraws}/${RECOMMENDED_DRAWS} (${((totalDraws / RECOMMENDED_DRAWS) * 100).toFixed(1)}%)`,\n      toOptimal: totalDraws >= OPTIMAL_DRAWS ? '\u2713 Achieved' : `${totalDraws}/${OPTIMAL_DRAWS} (${((totalDraws / OPTIMAL_DRAWS) * 100).toFixed(1)}%)`\n    }\n  },\n  recommendations: recommendations,\n  analysisReliability: {\n    statisticalSignificance: sufficientData ? 'Moderate to High' : 'Low - Insufficient Data',\n    predictionConfidence: sufficientData ? 'Acceptable with caveats' : 'Not Recommended',\n    uncertaintyLevel: totalDraws < MINIMUM_DRAWS_REQUIRED ? 'VERY HIGH' : totalDraws < RECOMMENDED_DRAWS ? 'HIGH' : totalDraws < OPTIMAL_DRAWS ? 'MODERATE' : 'MODERATE-LOW'\n  },\n  nextSteps: sufficientData \n    ? ['Proceed with analysis', 'Monitor prediction accuracy', 'Continue data collection']\n    : ['STOP: Do not proceed with analysis', 'Collect more historical data', 'Verify data sources', 'Ensure data completeness'],\n  disclaimer: 'This quality assessment is based on statistical best practices. Even with optimal data, lottery predictions remain highly uncertain due to the random nature of draws.'\n};\n\nconsole.log(`Data Quality Report: ${qualityLevel} - ${totalDraws} draws analyzed`);\n\nreturn [{ json: qualityReport }];"
      },
      "typeVersion": 2
    },
    {
      "id": "431bac68-3fde-4d32-982b-0289cd727a8b",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2176,
        2656
      ],
      "parameters": {
        "width": 416,
        "height": 272,
        "content": "## How It Works\nA 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 anomaly detection, while the other generates statistical and time-series forecasts. Results are then routed to an AI agent that integrates multi-model insights, evaluates prediction confidence, and synthesizes the final output. The system formats the results and delivers them through the selected export channel.\n\n"
      },
      "typeVersion": 1
    },
    {
      "id": "2c7f1efb-d2c2-4693-ae72-eb59050701ea",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2608,
        2656
      ],
      "parameters": {
        "color": 2,
        "width": 576,
        "height": 272,
        "content": "## Setup Instructions\n\n**1. Scheduler Config:** Adjust the trigger frequency (daily or weekly).\n**2. Data Sources:** Configure API endpoints or database connectors for TOTO/4D retrieval.\n**3. Data Mapping:** Align and map column structures for both 1D and 4D datasets in merge nodes.\n**4. AI Integration:** Insert the OpenAI API key and connect the required model nodes.\n**5. Export Paths:** Select and configure output channels (email, Google Sheets, webhook, or API)."
      },
      "typeVersion": 1
    },
    {
      "id": "94efa6d5-7fb7-48c6-831d-ae013f090a57",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3216,
        2672
      ],
      "parameters": {
        "color": 3,
        "height": 192,
        "content": "## Prerequisites\n- TOTO/4D data source access\n- OpenAI API key (GPT model)\n- n8n HTTP Request nodes\n- Basic SQL/database knowledge"
      },
      "typeVersion": 1
    },
    {
      "id": "8dae9c52-98e2-48a6-9c87-d83cd7c66558",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3920,
        2672
      ],
      "parameters": {
        "color": 6,
        "height": 128,
        "content": " \n## Customization\nReplace TOTO/4D sources with stock prices, crypto data, or sensor metrics.  \n"
      },
      "typeVersion": 1
    },
    {
      "id": "973233e5-e448-4139-b1ec-099a8870ab79",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3504,
        2672
      ],
      "parameters": {
        "color": 4,
        "width": 352,
        "content": "## Use Cases\n- Financial traders analyzing number patterns for lottery prediction\n- Data analysts automating multivariate time series forecasting\n"
      },
      "typeVersion": 1
    },
    {
      "id": "14a20d9d-c5cf-481c-97fa-232bff98814f",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4208,
        2672
      ],
      "parameters": {
        "color": 6,
        "width": 432,
        "height": 144,
        "content": "## Benefits\nAutomates complex multi-model analysis reducing manual work. AI agent intelligently routes data for users"
      },
      "typeVersion": 1
    },
    {
      "id": "11499375-93af-43ee-b815-7fd03eda5e5d",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2176,
        2944
      ],
      "parameters": {
        "color": 7,
        "width": 672,
        "height": 896,
        "content": "## 1. Schedule Trigger & Data Collection\n\nAutomatically fetches historical TOTO and 4D draw data at set intervals.\n**Why:** Eliminates manual work and ensures continuous, fresh data for consistent 24/7 predictions."
      },
      "typeVersion": 1
    },
    {
      "id": "36d76bae-b047-4975-8f69-e8e9a1cb343e",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2864,
        2944
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 896,
        "content": "## 2. Data Integration & Quality Control\n\nMerges 1D and 4D datasets and validates completeness and accuracy.\n**Why:** Catches errors early, preventing corrupt data from affecting downstream predictions."
      },
      "typeVersion": 1
    },
    {
      "id": "a76614b6-91ec-4828-8abf-50869813db5e",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3280,
        2944
      ],
      "parameters": {
        "color": 7,
        "width": 640,
        "height": 896,
        "content": "## 3. Parallel Analysis Layer\n\nRuns pattern mining and time series forecasting simultaneously.\n**Why:** Patterns reveal recurring number trends, while forecasting predicts future values, reducing single-perspective bias."
      },
      "typeVersion": 1
    },
    {
      "id": "2b465e7e-9476-4a85-989d-657c561da7fa",
      "name": "Sticky Note9",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3936,
        2944
      ],
      "parameters": {
        "color": 7,
        "width": 672,
        "height": 672,
        "content": "## 4. AI Predictive Analysis Agent (Intelligence Hub)\n\nRoutes all analysis results through AI evaluation tools (ensemble, probabilistic modeling, validation).\n**Why:** Synthesizes conflicting predictions to improve accuracy and prevent one model from dominating decisions."
      },
      "typeVersion": 1
    },
    {
      "id": "48e18cfe-c105-4b0f-a821-82acca7d1761",
      "name": "Sticky Note10",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4624,
        2944
      ],
      "parameters": {
        "color": 7,
        "height": 672,
        "content": "## 5. Output Formatting\n\nPackages predictions with confidence scores and methodology details.\n**Why:** Delivers actionable insights with transparency, showing users both what to predict and why."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "e86d65be-dce6-427b-a4df-634dac2a3f47",
  "connections": {
    "Merge 4D Data": {
      "main": [
        [
          {
            "node": "Statistical Pattern Mining - 4D",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculator Tool": {
      "ai_tool": [
        [
          {
            "node": "AI Predictive Analysis Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Merge TOTO Data": {
      "main": [
        [
          {
            "node": "Statistical Pattern Mining - TOTO",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check Data Quality",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "Workflow Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Predictive Analysis Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Check Data Quality": {
      "main": [
        [
          {
            "node": "Statistical Pattern Mining - TOTO",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Generate Quality Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch 4D Draw Data": {
      "main": [
        [
          {
            "node": "Merge 4D Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch TOTO Draw Data": {
      "main": [
        [
          {
            "node": "Merge TOTO Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Workflow Configuration": {
      "main": [
        [
          {
            "node": "Fetch TOTO Draw Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch 4D Draw Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Historical TOTO Dataset",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Historical 4D Dataset",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge All Analysis Results": {
      "main": [
        [
          {
            "node": "AI Predictive Analysis Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Historical 4D Dataset": {
      "main": [
        [
          {
            "node": "Merge 4D Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Probabilistic Modeling Tool": {
      "ai_tool": [
        [
          {
            "node": "AI Predictive Analysis Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "AI Predictive Analysis Agent": {
      "main": [
        [
          {
            "node": "Format Predictions Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Time Series Forecasting - 4D": {
      "main": [
        [
          {
            "node": "Merge All Analysis Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Cross-Validation Scoring Tool": {
      "ai_tool": [
        [
          {
            "node": "AI Predictive Analysis Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Historical TOTO Dataset": {
      "main": [
        [
          {
            "node": "Merge TOTO Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Time Series Forecasting - TOTO": {
      "main": [
        [
          {
            "node": "Merge All Analysis Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Statistical Pattern Mining - 4D": {
      "main": [
        [
          {
            "node": "AI Predictive Analysis Agent",
            "type": "main",
            "index": 0
          },
          {
            "node": "Advanced Markov Chain Analysis - 4D",
            "type": "main",
            "index": 0
          },
          {
            "node": "Time Series Forecasting - 4D",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Statistical Pattern Mining - TOTO": {
      "main": [
        [
          {
            "node": "AI Predictive Analysis Agent",
            "type": "main",
            "index": 0
          },
          {
            "node": "Advanced Markov Chain Analysis - TOTO",
            "type": "main",
            "index": 0
          },
          {
            "node": "Time Series Forecasting - TOTO",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Advanced Markov Chain Analysis - 4D": {
      "main": [
        [
          {
            "node": "Merge All Analysis Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Ensemble Prediction Aggregator Tool": {
      "ai_tool": [
        [
          {
            "node": "AI Predictive Analysis Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Advanced Markov Chain Analysis - TOTO": {
      "main": [
        [
          {
            "node": "Merge All Analysis Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}