@sailboat-computer/validation
Version:
Validation framework for sailboat computer v3
329 lines • 11.8 kB
JavaScript
"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