- Add AGENTS.md with documented solutions reference
- Include SVD label convention (right-wing parties on right side)
- Document SVD insight: labels reflect voting patterns, not semantics
- Fix SQL verification example to use Python approach
The component captures voting unity of the right-wing coalition vs left
opposition, NOT semantic content like 'defense' or 'EU integration'.
Motions about elderly care (Dobbe) appear because the left votes for them
while the right coalition votes against - this is coalition-opposition
polarization, not policy domain.
Bug: report_per_component used scored[:args.report_top_n] which took
top N by score (all positive for components with only positive scores).
JSON correctly separated positive and negative poles.
Fix: Use same positive/negative separation logic for report as JSON.
- Each motion now assigned to exactly one component (highest absolute score)
- Added --exclusive flag (default: True) for backward compatibility
- Added markdown report generation with motion details for label review
- Added --report-top-n for report size (default: 20 per component)
- Updated JSON output with 'exclusive' flag for transparency
- Add Dutch paragraph explaining Rice index and party discipline patterns
- Analysis covers high discipline parties (PVV, SGP) vs lower discipline parties
- Explains what discipline reveals about party dynamics
- Add _load_mp_vectors_by_party_for_window() to load SVD vectors for specific windows
- Add load_party_axis_scores_for_window() cached function
- Add year selector UI for components 3-10 similar to components 1-2
- Uses get_uniform_dim_windows() to get available windows
- Changed _render_party_axis_chart_1d from horizontal bar chart to scatter plot
- Same format as components 1-2: markers on horizontal line with axis arrows- Axis labels now show correct direction with arrows (← left | right →)
- Ensures consistent visualization across all SVD components
Previously the st.plotly_chart call was wrapped in 'except Exception: pass'
which silently swallowed all rendering errors. The user would see no chart
and no error message.
Now:
- Exception message is shown via st.error()
- Diagnostics JSON is shown when debug is enabled (EXPLORER_DEBUG_TRAJECTORIES=1
or UI checkbox), even when trace_count > 0
This reveals the actual root cause when the chart fails to render.
- Lock x_label/y_label to Links-Rechts / Progressief-Conservatief after
classify_axes; Procrustes sign-fixing in compute_2d_axes already ensures
the correct orientation so the heuristic _should_swap_axes call is removed
- Remove visual error bars from party axis chart; 95% CI is now shown in
hover text (party: score, N=n, 95%-BI: [low, high]) to keep the 1D
scatter clean
- Remove show_ci checkbox and parameter — CI is always accessible on hover
- Update tests to match new hover format and absence of error_x
- Extract shared helper that both load_party_axis_scores and
load_party_mp_vectors delegate to, eliminating ~40 lines of
duplicated DB query + vector parsing code
- Remove dead code in load_party_axis_scores that queried mp_metadata
twice (first without ORDER BY, then again with ORDER BY, overwriting)
- Fix _cached_bootstrap_cis parameter: remove _ prefix so Streamlit
actually hashes the input dict instead of caching with no key
- Add load_party_mp_vectors() to return raw per-MP SVD vectors by party
- Extract _build_party_axis_figure() as pure function for testability
- Modify _render_party_axis_chart to accept bootstrap_data and delegate
to the new builder
- When bootstrap_data present: show error_x bars, diamond markers for
N=1 parties, and N=count in hover text
- Wire up bootstrap computation in build_svd_components_tab via cached
_cached_bootstrap_cis wrapper
- Add 6 tests covering figure construction, bootstrap rendering, flip
behavior, and importability
Use one DuckDB write connection for the entire update loop instead of
opening/closing per row, wrapped in try/finally for proper cleanup.
Move 'import duckdb' to module level with other imports.
Enable backfilling body_text for existing motions that lack it (2016-2018 data).
New extract_besluit_id() and update_existing_motions() helpers support the
--update-existing mode, while --no-skip-details enables detail fetching during
normal downloads. Includes 7 tests covering URL parsing, DB update flow, and
argparse wiring.
Move rng initialization before the party loop so each party gets a
unique segment of the random stream instead of identical sequences.
Replace Python bootstrap loop with vectorized numpy indexing.
Pure numpy function that computes bootstrap confidence intervals for
party centroid vectors. Handles N>=2 (bootstrap), N=1 (degenerate CI),
and N=0 (excluded) cases. Uses np.random.default_rng for reproducibility.