|
|
|
@ -1,10 +1,13 @@ |
|
|
|
#!/usr/bin/env python3 |
|
|
|
#!/usr/bin/env python3 |
|
|
|
|
|
|
|
# HL REV:NEWFILE |
|
|
|
""" |
|
|
|
""" |
|
|
|
Automated probes for Trajectories tab diagnostics. |
|
|
|
Automated probes for Trajectories tab diagnostics. |
|
|
|
|
|
|
|
|
|
|
|
This script runs several simulated scenarios by monkeypatching explorer.load_positions, |
|
|
|
This script runs several simulated scenarios to diagnose the trajectories tab: |
|
|
|
explorer.load_party_map and explorer.select_trajectory_plot_data to reproduce common |
|
|
|
1. NORMAL: Uses real data from data/motions.db |
|
|
|
failure modes that lead to "no plot at all" and prints the module-level diagnostics. |
|
|
|
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 |
|
|
|
Run: python scripts/diagnose_trajectories_cli.py |
|
|
|
""" |
|
|
|
""" |
|
|
|
@ -13,6 +16,13 @@ import os |
|
|
|
import importlib |
|
|
|
import importlib |
|
|
|
import traceback |
|
|
|
import traceback |
|
|
|
import sys |
|
|
|
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(): |
|
|
|
def run(): |
|
|
|
@ -21,26 +31,76 @@ def run(): |
|
|
|
root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) |
|
|
|
root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) |
|
|
|
if root not in sys.path: |
|
|
|
if root not in sys.path: |
|
|
|
sys.path.insert(0, root) |
|
|
|
sys.path.insert(0, root) |
|
|
|
|
|
|
|
|
|
|
|
# Import explorer fresh so env var reads take effect |
|
|
|
# Import explorer fresh so env var reads take effect |
|
|
|
import explorer |
|
|
|
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( |
|
|
|
def run_scenario( |
|
|
|
name, |
|
|
|
name, |
|
|
|
load_positions_ret=None, |
|
|
|
load_positions_ret=None, |
|
|
|
load_party_map_ret=None, |
|
|
|
load_party_map_ret=None, |
|
|
|
select_helper_behavior=None, |
|
|
|
select_helper_behavior=None, |
|
|
|
|
|
|
|
use_real_data=False, |
|
|
|
): |
|
|
|
): |
|
|
|
print("\n" + "=" * 80) |
|
|
|
print("\n" + "=" * 80) |
|
|
|
print("SCENARIO:", name) |
|
|
|
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 |
|
|
|
# Backup originals |
|
|
|
orig_load_positions = getattr(explorer, "load_positions", None) |
|
|
|
orig_load_positions = getattr(explorer, "load_positions", None) |
|
|
|
orig_load_party_map = getattr(explorer, "load_party_map", None) |
|
|
|
orig_load_party_map = getattr(explorer, "load_party_map", None) |
|
|
|
orig_select_helper = getattr(explorer, "select_trajectory_plot_data", 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: |
|
|
|
if load_positions_ret is not None: |
|
|
|
explorer.load_positions = lambda db, ws: load_positions_ret |
|
|
|
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: |
|
|
|
if load_party_map_ret is not None: |
|
|
|
explorer.load_party_map = lambda db: load_party_map_ret |
|
|
|
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": |
|
|
|
if select_helper_behavior == "raise": |
|
|
|
|
|
|
|
|
|
|
|
@ -48,6 +108,9 @@ def run(): |
|
|
|
raise ValueError("simulated crash from select_trajectory_plot_data") |
|
|
|
raise ValueError("simulated crash from select_trajectory_plot_data") |
|
|
|
|
|
|
|
|
|
|
|
explorer.select_trajectory_plot_data = raising |
|
|
|
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": |
|
|
|
elif select_helper_behavior == "zero_traces": |
|
|
|
|
|
|
|
|
|
|
|
class DummyFig: |
|
|
|
class DummyFig: |
|
|
|
@ -58,61 +121,114 @@ def run(): |
|
|
|
return DummyFig(), 0, None |
|
|
|
return DummyFig(), 0, None |
|
|
|
|
|
|
|
|
|
|
|
explorer.select_trajectory_plot_data = zero |
|
|
|
explorer.select_trajectory_plot_data = zero |
|
|
|
|
|
|
|
scenario_result["monkeypatched"]["select_helper"] = "ZERO_TRACES" |
|
|
|
|
|
|
|
print(" Monkeypatched: select_trajectory_plot_data -> ZERO_TRACES") |
|
|
|
|
|
|
|
|
|
|
|
try: |
|
|
|
try: |
|
|
|
# Call the UI function; it's import-safe and uses a dummy st when streamlit is absent |
|
|
|
# Call the UI function; it's import-safe and uses a dummy st when streamlit is absent |
|
|
|
explorer.build_trajectories_tab(db_path="dummy", window_size=1) |
|
|
|
explorer.build_trajectories_tab(db_path=DB_PATH, window_size="annual") |
|
|
|
except Exception as e: |
|
|
|
except Exception as e: |
|
|
|
print("build_trajectories_tab RAISED:", type(e), e) |
|
|
|
print("build_trajectories_tab RAISED:", type(e), e) |
|
|
|
print(traceback.format_exc()) |
|
|
|
print(traceback.format_exc()) |
|
|
|
finally: |
|
|
|
scenario_result["exception"] = str(e) |
|
|
|
diag = getattr(explorer, "_last_trajectories_diagnostics", None) |
|
|
|
scenario_result["traceback"] = traceback.format_exc() |
|
|
|
print("module _last_trajectories_diagnostics:", diag) |
|
|
|
|
|
|
|
sh = None |
|
|
|
# Capture diagnostics |
|
|
|
if hasattr(explorer, "select_trajectory_plot_data"): |
|
|
|
diag = getattr(explorer, "_last_trajectories_diagnostics", None) |
|
|
|
sh = getattr( |
|
|
|
scenario_result["diagnostics"] = dict(diag) if diag else {} |
|
|
|
explorer.select_trajectory_plot_data, "_last_diagnostics", None |
|
|
|
print("module _last_trajectories_diagnostics:", diag) |
|
|
|
) |
|
|
|
|
|
|
|
print("select_helper _last_diagnostics:", sh) |
|
|
|
sh = None |
|
|
|
|
|
|
|
if hasattr(explorer, "select_trajectory_plot_data"): |
|
|
|
# restore |
|
|
|
sh = getattr( |
|
|
|
if orig_load_positions is not None: |
|
|
|
explorer.select_trajectory_plot_data, "_last_diagnostics", None |
|
|
|
explorer.load_positions = orig_load_positions |
|
|
|
) |
|
|
|
if orig_load_party_map is not None: |
|
|
|
scenario_result["select_helper_diagnostics"] = dict(sh) if sh else {} |
|
|
|
explorer.load_party_map = orig_load_party_map |
|
|
|
print("select_helper _last_diagnostics:", sh) |
|
|
|
if orig_select_helper is not None: |
|
|
|
|
|
|
|
explorer.select_trajectory_plot_data = orig_select_helper |
|
|
|
# restore originals |
|
|
|
|
|
|
|
if orig_load_positions is not None: |
|
|
|
# Scenario 1: load_positions returns empty |
|
|
|
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( |
|
|
|
run_scenario( |
|
|
|
"load_positions_empty", load_positions_ret=({}, None), load_party_map_ret={} |
|
|
|
"empty_positions_ARTIFICIAL", |
|
|
|
|
|
|
|
load_positions_ret=({}, {}), |
|
|
|
|
|
|
|
load_party_map_ret=None, # Use real party_map |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
# Scenario 2: positions present but MP coords malformed -> mp_positions empty |
|
|
|
# Scenario 3: Empty party_map (ARTIFICIAL - for comparison) |
|
|
|
positions_malformed = {"W1": {"mp1": ("bad", "bad")}} |
|
|
|
print("\n" + "=" * 80) |
|
|
|
|
|
|
|
print("SCENARIO 3: EMPTY_PARTY_MAP (ARTIFICIAL - for comparison)") |
|
|
|
|
|
|
|
print("=" * 80) |
|
|
|
run_scenario( |
|
|
|
run_scenario( |
|
|
|
"mp_positions_malformed", |
|
|
|
"empty_party_map_ARTIFICIAL", |
|
|
|
load_positions_ret=(positions_malformed, {}), |
|
|
|
load_positions_ret=None, # Use real positions |
|
|
|
load_party_map_ret={}, |
|
|
|
load_party_map_ret={}, |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
# Scenario 3: select_trajectory_plot_data raises an exception |
|
|
|
# Scenario 4: Both empty (ARTIFICIAL - for comparison) |
|
|
|
positions_valid = {"W1": {"mp1": (0.1, 0.2)}} |
|
|
|
print("\n" + "=" * 80) |
|
|
|
|
|
|
|
print("SCENARIO 4: BOTH_EMPTY (ARTIFICIAL - for comparison)") |
|
|
|
|
|
|
|
print("=" * 80) |
|
|
|
run_scenario( |
|
|
|
run_scenario( |
|
|
|
"select_helper_raise", |
|
|
|
"both_empty_ARTIFICIAL", load_positions_ret=({}, {}), load_party_map_ret={} |
|
|
|
load_positions_ret=(positions_valid, {}), |
|
|
|
|
|
|
|
load_party_map_ret={}, |
|
|
|
|
|
|
|
select_helper_behavior="raise", |
|
|
|
|
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
# Scenario 4: helper returns a fig with zero traces |
|
|
|
# Write results to diagnostics file |
|
|
|
run_scenario( |
|
|
|
output_dir = os.path.join(root, "thoughts", "shared", "diagnostics") |
|
|
|
"helper_zero_traces", |
|
|
|
os.makedirs(output_dir, exist_ok=True) |
|
|
|
load_positions_ret=(positions_valid, {}), |
|
|
|
output_path = os.path.join(output_dir, "2026-03-31-trajectories-diagnostics.json") |
|
|
|
load_party_map_ret={}, |
|
|
|
|
|
|
|
select_helper_behavior="zero_traces", |
|
|
|
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__": |
|
|
|
if __name__ == "__main__": |
|
|
|
run() |
|
|
|
results = run() |
|
|
|
|