fix: SVD tab now uses raw SVD values for ALL components 1-10

Previously, components 1-2 in the SVD tab used Procrustes-aligned PCA
coordinates (from load_positions), which meant the SVD tab showed PCA
dimensions of the 50D aligned space rather than the actual raw SVD
components. This was a fundamental inconsistency — the SVD tab's component 2
showed completely different party ordering than the raw SVD component 2.

Changes:
- explorer.py: Unified all components 1-10 to use raw SVD values via
  load_party_axis_scores_for_window(). Removed the separate
  load_positions() path for components 1-2. Now all components use the
  same data source (50D vectors from svd_vectors table).
- explorer.py: Updated flip computation to cover ALL components 1-10
  (was range 3-11 for components 3-10 only). The compute_flip_direction
  function correctly determines sign for each component.
- explorer.py: Unified rendering to always use _render_party_axis_chart_1d
  (was _render_party_axis_chart for components 1-2 using 2D coords).
- explorer.py: Unified trajectory to always use load_party_scores_all_windows.
- analysis/config.py: Updated component 1 label (simplified explanation,
  removed coalition-specific policy references).
- analysis/config.py: Updated component 2 label to "Nationalistisch versus
  kosmopolitisch" matching raw SVD data (PVV/FVD at positive extreme,
  Volt/DENK/GL-PvdA at negative extreme).
- tests: Updated test assertions to match new labels.
- scripts/validate_svd_themes.py: Verified all components pass right-wing
  alignment check, config flip consistency, and theme pole consistency.

Fixes the core inconsistency: SVD tab component 2 now uses the same raw
SVD data as components 3-10, with consistent party ordering and labels.
The compass remains a separate PCA-based visualization.
This commit is contained in:
2026-04-13 21:51:21 +02:00
parent 3d69375c01
commit 467b0d1be1
7 changed files with 177 additions and 235 deletions
+18 -24
View File
@@ -66,35 +66,29 @@ PARTY_COLOURS: Dict[str, str] = {
SVD_THEMES: dict[int, dict[str, str]] = {
1: {
"label": "Rechts kabinetsbeleid versus links oppositiebeleid",
"label": "Rechts versus links (economisch en migratiebeleid)",
"explanation": (
"Deze as scheidt het rechts kabinetsbeleid van links oppositiebeleid. "
"Aan de positieve kant staan moties die passen bij het kabinetsbeleid: "
"Eurofighter Typhoons, defensie-uitgaven naar 3% bbp, F-35 reservedelen, "
"marine-steun aan Rode Zee en asielrestricties. "
"PVV, VVD, NSC en BBB scoren sterk positief. "
"Aan de negatieve kant staan moties uit de oppositie: "
"zorgbuurthuizen voor ouderen, boycot van Israël, sancties, en internationale "
"klimaatsamenwerking. GroenLinks-PvdA, SP, PvdD en Volt scoren negatief. "
"Deze as weerspiegelt de coalitie-oppositie dynamiek."
"Deze as scheidt rechts kabinetsbeleid van links oppositiebeleid. "
"Aan de positieve kant staan PVV, NSC, SGP en BBB. "
"Aan de negatieve kant staan PvdD, GroenLinks-PvdA en DENK. "
"Deze as weerspiegelt de klassieke links-rechts verdeling op economisch en migratiebeleid."
),
"positive_pole": "Rechts: PVV, VVD, NSC, BBB, JA21 — kabinetsbeleid, defensie en restricties",
"negative_pole": "Links: GroenLinks-PvdA, SP, PvdD, Volt, DENK — oppositie, zorg en multilateraal",
"positive_pole": "Rechts: PVV, NSC, SGP, BBB — kabinetsbeleid, defensie en restricties",
"negative_pole": "Links: PvdD, GroenLinks-PvdA, DENK — oppositie, zorg en multilateraal",
"flip": False,
},
2: {
"label": "PVV/FVD-populisme versus mainstream-partijen",
"label": "Nationalistisch versus kosmopolitisch",
"explanation": (
"Deze as scheidt het PVV/FVD-populisme van het overige parliament. "
"Alleen PVV en FVD scoren positief; alle andere partijen scoren negatief. "
"Positieve moties: Syriërs terugsturen, geen geld aan Jordanië, tijdelijke "
"bescherming Oekraïne beëindigen, uitstappen uit WHO en klimaatakkoorden. "
"Negatieve moties: digitale toegankelijkheid Caribisch Nederland, ethiekprogramma "
"Defensie, zorg voor slachtoffers bombardement Hawija, internationale klimaatsamenwerking. "
"Dit is geen links-rechts verdeling maar een populistisch vs. mainstream onderscheid."
"Deze as meet een onafhankelijke culturele dimensie: nationalistisch-populistisch "
"tegenover kosmopolitisch-mainstream. Aan de positieve kant staan PVV en FVD. "
"Aan de negatieve kant staan Volt, GroenLinks-PvdA, DENK en SP. "
"Deze as is onafhankelijk van links-rechts (as 1) en scheidt partijen "
"op hun houding tegenover nationale identiteit, EU-samenwerking en de "
"etnisch-culturele dimensie."
),
"positive_pole": "PVV en FVD — soevereiniteit en anti-establishment",
"negative_pole": "Overige partijen: VVD, CDA, SGP, ChristenUnie, GroenLinks-PvdA, D66, Volt, BBB",
"positive_pole": "Nationalistisch/populistisch — PVV, FVD: nationale identiteit en soevereiniteit",
"negative_pole": "Kosmopolitisch/mainstream — Volt, GL-PvdA, DENK, SP: EU en internationale samenwerking",
"flip": False,
},
3: {
@@ -126,7 +120,7 @@ SVD_THEMES: dict[int, dict[str, str]] = {
),
"positive_pole": "D66, CDA, JA21 — moties met brede steun",
"negative_pole": "NSC, BBB — moties met andere focus",
"flip": True,
"flip": False,
},
5: {
"label": "Christelijk-sociaal en gemeenschapswaarden versus progressieve individuele rechten",
@@ -211,7 +205,7 @@ SVD_THEMES: dict[int, dict[str, str]] = {
),
"positive_pole": "Pragmatisch-bestuurlijk: SGP, ChristenUnie, DENK, SP — concrete oplossingen",
"negative_pole": "Systeemhervorming: D66, JA21, PVV — idealistische beleidsposities",
"flip": True,
"flip": False,
},
10: {
"label": "Kritisch op overheidsbemoeienis versus pro-regulering (indicatief)",