Commit Graph
75 Commits
Author SHA1 Message Date
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 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 35dbc8118a feat(explorer): add load_party_axis_scores helper
docs/superpowers/plans/2026-03-24-svd-tab-redesign.md
2026-03-24 23:56:37 +01:00
sgeboers 521385c832 docs: add SVD tab redesign implementation plan 2026-03-24 23:52:54 +01:00
sgeboers 8c79aaf917 docs: add SVD tab redesign spec 2026-03-24 23:50:40 +01:00
sgeboers c96c681641 feat(explorer): apply rigorous subagent-produced SVD axis themes
Replace draft SVD_THEMES with themes produced by per-axis analysis of all
10 unique top motions (zero cross-axis overlap, window=2025). Each axis now
has a detailed Dutch-language explanation, positive_pole and negative_pole
labels, displayed as colour-coded columns in the UI.
2026-03-24 23:38:50 +01:00
sgeboers b85a9cebd7 fix(data): remove 18 duplicate motions and redo SVD top-motions analysis
Deduplication:
- Identified 18 motion pairs with identical body_text and externe_identifier
- Kept the lower ID (first inserted) from each pair
- Cascaded deletes: 18 motions, 18 embeddings, 28 svd_vectors, 23 fused_embeddings
- motions table: 28172 → 28154, zero body_text duplicate groups remaining

SVD analysis:
- Regenerated top_svd_top_motions.json for window=2025 with clean data
  (7424 vectors, down from 7430)
- 100 unique motions across 10 axes, no title or ID duplicates
- De Vos huiseigenaren motie no longer appears twice in axis 3
2026-03-24 23:29:18 +01:00
sgeboers 8b73ab8bce feat(explorer): add SVD axis themes and unique-motion deduplication
- Regenerated top_svd_top_motions.json for window=2025 with strict
  cross-axis deduplication: 100 unique motions across 10 axes (10 per
  axis, zero overlap), sorted by absolute SVD score
- Added SVD_THEMES dict to build_svd_components_tab with Dutch-language
  theme label and political-polarisation explanation for each of the 10
  axes (e.g. 'Confessioneel-conservatief vs. seculier-progressief')
- Selectbox now shows 'As N — <theme>' instead of bare component number
- Each selected axis shows an info banner with the full explanation
- Motion list buttons show ▲/▼ to indicate positive/negative SVD loading
- Translated UI strings to Dutch for consistency
2026-03-24 23:11:51 +01:00
sgeboers e29d8a8055 ci(mindmodel): add scheduled validation workflow 2026-03-24 22:41:43 +01:00
sgeboers 2755dc373a chore(format): add pre-commit and formatter configs 2026-03-24 22:41:40 +01:00
sgeboers c24ab48704 chore(deps): move pytest to dev-dependencies 2026-03-24 22:41:36 +01:00
sgeboers 540099f2b7 ci(mindmodel): add report-only mindmodel validation workflow 2026-03-24 22:41:33 +01:00
sgeboers d1faf2b3e4 feat(mindmodel): add CLI wrapper, edge-case tests, and manifest schema tests 2026-03-24 22:41:28 +01:00
sgeboers ed289ff582 chore(mindmodel): add sanitized read-only manifest and reviewer README 2026-03-24 22:41:24 +01:00
sgeboers f091846dc8 feat(mindmodel): add report-only validator skeleton, types, and tests 2026-03-24 22:41:15 +01:00
sgeboers dbd8cc801a docs(mindmodel): add README and tooling note; chore(tooling): add minimal pre-commit 2026-03-24 21:31:53 +01:00
sgeboers 5caebe1331 chore(ci): add mindmodel validator workflow 2026-03-24 21:30:45 +01:00
sgeboers f77875ed54 feat(mindmodel): add CLI wrapper and tests 2026-03-24 21:29:10 +01:00
sgeboers a74e6006f5 feat(mindmodel): add validator and tests 2026-03-24 21:28:17 +01:00
sgeboers 7bd7d0d18c feat(mindmodel): add checks utilities and tests 2026-03-24 21:26:38 +01:00
sgeboers 2efd7ba3a0 feat(mindmodel): add manifest loader and tests 2026-03-24 21:24:58 +01:00
sgeboers 9c82962d47 chore: add .mindmodel/ project constraints and conventions
Generated mindmodel covering stack, architecture, domain glossary,
coding conventions, DuckDB/requests/embeddings/error-handling patterns,
anti-patterns, and explicit constraints for naming, imports, DB access,
error handling, and testing.
2026-03-24 20:51:53 +01:00
sgeboers 504400faf2 fix: quiz seed from motions with actual individual MP votes
Root causes:
- Seed selection sorted by controversy_score across all 28k motions, but
  only 282 have individual MP vote records. Top controversial motions only
  have party-level votes, so match_mps_for_votes always returned empty.
