"""Single-command launcher for the Self-Healing ML system. Starts API + Dashboard, waits for API to be ready, then runs drift simulation. Usage: python run.py # gradual drift, 500 steps python run.py --drift sudden # sudden drift python run.py --no-sim # API + dashboard only (no simulation) """ from __future__ import annotations import argparse import subprocess import sys import time import webbrowser from pathlib import Path ROOT = Path(__file__).resolve().parent # Always use the Python from the venv next to this file, regardless of how # the script was invoked (so `python run.py` works without activating venv). _venv_python = ROOT / ".venv" / "Scripts" / "python.exe" VENV_PYTHON = str(_venv_python) if _venv_python.exists() else sys.executable API_URL = "http://localhost:8000" DASH_URL = "http://localhost:8501" def wait_for_api(timeout: int = 30) -> bool: import urllib.request print(" Waiting for API to start", end="", flush=True) for _ in range(timeout): try: urllib.request.urlopen(f"{API_URL}/health", timeout=2) print(" ready!") return True except Exception: pass print(".", end="", flush=True) time.sleep(1) print(" TIMEOUT") return False def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--drift", default="gradual", choices=["gradual", "sudden", "seasonal", "mixed"]) parser.add_argument("--steps", type=int, default=500) parser.add_argument("--no-sim", action="store_true", help="Start API + dashboard only, skip simulation") args = parser.parse_args() procs = [] print("\n========================================") print(" Self-Healing ML — Starting services") print("========================================\n") # 1. Start API print("[1/3] Starting FastAPI server...") api_proc = subprocess.Popen( [VENV_PYTHON, "-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"], cwd=ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, ) procs.append(api_proc) if not wait_for_api(): print("ERROR: API failed to start. Check logs.") for p in procs: p.terminate() sys.exit(1) # 2. Start dashboard print("[2/3] Starting Streamlit dashboard...") dash_proc = subprocess.Popen( [VENV_PYTHON, "-m", "streamlit", "run", "dashboard/app.py", "--server.port", "8501", "--server.headless", "true", "--browser.gatherUsageStats", "false"], cwd=ROOT, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT, ) procs.append(dash_proc) time.sleep(3) # give streamlit a moment print(f"\n API docs : {API_URL}/docs") print(f" Dashboard : {DASH_URL}") print() # Open browser try: webbrowser.open(DASH_URL) except Exception: pass if args.no_sim: print("Services running. Press Ctrl+C to stop.\n") try: while True: time.sleep(1) except KeyboardInterrupt: pass else: # 3. Run simulation print(f"[3/3] Running drift simulation (type={args.drift}, steps={args.steps})...") print(" Dashboard will update every 10s — enable Auto-refresh in sidebar.\n") run = 1 try: while True: drift_types = ["gradual", "sudden", "gradual", "seasonal"] drift = drift_types[(run - 1) % len(drift_types)] print(f" Simulation run #{run} (drift={drift})...") subprocess.run( [VENV_PYTHON, "scripts/simulate_drift.py", "--drift-type", drift, "--steps", str(args.steps), "--delay", "0.05", "--feedback-lag", "5"], cwd=ROOT, check=True, ) run += 1 except KeyboardInterrupt: pass except subprocess.CalledProcessError as e: print(f"Simulation error: {e}") print("\nShutting down...") for p in procs: p.terminate() if __name__ == "__main__": main()