#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const { SatLocLogParser } = require('./helpers/satloc_log_parser'); const { fixedTo } = require('./helpers/utils'); // Use console.log directly as preferred // Check command line arguments let filePath; if (process.argv.length >= 3) { filePath = process.argv[2]; } else { // Hardcoded fallback file path // filePath = './test-logs/Liquid_IF2_G4.log'; // filePath = './test-logs/Liquid_IF2_Falcon.log'; // filePath = './test-logs/02220710.LOG'; // filePath = './test-logs/02221146.LOG'; // filePath = './test-logs/2007240626SatlocG4_695d.log'; filePath = './test-logs/Cloud/2007281153SatlocG40010.log'; // filePath = './test-logs/Sept-15_25/2508021622SatlocG4_8948A.log'; // filePath = './test-logs/Sept-15_25/2108311523SatlocG412177.log'; // filePath = './test-logs/Sept-15_25/2507140724SatlocG4_b4ef.log'; // filePath = './test-logs/Sept-15_25/JOB146 HK4704.log'; console.log(`No file path provided, using default: ${filePath}`); } // Check if file exists if (!fs.existsSync(filePath)) { console.error(`Error: File not found: ${filePath}`); if (process.argv.length < 3) { console.log('Usage: node test_satloc_pattern_brief.js '); console.log(`Or place your SatLoc file at: ${filePath}`); } process.exit(1); } async function analyzeSatLocPattern() { // const interestedRecordTypes = [32, 36, 100, 120, 140, 151, 152]; const interestedRecordTypes = [100, 120, 152]; try { const stats = fs.statSync(filePath); const fileName = path.basename(filePath); console.log('=== SatLoc Log Pattern Analysis ==='); console.log(`File: ${fileName}`); console.log(`Size: ${(stats.size / (1024 * 1024)).toFixed(2)} MB (${stats.size.toLocaleString()} bytes)`); console.log(''); // Create parser and parse file const parser = new SatLocLogParser({ debugRecordTypes: interestedRecordTypes, outputAllRecords: false, // Set to false for normal operation, true for job analysis verbose: false, }); const parseStartTime = Date.now(); const result = await parser.parseFile(filePath); const parseEndTime = Date.now(); // console.log('Parser result:', typeof result); // console.log('Result success:', result.success); // console.log('Result keys:', Object.keys(result)); if (!result.success) { console.error('Parser failed:', result.error); return; } // Extract all application details from jobGroups let applicationDetails = []; if (result.jobGroups) { Object.values(result.jobGroups).forEach(jobGroup => { applicationDetails = applicationDetails.concat(jobGroup); }); } console.log(`Records: ${result.records ? result.records.length.toLocaleString() : 'undefined'}`); console.log(`Application Details: ${applicationDetails ? applicationDetails.length.toLocaleString() : 'undefined'}`); console.log(`True sequence length: ${parser.recordSequence ? parser.recordSequence.length.toLocaleString() : 'undefined'}`); // Generate CSV file for application details if (applicationDetails.length > 0) { const csvFileName = `${path.basename(filePath, path.extname(filePath))}_application_details.csv`; console.log(`\nGenerating CSV file: ${csvFileName}`); // Get all unique keys from application details const allKeys = new Set(); applicationDetails.forEach(detail => { Object.keys(detail).forEach(key => allKeys.add(key)); }); const headers = Array.from(allKeys); //.sort(); const csvContent = [ headers.join(','), ...applicationDetails.map(detail => headers.map(header => { const value = detail[header]; if (value === null || value === undefined) return ''; if (typeof value === 'string' && value.includes(',')) return `"${value}"`; return value; }).join(',') ) ].join('\n'); fs.writeFileSync(csvFileName, csvContent); console.log('✓ CSV file generated successfully'); console.log(`CSV file saved: ${csvFileName} (${applicationDetails.length} records)`); } else { console.warn('\nNo application details found - CSV file not generated'); } // Job Matching Analysis console.log('\n=== Job Matching Information ==='); // Show filename-based job extraction console.log(`Log filename: ${fileName}`); console.log(`Filename-based job ID: ${result.filenameJobId || 'Not found'}`); // Extract job and aircraft information from parsed records const satlocJobIds = new Set(); let aircraftInfo = null; let swathingSetups = []; let systemSetups = []; result.records.forEach(record => { // Check for Swathing Setup (120) - stores record type as number if (record.recordType === 120) { if (record.jobId) { satlocJobIds.add(record.jobId); } swathingSetups.push({ jobId: record.jobId, swathWidth: record.swathWidth, patternType: record.patternType, patternLR: record.patternLR, jobLongLabelName: record.jobLongLabelName }); } // Check for System Setup (100) - stores record type as number if (record.recordType === 100) { if (!aircraftInfo) { // Use the first one found aircraftInfo = { aircraftId: record.aircraftId, pilotName: record.pilotName, loggingInterval: record.loggingInterval, gmtOffset: record.gmtOffset, timestamp: record.timestamp }; } systemSetups.push(record); } }); if (aircraftInfo) { console.log(`Aircraft ID: ${aircraftInfo.aircraftId || aircraftInfo.serialNumber || 'Not found'}`); console.log(`Pilot Name: ${aircraftInfo.pilotName || 'Not found'}`); console.log(`System Setup records: ${systemSetups.length}`); } else { console.log('No System Setup (100) record found'); } if (satlocJobIds.size > 0) { console.log(`SatLoc Job IDs found: ${Array.from(satlocJobIds).join(', ')}`); console.log(`Swathing Setup records: ${swathingSetups.length}`); // Show job distribution in application details if (applicationDetails.length > 0) { const jobDistribution = {}; applicationDetails.forEach(detail => { const jobId = detail.satlocJobId || 'unknown'; jobDistribution[jobId] = (jobDistribution[jobId] || 0) + 1; }); console.log('\nApplication Details distribution by Job ID:'); Object.entries(jobDistribution).forEach(([jobId, count]) => { const percentage = ((count / applicationDetails.length) * 100).toFixed(1); console.log(` Job "${jobId}": ${count.toLocaleString()} records (${percentage}%)`); }); } } else { console.log('No SatLoc Job IDs found in Swathing Setup records'); } // Show top record types using parser statistics const sortedTypes = Object.entries(parser.statistics.recordTypes) // .sort(([, a], [, b]) => b - a) .slice(0, 5); // Create debug output with record names const sortedTypesWithNames = sortedTypes.map(([type, count]) => { const typeName = parser.getRecordTypeName(parseInt(type)); return `${typeName} (${type}) [${count}]`; }); console.log('\n=== Top Record Types ==='); const totalRecords = parser.statistics.totalRecords; sortedTypes.forEach(([type, count]) => { const typeName = parser.getRecordTypeName(parseInt(type)); // const shortName = typeName.split('_')[0]; const percentage = ((count / totalRecords) * 100).toFixed(1); console.log(`${typeName} (${type}): ${count.toLocaleString()} (${percentage}%)`); }); // Create flow segments from the ACTUAL sequence tracked by the parser const flowSegments = []; let currentType = null; let currentEnhanced = null; let currentCount = 0; parser.recordSequence.forEach(item => { const recordKey = `${item.recordType}:${item.isEnhanced ? 'enhanced' : 'basic'}`; if (item.recordType === currentType && item.isEnhanced === currentEnhanced) { currentCount++; } else { if (currentType !== null) { flowSegments.push({ type: currentType, count: currentCount, isEnhanced: currentEnhanced }); } currentType = item.recordType; currentEnhanced = item.isEnhanced; currentCount = 1; } }); // Add the last segment if (currentType !== null) { flowSegments.push({ type: currentType, count: currentCount, isEnhanced: currentEnhanced }); } // Show complete flow pattern console.log('\n=== Complete Data Flow Pattern (True Log File Sequence) ==='); console.log('Note: This shows the EXACT sequence as it appears in the binary log file\n'); let currentLine = ''; const maxLineLength = 100; let segmentCount = 0; for (let i = 0; i < flowSegments.length; i++) { const segment = flowSegments[i]; const typeName = parser.getRecordTypeName(segment.type); const shortName = typeName.split('_')[0]; // Add enhanced/extended indicator for Position records let displayName = shortName; if (segment.type === 1 && segment.isEnhanced) { // Position records displayName = `${shortName}_EXT`; // Extended/Enhanced } const item = `${displayName} (${segment.type}) [${segment.count}]`; const connector = i < flowSegments.length - 1 ? ' => ' : ''; // Check if adding this item would exceed line length if (currentLine.length + item.length + connector.length > maxLineLength && currentLine.length > 0) { console.log(currentLine); currentLine = item + connector; segmentCount++; // Add occasional breaks for readability if (segmentCount % 10 === 0 && segmentCount > 0) { console.log(''); // Empty line every 10 lines } } else { currentLine += item + connector; } } // Print the last line if there's content if (currentLine.length > 0) { console.log(currentLine); } console.log(`\nTrue flow segments: ${flowSegments.length.toLocaleString()}`); console.log(`Total records in sequence: ${parser.recordSequence.length.toLocaleString()}`); console.log(`Records in result array: ${result.records.length.toLocaleString()}`); // Show summary statistics about the flow const typeFrequency = {}; flowSegments.forEach(segment => { const key = segment.isEnhanced && segment.type === 1 ? `${segment.type}_enhanced` : segment.type; typeFrequency[key] = (typeFrequency[key] || 0) + 1; }); // Create debug output with record names const typeFrequencyWithNames = Object.entries(typeFrequency).map(([key, count]) => { let type, isEnhanced = false; if (key.includes('_enhanced')) { type = parseInt(key.replace('_enhanced', '')); isEnhanced = true; } else { type = parseInt(key); } const typeName = parser.getRecordTypeName(type); const enhancedLabel = isEnhanced ? '_ENHANCED' : ''; return `${typeName}${enhancedLabel} (${type}) [${count}]`; }); console.log('\n=== Record Type Segment Frequency ==='); const sortedFreq = Object.entries(typeFrequency) // .sort(([, a], [, b]) => b - a) .slice(0, 10); sortedFreq.forEach(([type, segments]) => { let displayName; if (type.includes('_enhanced')) { const baseType = type.replace('_enhanced', ''); const typeName = parser.getRecordTypeName(parseInt(baseType)); const shortName = typeName.split('_')[0]; displayName = `${shortName}_EXT (${baseType})`; } else { const typeName = parser.getRecordTypeName(parseInt(type)); // const shortName = typeName.split('_')[0]; displayName = `${typeName} (${type})`; } console.log(`${displayName}: appears in ${segments} separate segments`); }); // Show largest segments // const largestSegments = [...flowSegments] // // .sort((a, b) => b.count - a.count) // .slice(0, 5); // console.log('\n=== Largest Data Blocks ==='); // largestSegments.forEach((segment, index) => { // const typeName = parser.getRecordTypeName(segment.type); // const shortName = typeName.split('_')[0]; // let displayName = shortName; // if (segment.type === 1 && segment.isEnhanced) { // displayName = `${shortName}_EXT`; // } // const percentage = ((segment.count / result.records.length) * 100).toFixed(1); // console.log(`${index + 1}. ${displayName} (${segment.type}): ${segment.count.toLocaleString()} records (${percentage}%)`); // }); } catch (error) { console.error('Error analyzing SatLoc file:', error.message); if (error.stack) { console.error('Stack trace:', error.stack); } process.exit(1); } } // Run the analysis console.log(`Starting SatLoc pattern analysis with file: ${filePath}`); analyzeSatLocPattern();