|
|
|
@ -258,7 +258,10 @@ def compute_party_discipline( |
|
|
|
|
|
|
|
|
|
|
|
Rice index per motion per party = fraction of party MPs voting with the party majority. |
|
|
|
Rice index per motion per party = fraction of party MPs voting with the party majority. |
|
|
|
The per-party score is the average Rice index across all motions in the date range. |
|
|
|
The per-party score is the average Rice index across all motions in the date range. |
|
|
|
|
|
|
|
Only 'voor' and 'tegen' votes are counted; absent and abstaining MPs are excluded from the |
|
|
|
|
|
|
|
Rice index calculation. |
|
|
|
""" |
|
|
|
""" |
|
|
|
|
|
|
|
conn = None |
|
|
|
try: |
|
|
|
try: |
|
|
|
conn = duckdb.connect(db_path, read_only=True) |
|
|
|
conn = duckdb.connect(db_path, read_only=True) |
|
|
|
result = conn.execute( |
|
|
|
result = conn.execute( |
|
|
|
@ -272,7 +275,7 @@ def compute_party_discipline( |
|
|
|
WHERE mp_name LIKE '%,%' |
|
|
|
WHERE mp_name LIKE '%,%' |
|
|
|
AND date >= CAST(? AS DATE) |
|
|
|
AND date >= CAST(? AS DATE) |
|
|
|
AND date <= CAST(? AS DATE) |
|
|
|
AND date <= CAST(? AS DATE) |
|
|
|
AND vote IN ('voor', 'tegen', 'afwezig', 'onthouden') |
|
|
|
AND vote IN ('voor', 'tegen') |
|
|
|
), |
|
|
|
), |
|
|
|
vote_counts AS ( |
|
|
|
vote_counts AS ( |
|
|
|
SELECT |
|
|
|
SELECT |
|
|
|
@ -313,11 +316,16 @@ def compute_party_discipline( |
|
|
|
""", |
|
|
|
""", |
|
|
|
[start_date, end_date], |
|
|
|
[start_date, end_date], |
|
|
|
).fetchdf() |
|
|
|
).fetchdf() |
|
|
|
conn.close() |
|
|
|
|
|
|
|
return result |
|
|
|
return result |
|
|
|
except Exception as exc: |
|
|
|
except Exception as exc: |
|
|
|
logger.warning("compute_party_discipline failed: %s", exc) |
|
|
|
logger.warning("compute_party_discipline failed: %s", exc) |
|
|
|
return pd.DataFrame(columns=["party", "n_motions", "discipline"]) |
|
|
|
return pd.DataFrame(columns=["party", "n_motions", "discipline"]) |
|
|
|
|
|
|
|
finally: |
|
|
|
|
|
|
|
if conn is not None: |
|
|
|
|
|
|
|
try: |
|
|
|
|
|
|
|
conn.close() |
|
|
|
|
|
|
|
except Exception: |
|
|
|
|
|
|
|
pass |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@st.cache_data(show_spinner="Partijposities op SVD-assen laden…") |
|
|
|
@st.cache_data(show_spinner="Partijposities op SVD-assen laden…") |
|
|
|
@ -955,14 +963,19 @@ def build_compass_tab(db_path: str, window_size: str) -> None: |
|
|
|
disc_df = compute_party_discipline(db_path, start_date, end_date) |
|
|
|
disc_df = compute_party_discipline(db_path, start_date, end_date) |
|
|
|
|
|
|
|
|
|
|
|
st.subheader("Stemgedrag cohesie") |
|
|
|
st.subheader("Stemgedrag cohesie") |
|
|
|
if disc_df.empty or disc_df["n_motions"].max() < _MIN_MOTIONS_FOR_DISCIPLINE: |
|
|
|
if disc_df.empty: |
|
|
|
|
|
|
|
st.caption( |
|
|
|
|
|
|
|
"Te weinig hoofdelijke stemmingen in dit venster voor een cohesieanalyse." |
|
|
|
|
|
|
|
) |
|
|
|
|
|
|
|
else: |
|
|
|
|
|
|
|
disc_df = disc_df[disc_df["n_motions"] >= _MIN_MOTIONS_FOR_DISCIPLINE].copy() |
|
|
|
|
|
|
|
if disc_df.empty: |
|
|
|
st.caption( |
|
|
|
st.caption( |
|
|
|
"Te weinig hoofdelijke stemmingen in dit venster voor een cohesieanalyse." |
|
|
|
"Te weinig hoofdelijke stemmingen in dit venster voor een cohesieanalyse." |
|
|
|
) |
|
|
|
) |
|
|
|
else: |
|
|
|
else: |
|
|
|
compass_parties = set(df_pos["party"].unique()) |
|
|
|
compass_parties = set(df_pos["party"].unique()) |
|
|
|
disc_df = disc_df[disc_df["party"].isin(compass_parties)].copy() |
|
|
|
disc_df = disc_df[disc_df["party"].isin(compass_parties)].copy() |
|
|
|
|
|
|
|
|
|
|
|
if disc_df.empty: |
|
|
|
if disc_df.empty: |
|
|
|
st.caption("Geen overlappende partijen tussen kompas en stemmingsdata.") |
|
|
|
st.caption("Geen overlappende partijen tussen kompas en stemmingsdata.") |
|
|
|
else: |
|
|
|
else: |
|
|
|
|