- global_db singleton was used for match/discriminate instead of the db_path
  passed to the tab builder.

Fixes:
- Add MotionDatabase.get_motions_with_individual_votes(k) which queries
  motions with comma-formatted mp_name votes, ordered by controversy_score
- Replace broken seed logic in build_mp_quiz_tab with this new method
- Replace global_db usages with a local MotionDatabase(db_path) instance
- Guard against motion IDs present in votes but absent from motions DataFrame
2026-03-24 20:29:05 +01:00
sgeboers 238d9e9ec2 test: add full quiz tab test suite and fix Geen stem normalization
- Fix match_mps_for_votes: 'Geen stem'/'no vote' now normalize to None (skipped),
  not 'afwezig' — so unanswered questions don't inflate overlap count
- Add 5 additional tests to test_match_mps.py: zero-overlap exclusion,
  empty input validation, Geen stem overlap skip, excluded motions respected
- Add tests/test_explorer_quiz.py: builder import smoke test plus 3 real-DB
  end-to-end scenarios (unique match, indistinguishable MPs, discriminating
  question reduces candidate set)
- Full suite: 73 passed, 2 skipped
2026-03-24 20:18:20 +01:00
sgeboers eb73275f32 feat(mp-quiz): add MP quiz tab and DB helpers; add design and plan docs 2026-03-24 19:59:25 +01:00
sgeboers b09e580f65 feat: motion content enrichment pipeline hardening
- ai_provider_wrapper: retry/fallback with exponential backoff, None sentinel for failed items
- text_pipeline: use wrapper, return 5-tuple (stored, skipped_existing, skipped_no_text, errors, failed_ids)
- similarity/compute: filter trivial 1.0 matches on identical short titles (<12 chars)
- rerun_embeddings: --retry-missing mode, calls ensure_text_embeddings_for_ids on failed ids
- sync_motion_content: per-ext_id retries, HTTPAdapter pool, --max-body-workers CLI flag, audit on failure
- qa_similarity script: samples motions, writes JSON ledger to thoughts/ledgers/
- All tests green: 61 passed, 2 skipped
2026-03-23 21:31:39 +01:00
sgeboers aef7c45074 Refactor tests: replace sys.modules hacks with real DI + in-memory DB
- Add db=None, embedder=None params to ai_provider_wrapper, text_pipeline, compute_similarities
- New conftest.py: FakeEmbedder, mem_db (in-memory DuckDB), fake_embedder fixtures
- Rewrite test_ai_provider_wrapper (4 tests), test_rerun_embeddings_retry (2 tests), test_similarity_compute_filter (1 test) with real implementations
- Fix rerun_embeddings tests hanging on _get_all_windows by patching it alongside _clear_embeddings
- All 53 tests pass (2 skipped), 0 sys.modules hacks in refactored files
2026-03-23 21:21:10 +01:00
sgeboers b7350d8f87 test: rewrite test_database_audit using mem_db fixture, no disk writes required 2026-03-23 21:01:09 +01:00
sgeboers e4f2c7ff59 fix: update integration test to unpack 5-tuple from ensure_text_embeddings 2026-03-23 20:30:35 +01:00
sgeboers ebb663aa8f docs: add test refactor implementation plan 2026-03-23 20:20:34 +01:00
sgeboers 07a89a207c docs: add test refactor design - replace monkeypatching with DI + in-memory DuckDB 2026-03-23 20:15:29 +01:00
sgeboers ce27dc6ac5 chore(ledgers): record fusion+similarity run summary and JSON details 2026-03-23 17:48:39 +01:00
sgeboers 22f53840b8 fix(explorer): normalise party name 'Nieuw Sociaal Contract' -> 'NSC' 2026-03-22 23:13:45 +01:00
sgeboers cbbdc6e738 fix(explorer): cleaner trajectories, NSC support, controversy filter, voting display, URL links 2026-03-22 23:12:43 +01:00
sgeboers cd7bb3b1e0 fix(explorer): filter to uniform-dim windows before PCA to prevent np.vstack shape mismatch 2026-03-22 22:54:54 +01:00
sgeboers 2891e9ee70 feat: add StemAtlas Streamlit app, explorer, Docker deployment, blog charts 2026-03-22 22:38:17 +01:00
sgeboers daa22c5e2b feat: complete parliamentary embedding pipeline with full historical coverage
- Add fused (SVD + text) embedding pipeline for annual windows 2016-2026
- Fix store_fused_embedding duplicate bug: DELETE before INSERT (idempotent)
- Add --text-batch-size CLI flag to run_pipeline.py (default 200)
- Add explicit --start-date/--end-date to download_past_year.py
- Backfill mp_votes for all motions (party-level votes, 111k new rows)
- Add similarity cache recompute: 212k rows across 9 annual windows
- Improve ai_provider retry logic, text_pipeline batching
- Improve analysis/political_axis PCA handling and visualizations
- Add diagnostic/utility scripts: compare_svd, generate_compass, inspect_axis, etc.
- Untrack data/motions.db (3.6GB binary), add to .gitignore with outputs/
- Update continuity ledger with full session state
2026-03-22 16:08:06 +01:00
sgeboers a78bee9b0a feat(similarity): add precomputed similarity cache, fix fusion N+1, add 429 retry
- Add similarity/ package (compute.py, lookup.py) with numpy-based
  pairwise cosine similarity and cached lookup
