feat(overton): add category domain decomposition with interactive charts and TDD tests

Populated the right_wing_motions.category column (previously 100% NULL across
3,030 motions) via parallel subagent classification — 80 agents derived a
10-category taxonomy and classified all motions in minutes.

Adds to the Overton QMD report:
- Plotly dropdown filter on Chart 1 to toggle between policy categories
- Chart 7: category delta bar chart (pre/post centrist support per domain)
- Chart 8: quarterly domain trajectories for the 5 largest categories
- Domain Decomposition narrative section

Also fixes a Streamlit tab crash (m.text -> m.body_text) and adds TDD tests.
This commit is contained in:
2026-06-15 21:49:37 +02:00
parent a5624f65bc
commit 19e8d5b8ba
24 changed files with 1296 additions and 140 deletions
+1 -1
View File
@@ -108,7 +108,7 @@ Three tiers:
- [ ] Mechanism taxonomy revision (κ=0.41 → improve agreement)
- [ ] Forward-looking scenario analysis (permanent vs temporary shift)
- [ ] Anti-institutional pivot deep-dive (abolition → contestation)
- [ ] Re-populate category column in right_wing_motions (wiped by DROP TABLE)
- [x] Re-populate category column in right_wing_motions (wiped by DROP TABLE)
### Presentation
- [ ] Quarto blog post with interactive charts
+22 -24
View File
@@ -1,7 +1,5 @@
# Overton Window Breakpoint Analysis (2D Extremity)
> **Part of the Overton Window Analysis.** See the [synthesis report](overton_window_synthesis.md) for the integrated narrative, or the [interactive article](overton_window.qmd) for the full story with charts.
**Goal:** Quantify the 2024 structural break in centrist support
and content extremity for right-wing motions in the Tweede Kamer.
@@ -73,8 +71,8 @@ Migration = category `asiel/vreemdelingen`. Non-migration = all other categories
| Domain | Pre-2024 Mean CS | Post-2024 Mean CS | Δ CS |
|--------|-----------------|------------------|------|
| Migration | nan | nan | +nan |
| Non-migration | 0.425 | 0.482 | +0.057 |
| Migration | 0.125 | 0.343 | +0.218 |
| Non-migration | 0.436 | 0.509 | +0.073 |
## 5. Material Impact-Stratified Centrist Support
@@ -161,26 +159,26 @@ If both rose equally, a systemic factor (coalition change, polarization) is at w
| # | Year | Category | Stijl | Materieel | Bucket | Agreed? | Driver |
|---|------|----------|-------|-----------|--------|---------|--------|
| 1 | 2021 | other | 1 | 1 | 1-2 (mild) | | |
| 2 | 2020 | other | 1 | 1 | 1-2 (mild) | | |
| 3 | 2023 | other | 1 | 1 | 1-2 (mild) | | |
| 4 | 2023 | other | 3 | 1 | 1-2 (mild) | | |
| 5 | 2022 | other | 1 | 1 | 1-2 (mild) | | |
| 6 | 2021 | other | 1 | 2 | 2-3 (moderate) | | |
| 7 | 2020 | other | 1 | 2 | 2-3 (moderate) | | |
| 8 | 2020 | other | 2 | 2 | 2-3 (moderate) | | |
| 9 | 2025 | other | 1 | 2 | 2-3 (moderate) | | |
| 10 | 2019 | other | 2 | 2 | 2-3 (moderate) | | |
| 11 | 2019 | other | 3 | 3 | 3-4 (high) | | |
| 12 | 2020 | other | 3 | 3 | 3-4 (high) | | |
| 13 | 2020 | other | 2 | 3 | 3-4 (high) | | |
| 14 | 2020 | other | 4 | 3 | 3-4 (high) | | |
| 15 | 2022 | other | 2 | 3 | 3-4 (high) | | |
| 16 | 2025 | other | 2 | 4 | 4-5 (extreme) | | |
| 17 | 2021 | other | 2 | 4 | 4-5 (extreme) | | |
| 18 | 2025 | other | 3 | 4 | 4-5 (extreme) | | |
| 19 | 2023 | other | 2 | 4 | 4-5 (extreme) | | |
| 20 | 2019 | other | 2 | 5 | 4-5 (extreme) | | |
| 1 | 2021 | landbouw/natuur | 1 | 1 | 1-2 (mild) | | |
| 2 | 2020 | veiligheid/justitie | 1 | 1 | 1-2 (mild) | | |
| 3 | 2023 | veiligheid/justitie | 1 | 1 | 1-2 (mild) | | |
| 4 | 2023 | overig | 3 | 1 | 1-2 (mild) | | |
| 5 | 2022 | verkeer/infrastructuur | 1 | 1 | 1-2 (mild) | | |
| 6 | 2021 | landbouw/natuur | 1 | 2 | 2-3 (moderate) | | |
| 7 | 2020 | veiligheid/justitie | 1 | 2 | 2-3 (moderate) | | |
| 8 | 2020 | buitenland/europa | 2 | 2 | 2-3 (moderate) | | |
| 9 | 2025 | energie/klimaat | 1 | 2 | 2-3 (moderate) | | |
| 10 | 2019 | buitenland/europa | 2 | 2 | 2-3 (moderate) | | |
| 11 | 2019 | overig | 3 | 3 | 3-4 (high) | | |
| 12 | 2020 | zorg/gezondheid | 3 | 3 | 3-4 (high) | | |
| 13 | 2020 | buitenland/europa | 2 | 3 | 3-4 (high) | | |
| 14 | 2020 | verkeer/infrastructuur | 4 | 3 | 3-4 (high) | | |
| 15 | 2022 | energie/klimaat | 2 | 3 | 3-4 (high) | | |
| 16 | 2025 | asiel/vreemdelingen | 2 | 4 | 4-5 (extreme) | | |
| 17 | 2021 | zorg/gezondheid | 2 | 4 | 4-5 (extreme) | | |
| 18 | 2025 | asiel/vreemdelingen | 3 | 4 | 4-5 (extreme) | | |
| 19 | 2023 | economie | 2 | 4 | 4-5 (extreme) | | |
| 20 | 2019 | veiligheid/justitie | 2 | 5 | 4-5 (extreme) | | |
## 10. Limitations
Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 152 KiB

