# RFCP Dependencies & Installer Specification ## Overview All dependencies needed for RFCP to work out of the box, including GPU acceleration. The installer must handle everything — user should NOT need to run pip manually. --- ## Python Dependencies ### Core (MUST have) ```txt # requirements.txt # Web framework fastapi>=0.104.0 uvicorn[standard]>=0.24.0 websockets>=12.0 # Scientific computing numpy>=1.24.0 scipy>=1.11.0 # Geospatial pyproj>=3.6.0 # coordinate transformations shapely>=2.0.0 # geometry operations (boundary contours) # Terrain data rasterio>=1.3.0 # GeoTIFF reading (optional, for custom terrain) # Note: SRTM .hgt files read with numpy directly # OSM data requests>=2.31.0 # HTTP client for OSM Overpass API geopy>=2.4.0 # distance calculations # Database # sqlite3 is built-in Python — no install needed # Utilities orjson>=3.9.0 # fast JSON (optional, faster API responses) pydantic>=2.0.0 # data validation (FastAPI dependency) ``` ### GPU Acceleration (OPTIONAL — auto-detected) ```txt # requirements-gpu-nvidia.txt cupy-cuda12x>=12.0.0 # For CUDA 12.x (RTX 30xx, 40xx) # OR cupy-cuda11x>=11.0.0 # For CUDA 11.x (older cards) # requirements-gpu-opencl.txt pyopencl>=2023.1 # For ANY GPU (Intel, AMD, NVIDIA) ``` ### Development / Testing ```txt # requirements-dev.txt pytest>=7.0.0 pytest-asyncio>=0.21.0 httpx>=0.25.0 # async test client ``` --- ## System Dependencies ### NVIDIA GPU Support ``` REQUIRED: NVIDIA Driver (comes with GPU) REQUIRED: CUDA Toolkit 12.x (for CuPy) Check if installed: nvidia-smi → shows driver version nvcc --version → shows CUDA toolkit version If missing CUDA toolkit: Download from: https://developer.nvidia.com/cuda-downloads Select: Windows > x86_64 > 11/10 > exe (local) Size: ~3 GB Alternative: cupy auto-installs CUDA runtime! pip install cupy-cuda12x This bundles CUDA runtime (~700 MB) — no separate install needed ``` ### Intel GPU Support (OpenCL) ``` REQUIRED: Intel GPU Driver (usually pre-installed) REQUIRED: Intel OpenCL Runtime Check if installed: Open Device Manager → Display Adapters → Intel UHD/Iris For OpenCL: Download Intel GPU Computing Runtime: https://github.com/intel/compute-runtime/releases Or: Intel oneAPI Base Toolkit (includes OpenCL) https://www.intel.com/content/www/us/en/developer/tools/oneapi/base-toolkit-download.html ``` ### AMD GPU Support (OpenCL) ``` REQUIRED: AMD Adrenalin Driver (includes OpenCL) Download from: https://www.amd.com/en/support ``` --- ## Node.js / Frontend Dependencies ### System Requirements ``` Node.js >= 18.0.0 (LTS recommended) npm >= 9.0.0 Check: node --version npm --version ``` ### Frontend packages (managed by npm) ```json // package.json — key dependencies { "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", "leaflet": "^1.9.4", "react-leaflet": "^4.2.0", "recharts": "^2.8.0", "zustand": "^4.4.0", "lucide-react": "^0.294.0" }, "devDependencies": { "vite": "^5.0.0", "typescript": "^5.3.0", "tailwindcss": "^3.4.0", "@types/leaflet": "^1.9.0" } } ``` --- ## Installer Script ### Windows Installer (NSIS or Electron-Builder) ```python # install_rfcp.py — Python-based installer/setup script import subprocess import sys import platform import os import shutil import json def check_python(): """Verify Python 3.10+ is available.""" version = sys.version_info if version.major < 3 or version.minor < 10: print(f"❌ Python 3.10+ required, found {version.major}.{version.minor}") return False print(f"✅ Python {version.major}.{version.minor}.{version.micro}") return True def check_node(): """Verify Node.js 18+ is available.""" try: result = subprocess.run(["node", "--version"], capture_output=True, text=True) version = result.stdout.strip().lstrip('v') major = int(version.split('.')[0]) if major < 18: print(f"❌ Node.js 18+ required, found {version}") return False print(f"✅ Node.js {version}") return True except FileNotFoundError: print("❌ Node.js not found") return False def detect_gpu(): """Detect available GPU hardware.""" gpus = { "nvidia": False, "nvidia_name": "", "intel": False, "intel_name": "", "amd": False, "amd_name": "" } # Check NVIDIA try: result = subprocess.run( ["nvidia-smi", "--query-gpu=name,driver_version,memory.total", "--format=csv,noheader"], capture_output=True, text=True, timeout=5 ) if result.returncode == 0: info = result.stdout.strip() gpus["nvidia"] = True gpus["nvidia_name"] = info.split(",")[0].strip() print(f"✅ NVIDIA GPU: {info}") except (FileNotFoundError, subprocess.TimeoutExpired): print("ℹ️ No NVIDIA GPU detected") # Check Intel/AMD via WMI (Windows) if platform.system() == "Windows": try: result = subprocess.run( ["wmic", "path", "win32_videocontroller", "get", "name,adapterram,driverversion", "/format:csv"], capture_output=True, text=True, timeout=5 ) for line in result.stdout.strip().split('\n'): if 'Intel' in line: gpus["intel"] = True gpus["intel_name"] = [x for x in line.split(',') if 'Intel' in x][0] print(f"✅ Intel GPU: {gpus['intel_name']}") elif 'AMD' in line or 'Radeon' in line: gpus["amd"] = True gpus["amd_name"] = [x for x in line.split(',') if 'AMD' in x or 'Radeon' in x][0] print(f"✅ AMD GPU: {gpus['amd_name']}") except Exception: pass return gpus def install_core_dependencies(): """Install core Python dependencies.""" print("\n📦 Installing core dependencies...") subprocess.run([ sys.executable, "-m", "pip", "install", "-r", "requirements.txt", "--quiet", "--no-warn-script-location" ], check=True) print("✅ Core dependencies installed") def install_gpu_dependencies(gpus: dict): """Install GPU-specific dependencies based on detected hardware.""" print("\n🎮 Setting up GPU acceleration...") gpu_installed = False # NVIDIA — install CuPy (includes CUDA runtime) if gpus["nvidia"]: print(f" Installing CuPy for {gpus['nvidia_name']}...") try: # Try CUDA 12 first (newer cards) subprocess.run([ sys.executable, "-m", "pip", "install", "cupy-cuda12x", "--quiet", "--no-warn-script-location" ], check=True, timeout=300) print(f" ✅ CuPy (CUDA 12) installed for {gpus['nvidia_name']}") gpu_installed = True except (subprocess.CalledProcessError, subprocess.TimeoutExpired): try: # Fallback to CUDA 11 subprocess.run([ sys.executable, "-m", "pip", "install", "cupy-cuda11x", "--quiet", "--no-warn-script-location" ], check=True, timeout=300) print(f" ✅ CuPy (CUDA 11) installed for {gpus['nvidia_name']}") gpu_installed = True except Exception as e: print(f" ⚠️ CuPy installation failed: {e}") print(f" 💡 Manual install: pip install cupy-cuda12x") # Intel/AMD — install PyOpenCL if gpus["intel"] or gpus["amd"]: gpu_name = gpus["intel_name"] or gpus["amd_name"] print(f" Installing PyOpenCL for {gpu_name}...") try: subprocess.run([ sys.executable, "-m", "pip", "install", "pyopencl", "--quiet", "--no-warn-script-location" ], check=True, timeout=120) print(f" ✅ PyOpenCL installed for {gpu_name}") gpu_installed = True except Exception as e: print(f" ⚠️ PyOpenCL installation failed: {e}") print(f" 💡 Manual install: pip install pyopencl") if not gpu_installed: print(" ℹ️ No GPU acceleration available — using CPU (NumPy)") print(" 💡 This is fine! GPU just makes large calculations faster.") return gpu_installed def install_frontend(): """Install frontend dependencies and build.""" print("\n🌐 Setting up frontend...") frontend_dir = os.path.join(os.path.dirname(__file__), "frontend") if os.path.exists(os.path.join(frontend_dir, "package.json")): subprocess.run(["npm", "install"], cwd=frontend_dir, check=True) subprocess.run(["npm", "run", "build"], cwd=frontend_dir, check=True) print("✅ Frontend built") else: print("⚠️ Frontend directory not found") def download_terrain_data(): """Pre-download SRTM terrain tiles for Ukraine.""" print("\n🏔️ Checking terrain data...") cache_dir = os.path.expanduser("~/.rfcp/terrain") os.makedirs(cache_dir, exist_ok=True) # Ukraine bounding box: lat 44-53, lon 22-41 # SRTM tiles needed for typical use required_tiles = [ # Lviv oblast area (common test area) "N49E025", "N49E024", "N49E026", "N50E025", "N50E024", "N50E026", # Dnipro area "N48E034", "N48E035", "N49E034", "N49E035", ] existing = [f.replace(".hgt", "") for f in os.listdir(cache_dir) if f.endswith(".hgt")] missing = [t for t in required_tiles if t not in existing] if missing: print(f" {len(missing)} terrain tiles needed (auto-download on first use)") else: print(f" ✅ {len(existing)} terrain tiles cached") def create_launcher(): """Create desktop shortcut / launcher script.""" print("\n🚀 Creating launcher...") if platform.system() == "Windows": # Create .bat launcher launcher = os.path.join(os.path.dirname(__file__), "RFCP.bat") with open(launcher, 'w') as f: f.write('@echo off\n') f.write('title RFCP - RF Coverage Planner\n') f.write('echo Starting RFCP...\n') f.write(f'cd /d "{os.path.dirname(__file__)}"\n') f.write(f'"{sys.executable}" -m uvicorn backend.app.main:app --host 0.0.0.0 --port 8888\n') print(f" ✅ Launcher created: {launcher}") return True def verify_installation(): """Run quick verification tests.""" print("\n🔍 Verifying installation...") checks = [] # Check core imports try: import numpy as np checks.append(f"✅ NumPy {np.__version__}") except ImportError: checks.append("❌ NumPy missing") try: import scipy checks.append(f"✅ SciPy {scipy.__version__}") except ImportError: checks.append("❌ SciPy missing") try: import fastapi checks.append(f"✅ FastAPI {fastapi.__version__}") except ImportError: checks.append("❌ FastAPI missing") try: import shapely checks.append(f"✅ Shapely {shapely.__version__}") except ImportError: checks.append("⚠️ Shapely missing (boundary features disabled)") # Check GPU try: import cupy as cp device = cp.cuda.Device(0) checks.append(f"✅ CuPy → {device.name} ({device.mem_info[1]//1024//1024} MB)") except ImportError: checks.append("ℹ️ CuPy not available") except Exception as e: checks.append(f"⚠️ CuPy error: {e}") try: import pyopencl as cl devices = [] for p in cl.get_platforms(): for d in p.get_devices(): devices.append(d.name) checks.append(f"✅ PyOpenCL → {', '.join(devices)}") except ImportError: checks.append("ℹ️ PyOpenCL not available") except Exception as e: checks.append(f"⚠️ PyOpenCL error: {e}") for check in checks: print(f" {check}") return all("❌" not in c for c in checks) def main(): """Main installer entry point.""" print("=" * 60) print(" RFCP — RF Coverage Planner — Installer") print("=" * 60) print() # Step 1: Check prerequisites print("📋 Checking prerequisites...") if not check_python(): sys.exit(1) check_node() # Step 2: Detect GPU gpus = detect_gpu() # Step 3: Install dependencies install_core_dependencies() install_gpu_dependencies(gpus) # Step 4: Frontend install_frontend() # Step 5: Terrain data download_terrain_data() # Step 6: Launcher create_launcher() # Step 7: Verify print() success = verify_installation() # Summary print() print("=" * 60) if success: print(" ✅ RFCP installed successfully!") print() print(" To start RFCP:") print(" python -m uvicorn backend.app.main:app --port 8888") print(" Then open: http://localhost:8888") print() if gpus["nvidia"]: print(f" 🎮 GPU: {gpus['nvidia_name']} (CUDA)") elif gpus["intel"] or gpus["amd"]: gpu_name = gpus["intel_name"] or gpus["amd_name"] print(f" 🎮 GPU: {gpu_name} (OpenCL)") else: print(" 💻 Mode: CPU only") else: print(" ⚠️ Installation completed with warnings") print(" Some features may be limited") print("=" * 60) if __name__ == "__main__": main() ``` --- ## Electron-Builder / NSIS Packaging ### For .exe Installer ```yaml # electron-builder.yml appId: com.rfcp.coverage-planner productName: "RFCP - RF Coverage Planner" copyright: "RFCP 2026" directories: output: dist buildResources: build files: - "backend/**/*" - "frontend/dist/**/*" - "requirements.txt" - "install_rfcp.py" - "!**/*.pyc" - "!**/node_modules/**" - "!**/venv/**" extraResources: - from: "python-embedded/" to: "python/" - from: "terrain-data/" to: "terrain/" win: target: - target: nsis arch: [x64] icon: "build/icon.ico" nsis: oneClick: false allowToChangeInstallationDirectory: true installerIcon: "build/icon.ico" license: "LICENSE.md" # Custom NSIS script for GPU detection include: "build/gpu-detect.nsh" # Install steps: # 1. Extract files # 2. Run install_rfcp.py (detects GPU, installs deps) # 3. Create Start Menu shortcuts # 4. Create Desktop shortcut ``` ### Portable Version (.zip) ``` RFCP-Portable/ ├── RFCP.bat # Main launcher ├── install.bat # First-time setup ├── backend/ │ ├── app/ │ │ ├── main.py │ │ ├── api/ │ │ ├── services/ │ │ └── models/ │ └── requirements.txt ├── frontend/ │ └── dist/ # Pre-built frontend ├── python/ # Embedded Python (optional) │ ├── python.exe │ └── Lib/ ├── terrain/ # Pre-cached .hgt files │ ├── N49E025.hgt │ └── ... ├── data/ │ ├── osm_cache.db # SQLite cache (created on first run) │ └── config.json # User settings └── README.md ``` ### install.bat (First-Time Setup) ```batch @echo off title RFCP - First Time Setup echo ============================================ echo RFCP - RF Coverage Planner - Setup echo ============================================ echo. REM Check if Python exists python --version >nul 2>&1 if errorlevel 1 ( echo ERROR: Python not found! echo Please install Python 3.10+ from python.org pause exit /b 1 ) REM Run installer python install_rfcp.py echo. echo Setup complete! Run RFCP.bat to start. pause ``` ### RFCP.bat (Launcher) ```batch @echo off title RFCP - RF Coverage Planner cd /d "%~dp0" REM Check if installed if not exist "backend\app\main.py" ( echo ERROR: RFCP not found. Run install.bat first. pause exit /b 1 ) echo Starting RFCP... echo Open http://localhost:8888 in your browser echo Press Ctrl+C to stop echo. python -m uvicorn backend.app.main:app --host 0.0.0.0 --port 8888 ``` --- ## Dependency Size Estimates | Component | Size | |-----------|------| | Python (embedded) | ~30 MB | | Core pip packages | ~80 MB | | CuPy + CUDA runtime | ~700 MB | | PyOpenCL | ~15 MB | | Frontend (built) | ~5 MB | | SRTM terrain (Ukraine) | ~300 MB | | **Total (with CUDA)** | **~1.1 GB** | | **Total (CPU only)** | **~415 MB** | --- ## Runtime Requirements | Resource | Minimum | Recommended | |----------|---------|-------------| | RAM | 4 GB | 8+ GB | | Disk | 500 MB | 2 GB (with terrain cache) | | CPU | 4 cores | 8+ cores | | GPU | - | NVIDIA GTX 1060+ / Intel UHD 630+ | | OS | Windows 10 | Windows 10/11 64-bit | | Python | 3.10 | 3.11+ | | Node.js | 18 | 20 LTS | --- ## Auto-Update Mechanism (Future) ```python # Check for updates on startup async def check_for_updates(): try: response = await httpx.get( "https://api.github.com/repos/user/rfcp/releases/latest", timeout=5 ) latest = response.json()["tag_name"] current = get_current_version() if latest != current: return { "update_available": True, "current": current, "latest": latest, "download_url": response.json()["assets"][0]["browser_download_url"] } except: pass return {"update_available": False} ```