361 lines
13 KiB
JavaScript
361 lines
13 KiB
JavaScript
#!/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 <satloc-file-path>');
|
|
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();
|