@ -627,9 +627,109 @@ def build_svd_components_tab(db_path: str) -> None:
""" New tab: show top motions contributing to top SVD components.
Reads thoughts / explorer / top_svd_top_motions . json and displays a selector
for components 1. .10 and a detail pane for selected motion .
for components 1. .10 with theme labels / explanations and a detail pane per motion .
"""
st . subheader ( " 🔬 SVD Components — top contributing motions " )
# Political polarisation themes per SVD component (1-indexed, window=2025)
SVD_THEMES : dict [ int , dict [ str , str ] ] = {
1 : {
" label " : " Regulering vs. status-quo " ,
" explanation " : (
" Deze as onderscheidt moties die inzetten op nieuwe regelgeving en "
" handhaving (kansspelen, EU-harmonisatie, parlementaire procedures) van moties "
" die eerder kiezen voor continuïteit of deregulering. "
" Positieve scores wijzen op meer reguleringsbereidheid. "
) ,
} ,
2 : {
" label " : " Progressief activisme vs. pragmatisch bestuur " ,
" explanation " : (
" De tweede as capteert het spanningsveld tussen linkse-progressieve eisen "
" (Israël-sancties, stikstofminister wantrouwen, klimaatrechtszaken) en "
" pragmatisch bestuur. Negatieve scores corresponderen met progressief-activistische "
" moties; positieve scores met de bestuurspragmatische kant. "
) ,
} ,
3 : {
" label " : " Eigendomsvrijheid en soevereiniteit vs. staatssturing " ,
" explanation " : (
" De sterkste as in het stempatroon (hoogste absolute scores). Hij stelt "
" het recht op vrije keuze van de eigenaar en nationale/individuele soevereiniteit "
" tegenover overheidsinterventie (woningmarkt, detentiebeleid, NGO-regulering). "
" Negatieve scores vertegenwoordigen een voorkeur voor eigendomsvrijheid; "
" positieve scores voor staatssturing. "
) ,
} ,
4 : {
" label " : " Betaalbaarheid publieke diensten vs. vrijhandel " ,
" explanation " : (
" Deze as weerspiegelt het debat over betaalbare zorg, openbaar vervoer en "
" defensie-infrastructuur (positief) tegenover internationale handelsakkoorden "
" zoals EU-Mercosur (negatief). Het scheidt partijen die nationale "
" dienstverlening centraal stellen van partijen die vrijhandel prioriteren. "
) ,
} ,
5 : {
" label " : " Confessioneel-conservatief vs. seculier-progressief " ,
" explanation " : (
" Een klassieke waarden-as: ChristenUnie- en SGP-moties over euthanasie, "
" ' Moeder en Kind ' -fonds, kerkruimte en gezinsbelasting scoren positief. "
" Deze as reflecteert het diepste sociale-waardenverschil in de Kamer. "
) ,
} ,
6 : {
" label " : " Bescherming kwetsbare groepen vs. marktwerking " ,
" explanation " : (
" Positieve scores: bescherming van pgb-houders, christelijke minderheden in "
" asielbeleid, en kinderopvangtoeslag voor middeninkomens. Negatieve scores: "
" meer marktwerking bij arbeidsmigratie en internationale hulp. Deze as "
" toont het spanningsveld tussen sociale bescherming en liberalisering. "
) ,
} ,
7 : {
" label " : " Democratische controle en sociale rechten vs. marktwerking " ,
" explanation " : (
" Moties die parlementaire controle op buitenlandbeleid (Ukraine-coalitie), "
" sociale rechten (BOSA-sportsubsidies, toeslagencompensatie) en publieke zorg "
" bepleiten scoren positief. Private equity in de zorg en marktgerichte "
" warmteoplossingen scoren negatief. "
) ,
} ,
8 : {
" label " : " Orde, veiligheid en soevereiniteit vs. sociale hervorming " ,
" explanation " : (
" Veiligheidsmoties (illegaal vuurwapen, Dutch Dome, controversiële-onderwerpenlijst) "
" en soevereiniteitsmoties domineren de negatieve pool. "
" Positief: sociaal-hervormende moties zoals afschaffing van de kostendelersnorm, "
" hernieuwbare energie en stikstof-maatwerk. Een klassieke links-rechts "
" veiligheid-versus-sociale-hervorming kloof. "
) ,
} ,
9 : {
" label " : " Caribisch koninkrijksbeleid en institutioneel toezicht " ,
" explanation " : (
" Deze specifiekere as bundelt moties over Caribische defensie en Syrische "
" VN-coalitie (positief) met zorgtoezicht-WNT-normen (negatief). Het "
" weerspiegelt een combinatie van koninkrijks-betrokkenheid en institutioneel "
" toezicht als onderscheidende dimensie. "
) ,
} ,
10 : {
" label " : " Arbeidsmigratie, dierenwelzijn en zorgtoezicht " ,
" explanation " : (
" Positieve pool: zorgfraudetoezicht (Wtza), EU-blauwe kaart voor "
" kennismigranten en dierenwelzijn in de kalverhouderij. Negatieve pool: "
" vitale productie onshoren en EU-Israël-betrekkingen. Een gemengde as die "
" pragmatisch migratiemanagement combineert met dierenwelzijn en zorgregulering. "
) ,
} ,
}
st . subheader ( " 🔬 SVD Assen — politieke polarisatiethema ' s " )
st . markdown (
" Elke SVD-as representeert een latente politieke dimensie afgeleid uit stempatronen "
" van alle Kamerleden. De top-10 moties per as zijn uniek (geen overlap) en illustreren "
" het spanningsveld dat de as beschrijft. "
)
json_path = os . path . join ( " thoughts " , " explorer " , " top_svd_top_motions.json " )
if not os . path . exists ( json_path ) :
@ -651,7 +751,7 @@ def build_svd_components_tab(db_path: str) -> None:
st . info ( " Geen top-moties in dataset " )
return
st . caption ( f " Top SVD contributors computed for window: { window } " )
st . caption ( f " Top SVD-bijdragers berekend voor venster: ** { window } ** " )
# Build mapping component -> list of motions (deduplicate by motion_id per component)
comp_map : dict [ int , list ] = { }
@ -663,16 +763,38 @@ def build_svd_components_tab(db_path: str) -> None:
bucket . append ( r )
comp_options = sorted ( comp_map . keys ( ) )
comp_sel = st . selectbox ( " Component " , options = comp_options , index = 0 )
# Build display labels for selectbox: "As 1 — Regulering vs. status-quo"
def _comp_label ( c : int ) - > str :
theme = SVD_THEMES . get ( c , { } )
lbl = theme . get ( " label " , " " )
return f " As { c } — { lbl } " if lbl else f " As { c } "
comp_display = [ _comp_label ( c ) for c in comp_options ]
comp_sel_idx = st . selectbox (
" Selecteer SVD-as " ,
options = list ( range ( len ( comp_options ) ) ) ,
format_func = lambda i : comp_display [ i ] ,
index = 0 ,
)
comp_sel = comp_options [ comp_sel_idx ]
# Show theme explanation
theme = SVD_THEMES . get ( comp_sel , { } )
if theme :
st . info ( f " ** { theme [ ' label ' ] } ** — { theme [ ' explanation ' ] } " )
motions = comp_map . get ( comp_sel , [ ] )
col1 , col2 = st . columns ( [ 1 , 2 ] )
with col1 :
st . markdown ( " **Top motions (title)** " )
motions = comp_map . get ( comp_sel , [ ] )
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 } "
if st . button ( f " { mid } : { title [ : 80 ] } " , key = f " btn_ { comp_sel } _ { 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
with col2 :
@ -701,10 +823,10 @@ def build_svd_components_tab(db_path: str) -> None:
if row [ 4 ] and str ( row [ 4 ] ) . startswith ( " http " ) :
st . markdown ( f " [🔗 Bekijk op Tweede Kamer]( { row [ 4 ] } ) " )
if row [ 5 ] :
with st . expander ( " Show body tex t" ) :
with st . expander ( " Toon volledige teks t" ) :
st . write ( row [ 5 ] )
else :
st . info ( f " Metadata not found in DB for motion { sel_mid } " )
else :
st . info ( f " Metadata not found in DB for motion { sel_mid } " )
def build_mp_quiz_tab ( db_path : str ) - > None :