1739 lines
No EOL
59 KiB
TypeScript
1739 lines
No EOL
59 KiB
TypeScript
// 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 cors from 'cors';
|
|
|
|
// Assuming these 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 './services/signal_processing_convolution';
|
|
import { TimeSeriesAnalyzer, ARIMAOptions } from './services/timeseries';
|
|
import { AnalysisPipelines } from './services/analysis_pipelines';
|
|
import { convolve1D, convolve2D, ConvolutionKernels } from './services/convolution';
|
|
import { DataSeries, DataMatrix, Condition, ApiResponse } from './types/index';
|
|
import { handleError, validateSeries, validateMatrix } from './services/analytics_engine';
|
|
import { ForecastResult } from './services/prediction';
|
|
import { analytics } from './services/analytics_engine';
|
|
import { purchaseRate, liftValue, costRatio, grossMarginRate, averageSpendPerCustomer, purchaseIndex } from './services/retail_metrics';
|
|
import { RollingWindow } from './services/rolling_window';
|
|
import { pivotTable, PivotOptions } from './services/pivot_table';
|
|
|
|
// Initialize Express app
|
|
const app = express();
|
|
app.use(express.json());
|
|
app.use(cors()); // <-- 2. ENABLE CORS FOR ALL ROUTES
|
|
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"], // Pointing to the correct, renamed file
|
|
};
|
|
|
|
const swaggerSpec = swaggerJsdoc(swaggerOptions);
|
|
|
|
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec));
|
|
|
|
// ========================================
|
|
// 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<number[]>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number[]>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<number[]>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number[]>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/pivot-table:
|
|
* post:
|
|
* summary: Generate a pivot table from records
|
|
* description: Returns a pivot table based on the provided data and options
|
|
* tags: [Data Transformation]
|
|
* requestBody:
|
|
* required: true
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* data:
|
|
* type: array
|
|
* items:
|
|
* type: object
|
|
* description: Array of records to pivot
|
|
* options:
|
|
* $ref: '#/components/schemas/PivotOptions'
|
|
* responses:
|
|
* '200':
|
|
* description: Pivot table generated successfully
|
|
* '400':
|
|
* description: Invalid input data
|
|
*/
|
|
app.post('/api/pivot-table', (req, res) => {
|
|
try {
|
|
const { data, options } = req.body;
|
|
// You can pass analytics.mean, analytics.count, etc. as options.aggFunc if needed
|
|
const result = pivotTable(data, options);
|
|
res.status(200).json({ success: true, data: result });
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<number[]>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number[]>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<number[][]>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number[][]>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/series/auto-arima-find:
|
|
* post:
|
|
* summary: (EXPERIMENTAL) Automatically find best SARIMA parameters
|
|
* description: Performs a grid search to find the best SARIMA parameters based on AIC. NOTE - This is a simplified estimation and may not find the true optimal model. For best results, use the identification tools and the 'manual-forecast' endpoint.
|
|
* tags: [Series Operations]
|
|
* requestBody:
|
|
* required: true
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* series:
|
|
* $ref: '#/components/schemas/DataSeries'
|
|
* seasonalPeriod:
|
|
* type: integer
|
|
* description: The seasonal period of the data (e.g., 7 for weekly).
|
|
* example: 7
|
|
* responses:
|
|
* '200':
|
|
* description: The best model found and the search log.
|
|
* '400':
|
|
* description: Invalid input data.
|
|
*/
|
|
app.post('/api/series/auto-arima-find', (req, res) => {
|
|
try {
|
|
const { series, seasonalPeriod } = req.body;
|
|
validateSeries(series);
|
|
const result = AnalysisPipelines.findBestArimaParameters(series.values, seasonalPeriod);
|
|
res.status(200).json({ success: true, data: result });
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/series/manual-forecast:
|
|
* post:
|
|
* summary: Generate a forecast with manually specified SARIMA parameters
|
|
* description: This is the primary forecasting tool. It allows an expert user (who has analyzed ACF/PACF plots) to apply a specific SARIMA model to a time series and generate a forecast.
|
|
* tags: [Series Operations]
|
|
* requestBody:
|
|
* required: true
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* series:
|
|
* $ref: '#/components/schemas/DataSeries'
|
|
* options:
|
|
* $ref: '#/components/schemas/ARIMAOptions'
|
|
* forecastSteps:
|
|
* type: integer
|
|
* description: The number of future time steps to predict.
|
|
* example: 7
|
|
* responses:
|
|
* '200':
|
|
* description: The forecast results.
|
|
* '400':
|
|
* description: Invalid input data
|
|
*/
|
|
app.post('/api/series/manual-forecast', (req, res) => {
|
|
try {
|
|
const { series, options, forecastSteps } = req.body;
|
|
validateSeries(series);
|
|
const result = TimeSeriesAnalyzer.arimaForecast(series.values, options, forecastSteps);
|
|
res.status(200).json({ success: true, data: result });
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/series/identify-correlations:
|
|
* post:
|
|
* summary: Calculate ACF and PACF for a time series
|
|
* description: Returns the Autocorrelation and Partial Autocorrelation function values, which are essential for identifying SARIMA model parameters.
|
|
* tags: [Series Operations]
|
|
* requestBody:
|
|
* required: true
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* series:
|
|
* $ref: '#/components/schemas/DataSeries'
|
|
* maxLag:
|
|
* type: integer
|
|
* description: The maximum number of lags to calculate.
|
|
* example: 40
|
|
* responses:
|
|
* '200':
|
|
* description: The calculated ACF and PACF values.
|
|
* '400':
|
|
* description: Invalid input data.
|
|
*/
|
|
app.post('/api/series/identify-correlations', (req, res) => {
|
|
try {
|
|
const { series, maxLag } = req.body;
|
|
validateSeries(series);
|
|
const acf = TimeSeriesAnalyzer.calculateACF(series.values, maxLag);
|
|
const pacf = TimeSeriesAnalyzer.calculatePACF(series.values, maxLag);
|
|
res.status(200).json({ success: true, data: { acf, pacf } });
|
|
} catch (error) {
|
|
res.status(400).json({ success: false, error: handleError(error) });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/series/decompose-stl:
|
|
* post:
|
|
* summary: Decompose a time series into components
|
|
* description: Applies Seasonal-Trend-Loess (STL) decomposition to separate the series into trend, seasonal, and residual components.
|
|
* tags: [Series Operations]
|
|
* requestBody:
|
|
* required: true
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* properties:
|
|
* series:
|
|
* $ref: '#/components/schemas/DataSeries'
|
|
* period:
|
|
* type: integer
|
|
* description: The seasonal period of the data (e.g., 7 for weekly).
|
|
* example: 7
|
|
* responses:
|
|
* '200':
|
|
* description: The decomposed components of the time series.
|
|
* '400':
|
|
* description: Invalid input data.
|
|
*/
|
|
app.post('/api/series/decompose-stl', (req, res) => {
|
|
try {
|
|
const { series, period } = req.body;
|
|
validateSeries(series);
|
|
const result = TimeSeriesAnalyzer.stlDecomposition(series.values, period);
|
|
res.status(200).json({ success: true, data: result });
|
|
} catch (error) {
|
|
res.status(400).json({ success: false, error: handleError(error) });
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<string>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<string>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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 = purchaseRate(req.body.productPurchases, req.body.totalTransactions);
|
|
res.status(200).json({ success: true, data: result } as ApiResponse<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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 = liftValue(req.body.jointPurchaseRate, req.body.productAPurchaseRate, req.body.productBPurchaseRate);
|
|
res.status(200).json({ success: true, data: result } as ApiResponse<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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 = costRatio(req.body.cost, req.body.salePrice);
|
|
res.status(200).json({ success: true, data: result } as ApiResponse<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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 = grossMarginRate(req.body.salePrice, req.body.cost);
|
|
res.status(200).json({ success: true, data: result } as ApiResponse<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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 = averageSpendPerCustomer(totalRevenue, numberOfCustomers);
|
|
res.status(200).json({ success: true, data: result } as ApiResponse<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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 = purchaseIndex(totalItemsSold, numberOfCustomers);
|
|
res.status(200).json({ success: true, data: result } as ApiResponse<number>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<ForecastResult>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<ForecastResult>);
|
|
}
|
|
});
|
|
|
|
// ========================================
|
|
// NEW SIGNAL & IMAGE PROCESSING ROUTES
|
|
// ========================================
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/series/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: [Series Operations]
|
|
* 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/series/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<number[]>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<number[]>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/series/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: [Series Operations]
|
|
* 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/series/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<any>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<any>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/series/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: [Series Operations]
|
|
* 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/series/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<any>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<any>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/series/detect-outliers:
|
|
* post:
|
|
* summary: Detect outliers in a 1D data series
|
|
* description: Identifies outliers in a 1D data series using statistically sound methods.
|
|
* tags: [Series Operations]
|
|
* 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/series/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<any>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<any>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @swagger
|
|
* /api/series/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: [Series Operations]
|
|
* 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/series/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<any>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<any>);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @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<any>);
|
|
} catch (error) {
|
|
const errorMessage = handleError(error);
|
|
res.status(400).json({ success: false, error: errorMessage } as ApiResponse<any>);
|
|
}
|
|
});
|
|
|
|
// ========================================
|
|
// 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.
|
|
* ARIMAOptions:
|
|
* type: object
|
|
* properties:
|
|
* p:
|
|
* type: integer
|
|
* description: Non-seasonal AutoRegressive (AR) order.
|
|
* d:
|
|
* type: integer
|
|
* description: Non-seasonal Differencing (I) order.
|
|
* q:
|
|
* type: integer
|
|
* description: Non-seasonal Moving Average (MA) order.
|
|
* P:
|
|
* type: integer
|
|
* description: Seasonal AR order.
|
|
* D:
|
|
* type: integer
|
|
* description: Seasonal Differencing order.
|
|
* Q:
|
|
* type: integer
|
|
* description: Seasonal MA order.
|
|
* s:
|
|
* type: integer
|
|
* description: The seasonal period length (e.g., 7 for weekly).
|
|
* PivotOptions:
|
|
* type: object
|
|
* required:
|
|
* - index
|
|
* - columns
|
|
* - values
|
|
* properties:
|
|
* index:
|
|
* type: array
|
|
* items:
|
|
* type: string
|
|
* description: Keys to use as row labels
|
|
* columns:
|
|
* type: array
|
|
* items:
|
|
* type: string
|
|
* description: Keys to use as column labels
|
|
* values:
|
|
* type: string
|
|
* description: Key to aggregate
|
|
* aggFunc:
|
|
* type: string
|
|
* description: Aggregation function name (e.g., "sum", "mean", "count")
|
|
* 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 = `
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>API Documentation</title>
|
|
<link rel="stylesheet" type="text/css" href="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui.css" />
|
|
<style>
|
|
html {
|
|
box-sizing: border-box;
|
|
overflow: -moz-scrollbars-vertical;
|
|
overflow-y: scroll;
|
|
}
|
|
*, *:before, *:after {
|
|
box-sizing: inherit;
|
|
}
|
|
body {
|
|
margin:0;
|
|
background: #fafafa;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="swagger-ui"></div>
|
|
<script src="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui-bundle.js"></script>
|
|
<script src="https://unpkg.com/swagger-ui-dist@4.15.5/swagger-ui-standalone-preset.js"></script>
|
|
<script>
|
|
window.onload = function() {
|
|
const ui = SwaggerUIBundle({
|
|
spec: ${JSON.stringify(swaggerSpec)},
|
|
dom_id: '#swagger-ui',
|
|
deepLinking: true,
|
|
presets: [
|
|
SwaggerUIBundle.presets.apis,
|
|
SwaggerUIStandalonePreset
|
|
],
|
|
plugins: [
|
|
SwaggerUIBundle.plugins.DownloadUrl
|
|
],
|
|
layout: "StandaloneLayout"
|
|
});
|
|
};
|
|
</script>
|
|
</body>
|
|
</html>`;
|
|
|
|
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' });
|
|
});
|
|
|
|
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(`API Documentation: http://localhost:${PORT}/api-docs`);
|
|
});
|
|
|
|
export default app; |