server.ts を更新

This commit is contained in:
raymond 2025-09-02 00:34:10 +00:00
parent a1ceeb29ef
commit 9d2b0dc043

385
server.ts Normal file
View file

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