CoolFace
Apppublic

ModelMuse02/AI_Sales_Forecasting

sourceHugging Faceupdated 7mo agoView on Hugging Face
0likes
kpiCalculation.ts274 linesDownload Raw Back to root
1/**2 * KPI Calculation Module3 * Computes business metrics and performance indicators4 */5 6import {7  CleanedDataRow,8  MonthlyAggregation,9  ProductAggregation,10  KPIMetrics,11} from '../types';12 13/**14 * Calculate standard deviation15 */16function standardDeviation(values: number[]): number {17  if (values.length === 0) return 0;18  const mean = values.reduce((a, b) => a + b, 0) / values.length;19  const squaredDiffs = values.map(v => Math.pow(v - mean, 2));20  return Math.sqrt(squaredDiffs.reduce((a, b) => a + b, 0) / values.length);21}22 23/**24 * Calculate coefficient of variation (volatility)25 */26function coefficientOfVariation(values: number[]): number {27  if (values.length === 0) return 0;28  const mean = values.reduce((a, b) => a + b, 0) / values.length;29  if (mean === 0) return 0;30  return (standardDeviation(values) / mean) * 100;31}32 33/**34 * Classify volatility level35 */36function classifyVolatility(cv: number): 'Low' | 'Moderate' | 'High' | 'Very High' {37  if (cv < 15) return 'Low';38  if (cv < 30) return 'Moderate';39  if (cv < 50) return 'High';40  return 'Very High';41}42 43/**44 * Calculate Herfindahl-Hirschman Index for concentration45 */46function calculateHHI(products: ProductAggregation[]): number {47  const totalRevenue = products.reduce((sum, p) => sum + p.totalRevenue, 0);48  if (totalRevenue === 0) return 0;49  50  return products.reduce((sum, p) => {51    const share = (p.totalRevenue / totalRevenue) * 100;52    return sum + Math.pow(share, 2);53  }, 0);54}55 56/**57 * Classify concentration risk based on HHI58 */59function classifyConcentration(hhi: number): 'Low' | 'Moderate' | 'High' {60  if (hhi < 1500) return 'Low';61  if (hhi < 2500) return 'Moderate';62  return 'High';63}64 65/**66 * Calculate month-over-month growth rates67 */68function calculateGrowthRates(monthlyData: MonthlyAggregation[]): number[] {69  const growthRates: number[] = [];70  71  for (let i = 1; i < monthlyData.length; i++) {72    const previous = monthlyData[i - 1].totalRevenue;73    const current = monthlyData[i].totalRevenue;74    75    if (previous > 0) {76      const growthRate = ((current - previous) / previous) * 100;77      growthRates.push(growthRate);78    }79  }80  81  return growthRates;82}83 84/**85 * Detect seasonality strength using autocorrelation86 */87function detectSeasonalityStrength(monthlyData: MonthlyAggregation[]): number {88  if (monthlyData.length < 13) return 0;89  90  const revenues = monthlyData.map(m => m.totalRevenue);91  const mean = revenues.reduce((a, b) => a + b, 0) / revenues.length;92  93  // Calculate lag-12 autocorrelation (yearly seasonality)94  let numerator = 0;95  let denominator = 0;96  97  for (let i = 0; i < revenues.length - 12; i++) {98    numerator += (revenues[i] - mean) * (revenues[i + 12] - mean);99  }100  101  for (let i = 0; i < revenues.length; i++) {102    denominator += Math.pow(revenues[i] - mean, 2);103  }104  105  if (denominator === 0) return 0;106  107  const autocorrelation = numerator / denominator;108  return Math.max(0, Math.min(1, autocorrelation));109}110 111/**112 * Find peak and low seasons113 */114function findSeasons(monthlyData: MonthlyAggregation[]): { peak: string | null; low: string | null } {115  if (monthlyData.length === 0) {116    return { peak: null, low: null };117  }118  119  // Group by month of year120  const monthlyAverages = new Map<number, number[]>();121  122  for (const data of monthlyData) {123    const month = data.date.getMonth();124    if (!monthlyAverages.has(month)) {125      monthlyAverages.set(month, []);126    }127    monthlyAverages.get(month)!.push(data.totalRevenue);128  }129  130  const monthNames = ['January', 'February', 'March', 'April', 'May', 'June',131                      'July', 'August', 'September', 'October', 'November', 'December'];132  133  let peakMonth = 0;134  let lowMonth = 0;135  let maxAvg = -Infinity;136  let minAvg = Infinity;137  138  for (const [month, values] of monthlyAverages) {139    const avg = values.reduce((a, b) => a + b, 0) / values.length;140    if (avg > maxAvg) {141      maxAvg = avg;142      peakMonth = month;143    }144    if (avg < minAvg) {145      minAvg = avg;146      lowMonth = month;147    }148  }149  150  return {151    peak: monthNames[peakMonth],152    low: monthNames[lowMonth],153  };154}155 156/**157 * Calculate all KPIs from the dataset158 */159export function calculateKPIs(160  cleanedData: CleanedDataRow[],161  monthlyData: MonthlyAggregation[],162  productData: ProductAggregation[]163): KPIMetrics {164  // Basic aggregations165  const totalRevenue = cleanedData.reduce((sum, row) => sum + row.revenue, 0);166  const totalUnits = cleanedData.reduce((sum, row) => sum + row.unitsSold, 0);167  const totalTransactions = cleanedData.length;168  const averageOrderValue = totalTransactions > 0 ? totalRevenue / totalTransactions : 0;169  170  // Profit metrics (if cost data available)171  const hasCost = cleanedData.some(row => row.cost !== null);172  let totalProfit: number | null = null;173  let profitMargin: number | null = null;174  175  if (hasCost) {176    totalProfit = cleanedData.reduce((sum, row) => sum + (row.profit || 0), 0);177    profitMargin = totalRevenue > 0 ? (totalProfit / totalRevenue) * 100 : null;178  }179  180  // Growth metrics181  const growthRates = calculateGrowthRates(monthlyData);182  const averageMonthlyGrowth = growthRates.length > 0183    ? growthRates.reduce((a, b) => a + b, 0) / growthRates.length184    : 0;185  186  // Overall revenue growth (first to last period)187  let revenueGrowthRate = 0;188  if (monthlyData.length >= 2) {189    const first = monthlyData[0].totalRevenue;190    const last = monthlyData[monthlyData.length - 1].totalRevenue;191    revenueGrowthRate = first > 0 ? ((last - first) / first) * 100 : 0;192  }193  194  // Volatility metrics195  const monthlyRevenues = monthlyData.map(m => m.totalRevenue);196  const revenueVolatility = coefficientOfVariation(monthlyRevenues);197  const volatilityClassification = classifyVolatility(revenueVolatility);198  199  // Best and worst months200  let bestMonth: { period: string; revenue: number } | null = null;201  let worstMonth: { period: string; revenue: number } | null = null;202  203  if (monthlyData.length > 0) {204    const sorted = [...monthlyData].sort((a, b) => b.totalRevenue - a.totalRevenue);205    bestMonth = { period: sorted[0].period, revenue: sorted[0].totalRevenue };206    worstMonth = { period: sorted[sorted.length - 1].period, revenue: sorted[sorted.length - 1].totalRevenue };207  }208  209  // Product concentration210  const herfindahlIndex = calculateHHI(productData);211  const concentrationRisk = classifyConcentration(herfindahlIndex);212  const topProduct = productData[0];213  const productConcentration = topProduct ? topProduct.percentage : 0;214  215  // Seasonality216  const seasonalityStrength = detectSeasonalityStrength(monthlyData);217  const seasons = findSeasons(monthlyData);218  219  return {220    totalRevenue,221    totalUnits,222    totalTransactions,223    averageOrderValue,224    totalProfit,225    profitMargin,226    revenueGrowthRate,227    monthlyGrowthRates: growthRates,228    averageMonthlyGrowth,229    revenueVolatility,230    volatilityClassification,231    bestMonth,232    worstMonth,233    productConcentration,234    topProducts: productData.slice(0, 5),235    herfindahlIndex,236    concentrationRisk,237    seasonalityStrength,238    peakSeason: seasons.peak,239    lowSeason: seasons.low,240  };241}242 243/**244 * Format currency value245 */246export function formatCurrency(value: number): string {247  return new Intl.NumberFormat('en-US', {248    style: 'currency',249    currency: 'USD',250    minimumFractionDigits: 0,251    maximumFractionDigits: 0,252  }).format(value);253}254 255/**256 * Format percentage value257 */258export function formatPercentage(value: number): string {259  return `${value >= 0 ? '+' : ''}${value.toFixed(1)}%`;260}261 262/**263 * Format large numbers with abbreviations264 */265export function formatNumber(value: number): string {266  if (value >= 1000000) {267    return `${(value / 1000000).toFixed(1)}M`;268  }269  if (value >= 1000) {270    return `${(value / 1000).toFixed(1)}K`;271  }272  return value.toFixed(0);273}274