You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
motief/tests/test_trajectories_pipeline_...

102 lines
3.8 KiB

"""Integration test: full trajectory pipeline produces non-empty plot."""
import pytest
from explorer import load_positions, load_party_map, select_trajectory_plot_data
from explorer_helpers import compute_party_centroids
def test_trajectory_pipeline_produces_traces():
"""Regression: trajectories must produce colored traces, not empty charts."""
db_path = "data/motions.db"
window_size = "annual"
# Stage 1: load positions
positions_by_window, _ = load_positions(db_path, window_size)
assert len(positions_by_window) > 0, "Expected at least one window"
total_mps = sum(len(v) for v in positions_by_window.values())
assert total_mps > 0, "Expected MPs in windows"
# Stage 2: load party map
party_map = load_party_map(db_path)
assert len(party_map) > 0, "Expected party map entries"
# Stage 3: compute centroids
windows = list(positions_by_window.keys())
centroids, mp_positions = compute_party_centroids(
positions_by_window, party_map, windows
)
assert len(centroids) > 0, "Expected at least one party centroid"
# Stage 4: select trajectory plot data (default party selection)
# Use the same defaults as build_trajectories_tab: CDA, D66, VVD if available
default_parties = [p for p in ["CDA", "D66", "VVD"] if p in centroids]
if not default_parties:
default_parties = list(centroids.keys())[:3]
fig, trace_count, banner = select_trajectory_plot_data(
positions_by_window,
party_map,
windows,
selected_parties=default_parties,
smooth_alpha=0.35,
)
# Assertions
assert trace_count > 0, (
f"Expected traces but got trace_count={trace_count}, banner={banner}"
)
assert banner is None, f"Expected no fallback banner but got: {banner}"
assert len(fig.data) == trace_count, (
f"fig.data ({len(fig.data)}) should equal trace_count ({trace_count})"
)
# Verify traces have real coordinates (not all NaN)
for trace in fig.data:
assert len(trace.x) > 0, f"Trace {trace.name} has no x values"
assert len(trace.y) > 0, f"Trace {trace.name} has no y values"
# At least some values should be real (not NaN)
import math
real_x = sum(
1 for v in trace.x if not (v is None or (isinstance(v, float) and v != v))
) # v != v is True only for NaN
real_y = sum(
1 for v in trace.y if not (v is None or (isinstance(v, float) and v != v))
)
assert real_x > 0, f"Trace {trace.name} has all NaN x values"
assert real_y > 0, f"Trace {trace.name} has all NaN y values"
def test_trajectory_helper_skips_second_loop():
"""Regression: when select_trajectory_plot_data succeeds, build_trajectories_tab
should NOT add duplicate traces via the fallback loop.
This test verifies that the helper produces clean output without relying on
the second loop in build_trajectories_tab.
"""
db_path = "data/motions.db"
window_size = "annual"
positions_by_window, _ = load_positions(db_path, window_size)
party_map = load_party_map(db_path)
windows = list(positions_by_window.keys())
centroids, _ = compute_party_centroids(positions_by_window, party_map, windows)
# Use 6 parties like the app's multiselect
selected = list(centroids.keys())[:6]
fig, trace_count, banner = select_trajectory_plot_data(
positions_by_window,
party_map,
windows,
selected_parties=selected,
smooth_alpha=0.35,
)
# Should produce exactly the number of selected parties (or fewer if some have all-NaN)
assert trace_count <= len(selected), (
f"trace_count ({trace_count}) should not exceed selected ({len(selected)})"
)
assert banner is None, "No fallback should be needed with valid data"
assert len(fig.data) == trace_count