UNPKG

signalk-parquet

Version:

SignalK plugin and webapp that archives SK data to Parquet files with a regimen control system, advanced querying, Claude integrated AI analysis, spatial capabilities, and REST API.

663 lines (582 loc) 17.5 kB
import { Context, Path, ServerAPI, NormalizedDelta, SourceRef, } from '@signalk/server-api'; // Re-export SignalK types for convenience export { NormalizedDelta, SourceRef }; import { Request, Response, Router } from 'express'; // Forward declaration to avoid circular dependency export interface SchemaService { detectOptimalSchema(records: DataRecord[], currentPath?: string): Promise<any>; validateFileSchema(filePath: string): Promise<any>; repairFileSchema(filePath: string, filenamePrefix?: string): Promise<any>; } // SignalK Plugin Interface export interface SignalKPlugin { id: string; name: string; description: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any schema: any; start: (options: Partial<PluginConfig>) => void; stop: () => void; registerWithRouter?: (router: Router) => void; } // Plugin Configuration export interface PluginConfig { bufferSize: number; saveIntervalSeconds: number; outputDirectory: string; filenamePrefix: string; retentionDays: number; fileFormat: 'json' | 'csv' | 'parquet'; vesselMMSI: string; s3Upload: S3UploadConfig; enableStreaming?: boolean; // Enable WebSocket streaming functionality claudeIntegration?: ClaudeIntegrationConfig; homePortLatitude?: number; homePortLongitude?: number; setCurrentLocationAction?: { setCurrentLocation: boolean; }; unitConversionCacheMinutes?: number; // Cache duration for unit conversions from signalk-units-preference } import type { ClaudeModel } from './claude-models'; export interface ClaudeIntegrationConfig { enabled: boolean; apiKey?: string; model?: ClaudeModel; maxTokens?: number; temperature?: number; autoAnalysis?: { daily: boolean; anomaly: boolean; threshold: number; }; cacheEnabled?: boolean; templates?: string[]; } // Vessel Context Document for Claude AI Analysis export interface VesselContext { vesselInfo: VesselInfo; customContext: string; lastUpdated: string; autoExtracted: boolean; } export interface VesselInfo { // Basic vessel identification name?: string; callsign?: string; mmsi?: string; // Physical characteristics length?: number; // Length Overall (LOA) in meters beam?: number; // Beam in meters draft?: number; // Draft in meters height?: number; // Height/air draft in meters displacement?: number; // Weight/displacement in tons // Vessel classification vesselType?: string; // Type of vessel (sailboat, motorboat, cargo, etc.) classification?: string; // Classification society info flag?: string; // Flag state // Technical specifications grossTonnage?: number; netTonnage?: number; deadWeight?: number; // Build information builder?: string; buildYear?: number; hullNumber?: string; // Contact information ownerName?: string; port?: string; // Port of registry // Additional context notes?: string; } export interface VesselContextExtraction { path: string; signalkPath: string; displayName: string; unit?: string; category: 'identification' | 'physical' | 'classification' | 'technical' | 'build' | 'contact'; } export interface PathConfig { path: Path; name?: string; enabled?: boolean; regimen?: string; source?: string; context?: Context; excludeMMSI?: string[]; // Array of MMSI numbers to exclude when using vessels.* } // Command Registration Types /** * Threshold operator types based on data type */ export type ThresholdOperator = // Numeric/Angular operators | 'gt' // Greater than | 'lt' // Less than | 'eq' // Equal to | 'ne' // Not equal to | 'range' // Within range (min/max) // String operators | 'contains' // String contains substring | 'startsWith' // String starts with | 'endsWith' // String ends with | 'stringEquals' // String equals (case-sensitive) // Boolean operators | 'true' // Is true | 'false' // Is false // Position operators | 'withinRadius' // Within radius of point | 'outsideRadius' // Outside radius of point | 'inBoundingBox' // Inside bounding box | 'outsideBoundingBox'; // Outside bounding box /** * Bounding box for geographic area thresholds */ export interface BoundingBox { north: number; // Northern latitude boundary south: number; // Southern latitude boundary east: number; // Eastern longitude boundary west: number; // Western longitude boundary } /** * Type-aware threshold configuration */ export interface ThresholdConfig { enabled: boolean; watchPath: string; // SignalK path to monitor operator: ThresholdOperator; // Threshold operator // Simple value (for most operators) value?: number | boolean | string; // Range operator values valueMin?: number; // Minimum value for range operator valueMax?: number; // Maximum value for range operator // Position-based threshold values latitude?: number; // Target latitude for position operators longitude?: number; // Target longitude for position operators radius?: number; // Radius in meters for position operators boundingBox?: BoundingBox; // Bounding box for area operators (manual mode) useHomePort?: boolean; // Use home port location instead of custom lat/lon boxSize?: number; // Box size in meters (for home port-based bounding box) boxAnchor?: string; // Anchor point for home port-based box (nw, n, ne, w, center, e, sw, s, se) boxBuffer?: number; // Buffer in meters to add to bounding box (default: 5m for GPS accuracy) activateOnMatch: boolean; // true = activate command when condition met, false = deactivate hysteresis?: number; // Optional: prevent rapid switching (seconds) } export interface CommandConfig { command: string; path: string; registered: string; description?: string; keywords?: string[]; // For Claude context matching active?: boolean; lastExecuted?: string; defaultState?: boolean; // Default on/off state when no threshold or manual override thresholds?: ThresholdConfig[]; // Threshold-based activation (multiple thresholds supported) manualOverride?: boolean; // True when manually controlled via PUT manualOverrideUntil?: string; // ISO timestamp when override expires (optional) } export interface CommandRegistrationState { registeredCommands: Map<string, CommandConfig>; putHandlers: Map<string, CommandPutHandler>; } export interface CommandExecutionRequest { command: string; value: boolean; timestamp?: string; } export interface CommandRegistrationRequest { command: string; description?: string; keywords?: string[]; defaultState?: boolean; thresholds?: ThresholdConfig[]; } // Web App Configuration (stored separately from plugin config) export interface WebAppPathConfig { paths: PathConfig[]; commands: CommandConfig[]; } export interface S3UploadConfig { enabled: boolean; timing?: 'realtime' | 'consolidation'; bucket?: string; region?: string; keyPrefix?: string; accessKeyId?: string; secretAccessKey?: string; deleteAfterUpload?: boolean; } // SignalK Data Structures export interface SignalKSubscription { context: string; subscribe: Array<{ path: string; period: number; }>; } // Data Record Structure export interface DataRecord { received_timestamp: string; signalk_timestamp: string; context: string; path: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any; value_json?: string; source?: string; source_label?: string; source_type?: string; source_pgn?: number; source_src?: string; meta?: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any [key: string]: any; // For flattened object properties } // Parquet Writer Options export interface ParquetWriterOptions { format: 'json' | 'csv' | 'parquet'; app?: ServerAPI; } // File System Related export interface FileInfo { name: string; path: string; size: number; modified: string; } export interface PathInfo { path: string; directory: string; fileCount: number; } // API Response Types // eslint-disable-next-line @typescript-eslint/no-explicit-any export interface ApiResponse<T = any> { success: boolean; data?: T; error?: string; message?: string; } // Command API Response Types export interface CommandApiResponse extends ApiResponse { commands?: CommandConfig[]; command?: CommandConfig; count?: number; } export interface CommandExecutionResponse extends ApiResponse { command?: string; value?: boolean; executed?: boolean; timestamp?: string; } export interface PathsApiResponse extends ApiResponse { dataDirectory?: string; paths?: PathInfo[]; } export interface FilesApiResponse extends ApiResponse { path?: string; directory?: string; files?: FileInfo[]; } export interface QueryApiResponse extends ApiResponse { query?: string; rowCount?: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any data?: any[]; } export interface SampleApiResponse extends ApiResponse { path?: string; file?: string; columns?: string[]; rowCount?: number; // eslint-disable-next-line @typescript-eslint/no-explicit-any data?: any[]; } export interface ConfigApiResponse extends ApiResponse { paths?: PathConfig[]; } export interface HealthApiResponse extends ApiResponse { status?: string; timestamp?: string; duckdb?: string; } export interface S3TestApiResponse extends ApiResponse { bucket?: string; region?: string; keyPrefix?: string; } export interface ValidationViolation { file: string; vessel?: string; issues: string[]; } export interface ValidationApiResponse extends ApiResponse { totalFiles?: number; totalVessels?: number; correctSchemas?: number; violations?: number; violationDetails?: string[]; violationFiles?: ValidationViolation[]; debugMessages?: string[]; processedFiles?: number; processedVessels?: number; progress?: string; jobId?: string; cancelled?: boolean; } export interface ProcessStatusApiResponse extends ApiResponse { isRunning: boolean; processType?: ProcessType; startTime?: string; totalFiles?: number; processedFiles?: number; currentFile?: string; progress?: number; // percentage 0-100 } export interface ProcessCancelApiResponse extends ApiResponse { message: string; } // Claude Analysis API Response Types export interface AnalysisApiResponse extends ApiResponse { analysis?: AnalysisResult; history?: AnalysisResult[]; templates?: AnalysisTemplateInfo[]; usage?: { input_tokens: number; output_tokens: number; }; } export interface AnalysisResult { id: string; analysis: string; insights: string[]; recommendations?: string[]; anomalies?: AnomalyInfo[]; confidence: number; dataQuality: string; timestamp: string; metadata: AnalysisMetadata; } export interface AnomalyInfo { timestamp: string; value: any; expectedRange: { min: number; max: number }; severity: 'low' | 'medium' | 'high'; description: string; confidence: number; } export interface AnalysisMetadata { dataPath: string; analysisType: string; recordCount: number; timeRange?: { start: Date; end: Date }; templateUsed?: string; } export interface AnalysisTemplateInfo { id: string; name: string; description: string; category: string; icon: string; complexity: string; estimatedTime: string; requiredPaths: string[]; } export interface ClaudeConnectionTestResponse extends ApiResponse { model?: string; responseTime?: number; tokenUsage?: number; } // Express Router Types // eslint-disable-next-line @typescript-eslint/no-explicit-any export interface TypedRequest<T = any> extends Request { body: T; params: { [key: string]: string }; query: { [key: string]: string }; } // eslint-disable-next-line @typescript-eslint/no-explicit-any export interface TypedResponse<T = any> extends Response { json: (body: T) => this; status: (code: number) => this; } // Internal Plugin State // Process management types export type ProcessType = 'validation' | 'repair' | 'consolidation'; export interface ProcessState { type: ProcessType; isRunning: boolean; startTime: Date; totalFiles?: number; processedFiles?: number; currentFile?: string; cancelRequested?: boolean; abortController?: AbortController; } export interface PluginState { unsubscribes: Array<() => void>; // eslint-disable-next-line @typescript-eslint/no-explicit-any streamSubscriptions?: any[]; // Store streambundle stream references for cleanup historicalStreamingService?: any; // HistoricalStreamingService - avoiding circular import streamingService?: any; // WebSocket streaming service for runtime control streamingEnabled?: boolean; // Runtime control separate from config restoredSubscriptions?: Map<string, any>; // Track active subscriptions dataBuffers: Map<string, DataRecord[]>; activeRegimens: Set<string>; subscribedPaths: Set<string>; saveInterval?: NodeJS.Timeout; consolidationInterval?: NodeJS.Timeout; parquetWriter?: ParquetWriter; // eslint-disable-next-line @typescript-eslint/no-explicit-any s3Client?: any; currentConfig?: PluginConfig; commandState: CommandRegistrationState; // Process management currentProcess?: ProcessState; } // Parquet Writer Class Interface export interface ParquetWriter { writeRecords(filepath: string, records: DataRecord[]): Promise<string>; writeJSON(filepath: string, records: DataRecord[]): Promise<string>; writeCSV(filepath: string, records: DataRecord[]): Promise<string>; writeParquet(filepath: string, records: DataRecord[]): Promise<string>; consolidateDaily( outputDirectory: string, date: Date, filenamePrefix: string ): Promise<number>; getSchemaService(): SchemaService | undefined; } // DuckDB Related Types export interface DuckDBConnection { runAndReadAll(query: string): Promise<DuckDBResult>; disconnectSync(): void; } export interface DuckDBResult { // eslint-disable-next-line @typescript-eslint/no-explicit-any getRowObjects(): any[]; } export interface DuckDBInstance { connect(): Promise<DuckDBConnection>; } // S3 Related Types export interface S3Config { region: string; credentials?: { accessKeyId: string; secretAccessKey: string; }; } // Query Request/Response Types export interface QueryRequest { query: string; } export interface PathConfigRequest { path: Path; name?: string; enabled?: boolean; regimen?: string; source?: string; context?: Context; } //FIXME https://github.com/SignalK/signalk-server/pull/2043 // Command Types export type CommandPutHandler = ( context: string, path: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any, callback?: (result: CommandExecutionResult) => void ) => CommandExecutionResult; export interface CommandExecutionResult { success: boolean; state: 'COMPLETED' | 'PENDING' | 'FAILED'; statusCode?: number; message?: string; timestamp: string; } export interface CommandHistoryEntry { command: string; action: 'EXECUTE' | 'STOP' | 'REGISTER' | 'UNREGISTER' | 'UPDATE'; value?: boolean; timestamp: string; success: boolean; error?: string; } export enum CommandStatus { ACTIVE = 'ACTIVE', INACTIVE = 'INACTIVE', PENDING = 'PENDING', ERROR = 'ERROR', } // Utility Types export type FileFormat = 'json' | 'csv' | 'parquet'; export type UploadTiming = 'realtime' | 'consolidation'; export type BufferKey = string; // Format: "context:path" // Error Types export interface PluginError extends Error { code?: string; // eslint-disable-next-line @typescript-eslint/no-explicit-any details?: any; } // Consolidation Types export interface ConsolidationOptions { outputDirectory: string; date: Date; filenamePrefix: string; } export interface ConsolidationResult { processedPaths: number; consolidatedFiles: string[]; errors: string[]; } // Schema Definition Types export interface ParquetField { type: string; optional?: boolean; repeated?: boolean; } export interface ParquetSchema { [fieldName: string]: ParquetField; } // Data Analysis Related Types export interface DataSummary { rowCount: number; timeRange: { start: Date; end: Date }; columns: ColumnInfo[]; statisticalSummary: Record<string, Statistics>; dataQuality: DataQualityMetrics; } export interface ColumnInfo { name: string; type: string; nullCount: number; uniqueCount: number; sampleValues: any[]; } export interface Statistics { count: number; mean?: number; median?: number; min?: any; max?: any; stdDev?: number; } export interface DataQualityMetrics { completeness: number; // Percentage of non-null values consistency: number; // Data format consistency timeliness: number; // Data freshness accuracy: number; // Estimated data accuracy } // File Processing Types export interface ProcessingStats { totalBuffers: number; buffersWithData: number; totalRecords: number; processedPaths: string[]; }