"""MP Quiz tab for the parliamentary explorer.""" from __future__ import annotations import pandas as pd import analysis.explorer_data as explorer_data from analysis.tabs._rendering import st def build_mp_quiz_tab(db_path: str) -> None: """Interactive quiz: narrow MPs by asking motion vote questions. Minimal viable flow: - seed with top-N controversial motions (SEED_MOTIONS) - present one question at a time, store answers in st.session_state['mp_quiz_votes'] - after each answer call MotionDatabase.match_mps_for_votes to rank MPs - if multiple candidates remain, call choose_discriminating_motions to pick next question - stop when unique MP found or no discriminating motions remain """ st.subheader("Welk tweede kamerlid ben jij?") st.markdown( "Beantwoord een paar eenvoudige ja/nee/onthoud vragen over moties om te zien welk Kamerlid het meest op jou lijkt." ) SEED_MOTIONS = 8 MAX_QUESTIONS = 20 if "mp_quiz_votes" not in st.session_state: st.session_state["mp_quiz_votes"] = {} if "mp_quiz_asked" not in st.session_state: st.session_state["mp_quiz_asked"] = [] from database import MotionDatabase as _MotionDatabase db_inst = _MotionDatabase(db_path) df = explorer_data.load_motions_df(db_path) if df.empty: st.warning("Geen moties beschikbaar om de quiz te starten.") return seed_ids = db_inst.get_motions_with_individual_votes(k=SEED_MOTIONS) if not seed_ids: st.warning("Geen individuele stemdata beschikbaar voor de quiz.") return def _next_motion_id(): for mid in seed_ids: if str(mid) not in st.session_state["mp_quiz_votes"]: return mid try: user_votes = { int(k): v for k, v in st.session_state["mp_quiz_votes"].items() } ranked = db_inst.match_mps_for_votes(user_votes, limit=200) except Exception: ranked = [] candidates = [r["mp_name"] for r in ranked] excluded = [int(k) for k in st.session_state["mp_quiz_votes"].keys()] if not candidates: return None try: next_ids = db_inst.choose_discriminating_motions(candidates, excluded, k=1) return next_ids[0] if next_ids else None except Exception: return None col1, col2 = st.columns([3, 1]) with col2: st.caption( f"Vragen beantwoord: {len(st.session_state['mp_quiz_votes'])}/{MAX_QUESTIONS}" ) if st.button("Reset quiz"): st.session_state["mp_quiz_votes"] = {} st.session_state["mp_quiz_asked"] = [] st.rerun() next_mid = _next_motion_id() if next_mid is None: st.info("Geen nieuwe vragen beschikbaar om kandidaten te scheiden.") else: motion_rows = df[df["id"] == next_mid] if motion_rows.empty: st.session_state["mp_quiz_votes"][str(next_mid)] = "Geen stem" st.rerun() return motion_row = motion_rows.iloc[0] st.markdown(f"### {motion_row.get('title') or f'Motie #{next_mid}'}") if motion_row.get("layman_explanation"): st.info(motion_row.get("layman_explanation")) with st.form(key=f"mp_quiz_form_{next_mid}"): choice = st.radio( "Wat zou jij stemmen?", options=["Voor", "Tegen", "Onthouden", "Geen stem"], index=3, ) submitted = st.form_submit_button("Beantwoord en verder") if submitted: st.session_state["mp_quiz_votes"][str(next_mid)] = choice st.session_state["mp_quiz_asked"].append(next_mid) st.rerun() try: user_votes = {int(k): v for k, v in st.session_state["mp_quiz_votes"].items()} ranking = db_inst.match_mps_for_votes(user_votes, limit=50) except Exception: ranking = [] if ranking: st.markdown("**Top kandidaten**") rdf = pd.DataFrame(ranking) st.dataframe(rdf.head(10), use_container_width=True) top_pct = ranking[0]["agreement_pct"] if ranking else 0.0 top_matches = [r for r in ranking if r["agreement_pct"] == top_pct] if len(top_matches) == 1 and top_matches[0]["overlap"] > 0: st.success( f"Unieke match gevonden: {top_matches[0]['mp_name']} ({top_matches[0]['party']})" ) else: if len(st.session_state["mp_quiz_asked"]) >= MAX_QUESTIONS: st.warning( "Maximaal aantal vragen beantwoord. Je hebt meerdere vergelijkbare kandidaten." ) else: st.info("Nog geen unieke match — vraag meer om verder te verfijnen.") else: st.info("Nog geen antwoorden of geen overlapping met bestaande stemdata.")