diff --git a/explorer.py b/explorer.py index 9db4131..019e84a 100644 --- a/explorer.py +++ b/explorer.py @@ -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 - - 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}") + logger.exception("Failed to batch-fetch motion details") + + # 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: