@ -57,6 +57,96 @@ def test_compute_2d_axes_pca_synthetic(monkeypatch):
assert axis_def . get ( " method " ) == " pca "
def test_per_window_y_orientation ( monkeypatch ) :
""" Per-window Y correction must ensure prog_avg_y > cons_avg_y in every window.
We construct two windows :
- w_good : progressive MPs at + Y , conservative MPs at - Y ( already correct )
- w_bad : conservative MPs at + Y , progressive MPs at - Y ( inverted )
We weight w_good with many more MPs so the GLOBAL centroid check passes
without noticing the per - window inversion . The per - window correction must
then flip w_bad so both windows end up with prog_avg_y > cons_avg_y .
"""
# Helpers to make slightly varied vectors
def pv ( base ) :
return np . array ( base , dtype = float )
# w_good: large left/right spread on dim-0, prog up (+Y), cons down (-Y)
w_good = {
# right / conservative
" Wilders, G. " : pv ( [ - 3.0 , - 1.0 , 0.0 ] ) ,
" Rutte, M. " : pv ( [ - 3.0 , - 0.9 , 0.0 ] ) ,
" van der Staaij, K. " : pv ( [ - 2.9 , - 0.95 , 0.0 ] ) ,
" Omtzigt, P. " : pv ( [ - 2.8 , - 0.85 , 0.0 ] ) ,
# left / progressive
" Marijnissen, L. " : pv ( [ 3.0 , 1.0 , 0.0 ] ) ,
" Klever, A. " : pv ( [ 3.0 , 0.9 , 0.0 ] ) ,
" Bromet, L. " : pv ( [ 2.9 , 0.95 , 0.0 ] ) ,
" Nijboer, H. " : pv ( [ 2.8 , 0.85 , 0.0 ] ) ,
}
# w_bad: same left/right structure but Y is inverted relative to w_good
# (conservative at +Y, progressive at -Y)
w_bad = {
" Wilders, G. " : pv ( [ - 3.0 , 1.0 , 0.0 ] ) , # cons at +Y
" Rutte, M. " : pv ( [ - 3.0 , 0.9 , 0.0 ] ) ,
" van der Staaij, K. " : pv ( [ - 2.9 , 0.95 , 0.0 ] ) ,
" Omtzigt, P. " : pv ( [ - 2.8 , 0.85 , 0.0 ] ) ,
" Marijnissen, L. " : pv ( [ 3.0 , - 1.0 , 0.0 ] ) , # prog at -Y
" Klever, A. " : pv ( [ 3.0 , - 0.9 , 0.0 ] ) ,
" Bromet, L. " : pv ( [ 2.9 , - 0.95 , 0.0 ] ) ,
" Nijboer, H. " : pv ( [ 2.8 , - 0.85 , 0.0 ] ) ,
}
aligned = { " w_good " : w_good , " w_bad " : w_bad }
mp_metadata = [
( " Wilders, G. " , " PVV " ) ,
( " Rutte, M. " , " VVD " ) ,
( " van der Staaij, K. " , " SGP " ) ,
( " Omtzigt, P. " , " Nieuw Sociaal Contract " ) ,
( " Marijnissen, L. " , " SP " ) ,
( " Klever, A. " , " GroenLinks-PvdA " ) ,
( " Bromet, L. " , " GroenLinks-PvdA " ) ,
( " Nijboer, H. " , " SP " ) ,
]
fake_traj = _make_fake_traj ( aligned )
monkeypatch . setitem ( sys . modules , " analysis.trajectory " , fake_traj )
import types as _types
fake_conn = _types . SimpleNamespace (
execute = lambda q : _types . SimpleNamespace ( fetchall = lambda : mp_metadata ) ,
close = lambda : None ,
)
import duckdb as _duckdb
monkeypatch . setattr ( _duckdb , " connect " , lambda db_path , * * kw : fake_conn )
import importlib , analysis . political_axis as _ax
importlib . reload ( _ax )
from analysis . political_axis import compute_2d_axes
positions_by_window , axis_def = compute_2d_axes (
db_path = " dummy " , window_ids = [ " w_good " , " w_bad " ] , method = " pca "
)
prog_mps = { " Marijnissen, L. " , " Klever, A. " , " Bromet, L. " , " Nijboer, H. " }
cons_mps = { " Wilders, G. " , " Rutte, M. " , " van der Staaij, K. " , " Omtzigt, P. " }
for wid in ( " w_good " , " w_bad " ) :
pos = positions_by_window [ wid ]
prog_y = np . mean ( [ pos [ mp ] [ 1 ] for mp in prog_mps if mp in pos ] )
cons_y = np . mean ( [ pos [ mp ] [ 1 ] for mp in cons_mps if mp in pos ] )
assert prog_y > cons_y , (
f " Window ' { wid } ' : expected prog_avg_y ( { prog_y : .3f } ) > cons_avg_y ( { cons_y : .3f } ) "
)
def test_pca_axis_orientation ( monkeypatch ) :
""" PCA axes must be oriented so right parties score higher on X and
progressive parties score higher on Y than their respective opposites .