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...

3.8 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
  • Pole labels (left_pole/right_pole) described parties that weren't actually on those sides after flip
  • The flip mechanism (compute_flip_direction) worked correctly, but theme text was stale

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

Added left_pole and right_pole fields to all 10 SVD_THEMES entries. These describe what's on the left and right sides AFTER flip, decoupling label text from raw SVD math. Updated 4 rendering locations in explorer.py to use these semantic labels with backward compat fallback.

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
  • Consider automating theme generation from party positions + motion analysis
  • analysis/config.py — SVD_THEMES with left_pole/right_pole fields
  • explorer.py — rendering functions using semantic pole labels
  • analysis/svd_labels.py — compute_flip_direction() function
  • scripts/validate_svd_themes.py — validation hook