/** * AI Price Recommendation Engine * Rule-based algorithm with weighted scoring */ interface PriceData { avgPrice: number; medianPrice: number; minPrice: number; maxPrice: number; stdDeviation: number; top10Lowest: number[]; top10Highest: number[]; totalProducts: number; avgRating: number; totalSold: number; ratingDistribution: Record; } interface PriceRecommendation { entryPrice: number; competitivePrice: number; premiumPrice: number; riskScore: 'low' | 'medium' | 'high'; riskValue: number; insights: string[]; priceRanges: { budget: { min: number; max: number }; midRange: { min: number; max: number }; premium: { min: number; max: number }; }; competitionDensity: 'low' | 'medium' | 'high'; marketPosition: string; } export class AIEngine { /** * Generate price recommendation based on market data */ recommend(data: PriceData): PriceRecommendation { const insights: string[] = []; // === Competition Density === const competitionDensity = this.calculateCompetitionDensity(data.totalProducts); // === Price Range Analysis === const priceRange = data.maxPrice - data.minPrice; const coefficientOfVariation = data.stdDeviation / data.avgPrice; // === Entry Price (Penetration Pricing) === // Weighted: 40% top10 avg, 30% median, 20% Q1, 10% min const top10Avg = data.top10Lowest.reduce((a, b) => a + b, 0) / data.top10Lowest.length; const q1 = this.calculatePercentile(data.top10Lowest.concat(data.top10Highest), 25); const entryPrice = Math.round( top10Avg * 0.4 + data.medianPrice * 0.3 + q1 * 0.2 + data.minPrice * 0.1 ); // === Competitive Price (Market Average) === // Weighted: 50% median, 30% avg, 20% mode approximation const modeApprox = (2 * data.medianPrice + data.avgPrice) / 3; const competitivePrice = Math.round( data.medianPrice * 0.5 + data.avgPrice * 0.3 + modeApprox * 0.2 ); // === Premium Price === // Weighted: 40% Q3, 30% top10 highest avg, 20% avg+1σ, 10% max const q3 = this.calculatePercentile(data.top10Lowest.concat(data.top10Highest), 75); const top10HighAvg = data.top10Highest.reduce((a, b) => a + b, 0) / data.top10Highest.length; const avgPlusSigma = data.avgPrice + data.stdDeviation; const premiumPrice = Math.round( q3 * 0.4 + top10HighAvg * 0.3 + avgPlusSigma * 0.2 + data.maxPrice * 0.1 ); // === Risk Score === const riskValue = this.calculateRisk(data, coefficientOfVariation, competitionDensity); const riskScore = riskValue <= 0.35 ? 'low' : riskValue <= 0.65 ? 'medium' : 'high'; // === Price Ranges === const priceRanges = { budget: { min: Math.round(data.minPrice), max: Math.round(data.medianPrice * 0.7), }, midRange: { min: Math.round(data.medianPrice * 0.7), max: Math.round(data.medianPrice * 1.3), }, premium: { min: Math.round(data.medianPrice * 1.3), max: Math.round(data.maxPrice), }, }; // === Insights === if (coefficientOfVariation > 0.5) { insights.push('Harga sangat bervariasi di pasar ini. Ada peluang untuk diferensiasi harga.'); } else if (coefficientOfVariation < 0.2) { insights.push('Pasar cukup seragam dalam hal harga. Kompetisi berdasarkan harga akan ketat.'); } if (competitionDensity === 'high') { insights.push('Kompetisi sangat tinggi. Pertimbangkan diferensiasi produk selain harga.'); } else if (competitionDensity === 'low') { insights.push('Kompetisi rendah. Ada peluang besar untuk masuk ke pasar ini.'); } if (data.avgRating > 4.5) { insights.push('Rating produk rata-rata tinggi. Kualitas produk sangat penting di kategori ini.'); } if (priceRange / data.avgPrice > 2) { insights.push('Range harga sangat lebar. Target segmen harga spesifik untuk hasil optimal.'); } const priceTrend = data.medianPrice > data.avgPrice ? 'premium' : 'value'; insights.push( priceTrend === 'premium' ? 'Pasar cenderung premium - median di atas rata-rata.' : 'Pasar cenderung value-oriented - banyak produk di harga terjangkau.' ); // Market position recommendation let marketPosition: string; if (riskValue <= 0.35) { marketPosition = 'Masuk dengan harga kompetitif aman. Pasar stabil.'; } else if (riskValue <= 0.65) { marketPosition = 'Perlu strategi harga yang cermat. Monitor kompetitor secara berkala.'; } else { marketPosition = 'Pasar berisiko tinggi. Pertimbangkan niche atau diferensiasi kuat.'; } return { entryPrice: Math.max(entryPrice, Math.round(data.minPrice * 1.05)), competitivePrice, premiumPrice: Math.min(premiumPrice, Math.round(data.maxPrice * 0.95)), riskScore, riskValue: Math.round(riskValue * 100) / 100, insights, priceRanges, competitionDensity, marketPosition, }; } private calculateCompetitionDensity(totalProducts: number): 'low' | 'medium' | 'high' { if (totalProducts < 20) return 'low'; if (totalProducts < 100) return 'medium'; return 'high'; } private calculatePercentile(values: number[], percentile: number): number { const sorted = [...values].sort((a, b) => a - b); const index = (percentile / 100) * (sorted.length - 1); const lower = Math.floor(index); const upper = Math.ceil(index); const weight = index - lower; if (upper >= sorted.length) return sorted[sorted.length - 1]; return sorted[lower] * (1 - weight) + sorted[upper] * weight; } private calculateRisk( data: PriceData, coefficientOfVariation: number, competitionDensity: 'low' | 'medium' | 'high' ): number { let risk = 0; // CV contributes 30% to risk risk += Math.min(coefficientOfVariation, 1) * 0.3; // Competition density 25% const densityMap = { low: 0.2, medium: 0.5, high: 0.9 }; risk += densityMap[competitionDensity] * 0.25; // Rating distribution spread 20% if (data.avgRating < 4.0) { risk += 0.15; } else if (data.avgRating < 4.5) { risk += 0.08; } // Price concentration 25% const priceSpread = (data.maxPrice - data.minPrice) / data.avgPrice; risk += Math.min(priceSpread / 3, 1) * 0.25; return Math.min(risk, 1); } /** * Calculate basic statistics from price array */ calculateStats(prices: number[]) { if (prices.length === 0) return null; const sorted = [...prices].sort((a, b) => a - b); const sum = sorted.reduce((a, b) => a + b, 0); const avg = sum / sorted.length; const median = sorted.length % 2 === 0 ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2 : sorted[Math.floor(sorted.length / 2)]; const variance = sorted.reduce((acc, val) => acc + Math.pow(val - avg, 2), 0) / sorted.length; const stdDev = Math.sqrt(variance); // IQR outlier detection const q1 = this.calculatePercentile(sorted, 25); const q3 = this.calculatePercentile(sorted, 75); const iqr = q3 - q1; const lowerBound = q1 - 1.5 * iqr; const upperBound = q3 + 1.5 * iqr; const outliers = sorted.filter((p) => p < lowerBound || p > upperBound); const cleanPrices = sorted.filter((p) => p >= lowerBound && p <= upperBound); return { min: sorted[0], max: sorted[sorted.length - 1], avg: Math.round(avg), median: Math.round(median), stdDeviation: Math.round(stdDev), q1: Math.round(q1), q3: Math.round(q3), iqr: Math.round(iqr), outlierCount: outliers.length, outlierBounds: { lower: Math.round(lowerBound), upper: Math.round(upperBound) }, cleanAvg: cleanPrices.length > 0 ? Math.round(cleanPrices.reduce((a, b) => a + b, 0) / cleanPrices.length) : avg, top10Lowest: sorted.slice(0, 10), top10Highest: sorted.slice(-10).reverse(), }; } /** * Basic K-means clustering for price segmentation */ kMeansClustering(prices: number[], k = 3, maxIterations = 100): Array<{ centroid: number; items: number[] }> { if (prices.length < k) { return [{ centroid: prices.reduce((a, b) => a + b, 0) / prices.length, items: prices }]; } // Initialize centroids evenly spaced const sorted = [...prices].sort((a, b) => a - b); let centroids: number[] = []; for (let i = 0; i < k; i++) { const idx = Math.floor((i / k) * sorted.length); centroids.push(sorted[idx]); } let assignments: number[] = new Array(prices.length).fill(0); let converged = false; let iteration = 0; while (!converged && iteration < maxIterations) { converged = true; iteration++; // Assign each price to nearest centroid for (let i = 0; i < prices.length; i++) { let minDist = Infinity; let bestCluster = 0; for (let j = 0; j < k; j++) { const dist = Math.abs(prices[i] - centroids[j]); if (dist < minDist) { minDist = dist; bestCluster = j; } } if (assignments[i] !== bestCluster) { assignments[i] = bestCluster; converged = false; } } // Update centroids for (let j = 0; j < k; j++) { const clusterPrices = prices.filter((_, idx) => assignments[idx] === j); if (clusterPrices.length > 0) { centroids[j] = clusterPrices.reduce((a, b) => a + b, 0) / clusterPrices.length; } } } // Build result const clusters: Array<{ centroid: number; items: number[] }> = []; for (let j = 0; j < k; j++) { const items = prices.filter((_, idx) => assignments[idx] === j).sort((a, b) => a - b); if (items.length > 0) { clusters.push({ centroid: Math.round(centroids[j]), items, }); } } return clusters.sort((a, b) => a.centroid - b.centroid); } } export default new AIEngine();