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.
102 lines
3.8 KiB
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
|
|
|