- database.py: create embeddings + similarity_cache tables in _init_database(),
  add store_similarity_batch/get_cached_similarities/clear_similarity_cache helpers
- pipeline/fusion.py: replace N+1 per-motion embedding SELECT with single
  bulk JOIN using DuckDB QUALIFY window function
- ai_provider.py: retry HTTP 429 with Retry-After header support
- migrations/2026-03-22-add-similarity-cache.sql: make executable
- Add tests for similarity compute, db helpers, and 429 retry (34 pass, 2 skip)
2026-03-22 03:02:25 +01:00
sgeboers a248807e03 Add design: embedding-based motion similarity cache
Precomputed top-K similarity cache replacing the naive Python-scan
search_similar(). Also covers fixes for: embeddings table missing from
_init_database, fusion N+1 query, and ai_provider 429 retry.
2026-03-22 02:39:20 +01:00
sgeboers bf68e48460 fix(analysis): improve PCA handling when PC1 dominates, add pca_residual option and plot autoscaling/variance annotation 2026-03-22 00:13:56 +01:00
sgeboers 23a1234314 feat(analysis): add 2D political compass (PCA/anchor) and 2D trajectories + visualizations
- compute_2d_axes (pca, anchor) with optional L2-normalisation pre-projection
- compute_2d_trajectories: per-MP coords, step vectors, magnitudes, totals
- plot_political_compass and plot_2d_trajectories (Plotly HTML)
- tests/test_political_compass.py (synthetic unit test)
2026-03-21 23:58:38 +01:00
sgeboers 3551a82f83 feat(analysis): add 2D political compass and 2D trajectories
- compute_2d_axes (PCA + anchor)
- compute_2d_trajectories
- plot_political_compass, plot_2d_trajectories
- unit test: tests/test_political_compass.py
2026-03-21 23:53:55 +01:00
sgeboers f7d806dc3a fix(analysis): add Procrustes alignment and normalize vectors for drift computation
SVD sign/rotation is arbitrary per window. Without alignment, drift was
dominated by basis flips (~1.9/step max=2.0) rather than real political movement.

- _procrustes_align_windows(): aligns each window to the previous using
  orthogonal Procrustes on common entities (scipy, falls back gracefully)
- compute_trajectories(): builds aligned window dict before per-MP drift calc,
  adds normalize=True (L2-normalise) to remove cross-window magnitude differences
  caused by varying numbers of motions per quarter
