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

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

@ -13,6 +13,8 @@ import numpy as np
import re
import json
from analysis.svd_labels import get_svd_label
_logger = logging.getLogger(__name__)
# Module-level caches — loaded once per process lifetime.
@ -23,15 +25,15 @@ _coalition_cache: Optional[Dict[str, set]] = None
_THRESHOLD = 0.65
_LABELS = {
"lr": "Links\u2013Rechts",
"co": "Coalitie\u2013Oppositie",
"pc": "Conservatief\u2013Progressief",
# When we have no interpretable classifier signal, fall back to numeric SVD
# component names (As 1 / As 2) rather than the vague "Stempatroon As N".
# The UI will still add directional annotations (▲/▼) when rendering the
# vertical axis to make polarity unambiguous.
"fallback_x": "As 1",
"fallback_y": "As 2",
"lr": "Verzorgingsstaat–Marktwerking",
"eu": "EU-integratie–Nationalisme",
"pi": "Populistisch–Institutioneel",
"co": "Coalitie–Oppositie",
"pc": "Conservatief–Progressief",
# When we have no interpretable classifier signal, fall back to the known
# SVD component meanings rather than generic "As N" labels.
"fallback_x": get_svd_label(1),
"fallback_y": get_svd_label(2),
}
@ -39,11 +41,29 @@ _LABELS = {
# 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 = {
"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": (
"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.",
"fallback": (
@ -55,8 +75,10 @@ _INTERPRETATION_TEMPLATES = {
# Maps motion-path keyword labels to _INTERPRETATION_TEMPLATES keys.
# Labels not present here fall back to "fallback".
_MOTION_LABEL_TEMPLATE_KEY: Dict[str, str] = {
"Links\u2013Rechts": "lr",
"Progressief\u2013Conservatief": "pc",
"Verzorgingsstaat–Marktwerking": "lr",
"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
_KEYWORDS: Dict[str, List[str]] = {
"Links\u2013Rechts": [
# economic
"Verzorgingsstaat–Marktwerking": [
# economic / welfare state
"belasting",
"uitkering",
"bijstand",
@ -78,18 +100,57 @@ _KEYWORDS: Dict[str, List[str]] = {
"pensioen",
"aow",
"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",
"asielaanvraag",
"migratie",
"vreemdeling",
"vluchtelingen",
"terugkeer",
"grenzen",
"opvang",
"statushouder",
"soevereiniteit",
"nationaal",
],
"Populistisch–Institutioneel": [
# Populist/nationalist themes
"terugsturen",
"syrië",
"syrier",
"grenzen dicht",
"remigratie",
"eigen volk",
"nederland eerst",
"corona",
"vaccin",
"ivermectine",
"hydroxychloroquine",
"complot",
"deep state",
"establishment",
"elite",
"herstelbetaling",
"excuses",
],
"Progressief\u2013Conservatief": [
"ProgressiefConservatief": [
# environment
"klimaat",
"stikstof",
@ -109,16 +170,6 @@ _KEYWORDS: Dict[str, List[str]] = {
"religie",
"geloof",
],
"Nationaal\u2013Internationaal": [
"navo",
"nato",
"europees",
"europese",
" eu ",
"verdrag",
" vn ",
"internationaal",
],
}
# 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:
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] = {}
y_quality: Dict[str, float] = {}
x_interpretation: Dict[str, str] = {}

Loading…
Cancel
Save