From 0a39fa0fe3df80115aba42cfe05555ebae1c12d8 Mon Sep 17 00:00:00 2001 From: Sven Geboers Date: Sun, 5 Apr 2026 01:49:26 +0200 Subject: [PATCH] docs: add SVD axis labels design spec --- .../2026-04-05-svd-axis-labels-design.md | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-05-svd-axis-labels-design.md diff --git a/docs/superpowers/specs/2026-04-05-svd-axis-labels-design.md b/docs/superpowers/specs/2026-04-05-svd-axis-labels-design.md new file mode 100644 index 0000000..90c97e9 --- /dev/null +++ b/docs/superpowers/specs/2026-04-05-svd-axis-labels-design.md @@ -0,0 +1,103 @@ +# SVD Axis Labels: Enforce Canonical Left/Right Orientation + +## Problem + +SVD axis labels do not consistently reflect the repository's political convention: right-wing parties (PVV, FVD, JA21, SGP) must appear on the RIGHT side of all axes in visualizations. Current code uses inconsistent inline party sets across files (`svd_labels.py` has 9 right parties, `political_axis.py` has 6), and no automated validation exists to enforce the convention. + +## Goals + +- Guarantee that canonical right-wing parties (PVV, FVD, JA21, SGP) appear on the RIGHT side of all SVD axes. +- Centralize canonical party sets in a single source of truth. +- Automatically flip axis orientation when canonical parties appear on the wrong side. +- Add unit tests and CI validation to prevent regressions. +- Maintain backward compatibility for existing callers. + +## Architecture + +### New Module: `analysis/config.py` + +```python +"""Canonical political party sets for axis orientation validation.""" + +CANONICAL_RIGHT = frozenset({"PVV", "FVD", "JA21", "SGP"}) +CANONICAL_LEFT = frozenset({ + "SP", "PvdA", "GL", "GroenLinks", "GroenLinks-PvdA", + "DENK", "PvdD", "Volt" +}) +``` + +### Updated Module: `analysis/svd_labels.py` + +- Import canonical sets from `analysis.config`. +- Replace inline `RIGHT_PARTIES` / `LEFT_PARTIES` with config values. +- Export backward-compat aliases: + ```python + from analysis.config import CANONICAL_RIGHT, CANONICAL_LEFT + RIGHT_PARTIES = CANONICAL_RIGHT + LEFT_PARTIES = CANONICAL_LEFT + ``` +- Update `compute_flip_direction()` to use `CANONICAL_RIGHT` for flip decisions. + +### Data Flow + +1. Compute raw component scores/centroids (existing logic). +2. Project canonical right party vectors onto the component. +3. Determine where canonical right parties lie along the component axis. +4. If majority of `CANONICAL_RIGHT` items are on the left side, set `flip = True`. +5. When `flip` is True, swap/negate the axis and flip label semantics before returning to callers. +6. Numerical PCs remain intact; only orientation + labels change at presentation boundary. + +### Error Handling & Fallbacks + +- **No canonical parties present**: Log warning, fall back to existing behavior (no flip), optionally surface UI notice. +- **Ambiguous placement**: Use majority rule with deterministic tie-breaking (tie → do not flip, log warning). +- **Backward compatibility**: Preserve public functions (`get_svd_label`, `get_fallback_labels`, `compute_flip_direction`); only orientation may flip. + +## Testing Strategy + +### Unit Tests (`tests/test_svd_labels.py`) + +- **Synthetic flip test**: Create synthetic vectors where canonical right parties have negative component values; assert `compute_flip_direction` returns `True` and labels map correctly. +- **Synthetic no-flip test**: Canonical right parties on positive side; assert `compute_flip_direction` returns `False`. +- **Real window test**: Load a representative window and assert every item from `CANONICAL_RIGHT` appears on the right side after flip logic. +- **Backward compat test**: Existing tests in `test_svd_labels.py` continue to pass after import swap. + +### CI Validation + +- Add pytest test group enforcing the rule: + ```bash + pytest -q tests/test_svd_labels.py::test_canonical_right_on_right + ``` +- Test loads at least one representative window and asserts PVV/FVD/JA21/SGP appear on the right side of the principal political axis. +- Prevents regressions in PR pipeline. + +## Migration Plan + +1. Create `analysis/config.py` with canonical sets. +2. Update `analysis/svd_labels.py` imports and export aliases. +3. Update `compute_flip_direction` to use canonical sets. +4. Add/update unit tests in `tests/test_svd_labels.py`. +5. Run full test suite to verify backward compatibility. +6. Commit changes locally (no push until user approves). + +### Out of Scope (Follow-up) + +- `analysis/political_axis.py` currently uses different party sets for PCA centroid orientation. Recommended follow-up: align with `config.py` in separate PR. +- UI notice for missing canonical parties (non-blocking enhancement). + +## Success Criteria + +- `CANONICAL_RIGHT` and `CANONICAL_LEFT` defined in `analysis/config.py`. +- `svd_labels.py` imports from config; exports aliases for backward compat. +- All existing tests pass after changes. +- New test `test_canonical_right_on_right` passes. +- Running `pytest tests/test_svd_labels.py -q` shows no regressions. +- Right-wing parties (PVV, FVD, JA21, SGP) consistently appear on the RIGHT side of all axes in visualizations. + +## Related Files + +- `analysis/config.py` (new) +- `analysis/svd_labels.py` (update) +- `tests/test_svd_labels.py` (update) +- `docs/solutions/best-practices/svd-labels-voting-patterns-not-semantics.md` (existing convention doc) +- `AGENTS.md` (convention reference)