#!/usr/bin/env python3 """ Report Generator Tool Regenerates markdown reports from JSON data files saved by the LoRa Mesh Analyzer. Usage: python report_generate.py [--output ] Example: python report_generate.py reports/report-20251128-145548.json python report_generate.py reports/report-20251128-145548.json --output custom-report.md """ import json import sys import os import argparse from datetime import datetime # Add mesh_analyzer to path (parent directory) sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) from mesh_analyzer.reporter import NetworkReporter from mesh_analyzer.route_analyzer import RouteAnalyzer def load_json_data(json_filepath): """ Load raw data from JSON file. """ if not os.path.exists(json_filepath): print(f"Error: File not found: {json_filepath}") sys.exit(1) try: with open(json_filepath, 'r', encoding='utf-8') as f: data = json.load(f) return data except json.JSONDecodeError as e: print(f"Error: Invalid JSON file: {e}") sys.exit(1) except Exception as e: print(f"Error loading file: {e}") sys.exit(1) def generate_report_from_json(json_filepath, output_path=None): """ Regenerate markdown report from JSON data. """ print(f"Loading data from: {json_filepath}") # Load the JSON data full_data = load_json_data(json_filepath) # Extract session and data session = full_data.get('session', {}) data = full_data.get('data', {}) # Extract all the components nodes = data.get('nodes', {}) test_results = data.get('test_results', []) analysis_issues = data.get('analysis_issues', []) router_stats = data.get('router_stats', []) route_analysis = data.get('route_analysis', {}) local_node = data.get('local_node') config = session.get('config', {}) print(f"Session timestamp: {session.get('timestamp', 'Unknown')}") print(f"Nodes: {len(nodes)}") print(f"Test results: {len(test_results)}") print(f"Analysis issues: {len(analysis_issues)}") # Create a custom reporter that generates the file at the specified location if output_path: # Use the directory and filename from output_path report_dir = os.path.dirname(output_path) or "." filename_base = os.path.basename(output_path).replace('.md', '') else: # Generate new report in reports/ with regenerated timestamp report_dir = "reports" filename_base = None reporter = NetworkReporter(report_dir=report_dir, config=config) # Apply manual positions from config to nodes # This ensures that even if the JSON data lacks positions, we use the latest config manual_positions = config.get('manual_positions', {}) if manual_positions: print(f"Applying {len(manual_positions)} manual positions from config...") for node_id, pos in manual_positions.items(): if node_id in nodes: node = nodes[node_id] if 'position' not in node: node['position'] = {} if 'lat' in pos and 'lon' in pos: node['position']['latitude'] = pos['lat'] node['position']['longitude'] = pos['lon'] # Recreate analyzer and re-run analysis to populate cluster_data and ch_util_data from mesh_analyzer.analyzer import NetworkHealthAnalyzer analyzer = NetworkHealthAnalyzer(config=config) # Re-run analysis to populate analyzer data structures AND get new issues new_issues = analyzer.analyze(nodes, packet_history=[], my_node=local_node, test_results=test_results) # Run additional checks if test_results: new_issues.extend(analyzer.check_router_efficiency(nodes, test_results=test_results)) new_issues.extend(analyzer.check_route_quality(nodes, test_results=test_results)) # Use new issues for the report analysis_issues = new_issues # Determine output filename if provided output_filename = None if output_path: output_filename = os.path.basename(output_path) # Extract session metadata # Use the 'session' variable already extracted from 'full_data' original_timestamp = session.get('timestamp') test_location = session.get('test_location') # Generate the report result = reporter.generate_report( nodes=nodes, test_results=test_results, analysis_issues=analysis_issues, local_node=local_node, router_stats=router_stats, analyzer=analyzer, # Pass analyzer parameter override_timestamp=original_timestamp, override_location=test_location, save_json=False, # Do not overwrite JSON when regenerating output_filename=output_filename ) return result def main(): parser = argparse.ArgumentParser( description='Regenerate markdown reports from JSON data files', formatter_class=argparse.RawDescriptionHelpFormatter, epilog=""" Examples: python report_generate.py reports/report-20251128-145548.json python report_generate.py reports/report-20251128-145548.json --output custom-report.md """ ) parser.add_argument( 'json_file', help='Path to the JSON data file' ) parser.add_argument( '--output', '-o', help='Custom output path for the markdown report (optional)', default=None ) args = parser.parse_args() # Generate the report generate_report_from_json(args.json_file, args.output) if __name__ == "__main__": main()