#!/usr/bin/env python3 # HL REV:NEWFILE """ Automated probes for Trajectories tab diagnostics. This script runs several simulated scenarios to diagnose the trajectories tab: 1. NORMAL: Uses real data from data/motions.db 2. EMPTY_POSITIONS: Simulates load_positions returning empty (realistic failure mode) 3. EMPTY_PARTY_MAP: Simulates load_party_map returning empty (realistic failure mode) 4. BOTH_EMPTY: Simulates both returning empty (severe failure mode) Run: python scripts/diagnose_trajectories_cli.py """ import os import importlib import traceback import sys import json from datetime import datetime # Real DB path relative to project root DB_PATH = os.path.abspath( os.path.join(os.path.dirname(__file__), "..", "data", "motions.db") ) def run(): os.environ.setdefault("EXPLORER_DEBUG_TRAJECTORIES", "1") # Ensure project root is on sys.path so 'import explorer' finds the module root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) if root not in sys.path: sys.path.insert(0, root) # Import explorer fresh so env var reads take effect import explorer # Results collection for all scenarios all_results = { "generated_at": datetime.now().isoformat(), "db_path": DB_PATH, "db_exists": os.path.exists(DB_PATH), "scenarios": {}, } def run_scenario( name, load_positions_ret=None, load_party_map_ret=None, select_helper_behavior=None, use_real_data=False, ): print("\n" + "=" * 80) print("SCENARIO:", name) print("=" * 80) scenario_result = { "name": name, "use_real_data": use_real_data, "monkeypatched": {}, } # Clear previous diagnostics explorer._last_trajectories_diagnostics.clear() # Backup originals orig_load_positions = getattr(explorer, "load_positions", None) orig_load_party_map = getattr(explorer, "load_party_map", None) orig_select_helper = getattr(explorer, "select_trajectory_plot_data", None) # If using real data, call the real functions once to get actual values if use_real_data and os.path.exists(DB_PATH): print(f"Loading real data from: {DB_PATH}") try: real_positions = explorer.load_positions(DB_PATH, "annual") real_party_map = explorer.load_party_map(DB_PATH) print( f" Real load_positions: {len(real_positions[0]) if real_positions[0] else 0} windows" ) print(f" Real party_map: {len(real_party_map)} entries") scenario_result["real_data"] = { "positions_windows": len(real_positions[0]) if real_positions[0] else 0, "party_map_count": len(real_party_map), } except Exception as e: print(f" ERROR loading real data: {e}") scenario_result["real_data_error"] = str(e) if load_positions_ret is not None: explorer.load_positions = lambda db, ws: load_positions_ret scenario_result["monkeypatched"]["load_positions"] = "ARTIFICIAL_EMPTY" print( " Monkeypatched: load_positions -> ARTIFICIAL_EMPTY (for comparison)" ) if load_party_map_ret is not None: explorer.load_party_map = lambda db: load_party_map_ret scenario_result["monkeypatched"]["load_party_map"] = "ARTIFICIAL_EMPTY" print( " Monkeypatched: load_party_map -> ARTIFICIAL_EMPTY (for comparison)" ) if select_helper_behavior == "raise": def raising(*args, **kwargs): raise ValueError("simulated crash from select_trajectory_plot_data") explorer.select_trajectory_plot_data = raising scenario_result["monkeypatched"]["select_helper"] = "RAISE_ERROR" print(" Monkeypatched: select_trajectory_plot_data -> RAISE_ERROR") elif select_helper_behavior == "zero_traces": class DummyFig: def __init__(self): self.data = [] def zero(*args, **kwargs): return DummyFig(), 0, None explorer.select_trajectory_plot_data = zero scenario_result["monkeypatched"]["select_helper"] = "ZERO_TRACES" print(" Monkeypatched: select_trajectory_plot_data -> ZERO_TRACES") try: # Call the UI function; it's import-safe and uses a dummy st when streamlit is absent explorer.build_trajectories_tab(db_path=DB_PATH, window_size="annual") except Exception as e: print("build_trajectories_tab RAISED:", type(e), e) print(traceback.format_exc()) scenario_result["exception"] = str(e) scenario_result["traceback"] = traceback.format_exc() # Capture diagnostics diag = getattr(explorer, "_last_trajectories_diagnostics", None) scenario_result["diagnostics"] = dict(diag) if diag else {} print("module _last_trajectories_diagnostics:", diag) sh = None if hasattr(explorer, "select_trajectory_plot_data"): sh = getattr( explorer.select_trajectory_plot_data, "_last_diagnostics", None ) scenario_result["select_helper_diagnostics"] = dict(sh) if sh else {} print("select_helper _last_diagnostics:", sh) # restore originals if orig_load_positions is not None: explorer.load_positions = orig_load_positions if orig_load_party_map is not None: explorer.load_party_map = orig_load_party_map if orig_select_helper is not None: explorer.select_trajectory_plot_data = orig_select_helper all_results["scenarios"][name] = scenario_result # Scenario 1: NORMAL - Uses real data print("\n" + "=" * 80) print("SCENARIO 1: NORMAL (using real data)") print("=" * 80) run_scenario("normal", use_real_data=True) # Scenario 2: Empty positions (ARTIFICIAL - for comparison) print("\n" + "=" * 80) print("SCENARIO 2: EMPTY_POSITIONS (ARTIFICIAL - for comparison)") print("=" * 80) run_scenario( "empty_positions_ARTIFICIAL", load_positions_ret=({}, {}), load_party_map_ret=None, # Use real party_map ) # Scenario 3: Empty party_map (ARTIFICIAL - for comparison) print("\n" + "=" * 80) print("SCENARIO 3: EMPTY_PARTY_MAP (ARTIFICIAL - for comparison)") print("=" * 80) run_scenario( "empty_party_map_ARTIFICIAL", load_positions_ret=None, # Use real positions load_party_map_ret={}, ) # Scenario 4: Both empty (ARTIFICIAL - for comparison) print("\n" + "=" * 80) print("SCENARIO 4: BOTH_EMPTY (ARTIFICIAL - for comparison)") print("=" * 80) run_scenario( "both_empty_ARTIFICIAL", load_positions_ret=({}, {}), load_party_map_ret={} ) # Write results to diagnostics file output_dir = os.path.join(root, "thoughts", "shared", "diagnostics") os.makedirs(output_dir, exist_ok=True) output_path = os.path.join(output_dir, "2026-03-31-trajectories-diagnostics.json") with open(output_path, "w") as f: json.dump(all_results, f, indent=2, default=str) print("\n" + "=" * 80) print(f"DIAGNOSTICS SAVED TO: {output_path}") print("=" * 80) # Summary print("\n" + "=" * 80) print("SUMMARY") print("=" * 80) normal_scenario = all_results["scenarios"].get("normal", {}) real_data = normal_scenario.get("real_data", {}) party_map_count = real_data.get("party_map_count", 0) print(f"DB Path: {DB_PATH}") print(f"DB Exists: {all_results['db_exists']}") print(f"Real data party_map_count: {party_map_count}") if party_map_count > 0: print("\n✅ SUCCESS: Real data scenario shows party_map_count > 0") print(f" Found {party_map_count} party entries (expected ~1000+)") else: print("\n❌ ISSUE: Real data scenario shows party_map_count = 0") # Check diagnostics from normal scenario normal_diag = normal_scenario.get("diagnostics", {}) if normal_diag: print(f"\nNormal scenario diagnostics keys: {list(normal_diag.keys())}") print(f"Normal scenario stage: {normal_diag.get('stage', 'N/A')}") return all_results if __name__ == "__main__": results = run()