fix: connection leak, Rice index excludes absences, per-party motion count guard
This commit is contained in:
+75
-62
@@ -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,74 +963,79 @@ 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(
|
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())
|
disc_df = disc_df[disc_df["n_motions"] >= _MIN_MOTIONS_FOR_DISCIPLINE].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(
|
||||||
|
"Te weinig hoofdelijke stemmingen in dit venster voor een cohesieanalyse."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
disc_df["discipline_pct"] = (disc_df["discipline"] * 100).round(1)
|
compass_parties = set(df_pos["party"].unique())
|
||||||
disc_df["party_label"] = disc_df.apply(
|
disc_df = disc_df[disc_df["party"].isin(compass_parties)].copy()
|
||||||
lambda r: f"{r['party']} ({int(r['n_motions'])} moties)", axis=1
|
if disc_df.empty:
|
||||||
)
|
st.caption("Geen overlappende partijen tussen kompas en stemmingsdata.")
|
||||||
|
else:
|
||||||
bar_fig = px.bar(
|
disc_df["discipline_pct"] = (disc_df["discipline"] * 100).round(1)
|
||||||
disc_df.sort_values("discipline"),
|
disc_df["party_label"] = disc_df.apply(
|
||||||
x="discipline_pct",
|
lambda r: f"{r['party']} ({int(r['n_motions'])} moties)", axis=1
|
||||||
y="party_label",
|
|
||||||
orientation="h",
|
|
||||||
color="discipline_pct",
|
|
||||||
color_continuous_scale="RdYlGn",
|
|
||||||
range_color=[80, 100],
|
|
||||||
labels={"discipline_pct": "Cohesie (%)", "party_label": "Partij"},
|
|
||||||
title="Cohesie bij hoofdelijke stemmingen",
|
|
||||||
)
|
|
||||||
bar_fig.update_layout(
|
|
||||||
height=max(300, len(disc_df) * 35 + 80),
|
|
||||||
showlegend=False,
|
|
||||||
coloraxis_showscale=False,
|
|
||||||
yaxis_title="",
|
|
||||||
)
|
|
||||||
st.plotly_chart(bar_fig, use_container_width=True)
|
|
||||||
|
|
||||||
top3 = disc_df.nlargest(3, "discipline")[
|
|
||||||
["party", "discipline_pct", "n_motions"]
|
|
||||||
]
|
|
||||||
bot3 = disc_df.nsmallest(3, "discipline")[
|
|
||||||
["party", "discipline_pct", "n_motions"]
|
|
||||||
]
|
|
||||||
col_a, col_b = st.columns(2)
|
|
||||||
with col_a:
|
|
||||||
st.markdown("**Meest eensgezind**")
|
|
||||||
st.dataframe(
|
|
||||||
top3.rename(
|
|
||||||
columns={
|
|
||||||
"party": "Partij",
|
|
||||||
"discipline_pct": "Cohesie (%)",
|
|
||||||
"n_motions": "Moties",
|
|
||||||
}
|
|
||||||
),
|
|
||||||
hide_index=True,
|
|
||||||
use_container_width=True,
|
|
||||||
)
|
)
|
||||||
with col_b:
|
|
||||||
st.markdown("**Meest verdeeld**")
|
bar_fig = px.bar(
|
||||||
st.dataframe(
|
disc_df.sort_values("discipline"),
|
||||||
bot3.rename(
|
x="discipline_pct",
|
||||||
columns={
|
y="party_label",
|
||||||
"party": "Partij",
|
orientation="h",
|
||||||
"discipline_pct": "Cohesie (%)",
|
color="discipline_pct",
|
||||||
"n_motions": "Moties",
|
color_continuous_scale="RdYlGn",
|
||||||
}
|
range_color=[80, 100],
|
||||||
),
|
labels={"discipline_pct": "Cohesie (%)", "party_label": "Partij"},
|
||||||
hide_index=True,
|
title="Cohesie bij hoofdelijke stemmingen",
|
||||||
use_container_width=True,
|
|
||||||
)
|
)
|
||||||
|
bar_fig.update_layout(
|
||||||
|
height=max(300, len(disc_df) * 35 + 80),
|
||||||
|
showlegend=False,
|
||||||
|
coloraxis_showscale=False,
|
||||||
|
yaxis_title="",
|
||||||
|
)
|
||||||
|
st.plotly_chart(bar_fig, use_container_width=True)
|
||||||
|
|
||||||
|
top3 = disc_df.nlargest(3, "discipline")[
|
||||||
|
["party", "discipline_pct", "n_motions"]
|
||||||
|
]
|
||||||
|
bot3 = disc_df.nsmallest(3, "discipline")[
|
||||||
|
["party", "discipline_pct", "n_motions"]
|
||||||
|
]
|
||||||
|
col_a, col_b = st.columns(2)
|
||||||
|
with col_a:
|
||||||
|
st.markdown("**Meest eensgezind**")
|
||||||
|
st.dataframe(
|
||||||
|
top3.rename(
|
||||||
|
columns={
|
||||||
|
"party": "Partij",
|
||||||
|
"discipline_pct": "Cohesie (%)",
|
||||||
|
"n_motions": "Moties",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
hide_index=True,
|
||||||
|
use_container_width=True,
|
||||||
|
)
|
||||||
|
with col_b:
|
||||||
|
st.markdown("**Meest verdeeld**")
|
||||||
|
st.dataframe(
|
||||||
|
bot3.rename(
|
||||||
|
columns={
|
||||||
|
"party": "Partij",
|
||||||
|
"discipline_pct": "Cohesie (%)",
|
||||||
|
"n_motions": "Moties",
|
||||||
|
}
|
||||||
|
),
|
||||||
|
hide_index=True,
|
||||||
|
use_container_width=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user