-2
View File
@@ -1,7 +1,5 @@
# Causal Timing: Centrist Support Shift for Right-Wing Motions
> **Part of the Overton Window Analysis.** See the [synthesis report](overton_window_synthesis.md) for the integrated narrative, or the [interactive article](overton_window.qmd) for the full story with charts.
**Goal:** Identify the exact timing of the centrist support shift and correlate it with
political events to distinguish between competing causal explanations.
+28 -22
View File
@@ -1,7 +1,5 @@
# 2D Extremity Temporal Decomposition
> **Part of the Overton Window Analysis.** See the [synthesis report](overton_window_synthesis.md) for the integrated narrative, or the [interactive article](overton_window.qmd) for the full story with charts.
**Goal:** Test whether the "flat single-dimension trend" masks diverging trajectories
when stylistic and material extremity scores are analyzed separately over time.
@@ -17,8 +15,8 @@ when stylistic and material extremity scores are analyzed separately over time.
## 1. Key Findings
**Overall correlation r(stijl, materieel):** 0.472 (p=0.000000)
**Migration domain r(stijl, materieel):** N/A (p=N/A, n=0)
**Non-migration domain r(stijl, materieel):** 0.472 (p=0.000000, n=3030)
**Migration domain r(stijl, materieel):** 0.471 (p=0.000000, n=423)
**Non-migration domain r(stijl, materieel):** 0.424 (p=0.000000, n=2607)
---
@@ -40,14 +38,14 @@ when stylistic and material extremity scores are analyzed separately over time.
| 2016 * | 6 | 1.667 | 2.333 | 2.000 | 0.667 | 0 | N/A | N/A | 6 | 1.667 | 2.333 | N/A |
| 2017 * | 0 | N/A | N/A | N/A | N/A | 0 | N/A | N/A | 0 | N/A | N/A | N/A |
| 2018 * | 5 | 1.000 | 1.400 | 1.400 | 0.400 | 0 | N/A | N/A | 5 | 1.000 | 1.400 | N/A |
| 2019 | 195 | 2.046 | 2.913 | 2.138 | 0.867 | 0 | N/A | N/A | 195 | 2.046 | 2.913 | 0.491 |
| 2020 | 469 | 2.228 | 2.906 | 2.262 | 0.678 | 0 | N/A | N/A | 469 | 2.228 | 2.906 | 0.603 |
| 2021 | 425 | 1.755 | 2.976 | 2.240 | 1.221 | 0 | N/A | N/A | 425 | 1.755 | 2.976 | 0.503 |
| 2022 | 446 | 1.800 | 2.525 | 2.161 | 0.724 | 0 | N/A | N/A | 446 | 1.800 | 2.525 | 0.429 |
| 2023 | 365 | 1.575 | 2.690 | 2.238 | 1.115 | 0 | N/A | N/A | 365 | 1.575 | 2.690 | 0.346 |
| 2024 | 469 | 1.680 | 2.582 | 1.985 | 0.902 | 0 | N/A | N/A | 469 | 1.680 | 2.582 | 0.391 |
| 2025 | 455 | 1.695 | 2.332 | 2.253 | 0.637 | 0 | N/A | N/A | 455 | 1.695 | 2.332 | 0.592 |
| 2026 | 195 | 2.015 | 2.405 | 2.331 | 0.390 | 0 | N/A | N/A | 195 | 2.015 | 2.405 | 0.335 |
| 2019 | 195 | 2.046 | 2.913 | 2.138 | 0.867 | 17 | 2.941 | 2.824 | 178 | 1.961 | 2.921 | 0.491 |
| 2020 | 469 | 2.228 | 2.906 | 2.262 | 0.678 | 36 | 3.389 | 3.472 | 433 | 2.132 | 2.859 | 0.603 |
| 2021 | 425 | 1.755 | 2.976 | 2.240 | 1.221 | 30 | 2.967 | 3.833 | 395 | 1.663 | 2.911 | 0.503 |
| 2022 | 446 | 1.800 | 2.525 | 2.161 | 0.724 | 77 | 2.260 | 3.026 | 369 | 1.705 | 2.420 | 0.429 |
| 2023 | 365 | 1.575 | 2.690 | 2.238 | 1.115 | 65 | 2.231 | 3.308 | 300 | 1.433 | 2.557 | 0.346 |
| 2024 | 469 | 1.680 | 2.582 | 1.985 | 0.902 | 63 | 2.571 | 3.175 | 406 | 1.542 | 2.490 | 0.391 |
| 2025 | 455 | 1.695 | 2.332 | 2.253 | 0.637 | 94 | 2.543 | 3.277 | 361 | 1.474 | 2.086 | 0.592 |
| 2026 | 195 | 2.015 | 2.405 | 2.331 | 0.390 | 41 | 2.707 | 2.927 | 154 | 1.831 | 2.266 | 0.335 |
> * Years with <50 scored motions; confidence intervals are wider or N/A.
@@ -80,21 +78,29 @@ and material extremity.
| 2017 | N/A | N/A | 0 | All |
| 2018 | N/A | N/A | 5 | All |
| 2019 | 0.491 | 0.000000 | 195 | All |
| | 0.491 | 0.000000 | 195 | Non-migration |
| | 0.769 | 0.000313 | 17 | Migration |
| | 0.500 | 0.000000 | 178 | Non-migration |
| 2020 | 0.603 | 0.000000 | 469 | All |
| | 0.603 | 0.000000 | 469 | Non-migration |
| | 0.352 | 0.035486 | 36 | Migration |
| | 0.604 | 0.000000 | 433 | Non-migration |
| 2021 | 0.503 | 0.000000 | 425 | All |
| | 0.503 | 0.000000 | 425 | Non-migration |
| | 0.600 | 0.000460 | 30 | Migration |
| | 0.455 | 0.000000 | 395 | Non-migration |
| 2022 | 0.429 | 0.000000 | 446 | All |
| | 0.429 | 0.000000 | 446 | Non-migration |
| | 0.557 | 0.000000 | 77 | Migration |
| | 0.327 | 0.000000 | 369 | Non-migration |
| 2023 | 0.346 | 0.000000 | 365 | All |
| | 0.346 | 0.000000 | 365 | Non-migration |
| | 0.487 | 0.000039 | 65 | Migration |
| | 0.218 | 0.000146 | 300 | Non-migration |
| 2024 | 0.391 | 0.000000 | 469 | All |
| | 0.391 | 0.000000 | 469 | Non-migration |
| | 0.155 | 0.224438 | 63 | Migration |
| | 0.355 | 0.000000 | 406 | Non-migration |
| 2025 | 0.592 | 0.000000 | 455 | All |
| | 0.592 | 0.000000 | 455 | Non-migration |
| | 0.561 | 0.000000 | 94 | Migration |
| | 0.378 | 0.000000 | 361 | Non-migration |
| 2026 | 0.335 | 0.000002 | 195 | All |
| | 0.335 | 0.000002 | 195 | Non-migration |
| | 0.536 | 0.000308 | 41 | Migration |
| | 0.098 | 0.227990 | 154 | Non-migration |
---
@@ -137,8 +143,8 @@ trend is an accurate summary (no masked divergence).
| Domain | Pre Mean Stijl | Pre Mean Mat | Post Mean Stijl | Post Mean Mat | Pre Gap | Post Gap | Pre r | Post r |
|--------|---------------|-------------|----------------|---------------|---------|----------|-------|--------|
| Migration | N/A | N/A | N/A | N/A | N/A | N/A | N/A | N/A |
| Non-migration | 1.725 | 2.535 | 1.797 | 2.440 | 0.810 | 0.643 | 0.475 | 0.439 |
| Migration | 2.757 | 3.293 | 2.607 | 3.126 | 0.535 | 0.519 | 0.553 | 0.417 |
| Non-migration | 1.651 | 2.486 | 1.616 | 2.281 | 0.835 | 0.665 | 0.421 | 0.277 |
---
Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 KiB

