From 5b3cf23d363fee98a2c8ab21547e0b31538e5793 Mon Sep 17 00:00:00 2001 From: Sven Geboers Date: Thu, 2 Apr 2026 21:05:30 +0200 Subject: [PATCH] refactor: use svd_labels for fallback labels in explorer and axis_classifier (Task 4) --- analysis/axis_classifier.py | 27 ++++++++++------ explorer.py | 9 ++++-- tests/test_axis_label_fallback.py | 52 +++++++++++++++++++++---------- 3 files changed, 58 insertions(+), 30 deletions(-) diff --git a/analysis/axis_classifier.py b/analysis/axis_classifier.py index a26ed70..14a281f 100644 --- a/analysis/axis_classifier.py +++ b/analysis/axis_classifier.py @@ -13,7 +13,7 @@ import numpy as np import re import json -from analysis.svd_labels import get_svd_label +from analysis.svd_labels import get_svd_label, get_fallback_labels _logger = logging.getLogger(__name__) @@ -44,16 +44,21 @@ _LABELS = { 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. + Maps numeric fallback names 'As 1' / 'Stempatroon As 1' to the + semantic labels from SVD_THEMES. 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" + # Fallback to component 1 (x) or 2 (y) + comp = 1 if axis == "x" else 2 + return get_svd_label(comp) + + # Map "As 1" / "As 2" to semantic labels if axis == "x" and modal_label in ("As 1", "Stempatroon As 1"): - return "Links\u2013Rechts" + return get_svd_label(1) if axis == "y" and modal_label in ("As 2", "Stempatroon As 2"): - return "Progressief\u2013Conservatief" + return get_svd_label(2) + return modal_label @@ -430,7 +435,8 @@ def _assign_label( Returns (label, interpretation_string, quality_score). """ orientation = "horizontale" if axis == "x" else "verticale" - fallback_label = _LABELS["fallback_x"] if axis == "x" else _LABELS["fallback_y"] + _x_fallback, _y_fallback = get_fallback_labels() + fallback_label = _x_fallback if axis == "x" else _y_fallback quality = max(abs(r_lr), abs(r_co), abs(r_pc)) if abs(r_lr) >= _THRESHOLD: @@ -608,13 +614,14 @@ def classify_axes( # ── Final label resolution ──────────────────────────────────────────── # If both motion and ideology paths produced nothing, use generic fallback. + _x_fallback, _y_fallback = get_fallback_labels() if x_lbl is None: - x_lbl = _LABELS["fallback_x"] + x_lbl = _x_fallback x_int = _INTERPRETATION_TEMPLATES["fallback"].format( orientation="horizontale" ) if y_lbl is None: - y_lbl = _LABELS["fallback_y"] + y_lbl = _y_fallback y_int = _INTERPRETATION_TEMPLATES["fallback"].format( orientation="verticale" ) diff --git a/explorer.py b/explorer.py index d568ec9..dfe937a 100644 --- a/explorer.py +++ b/explorer.py @@ -1612,15 +1612,18 @@ def build_compass_tab(db_path: str, window_size: str) -> None: # Use the classifier helper to map internal/modal labels (e.g. "As 1") to # user-facing labels. Import at function-time to avoid module import cycles # and keep explorer lightweight. If the helper is unavailable fall back to - # conventional semantic defaults so the UI remains readable. + # labels from the unified svd_labels module. try: from analysis.axis_classifier import display_label_for_modal _x_label = display_label_for_modal(_raw_x, "x") _y_label = display_label_for_modal(_raw_y, "y") except Exception: - _x_label = _raw_x or "EU-integratie–Nationalisme" - _y_label = _raw_y or "Populistisch–Institutioneel" + from analysis.svd_labels import get_fallback_labels + + _x_fallback, _y_fallback = get_fallback_labels() + _x_label = _raw_x or _x_fallback + _y_label = _raw_y or _y_fallback if level == "Partijen": # Aggregate to party centroids diff --git a/tests/test_axis_label_fallback.py b/tests/test_axis_label_fallback.py index ad5d994..81046c9 100644 --- a/tests/test_axis_label_fallback.py +++ b/tests/test_axis_label_fallback.py @@ -4,21 +4,34 @@ from analysis import axis_classifier def test_display_label_for_modal(): - assert axis_classifier.display_label_for_modal("As 1", "x") == "Links\u2013Rechts" - assert ( - axis_classifier.display_label_for_modal("Stempatroon As 1", "x") - == "Links\u2013Rechts" - ) - assert ( - axis_classifier.display_label_for_modal("As 2", "y") - == "Conservatief\u2013Progressief" - ) - assert ( - axis_classifier.display_label_for_modal("Stempatroon As 2", "y") - == "Conservatief\u2013Progressief" - ) - # None maps to conventional fallback - assert axis_classifier.display_label_for_modal(None, "x") == "Links\u2013Rechts" + """Test that display_label_for_modal uses SVD_THEMES for fallback labels.""" + # None should return fallback from SVD_THEMES + x_label = axis_classifier.display_label_for_modal(None, "x") + y_label = axis_classifier.display_label_for_modal(None, "y") + + # Should return component 1 and 2 labels from SVD_THEMES + assert "EU-integratie" in x_label or "Nationalisme" in x_label + assert "Populistisch" in y_label or "Institutioneel" in y_label + + +def test_display_label_for_modal_maps_as_labels(): + """Test that 'As 1' and 'As 2' are mapped to semantic labels.""" + x_label = axis_classifier.display_label_for_modal("As 1", "x") + y_label = axis_classifier.display_label_for_modal("As 2", "y") + + # Should return component 1 and 2 labels + assert "EU-integratie" in x_label or "Nationalisme" in x_label + assert "Populistisch" in y_label or "Institutioneel" in y_label + + +def test_display_label_for_modal_stempatroon(): + """Test that 'Stempatroon As N' are mapped to semantic labels.""" + x_label = axis_classifier.display_label_for_modal("Stempatroon As 1", "x") + y_label = axis_classifier.display_label_for_modal("Stempatroon As 2", "y") + + # Should return component 1 and 2 labels + assert "EU-integratie" in x_label or "Nationalisme" in x_label + assert "Populistisch" in y_label or "Institutioneel" in y_label def test_classify_axes_modal_fallback(monkeypatch, tmp_path): @@ -69,5 +82,10 @@ def test_classify_axes_modal_fallback(monkeypatch, tmp_path): if not enriched or not isinstance(enriched, dict): pytest.skip("classify_axes returned no enrichment in this environment") - assert enriched["x_label"] == "Links\u2013Rechts" - assert enriched["y_label"] == "Progressief\u2013Conservatief" + # Should now return SVD component labels instead of hardcoded values + assert ( + "EU-integratie" in enriched["x_label"] or "Nationalisme" in enriched["x_label"] + ) + assert ( + "Populistisch" in enriched["y_label"] or "Institutioneel" in enriched["y_label"] + )