# app.py import streamlit as st import pandas as pd from datetime import datetime from database import db from summarizer import summarizer from config import config import json # Page config st.set_page_config( page_title="Nederlandse Politieke Kompas", page_icon="πŸ‡³πŸ‡±", layout="wide" ) def main(): st.title("πŸ‡³πŸ‡± Nederlandse Politieke Kompas") st.markdown( "Ontdek welke politieke partij het beste bij jouw idealen past door te stemmen op echte Tweede Kamer moties." ) # Initialize session state if "session_id" not in st.session_state: st.session_state.session_id = None if "current_motion_index" not in st.session_state: st.session_state.current_motion_index = 0 if "motions" not in st.session_state: st.session_state.motions = [] if "show_results" not in st.session_state: st.session_state.show_results = False # Sidebar configuration with st.sidebar: st.header("Instellingen") motion_count = st.slider( "Aantal moties", min_value=5, max_value=25, value=config.DEFAULT_MOTION_COUNT, ) policy_area = st.selectbox("Beleidsgebied", config.POLICY_AREAS) margin_range = st.slider( "ControversiΓ«le moties (%)", min_value=0, max_value=100, value=( config.DEFAULT_WINNING_MARGIN_MIN, config.DEFAULT_WINNING_MARGIN_MAX, ), ) if st.button("Start Nieuwe Sessie"): start_new_session(motion_count, policy_area, margin_range) if st.button("Genereer AI Samenvattingen"): with st.spinner("Genereren van samenvattingen..."): summarizer.update_motion_summaries() st.success("Samenvattingen bijgewerkt!") # Main content if not st.session_state.session_id: show_welcome_screen(motion_count, policy_area, margin_range) elif st.session_state.show_results: show_results() else: show_motion_interface() def start_new_session(motion_count, policy_area, margin_range): """Start a new voting session""" # Get filtered motions motions = db.get_filtered_motions( policy_area=policy_area, min_margin=margin_range[0] / 100, max_margin=margin_range[1] / 100, limit=motion_count, ) if len(motions) < motion_count: st.warning( f"Slechts {len(motions)} moties gevonden met de geselecteerde criteria." ) # Create session session_id = db.create_session(motion_count) # Update session state st.session_state.session_id = session_id st.session_state.motions = motions[:motion_count] st.session_state.current_motion_index = 0 st.session_state.show_results = False st.rerun() def show_welcome_screen(motion_count, policy_area, margin_range): """Show welcome screen with start button""" col1, col2, col3 = st.columns([1, 2, 1]) with col2: st.markdown("### Welkom bij de Nederlandse Politieke Kompas!") st.markdown(f""" **Jouw instellingen:** - πŸ“Š **{motion_count} moties** uit het beleidsgebied **{policy_area}** - 🎯 **ControversiΓ«le moties** tussen {margin_range[0]}% en {margin_range[1]}% marge Klik op "Start Nieuwe Sessie" in de zijbalk om te beginnen met stemmen. """) st.info( "πŸ’‘ **Tip**: Kies 'Alle' als beleidsgebied voor een breed overzicht van verschillende onderwerpen." ) def show_motion_interface(): """Show motion voting interface""" if not st.session_state.motions: st.error("Geen moties gevonden. Start een nieuwe sessie.") return current_index = st.session_state.current_motion_index total_motions = len(st.session_state.motions) # Progress bar progress = (current_index) / total_motions st.progress(progress, text=f"Motie {current_index + 1} van {total_motions}") if current_index >= total_motions: st.session_state.show_results = True st.rerun() return motion = st.session_state.motions[current_index] # Motion display st.header(f"Motie {current_index + 1}: {motion['title']}") # Policy area tag st.markdown(f"**Beleidsgebied:** {motion['policy_area']}") # Layman explanation (prominent) if motion.get("layman_explanation"): st.markdown("### πŸ“ Uitleg in begrijpelijke taal:") st.markdown(f"*{motion['layman_explanation']}*") # Original description (collapsible) motion_text = motion.get("body_text") or motion.get("description", "") if motion_text: label = ( "πŸ“‹ Volledige motietekst" if motion.get("body_text") else "πŸ“‹ Originele motiebeschrijving" ) with st.expander(label): st.write(motion_text) # Voting buttons st.markdown("### πŸ—³οΈ Hoe zou jij stemmen?") col1, col2, col3 = st.columns(3) with col1: if st.button("βœ… Voor", use_container_width=True, type="primary"): cast_vote("Voor") with col2: if st.button("❌ Tegen", use_container_width=True): cast_vote("Tegen") with col3: if st.button("🚫 Geen stem", use_container_width=True): cast_vote("Geen stem") def cast_vote(vote_choice): """Record user vote and move to next motion""" current_motion = st.session_state.motions[st.session_state.current_motion_index] # Save vote to database db.update_user_vote(st.session_state.session_id, current_motion["id"], vote_choice) # Move to next motion st.session_state.current_motion_index += 1 st.rerun() def show_results(): """Show voting results and party matches""" st.header("🎯 Jouw Resultaten") # Calculate party matches party_matches = db.calculate_party_matches(st.session_state.session_id) if not party_matches: st.error("Geen resultaten beschikbaar.") return # Party ranking table st.subheader("πŸ“Š Partij Overeenkomsten (van hoog naar laag)") df = pd.DataFrame(party_matches) df.columns = ["Partij", "Overeenkomst %", "Eens", "Totaal"] # Style the dataframe def color_agreement(val): if val >= 80: return "background-color: #d4edda" elif val >= 60: return "background-color: #fff3cd" else: return "background-color: #f8d7da" styled_df = df.style.applymap(color_agreement, subset=["Overeenkomst %"]) st.dataframe(styled_df, use_container_width=True, hide_index=True) # Top match highlight top_match = party_matches[0] st.success( f"πŸ† **Beste match:** {top_match['party']} ({top_match['agreement_percentage']}% overeenkomst)" ) # Detailed motion overview st.subheader("πŸ“‹ Gedetailleerd Overzicht per Motie") show_detailed_motion_results() # New session button if st.button("πŸ”„ Start Nieuwe Sessie"): # Clear session state for key in ["session_id", "motions", "current_motion_index", "show_results"]: if key in st.session_state: del st.session_state[key] st.rerun() def show_detailed_motion_results(): """Show detailed voting results for each motion""" import duckdb conn = duckdb.connect(config.DATABASE_PATH) # Get user votes user_data = conn.execute( """ SELECT user_votes FROM user_sessions WHERE session_id = ? """, (st.session_state.session_id,), ).fetchone() if not user_data: return user_votes = json.loads(user_data[0]) # Get motion details motion_ids = list(user_votes.keys()) if motion_ids: placeholders = ",".join(["?" for _ in motion_ids]) motions = conn.execute( f""" SELECT id, title, layman_explanation, body_text, description, voting_results FROM motions WHERE id IN ({placeholders}) """, motion_ids, ).fetchall() for ( motion_id, title, layman_explanation, body_text, description, voting_results_json, ) in motions: voting_results = json.loads(voting_results_json) user_vote = user_votes[str(motion_id)] with st.expander(f"**{title}** (Jouw stem: {user_vote})"): # Show layman explanation prominently if layman_explanation: st.markdown("**πŸ“ Uitleg:**") st.markdown(f"*{layman_explanation}*") # Show full motion body text if available, otherwise description motion_text = body_text or description if motion_text: st.markdown("**πŸ“‹ Motiebeschrijving:**") st.write(motion_text) # Create voting overview parties_voor = [p for p, v in voting_results.items() if v == "voor"] parties_tegen = [p for p, v in voting_results.items() if v == "tegen"] col1, col2 = st.columns(2) with col1: st.markdown("**Voor:**") st.write(", ".join(parties_voor) if parties_voor else "Geen") with col2: st.markdown("**Tegen:**") st.write(", ".join(parties_tegen) if parties_tegen else "Geen") conn.close() if __name__ == "__main__": main()