- Results now in sensible range: NSC=2.28, DENK=1.90, ... PVV=0.82, FVD=0.70
- NSC large late jump (1.39 in Q4→Q1 2026) matches its parliamentary fracture
- Add outputs/trajectories_party_aligned.html with cleaned-up drift chart
2026-03-21 23:37:20 +01:00
sgeboers aa2f66ac9f feat(analysis): fetch real MP metadata, fix anchor axis for party-level actors
- fetch_mp_metadata: use real OData URL with pagination (1200 records, 5 pages)
  uses Fractie.Afkorting not NaamNL for abbreviation matching
  skips Verwijderd=true records
- upsert_mp_metadata: keep most recent membership (prefer active over ended,
  then higher Van date) so current party affiliations are not overwritten by historical
- compute_anchor_axis: anchor directly on party-level SVD entities (GroenLinks-PvdA etc)
  before falling back to mp_metadata individual MP lookup
- test_fetch_mp_metadata: fix mock for timeout kwarg + pagination + Afkorting field
- Generated anchor axis HTML for 2025-Q2 through 2026-Q1 in outputs/
2026-03-21 23:33:47 +01:00
sgeboers 5ad83ef1be fix(tests): update test_extract_mp_votes for party-level actor inclusion
New extract_mp_votes behavior inserts all actors (party + individual MPs),
not only comma-name MPs. Test now validates both types and their party column.
Also adds generated HTML visualizations (political axis x5 windows + trajectories).
2026-03-21 23:25:27 +01:00
sgeboers 847b783877 fix(pipeline): fix API pagination, add skip_details fast path, bulk mp_votes insert
- _get_voting_records returns (records, besluit_meta) tuple; paginate via Besluit?expand=Stemming (469/mo vs 8400)
- get_motions(skip_details=True) bypasses per-motion detail chain (3 HTTP calls/motion)
- extract_mp_votes rewritten: bulk DataFrame insert (80k rows in 1.9s), includes party-level actors
- run_pipeline.py fixed: pass db_path not db, handle dict/int return types
- download_past_year.py: skip_details=True default, limit-per-chunk default 50000
2026-03-21 23:24:06 +01:00
sgeboers f2a831dfcf feat(pipeline): add orchestrator CLI, analysis modules, and ActorFractie ingestion
- pipeline/run_pipeline.py: CLI orchestrator for all 5 pipeline phases with
  --dry-run, --skip-*, --window-size, --svd-k, --start/end-date flags
- analysis/{political_axis,trajectory,clustering,visualize}.py: PCA/anchor
  ideological axis, MP drift trajectories, UMAP + KMeans clustering, Plotly HTML output
- api_client.py: capture ActorFractie per individual MP vote (comma in ActorNaam)
  into mp_vote_parties dict on each motion
- database.insert_motion: auto-insert mp_votes rows with party affiliation for
  newly ingested motions when mp_vote_parties is present
- Add scikit-learn to pyproject.toml for KMeans clustering
- tests/test_run_pipeline.py: window generation, dry-run, skip-all paths
- tests/test_analysis.py: PCA axis, anchor axis, trajectory drift, KMeans

Ref: thoughts/shared/plans/2026-03-21-parliamentary-embedding-pipeline-plan.md
2026-03-21 22:40:28 +01:00
sgeboers a36e6cba4e feat(pipeline): implement parliamentary embedding pipeline MVP
- Add 4 migration files: mp_votes, mp_metadata, svd_vectors, fused_embeddings
- Extend database.py with 5 new helper methods and table init
- Add pipeline/ package: extract_mp_votes, fetch_mp_metadata, text_pipeline,
  svd_pipeline (with Procrustes alignment), fusion
- Add full test suite (17 tests) covering all pipeline modules and migrations
- Fix Procrustes alignment bug: scipy scale is a norm value, not a multiplier
- Fix DuckDB date type handling in test assertions (datetime.date vs string)
- Remove duckdb.py shim; tests now run against real duckdb + scipy via uv

Ref: thoughts/shared/plans/2026-03-21-parliamentary-embedding-pipeline-plan.md
2026-03-21 22:31:22 +01:00
sgeboers c498c3467e update plan: replace spike with confirmed FractieZetelPersoon fetch task 2026-03-21 22:05:56 +01:00
sgeboers 0bbda408fb plan: parliamentary embedding pipeline MVP implementation plan 2026-03-21 21:57:39 +01:00
sgeboers fd73da3752 design: parliamentary embedding pipeline (late fusion SVD + text) 2026-03-21 21:54:32 +01:00