feat(explorer): restructure SVD tab — pole-split motions, party axis chart, inline expanders with voting
This commit is contained in:
+72
-44
@@ -929,62 +929,90 @@ def build_svd_components_tab(db_path: str) -> None:
|
||||
)
|
||||
comp_sel = comp_options[comp_sel_idx]
|
||||
|
||||
# Show theme explanation + poles
|
||||
# Show theme explanation
|
||||
theme = SVD_THEMES.get(comp_sel, {})
|
||||
if theme:
|
||||
st.info(f"**{theme['label']}** — {theme['explanation']}")
|
||||
pos = theme.get("positive_pole", "")
|
||||
neg = theme.get("negative_pole", "")
|
||||
if pos or neg:
|
||||
pcol, ncol = st.columns(2)
|
||||
with pcol:
|
||||
st.success(f"▲ **Positieve pool:** {pos}")
|
||||
with ncol:
|
||||
st.error(f"▼ **Negatieve pool:** {neg}")
|
||||
|
||||
motions = comp_map.get(comp_sel, [])
|
||||
|
||||
col1, col2 = st.columns([1, 2])
|
||||
with col1:
|
||||
st.markdown("**Top-moties (titels)**")
|
||||
for m in motions:
|
||||
mid = m.get("motion_id")
|
||||
score = m.get("score", 0.0)
|
||||
title = m.get("title") or f"Motie #{mid}"
|
||||
sign = "▲" if score >= 0 else "▼"
|
||||
if st.button(f"{sign} {mid}: {title[:72]}", key=f"btn_{comp_sel}_{mid}"):
|
||||
st.session_state["svd_selected_mid"] = mid
|
||||
# Party axis chart
|
||||
party_scores = load_party_axis_scores(db_path)
|
||||
_render_party_axis_chart(party_scores, comp_sel)
|
||||
|
||||
with col2:
|
||||
sel_mid = st.session_state.get("svd_selected_mid")
|
||||
if not sel_mid and motions:
|
||||
sel_mid = motions[0].get("motion_id")
|
||||
if sel_mid:
|
||||
# fetch motion metadata from DB for completeness
|
||||
# Batch-fetch motion details (title, date, policy_area, url, body_text, voting_results)
|
||||
motion_ids = [m.get("motion_id") for m in motions if m.get("motion_id") is not None]
|
||||
motion_details: Dict[int, tuple] = {}
|
||||
if motion_ids:
|
||||
try:
|
||||
placeholders = ", ".join("?" for _ in motion_ids)
|
||||
con = duckdb.connect(database=db_path, read_only=True)
|
||||
row = con.execute(
|
||||
"SELECT id, title, date, policy_area, url, body_text FROM motions WHERE id=?",
|
||||
[int(sel_mid)],
|
||||
).fetchone()
|
||||
db_rows = con.execute(
|
||||
f"SELECT id, title, date, policy_area, url, body_text, voting_results "
|
||||
f"FROM motions WHERE id IN ({placeholders})",
|
||||
[int(mid) for mid in motion_ids],
|
||||
).fetchall()
|
||||
con.close()
|
||||
motion_details = {r[0]: r for r in db_rows}
|
||||
except Exception:
|
||||
row = None
|
||||
logger.exception("Failed to batch-fetch motion details")
|
||||
|
||||
if row:
|
||||
st.markdown(f"### {row[1] or f'Motie #{row[0]}'}")
|
||||
try:
|
||||
date_str = str(row[2])[:10]
|
||||
except Exception:
|
||||
date_str = "?"
|
||||
st.caption(f"📅 {date_str} | {row[3]}")
|
||||
if row[4] and str(row[4]).startswith("http"):
|
||||
st.markdown(f"[🔗 Bekijk op Tweede Kamer]({row[4]})")
|
||||
if row[5]:
|
||||
with st.expander("Toon volledige tekst"):
|
||||
st.write(row[5])
|
||||
else:
|
||||
st.info(f"Metadata not found in DB for motion {sel_mid}")
|
||||
# Split motions by pole sign
|
||||
pos_motions = [m for m in motions if float(m.get("score", 0.0)) >= 0]
|
||||
neg_motions = [m for m in motions if float(m.get("score", 0.0)) < 0]
|
||||
|
||||
pos_pole = (
|
||||
theme.get("positive_pole", "Positieve pool") if theme else "Positieve pool"
|
||||
)
|
||||
neg_pole = (
|
||||
theme.get("negative_pole", "Negatieve pool") if theme else "Negatieve pool"
|
||||
)
|
||||
|
||||
pcol, ncol = st.columns(2)
|
||||
|
||||
with pcol:
|
||||
st.success(f"▲ **Positieve pool:** {pos_pole}")
|
||||
for m in pos_motions:
|
||||
mid = m.get("motion_id")
|
||||
raw_title = m.get("title") or f"Motie #{mid}"
|
||||
with st.expander(f"▲ {raw_title[:80]}"):
|
||||
row = motion_details.get(int(mid)) if mid is not None else None
|
||||
if row:
|
||||
try:
|
||||
date_str = str(row[2])[:10]
|
||||
except Exception:
|
||||
date_str = "?"
|
||||
st.caption(f"📅 {date_str} | {row[3] or '—'}")
|
||||
if row[4] and str(row[4]).startswith("http"):
|
||||
st.markdown(f"[🔗 Bekijk op Tweede Kamer]({row[4]})")
|
||||
if row[5]:
|
||||
with st.expander("Toon volledige tekst"):
|
||||
st.write(row[5])
|
||||
_render_voting_results(row[6])
|
||||
else:
|
||||
st.caption("_Geen metadata beschikbaar_")
|
||||
|
||||
with ncol:
|
||||
st.error(f"▼ **Negatieve pool:** {neg_pole}")
|
||||
for m in neg_motions:
|
||||
mid = m.get("motion_id")
|
||||
raw_title = m.get("title") or f"Motie #{mid}"
|
||||
with st.expander(f"▼ {raw_title[:80]}"):
|
||||
row = motion_details.get(int(mid)) if mid is not None else None
|
||||
if row:
|
||||
try:
|
||||
date_str = str(row[2])[:10]
|
||||
except Exception:
|
||||
date_str = "?"
|
||||
st.caption(f"📅 {date_str} | {row[3] or '—'}")
|
||||
if row[4] and str(row[4]).startswith("http"):
|
||||
st.markdown(f"[🔗 Bekijk op Tweede Kamer]({row[4]})")
|
||||
if row[5]:
|
||||
with st.expander("Toon volledige tekst"):
|
||||
st.write(row[5])
|
||||
_render_voting_results(row[6])
|
||||
else:
|
||||
st.caption("_Geen metadata beschikbaar_")
|
||||
|
||||
|
||||
def build_mp_quiz_tab(db_path: str) -> None:
|
||||
|
||||
Reference in New Issue
Block a user