Commit Graph
202 Commits
Author SHA1 Message Date
sgeboers 392fd3afce fix: add per-window X-axis orientation correction
The global PCA X-axis flip uses centroids averaged across all windows,
which can leave individual windows with left/right inverted (e.g. PvdA
appearing right of VVD in 2020). Mirror the existing per-window Y-axis
correction to also check and flip X values per window.
2026-03-29 01:15:21 +01:00
sgeboers 34c08a40fa feat: use dynamic axis labels in compass and trajectories UI
Replace hardcoded 'Links-Rechts' / 'Progressief-Conservatief' axis labels
with values from classify_axes(). Add per-year interpretation caption when
axis quality score is below the 0.65 correlation threshold.
2026-03-29 01:04:54 +01:00
sgeboers 5ec1f7af75 feat: add axis classifier with party ideology reference data
classify_axes() correlates per-party PCA positions against party_ideologies.csv
to assign honest dynamic labels (Links-Rechts, Coalitie-Oppositie, etc.)
instead of always assuming the first PCA axis is left-right.
2026-03-29 01:00:55 +01:00
sgeboers 23849c9cb6 docs: add axis classification implementation plan 2026-03-29 00:58:53 +01:00
sgeboers 6b811364c5 docs: add deployment note to axis classification spec
CSV files committed to git, baked into Docker image — no rsync needed.
2026-03-29 00:53:54 +01:00
sgeboers bb5f2961d1 docs: fix two spec ambiguities in axis classification design
- Clarify CSV path derivation from db_path (same data/ directory)
- Handle current_parliament window exclusion from modal label voting
2026-03-29 00:52:11 +01:00
sgeboers bed911b92c docs: add axis classification design spec
Add design for honest PCA axis labeling — validates each compass axis
against a party ideology reference CSV and labels dynamically (Links–Rechts,
Coalitie–Oppositie, or fallback) instead of hardcoding Left–Right always.
2026-03-29 00:51:34 +01:00
sgeboers 50f8a06c6d fix: connection leak, Rice index excludes absences, per-party motion count guard 2026-03-28 23:50:09 +01:00
sgeboers bcf9407957 feat: add voting discipline section below political compass 2026-03-28 23:45:38 +01:00
sgeboers ab99b7de18 fix: replace sideways Y-axis arrows with proper top/bottom annotations 2026-03-28 23:41:01 +01:00
sgeboers aac8a89118 fix: add missing party justifications in SVD_THEMES axes 3 and 5 explanations 2026-03-28 23:39:24 +01:00
sgeboers b6c2a9bacf fix: update SVD_THEMES axes 3-5 descriptions to reflect stable multi-year patterns 2026-03-28 23:37:10 +01:00
sgeboers 6914b2284a Add implementation plan for compass UI improvements 2026-03-28 23:33:03 +01:00
sgeboers c5b39ced5f Add design doc for compass UI improvements (axes 3-5, Y-axis arrows, discipline section) 2026-03-28 23:30:32 +01:00
sgeboers 064cd059d4 fix: per-window Y-axis correction for political compass
The global orientation check using party centroids averaged across all
windows was insufficient — individual windows (notably 2023) could still
have conservative parties above progressive ones on the Y-axis.

Added a per-window flip in compute_2d_axes (PCA branch) that checks
prog_avg_y vs cons_avg_y for each window independently and negates all
Y values in that window when cons > prog. Flipped window IDs are stored
in axis_def['y_flipped_windows'] for diagnostics.

Moved the canonical party set definitions outside the orientation try-
block so they are always in scope for the per-window correction.

