|
|
"""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.")
|
|
|
|