bowling-analysis-system
Version:
A comprehensive system for analyzing bowling techniques using video processing and metrics calculation
247 lines (204 loc) • 6.51 kB
JavaScript
/**
* @module core/metrics/validation
* @description Utilities for validating metrics data
*/
/**
* Configuration for logging validation messages
* @type {Object}
*/
const logConfig = {
// Whether to log validation errors
showErrors: false,
// Whether to log validation warnings
showWarnings: false,
// Whether to log detailed validation info
showDetails: false,
// Counts of different message types (for summary reporting)
counts: {
errors: 0,
warnings: 0,
info: 0
}
};
/**
* Log a validation message if enabled
* @param {string} type - Type of message ('error', 'warning', 'info')
* @param {string} message - Message to log
*/
function logValidation(type, message) {
// Track counts for summary reporting
if (type in logConfig.counts) {
logConfig.counts[type]++;
}
// Only log if enabled for this type
if ((type === 'error' && logConfig.showErrors) ||
(type === 'warning' && logConfig.showWarnings) ||
(type === 'info' && logConfig.showDetails)) {
console.log(`[Validation ${type.toUpperCase()}]: ${message}`);
}
}
/**
* Reset validation log counts
*/
function resetLogCounts() {
Object.keys(logConfig.counts).forEach(key => {
logConfig.counts[key] = 0;
});
}
/**
* Configure validation logging
* @param {Object} config - Configuration options
*/
function configureLogging(config = {}) {
if (typeof config.showErrors === 'boolean') {
logConfig.showErrors = config.showErrors;
}
if (typeof config.showWarnings === 'boolean') {
logConfig.showWarnings = config.showWarnings;
}
if (typeof config.showDetails === 'boolean') {
logConfig.showDetails = config.showDetails;
}
resetLogCounts();
}
/**
* Get validation log counts
* @returns {Object} Log counts by type
*/
function getLogCounts() {
return { ...logConfig.counts };
}
/**
* Validate time series metrics
* @param {Object} metrics - Time series metrics to validate
* @returns {Object} Validation results
*/
function validateTimeSeriesMetrics(metrics) {
resetLogCounts();
const results = {
allNulls: [],
allZeros: [],
lowVariance: [],
validMetrics: []
};
if (!metrics) {
logValidation('error', 'No metrics provided for validation');
return results;
}
// Process each category
for (const category in metrics) {
const categoryMetrics = metrics[category];
// Process each metric
for (const metricName in categoryMetrics) {
const metricData = categoryMetrics[metricName];
const metricKey = `${category}.${metricName}`;
// Skip if metric data is missing
if (!metricData) {
logValidation('warning', `Metric ${metricKey} has no data`);
continue;
}
// Count valid points
const pointCount = countValidPoints(metricData);
if (pointCount === 0) {
results.allNulls.push(metricKey);
logValidation('warning', `Metric ${metricKey} has no valid data points`);
continue;
}
// Check if all values are zero
const nonZeroCount = countNonZeroPoints(metricData);
if (nonZeroCount === 0) {
results.allZeros.push(metricKey);
logValidation('warning', `Metric ${metricKey} has all zero values`);
continue;
}
// Check variance
const variance = calculateVariance(metricData);
if (variance < 0.001) {
results.lowVariance.push(`${metricKey} (${variance.toFixed(6)})`);
logValidation('info', `Metric ${metricKey} has low variance: ${variance.toFixed(6)}`);
continue;
}
// Valid metric
results.validMetrics.push(metricKey);
logValidation('info', `Metric ${metricKey} passed validation`);
}
}
return results;
}
/**
* Output validation results
* @param {Object} metrics - Metrics data
* @param {Object} results - Validation results
*/
function outputValidationResults(metrics, results) {
if (!metrics || !results) {
logValidation('error', 'Cannot output validation results: missing data');
return;
}
// Count metrics
let totalMetrics = 0;
for (const category in metrics) {
totalMetrics += Object.keys(metrics[category] || {}).length;
}
// Log summary
const validCount = results.validMetrics.length;
const invalidCount = totalMetrics - validCount;
logValidation('info', `Validation summary: ${validCount}/${totalMetrics} metrics valid`);
if (results.allNulls.length > 0) {
logValidation('warning', `${results.allNulls.length} metrics have no valid data`);
}
if (results.allZeros.length > 0) {
logValidation('warning', `${results.allZeros.length} metrics have all zero values`);
}
if (results.lowVariance.length > 0) {
logValidation('info', `${results.lowVariance.length} metrics have low variance`);
}
}
/**
* Count valid (non-null, non-undefined) points in data
* @param {Array} data - Data to count valid points in
* @returns {number} Count of valid points
*/
function countValidPoints(data) {
if (!Array.isArray(data)) {
return 0;
}
return data.filter(point => point !== null && point !== undefined).length;
}
/**
* Count non-zero points in data
* @param {Array} data - Data to count non-zero points in
* @returns {number} Count of non-zero points
*/
function countNonZeroPoints(data) {
if (!Array.isArray(data)) {
return 0;
}
return data.filter(point => point !== null && point !== undefined && point !== 0).length;
}
/**
* Calculate variance of data
* @param {Array} data - Data to calculate variance for
* @returns {number} Variance
*/
function calculateVariance(data) {
if (!Array.isArray(data)) {
return 0;
}
const validData = data.filter(point => point !== null && point !== undefined);
if (validData.length === 0) {
return 0;
}
const mean = validData.reduce((sum, value) => sum + value, 0) / validData.length;
const squaredDiffs = validData.map(value => Math.pow(value - mean, 2));
const variance = squaredDiffs.reduce((sum, value) => sum + value, 0) / validData.length;
return variance;
}
module.exports = {
validateTimeSeriesMetrics,
outputValidationResults,
countValidPoints,
configureLogging,
getLogCounts,
logValidation
};