Added test_per_window_y_orientation to cover the case where one window
is globally fine but locally inverted.
2026-03-28 22:45:40 +01:00
sgeboers 6329d6a256 UI improvements + add axis orientation test
- Rename app to 'Motief: de stematlas' in Home.py
- Remove PCA variance caption from compass tab
- Hardcode db_path and window_size; remove sidebar inputs
- Change trajectories default to [CDA, D66, VVD]
- Move quiz to pages/1_Stemwijzer.py; wrap in st.form
- Remove quiz tab from main explorer
- Add pytest dev dep + fix test fixtures (_load_mp_vectors_for_window)
- Add test_pca_axis_orientation with proper PCA variance dominance
2026-03-28 22:27:39 +01:00
sgeboers 72fbe0008e fix(ansible): add headless and CORS flags for reverse proxy 2026-03-28 22:01:04 +01:00
sgeboers b50ee650de fix(ansible): create data directory on server before rsync 2026-03-28 21:39:33 +01:00
sgeboers cbab8f080d fix(ansible): use rsync with checksum for motions.db sync 2026-03-28 21:09:17 +01:00
sgeboers 0bd1c08cb2 fix(ansible): ignore pkill errors, add motions.db sync task 2026-03-28 21:04:49 +01:00
sgeboers 22067fd162 fix(ansible): use full path for uv binary in shell tasks 2026-03-28 21:02:01 +01:00
sgeboers de6ed29bf7 fix(ansible): use port 222 for Gitea SSH, write SSH config on server 2026-03-28 20:46:57 +01:00
sgeboers 13cb746d06 fix(ansible): correct deploy key path to /home/webapps/.ssh/ed25519 2026-03-28 20:39:45 +01:00
sgeboers a4481af8e2 fix(ansible): use webapps deploy key for git clone instead of agent forwarding 2026-03-28 20:33:09 +01:00
sgeboers 8579da68bd fix(ansible): add git.sgeboers.nl to known_hosts before git clone 2026-03-28 20:29:06 +01:00
sgeboers 57083e496d chore(ansible): add ansible.cfg with SSH agent forwarding for private repo access 2026-03-28 20:25:36 +01:00
sgeboers c0d1c59bd7 chore(deploy): rewrite ansible for motief on motief.sgeboers.nl, remove drone
- Replace gtfs/bokeh deploy with motief/streamlit (port 8501)
- Update inventory to motief.sgeboers.nl
- Remove stale .drone.yml
- Add CI guard to forbid .env in repo
- Add env removal report and secrets rotation checklist
2026-03-28 20:12:36 +01:00
sgeboers 35f4667982 chore(secrets): stop tracking .env and add to .gitignore 2026-03-28 20:02:54 +01:00
sgeboers 445f0bfb24 feat(ansible-example): add @ansible/example package, tests, CI, publish & deploy workflows, docs and changelog 2026-03-28 20:00:31 +01:00
sgeboers b5c14d0c65 deploy to server 2026-03-26 22:52:11 +01:00
sgeboers c3f74433b2 chore(data): regenerate top SVD motions JSON with party-expanded SVD vectors 2026-03-25 23:49:35 +01:00
sgeboers 6cb89126a7 fix(svd): expand party votes to individual MPs before SVD computation
The mp_votes table contains both party-aggregate rows (e.g. 'PVV', 'NSC')
and individual MP rows (e.g. 'Aardema, M.'). Running SVD on both together
creates a block-diagonal vote matrix where party codes and individual MPs
occupy disjoint SVD dimensions — causing dim 0 to be zero for all 421 MPs.

Fix: _build_expanded_rows() converts every party-level vote to individual
MP votes using mp_metadata date ranges (active MPs on motion date). Motions
that already have individual MP records are kept as-is. A party name mapping
handles NSC/Nieuw Sociaal Contract and other canonical name variants.

Results for current_parliament: 517 individual MPs, all 8732 motions covered,
dim 0 std=23.1 (was 0.0 for all MPs). PVV/NSC/BBB on positive end, SP/GL/PvdD
on negative end — matches expected left-right political axis.

All 11 annual windows (2016-2026) re-run with the new pipeline.
2026-03-25 23:48:55 +01:00
sgeboers a7517bb6ae fix(svd): use averaged individual MP vectors for party axis scores, fix scree to %, re-add component 4
- load_party_axis_scores now loads individual MP vectors and averages
  per party (same data source as the political compass), so SVD axis
  rankings are consistent between the two tabs. Previously it used
  party-aggregate rows which gave structurally 0 signal on dim 3.

