commit a1ceeb29ef4515df76387eb8f1c5884836aa9f69 Author: raymond Date: Mon Sep 1 23:56:24 2025 +0000 fundamental commit APIの中にmean, variance, std, unique, count, percentile, median, mode, topn, max, min, percent-change, correlationの計算実現します。 diff --git a/server_ok.ts b/server_ok.ts new file mode 100644 index 0000000..52b9fae --- /dev/null +++ b/server_ok.ts @@ -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 { + 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); + } catch (error) { + const errorMessage = handleError(error); + res.status(400).json({ success: false, error: errorMessage } as ApiResponse); + } +}); + +// 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); + } catch (error) { + const errorMessage = handleError(error); + res.status(400).json({ success: false, error: errorMessage } as ApiResponse); + } +}); + +// 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); + } catch (error) { + const errorMessage = handleError(error); + res.status(400).json({ success: false, error: errorMessage } as ApiResponse); + } +}); + +// 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); + } catch (error) { + const errorMessage = handleError(error); + res.status(400).json({ success: false, error: errorMessage } as ApiResponse); + } +}); + +// 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); + } catch (error) { + const errorMessage = handleError(error); + res.status(400).json({ success: false, error: errorMessage } as ApiResponse); + } +}); + +// 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); + } catch (error) { + const errorMessage = handleError(error); + res.status(400).json({ success: false, error: errorMessage } as ApiResponse); + } +}); + +// 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); + } catch (error) { + const errorMessage = handleError(error); + res.status(400).json({ success: false, error: errorMessage } as ApiResponse); + } +}); + +// 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); + } catch (error) { + const errorMessage = handleError(error); + res.status(400).json({ success: false, error: errorMessage } as ApiResponse); + } +}); + +// 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); + } catch (error) { + const errorMessage = handleError(error); + res.status(400).json({ success: false, error: errorMessage } as ApiResponse); + } +}); + +// 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); + } catch (error) { + const errorMessage = handleError(error); + res.status(400).json({ success: false, error: errorMessage } as ApiResponse); + } +}); + +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); + } catch (error) { + const errorMessage = handleError(error); + res.status(400).json({ success: false, error: errorMessage } as ApiResponse); + } +}); + +// 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); + } catch (error) { + const errorMessage = handleError(error); + res.status(400).json({ success: false, error: errorMessage } as ApiResponse); + } +}); + +// 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); + } catch (error) { + const errorMessage = handleError(error); + res.status(400).json({ success: false, error: errorMessage } as ApiResponse); + } +}); + +// 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); +}); + +// 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; \ No newline at end of file