agmission/Development/server/test_satloc_pattern_brief.js

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();