const axios = require('axios'); const EVOLUTION_SERVICE_URL = process.env.EVOLUTION_SERVICE_URL || 'http://localhost:5001'; const EVOLUTION_TIMEOUT = 5000; const TRACKED_SIGNALS = [ 'usage_rate', 'assist_rate', 'three_pt_attempt_rate', 'shot_location', 'aggression_score', 'minutes_trajectory', ]; /** * Detect changepoints in a player metric time series via Python PELT microservice. * @param {string} playerId * @param {string} metric - Metric name * @param {Array} values - Time series values * @param {Array} timestamps - ISO timestamps * @returns {object} Changepoint result or graceful degradation */ async function detectChangepoints(playerId, metric, values, timestamps) { try { const response = await axios.post( `${EVOLUTION_SERVICE_URL}/detect`, { player_id: playerId, metric, values, timestamps }, { timeout: EVOLUTION_TIMEOUT } ); return response.data; } catch (error) { const reason = error.code === 'ECONNABORTED' ? 'timeout' : error.response ? `HTTP ${error.response.status}` : error.message; return { evolution_detected: false, error: reason, playerId, metric, }; } } /** * Check if multiple signals are inflecting simultaneously. * If 2+ signals inflecting above 70% confidence, evolution is detected. * @param {object} playerMetrics - { signal_name: { values, timestamps, confidence } } * @returns {object} { evolution_detected, confidence, signals } */ async function checkMultiSignalEvolution(playerMetrics) { const results = []; for (const signal of TRACKED_SIGNALS) { if (playerMetrics[signal]) { const { values, timestamps } = playerMetrics[signal]; const result = await detectChangepoints( playerMetrics.playerId, signal, values, timestamps ); if (result && !result.error) { results.push({ signal, ...result }); } } } const inflecting = results.filter(r => r.confidence >= 0.70); if (inflecting.length >= 2) { const avgConfidence = inflecting.reduce((s, r) => s + r.confidence, 0) / inflecting.length; return { evolution_detected: true, confidence: Math.round(avgConfidence * 100) / 100, signals: inflecting.map(r => r.signal), details: inflecting, }; } return { evolution_detected: false, confidence: 0, signals: [], checked: TRACKED_SIGNALS.filter(s => playerMetrics[s]), }; } module.exports = { EVOLUTION_SERVICE_URL, EVOLUTION_TIMEOUT, TRACKED_SIGNALS, detectChangepoints, checkMultiSignalEvolution, };