--- title: "Enforce left-right orientation across all SVD axis labels" type: refactor status: active date: 2026-04-05 origin: docs/superpowers/specs/2026-04-05-svd-axis-labels-design.md --- # Enforce Left-Right Orientation Across All SVD Axis Labels ## Overview Update SVD component labels in `analysis/config.py` so all 10 axes consistently reflect left-right positioning, and add validation tests to ensure canonical right-wing parties (PVV, FVD, JA21, SGP) appear on the right side after flip computation. The flip mechanism already works; this plan focuses on label consistency and test coverage. ## Problem Frame SVD axis labels do not consistently reflect left-right positioning. Some axes describe dimensions like "populist vs mainstream" or "pragmatism vs ideology" without framing how right/conservative and left/progressive parties cluster on each pole. The repo convention (AGENTS.md) requires right-wing parties to appear on the RIGHT side of all axes, and labels should reflect this orientation. ## Requirements Trace - R1. All 10 SVD component labels consistently frame the dimension in left-right terms - R2. Canonical right-wing parties (PVV, FVD, JA21, SGP) appear on the right side after flip computation - R3. Backward compatibility preserved for existing callers of `get_svd_label`, `get_fallback_labels`, `compute_flip_direction` - R4. Unit tests validate flip behavior and label correctness ## Scope Boundaries - In scope: `analysis/config.py` SVD_THEMES labels, `tests/test_svd_labels.py` additions - Out of scope: `analysis/political_axis.py` party sets (follow-up), UI changes, flip logic changes (already works) ## Context & Research ### Relevant Code and Patterns - `analysis/config.py` — defines `SVD_THEMES` with 10 components, each with `label`, `explanation`, `positive_pole`, `negative_pole`, `flip` - `analysis/svd_labels.py` — imports `CANONICAL_RIGHT`/`CANONICAL_LEFT` from config, exports aliases, `compute_flip_direction` uses them - `explorer.py:2680-2690` — dynamically computes flip for all 10 components at runtime, overwrites static `flip` values ### Key Technical Decisions - **Keep flip mechanism as-is**: `compute_flip_direction` already uses canonical party sets to force right-wing parties to the right. No changes needed. - **Update labels, not flip logic**: The work is in `SVD_THEMES` label text — reframing each component's label to reflect left-right positioning while preserving the underlying voting pattern description. - **Preserve explanation text**: The `explanation` field can remain detailed and nuanced; only the `label` and pole descriptions need left-right framing. ## Implementation Units - [ ] **Unit 1: Update SVD_THEMES labels for left-right consistency** **Goal:** Reframe all 10 SVD component labels to consistently reflect left-right positioning. **Requirements:** R1, R3 **Dependencies:** None **Files:** - Modify: `analysis/config.py` **Approach:** - For each component (1-10), update the `label` field to frame the dimension in left-right terms - Update `positive_pole` and `negative_pole` to explicitly mention which parties cluster on each side and their left/right positioning - Preserve the `explanation` text (it's already detailed and accurate) - Keep `flip` values as-is (they're overwritten at runtime anyway) **Patterns to follow:** - Component 1 label pattern: "Rechts kabinetsbeleid versus links oppositiebeleid" — this is the model - Component 3 label pattern: "Verzorgingsstaat versus bezuinigingen en marktwerking" — economic left-right - Component 6 label pattern: "Migratie en cultuur versus klimaat en progressieve inclusie" — cultural left-right (GAL-TAN) **Test scenarios:** - Test expectation: none — this is a label text update, no behavioral change. Verification is manual review of label text. **Verification:** - All 10 component labels explicitly reference left/right positioning or conservative/progressive framing - `positive_pole` and `negative_pole` descriptions mention party clusters and their left/right orientation - [ ] **Unit 2: Add validation test for canonical right-on-right** **Goal:** Add a test that verifies canonical right-wing parties appear on the right side after flip computation. **Requirements:** R2, R4 **Dependencies:** Unit 1 (labels updated, flip logic unchanged) **Files:** - Modify: `tests/test_svd_labels.py` **Approach:** - Add `test_canonical_right_on_right` that: 1. Creates synthetic party scores where canonical right parties have negative values (on the left) 2. Asserts `compute_flip_direction` returns `True` for all components 1-10 3. Creates synthetic scores where canonical right parties have positive values (on the right) 4. Asserts `compute_flip_direction` returns `False` for all components - Add `test_all_canonical_parties_used` that verifies `CANONICAL_RIGHT` and `CANONICAL_LEFT` from config contain the expected parties (PVV, FVD, JA21, SGP for right; SP, PvdA, GL, etc. for left) **Execution note:** Test-first — write failing test, then verify it passes after Unit 1. **Patterns to follow:** - Existing test style in `tests/test_svd_labels.py` (synthetic dict-based party scores, assert on boolean flip result) - `test_auto_flip_computation_for_all_components` already tests flip for all 10 components — new test should follow same pattern but explicitly use `CANONICAL_RIGHT`/`CANONICAL_LEFT` from config **Test scenarios:** - Happy path: Canonical right parties on right side → `compute_flip_direction` returns `False` for all components - Happy path: Canonical right parties on left side → `compute_flip_direction` returns `True` for all components - Edge case: Mixed placement (some right parties on left, some on right) → flip based on majority mean - Edge case: No canonical parties present → returns `False` (existing behavior, verify unchanged) **Verification:** - `pytest tests/test_svd_labels.py -q` passes with no regressions - New tests explicitly validate canonical right-on-right behavior ## System-Wide Impact - **Interaction graph:** `explorer.py` dynamically computes flip at runtime — no changes needed there. Labels flow from `config.py` → `svd_labels.py` → UI rendering. - **Unchanged invariants:** `compute_flip_direction` logic unchanged. Public API (`get_svd_label`, `get_fallback_labels`, `compute_flip_direction`) unchanged. Static `flip` values in `SVD_THEMES` still overwritten at runtime. - **API surface parity:** Labels change text but not structure. Callers expecting string labels continue to work. ## Risks & Dependencies | Risk | Mitigation | |------|------------| | Label changes may not capture nuance of non-left-right axes | Preserve detailed `explanation` text; labels are shorthand, explanations carry full context | | Tests may pass but labels still feel off | Manual review of all 10 labels before committing | | `political_axis.py` still uses different party sets | Document as follow-up; out of scope for this plan | ## Documentation / Operational Notes - Update or reference `docs/solutions/best-practices/svd-labels-voting-patterns-not-semantics.md` if label convention changes materially - No rollout or monitoring impacts — label text change only ## Sources & References - **Origin document:** [docs/superpowers/specs/2026-04-05-svd-axis-labels-design.md](docs/superpowers/specs/2026-04-05-svd-axis-labels-design.md) - Related code: `analysis/config.py`, `analysis/svd_labels.py`, `tests/test_svd_labels.py` - Convention reference: `AGENTS.md` (right-wing parties must appear on RIGHT side)