add timeseries endpoints and update server.ts
/api/series/auto-arima-find: find parameters of SARIMA model automatically /api/series/manual-forecast: use determined model with parameters to forecast next values /api/series/identify-correlations: Calculate ACF and PACF for a time series /api/series/decompose-stl: Applies Seasonal-Trend-Loess (STL) decomposition to separate the series into trend, seasonal, and residual components.
This commit is contained in:
parent
13f3c7b053
commit
b1b2dcf18c
3 changed files with 682 additions and 26 deletions
229
server.ts
229
server.ts
|
|
@ -8,19 +8,20 @@ import swaggerJsdoc from 'swagger-jsdoc';
|
|||
import swaggerUi from 'swagger-ui-express';
|
||||
import * as math from 'mathjs';
|
||||
import * as _ from 'lodash';
|
||||
import cors from 'cors'; // <-- 1. IMPORT THE CORS PACKAGE
|
||||
|
||||
// These imports assume the files exist in the same directory
|
||||
// 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 './signal_processing_convolution';
|
||||
import { convolve1D, ConvolutionKernels } from './convolution'; // Direct import for new functions
|
||||
import { TimeSeriesAnalyzer, ARIMAOptions } from './timeseries';
|
||||
import { AnalysisPipelines } from './analysis_pipelines';
|
||||
import { convolve1D, convolve2D, ConvolutionKernels } from './convolution';
|
||||
|
||||
// Dummy interfaces/classes if the files are not present, to prevent compile errors
|
||||
interface KMeansOptions {}
|
||||
class KMeans {
|
||||
constructor(p: any, n: any, o: any) {}
|
||||
run = () => ({ clusters: [] })
|
||||
}
|
||||
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 {}
|
||||
|
|
@ -28,25 +29,27 @@ 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());
|
||||
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}`,
|
||||
},
|
||||
],
|
||||
swaggerDefinition: {
|
||||
openapi: '3.0.0',
|
||||
info: {
|
||||
title: 'My Express API',
|
||||
version: '1.0.0',
|
||||
description: 'API documentation for my awesome Express app',
|
||||
},
|
||||
apis: ["./server.ts"], // Pointing to this file for Swagger docs
|
||||
servers: [
|
||||
{
|
||||
url: `http://localhost:${PORT}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
apis: ["./server_convolution.ts"], // Pointing to the correct, renamed file
|
||||
};
|
||||
|
||||
const swaggerSpec = swaggerJsdoc(swaggerOptions);
|
||||
|
|
@ -67,7 +70,6 @@ interface DataMatrix {
|
|||
columns?: string[];
|
||||
rows?: string[];
|
||||
}
|
||||
|
||||
interface Condition {
|
||||
field: string;
|
||||
operator: '>' | '<' | '=' | '>=' | '<=' | '!=';
|
||||
|
|
@ -85,9 +87,8 @@ interface ApiResponse<T> {
|
|||
// ========================================
|
||||
|
||||
const handleError = (error: unknown): string => {
|
||||
return error instanceof Error ? error.message : 'Unknown error';
|
||||
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');
|
||||
|
|
@ -854,6 +855,159 @@ app.post('/api/series/rolling', (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @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:
|
||||
|
|
@ -1648,6 +1802,30 @@ app.get('/api/kernels/:name', (req, res) => {
|
|||
* 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).
|
||||
* ApiResponse:
|
||||
* type: object
|
||||
* properties:
|
||||
|
|
@ -1780,7 +1958,7 @@ app.get('/api/docs/export/html', (req, res) => {
|
|||
|
||||
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<any>);
|
||||
res.status(500).json({ success: false, error: 'Internal server error' });
|
||||
});
|
||||
|
||||
app.use('*', (req, res) => {
|
||||
|
|
@ -1792,9 +1970,8 @@ app.use('*', (req, res) => {
|
|||
// ========================================
|
||||
|
||||
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`);
|
||||
console.log(`Analytics API server running on port ${PORT}`);
|
||||
console.log(`API Documentation: http://localhost:${PORT}/api-docs`);
|
||||
});
|
||||
|
||||
export default app;
|
||||
Loading…
Add table
Add a link
Reference in a new issue