- Re-add component 4 (dim 3) to SVD_THEMES and revert comp_options
  filter — with individual MPs averaged, dim 3 now shows real party
  separation (the 'publieke voorzieningen vs marktwerking' axis).

- Scree plot y-axis now shows percentage of total variance instead of
  raw L2-norms; hover also updated to show '% van totaal'.
2026-03-25 23:06:35 +01:00
sgeboers 0a2238f2ba fix(svd): remove component 4 from SVD_THEMES (dim 3 has zero party signal)
Dim 3 captures within-party individual disagreement (MPs splitting
from party lines). Party-aggregate votes are structurally 0 on this
dimension, so the axis chart showed all parties at ~0 with no
discrimination. Confirmed via dry-run: identical to existing data.

Also filter comp_options to only show components with a defined theme,
so component 4 is hidden from the selectbox entirely.
2026-03-25 22:56:47 +01:00
sgeboers 26acd8b964 fix: scree plot uses party vectors instead of individual MPs
current_parliament has two separate SVD data spaces mixed together.
Party vectors (entity_id without comma) carry the between-party signal
in dims 0-15. Individual MP vectors only have signal in dim 3 and
dims 16-49 (within-party variance). The axis chart uses party vectors,
so the scree must too.
2026-03-25 22:44:57 +01:00
sgeboers 559e1adb82 fix(compass): fix annual window detection in get_uniform_dim_windows
Previous query used DISTINCT ON without ordering by dim, picking arbitrary
(often non-50) dim per window. Rewritten to find the dominant dim per window
(highest count) and include only windows where dominant dim = 50 with >= 10
entities. This surfaces annual windows 2016/2018/2019/2022-2026 that were
previously excluded due to mixed-dim rows from multiple pipeline runs.
2026-03-25 22:41:35 +01:00
sgeboers c386073430 feat(compass): switch to annual SVD windows by default
- load_positions annual mode now selects actual annual window_ids
  ('2022', '2023', etc.) instead of Q4 quarterly approximations,
  with current_parliament appended as the most-recent anchor
- Sidebar radio defaults to 'Per jaar' (annual) instead of quarterly
- Dutch labels for window size radio: 'Per jaar' / 'Per kwartaal'
2026-03-25 22:37:01 +01:00
sgeboers 96ea4c5522 feat(compass): fix duplicates, axes, controls, add party/MP toggle
- Add import re (needed for strip_paren deduplication)
- Deduplicate MPs with parenthetical first-name variants (e.g. 'Dijk, J.P.'
  and 'Dijk, J.P. (Jimmy)') by stripping parens and averaging positions;
  fixes 22 duplicate groups across all windows
- Replace select_slider with selectbox for time window control
- Remove non-functional 'Toon namen' checkbox and its usage
- Remove 'Min. MPs per partij' slider and party-size filter
- Add 'Weergave' radio toggle: Kamerleden (individual MPs) vs Partijen
  (party centroids computed as mean x/y per party)
- Fix axis ranges: x [-1, 1], y [-0.6, 0.6]
- Party view shows party abbreviation as dot label and hover shows MP count
2026-03-25 22:24:05 +01:00
sgeboers cf22ffc093 fix(explorer): fix scree plot data and add bar+line combo chart
- Use all individual MPs (not party aggregates) for L2-norm computation;
  party-aggregated vectors have near-zero values on some dims due to
  Procrustes alignment, producing spurious zeros
- Sort importances descending so scree plot is properly monotonic
- Relabel x-axis as 'Rang' since dim ordering after Procrustes alignment
  no longer matches original singular value order
- Add Scatter line trace connecting bar tops for elbow visibility
2026-03-25 22:11:09 +01:00
sgeboers c5cbc89c1f feat(explorer): add scree plot and clean up SVD axis chart
- Add load_scree_data() cached loader computing L2-norm of party scores
  per SVD dimension as a proxy for component importance
