UNPKG

polyv-live-cli

Version:

CLI tool for managing PolyV live streaming services.

421 lines 15.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ChangeDetector = void 0; const crypto_1 = require("crypto"); const events_1 = require("events"); class ChangeDetector extends events_1.EventEmitter { constructor(config = {}) { super(); this.dataSamples = new Map(); this.lastKnownHashes = new Map(); this.patternCache = new Map(); this.cacheExpiry = new Map(); this.config = { sampleSize: 100, minSampleInterval: 1000, changeThreshold: 0.1, deepComparison: true, patternRecognition: true, ...config, }; } detectChange(dataSourceId, currentData, previousData) { const currentHash = this.generateDataHash(currentData); const previousHash = previousData ? this.generateDataHash(previousData) : this.lastKnownHashes.get(dataSourceId); this.lastKnownHashes.set(dataSourceId, currentHash); const hasBasicChange = currentHash !== previousHash; let result = { hasChanged: hasBasicChange, changeType: 'none', confidence: 1.0, currentHash, previousHash: previousHash || '', }; if (hasBasicChange && this.config.deepComparison) { result = this.performDeepComparison(currentData, previousData, result); } this.recordSample(dataSourceId, { timestamp: Date.now(), dataHash: currentHash, changeType: result.changeType, confidence: result.confidence, size: this.estimateDataSize(currentData), }); if (result.hasChanged) { this.emit('changeDetected', { dataSourceId, result, timestamp: Date.now(), }); } return result; } analyzePattern(dataSourceId) { const cached = this.patternCache.get(dataSourceId); const cacheTime = this.cacheExpiry.get(dataSourceId); if (cached && cacheTime && Date.now() - cacheTime < 60000) { return cached; } const samples = this.dataSamples.get(dataSourceId) || []; if (samples.length < 10) { return { patternType: 'stable', strength: 0.1, changeFrequency: 0, volatility: 0, }; } const analysis = this.performPatternAnalysis(samples); this.patternCache.set(dataSourceId, analysis); this.cacheExpiry.set(dataSourceId, Date.now()); return analysis; } classifyActivity(dataSourceId) { const pattern = this.analyzePattern(dataSourceId); const samples = this.dataSamples.get(dataSourceId) || []; let activityLevel = 'static'; let recommendedInterval = 60000; if (pattern.volatility > 0.8) { activityLevel = 'volatile'; recommendedInterval = 1000; } else if (pattern.changeFrequency > 30) { activityLevel = 'high'; recommendedInterval = 2000; } else if (pattern.changeFrequency > 10) { activityLevel = 'medium'; recommendedInterval = 5000; } else if (pattern.changeFrequency > 2) { activityLevel = 'low'; recommendedInterval = 15000; } if (pattern.patternType === 'periodic' && pattern.nextChangeTime) { const timeUntilNextChange = pattern.nextChangeTime - Date.now(); if (timeUntilNextChange > 0) { recommendedInterval = Math.min(recommendedInterval, timeUntilNextChange * 0.8); } } return { dataSourceId, activityLevel, pattern, recommendedInterval, confidence: this.calculateClassificationConfidence(samples), }; } getSamples(dataSourceId) { return [...(this.dataSamples.get(dataSourceId) || [])]; } clearSamples(dataSourceId) { this.dataSamples.delete(dataSourceId); this.lastKnownHashes.delete(dataSourceId); this.patternCache.delete(dataSourceId); this.cacheExpiry.delete(dataSourceId); } getStatistics() { const activityDistribution = { static: 0, low: 0, medium: 0, high: 0, volatile: 0, }; const patternDistribution = { stable: 0, periodic: 0, volatile: 0, trending: 0, }; let totalSamples = 0; for (const [dataSourceId, samples] of this.dataSamples) { totalSamples += samples.length; const activity = this.classifyActivity(dataSourceId); activityDistribution[activity.activityLevel] = (activityDistribution[activity.activityLevel] || 0) + 1; patternDistribution[activity.pattern.patternType] = (patternDistribution[activity.pattern.patternType] || 0) + 1; } return { totalDataSources: this.dataSamples.size, totalSamples, activityDistribution, patternDistribution, }; } generateDataHash(data) { if (data === null || data === undefined) { return 'null'; } try { const seen = new WeakSet(); const serialized = JSON.stringify(data, (_key, value) => { if (typeof value === 'object' && value !== null) { if (seen.has(value)) { return '[Circular]'; } seen.add(value); } return value; }); return (0, crypto_1.createHash)('sha256').update(serialized).digest('hex'); } catch (error) { return (0, crypto_1.createHash)('sha256').update(String(data)).digest('hex'); } } performDeepComparison(currentData, previousData, result) { if (!previousData) { return { ...result, changeType: 'major', confidence: 0.9, }; } const changeDetails = { addedFields: [], removedFields: [], modifiedFields: [], sizeChange: 0, }; const currentKeys = new Set(Object.keys(currentData || {})); const previousKeys = new Set(Object.keys(previousData || {})); for (const key of currentKeys) { if (!previousKeys.has(key)) { changeDetails.addedFields.push(key); } } for (const key of previousKeys) { if (!currentKeys.has(key)) { changeDetails.removedFields.push(key); } } for (const key of currentKeys) { if (previousKeys.has(key)) { const currentValue = currentData[key]; const previousValue = previousData[key]; if (JSON.stringify(currentValue) !== JSON.stringify(previousValue)) { changeDetails.modifiedFields.push(key); } } } const currentSize = this.estimateDataSize(currentData); const previousSize = this.estimateDataSize(previousData); changeDetails.sizeChange = currentSize - previousSize; let changeType = 'none'; let confidence = 1.0; const totalChanges = changeDetails.addedFields.length + changeDetails.removedFields.length + changeDetails.modifiedFields.length; if (changeDetails.addedFields.length > 0 || changeDetails.removedFields.length > 0) { changeType = 'structural'; confidence = 0.95; } else if (totalChanges > currentKeys.size * 0.5) { changeType = 'major'; confidence = 0.9; } else if (totalChanges > 0) { changeType = 'minor'; confidence = 0.8; } return { ...result, changeType, confidence, changeDetails, }; } performPatternAnalysis(samples) { const recentSamples = samples.slice(-this.config.sampleSize); const timespan = recentSamples.length > 1 ? (recentSamples[recentSamples.length - 1]?.timestamp || 0) - (recentSamples[0]?.timestamp || 0) : 3600000; const changedSamples = recentSamples.filter(s => s.changeType !== 'none'); const changeFrequency = (changedSamples.length / (timespan / 3600000)); const volatility = this.calculateVolatility(recentSamples); const patternType = this.detectPatternType(recentSamples); const strength = this.calculatePatternStrength(recentSamples, patternType); let nextChangeTime; if (patternType === 'periodic') { nextChangeTime = this.predictNextChangeTime(recentSamples); } return { patternType, strength, changeFrequency, volatility, ...(nextChangeTime && { nextChangeTime }), }; } calculateVolatility(samples) { if (samples.length < 3) { return 0; } const intervals = []; for (let i = 1; i < samples.length; i++) { const currentSample = samples[i]; const previousSample = samples[i - 1]; if (currentSample && previousSample && currentSample.changeType !== 'none') { intervals.push(currentSample.timestamp - previousSample.timestamp); } } if (intervals.length < 2) { return 0; } const mean = intervals.reduce((sum, interval) => sum + interval, 0) / intervals.length; const variance = intervals.reduce((sum, interval) => sum + Math.pow(interval - mean, 2), 0) / intervals.length; const stdDev = Math.sqrt(variance); return Math.min(stdDev / mean, 1); } detectPatternType(samples) { if (samples.length < 10) { return 'stable'; } const changedSamples = samples.filter(s => s.changeType !== 'none'); if (changedSamples.length === 0) { return 'stable'; } if (this.isPeriodicPattern(changedSamples)) { return 'periodic'; } if (this.isTrendingPattern(samples)) { return 'trending'; } const volatility = this.calculateVolatility(samples); if (volatility > 0.7) { return 'volatile'; } return 'stable'; } isPeriodicPattern(changedSamples) { if (changedSamples.length < 4) { return false; } const intervals = []; for (let i = 1; i < changedSamples.length; i++) { const currentSample = changedSamples[i]; const previousSample = changedSamples[i - 1]; if (currentSample && previousSample) { intervals.push(currentSample.timestamp - previousSample.timestamp); } } const mean = intervals.reduce((sum, interval) => sum + interval, 0) / intervals.length; const consistentIntervals = intervals.filter(interval => Math.abs(interval - mean) < mean * 0.3); return consistentIntervals.length / intervals.length > 0.7; } isTrendingPattern(samples) { if (samples.length < 10) { return false; } const windowSize = 5; const windows = []; for (let i = 0; i <= samples.length - windowSize; i++) { const window = samples.slice(i, i + windowSize); const changeCount = window.filter(s => s.changeType !== 'none').length; windows.push(changeCount); } let increasing = 0; let decreasing = 0; for (let i = 1; i < windows.length; i++) { const currentWindow = windows[i]; const previousWindow = windows[i - 1]; if (currentWindow !== undefined && previousWindow !== undefined) { if (currentWindow > previousWindow) { increasing++; } else if (currentWindow < previousWindow) { decreasing++; } } } const totalComparisons = windows.length - 1; return (increasing / totalComparisons > 0.7) || (decreasing / totalComparisons > 0.7); } calculatePatternStrength(samples, patternType) { switch (patternType) { case 'stable': return 1.0 - (samples.filter(s => s.changeType !== 'none').length / samples.length); case 'periodic': return this.isPeriodicPattern(samples.filter(s => s.changeType !== 'none')) ? 0.8 : 0.3; case 'volatile': return this.calculateVolatility(samples); case 'trending': return this.isTrendingPattern(samples) ? 0.7 : 0.3; default: return 0.5; } } predictNextChangeTime(samples) { const changedSamples = samples.filter(s => s.changeType !== 'none'); if (changedSamples.length < 3) { return undefined; } const intervals = []; for (let i = 1; i < changedSamples.length; i++) { const currentSample = changedSamples[i]; const previousSample = changedSamples[i - 1]; if (currentSample && previousSample) { intervals.push(currentSample.timestamp - previousSample.timestamp); } } const averageInterval = intervals.reduce((sum, interval) => sum + interval, 0) / intervals.length; const lastSample = changedSamples[changedSamples.length - 1]; if (!lastSample) { return undefined; } const lastChangeTime = lastSample.timestamp; return lastChangeTime + averageInterval; } calculateClassificationConfidence(samples) { if (samples.length < 10) { return 0.3; } if (samples.length < 50) { return 0.6; } return 0.9; } estimateDataSize(data) { if (data === null || data === undefined) { return 0; } try { const seen = new WeakSet(); const serialized = JSON.stringify(data, (_key, value) => { if (typeof value === 'object' && value !== null) { if (seen.has(value)) { return '[Circular]'; } seen.add(value); } return value; }); return serialized.length; } catch (error) { return String(data).length; } } recordSample(dataSourceId, sample) { if (!this.dataSamples.has(dataSourceId)) { this.dataSamples.set(dataSourceId, []); } const samples = this.dataSamples.get(dataSourceId); if (samples.length > 0) { const lastSample = samples[samples.length - 1]; if (lastSample && sample.timestamp - lastSample.timestamp < this.config.minSampleInterval) { return; } } samples.push(sample); if (samples.length > this.config.sampleSize) { samples.shift(); } this.patternCache.delete(dataSourceId); this.cacheExpiry.delete(dataSourceId); } } exports.ChangeDetector = ChangeDetector; //# sourceMappingURL=change-detector.js.map