/** * ACCURACY VALIDATOR * Validate data accuracy and consistency across the application */ export class AccuracyValidator { constructor() { this.validationRules = new Map(); this.validationHistory = []; this.setupDefaultRules(); } setupDefaultRules() { // Environmental data validation rules this.addRule('temperature', { min: -50, max: 60, unit: '°C', precision: 1 }); this.addRule('humidity', { min: 0, max: 100, unit: '%', precision: 1 }); this.addRule('airQuality', { min: 0, max: 500, unit: 'AQI', precision: 0 }); this.addRule('waterPH', { min: 0, max: 14, unit: 'pH', precision: 2 }); this.addRule('coordinates', { latitude: { min: -90, max: 90 }, longitude: { min: -180, max: 180 }, precision: 6 }); // Device pricing validation this.addRule('devicePrice', { min: 0, max: 10000, unit: '$', precision: 0 }); // Carbon footprint validation this.addRule('carbonFootprint', { min: 0, max: 1000, unit: 'kg CO2', precision: 2 }); } addRule(field, rule) { this.validationRules.set(field, rule); } validateField(field, value, context = {}) { const rule = this.validationRules.get(field); if (!rule) { return { valid: true, message: 'No validation rule found' }; } const result = { valid: true, message: '', warnings: [], correctedValue: value }; try { // Type validation if (typeof value !== 'number' && !Array.isArray(value) && typeof value !== 'object') { const numValue = parseFloat(value); if (isNaN(numValue)) { result.valid = false; result.message = `Invalid ${field}: must be a number`; return result; } result.correctedValue = numValue; value = numValue; } // Range validation if (rule.min !== undefined && value < rule.min) { result.valid = false; result.message = `${field} value ${value} is below minimum ${rule.min}`; result.correctedValue = rule.min; } if (rule.max !== undefined && value > rule.max) { result.valid = false; result.message = `${field} value ${value} is above maximum ${rule.max}`; result.correctedValue = rule.max; } // Precision validation if (rule.precision !== undefined) { const rounded = parseFloat(value.toFixed(rule.precision)); if (rounded !== value) { result.warnings.push(`${field} rounded to ${rule.precision} decimal places`); result.correctedValue = rounded; } } // Special validations if (field === 'coordinates') { return this.validateCoordinates(value, rule); } // Context-based validation if (context.previousValue !== undefined) { const change = Math.abs(value - context.previousValue); const threshold = (rule.max - rule.min) * 0.1; // 10% change threshold if (change > threshold) { result.warnings.push(`Large change detected: ${change.toFixed(2)} ${rule.unit || ''}`); } } } catch (error) { result.valid = false; result.message = `Validation error: ${error.message}`; } // Record validation this.recordValidation(field, value, result); return result; } validateCoordinates(coords, rule) { const result = { valid: true, message: '', warnings: [], correctedValue: coords }; if (!coords || typeof coords !== 'object') { result.valid = false; result.message = 'Coordinates must be an object with lat and lng properties'; return result; } const { lat, lng } = coords; // Validate latitude if (typeof lat !== 'number' || lat < rule.latitude.min || lat > rule.latitude.max) { result.valid = false; result.message = `Invalid latitude: ${lat}. Must be between ${rule.latitude.min} and ${rule.latitude.max}`; } // Validate longitude if (typeof lng !== 'number' || lng < rule.longitude.min || lng > rule.longitude.max) { result.valid = false; result.message = `Invalid longitude: ${lng}. Must be between ${rule.longitude.min} and ${rule.longitude.max}`; } // Precision correction if (rule.precision !== undefined) { result.correctedValue = { lat: parseFloat(lat.toFixed(rule.precision)), lng: parseFloat(lng.toFixed(rule.precision)) }; } return result; } validateDataSet(data, schema = {}) { const results = { valid: true, errors: [], warnings: [], correctedData: { ...data } }; for (const [field, value] of Object.entries(data)) { const context = schema[field] || {}; const validation = this.validateField(field, value, context); if (!validation.valid) { results.valid = false; results.errors.push({ field, message: validation.message, value, correctedValue: validation.correctedValue }); } if (validation.warnings.length > 0) { results.warnings.push(...validation.warnings.map(w => ({ field, warning: w }))); } results.correctedData[field] = validation.correctedValue; } return results; } recordValidation(field, value, result) { this.validationHistory.push({ field, value, result, timestamp: Date.now() }); // Keep only last 1000 validations if (this.validationHistory.length > 1000) { this.validationHistory = this.validationHistory.slice(-1000); } } getValidationStats() { const stats = { totalValidations: this.validationHistory.length, successRate: 0, fieldStats: new Map(), recentErrors: [] }; let successCount = 0; const now = Date.now(); const oneHourAgo = now - (60 * 60 * 1000); this.validationHistory.forEach(validation => { if (validation.result.valid) { successCount++; } else if (validation.timestamp > oneHourAgo) { stats.recentErrors.push({ field: validation.field, message: validation.result.message, timestamp: validation.timestamp }); } // Field statistics if (!stats.fieldStats.has(validation.field)) { stats.fieldStats.set(validation.field, { total: 0, errors: 0, warnings: 0 }); } const fieldStat = stats.fieldStats.get(validation.field); fieldStat.total++; if (!validation.result.valid) fieldStat.errors++; if (validation.result.warnings && validation.result.warnings.length > 0) { fieldStat.warnings++; } }); stats.successRate = stats.totalValidations > 0 ? (successCount / stats.totalValidations) * 100 : 100; return stats; } clearHistory() { this.validationHistory = []; } } // Global validator instance export const accuracyValidator = new AccuracyValidator(); // Utility functions export const validateField = (field, value, context) => accuracyValidator.validateField(field, value, context); export const validateDataSet = (data, schema) => accuracyValidator.validateDataSet(data, schema); export const getValidationStats = () => accuracyValidator.getValidationStats(); export const addValidationRule = (field, rule) => accuracyValidator.addRule(field, rule);