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.
 
 
motief/docs/solutions/logic-errors/svd-theme-divergence-from-p...

4.4 KiB

title module date problem_type component severity tags
SVD theme divergence from actual party positions analysis 2026-04-05 logic_error analysis medium [svd themes party-positions validation data-drift]

SVD Theme Divergence from Actual Party Positions

Problem

SVD axis themes in analysis/config.py can drift from actual party positions in svd_vectors. Themes are derived from subagent summaries of top motions, but party positions reflect voting on ALL motions. When the SVD is recomputed or voting patterns shift, themes may no longer match the data.

Symptoms

  • Axis 4 theme said "Mainstreampartijen versus FVD/DENK-oppositie" but actual party positions showed NSC (-24.47) and BBB (-4.58) on the left extreme, D66 (10.53)/CDA (10.11)/JA21 (9.90) on the right extreme, and FVD/DENK in the middle
  • The flip mechanism (compute_flip_direction) worked correctly, but theme text was stale
  • NOTE (2026-04-12): The left_pole/right_pole static fields added here caused the same bug — when runtime flip differed from static config flip, labels pointed to wrong sides. These fields were removed. See docs/solutions/ui-bugs/svd-axis-pole-labels-incorrect-after-flip.md for the corrected approach.

Root Cause

Themes were written manually by subagents summarizing top 20 motions per component. This captures what motions drive each axis, but party positions come from how parties voted on ALL 8,732 motions. The divergence occurs because:

  1. Motion sponsors ≠ voting patterns (a motion sponsored by FVD/DENK may be voted on differently by all parties)
  2. The "long tail" of motions also loads on each component and can shift party positions
  3. No automated validation existed to detect when themes drift from actual data

Solution

1. Fixed axis 4 theme to match actual data

Updated analysis/config.py component 4:

# Before (wrong):
"label": "Mainstreampartijen versus FVD/DENK-oppositie",
"left_pole": "FVD en DENK: oppositieposities buiten de mainstream",
"right_pole": "Mainstreampartijen: D66, CDA, VVD, PVV, GL-PvdA, SP, Volt, 50PLUS",

# After (matches actual positions):
"label": "NSC/BBB versus D66/CDA/JA21 (indicatief)",
"left_pole": "NSC, BBB — moties met andere focus",
"right_pole": "D66, CDA, JA21 — moties met brede steun",

2. Added semantic left_pole/right_pole labels — SUPERSEDED (2026-04-12)

This approach caused the same bug. The static left_pole/right_pole fields assumed a fixed flip direction, but compute_flip_direction determines flip at runtime. When runtime flip differed from static config, labels pointed to wrong sides. These fields were removed. See docs/solutions/ui-bugs/svd-axis-pole-labels-incorrect-after-flip.md for the corrected approach.

3. Created validation hook

Created scripts/validate_svd_themes.py that validates:

  • Canonical right-wing parties (PVV, FVD, JA21, SGP) appear on the right side after flip
  • Theme pole labels match actual party positions
  • Uses full vectors for flip computation (not single-component scores)

Usage:

uv run python scripts/validate_svd_themes.py --db data/motions.db

Returns exit code 1 if any divergence found — suitable for CI integration.

Why This Works

The flip mechanism (compute_flip_direction) correctly positions canonical right parties on the right side by comparing mean scores. The validation hook uses the same function with full average vectors to verify post-flip positions. Theme pole labels are now pre-computed semantic descriptions that match the flipped orientation, not raw SVD positive/negative poles.

Prevention

  • Run scripts/validate_svd_themes.py after any SVD recomputation
  • Add to CI pipeline: uv run python scripts/validate_svd_themes.py --db data/motions.db
  • When updating themes, verify against actual party positions from svd_vectors, not just motion sponsors
  • NEVER add static left_pole/right_pole fields — derive labels at render time from runtime flip (see corrected approach in svd-axis-pole-labels-incorrect-after-flip.md)
  • Run tests/test_svd_axis_alignment.py to validate alignment after SVD recomputation
  • analysis/config.py — SVD_THEMES (no left_pole/right_pole)
  • explorer.py — label derivation and component 3-10 scoring
  • analysis/svd_labels.py — compute_flip_direction() function
  • scripts/validate_svd_themes.py — validation hook
  • tests/test_svd_axis_alignment.py — alignment tests (added 2026-04-12)