parent
ea6e33fa3f
commit
0a39fa0fe3
@ -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) |
||||
Loading…
Reference in new issue