--- title: Visualize and Report Migration-Anti-Democratic Overlap Findings type: feat status: active date: 2026-05-08 --- # Visualize and Report Migration-Anti-Democratic Overlap Findings ## Summary Turn the Direction 3 analysis (migration as the dominant vehicle for anti-democratic rhetoric in right-wing motions) into a publishable artifact: committed script, visualizations, mechanism-coded extreme motions, and a findings report. --- ## Problem Frame The Direction 3 analysis script `analysis/right_wing/direction3_migration_antidemocratic.py` has been written and run successfully against all 2,986 scored right-wing motions. It uncovered strong evidence that migration is the primary carrier of anti-democratic extremity (45% of ≥4.0-scored motions are migration-related). However, the findings currently exist only as console output. They need to be preserved, visualized, deepened with mechanism coding, and compiled into a shareable report. --- ## Requirements - R1. The analysis script is committed to the feature branch. - R2. Five focused charts visualize the key findings for immediate readability. - R3. The 212 motions scoring ≥3.5 extremity are mechanism-coded via lightweight LLM batch. - R4. A markdown report compiles numbers, charts, and mechanism coding into a narrative. - R5. All artifacts are saved under `reports/right_wing_migration_antidemocratic/`. --- ## Scope Boundaries - No new LLM pipeline infrastructure (reuses existing `ai_provider.chat_completion_json_parallel` pattern from `extremity_scorer.py`). - No changes to the database schema. - No Streamlit UI integration (this is a research artifact, not a UI feature). - No anti-democratic scoring pipeline for the full 2,986 motions (that would be Direction 2, deferred). ### Deferred to Follow-Up Work - Full anti-democratic scoring pipeline (Direction 2): separate plan if mechanism coding validates the concept. - Streamlit tab to expose right-wing analysis tables: separate UI work. --- ## Context & Research ### Relevant Code and Patterns - `analysis/right_wing/direction3_migration_antidemocratic.py` — the analysis script to commit - `analysis/right_wing/extremity_scorer.py` — LLM batch pattern with `chat_completion_json_parallel` - `analysis/right_wing/derive_categories.py` — two-phase LLM approach (derive taxonomy, then apply) - `scripts/motion_drift.py` — chart generation pattern using matplotlib, saved to `reports/` - `analysis/config.py` — `CANONICAL_RIGHT`, `CANONICAL_LEFT`, `PARTY_COLOURS` ### Institutional Learnings - `docs/solutions/best-practices/svd-labels-voting-patterns-not-semantics.md` — SVD labels should reflect voting patterns, not semantic content. Same discipline applies here: mechanism coding should reflect procedural/institutional characteristics, not just topic keywords. --- ## Key Technical Decisions - **Matplotlib for charts:** The repo already uses matplotlib in `scripts/motion_drift.py` and `explorer.py`. No new charting library. - **Reuse existing LLM batch infrastructure:** The mechanism coder will call `ai_provider.chat_completion_json_parallel` with a JSON schema, same pattern as extremity/sentiment scoring. Batch size 10, ~22 batches for 212 motions. - **Mechanism taxonomy (6 classes):** punitive, exclusionary, sovereignty-claiming, procedural-breaking, institutional-dismantling, none. Chosen to capture the observed framing evolution from "keep them out" to "dismantle the system that lets them in." - **Reports directory:** `reports/right_wing_migration_antidemocratic/` mirrors the `reports/drift/` pattern. --- ## Open Questions ### Resolved During Planning - **Q: Which visualization library?** Matplotlib — already used in the repo. - **Q: How many mechanisms to code?** 6-class taxonomy, validated against the 5.0-scored motion sample. ### Deferred to Implementation - **Q: Exact chart styling and color palette** — depends on what looks good when rendered; iterate visually. - **Q: Whether to include migration-adjacent motions in the mechanism coding** — start with pure migration (404), expand to adjacent (55) if time permits. --- ## Implementation Units - U1. **Commit Direction 3 Analysis Script** **Goal:** Save the working analysis script to the feature branch. **Requirements:** R1 **Dependencies:** None **Files:** - Create: `analysis/right_wing/direction3_migration_antidemocratic.py` **Approach:** - The script already exists and produces correct output. Stage and commit it with a conventional message. **Test scenarios:** - Test expectation: none — this is a standalone analysis script with no testable behavior changes. **Verification:** - Script is committed to `feat/right-wing-motion-analysis` and runs without errors. --- - U2. **Generate Five Key Visualizations** **Goal:** Produce charts that make the Direction 3 findings immediately readable. **Requirements:** R2, R5 **Dependencies:** U1 **Files:** - Create: `analysis/right_wing/visualize_direction3.py` - Output: `reports/right_wing_migration_antidemocratic/*.png` **Approach:** - Write a script that queries the scored data and generates 5 charts: 1. **Stacked bar:** Category breakdown of ≥4.0 extremity motions (migration's 44.8% dominance) 2. **Line chart:** Migration motion volume + avg extremity by year (2018–2026) 3. **Horizontal bar:** Party avg extremity on migration (Wilders 3.79, Eerdmans 2.69, etc.) 4. **Grouped bar:** Migration sentiment by extremity bucket (the −0.717 drop) 5. **Treemap or stacked bar:** Migration + migration-adjacent by category (15.4% total footprint) - Follow the matplotlib patterns from `scripts/motion_drift.py` and `explorer.py`. - Save charts to `reports/right_wing_migration_antidemocratic/`. **Patterns to follow:** - `scripts/motion_drift.py` — chart saving with `plt.savefig()`, DPI settings - `explorer.py` — color palette using `analysis/config.PARTY_COLOURS` **Test scenarios:** - Happy path: script runs and produces 5 PNG files in the reports directory. - Edge case: empty result set for a chart component should render gracefully (e.g., skip treemap segment with zero count). **Verification:** - 5 PNG files exist in `reports/right_wing_migration_antidemocratic/` and render legible charts. --- - U3. **Mechanism-Code the 212 Extreme Motions** **Goal:** Classify the anti-democratic mechanism for each motion scoring ≥3.5 extremity. **Requirements:** R3 **Dependencies:** U1 **Files:** - Create: `analysis/right_wing/mechanism_coder.py` - Create: `data/mechanism_codes.json` (or table: `right_wing_mechanisms`) **Approach:** - Query the 212 motion IDs with title, year, and category from `right_wing_motions` + `extremity_scores`. - Use `ai_provider.chat_completion_json_parallel` with a JSON schema: ```json {"mechanism": "punitive|exclusionary|sovereignty-claiming|procedural-breaking|institutional-dismantling|none", "confidence": 1-5, "rationale": "string"} ``` - Batch size 10, ~22 batches. Skip already-coded motions for resumability. - Store results in a new table `right_wing_mechanisms` (motion_id, mechanism, confidence, rationale) with `CREATE TABLE IF NOT EXISTS` + `INSERT OR REPLACE`. **Execution note:** Start with a small validation batch (10 motions) and spot-check the taxonomy before running the full 212. **Patterns to follow:** - `analysis/right_wing/extremity_scorer.py` — batch loop, `chat_completion_json_parallel`, resumability pattern - `analysis/right_wing/derive_categories.py` — two-phase validation (small sample first, then apply) **Test scenarios:** - Happy path: all 212 motions are coded with a valid mechanism and confidence 1–5. - Edge case: LLM returns invalid JSON → retry or log error, don't crash. - Edge case: script interrupted mid-batch → rerunning skips already-coded motions. - Integration: mechanism table can be joined back to `right_wing_motions` for reporting. **Verification:** - `right_wing_mechanisms` table has 212 rows with no NULL mechanisms. - Spot-check 10 random rows: rationale is coherent and mechanism assignment is defensible. --- - U4. **Compile Findings Report** **Goal:** Write a markdown narrative pulling together numbers, charts, and mechanism coding. **Requirements:** R4, R5 **Dependencies:** U2, U3 **Files:** - Create: `reports/right_wing_migration_antidemocratic/findings_report.md` **Approach:** - Structure the report around the 5 analytical sections from Direction 3: 1. Overlap quantification (migration's 44.8% share of high extremity) 2. Party strategy (PVV/Wilders dominance, JA21 volume-vs-intensity tradeoff) 3. Framing shift (2018–2020 direct exclusion → 2023–2025 institutional dismantling) 4. Cross-category adjacency (55 migration-adjacent motions, veiligheid/justitie as primary spillover) 5. Sentiment divergence (migration as the only negative-sentiment category, −0.717 at high extremity) - Embed the 5 charts using relative paths. - Include a mechanism-coding summary table (distribution of the 6 mechanisms across categories and parties). - Conclude with the bottom-line hypothesis confirmation. **Patterns to follow:** - `reports/drift/report.md` — existing report format in the repo **Test scenarios:** - Happy path: report renders correctly in a markdown viewer with all images loading. - Edge case: report references charts that don't exist → verify all image paths are valid. **Verification:** - `reports/right_wing_migration_antidemocratic/findings_report.md` exists and is a complete, readable narrative. - All chart references resolve to existing PNG files. --- ## System-Wide Impact - **Interaction graph:** None — this is a research artifact pipeline. No callbacks, middleware, or UI changes. - **Error propagation:** LLM batch failures in U3 should log errors per-batch, not crash the script. - **State lifecycle risks:** The `right_wing_mechanisms` table is idempotent (INSERT OR REPLACE). Rerunning is safe. - **API surface parity:** No public API changes. - **Integration coverage:** U3's integration with the LLM provider is the only cross-layer concern; verify the batch loop handles rate limits gracefully. - **Unchanged invariants:** Database schema, existing right-wing analysis tables, Streamlit UI, and agent_tools surface remain untouched. --- ## Risks & Dependencies | Risk | Mitigation | |------|------------| | LLM API costs for 212 motions exceed budget | Validate on 10-motion sample first; abort if cost-per-motion is unexpectedly high. | | Mechanism taxonomy doesn't map cleanly to the data | Start with 6 classes; if LLM struggles, collapse to 4 or add an "other" catch-all. | | Charts look unprofessional or cluttered | Iterate on 1–2 charts first, get feedback, then apply style to all 5. | | Report becomes too long or unfocused | Cap at ~2,000 words; use summary tables and embedded charts to carry the narrative. | --- ## Documentation / Operational Notes - The report is intended for internal research consumption and potential external sharing. No operational rollout needed. - If the mechanism coding validates the anti-democratic concept, it becomes the evidence base for a future Direction 2 plan (full anti-democratic scoring pipeline). --- ## Sources & References - Origin analysis: `analysis/right_wing/direction3_migration_antidemocratic.py` - Related plan: `docs/plans/2026-05-05-001-feat-right-wing-motion-analysis-plan.md` - Chart patterns: `scripts/motion_drift.py`, `explorer.py` - LLM batch patterns: `analysis/right_wing/extremity_scorer.py`, `analysis/right_wing/derive_categories.py`