After

Width:  |  Height:  |  Size: 439 KiB

+4 -6
View File
@@ -1,7 +1,5 @@
# Left-Wing Response to Right-Wing Motions
> **Part of the Overton Window Analysis.** See the [synthesis report](overton_window_synthesis.md) for the integrated narrative, or the [interactive article](overton_window.qmd) for the full story with charts.
**Goal:** Determine whether the centrist support surge reflects right-wing
moderation, centrist acceptance, or left-wing opposition hardening.
@@ -83,10 +81,10 @@ Non-migration = all other categories.
| Domain | Period | Left Support | Centrist Support | Gap | N |
|--------|--------|-------------|-----------------|-----|---|
| migration | Pre-2024 | N/A | N/A | N/A | 0 |
| migration | Post-2024 | N/A | N/A | N/A | 0 |
| non-migration | Pre-2024 | 0.2680 | 0.425 | +0.157 | 1911 |
| non-migration | Post-2024 | 0.2044 | 0.482 | +0.277 | 1119 |
| migration | Pre-2024 | 0.0465 | 0.125 | +0.079 | 225 |
| migration | Post-2024 | 0.0887 | 0.343 | +0.255 | 198 |
| non-migration | Pre-2024 | 0.2828 | 0.436 | +0.153 | 1686 |
| non-migration | Post-2024 | 0.2291 | 0.509 | +0.280 | 921 |
---
Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

After

Width:  |  Height:  |  Size: 278 KiB

