refactor: use svd_labels module for fallback labels in axis_classifier (Task 3)

main
Sven Geboers 1 month ago
parent 5b1be26050
commit 36b58ad50d
  1. 139
      analysis/axis_classifier.py

@ -13,6 +13,8 @@ import numpy as np
import re import re
import json import json
from analysis.svd_labels import get_svd_label
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
# Module-level caches — loaded once per process lifetime. # Module-level caches — loaded once per process lifetime.
@ -23,15 +25,15 @@ _coalition_cache: Optional[Dict[str, set]] = None
_THRESHOLD = 0.65 _THRESHOLD = 0.65
_LABELS = { _LABELS = {
"lr": "Links\u2013Rechts", "lr": "Verzorgingsstaat–Marktwerking",
"co": "Coalitie\u2013Oppositie", "eu": "EU-integratie–Nationalisme",
"pc": "Conservatief\u2013Progressief", "pi": "Populistisch–Institutioneel",
# When we have no interpretable classifier signal, fall back to numeric SVD "co": "Coalitie–Oppositie",
# component names (As 1 / As 2) rather than the vague "Stempatroon As N". "pc": "Conservatief–Progressief",
# The UI will still add directional annotations (▲/▼) when rendering the # When we have no interpretable classifier signal, fall back to the known
# vertical axis to make polarity unambiguous. # SVD component meanings rather than generic "As N" labels.
"fallback_x": "As 1", "fallback_x": get_svd_label(1),
"fallback_y": "As 2", "fallback_y": get_svd_label(2),
} }
@ -39,11 +41,29 @@ _LABELS = {
# Remove duplicate lower definition (keep the one at the top) # Remove duplicate lower definition (keep the one at the top)
def display_label_for_modal(modal_label: Optional[str], axis: str) -> str:
"""Return a user-facing axis label for a modal/internal label.
Keeps existing behavior: map numeric fallback names 'As 1' / 'Stempatroon As 1'
to the conventional semantic defaults used in the UI. Any other label is
returned unchanged; None is treated as the semantic fallback for the axis.
"""
if modal_label is None:
return "Links\u2013Rechts" if axis == "x" else "Progressief\u2013Conservatief"
if axis == "x" and modal_label in ("As 1", "Stempatroon As 1"):
return "Links\u2013Rechts"
if axis == "y" and modal_label in ("As 2", "Stempatroon As 2"):
return "Progressief\u2013Conservatief"
return modal_label
_INTERPRETATION_TEMPLATES = { _INTERPRETATION_TEMPLATES = {
"lr": "De {orientation} as weerspiegelt de klassieke links-rechts tegenstelling.", "lr": "De {orientation} as weerspiegelt de economische tegenstelling tussen verzorgingsstaat en marktwerking.",
"eu": "De {orientation} as weerspiegelt de tegenstelling tussen EU-integratie/internationalisme en nationalisme/soevereiniteit.",
"pi": "De {orientation} as scheidt populistisch-nationalistische partijen van het institutioneel-parlementaire establishment.",
"co": ( "co": (
"De {orientation} as weerspiegelt stemgedrag van coalitie- versus " "De {orientation} as weerspiegelt stemgedrag van coalitie- versus "
"oppositiepartijen (r={r:.2f}). Links-rechts is minder dominant dit jaar." "oppositiepartijen (r={r:.2f}). Ideologische tegenstellingen zijn minder dominant dit jaar."
), ),
"pc": "De {orientation} as weerspiegelt de progressief-conservatieve tegenstelling.", "pc": "De {orientation} as weerspiegelt de progressief-conservatieve tegenstelling.",
"fallback": ( "fallback": (
@ -55,8 +75,10 @@ _INTERPRETATION_TEMPLATES = {
# Maps motion-path keyword labels to _INTERPRETATION_TEMPLATES keys. # Maps motion-path keyword labels to _INTERPRETATION_TEMPLATES keys.
# Labels not present here fall back to "fallback". # Labels not present here fall back to "fallback".
_MOTION_LABEL_TEMPLATE_KEY: Dict[str, str] = { _MOTION_LABEL_TEMPLATE_KEY: Dict[str, str] = {
"Links\u2013Rechts": "lr", "Verzorgingsstaat–Marktwerking": "lr",
"Progressief\u2013Conservatief": "pc", "EU-integratie–Nationalisme": "eu",
"Populistisch–Institutioneel": "pi",
"Progressief–Conservatief": "pc",
} }
@ -64,8 +86,8 @@ _MOTION_LABEL_TEMPLATE_KEY: Dict[str, str] = {
_KEYWORD_THRESHOLD = 0.4 _KEYWORD_THRESHOLD = 0.4
_KEYWORDS: Dict[str, List[str]] = { _KEYWORDS: Dict[str, List[str]] = {
"Links\u2013Rechts": [ "Verzorgingsstaat–Marktwerking": [
# economic # economic / welfare state
"belasting", "belasting",
"uitkering", "uitkering",
"bijstand", "bijstand",
@ -78,18 +100,57 @@ _KEYWORDS: Dict[str, List[str]] = {
"pensioen", "pensioen",
"aow", "aow",
"zorg", "zorg",
# immigration "huur",
"woning",
"sociaal",
"werkloos",
"ww",
"arbeidsongeschik",
"wao",
"gemeentefonds",
],
"EU-integratie–Nationalisme": [
# EU and international cooperation
"europees",
"europese",
" eu ",
"eu-",
"verdrag",
"intergouvernementeel",
"samenwerking",
"internationaal",
"navo",
"nato",
" vn ",
"vn-",
"sancties",
"israël",
"vluchteling",
"asiel", "asiel",
"asielaanvraag", "soevereiniteit",
"migratie", "nationaal",
"vreemdeling", ],
"vluchtelingen", "Populistisch–Institutioneel": [
"terugkeer", # Populist/nationalist themes
"grenzen", "terugsturen",
"opvang", "syrië",
"statushouder", "syrier",
"grenzen dicht",
"remigratie",
"eigen volk",
"nederland eerst",
"corona",
"vaccin",
"ivermectine",
"hydroxychloroquine",
"complot",
"deep state",
"establishment",
"elite",
"herstelbetaling",
"excuses",
], ],
"Progressief\u2013Conservatief": [ "ProgressiefConservatief": [
# environment # environment
"klimaat", "klimaat",
"stikstof", "stikstof",
@ -109,16 +170,6 @@ _KEYWORDS: Dict[str, List[str]] = {
"religie", "religie",
"geloof", "geloof",
], ],
"Nationaal\u2013Internationaal": [
"navo",
"nato",
"europees",
"europese",
" eu ",
"verdrag",
" vn ",
"internationaal",
],
} }
# Pre-compiled regexes for keyword matching. We escape keywords but do NOT add # Pre-compiled regexes for keyword matching. We escape keywords but do NOT add
@ -454,24 +505,6 @@ def classify_axes(
if not ideology and not motion_path_available: if not ideology and not motion_path_available:
return axes return axes
def display_label_for_modal(modal_label: Optional[str], axis: str) -> str:
"""Return a user-facing axis label for a modal/internal label.
Keeps existing behavior: map numeric fallback names 'As 1' / 'Stempatroon As 1'
to the conventional semantic defaults used in the UI. Any other label is
returned unchanged; None is treated as the semantic fallback for the axis.
"""
if modal_label is None:
return "Links\u2013Rechts" if axis == "x" else "Conservatief\u2013Progressief"
if axis == "x" and modal_label in ("As 1", "Stempatroon As 1"):
return "Links\u2013Rechts"
if axis == "y" and modal_label in ("As 2", "Stempatroon As 2"):
return "Conservatief\u2013Progressief"
return modal_label
# duplicate early-exit guard removed here
x_quality: Dict[str, float] = {} x_quality: Dict[str, float] = {}
y_quality: Dict[str, float] = {} y_quality: Dict[str, float] = {}
x_interpretation: Dict[str, str] = {} x_interpretation: Dict[str, str] = {}

Loading…
Cancel
Save