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.
103 lines
4.6 KiB
103 lines
4.6 KiB
# 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)
|
|
|