From bce0ed56de41551747a7e7edaba4137658c33730 Mon Sep 17 00:00:00 2001 From: Sven Geboers Date: Sun, 5 Apr 2026 02:11:19 +0200 Subject: [PATCH] fix: add semantic left_pole/right_pole labels to SVD axes --- analysis/config.py | 24 ++++++++++++++++++++++-- explorer.py | 24 ++++++++++++++++-------- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/analysis/config.py b/analysis/config.py index 2e9a2bf..363558d 100644 --- a/analysis/config.py +++ b/analysis/config.py @@ -78,8 +78,10 @@ SVD_THEMES: dict[int, dict[str, str]] = { "klimaatsamenwerking. GroenLinks-PvdA, SP, PvdD en Volt scoren negatief. " "Deze as weerspiegelt de coalitie-oppositie dynamiek." ), - "positive_pole": "Kabinetsbeleid: PVV, VVD, NSC, BBB, JA21 — defensie en restricties", - "negative_pole": "Oppositiebeleid: GroenLinks-PvdA, SP, PvdD, Volt, DENK — zorg en multilateraal", + "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", + "left_pole": "Links: GroenLinks-PvdA, SP, PvdD, Volt, DENK — oppositie, zorg en multilateraal", + "right_pole": "Rechts: PVV, VVD, NSC, BBB, JA21 — kabinetsbeleid, defensie en restricties", "flip": False, }, 2: { @@ -95,6 +97,8 @@ SVD_THEMES: dict[int, dict[str, str]] = { ), "positive_pole": "PVV en FVD — soevereiniteit en anti-establishment", "negative_pole": "Overige partijen: VVD, CDA, SGP, ChristenUnie, GroenLinks-PvdA, D66, Volt, BBB", + "left_pole": "Overige partijen: VVD, CDA, SGP, ChristenUnie, GroenLinks-PvdA, D66, Volt, BBB", + "right_pole": "PVV en FVD — soevereiniteit en anti-establishment", "flip": False, }, 3: { @@ -112,6 +116,8 @@ SVD_THEMES: dict[int, dict[str, str]] = { ), "positive_pole": "Pro-verzorgingsstaat: SP, PvdD, GroenLinks-PvdA (anti-bezuinigingen)", "negative_pole": "Marktliberaal en fiscaal conservatief: VVD, D66, CDA, SGP, BBB", + "left_pole": "Marktliberaal en fiscaal conservatief: VVD, D66, CDA, SGP, BBB", + "right_pole": "Pro-verzorgingsstaat: SP, PvdD, GroenLinks-PvdA (anti-bezuinigingen)", "flip": True, }, 4: { @@ -127,6 +133,8 @@ SVD_THEMES: dict[int, dict[str, str]] = { ), "positive_pole": "Mainstreampartijen: D66, CDA, VVD, PVV, GL-PvdA, SP, Volt, 50PLUS — breedgedragen moties", "negative_pole": "FVD en DENK: oppositieposities buiten de mainstream", + "left_pole": "FVD en DENK: oppositieposities buiten de mainstream", + "right_pole": "Mainstreampartijen: D66, CDA, VVD, PVV, GL-PvdA, SP, Volt, 50PLUS — breedgedragen moties", "flip": True, }, 5: { @@ -143,6 +151,8 @@ SVD_THEMES: dict[int, dict[str, str]] = { ), "positive_pole": "Christelijk-sociaal: ChristenUnie, SGP, CDA, NSC — gemeenschap en vrijwilligers", "negative_pole": "Progressief-individueel: SP, VVD, GL-PvdA, PvdD, Volt — individuele rechten", + "left_pole": "Progressief-individueel: SP, VVD, GL-PvdA, PvdD, Volt — individuele rechten", + "right_pole": "Christelijk-sociaal: ChristenUnie, SGP, CDA, NSC — gemeenschap en vrijwilligers", "flip": False, }, 6: { @@ -159,6 +169,8 @@ SVD_THEMES: dict[int, dict[str, str]] = { ), "positive_pole": "Restrictief migratiebeleid: PVV, JA21, BBB, CDA, ChristenUnie, VVD, SGP, FVD, DENK", "negative_pole": "Progressieve inclusie: SP, PvdD, D66, GL-PvdA, Volt — klimaat en diversiteit", + "left_pole": "Progressieve inclusie: SP, PvdD, D66, GL-PvdA, Volt — klimaat en diversiteit", + "right_pole": "Restrictief migratiebeleid: PVV, JA21, BBB, CDA, ChristenUnie, VVD, SGP, FVD, DENK", "flip": False, }, 7: { @@ -177,6 +189,8 @@ SVD_THEMES: dict[int, dict[str, str]] = { ), "positive_pole": "Praktisch-bestuurlijk: ChristenUnie, Volt, SGP, DENK, SP", "negative_pole": "Ideologisch-principieel: GroenLinks-PvdA, VVD, FVD, JA21", + "left_pole": "Ideologisch-principieel: GroenLinks-PvdA, VVD, FVD, JA21", + "right_pole": "Praktisch-bestuurlijk: ChristenUnie, Volt, SGP, DENK, SP", "flip": True, }, 8: { @@ -194,6 +208,8 @@ SVD_THEMES: dict[int, dict[str, str]] = { ), "positive_pole": "Onderwijs en volksgezondheid: BBB, SGP, JA21 — vaccinatie, profielkeuze, woningbouw", "negative_pole": "Zorg en toegankelijkheid: SP, DENK, PvdD, Volt — coronaonderzoek, energie, basispakket", + "left_pole": "Zorg en toegankelijkheid: SP, DENK, PvdD, Volt — coronaonderzoek, energie, basispakket", + "right_pole": "Onderwijs en volksgezondheid: BBB, SGP, JA21 — vaccinatie, profielkeuze, woningbouw", "flip": False, }, 9: { @@ -212,6 +228,8 @@ 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", + "left_pole": "Systeemhervorming: D66, JA21, PVV — idealistische beleidsposities", + "right_pole": "Pragmatisch-bestuurlijk: SGP, ChristenUnie, DENK, SP — concrete oplossingen", "flip": True, }, 10: { @@ -230,6 +248,8 @@ SVD_THEMES: dict[int, dict[str, str]] = { ), "positive_pole": "Kritisch op overheidsbemoeienis: DENK, SP, PvdD — minder inspectielast en lastenverlichting", "negative_pole": "Pro-regulering: GroenLinks-PvdA, CDA, SGP — veiligheid, naleving en handhaving", + "left_pole": "Pro-regulering: GroenLinks-PvdA, CDA, SGP — veiligheid, naleving en handhaving", + "right_pole": "Kritisch op overheidsbemoeienis: DENK, SP, PvdD — minder inspectielast en lastenverlichting", "flip": True, }, } diff --git a/explorer.py b/explorer.py index dd352b1..784dfb1 100644 --- a/explorer.py +++ b/explorer.py @@ -966,8 +966,8 @@ def _build_party_axis_figure( pos_pole = theme.get("positive_pole", "") neg_pole = theme.get("negative_pole", "") - left_label = pos_pole if flip else neg_pole - right_label = neg_pole if flip else pos_pole + left_label = theme.get("left_pole", pos_pole if flip else neg_pole) + right_label = theme.get("right_pole", neg_pole if flip else pos_pole) fig.update_layout( height=160, @@ -1086,8 +1086,8 @@ def _render_party_axis_chart_1d( # Determine pole labels based on flip pos_pole = theme.get("positive_pole", "") neg_pole = theme.get("negative_pole", "") - left_label = pos_pole if flip else neg_pole - right_label = neg_pole if flip else pos_pole + left_label = theme.get("left_pole", pos_pole if flip else neg_pole) + right_label = theme.get("right_pole", neg_pole if flip else pos_pole) # Update layout with same format as components 1-2 fig.update_layout( @@ -1253,8 +1253,8 @@ def _render_svd_time_trajectory( neg_pole = theme.get("negative_pole", "") # Use the theme's flip value (computed from current_parliament) for label orientation reference_flip = theme.get("flip", False) - left_label = pos_pole if reference_flip else neg_pole - right_label = neg_pole if reference_flip else pos_pole + left_label = theme.get("left_pole", pos_pole if reference_flip else neg_pole) + right_label = theme.get("right_pole", neg_pole if reference_flip else pos_pole) # Y-axis labels y_labels = {} @@ -2806,8 +2806,16 @@ def build_svd_components_tab(db_path: str) -> None: pos_pole = theme.get("positive_pole", "") if theme else "" neg_pole = theme.get("negative_pole", "") if theme else "" - # Determine which pole goes left (progressive) and which goes right - if flip: + # Use semantic left/right pole labels if available, otherwise compute from flip + semantic_left = theme.get("left_pole") if theme else None + semantic_right = theme.get("right_pole") if theme else None + if semantic_left and semantic_right: + left_pole, right_pole = semantic_left, semantic_right + left_motions, right_motions = ( + (pos_motions, neg_motions) if flip else (neg_motions, pos_motions) + ) + left_arrow, right_arrow = ("▲", "▼") if flip else ("▼", "▲") + elif flip: left_pole, right_pole = pos_pole, neg_pole left_motions, right_motions = pos_motions, neg_motions left_arrow, right_arrow = "▲", "▼"