Files
vyndr/src/services/evolutionEngine.js
T

97 lines
2.6 KiB
JavaScript

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<number>} values - Time series values
* @param {Array<string>} 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,
};