---
date: 2026-03-30
topic: "diagnose-no-plot-trajectories"
status: draft
---

## Problem Statement

The Trajectories tab currently shows **no Plotly chart at all** (not just an empty chart). We need a low-risk way to determine exactly which runtime gate or swallowed exception is preventing any plot from being rendered and fix it so the chart appears or we surface a clear error message.

**Key observation:** upstream code contains multiple early-returns (no data), and broad except/pass handlers that can silently swallow exceptions — either can cause the UI to skip calling st.plotly_chart entirely.

## Constraints

- Keep changes small and reversible.
- Do not change user-facing defaults unless gated by an explicit debug toggle or environment variable.
- Prefer adding diagnostics and logging over big refactors; short-term changes must be removable after diagnosis.
- Preserve public function locations and names used by other code/tests.

## Chosen approach (what I'll do)

I'm choosing a focused instrumentation strategy: add a temporary, opt-in **debug mode** that surfaces the exact runtime decisions and any exceptions taken along the Trajectories rendering path, and un-silence key broad excepts so we can observe stack traces.

**Why:** It's the fastest, lowest-risk way to get definitive evidence of why the plot doesn't render, and it avoids changing production logic except under an explicit debug toggle.

**High-level changes:**
- Add a **DEBUG toggle** (UI checkbox + env var EXPLORER_DEBUG_TRAJECTORIES) that enables verbose diagnostics in the Trajectories UI.
- When debug is enabled, show step-by-step status for each early-return gate: result of load_positions, axis_def presence, length of positions_by_window, centroids size, mp_positions size, helper returns (fig/trace_count) and any exception tracebacks.
- Replace the helper-call swallow (`except Exception: pass`) around select_trajectory_plot_data with a handler that logs and displays the exception (only when debug is enabled) and increments a visible diagnostic counter.
- Add compact, structured diagnostics to the existing DEBUG expander (windows_count, party_map_count, centroids_sample, mp_positions_sample, helper_trace_count, helper_exception_string).

## Alternatives considered (brief)

1. Force-show MP fallback unconditionally. Pros: quickly confirm plotting plumbing works. Cons: noisy, may mask root cause and changes production behaviour.
2. Heavy refactor to move pure plotting logic into an import-safe separate module and run offline tests. Pros: clean separation and easier tests. Cons: slower and higher-risk for this urgent diagnosis.

I rejected both for immediate work because they are heavier than necessary to learn the root cause.

## Architecture (where changes live)

- Explorer UI (explorer.py) — add debug checkbox and diagnostic panel wiring inside build_trajectories_tab.
- Diagnostics collector (small helper in explorer_helpers.py or local helper) — produce structured status dicts (counts, samples) used by the UI.
- Error surfacer — modify the select_trajectory_plot_data call-site to log exceptions (logger.exception) and, when debug enabled, call st.exception(...) or st.text_area(...) with the traceback.

## Components and responsibilities

- **Debug toggle UI:** checkbox + env var binding; enables/disables verbose diagnostics.
- **Diagnostic collector:** pure helper that inspects positions_by_window, party_map, centroids, mp_positions and returns compact samples and counts.
- **Exception handler change:** convert broad `except: pass` at the helper boundary into `except Exception as e: logger.exception(e); diagnostic['select_helper_exception']=traceback; if debug: st.exception(e)`.
- **Temporary UX:** display a compact, clearly labeled diagnostics block inside the DEBUG expander. Make it obvious this is a temporary troubleshooting aid.

## Data flow (quick)

- load_positions(db) -> positions_by_window, axis_def
- diagnostic collector inspects positions_by_window and party_map
- build_trajectories_tab calls select_trajectory_plot_data(...) inside a try/except
- on success: use returned fig and trace_count to decide whether to call st.plotly_chart
- on exception: diagnostic collector records traceback and UI shows it if debug enabled

## Error handling strategy

- Do not swallow exceptions silently at the helper boundary. Always log with logger.exception(...).
- Only surface full tracebacks to the Streamlit UI when **debug mode** is enabled.
- Keep production behaviour unchanged when debug mode is off.

## Testing approach

- Unit tests for the diagnostic collector with synthetic positions_by_window covering: empty data, partial centroids, and full centroids.
- Unit test that simulates the helper raising an exception (monkeypatch) and asserts that the exception is logged and (when debug enabled) that the diagnostics struct contains the exception string.
- Manual reproduction: run Streamlit locally with EXPLORER_DEBUG_TRAJECTORIES=1 and the same DB used in production to capture the diagnostics panel and fix the underlying issue.

## Open questions

- Can you reproduce the issue locally (same DB and same command to start Streamlit)? I assume yes and will base debug advice on that.
- Are we allowed to enable a short-lived debug toggle in production logs if needed, or will you only run this locally?

---

I'm proceeding to create the design doc. Interrupt if you want changes.
\n+## Environment management (use uv, not pip)

We will not use pip directly. Use the project's `uv` tool to manage dependencies and run scripts so the environment is reproducible and follows local project conventions.

Recommended commands:

- Add duckdb to the project virtual environment:
  - `uv add duckdb`
- Run the diagnostic CLI with debug enabled:
  - `EXPLORER_DEBUG_TRAJECTORIES=1 uv run python scripts/diagnose_trajectories_cli.py`
- Start Streamlit inside the uv-managed environment (example):
  - `uv run streamlit run pages/2_Explorer.py`

Notes:

- If the planner or any follow-up steps need to install or run packages, they should use `uv add` and `uv run` rather than `pip install` or direct interpreter calls.
- If `uv` is not on PATH in a particular environment, prefer `python -m uv` or consult the project README/ARCHITECTURE.md for local developer environment instructions.
