UNPKG

@sailboat-computer/health-monitoring

Version:

Comprehensive health monitoring system for sailboat computer v3 with marine-specific health checks

247 lines (224 loc) 7.61 kB
/** * GPS Health Check for marine navigation systems */ import { BaseHealthCheck, HealthCheckResult, HealthCheckConfig, HealthCheckType, HealthStatus, HealthStatusType, OperationalContext, OperationalContextType, MarineSystemType, MarineSystemTypeType, MarineEnvironmentStatus } from '../index'; /** * GPS data interface for health checking */ export interface GPSData { latitude: number; longitude: number; altitude: number; satelliteCount: number; hdop: number; // Horizontal Dilution of Precision vdop: number; // Vertical Dilution of Precision speed: number; // Speed over ground in knots course: number; // Course over ground in degrees timestamp: Date; fix: 'none' | '2d' | '3d'; } /** * GPS Health Check implementation */ export class GPSHealthCheck extends BaseHealthCheck { private gpsDataProvider: () => Promise<GPSData>; private lastKnownPosition?: { lat: number; lon: number; timestamp: Date }; constructor( config: HealthCheckConfig, gpsDataProvider: () => Promise<GPSData> ) { super(config); this.gpsDataProvider = gpsDataProvider; } protected async performCheck( operationalContext: OperationalContextType, marineEnvironment?: MarineEnvironmentStatus ): Promise<HealthCheckResult> { try { const gpsData = await this.gpsDataProvider(); // Calculate health score based on multiple factors let score = 1.0; let status: HealthStatusType = HealthStatus.HEALTHY; const issues: string[] = []; const details: Record<string, any> = { satelliteCount: gpsData.satelliteCount, hdop: gpsData.hdop, vdop: gpsData.vdop, fix: gpsData.fix, dataAge: Date.now() - gpsData.timestamp.getTime() }; // Check GPS fix quality if (gpsData.fix === 'none') { score = 0.0; status = HealthStatus.CRITICAL; issues.push('No GPS fix available'); } else if (gpsData.fix === '2d') { score -= 0.3; issues.push('Only 2D GPS fix available'); } // Check satellite count if (gpsData.satelliteCount < 4) { score -= 0.4; issues.push(`Low satellite count: ${gpsData.satelliteCount}`); } else if (gpsData.satelliteCount < 6) { score -= 0.2; issues.push(`Marginal satellite count: ${gpsData.satelliteCount}`); } // Check HDOP (Horizontal Dilution of Precision) if (gpsData.hdop > 5.0) { score -= 0.3; issues.push(`Poor horizontal accuracy: HDOP ${gpsData.hdop}`); } else if (gpsData.hdop > 2.0) { score -= 0.1; issues.push(`Marginal horizontal accuracy: HDOP ${gpsData.hdop}`); } // Check data freshness const dataAge = Date.now() - gpsData.timestamp.getTime(); if (dataAge > 30000) { // 30 seconds score -= 0.4; issues.push(`Stale GPS data: ${Math.round(dataAge / 1000)}s old`); } else if (dataAge > 10000) { // 10 seconds score -= 0.2; issues.push(`Old GPS data: ${Math.round(dataAge / 1000)}s old`); } // Check for position jumps (if we have previous position) if (this.lastKnownPosition) { const distance = this.calculateDistance( this.lastKnownPosition.lat, this.lastKnownPosition.lon, gpsData.latitude, gpsData.longitude ); const timeDiff = (gpsData.timestamp.getTime() - this.lastKnownPosition.timestamp.getTime()) / 1000; const maxReasonableSpeed = 50; // knots const maxDistance = (maxReasonableSpeed * 0.514444 * timeDiff) / 1000; // km if (distance > maxDistance * 2) { score -= 0.3; issues.push(`Suspicious position jump: ${distance.toFixed(2)}km`); details['positionJump'] = distance; } } // Environmental adjustments if (marineEnvironment) { if (marineEnvironment.weather === 'storm') { // GPS can be affected by heavy weather score = Math.max(score - 0.1, 0); details['environmentalImpact'] = 'Storm conditions may affect GPS accuracy'; } } // Update last known position if (gpsData.fix !== 'none') { this.lastKnownPosition = { lat: gpsData.latitude, lon: gpsData.longitude, timestamp: gpsData.timestamp }; } // Determine final status score = Math.max(0, Math.min(1, score)); if (score >= 0.8) { status = HealthStatus.HEALTHY; } else if (score >= 0.6) { status = HealthStatus.DEGRADED; } else if (score >= 0.3) { status = HealthStatus.UNHEALTHY; } else { status = HealthStatus.CRITICAL; } const message = issues.length > 0 ? `GPS issues detected: ${issues.join(', ')}` : 'GPS operating normally'; return { checkId: this.config.checkId, name: this.config.name, type: this.config.type, marineSystemType: this.config.marineSystemType, status, score, message, details, timestamp: new Date(), executionTime: 0, // Will be set by base class marineContext: { operationalContext, environmentalImpact: marineEnvironment?.weather === 'storm', safetyImpact: true, // GPS is critical for navigation safety powerImpact: 5 // GPS typically uses ~5W }, thresholds: this.config.thresholds }; } catch (error) { throw new Error(`GPS health check failed: ${(error as Error).message}`); } } /** * Calculate distance between two GPS coordinates using Haversine formula */ private calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number { const R = 6371; // Earth's radius in kilometers const dLat = this.toRadians(lat2 - lat1); const dLon = this.toRadians(lon2 - lon1); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.toRadians(lat1)) * Math.cos(this.toRadians(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } private toRadians(degrees: number): number { return degrees * (Math.PI / 180); } } /** * Factory function to create GPS health check with standard marine configuration */ export function createGPSHealthCheck( gpsDataProvider: () => Promise<GPSData>, customConfig?: Partial<HealthCheckConfig> ): GPSHealthCheck { const defaultConfig: HealthCheckConfig = { checkId: 'gps-health', name: 'GPS Navigation Health', type: HealthCheckType.SENSOR, marineSystemType: MarineSystemType.NAVIGATION as MarineSystemTypeType, interval: 10000, // Check every 10 seconds timeout: 5000, // 5 second timeout retryCount: 2, thresholds: { warning: 0.6, critical: 0.3, unit: 'health_score' }, marineSettings: { operationalContexts: [ OperationalContext.SAILING, OperationalContext.MOTORING, OperationalContext.ANCHORED, OperationalContext.DOCKED ] as OperationalContextType[], environmentalSensitivity: 0.3, // Moderately sensitive to weather powerAware: true, safetyImpact: true }, advanced: { trendAnalysis: true, predictiveAlerts: true, adaptiveThresholds: false, historicalComparison: true }, ...customConfig }; return new GPSHealthCheck(defaultConfig, gpsDataProvider); }