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.
5.3 KiB
5.3 KiB
| date | topic |
|---|---|
| 2026-04-05 | right-wing-party-axis-validation |
Right-Wing Party Axis Validation
Problem Frame
The project convention states that PVV, FVD, JA21, and SGP must appear on the RIGHT side of all axes in visualizations (AGENTS.md). This is the #1 documented convention with zero automated enforcement. A single test prevents regression when SVD labels change or new components are added.
Requirements
R1. Canonical party sets defined once, imported everywhere
- Define
CANONICAL_RIGHT = frozenset({"PVV", "FVD", "JA21", "SGP"})inanalysis/config.py - Define
CANONICAL_LEFT = frozenset({"SP", "PvdA", "GL", "GroenLinks", "GroenLinks-PvdA", "DENK", "PvdD", "Volt"})inanalysis/config.py— matches svd_labels.py LEFT_PARTIES exactly - All code that checks political orientation (svd_labels.py, political_axis.py) imports from config instead of defining inline
R2. Validation test loads real data from DuckDB
- Test file:
tests/test_axis_political_orientation.py - Uses existing data loading functions (
load_party_scores_all_windows_alignedfromanalysis/explorer_data.py) - No synthetic data — validates against actual
party_axis_scorestable
R3. 2D political compass orientation check (statistical, not per-party)
party_axis_scorestable hasx_axis_aligned(component 1) andy_axis_aligned(component 2)- For each window, validate both axes using mean scores:
- Axis 1 (x): Compute mean of
CANONICAL_RIGHTx-values and mean ofCANONICAL_LEFTx-values. Assertright_mean > left_mean - Axis 2 (y): Same for y-values. Assert
right_mean > left_mean
- Axis 1 (x): Compute mean of
- "Right on right" means the average right party is right of the average left party — individual parties may deviate slightly (e.g., one right party slightly negative is fine)
compute_flip_directionalready implements this logic (compares group means) — use it- Skips parties not present in a given window (graceful, not a failure)
R4. compute_flip_direction consistency check
- After loading data, call
compute_flip_direction(1, party_scores)andcompute_flip_direction(2, party_scores)per window - Assert both return
False(no flip needed) when data is already correctly oriented - If either returns
True, the data violates the convention and the test fails with a clear message
R5. Clear failure messages
- When orientation check fails, report: window, axis (x/y), right_mean, left_mean, difference
- Example:
"Window '2021-2023', x-axis: right_mean=-0.12, left_mean=0.08 (right parties on LEFT side — flip direction=True)"
Success Criteria
- Test runs as part of
pytestsuite (.venv/bin/python -m pytest tests/test_axis_political_orientation.py) - Test passes with current data (convention currently holds — this establishes the baseline)
- If convention is violated in future data, test fails with actionable message
- Test works for all windows in the database (not just current)
- Statistical check (mean-based) — test passes even if individual parties deviate slightly from group mean
Scope Boundaries
- Not included: Testing unaligned scores (only aligned scores are validated — these are what users see)
- Not included: VVD, NSC, BBB, CDA, ChristenUnie — these are center parties, not right-wing per AGENTS.md convention
- Not included: Per-party strict sign checks (statistical mean check is sufficient and more robust)
- Not included: Updating
political_axis.py— R1 only updatessvd_labels.pyto import from config;political_axis.pyuses different party sets for PCA centroid orientation and is out of scope
Key Decisions
- Canonical sets match AGENTS.md for right, svd_labels.py for left:
CANONICAL_RIGHT = {PVV, FVD, JA21, SGP}matches AGENTS.md exactly.CANONICAL_LEFT = {SP, PvdA, GL, GroenLinks, GroenLinks-PvdA, DENK, PvdD, Volt}matches svd_labels.py LEFT_PARTIES exactly. - Single unified source of truth in config.py:
CANONICAL_RIGHTandCANONICAL_LEFTfrozensets go inconfig.py— it's a prerequisite for the test to work correctly. Onlysvd_labels.pyis updated to import from config;political_axis.pyis out of scope (uses party sets for PCA centroid orientation, not the same usage). - Aligned scores only: Unaligned scores may vary across windows due to Procrustes alignment drift; aligned scores are the stable, user-facing representation.
- Statistical (mean-based) validation, not per-party: The orientation check compares group means, not individual party scores. A single right party being slightly negative is not a failure — the mean right score must exceed the mean left score.
Dependencies / Assumptions
- DuckDB database is populated with
party_axis_scorestable withx_axis_alignedandy_axis_alignedcolumns (verified) analysis/explorer_data.pyfunctions work correctly (already tested)_PARTY_NORMALIZEalready exists inconfig.py(lines 247-256) — use it for party name alias normalizationconfig.pycurrently lacksCANONICAL_RIGHT/CANONICAL_LEFTfrozensets — these must be added as part of R1compute_flip_direction()insvd_labels.pycurrently uses inlineRIGHT_PARTIES/LEFT_PARTIES— must be updated to import from config after R1
Outstanding Questions
All resolved. Key decisions documented above.
Next Steps
→ /ce:plan for structured implementation planning