---
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)
