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.
134 lines
7.4 KiB
134 lines
7.4 KiB
---
|
|
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)
|
|
|