You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
motief/app.py

310 lines
9.4 KiB

# 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()