// package.json dependencies needed: // npm install express mathjs lodash // npm install -D @types/express @types/node @types/lodash typescript ts-node import express from 'express'; import * as math from 'mathjs'; import * as _ from 'lodash'; const app = express(); app.use(express.json()); // Types for our data structures interface DataSeries { values: number[]; labels?: string[]; } interface Condition { field: string; operator: '>' | '<' | '=' | '>=' | '<=' | '!='; value: number | string; } interface ApiResponse { success: boolean; data?: T; error?: string; } // Helper function for error handling const handleError = (error: unknown): string => { return error instanceof Error ? error.message : 'Unknown error'; }; // Core statistical functions class AnalyticsEngine { // Apply conditions to filter data private applyConditions(series: DataSeries, conditions: Condition[] = []): number[] { if (conditions.length === 0) return series.values; // For now, just return all values - you'd implement condition logic here // This would involve checking conditions against associated metadata return series.values; } // Remove duplicates from series unique(series: DataSeries): number[] { return _.uniq(series.values); } // Calculate mean with optional conditions mean(series: DataSeries, conditions: Condition[] = []): number { const filteredValues = this.applyConditions(series, conditions); if (filteredValues.length === 0) throw new Error('No data points match conditions'); return Number(math.mean(filteredValues)); } // Count values with optional conditions count(series: DataSeries, conditions: Condition[] = []): number { const filteredValues = this.applyConditions(series, conditions); return filteredValues.length; } // Calculate variance variance(series: DataSeries, conditions: Condition[] = []): number { const filteredValues = this.applyConditions(series, conditions); if (filteredValues.length === 0) throw new Error('No data points match conditions'); return Number(math.variance(filteredValues)); } // Calculate standard deviation standardDeviation(series: DataSeries, conditions: Condition[] = []): number { const filteredValues = this.applyConditions(series, conditions); if (filteredValues.length === 0) throw new Error('No data points match conditions'); return Number(math.std(filteredValues)); } // Calculate percentile/quantile percentile( series: DataSeries, percent: number, ascending: boolean = true, conditions: Condition[] = [] ): number { const filteredValues = this.applyConditions(series, conditions); if (filteredValues.length === 0) throw new Error('No data points match conditions'); const sorted = ascending ? _.sortBy(filteredValues) : _.sortBy(filteredValues).reverse(); const index = (percent / 100) * (sorted.length - 1); const lower = Math.floor(index); const upper = Math.ceil(index); const weight = index % 1; return sorted[lower] * (1 - weight) + sorted[upper] * weight; } // Calculate median (50th percentile) median(series: DataSeries, conditions: Condition[] = []): number { return this.percentile(series, 50, true, conditions); } // Calculate mode (most frequent value) mode(series: DataSeries, conditions: Condition[] = []): number[] { const filteredValues = this.applyConditions(series, conditions); const frequency = _.countBy(filteredValues); const maxFreq = Math.max(...Object.values(frequency)); return Object.keys(frequency) .filter(key => frequency[key] === maxFreq) .map(Number); } // Rank values and get top N topN( series: DataSeries, n: number, ascending: boolean = false, conditions: Condition[] = [] ): number[] { const filteredValues = this.applyConditions(series, conditions); const sorted = ascending ? _.sortBy(filteredValues) : _.sortBy(filteredValues).reverse(); return sorted.slice(0, n); } // Get maximum value max(series: DataSeries, conditions: Condition[] = []): number { const filteredValues = this.applyConditions(series, conditions); if (filteredValues.length === 0) throw new Error('No data points match conditions'); return Math.max(...filteredValues); } // Get minimum value min(series: DataSeries, conditions: Condition[] = []): number { const filteredValues = this.applyConditions(series, conditions); if (filteredValues.length === 0) throw new Error('No data points match conditions'); return Math.min(...filteredValues); } // Calculate percent change percentChange(series: DataSeries, step: number = 1): number[] { const values = series.values; const changes: number[] = []; for (let i = step; i < values.length; i++) { const change = ((values[i] - values[i - step]) / values[i - step]) * 100; changes.push(change); } return changes; } // Basic correlation between two series correlation(series1: DataSeries, series2: DataSeries): number { if (series1.values.length !== series2.values.length) { throw new Error('Series must have same length for correlation'); } const x = series1.values; const y = series2.values; const n = x.length; const sumX = _.sum(x); const sumY = _.sum(y); const sumXY = _.sum(x.map((xi, i) => xi * y[i])); const sumX2 = _.sum(x.map(xi => xi * xi)); const sumY2 = _.sum(y.map(yi => yi * yi)); const numerator = n * sumXY - sumX * sumY; const denominator = Math.sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY)); return numerator / denominator; } } // Initialize analytics engine const analytics = new AnalyticsEngine(); // API Routes app.get('/api/health', (req, res) => { res.json({ status: 'OK', timestamp: new Date().toISOString() }); }); // Unique values endpoint app.post('/api/unique', (req, res) => { try { const { series }: { series: DataSeries } = req.body; const result = analytics.unique(series); res.json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); // Mean calculation endpoint app.post('/api/mean', (req, res) => { try { const { series, conditions = [] }: { series: DataSeries; conditions?: Condition[] } = req.body; const result = analytics.mean(series, conditions); res.json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); // Count endpoint app.post('/api/count', (req, res) => { try { const { series, conditions = [] }: { series: DataSeries; conditions?: Condition[] } = req.body; const result = analytics.count(series, conditions); res.json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); // Variance endpoint app.post('/api/variance', (req, res) => { try { const { series, conditions = [] }: { series: DataSeries; conditions?: Condition[] } = req.body; const result = analytics.variance(series, conditions); res.json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); // Standard deviation endpoint app.post('/api/std', (req, res) => { try { const { series, conditions = [] }: { series: DataSeries; conditions?: Condition[] } = req.body; const result = analytics.standardDeviation(series, conditions); res.json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); // Percentile endpoint app.post('/api/percentile', (req, res) => { try { const { series, percent, ascending = true, conditions = [] }: { series: DataSeries; percent: number; ascending?: boolean; conditions?: Condition[] } = req.body; const result = analytics.percentile(series, percent, ascending, conditions); res.json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); // Median endpoint app.post('/api/median', (req, res) => { try { const { series, conditions = [] }: { series: DataSeries; conditions?: Condition[] } = req.body; const result = analytics.median(series, conditions); res.json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); // Mode endpoint app.post('/api/mode', (req, res) => { try { const { series, conditions = [] }: { series: DataSeries; conditions?: Condition[] } = req.body; const result = analytics.mode(series, conditions); res.json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); // Top N endpoint app.post('/api/topn', (req, res) => { try { const { series, n, ascending = false, conditions = [] }: { series: DataSeries; n: number; ascending?: boolean; conditions?: Condition[] } = req.body; const result = analytics.topN(series, n, ascending, conditions); res.json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); // Max/Min endpoints app.post('/api/max', (req, res) => { try { const { series, conditions = [] }: { series: DataSeries; conditions?: Condition[] } = req.body; const result = analytics.max(series, conditions); res.json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); app.post('/api/min', (req, res) => { try { const { series, conditions = [] }: { series: DataSeries; conditions?: Condition[] } = req.body; const result = analytics.min(series, conditions); res.json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); // Percent change endpoint app.post('/api/percent-change', (req, res) => { try { const { series, step = 1 }: { series: DataSeries; step?: number } = req.body; const result = analytics.percentChange(series, step); res.json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); // Correlation endpoint app.post('/api/correlation', (req, res) => { try { const { series1, series2 }: { series1: DataSeries; series2: DataSeries } = req.body; const result = analytics.correlation(series1, series2); res.json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); // Error handling middleware app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => { console.error(err.stack); res.status(500).json({ success: false, error: 'Internal server error' } as ApiResponse); }); // Start server const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Analytics API server running on port ${PORT}`); console.log(`Health check: http://localhost:${PORT}/api/health`); }); export default app;