UNPKG

@sailboat-computer/validation

Version:

Validation framework for sailboat computer v3

329 lines 11.8 kB
"use strict"; /** * Core validation engine implementation */ Object.defineProperty(exports, "__esModule", { value: true }); exports.createValidationEngine = exports.ValidationEngine = void 0; const types_1 = require("./types"); /** * Default validation engine configuration */ const DEFAULT_CONFIG = { enableBatchValidation: true, batchSize: 50, parallelProcessing: true, caching: { validationRules: true, sensorSpecs: true, contextData: true }, earlyTermination: { enabled: true, criticalRules: ['range_validation', 'safety_limits'] }, prioritization: { criticalDataFirst: true, healthBasedPriority: true } }; /** * Core validation engine */ class ValidationEngine { constructor(config = {}) { this.rules = new Map(); this.rulesByCategory = new Map(); this.config = { ...DEFAULT_CONFIG, ...config }; this.metrics = this.initializeMetrics(); } /** * Register a validation rule */ registerRule(rule) { this.rules.set(rule.name, rule); // Group by category for efficient lookup if (!this.rulesByCategory.has(rule.category)) { this.rulesByCategory.set(rule.category, []); } this.rulesByCategory.get(rule.category).push(rule); } /** * Register multiple validation rules */ registerRules(rules) { rules.forEach(rule => this.registerRule(rule)); } /** * Validate a single sensor data point */ async validate(data, context) { const startTime = Date.now(); try { // Get applicable rules for this data const applicableRules = this.getApplicableRules(data); // Sort rules by priority if enabled if (this.config.prioritization.criticalDataFirst) { applicableRules.sort((a, b) => this.getRulePriority(b) - this.getRulePriority(a)); } // Execute validation rules const validationResults = []; let shouldTerminateEarly = false; for (const rule of applicableRules) { const result = await this.executeRule(rule, data, context); validationResults.push(result); // Check for early termination if (this.config.earlyTermination.enabled && this.config.earlyTermination.criticalRules.includes(rule.name) && !result.passed && result.severity === types_1.AlertSeverity.CRITICAL) { shouldTerminateEarly = true; break; } } // Calculate overall validity and confidence const isValid = validationResults.every(r => r.passed || r.severity === types_1.AlertSeverity.INFO); const confidence = this.calculateConfidence(validationResults, context); // Create validated data result const validatedData = { sourceData: data, validationResults, isValid, confidence, validatedAt: new Date(), validationRules: applicableRules.map(r => r.name), warnings: validationResults .filter(r => !r.passed && r.severity === types_1.AlertSeverity.WARNING) .map(r => r.message || `${r.rule} validation failed`) }; // Update metrics this.updateMetrics(Date.now() - startTime, validationResults); return validatedData; } catch (error) { // Handle validation errors this.updateMetrics(Date.now() - startTime, [], true); throw new Error(`Validation failed: ${error instanceof Error ? error.message : String(error)}`); } } /** * Validate multiple sensor data points */ async validateBatch(dataPoints, context) { if (!this.config.enableBatchValidation) { // Process individually const results = []; for (const data of dataPoints) { results.push(await this.validate(data, context)); } return results; } // Process in batches const results = []; const batchSize = this.config.batchSize; for (let i = 0; i < dataPoints.length; i += batchSize) { const batch = dataPoints.slice(i, i + batchSize); if (this.config.parallelProcessing) { // Process batch in parallel const batchPromises = batch.map(data => this.validate(data, context)); const batchResults = await Promise.all(batchPromises); results.push(...batchResults); } else { // Process batch sequentially for (const data of batch) { results.push(await this.validate(data, context)); } } } return results; } /** * Get validation metrics */ getMetrics() { return { ...this.metrics }; } /** * Reset validation metrics */ resetMetrics() { this.metrics = this.initializeMetrics(); } /** * Get applicable rules for sensor data */ getApplicableRules(data) { const applicableRules = []; for (const rule of this.rules.values()) { if (rule.isApplicable(data)) { applicableRules.push(rule); } } return applicableRules; } /** * Execute a single validation rule */ async executeRule(rule, data, context) { try { return rule.validate(data, context); } catch (error) { return { rule: rule.name, passed: false, message: `Rule execution failed: ${error instanceof Error ? error.message : String(error)}`, severity: types_1.AlertSeverity.ALARM, details: { error: String(error) } }; } } /** * Calculate confidence score based on validation results */ calculateConfidence(results, context) { if (results.length === 0) return 0; let baseConfidence = 1.0; let totalWeight = 0; for (const result of results) { const weight = this.getResultWeight(result); totalWeight += weight; if (!result.passed) { // Apply penalty based on severity const penalty = this.getSeverityPenalty(result.severity); baseConfidence -= penalty * weight; } } // Normalize by total weight if (totalWeight > 0) { baseConfidence = Math.max(0, baseConfidence / totalWeight); } // Apply contextual adjustments baseConfidence = this.applyContextualAdjustments(baseConfidence, context); return Math.max(0, Math.min(1, baseConfidence)); } /** * Get weight for validation result based on rule importance */ getResultWeight(result) { switch (result.severity) { case types_1.AlertSeverity.CRITICAL: return 1.0; case types_1.AlertSeverity.ALARM: return 0.8; case types_1.AlertSeverity.WARNING: return 0.5; case types_1.AlertSeverity.INFO: return 0.2; default: return 0.5; } } /** * Get penalty for validation failure based on severity */ getSeverityPenalty(severity) { switch (severity) { case types_1.AlertSeverity.CRITICAL: return 0.5; case types_1.AlertSeverity.ALARM: return 0.3; case types_1.AlertSeverity.WARNING: return 0.1; case types_1.AlertSeverity.INFO: return 0.0; default: return 0.2; } } /** * Apply contextual adjustments to confidence score */ applyContextualAdjustments(baseConfidence, context) { let adjustedConfidence = baseConfidence; // Adjust based on sensor health const sensorId = Object.keys(context.sensorHealth)[0]; const sensorHealth = sensorId ? context.sensorHealth[sensorId] : undefined; if (sensorHealth) { // Use metrics.errorRate as a proxy for health since overallHealth doesn't exist const healthFactor = 1 - (sensorHealth.metrics?.errorRate || 0); adjustedConfidence *= (0.5 + healthFactor * 0.5); // Scale by health (0.5-1.0) } // Adjust based on data age (temporal relevance) const dataAge = context.recentData.length > 0 ? Date.now() - context.recentData[0].timestamp.getTime() : 0; const ageHours = dataAge / (1000 * 60 * 60); if (ageHours > 24) { adjustedConfidence *= 0.8; // Reduce confidence for old data } else if (ageHours > 1) { adjustedConfidence *= 0.9; // Slight reduction for data over 1 hour } return adjustedConfidence; } /** * Get rule priority for sorting */ getRulePriority(rule) { switch (rule.severity) { case types_1.AlertSeverity.CRITICAL: return 4; case types_1.AlertSeverity.EMERGENCY: return 4; case types_1.AlertSeverity.ALARM: return 3; case types_1.AlertSeverity.WARNING: return 2; case types_1.AlertSeverity.INFO: return 1; default: return 0; } } /** * Update validation metrics */ updateMetrics(latencyMs, results, isError = false) { // Update throughput metrics this.metrics.throughput.averageValidationTime = (this.metrics.throughput.averageValidationTime + latencyMs) / 2; // Update quality metrics if (results.length > 0) { const passedCount = results.filter(r => r.passed).length; const passRate = passedCount / results.length; this.metrics.qualityMetrics.overallPassRate = (this.metrics.qualityMetrics.overallPassRate + passRate) / 2; // Update rule-specific pass rates for (const result of results) { const currentRate = this.metrics.qualityMetrics.ruleSpecificPassRates[result.rule] || 0; this.metrics.qualityMetrics.ruleSpecificPassRates[result.rule] = (currentRate + (result.passed ? 1 : 0)) / 2; } } // Update error metrics if (isError) { this.metrics.errorMetrics.validationErrorRate++; } } /** * Initialize metrics structure */ initializeMetrics() { return { throughput: { validationsPerSecond: 0, averageValidationTime: 0, batchProcessingEfficiency: 0 }, qualityMetrics: { overallPassRate: 1.0, ruleSpecificPassRates: {}, confidenceDistribution: [] }, errorMetrics: { validationErrorRate: 0, storageFailureRate: 0, serviceUnavailabilityRate: 0 }, healthMetrics: { sensorHealthDistribution: {}, calibrationStatus: {}, maintenanceAlerts: 0 } }; } } exports.ValidationEngine = ValidationEngine; /** * Create a validation engine with default configuration */ function createValidationEngine(config) { return new ValidationEngine(config); } exports.createValidationEngine = createValidationEngine; //# sourceMappingURL=engine.js.map