#!/usr/bin/env python3 """ Deploy script for DR MURPHY ODE Dashboard to HuggingFace Spaces. Run this from any directory: python3 /path/to/hf_ode_space/deploy.py Steps: 1. Copy source files from the project tree into this flat directory 2. Patch sys.path imports for flat structure 3. Fix run_sweep_unified argument mismatch 4. Test import chain 5. Deploy to HuggingFace """ import shutil import os import sys import re HERE = os.path.dirname(os.path.abspath(__file__)) SRC_BASE = os.path.dirname(os.path.dirname(HERE)) # cycle2mdot_acceleration/ def step1_copy_files(): """Copy all source files into the flat deployment directory.""" print("Step 1: Copying source files...") # Dashboard files dash = os.path.join(SRC_BASE, 'ode_reformulation/dashboard') for f in ['scenario_gui.py', 'scenario_analyzer.py', 'sweep_cache.py', 'pump_animation.js']: src = os.path.join(dash, f) dst = os.path.join(HERE, f) shutil.copy2(src, dst) print(f" Copied dashboard/{f}") # ODE engine files ode = os.path.join(SRC_BASE, 'ode_reformulation') for f in ['cycle2mdot_ode_v2.py', 'cycle2mdot_10var_njit.py']: shutil.copy2(os.path.join(ode, f), os.path.join(HERE, f)) print(f" Copied ode_reformulation/{f}") # Project root files for f in ['cycle2mdot_fast.py', 'cycle2mdot_cached.py', 'h2_ps_lut.npz']: shutil.copy2(os.path.join(SRC_BASE, f), os.path.join(HERE, f)) print(f" Copied {f}") # break_coolprop directory bc_src = os.path.join(SRC_BASE, 'break_coolprop') bc_dst = os.path.join(HERE, 'break_coolprop') os.makedirs(bc_dst, exist_ok=True) for f in ['__init__.py', 'helmholtz_h2.py', 'lut_ps.py', 'h2_props.py', 'cycle2mdot_break.py']: shutil.copy2(os.path.join(bc_src, f), os.path.join(bc_dst, f)) print(f" Copied break_coolprop/{f}") # sweep_analysis.py is already created by the Write tool # engine_bridge.py, app.py, README.md, requirements.txt are already present print(" Done.") def step2_patch_imports(): """Patch sys.path hacks in copied engine files for flat structure.""" print("\nStep 2: Patching imports for flat structure...") # cycle2mdot_ode_v2.py: change sys.path.insert to use current dir _patch_sys_path(os.path.join(HERE, 'cycle2mdot_ode_v2.py')) # cycle2mdot_10var_njit.py: same _patch_sys_path(os.path.join(HERE, 'cycle2mdot_10var_njit.py')) print(" Done.") def _patch_sys_path(filepath): """Replace sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) with sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))""" with open(filepath, 'r') as f: content = f.read() # Replace grandparent path with current directory path old_pattern = "sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))" new_pattern = "sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))" if old_pattern in content: content = content.replace(old_pattern, new_pattern) with open(filepath, 'w') as f: f.write(content) print(f" Patched {os.path.basename(filepath)}: sys.path -> current dir") else: print(f" {os.path.basename(filepath)}: no sys.path patch needed") def step3_fix_sweep_args(): """Fix run_sweep_unified to accept the engine argument from sr_all_inputs.""" print("\nStep 3: Fixing run_sweep_unified argument mismatch...") gui_path = os.path.join(HERE, 'scenario_gui.py') with open(gui_path, 'r') as f: content = f.read() # Find the function signature and add engine parameter old_sig = " fill_type, vsnubber, vchss, aov140f, rodia,\n num_cycles=1):" new_sig = " fill_type, vsnubber, vchss, aov140f, rodia,\n num_cycles=1, engine='njit_10var'):" if old_sig in content and "def run_sweep_unified" in content: content = content.replace(old_sig, new_sig, 1) with open(gui_path, 'w') as f: f.write(content) print(" Added 'engine' parameter to run_sweep_unified()") else: print(" run_sweep_unified already has engine param or signature changed") print(" Done.") def step4_test_imports(): """Test that the import chain works in the flat structure.""" print("\nStep 4: Testing import chain...") # Add this directory to path if HERE not in sys.path: sys.path.insert(0, HERE) try: import engine_bridge print(f" engine_bridge: OK (engines: {engine_bridge.ENGINES})") except Exception as e: print(f" engine_bridge: FAILED - {e}") return False try: import scenario_analyzer print(" scenario_analyzer: OK") except Exception as e: print(f" scenario_analyzer: FAILED - {e}") return False try: import sweep_analysis print(" sweep_analysis: OK") except Exception as e: print(f" sweep_analysis: FAILED - {e}") return False try: import sweep_cache print(" sweep_cache: OK") except Exception as e: print(f" sweep_cache: FAILED - {e}") return False print(" All imports OK!") return True def step5_deploy(): """Deploy to HuggingFace Spaces.""" print("\nStep 5: Deploying to HuggingFace...") try: from huggingface_hub import HfApi except ImportError: print(" huggingface_hub not installed. Installing...") os.system(f"{sys.executable} -m pip install huggingface_hub") from huggingface_hub import HfApi api = HfApi() # Create the repo (or verify it exists) try: api.create_repo("csh2/ode-pump-dashboard", repo_type="space", space_sdk="gradio", exist_ok=True) print(" Repository csh2/ode-pump-dashboard created/verified.") except Exception as e: print(f" Error creating repo: {e}") print(" You may need to run: huggingface-cli login") return False # Upload the folder (exclude deploy.py, __pycache__, .db files) try: api.upload_folder( folder_path=HERE, repo_id="csh2/ode-pump-dashboard", repo_type="space", ignore_patterns=["deploy.py", "__pycache__", "*.db", "*.db-shm", "*.db-wal", ".DS_Store", "*.pyc"], ) print(" Upload complete!") print(" Space URL: https://huggingface.co/spaces/csh2/ode-pump-dashboard") except Exception as e: print(f" Upload failed: {e}") print(" You may need to run: huggingface-cli login") return False return True def main(): print("=" * 60) print("DR MURPHY ODE -- HuggingFace Space Deployment") print("=" * 60) step1_copy_files() step2_patch_imports() step3_fix_sweep_args() ok = step4_test_imports() if not ok: print("\nImport test failed. Fix errors before deploying.") sys.exit(1) step5_deploy() print("\n" + "=" * 60) print("Deployment complete!") print("=" * 60) if __name__ == "__main__": main()