From 9d80922f5f64a40381976e85b78abec6d77a1484 Mon Sep 17 00:00:00 2001 From: raymond Date: Wed, 10 Sep 2025 06:19:10 +0000 Subject: [PATCH] =?UTF-8?q?server=5Fconvolution.ts=20=E3=82=92=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server_convolution.ts | 1801 ----------------------------------------- 1 file changed, 1801 deletions(-) delete mode 100644 server_convolution.ts diff --git a/server_convolution.ts b/server_convolution.ts deleted file mode 100644 index 4a9e7b3..0000000 --- a/server_convolution.ts +++ /dev/null @@ -1,1801 +0,0 @@ -// server.ts - Simplified main server file -// package.json dependencies needed: -// npm install express mathjs lodash date-fns swagger-jsdoc swagger-ui-express js-yaml -// 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'; - -// These imports assume the files exist in the same directory -// import { KMeans, KMeansOptions } from './kmeans'; -// import { getWeekNumber, getSameWeekDayLastYear } from './time-helper'; -// import { calculateLinearRegression, generateForecast, calculatePredictionIntervals, ForecastResult } from './prediction'; -import { SignalProcessor, SmoothingOptions, EdgeDetectionOptions } from './signal_processing_convolution'; -import { convolve1D, ConvolutionKernels } from './convolution'; // Direct import for new functions - -interface KMeansOptions {} -class KMeans { constructor(p: any, n: any, o: any) {}; run = () => ({ clusters: [] }) } -const getWeekNumber = (d: string) => 1; -const getSameWeekDayLastYear = (d: string) => new Date().toISOString(); -interface ForecastResult {} -const calculateLinearRegression = (v: any) => ({slope: 1, intercept: 0}); -const generateForecast = (m: any, l: any, p: any) => []; -const calculatePredictionIntervals = (v: any, m: any, f: any) => []; - - -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}`, - }, - ], - }, - apis: ["./server.ts"], // IMPORTANT: Changed to only scan this file -}; - -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 as any).centroid); - const clusters = result.clusters.map(c => (c as any).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 - * tags: [Health] - * 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 - * tags: [Statistics] - * 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 - * tags: [Statistics] - * 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 - * tags: [Statistics] - * 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 - * tags: [Statistics] - * 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 - * tags: [Statistics] - * 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 - * tags: [Statistics] - * 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 - * tags: [Statistics] - * 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 - * tags: [Statistics] - * 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 - * tags: [Statistics] - * 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 - * tags: [Statistics] - * 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 - * tags: [Statistics] - * 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 - * tags: [Series Operations] - * 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 - * tags: [Series Operations] - * 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 - * tags: [Machine Learning] - * 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 - * tags: [Time] - * 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 - * tags: [Time] - * 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 - * tags: [Retail] - * 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 - * tags: [Retail] - * 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) - * tags: [Retail] - * 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 - * tags: [Retail] - * 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 - * tags: [Retail] - * 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) - * tags: [Retail] - * 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 - * tags: [Prediction] - * 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); - } -}); - -// ======================================== -// NEW SIGNAL & IMAGE PROCESSING ROUTES -// ======================================== - -/** - * @swagger - * /api/signal/smooth: - * post: - * summary: Smooth a 1D data series - * description: Applies a smoothing filter (Gaussian or Moving Average) to a 1D data series to reduce noise. - * tags: [Signal Processing] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * series: - * $ref: '#/components/schemas/DataSeries' - * options: - * $ref: '#/components/schemas/SmoothingOptions' - * responses: - * '200': - * description: The smoothed data series - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/ApiResponse' - * '400': - * description: Invalid input data - */ -app.post('/api/signal/smooth', (req, res) => { - try { - const { series, options } = req.body; - validateSeries(series); - const result = SignalProcessor.smooth(series.values, options); - 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/signal/detect-peaks: - * post: - * summary: Detect peaks in a 1D data series - * description: Identifies local maxima (peaks) in a 1D data series. More robust and accurate logic. - * tags: [Signal Processing] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * series: - * $ref: '#/components/schemas/DataSeries' - * options: - * type: object - * properties: - * smoothWindow: - * type: integer - * description: Optional window size for Gaussian smoothing to reduce noise before peak detection. - * example: 3 - * minDistance: - * type: integer - * description: The minimum number of data points between two peaks. - * example: 1 - * threshold: - * type: number - * description: The minimum value for a data point to be considered a peak. - * example: 0.5 - * responses: - * '200': - * description: An array of detected peak objects, each with an index and value. - * '400': - * description: Invalid input data - */ -app.post('/api/signal/detect-peaks', (req, res) => { - try { - const { series, options } = req.body; - validateSeries(series); - const result = SignalProcessor.detectPeaksConvolution(series.values, options); - 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/signal/detect-valleys: - * post: - * summary: Detect valleys in a 1D data series - * description: Identifies local minima (valleys) in a 1D data series. More robust and accurate logic. - * tags: [Signal Processing] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * series: - * $ref: '#/components/schemas/DataSeries' - * options: - * type: object - * properties: - * smoothWindow: - * type: integer - * description: Optional window size for Gaussian smoothing to reduce noise before valley detection. - * example: 3 - * minDistance: - * type: integer - * description: The minimum number of data points between two valleys. - * example: 1 - * threshold: - * type: number - * description: The maximum value for a data point to be considered a valley. - * example: -0.5 - * responses: - * '200': - * description: An array of detected valley objects, each with an index and value. - * '400': - * description: Invalid input data - */ -app.post('/api/signal/detect-valleys', (req, res) => { - try { - const { series, options } = req.body; - validateSeries(series); - const result = SignalProcessor.detectValleysConvolution(series.values, options); - 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/signal/detect-outliers: - * post: - * summary: Detect outliers in a 1D data series - * description: Identifies outliers in a 1D data series using statistically sound methods. - * tags: [Signal Processing] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * series: - * $ref: '#/components/schemas/DataSeries' - * options: - * type: object - * properties: - * method: - * type: string - * enum: [local_deviation, mean_diff] - * default: local_deviation - * windowSize: - * type: integer - * default: 7 - * threshold: - * type: number - * description: "The sensitivity threshold. For 'local_deviation', this is the number of standard deviations (Z-score)." - * default: 3.0 - * responses: - * '200': - * description: An array of detected outlier objects. - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/ApiResponse' - * '400': - * description: Invalid input data - */ -app.post('/api/signal/detect-outliers', (req, res) => { - try { - const { series, options } = req.body; - validateSeries(series); - const result = SignalProcessor.detectOutliersConvolution(series.values, options); - 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/signal/detect-vertices: - * post: - * summary: Detect trend vertices (turning points) in a 1D series - * description: Identifies all significant peaks and valleys in a data series trend using a robust local maxima/minima search. - * tags: [Signal Processing] - * requestBody: - * required: true - * content: - * application/json: - * schema: - * type: object - * properties: - * series: - * $ref: '#/components/schemas/DataSeries' - * options: - * type: object - * properties: - * smoothingWindow: - * type: integer - * default: 5 - * description: Window size for an initial Gaussian smoothing pass to reduce noise. - * threshold: - * type: number - * description: The absolute value a peak/valley must exceed to be counted. - * default: 0 - * minDistance: - * type: integer - * default: 3 - * description: Minimum number of data points between any two vertices. - * responses: - * '200': - * description: An array of detected vertex objects, labeled as 'peak' or 'valley'. - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/ApiResponse' - * '400': - * description: Invalid input data - */ -app.post('/api/signal/detect-vertices', (req, res) => { - try { - const { series, options } = req.body; - validateSeries(series); - const result = SignalProcessor.detectTrendVertices(series.values, options); - 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/kernels/{name}: - * get: - * summary: Get a pre-defined convolution kernel - * description: Retrieves a standard 1D or 2D convolution kernel by its name. - * tags: [Kernels] - * parameters: - * - in: path - * name: name - * required: true - * schema: - * type: string - * enum: [sobel-x, sobel-y, laplacian, difference1d, average1d] - * description: The name of the kernel to retrieve. - * - in: query - * name: size - * schema: - * type: integer - * default: 3 - * description: The size of the kernel (for kernels like 'average1d'). - * responses: - * '200': - * description: The requested kernel as a 1D or 2D array. - * content: - * application/json: - * schema: - * $ref: '#/components/schemas/ApiResponse' - * '400': - * description: Unknown kernel name or invalid options. - */ -app.get('/api/kernels/:name', (req, res) => { - try { - const kernelName = req.params.name; - const size = req.query.size ? parseInt(req.query.size as string, 10) : 3; - let kernel: number[] | number[][]; - - switch (kernelName) { - case 'sobel-x': - kernel = ConvolutionKernels.sobel('x'); - break; - case 'sobel-y': - kernel = ConvolutionKernels.sobel('y'); - break; - case 'laplacian': - kernel = ConvolutionKernels.laplacian(); - break; - case 'difference1d': - kernel = ConvolutionKernels.difference1D(); - break; - case 'average1d': - kernel = ConvolutionKernels.average1D(size); - break; - default: - throw new Error(`Unknown kernel name: ${kernelName}`); - } - res.status(200).json({ success: true, data: kernel } 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 - * SmoothingOptions: - * type: object - * properties: - * method: - * type: string - * enum: [gaussian, moving_average] - * default: gaussian - * description: The smoothing method to use. - * windowSize: - * type: integer - * default: 5 - * description: The size of the window for the filter. Must be an odd number for Gaussian. - * sigma: - * type: number - * default: 1.0 - * description: The standard deviation for the Gaussian filter. - * EdgeDetectionOptions: - * type: object - * properties: - * method: - * type: string - * enum: [sobel, laplacian] - * default: sobel - * description: The edge detection algorithm to use. - * threshold: - * type: number - * default: 0.1 - * description: The sensitivity threshold for detecting an edge. Values below this will be set to 0. - * 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 - * tags: [Documentation] - * 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 - * tags: [Documentation] - * 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 - * tags: [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; \ No newline at end of file