#!/usr/bin/env python3 """ Automated Daily Scans for Discovered Routes Reads discovered_routes.json and runs targeted daily scans. Much faster than scanning all airports because it only queries known routes. Usage: # First, discover routes python discover_routes.py --from BDS --to-country DE --window 3 # Then, run targeted daily scans python scan_discovered_routes.py discovered_routes.json --daily-scan """ import json import subprocess import sys import click from datetime import datetime @click.command() @click.argument('routes_file', type=click.Path(exists=True)) @click.option('--daily-scan', is_flag=True, help='Run daily scans (vs seasonal)') @click.option('--start-date', help='Start date for daily scan (YYYY-MM-DD)') @click.option('--end-date', help='End date for daily scan (YYYY-MM-DD)') @click.option('--window', type=int, help='Override window months from discovery') @click.option('--workers', default=5, type=int, help='Concurrency level (default: 5)') @click.option('--output-dir', default='results', help='Directory to save results (default: results)') @click.option('--dry-run', is_flag=True, help='Show what would be scanned without executing') def scan_discovered(routes_file, daily_scan, start_date, end_date, window, workers, output_dir, dry_run): """ Run targeted scans on discovered routes. Example: python scan_discovered_routes.py discovered_routes.json --daily-scan """ # Load discovered routes with open(routes_file, 'r') as f: data = json.load(f) origin = data['origin'] routes = data['routes'] default_window = data.get('window_months', 3) if window is None: window = default_window print() print("=" * 70) print("TARGETED SCAN OF DISCOVERED ROUTES") print("=" * 70) print(f"Origin: {origin}") print(f"Discovered routes: {len(routes)}") print(f"Mode: {'Daily scan' if daily_scan else 'Seasonal scan'}") if daily_scan and start_date and end_date: print(f"Date range: {start_date} to {end_date}") else: print(f"Window: {window} months") print(f"Workers: {workers}") print() if not routes: print("⚠️ No routes to scan!") print(f"Discovery file {routes_file} contains no routes with flights.") sys.exit(1) # Display routes to scan print("Routes to scan:") for i, route in enumerate(routes, 1): dest = route['destination'] city = route['destination_city'] airlines = ', '.join(route['airlines'][:2]) if len(route['airlines']) > 2: airlines += f" +{len(route['airlines']) - 2}" print(f" {i}. {origin} → {dest} ({city}) - {airlines}") print() if dry_run: print("=" * 70) print("DRY RUN - Commands that would be executed:") print("=" * 70) print() # Build and execute commands results_summary = { "scan_date": datetime.now().isoformat(), "origin": origin, "routes_scanned": len(routes), "mode": "daily" if daily_scan else "seasonal", "results": [] } for i, route in enumerate(routes, 1): dest = route['destination'] city = route['destination_city'] # Build command cmd_parts = [ "python", "main.py", "--from", origin, "--to", dest, ] if daily_scan: cmd_parts.append("--daily-scan") if start_date: cmd_parts.extend(["--start-date", start_date]) if end_date: cmd_parts.extend(["--end-date", end_date]) if not start_date and not end_date: cmd_parts.extend(["--window", str(window)]) else: cmd_parts.extend(["--window", str(window)]) cmd_parts.extend(["--workers", str(workers)]) # Add output file output_file = f"{output_dir}/{origin}_{dest}_{'daily' if daily_scan else 'seasonal'}.json" cmd_parts.extend(["--output", "json"]) command = " ".join(cmd_parts) print(f"[{i}/{len(routes)}] Scanning {origin} → {dest} ({city})") if dry_run: print(f" Command: {command}") print() continue try: # Execute command result = subprocess.run( command, shell=True, capture_output=True, text=True, timeout=600 # 10 minute timeout per route ) output = result.stdout + result.stderr # Parse results (look for flight count) import re flights_match = re.search(r'Flights Found:\s+(\d+)', output) flights_found = int(flights_match.group(1)) if flights_match else 0 # Save output to file import os os.makedirs(output_dir, exist_ok=True) with open(output_file, 'w') as f: f.write(output) results_summary["results"].append({ "destination": dest, "destination_city": city, "flights_found": flights_found, "output_file": output_file, "success": result.returncode == 0 }) print(f" ✅ Complete - {flights_found} flights found") print(f" 📄 Saved to: {output_file}") except subprocess.TimeoutExpired: print(f" ⏱️ Timeout - scan took too long") results_summary["results"].append({ "destination": dest, "destination_city": city, "error": "timeout", "success": False }) except Exception as e: print(f" ❌ Error: {e}") results_summary["results"].append({ "destination": dest, "destination_city": city, "error": str(e), "success": False }) print() if not dry_run: # Save summary summary_file = f"{output_dir}/scan_summary.json" with open(summary_file, 'w') as f: json.dump(results_summary, f, indent=2) # Display summary print("=" * 70) print("SCAN SUMMARY") print("=" * 70) total_scanned = len(routes) successful = sum(1 for r in results_summary["results"] if r.get("success", False)) total_flights = sum(r.get("flights_found", 0) for r in results_summary["results"]) print(f"Routes scanned: {total_scanned}") print(f"Successful: {successful}/{total_scanned}") print(f"Total flights found: {total_flights}") print() print(f"Results saved to: {output_dir}/") print(f"Summary: {summary_file}") print() # Show top routes by flight count sorted_results = sorted( results_summary["results"], key=lambda x: x.get("flights_found", 0), reverse=True ) print("Top routes by flight count:") for route in sorted_results[:5]: if route.get("flights_found", 0) > 0: print(f" {origin} → {route['destination']}: {route['flights_found']} flights") print() if __name__ == '__main__': scan_discovered()