@@ -1,7 +1,5 @@
# Mechanism Classification Report
> **Part of the Overton Window Analysis.** See the [synthesis report](overton_window_synthesis.md) for the integrated narrative, or the [interactive article](overton_window.qmd) for the full story with charts.
**Sample:** 200 motions (stratified: 50 pre-2024, 150 post-2024)
**Classified:** 200 motions | **Unclassified:** 0
+207 -9
View File
@@ -117,7 +117,7 @@ party-level axis scores within each bloc.
#| fig-cap: "Centrist Support for Right-Wing Motions Over Time (20162026)"
#| column: page
yearly = con.execute("""
yearly_agg = con.execute("""
SELECT
year,
AVG(centrist_support_strict) AS mean_cs,
@@ -128,44 +128,102 @@ yearly = con.execute("""
GROUP BY year ORDER BY year
""").fetchdf()
yearly_cat = con.execute("""
SELECT
year,
category,
AVG(centrist_support_strict) AS cs,
COUNT(*) AS n
FROM right_wing_motions
WHERE classified = TRUE AND year >= 2017 AND category IS NOT NULL
GROUP BY year, category
ORDER BY year, category
""").fetchdf()
categories_list = sorted(yearly_cat["category"].unique())
cat_colors = ["#66C2A5", "#FC8D62", "#8DA0CB", "#E78AC3", "#A6D854",
"#FFD92F", "#E5C494", "#B3B3B3", "#1E88E5", "#D81B60"]
cat_labels = {
"asiel/vreemdelingen": "Asiel & Vreemdelingen",
"landbouw/natuur": "Landbouw & Natuur",
"veiligheid/justitie": "Veiligheid & Justitie",
"zorg/gezondheid": "Zorg & Gezondheid",
"economie": "Economie",
"energie/klimaat": "Energie & Klimaat",
"buitenland/europa": "Buitenland & Europa",
"onderwijs/wetenschap": "Onderwijs & Wetenschap",
"verkeer/infrastructuur": "Verkeer & Infrastructuur",
"overig": "Overig",
}
fig1 = go.Figure()
fig1.add_trace(go.Scatter(
x=yearly["year"], y=yearly["mean_cs"],
x=yearly_agg["year"], y=yearly_agg["mean_cs"],
mode="lines+markers", name="All right-wing",
line=dict(color="#002366", width=3),
marker=dict(size=8),
error_y=dict(
type="data",
array=1.96 * yearly["std_cs"] / np.sqrt(yearly["n"]),
array=1.96 * yearly_agg["std_cs"] / np.sqrt(yearly_agg["n"]),
visible=True, thickness=0.8, width=2
)
),
))
pre = yearly[yearly["year"] < BREAK_YEAR]
post = yearly[yearly["year"] >= BREAK_YEAR]
for i, cat in enumerate(categories_list):
cat_data = yearly_cat[yearly_cat["category"] == cat]
if cat_data.empty:
continue
fig1.add_trace(go.Scatter(
x=cat_data["year"], y=cat_data["cs"],
mode="lines+markers",
name=cat_labels.get(cat, cat),
line=dict(color=cat_colors[i % len(cat_colors)], width=2, dash="dash"),
marker=dict(size=6),
visible=False,
))
pre = yearly_agg[yearly_agg["year"] < BREAK_YEAR]
post = yearly_agg[yearly_agg["year"] >= BREAK_YEAR]
fig1.add_hline(
y=pre["mean_cs"].mean(),
line_dash="dot", line_color="#90CAF9",
annotation_text=f"Pre-2024 mean ({pre['mean_cs'].mean():.3f})"
annotation_text=f"Pre-2024 mean ({pre['mean_cs'].mean():.3f})",
)
fig1.add_hline(
y=post["mean_cs"].mean(),
line_dash="dot", line_color="#1E88E5",
annotation_text=f"Post-2024 mean ({post['mean_cs'].mean():.3f})"
annotation_text=f"Post-2024 mean ({post['mean_cs'].mean():.3f})",
)
fig1.add_vline(
x=BREAK_YEAR - 0.5, line_dash="dot", line_color="black", opacity=0.5
x=BREAK_YEAR - 0.5, line_dash="dot", line_color="black", opacity=0.5,
)
buttons = [
dict(label="All right-wing", method="update",
args=[{"visible": [True] + [False] * len(categories_list)}])
]
for i, cat in enumerate(categories_list):
vis = [True] + [False] * len(categories_list)
vis[i + 1] = True
buttons.append(dict(
label=cat_labels.get(cat, cat),
method="update",
args=[{"visible": vis}],
))
fig1.update_layout(
title="Centrist Support (Strict) for Right-Wing Motions",
xaxis=dict(title="Year", dtick=1),
yaxis=dict(title="Centrist Support (fraction of parties)", range=[0, 1.1]),
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
template="plotly_white", height=450,
updatemenus=[dict(
type="dropdown", direction="down", showactive=True,
buttons=buttons, x=1.05, y=1.15,
)],
)
fig1.show()
```
@@ -258,6 +316,69 @@ specific: right-wing centrist support surged by +0.236, while non-right-wing
motions remained essentially flat (0.006). This is a right-wing-specific
phenomenon, not a general parliamentary trend.
## Domain Decomposition
Right-wing motions span ten policy categories, and the 2024 centrist support shift
was not uniform across them. Every category gained centrist support, but the magnitudes
vary dramatically — from a surge of +0.39 for climate and energy to a modest +0.08
for education and science.
Leading the shift are **energie/klimaat** (+0.392), **buitenland/europa** (+0.364),
and **economie** (+0.342) — domains where right-wing parties adopted centrist-friendly
framing and right-wing governments in other European countries provided template
policies. At the other end, **onderwijs/wetenschap** (+0.081) and **veiligheid/justitie**
(+0.138) barely moved, consistent with their roles as high-consensus domains where
the window was already wide.
```{python}
#| label: chart-7-category-delta
#| fig-cap: "Pre/Post 2024 Centrist Support Delta by Policy Category"
#| column: page
delta = con.execute("""
SELECT
category,
AVG(CASE WHEN year < 2024 THEN centrist_support_strict END) as pre_cs,
AVG(CASE WHEN year >= 2024 THEN centrist_support_strict END) as post_cs,
COUNT(*) as n
FROM right_wing_motions
WHERE classified = TRUE AND year >= 2017 AND category IS NOT NULL
GROUP BY category
ORDER BY (AVG(CASE WHEN year >= 2024 THEN centrist_support_strict END)
- AVG(CASE WHEN year < 2024 THEN centrist_support_strict END)) DESC
""").fetchdf()
delta["d"] = delta["post_cs"] - delta["pre_cs"]
fig7 = go.Figure()
fig7.add_trace(go.Bar(
x=delta["d"],
y=delta["category"],
orientation="h",
marker_color=["#2ECC71" if d > 0 else "#E74C3C" for d in delta["d"]],
text=[f"{d:.3f}" for d in delta["d"]],
textposition="outside",
cliponaxis=False,
))
fig7.update_layout(
title="Pre/Post 2024 Centrist Support Delta by Category",
xaxis=dict(title="Delta (post-2024 pre-2024)", range=[-0.05, 0.48]),
yaxis=dict(title="", autorange="reversed"),
template="plotly_white", height=450,
margin=dict(l=200),
)
fig7.show()
```
The pattern reveals a political gradient. Domains tied to European integration
(buitenland/europa) and climate action (energie/klimaat) — where center-right
governments abroad provided cover — saw the largest shifts. Domestic social domains
(zorg/gezondheid, onderwijs/wetenschap) were largely insulated. The migration domain
(asiel/vreemdelingen), central to the Overton narrative, ranked seventh with a
+0.210 delta — substantial but not exceptional. Its importance lies not in the
magnitude of the shift but in its durability: migration centrist support sustained
its gains through 2025 while non-migration domains reverted.
## Indicator 2: Spatial Divergence
If centrists are voting more with right-wing motions, one might expect
@@ -619,6 +740,83 @@ suggesting the 2024 spike was primarily an electoral shock for non-migration dom
| Gradual learning | Jump was 1.9× average quarterly — discrete, not incremental | **Less consistent with the data** |
| European contagion | No Dutch response during 20222023 European shift | **Less consistent with the data** |
```{python}
#| label: chart-8-domain-trajectories
#| fig-cap: "Quarterly Centrist Support by Domain (5 Key Categories)"
#| column: page
key_cats = ["asiel/vreemdelingen", "energie/klimaat", "buitenland/europa",
"landbouw/natuur", "economie"]
quarterly_cat = con.execute("""
SELECT
EXTRACT(YEAR FROM m.date) AS y,
CEIL(EXTRACT(MONTH FROM m.date) / 3.0) AS q,
r.category,
AVG(r.centrist_support_strict) AS cs,
COUNT(*) AS n
FROM right_wing_motions r
JOIN motions m ON r.motion_id = m.id
WHERE r.classified = TRUE AND m.date IS NOT NULL
AND r.category IN ({})
GROUP BY y, q, r.category
ORDER BY y, q
""".format(",".join(f"'{c}'" for c in key_cats))).fetchdf()
quarterly_cat["label"] = (
quarterly_cat["y"].astype(int).astype(str)
+ "-Q"
+ quarterly_cat["q"].astype(int).astype(str)
)
fig8 = go.Figure()
fig8.add_trace(go.Scatter(
x=quarterly["label"], y=quarterly["cs"],
mode="lines+markers", name="All right-wing",
line=dict(color="#002366", width=3),
marker=dict(size=6),
))
domain_colors = {
"asiel/vreemdelingen": "#D81B60",
"energie/klimaat": "#1E88E5",
"buitenland/europa": "#FF8F00",
"landbouw/natuur": "#2E7D32",
"economie": "#8E44AD",
}
for cat in key_cats:
cat_data = quarterly_cat[quarterly_cat["category"] == cat]
if cat_data.empty:
continue
fig8.add_trace(go.Scatter(
x=cat_data["label"], y=cat_data["cs"],
mode="lines+markers",
name=cat_labels.get(cat, cat),
line=dict(color=domain_colors.get(cat, "#666"), width=2, dash="dash"),
marker=dict(size=5),
))
fig8.add_shape(
type="line", x0="2024-Q1", x1="2024-Q1", y0=0, y1=1,
line=dict(dash="dot", color="red", width=1.5),
)
fig8.update_layout(
title="Quarterly Centrist Support by Domain",
xaxis=dict(
title="Quarter", tickangle=45,
tickmode="array",
tickvals=quarterly["label"][::4],
),
yaxis=dict(title="Centrist Support", range=[0, 1.0]),
template="plotly_white", height=500,
legend=dict(yanchor="top", y=0.99, xanchor="left", x=0.01),
)
fig8.show()
```
## Verdict: The Window Widened Through Moderation
**The Overton window widened: more right-wing positions became politically
@@ -1,6 +1,6 @@
# Has the Overton Window Shifted? A Synthesis
**Date:** 2026-05-26
**Date:** 2026-06-15
**Analysis period:** 2016-2026
**Data:** 29,591 motions with 2D extremity scores (`extremity_scores_all`), including 3,089 right-wing motions with dedicated 2D scores (`extremity_scores_2d`), Procrustes-aligned SVD party positions across 10 annual windows, MP-level vote records for centrist parties (D66, CDA, ChristenUnie, NSC) and left-wing parties (SP, GroenLinks-PvdA, PvdD, Volt, DENK), quarterly centrist support trajectories (33 quarters), 150-motion systematic mechanism classification
@@ -284,7 +284,7 @@ Centrist support for right-wing motions surged from 25% to 51%, while centrist s
- **Success ceiling:** The 96%+ pass rate makes pass rate an insensitive dependent variable for measuring centrist influence on legislative outcomes. The success correlation findings should be interpreted as describing a real but practically constrained relationship.
- **NSC sensitivity:** Removing NSC from the strict centrist set (leaving D66/CDA/CU) yields a nearly identical surge (+0.248 vs +0.256, Cohen's d = 0.63 vs 0.66). Only 3.1% of the reported effect is attributable to NSC inclusion.
- **Submitter parsing:** The opposition-only filter relies on parse_lead_submitter(), which fails on 20% of pre-2024 and 29% of post-2024 motions. Unparsed motions have systematically higher centrist support (0.40 pre, 0.65 post vs 0.21 pre, 0.45 post for parsed). The reported opposition-only effect (d=0.85) is likely inflated by ~0.10-0.20; the true effect is probably d≈0.65-0.75. The direction is robust but the magnitude should be interpreted conservatively.
- **Migration domain provenance:** The `category` column in `right_wing_motions` is NULL for all 3,030 classified motions (wiped by a pipeline bug). Migration vs non-migration classification relies on title keyword matching (e.g., "asiel", "migratie", "vreemdeling"), which is less reliable than the original LLM-based classification. The migration gateway finding is directionally robust but exact domain boundaries should be treated as approximate.
- **Migration domain provenance:** The `category` column in `right_wing_motions` has been repopulated via LLM-based classification (10-category taxonomy, 3,030 motions). Migration (asiel/vreemdelingen) vs non-migration splits now use the LLM-derived categories rather than title keyword matching.
- **2026-Q2 sample size:** The 2026-Q2 "bounce" (CS=0.523) is based on only 44 motions with a bimodal distribution (20 at CS=1.0, 18 at CS=0.0). The CS=1.0 motions include many consensus items (defense, infrastructure) unrelated to migration. This quarter's mean is sensitive to composition and should not be over-interpreted as evidence of a sustained trend.
### Visualization
@@ -1,7 +1,5 @@
# Right-Wing Party Differentiation
> **Part of the Overton Window Analysis.** See the [synthesis report](overton_window_synthesis.md) for the integrated narrative, or the [interactive article](overton_window.qmd) for the full story with charts.
**Goal:** Break down right-wing motion metrics by party (PVV, FVD, JA21, SGP)
to identify which party drives the moderation effect.
+34 -36
View File
@@ -1,8 +1,6 @@
# Predictive Model: Centrist Support
> **Part of the Overton Window Analysis.** See the [synthesis report](overton_window_synthesis.md) for the integrated narrative, or the [interactive article](overton_window.qmd) for the full story with charts.
**Generated:** 2026-06-06 10:39
**Generated:** 2026-06-15 21:10
## Data Summary
@@ -11,7 +9,7 @@
- High centrist support (>0.5) : 120 motions
- Low centrist support (<=0.5): 845 motions
- Class imbalance ratio: 7.0:1 (low:high)
- Features: 10
- Features: 19
## Model Performance
@@ -19,15 +17,15 @@
| Model | Accuracy | Precision | Recall | AUC-ROC |
|-------|----------|-----------|--------|---------|
| Logistic Regression | 0.725 | 0.262 | 0.667 | 0.799 |
| Random Forest | 0.839 | 0.111 | 0.042 | 0.769 |
| Logistic Regression | 0.746 | 0.302 | 0.792 | 0.791 |
| Random Forest | 0.855 | 0.400 | 0.333 | 0.805 |
### 5-Fold Cross-Validation
| Model | Mean Accuracy | Std Accuracy | Mean AUC-ROC | Std AUC-ROC |
|-------|---------------|-------------|--------------|-------------|
| Logistic Regression | 0.730 | 0.021 | 0.828 | 0.039 |
| Random Forest | 0.854 | 0.023 | 0.831 | 0.035 |
| Logistic Regression | 0.718 | 0.026 | 0.816 | 0.026 |
| Random Forest | 0.861 | 0.017 | 0.845 | 0.039 |
## Feature Importance
@@ -35,16 +33,16 @@
| Feature | Coefficient | Odds Ratio |
|---------|-------------|------------|
| `party_FVD` | -1.0534 | 0.3488 |
| `party_SGP` | 1.0354 | 2.8163 |
| `stijl_extremiteit` | -0.7955 | 0.4514 |
| `party_JA21` | 0.6673 | 1.9489 |
| `party_PVV` | -0.6524 | 0.5208 |
| `materiele_impact` | -0.5428 | 0.5811 |
| `year` | 0.4052 | 1.4996 |
| `is_opposition` | -0.3080 | 0.7349 |
| `text_length` | 0.1133 | 1.1200 |
| `cat_overig` | -0.0031 | 0.9969 |
| `party_FVD` | -0.9773 | 0.3763 |
| `cat_zorg/gezondheid` | -0.9527 | 0.3857 |
| `party_JA21` | 0.8807 | 2.4127 |
| `party_SGP` | 0.8254 | 2.2828 |
| `cat_economie` | 0.7537 | 2.1248 |
| `party_PVV` | -0.7346 | 0.4797 |
| `stijl_extremiteit` | -0.7192 | 0.4871 |
| `materiele_impact` | -0.6077 | 0.5446 |
| `cat_landbouw/natuur` | 0.5100 | 1.6654 |
| `cat_onderwijs/wetenschap` | 0.4733 | 1.6052 |
*Positive coefficient = higher feature value increases odds of high centrist support.*
@@ -52,33 +50,33 @@
| Feature | Importance (Gini) |
|---------|-------------------|
| `text_length` | 0.3287 |
| `year` | 0.2176 |
| `stijl_extremiteit` | 0.1893 |
| `materiele_impact` | 0.1147 |
| `text_length` | 0.2241 |
| `year` | 0.1866 |
| `stijl_extremiteit` | 0.1684 |
| `materiele_impact` | 0.1007 |
| `party_SGP` | 0.0508 |
| `party_FVD` | 0.0360 |
| `party_PVV` | 0.0298 |
| `party_JA21` | 0.0200 |
| `is_opposition` | 0.0132 |
| `cat_overig` | 0.0000 |
| `party_PVV` | 0.0381 |
| `party_FVD` | 0.0366 |
| `cat_veiligheid/justitie` | 0.0310 |
| `cat_buitenland/europa` | 0.0256 |
| `party_JA21` | 0.0215 |
## Interpretation
### Top 5 Most Important Features
**Logistic Regression (coefficient magnitude):**
1. `party_FVD` (coef=-1.0534, OR=0.3488) — decreases odds of high centrist support
2. `party_SGP` (coef=1.0354, OR=2.8163) — increases odds of high centrist support
3. `stijl_extremiteit` (coef=-0.7955, OR=0.4514) — decreases odds of high centrist support
4. `party_JA21` (coef=0.6673, OR=1.9489) — increases odds of high centrist support
5. `party_PVV` (coef=-0.6524, OR=0.5208) — decreases odds of high centrist support
1. `party_FVD` (coef=-0.9773, OR=0.3763) — decreases odds of high centrist support
2. `cat_zorg/gezondheid` (coef=-0.9527, OR=0.3857) — decreases odds of high centrist support
3. `party_JA21` (coef=0.8807, OR=2.4127) — increases odds of high centrist support
4. `party_SGP` (coef=0.8254, OR=2.2828) — increases odds of high centrist support
5. `cat_economie` (coef=0.7537, OR=2.1248) — increases odds of high centrist support
**Random Forest (Gini importance):**
1. `text_length` (importance=0.3287)
2. `year` (importance=0.2176)
3. `stijl_extremiteit` (importance=0.1893)
4. `materiele_impact` (importance=0.1147)
1. `text_length` (importance=0.2241)
2. `year` (importance=0.1866)
3. `stijl_extremiteit` (importance=0.1684)
4. `materiele_impact` (importance=0.1007)
5. `party_SGP` (importance=0.0508)
### Which features best predict centrist support?
Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

@@ -1,7 +1,5 @@
# Motion Success Correlation Analysis
> **Part of the Overton Window Analysis.** See the [synthesis report](overton_window_synthesis.md) for the integrated narrative, or the [interactive article](overton_window.qmd) for the full story with charts.
**Goal:** Test whether motions with high centrist support actually passed at higher rates,
validating that centrist support translates to legislative success.
+27 -29
View File
@@ -1,7 +1,5 @@
# Temporal Trajectory: Centrist Support for Right-Wing Motions
> **Part of the Overton Window Analysis.** See the [synthesis report](overton_window_synthesis.md) for the integrated narrative, or the [interactive article](overton_window.qmd) for the full story with charts.
**Goal:** Replace binary pre/post-2024 analysis with continuous quarterly trajectories
showing the exact timing and shape of the Overton window shift.
@@ -81,34 +79,34 @@ the new political reality, not as a response to coalition dynamics.
| 2018-Q4 | 4 | 1.000 | N/A | N/A | 0 | N/A | 0 | N/A | 4 | 1.000 | 0.938 |
| 2019-Q1 | 1 | 0.000 | N/A | N/A | 0 | N/A | 0 | N/A | 1 | 0.000 | 0.833 |
| 2019-Q2 | 4 | 0.500 | N/A | N/A | 2 | 0.000 | 0 | N/A | 4 | 0.500 | 0.667 |
| 2019-Q3 | 25 | 0.300 | 0.160 | 0.460 | 17 | 0.176 | 0 | N/A | 25 | 0.300 | 0.317 |
| 2019-Q4 | 165 | 0.391 | 0.333 | 0.455 | 86 | 0.181 | 0 | N/A | 165 | 0.391 | 0.382 |
| 2020-Q1 | 79 | 0.278 | 0.190 | 0.367 | 45 | 0.100 | 0 | N/A | 79 | 0.278 | 0.350 |
| 2020-Q2 | 130 | 0.258 | 0.188 | 0.323 | 87 | 0.086 | 0 | N/A | 130 | 0.258 | 0.321 |
| 2020-Q3 | 78 | 0.167 | 0.102 | 0.237 | 57 | 0.088 | 0 | N/A | 78 | 0.167 | 0.239 |
| 2020-Q4 | 182 | 0.396 | 0.338 | 0.462 | 98 | 0.204 | 0 | N/A | 182 | 0.396 | 0.304 |
| 2021-Q1 | 90 | 0.150 | 0.083 | 0.222 | 65 | 0.015 | 0 | N/A | 90 | 0.150 | 0.281 |
| 2021-Q2 | 104 | 0.139 | 0.091 | 0.197 | 84 | 0.065 | 0 | N/A | 104 | 0.139 | 0.266 |
| 2021-Q3 | 68 | 0.167 | 0.105 | 0.228 | 54 | 0.127 | 0 | N/A | 68 | 0.167 | 0.150 |
| 2021-Q4 | 163 | 0.215 | 0.163 | 0.273 | 119 | 0.155 | 0 | N/A | 163 | 0.215 | 0.182 |
| 2019-Q3 | 25 | 0.300 | 0.160 | 0.460 | 17 | 0.176 | 3 | 0.167 | 22 | 0.318 | 0.317 |
| 2019-Q4 | 165 | 0.391 | 0.333 | 0.455 | 86 | 0.181 | 14 | 0.143 | 151 | 0.414 | 0.382 |
| 2020-Q1 | 79 | 0.278 | 0.190 | 0.367 | 45 | 0.100 | 13 | 0.000 | 66 | 0.333 | 0.350 |
| 2020-Q2 | 130 | 0.258 | 0.188 | 0.323 | 87 | 0.086 | 9 | 0.278 | 121 | 0.256 | 0.321 |
| 2020-Q3 | 78 | 0.167 | 0.102 | 0.237 | 57 | 0.088 | 4 | 0.000 | 74 | 0.176 | 0.239 |
| 2020-Q4 | 182 | 0.396 | 0.338 | 0.462 | 98 | 0.204 | 10 | 0.150 | 172 | 0.410 | 0.304 |
| 2021-Q1 | 90 | 0.150 | 0.083 | 0.222 | 65 | 0.015 | 1 | 0.000 | 89 | 0.152 | 0.281 |
| 2021-Q2 | 104 | 0.139 | 0.091 | 0.197 | 84 | 0.065 | 9 | 0.000 | 95 | 0.153 | 0.266 |
| 2021-Q3 | 68 | 0.167 | 0.105 | 0.228 | 54 | 0.127 | 8 | 0.125 | 60 | 0.173 | 0.150 |
| 2021-Q4 | 163 | 0.215 | 0.163 | 0.273 | 119 | 0.155 | 12 | 0.042 | 151 | 0.228 | 0.182 |
| 2022-Q1 | 15 | 0.067 | 0.000 | 0.167 | 13 | 0.038 | 0 | N/A | 15 | 0.067 | 0.193 |
| 2022-Q2 | 119 | 0.214 | 0.147 | 0.282 | 84 | 0.077 | 0 | N/A | 119 | 0.214 | 0.207 |
| 2022-Q3 | 83 | 0.133 | 0.072 | 0.199 | 71 | 0.063 | 0 | N/A | 83 | 0.133 | 0.173 |
| 2022-Q4 | 229 | 0.227 | 0.183 | 0.273 | 159 | 0.148 | 0 | N/A | 229 | 0.227 | 0.205 |
| 2023-Q1 | 77 | 0.148 | 0.091 | 0.213 | 56 | 0.107 | 0 | N/A | 77 | 0.148 | 0.191 |
| 2023-Q2 | 90 | 0.306 | 0.233 | 0.389 | 58 | 0.190 | 0 | N/A | 90 | 0.306 | 0.230 |
| 2023-Q3 | 68 | 0.184 | 0.110 | 0.257 | 53 | 0.104 | 0 | N/A | 68 | 0.184 | 0.219 |
| 2023-Q4 | 130 | 0.321 | 0.260 | 0.383 | 87 | 0.262 | 0 | N/A | 130 | 0.321 | 0.284 |
| 2024-Q1 | 98 | 0.501 | 0.429 | 0.571 | 40 | 0.358 | 0 | N/A | 98 | 0.501 | 0.349 |
| 2024-Q2 | 124 | 0.573 | 0.505 | 0.640 | 45 | 0.504 | 0 | N/A | 124 | 0.573 | 0.460 |
| 2024-Q3 | 17 | 0.588 | 0.412 | 0.765 | 7 | 0.476 | 0 | N/A | 17 | 0.588 | 0.544 |
| 2024-Q4 | 230 | 0.648 | 0.601 | 0.695 | 89 | 0.509 | 0 | N/A | 230 | 0.648 | 0.620 |
| 2025-Q1 | 29 | 0.598 | 0.448 | 0.747 | 12 | 0.778 | 0 | N/A | 29 | 0.598 | 0.639 |
| 2025-Q2 | 165 | 0.503 | 0.442 | 0.562 | 60 | 0.483 | 0 | N/A | 165 | 0.503 | 0.588 |
| 2025-Q3 | 155 | 0.437 | 0.370 | 0.503 | 48 | 0.333 | 0 | N/A | 155 | 0.437 | 0.481 |
| 2025-Q4 | 106 | 0.450 | 0.373 | 0.532 | 35 | 0.416 | 0 | N/A | 106 | 0.450 | 0.466 |
| 2026-Q1 | 151 | 0.334 | 0.265 | 0.400 | 69 | 0.325 | 0 | N/A | 151 | 0.334 | 0.402 |
| 2026-Q2 | 44 | 0.523 | 0.386 | 0.670 | 0 | N/A | 0 | N/A | 44 | 0.523 | 0.402 |
| 2022-Q2 | 119 | 0.214 | 0.147 | 0.282 | 84 | 0.077 | 22 | 0.045 | 97 | 0.253 | 0.207 |
| 2022-Q3 | 83 | 0.133 | 0.072 | 0.199 | 71 | 0.063 | 26 | 0.077 | 57 | 0.158 | 0.173 |
| 2022-Q4 | 229 | 0.227 | 0.183 | 0.273 | 159 | 0.148 | 29 | 0.241 | 200 | 0.225 | 0.205 |
| 2023-Q1 | 77 | 0.148 | 0.091 | 0.213 | 56 | 0.107 | 9 | 0.056 | 68 | 0.160 | 0.191 |
| 2023-Q2 | 90 | 0.306 | 0.233 | 0.389 | 58 | 0.190 | 8 | 0.375 | 82 | 0.299 | 0.230 |
| 2023-Q3 | 68 | 0.184 | 0.110 | 0.257 | 53 | 0.104 | 15 | 0.167 | 53 | 0.189 | 0.219 |
| 2023-Q4 | 130 | 0.321 | 0.260 | 0.383 | 87 | 0.262 | 33 | 0.187 | 97 | 0.366 | 0.284 |
| 2024-Q1 | 98 | 0.501 | 0.429 | 0.571 | 40 | 0.358 | 10 | 0.333 | 88 | 0.520 | 0.349 |
| 2024-Q2 | 124 | 0.573 | 0.505 | 0.640 | 45 | 0.504 | 15 | 0.422 | 109 | 0.593 | 0.460 |
| 2024-Q3 | 17 | 0.588 | 0.412 | 0.765 | 7 | 0.476 | 3 | 0.444 | 14 | 0.619 | 0.544 |
| 2024-Q4 | 230 | 0.648 | 0.601 | 0.695 | 89 | 0.509 | 35 | 0.324 | 195 | 0.706 | 0.620 |
| 2025-Q1 | 29 | 0.598 | 0.448 | 0.747 | 12 | 0.778 | 3 | 0.444 | 26 | 0.615 | 0.639 |
| 2025-Q2 | 165 | 0.503 | 0.442 | 0.562 | 60 | 0.483 | 30 | 0.333 | 135 | 0.541 | 0.588 |
| 2025-Q3 | 155 | 0.437 | 0.370 | 0.503 | 48 | 0.333 | 51 | 0.320 | 104 | 0.494 | 0.481 |
| 2025-Q4 | 106 | 0.450 | 0.373 | 0.532 | 35 | 0.416 | 10 | 0.374 | 96 | 0.457 | 0.466 |
| 2026-Q1 | 151 | 0.334 | 0.265 | 0.400 | 69 | 0.325 | 30 | 0.300 | 121 | 0.342 | 0.402 |
| 2026-Q2 | 44 | 0.523 | 0.386 | 0.670 | 0 | N/A | 11 | 0.455 | 33 | 0.545 | 0.402 |
> **Note:** CI intervals use 1000-iteration bootstrap resampling.
> Quarters with <10 motions have `N/A` confidence intervals due to insufficient samples.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 320 KiB

After

Width:  |  Height:  |  Size: 374 KiB

-2
View File
@@ -1,7 +1,5 @@
# Voting Margin Analysis
> **Part of the Overton Window Analysis.** See the [synthesis report](overton_window_synthesis.md) for the integrated narrative, or the [interactive article](overton_window.qmd) for the full story with charts.
**Goal:** Replace binary pass/fail with continuous voting margin as the primary
success metric for right-wing motions in the Tweede Kamer.