fix(overton): reframe verdict and migration as gateway domain

- Verdict now says 'window widened' (not 'did not shift') — centrist
  support surged for right-wing motions while staying flat for left-wing
- Migration reframed from 'one exception' to 'gateway domain' — where
  acceptance expanded most genuinely and right-wing parties learned
  frames they applied elsewhere
- Explorer Overton tab: added migration gateway section with pre/post
  metrics, full motion text (no truncation), 100-motion browser
- Explorer Kompas tab: updated Overton context to lead with the shift
- Explorer Trajectories tab: Dutch-language Overton annotation
- Synthesis, QMD, HTML report, STATUS.md all updated consistently
This commit is contained in:
2026-06-07 22:29:29 +02:00
parent 5706e86777
commit 8826346190
7 changed files with 106 additions and 39 deletions
+9 -6
View File
@@ -185,13 +185,16 @@ def build_compass_tab(db_path: str, window_size: str) -> None:
with st.expander("Overton Window Context"):
st.markdown(
"Het SVD-kompas visualiseert direct de dynamiek achter de "
"Overton-verschuiving in de Tweede Kamer.\n\n"
"**Centristische partijen** (D66, CDA, CU, NSC) zijn op **beide assen naar"
"Het SVD-kompas visualiseert de dynamiek achter de "
"**verbreding van het Overton-venster** in de Tweede Kamer.\n\n"
"Centristische steun voor rechtse moties steeg van 25% naar 51% na 2024, "
"terwijl steun voor linkse moties gelijk bleef. Het venster verschoof — "
"meer rechtse standpunten werden acceptabel.\n\n"
"Maar: **centristische partijen** (D66, CDA, CU, NSC) zijn op **beide assen naar"
" links** verschoven, terwijl rechtse partijen stabiel bleven. Dit patroon"
' van "acceptatie zonder conversie" betekent dat centristen meer met'
" rechtse moties meestemmen terwijl ze ideologisch verder van rechts af"
" komen te staan.\n\n"
' van "acceptatie zonder conversie" betekent dat rechtse partijen mildere'
" moties gingen indienen, en centristen daardoor vaker konden meestemmen — "
"zonder dat ze ideologisch naar rechts opschoven.\n\n"
"[Lees de volledige analyse](../reports/overton_window/overton_window.qmd)\n\n"
"Probeer de **Stemwijzer-quiz** om te zien welke MP bij jouw standpunten past."
)
+59 -9
View File
@@ -17,8 +17,10 @@ def build_overton_tab(db_path: str) -> None:
"""Build the Overton Window tab."""
st.subheader("Overton Window Analyse")
st.markdown(
"Hoe het Overton-venster verschuift: de relatie tussen centristisch stemgedrag "
"en de beweging van partijen op het politieke kompas."
"Het Overton-venster **verbreedde** na 2024: centristische steun voor rechtse "
"moties steeg van 25% naar 51%, terwijl steun voor linkse moties gelijk bleef. "
"Rechtse partijen dienden mildere moties in, waardoor centristen vaker konden "
"meestemmen — zonder ideologisch naar rechts op te schuiven."
)
try:
@@ -44,6 +46,7 @@ def build_overton_tab(db_path: str) -> None:
try:
_render_centrist_support_chart(con)
_render_summary_stats(con)
_render_migration_gateway(con)
_render_motion_browser(con)
_render_explore_further()
except Exception as e:
@@ -135,23 +138,70 @@ def _render_summary_stats(con: duckdb.DuckDBPyConnection) -> None:
col4.metric("2D correlation r", "0.47")
def _render_migration_gateway(con: duckdb.DuckDBPyConnection) -> None:
st.subheader("Migratie: de gateway-domein")
st.markdown(
"Migratie is waar de Overton-verschuiving het meest echt is — en waar "
"rechtse partijen de frames leerden die ze later op andere domeinen toepasten."
)
df = con.execute("""
SELECT
CASE WHEN year < 2024 THEN 'Pre-2024' ELSE 'Post-2024' END as period,
AVG(centrist_support_strict) as cs_strict,
COUNT(*) as n_motions
FROM right_wing_motions
WHERE classified = TRUE
AND year >= 2016
AND category IN ('asiel/vreemdelingen', 'asiel')
GROUP BY period
ORDER BY period
""").fetchdf()
if df.empty or len(df) < 2:
return
pre = df[df["period"] == "Pre-2024"].iloc[0]
post = df[df["period"] == "Post-2024"].iloc[0]
col1, col2, col3, col4 = st.columns(4)
col1.metric("Pre-2024 CS (migratie)", f"{pre['cs_strict']:.3f}")
col2.metric("Post-2024 CS (migratie)", f"{post['cs_strict']:.3f}")
col3.metric("Shift", f"{post['cs_strict'] - pre['cs_strict']:+.3f}")
col4.metric("Moties", f"{int(pre['n_motions'] + post['n_motions'])}")
st.caption(
"Ter vergelijking: niet-migratie moties gingen van 0.276 naar 0.481 (+0.205). "
"Migratie steeg meer dan twee keer zo hard (+0.216), terwijl de materiële impact "
"nauwelijks daalde. CDA en ChristenUnie verdubbelden hun migratie-steun "
"(18%→40%, 10%→30%)."
)
def _render_motion_browser(con: duckdb.DuckDBPyConnection) -> None:
st.subheader("Rechtse Moties Browser")
df = con.execute("""
SELECT year, title, centrist_support_strict, category
FROM right_wing_motions
WHERE classified = TRUE
ORDER BY centrist_support_strict DESC
LIMIT 50
SELECT r.year, r.title, m.text, r.centrist_support_strict, r.category
FROM right_wing_motions r
LEFT JOIN motions m ON r.motion_id = m.id
WHERE r.classified = TRUE
ORDER BY r.centrist_support_strict DESC
LIMIT 100
""").fetchdf()
if df.empty:
st.info("Geen rechtse moties gevonden.")
return
df["title"] = df["title"].str.slice(0, 80)
st.dataframe(df, use_container_width=True)
df = df.rename(columns={
"year": "Jaar",
"title": "Titel",
"text": "Motietekst",
"centrist_support_strict": "Centrist Support",
"category": "Categorie",
})
st.dataframe(df, use_container_width=True, height=600)
def _render_explore_further() -> None:
+4 -2
View File
@@ -667,8 +667,10 @@ def build_trajectories_tab(db_path: str, window_size: str) -> None:
try:
st.plotly_chart(fig, use_container_width=True)
st.info(
"**Overton shift:** centrist support for right-wing motions surged "
"after PVV's Nov 2023 election win."
"**Overton-venster verbreed:** na PVV's verkiezingsoverwinning (nov 2023) "
"steeg centristische steun voor rechtse moties van 25% naar 51%, "
"terwijl steun voor linkse moties gelijk bleef. "
"Rechtse partijen matigden hun moties, centristen stemden vaker voor."
)
except Exception as e:
st.error(f"Trajectories rendering failed: {e}")