// server.ts - Simplified main server file // package.json dependencies needed: // npm install express mathjs lodash date-fns // npm install -D @types/express @types/node @types/lodash typescript ts-node import express from 'express'; import swaggerJsdoc from 'swagger-jsdoc'; import swaggerUi from 'swagger-ui-express'; import * as math from 'mathjs'; import * as _ from 'lodash'; import { KMeans, KMeansOptions } from './kmeans'; import { getWeekNumber, getSameWeekDayLastYear } from './time-helper'; import { calculateLinearRegression, generateForecast, calculatePredictionIntervals, ForecastResult } from './prediction'; const app = express(); app.use(express.json()); const PORT = process.env.PORT || 3000; const swaggerOptions = { swaggerDefinition: { openapi: '3.0.0', info: { title: 'My Express API', version: '1.0.0', description: 'API documentation for my awesome Express app', }, servers: [ { url: `http://localhost:${PORT}`, }, ], }, // Paths to files containing OpenAPI definitions apis: ["./*.ts"], // Make sure this path is correct }; const swaggerSpec = swaggerJsdoc(swaggerOptions); app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); // ======================================== // TYPE DEFINITIONS // ======================================== interface DataSeries { values: number[]; labels?: string[]; } interface DataMatrix { data: number[][]; columns?: string[]; rows?: string[]; } interface Condition { field: string; operator: '>' | '<' | '=' | '>=' | '<=' | '!='; value: number | string; } interface ApiResponse { success: boolean; data?: T; error?: string; } // ======================================== // HELPER FUNCTIONS // ======================================== const handleError = (error: unknown): string => { return error instanceof Error ? error.message : 'Unknown error'; }; const validateSeries = (series: DataSeries): void => { if (!series || !Array.isArray(series.values) || series.values.length === 0) { throw new Error('Series must contain at least one value'); } }; const validateMatrix = (matrix: DataMatrix): void => { if (!matrix || !Array.isArray(matrix.data) || matrix.data.length === 0) { throw new Error('Matrix must contain at least one row'); } }; /** * A helper class to provide a fluent API for rolling window calculations. */ class RollingWindow { private windows: number[][]; constructor(windows: number[][]) { this.windows = windows; } mean(): number[] { return this.windows.map(window => Number(math.mean(window))); } sum(): number[] { return this.windows.map(window => _.sum(window)); } min(): number[] { return this.windows.map(window => Math.min(...window)); } max(): number[] { return this.windows.map(window => Math.max(...window)); } toArray(): number[][] { return this.windows; } } // ======================================== // ANALYTICS ENGINE (Simplified) // ======================================== class AnalyticsEngine { private applyConditions(series: DataSeries, conditions: Condition[] = []): number[] { if (conditions.length === 0) return series.values; return series.values; // TODO: Implement filtering } // Basic statistical functions unique(series: DataSeries): number[] { validateSeries(series); return _.uniq(series.values); } mean(series: DataSeries, conditions: Condition[] = []): number { validateSeries(series); const filteredValues = this.applyConditions(series, conditions); if (filteredValues.length === 0) throw new Error('No data points match conditions'); return Number(math.mean(filteredValues)); } count(series: DataSeries, conditions: Condition[] = []): number { validateSeries(series); const filteredValues = this.applyConditions(series, conditions); if (filteredValues.length === 0) throw new Error('No data points match conditions'); return filteredValues.length; } variance(series: DataSeries, conditions: Condition[] = []): number { validateSeries(series); const filteredValues = this.applyConditions(series, conditions); if (filteredValues.length === 0) throw new Error('No data points match conditions'); return Number(math.variance(filteredValues)); } standardDeviation(series: DataSeries, conditions: Condition[] = []): number { validateSeries(series); const filteredValues = this.applyConditions(series, conditions); if (filteredValues.length === 0) throw new Error('No data points match conditions'); return Number(math.std(filteredValues)); } percentile(series: DataSeries, percent: number, ascending: boolean = true, conditions: Condition[] = []): number { validateSeries(series); 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; } median(series: DataSeries, conditions: Condition[] = []): number { return this.percentile(series, 50, true, conditions); } mode(series: DataSeries, conditions: Condition[] = []): number[] { validateSeries(series); 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); } max(series: DataSeries, conditions: Condition[] = []): number { validateSeries(series); const filteredValues = this.applyConditions(series, conditions); if (filteredValues.length === 0) throw new Error('No data points match conditions'); return Math.max(...filteredValues); } min(series: DataSeries, conditions: Condition[] = []): number { validateSeries(series); const filteredValues = this.applyConditions(series, conditions); if (filteredValues.length === 0) throw new Error('No data points match conditions'); return Math.min(...filteredValues); } correlation(series1: DataSeries, series2: DataSeries): number { validateSeries(series1); validateSeries(series2); 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; } // Rolling window functions rolling(series: DataSeries, windowSize: number): RollingWindow { validateSeries(series); if (windowSize <= 0) { throw new Error('Window size must be a positive number.'); } if (series.values.length < windowSize) { return new RollingWindow([]); } const windows: number[][] = []; for (let i = 0; i <= series.values.length - windowSize; i++) { const window = series.values.slice(i, i + windowSize); windows.push(window); } return new RollingWindow(windows); } movingAverage(series: DataSeries, windowSize: number): number[] { return this.rolling(series, windowSize).mean(); } // K-means wrapper (uses imported KMeans class) kmeans(matrix: DataMatrix, nClusters: number, options: KMeansOptions = {}): { clusters: number[][][], centroids: number[][] } { validateMatrix(matrix); const points: number[][] = matrix.data; // Use the new MiniBatchKMeans class const kmeans = new KMeans(points, nClusters, options); const result = kmeans.run(); const centroids = result.clusters.map(c => c.centroid); const clusters = result.clusters.map(c => c.points); return { clusters, centroids }; } // Time helper wrapper functions getWeekNumber(dateString: string): number { return getWeekNumber(dateString); } getSameWeekDayLastYear(dateString: string): string { return getSameWeekDayLastYear(dateString); } // Retail functions purchaseRate(productPurchases: number, totalTransactions: number): number { if (totalTransactions === 0) throw new Error('Total transactions cannot be zero'); return (productPurchases / totalTransactions) * 100; } liftValue(jointPurchaseRate: number, productAPurchaseRate: number, productBPurchaseRate: number): number { const expectedJointRate = productAPurchaseRate * productBPurchaseRate; if (expectedJointRate === 0) throw new Error('Expected joint rate cannot be zero'); return jointPurchaseRate / expectedJointRate; } costRatio(cost: number, salePrice: number): number { if (salePrice === 0) throw new Error('Sale price cannot be zero'); return cost / salePrice; } grossMarginRate(salePrice: number, cost: number): number { if (salePrice === 0) throw new Error('Sale price cannot be zero'); return (salePrice - cost) / salePrice; } averageSpendPerCustomer(totalRevenue: number, numberOfCustomers: number): number { if (numberOfCustomers === 0) { throw new Error('Number of customers cannot be zero'); } return totalRevenue / numberOfCustomers; } purchaseIndex(totalItemsSold: number, numberOfCustomers: number): number { if (numberOfCustomers === 0) { throw new Error('Number of customers cannot be zero'); } return (totalItemsSold / numberOfCustomers) * 1000; } // ======================================== // Prediction functions // ======================================== timeSeriesForecast(series: DataSeries, forecastPeriods: number): ForecastResult { validateSeries(series); const model = calculateLinearRegression(series.values); const forecast = generateForecast(model, series.values.length, forecastPeriods); const predictionIntervals = calculatePredictionIntervals(series.values, model, forecast); return { forecast, predictionIntervals, modelParameters: { slope: model.slope, intercept: model.intercept, }, }; } } // Initialize analytics engine const analytics = new AnalyticsEngine(); // ======================================== // API ROUTES // ======================================== /** * @swagger * /api/health: * get: * summary: Health check endpoint * description: Returns the health status of the API * responses: * 200: * description: API is healthy * content: * application/json: * schema: * type: object * properties: * status: * type: string * example: OK * timestamp: * type: string * format: date-time */ app.get('/api/health', (req, res) => { res.status(200).json({ status: 'OK', timestamp: new Date().toISOString() }); }); /** * @swagger * /api/unique: * post: * summary: Get unique values from a data series * description: Returns an array of unique values from the provided data series * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * series: * $ref: '#/components/schemas/DataSeries' * responses: * 200: * description: Unique values calculated successfully * 400: * description: Invalid input data */ app.post('/api/unique', (req, res) => { try { const result = analytics.unique(req.body.series); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/mean: * post: * summary: Calculate mean of a data series * description: Returns the arithmetic mean of the provided data series, optionally filtered by conditions * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * series: * $ref: '#/components/schemas/DataSeries' * conditions: * type: array * items: * $ref: '#/components/schemas/Condition' * responses: * 200: * description: Mean calculated successfully * 400: * description: Invalid input data */ app.post('/api/mean', (req, res) => { try { const result = analytics.mean(req.body.series, req.body.conditions); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/count: * post: * summary: Count data points in a series * description: Returns the count of data points in the series, optionally filtered by conditions * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * series: * $ref: '#/components/schemas/DataSeries' * conditions: * type: array * items: * $ref: '#/components/schemas/Condition' * responses: * 200: * description: Count calculated successfully * 400: * description: Invalid input data */ app.post('/api/count', (req, res) => { try { const result = analytics.count(req.body.series, req.body.conditions); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/variance: * post: * summary: Calculate variance of a data series * description: Returns the variance of the provided data series * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * series: * $ref: '#/components/schemas/DataSeries' * conditions: * type: array * items: * $ref: '#/components/schemas/Condition' * responses: * 200: * description: Variance calculated successfully * 400: * description: Invalid input data */ app.post('/api/variance', (req, res) => { try { const result = analytics.variance(req.body.series, req.body.conditions); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/std: * post: * summary: Calculate standard deviation of a data series * description: Returns the standard deviation of the provided data series * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * series: * $ref: '#/components/schemas/DataSeries' * conditions: * type: array * items: * $ref: '#/components/schemas/Condition' * responses: * 200: * description: Standard deviation calculated successfully * 400: * description: Invalid input data */ app.post('/api/std', (req, res) => { try { const result = analytics.standardDeviation(req.body.series, req.body.conditions); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/percentile: * post: * summary: Calculate percentile of a data series * description: Returns the specified percentile of the provided data series * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * series: * $ref: '#/components/schemas/DataSeries' * percent: * type: number * description: Percentile to calculate (0-100) * example: 95 * ascending: * type: boolean * description: Sort order * default: true * conditions: * type: array * items: * $ref: '#/components/schemas/Condition' * responses: * 200: * description: Percentile calculated successfully * 400: * description: Invalid input data */ app.post('/api/percentile', (req, res) => { try { const result = analytics.percentile(req.body.series, req.body.percent, req.body.ascending, req.body.conditions); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/median: * post: * summary: Calculate median of a data series * description: Returns the median (50th percentile) of the provided data series * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * series: * $ref: '#/components/schemas/DataSeries' * conditions: * type: array * items: * $ref: '#/components/schemas/Condition' * responses: * 200: * description: Median calculated successfully * 400: * description: Invalid input data */ app.post('/api/median', (req, res) => { try { const result = analytics.median(req.body.series, req.body.conditions); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/mode: * post: * summary: Calculate mode of a data series * description: Returns the mode (most frequent values) of the provided data series * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * series: * $ref: '#/components/schemas/DataSeries' * conditions: * type: array * items: * $ref: '#/components/schemas/Condition' * responses: * 200: * description: Mode calculated successfully * 400: * description: Invalid input data */ app.post('/api/mode', (req, res) => { try { const result = analytics.mode(req.body.series, req.body.conditions); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/max: * post: * summary: Find maximum value in a data series * description: Returns the maximum value from the provided data series * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * series: * $ref: '#/components/schemas/DataSeries' * conditions: * type: array * items: * $ref: '#/components/schemas/Condition' * responses: * 200: * description: Maximum value found successfully * 400: * description: Invalid input data */ app.post('/api/max', (req, res) => { try { const result = analytics.max(req.body.series, req.body.conditions); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/min: * post: * summary: Find minimum value in a data series * description: Returns the minimum value from the provided data series * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * series: * $ref: '#/components/schemas/DataSeries' * conditions: * type: array * items: * $ref: '#/components/schemas/Condition' * responses: * 200: * description: Minimum value found successfully * 400: * description: Invalid input data */ app.post('/api/min', (req, res) => { try { const result = analytics.min(req.body.series, req.body.conditions); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/correlation: * post: * summary: Calculate correlation between two data series * description: Returns the Pearson correlation coefficient between two data series * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * series1: * $ref: '#/components/schemas/DataSeries' * series2: * $ref: '#/components/schemas/DataSeries' * responses: * 200: * description: Correlation calculated successfully * 400: * description: Invalid input data or series have different lengths */ app.post('/api/correlation', (req, res) => { try { const result = analytics.correlation(req.body.series1, req.body.series2); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/series/moving-average: * post: * summary: Calculate moving average of a data series * description: Returns the moving average of the provided data series with specified window size * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * series: * $ref: '#/components/schemas/DataSeries' * windowSize: * type: integer * description: Size of the moving window * minimum: 1 * example: 5 * responses: * 200: * description: Moving average calculated successfully * 400: * description: Invalid input data or window size */ app.post('/api/series/moving-average', (req, res) => { try { const { series, windowSize } = req.body; const result = analytics.movingAverage(series, windowSize); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/series/rolling: * post: * summary: Get rolling windows of a data series * description: Returns rolling windows of the provided data series with specified window size * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * series: * $ref: '#/components/schemas/DataSeries' * windowSize: * type: integer * description: Size of the rolling window * minimum: 1 * example: 3 * responses: * 200: * description: Rolling windows calculated successfully * 400: * description: Invalid input data or window size */ app.post('/api/series/rolling', (req, res) => { try { const { series, windowSize } = req.body; const result = analytics.rolling(series, windowSize).toArray(); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/ml/kmeans: * post: * summary: Perform K-means clustering * description: Performs K-means clustering on the provided data matrix * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * matrix: * $ref: '#/components/schemas/DataMatrix' * nClusters: * type: integer * description: Number of clusters * minimum: 1 * example: 3 * options: * type: object * description: K-means options * responses: * 200: * description: K-means clustering completed successfully * 400: * description: Invalid input data */ app.post('/api/ml/kmeans', (req, res) => { try { const result = analytics.kmeans(req.body.matrix, req.body.nClusters, req.body.options); res.status(200).json({ success: true, data: result } as ApiResponse<{ clusters: number[][][], centroids: number[][] }>); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse<{ clusters: number[][][], centroids: number[][] }>); } }); /** * @swagger * /api/time/week-number: * post: * summary: Get week number from date * description: Returns the ISO week number for the provided date string * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * date: * type: string * format: date * description: Date string in ISO format * example: "2024-03-15" * responses: * 200: * description: Week number calculated successfully * 400: * description: Invalid date format */ app.post('/api/time/week-number', (req, res) => { try { const { date } = req.body; const result = analytics.getWeekNumber(date); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/time/same-day-last-year: * post: * summary: Get same day of week from last year * description: Returns the date string for the same day of the week from the previous year * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * date: * type: string * format: date * description: Date string in ISO format * example: "2024-03-15" * responses: * 200: * description: Same day last year calculated successfully * 400: * description: Invalid date format */ app.post('/api/time/same-day-last-year', (req, res) => { try { const { date } = req.body; const result = analytics.getSameWeekDayLastYear(date); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/retail/purchase-rate: * post: * summary: Calculate purchase rate * description: Calculates the purchase rate as a percentage of product purchases over total transactions * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * productPurchases: * type: number * description: Number of product purchases * example: 150 * totalTransactions: * type: number * description: Total number of transactions * example: 1000 * responses: * 200: * description: Purchase rate calculated successfully * 400: * description: Invalid input data or division by zero */ app.post('/api/retail/purchase-rate', (req, res) => { try { const result = analytics.purchaseRate(req.body.productPurchases, req.body.totalTransactions); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/retail/lift-value: * post: * summary: Calculate lift value * description: Calculates the lift value for market basket analysis * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * jointPurchaseRate: * type: number * description: Joint purchase rate of both products * example: 0.05 * productAPurchaseRate: * type: number * description: Purchase rate of product A * example: 0.2 * productBPurchaseRate: * type: number * description: Purchase rate of product B * example: 0.3 * responses: * 200: * description: Lift value calculated successfully * 400: * description: Invalid input data or division by zero */ app.post('/api/retail/lift-value', (req, res) => { try { const result = analytics.liftValue(req.body.jointPurchaseRate, req.body.productAPurchaseRate, req.body.productBPurchaseRate); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/retail/cost-ratio: * post: * summary: Calculate cost ratio * description: Calculates the cost ratio (cost divided by sale price) * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * cost: * type: number * description: Cost of the product * example: 50 * salePrice: * type: number * description: Sale price of the product * example: 100 * responses: * 200: * description: Cost ratio calculated successfully * 400: * description: Invalid input data or division by zero */ app.post('/api/retail/cost-ratio', (req, res) => { try { const result = analytics.costRatio(req.body.cost, req.body.salePrice); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/retail/gross-margin: * post: * summary: Calculate gross margin rate * description: Calculates the gross margin rate as a percentage * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * salePrice: * type: number * description: Sale price of the product * example: 100 * cost: * type: number * description: Cost of the product * example: 60 * responses: * 200: * description: Gross margin rate calculated successfully * 400: * description: Invalid input data or division by zero */ app.post('/api/retail/gross-margin', (req, res) => { try { const result = analytics.grossMarginRate(req.body.salePrice, req.body.cost); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/retail/average-spend: * post: * summary: Calculate average spend per customer * description: Calculates the average amount spent per customer * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * totalRevenue: * type: number * description: Total revenue * example: 50000 * numberOfCustomers: * type: number * description: Number of customers * example: 500 * responses: * 200: * description: Average spend calculated successfully * 400: * description: Invalid input data or division by zero */ app.post('/api/retail/average-spend', (req, res) => { try { const { totalRevenue, numberOfCustomers } = req.body; const result = analytics.averageSpendPerCustomer(totalRevenue, numberOfCustomers); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/retail/purchase-index: * post: * summary: Calculate purchase index * description: Calculates the purchase index (items per 1000 customers) * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * totalItemsSold: * type: number * description: Total number of items sold * example: 2500 * numberOfCustomers: * type: number * description: Number of customers * example: 1000 * responses: * 200: * description: Purchase index calculated successfully * 400: * description: Invalid input data or division by zero */ app.post('/api/retail/purchase-index', (req, res) => { try { const { totalItemsSold, numberOfCustomers } = req.body; const result = analytics.purchaseIndex(totalItemsSold, numberOfCustomers); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); /** * @swagger * /api/predict/forecast: * post: * summary: Generate time series forecast * description: Generates a forecast for time series data using linear regression * requestBody: * required: true * content: * application/json: * schema: * type: object * properties: * series: * $ref: '#/components/schemas/DataSeries' * forecastPeriods: * type: integer * description: Number of periods to forecast * minimum: 1 * example: 10 * responses: * 200: * description: Forecast generated successfully * 400: * description: Invalid input data */ app.post('/api/predict/forecast', (req, res) => { try { const { series, forecastPeriods } = req.body; const result = analytics.timeSeriesForecast(series, forecastPeriods); res.status(200).json({ success: true, data: result } as ApiResponse); } catch (error) { const errorMessage = handleError(error); res.status(400).json({ success: false, error: errorMessage } as ApiResponse); } }); // ======================================== // SWAGGER COMPONENTS // ======================================== /** * @swagger * components: * schemas: * DataSeries: * type: object * required: * - values * properties: * values: * type: array * items: * type: number * description: Array of numerical values * example: [1, 2, 3, 4, 5] * labels: * type: array * items: * type: string * description: Optional labels for the values * example: ["Jan", "Feb", "Mar", "Apr", "May"] * * DataMatrix: * type: object * required: * - data * properties: * data: * type: array * items: * type: array * items: * type: number * description: 2D array of numerical values * example: [[1, 2], [3, 4], [5, 6]] * columns: * type: array * items: * type: string * description: Optional column names * example: ["x", "y"] * rows: * type: array * items: * type: string * description: Optional row names * example: ["row1", "row2", "row3"] * * Condition: * type: object * required: * - field * - operator * - value * properties: * field: * type: string * description: Field name to apply condition on * example: "value" * operator: * type: string * enum: [">", "<", "=", ">=", "<=", "!="] * description: Comparison operator * example: ">" * value: * oneOf: * - type: number * - type: string * description: Value to compare against * example: 10 * * ApiResponse: * type: object * properties: * success: * type: boolean * description: Whether the request was successful * data: * description: Response data (varies by endpoint) * error: * type: string * description: Error message if success is false */ /** * @swagger * /api/docs/export/json: * get: * summary: Export API documentation as JSON * description: Returns the complete OpenAPI specification in JSON format * responses: * 200: * description: OpenAPI specification in JSON format * content: * application/json: * schema: * type: object */ app.get('/api/docs/export/json', (req, res) => { res.setHeader('Content-Disposition', 'attachment; filename="api-documentation.json"'); res.setHeader('Content-Type', 'application/json'); res.json(swaggerSpec); }); /** * @swagger * /api/docs/export/yaml: * get: * summary: Export API documentation as YAML * description: Returns the complete OpenAPI specification in YAML format * responses: * 200: * description: OpenAPI specification in YAML format * content: * text/yaml: * schema: * type: string */ app.get('/api/docs/export/yaml', (req, res) => { const yaml = require('js-yaml'); const yamlString = yaml.dump(swaggerSpec, { indent: 2 }); res.setHeader('Content-Disposition', 'attachment; filename="api-documentation.yaml"'); res.setHeader('Content-Type', 'text/yaml'); res.send(yamlString); }); /** * @swagger * /api/docs/export/html: * get: * summary: Export API documentation as HTML * description: Returns a standalone HTML file with the complete API documentation * responses: * 200: * description: Standalone HTML documentation * content: * text/html: * schema: * type: string */ app.get('/api/docs/export/html', (req, res) => { const htmlTemplate = ` API Documentation
`; res.setHeader('Content-Disposition', 'attachment; filename="api-documentation.html"'); res.setHeader('Content-Type', 'text/html'); res.send(htmlTemplate); }); // ======================================== // ERROR HANDLING // ======================================== 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); }); app.use('*', (req, res) => { res.status(404).json({ success: false, error: 'Endpoint not found' }); }); // ======================================== // SERVER STARTUP // ======================================== app.listen(PORT, () => { console.log(`Analytics API server running on port ${PORT}`); console.log(`Health check: http://localhost:${PORT}/api/health`); console.log(`API Documentation: http://localhost:${PORT}/api-docs`); }); export default app;