You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
117 lines
5.9 KiB
117 lines
5.9 KiB
---
|
|
date: 2026-03-30
|
|
topic: "compass-trajectory-consistency"
|
|
status: validated
|
|
---
|
|
|
|
## Problem Statement
|
|
|
|
What we're solving and why
|
|
|
|
We must ensure the political compass (single-window snapshot) and the Explorer trajectories use the same numeric coordinate frame for the first two SVD axes so the compass numbers match the trajectory centroids exactly.
|
|
|
|
**Key issue:** Component 1 already matched, but component 2 shows persistent mismatches due to an API/shape ambiguity and occasional fallback logic differences. Fixing this prevents confusing, inconsistent numbers in the UI.
|
|
|
|
|
|
## Constraints
|
|
|
|
Non-negotiables and limitations
|
|
|
|
- The canonical coordinate frame is the Procrustes-aligned output of **compute_2d_axes** (the repo artifact that produces **positions_by_window**).
|
|
- Keep UI responsiveness and existing cache usage (@st.cache_data where present).
|
|
- Minimal, focused changes: only update Explorer call sites and the compass renderer API. Do not change the SVD pipeline outputs.
|
|
- Use the **first chronological party vector** as the fallback when a party has no MPs in a window (user decision).
|
|
|
|
|
|
## Approach
|
|
|
|
Chosen approach and why
|
|
|
|
We will adopt an explicit API for the compass renderer: pass per-party 2D projected coordinates (party → (x,y)) computed from **positions_by_window** for the target window. This eliminates shape/indexing ambiguity and guarantees numeric equality with trajectory centroids.
|
|
|
|
**Why:**
|
|
- Simpler and less error-prone than synthesizing k-dimensional vectors or changing compute_2d_axes.
|
|
- Keeps the canonical data source unchanged (positions_by_window) and makes intent explicit at the Explorer surface.
|
|
- Easy to test: we can assert numeric equality directly on the 2D coordinates.
|
|
|
|
|
|
## Architecture
|
|
|
|
High-level structure of the change
|
|
|
|
**Key pieces:**
|
|
- **compute_2d_axes** (unchanged): produces **positions_by_window** which is the canonical frame.
|
|
- **Explorer: party centroid helper:** new helper that computes per-party (x, y) means from positions_by_window for a window.
|
|
- **_build_party_axis_figure (changed API):** now accepts **party_coords: Dict[str, Tuple[float,float]]** and a selected component index (1 or 2) and uses the explicit coordinate values for plotting.
|
|
- **Call-site updates:** update all places that previously passed party SVD vectors to instead compute and pass party_coords (use first-chronological party vector only when no MPs are present for that party in the window).
|
|
|
|
|
|
## Components
|
|
|
|
Key pieces and responsibilities
|
|
|
|
- **compute_party_coords(positions_by_window, party_map, window_id):**
|
|
- Input: positions_by_window, party->MP mapping (load_party_map or similar), window id.
|
|
- Output: party -> (x_mean, y_mean). If no MPs for a party, returns None or uses fallback loader.
|
|
|
|
- **_build_party_axis_figure(party_coords, comp_sel, ...):**
|
|
- Input: explicit 2D coords; **comp_sel** ∈ {1,2}.
|
|
- Behavior: uses party_coords[p][comp_sel-1] as the axis value, constructs hover text, CIs, and plots. No indexing into long SVD vectors.
|
|
|
|
- **Fallback loader:** existing **load_party_axis_scores** (unchanged). When compute_party_coords finds no MPs, we will use the party's first chronological vector from load_party_axis_scores(window) as fallback and indicate fallback in hover text.
|
|
|
|
- **Callers to update:**
|
|
- build_svd_components_tab
|
|
- any other explorer function that previously passed party-axis vectors into _build_party_axis_figure
|
|
|
|
|
|
## Data Flow
|
|
|
|
How data moves through the updated code path
|
|
|
|
1. UI requests compass for window W and component C.
|
|
2. Explorer calls load_positions(db_path) → gets positions_by_window.
|
|
3. compute_party_coords builds per-party (x,y) means from positions_by_window[W].
|
|
4. For parties with zero MPs in W, call load_party_axis_scores(window) and take the **first chronological** party vector as fallback; annotate hover that a fallback is used.
|
|
5. Pass party_coords to _build_party_axis_figure which reads comp_sel and uses the explicit coordinate at index 0 or 1.
|
|
6. Explorer trajectories tab already computes the same centroids from positions_by_window; therefore numbers match exactly.
|
|
|
|
|
|
## Error Handling
|
|
|
|
Strategy for failures and edge cases
|
|
|
|
- If positions_by_window is missing or corrupted: surface a clear diagnostic message in the UI recommending running the SVD recompute pipeline, and avoid attempting to plot mismatched values.
|
|
- If a party has no MPs and load_party_axis_scores also returns no data: omit that party from the compass and add a tooltip note in the UI explaining why.
|
|
- If any coordinate is NaN/inf: skip plotting that party and log a debug message with the party id and window.
|
|
- Log a WARN when a fallback is used so we can find parties with no MPs across windows.
|
|
|
|
|
|
## Testing Strategy
|
|
|
|
How we will verify correctness
|
|
|
|
- Unit tests
|
|
- Synthetic positions_by_window: build a small fake positions_by_window with known MP coordinates and party→MP mappings. Assert compute_party_coords outputs expected means and that _build_party_axis_figure uses those exact numbers for components 1 and 2.
|
|
- Fallback behavior: create a window with a party that has no MPs and assert load_party_axis_scores is called and its first chronological vector is used.
|
|
|
|
- Integration tests
|
|
- Run against a small real DB snapshot used in prior verification. Assert for a representative set of parties across several windows that compass numbers equal the trajectory centroids for components 1 and 2.
|
|
|
|
- CI
|
|
- Run full test suite. Known pre-existing failures unrelated to this change may persist; document them separately but do not block this change on them.
|
|
|
|
- Manual QA
|
|
- Run Explorer locally and spot-check compass tooltips vs trajectory hover values for multiple parties and windows.
|
|
|
|
|
|
## Open Questions
|
|
|
|
Unresolved items (minor)
|
|
|
|
- None critical: the user selected the fallback preference (first chronological party vector) and agreed to update all callers without backward compatibility.
|
|
|
|
|
|
---
|
|
|
|
I'm proceeding to create the implementation plan. Interrupt if you want changes to this design.
|
|
|