- Add _render_scree_plot() rendering a bar chart of the first 15 components
- Insert scree plot + Dutch explanation at the top of build_svd_components_tab
- Clean up _render_party_axis_chart: remove tick numbers, axis line, grid,
  and zero-line from the x-axis (pole labels remain as chart title)
2026-03-25 22:03:01 +01:00
sgeboers a20bd834fc chore(repo): remove stale scripts, caches, and old workflow
Cleanup performed by assistant: removed generated caches and stale files: __pycache__, *.pyc, .pytest_cache, .ruff_cache, dummy/, test.py, read.py, reset.py, fix_database.py, thoughts/thoughts/, .github/workflows/mindmodel-validate.yml. No push performed.
2026-03-25 21:37:57 +01:00
sgeboers 867fcd1989 feat: recompute SVD for full current parliament (2023-11-22 to 2026-03-17)
Adds new SVD window 'current_parliament' covering 8732 motions and 451 MPs
(vs 7424 motions in old '2025' window, adding ~1300 motions from 2025-Q4+).
Updates explorer.py to query the new window. Regenerates top_svd_top_motions.json.
Also clarifies axis 3 explanation noting FVD's anti-American positioning.
2026-03-25 01:16:33 +01:00
sgeboers 9daa899885 fix: remove motion title truncation, add SVD JSON generation script
Removes the raw_title[:80] cap on expander labels so full titles show.
Adds scripts/generate_svd_json.py to regenerate top_svd_top_motions.json
from any SVD window after a recompute.
2026-03-25 01:15:42 +01:00
sgeboers 49a1f2f67d fix: correct axis 4 and 5 pole labels and flip orientation in SVD_THEMES
Axes 4 and 5 had inverted sign conventions relative to actual party votes.
Diagnostic confirmed SP/PvdD scored negative on axis 4 (free trade motions)
and FVD scored negative on axis 5 (secular motions), opposite to their
voting behaviour. Fix: swap positive_pole/negative_pole for both axes and
set correct flip direction so progressive parties appear on the left.
2026-03-25 00:43:18 +01:00
sgeboers 361cf9fd35 feat(explorer): SVD tab redesign — pole-split motions, party axis chart, inline expanders with voting
- Add ChristenUnie colour alias and CURRENT_PARLIAMENT_PARTIES frozenset (15 parties)
- Add load_party_axis_scores() — queries party SVD vectors from window=2025, cached
- Add _render_party_axis_chart() — 1D Plotly scatter of party positions per axis
- Restructure build_svd_components_tab: replace session-state button/detail-pane with
  inline st.expander per motion, split into pos/neg pole columns, batch DB query for
  all 10 motions including voting_results, rendered via _render_voting_results

Smoke-tested: 15 parties loaded, all 10 axis-1 motions returned with voting data.
2026-03-25 00:11:37 +01:00
sgeboers 9caaa8baca feat(explorer): finalise SVD tab helper robustness and constants
Include plan: docs/superpowers/plans/2026-03-24-svd-tab-redesign.md
2026-03-25 00:04:54 +01:00
sgeboers 32fe3aed18 feat(explorer): harden _render_party_axis_chart axis bounds and range
Plan: docs/superpowers/plans/2026-03-24-svd-tab-redesign.md
2026-03-25 00:01:18 +01:00
sgeboers 5e061b0e40 feat(explorer): include CU alias in CURRENT_PARLIAMENT_PARTIES
Include plan path docs/superpowers/plans/2026-03-24-svd-tab-redesign.md
2026-03-25 00:01:15 +01:00
sgeboers 9f3ae15a16 feat(explorer): harden load_party_axis_scores (close DB, deterministic params)
Plan: docs/superpowers/plans/2026-03-24-svd-tab-redesign.md
2026-03-25 00:01:12 +01:00
sgeboers fc1884ecd8 feat(explorer): harden SVD tab batch-fetch motion details
Include plan: docs/superpowers/plans/2026-03-24-svd-tab-redesign.md
2026-03-25 00:00:55 +01:00