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.
5.1 KiB
5.1 KiB
| date | topic | status |
|---|---|---|
| 2026-03-24 | SVD Tab Redesign | validated |
SVD Tab Redesign
Problem Statement
The SVD components tab lists all motions in a single column, shows detail in a separate right-hand pane (session state driven), has no party-level spatial overview, and does not show voting breakdowns. Users need a cleaner split of positive/negative motions, inline expand/collapse, a party axis chart, and full voting data per motion.
Constraints
- All UI text in Dutch
- Use
PARTY_COLOURSandplotly.express/plotly.graph_objects(already imported) - No new DB tables — reuse
svd_vectors(entity_type='mp', window_id='2025') andmotions.voting_results - No push to remote without explicit instruction
Approach
Restructure build_svd_components_tab in explorer.py — no new files, no new tables. Three additions to the function:
- New
@st.cache_datahelperload_party_axis_scores(db_path)inside the module (top-level, not nested) — queries party SVD vectors once, returns{party: [float * 50]} - New private render helper
_render_party_axis_chart(party_scores, comp_sel)— builds and returns a Plotly figure for the 1D horizontal scatter - Replace motion list + session state + right column with
st.expanderper motion, split into two equal columns by pole sign
Architecture
Single file change: explorer.py.
load_party_axis_scores(db_path)— new@st.cache_datatop-level function_render_party_axis_chart(party_scores, comp_sel)— new private render helperbuild_svd_components_tab(db_path)— restructured; no new function signature
Components
load_party_axis_scores(db_path: str) -> dict[str, list[float]]
@st.cache_data- Queries
svd_vectorsWHEREentity_type='mp'ANDwindow_id='2025'ANDentity_id IN (CURRENT_PARLIAMENT_PARTIES) - Parses JSON vector per row, returns
{party_name: [float * 50]} CURRENT_PARLIAMENT_PARTIES= frozenset of 15 parties: PVV, VVD, NSC, BBB, D66, GroenLinks-PvdA, CDA, SP, ChristenUnie, SGP, Volt, DENK, PvdD, JA21, FVD
_render_party_axis_chart(party_scores, comp_sel)
- Extracts score at index
comp_sel - 1for each party - Creates a
go.Figurewithgo.Scatter:x= list of scores,y= list of zerosmode='markers+text',text= party name,textposition='top center'- Marker colour from
PARTY_COLOURS(withChristenUniealias →#0288D1) - Marker size = 12, hover text =
"{party}: {score:.3f}"
- Adds a thin horizontal baseline (
go.Scatterwith two x extremes, y=0, grey line, no hover) - Layout:
height=180, y-axis hidden, x-axis labeled"← Negatieve pool | Positieve pool →", no legend, minimal margins - Renders via
st.plotly_chart(fig, use_container_width=True)
build_svd_components_tab restructure
Remove:
col1, col2 = st.columns([1, 2])motion layout- All
st.buttonmotion-list code st.session_state["svd_selected_mid"]read/write- Right-column detail pane (single DB query per selected motion)
Add:
- Call
load_party_axis_scores(db_path)→ pass to_render_party_axis_chart - Batch DB query:
SELECT id, title, date, policy_area, url, body_text, voting_results FROM motions WHERE id IN (...)for all motion IDs ofcomp_sel— one query, results asdict[int, row] - Split motions:
pos_motions = [m for m in motions if m["score"] >= 0],neg_motions = [m for m in motions if m["score"] < 0] pcol, ncol = st.columns(2)pcol:st.success("▲ Positieve pool: {positive_pole}")+ expanders forpos_motionsncol:st.error("▼ Negatieve pool: {negative_pole}")+ expanders forneg_motions
- Each expander:
st.expander(f"▲/▼ {title[:80]}")- Inside:
st.caption(f"📅 {date} | {policy_area}"), URL link if present st.expander("Toon volledige tekst")→st.write(body_text)_render_voting_results(voting_results_json)
- Inside:
Data Flow
DB: svd_vectors (entity_type='mp', window='2025')
→ load_party_axis_scores() [cached]
→ _render_party_axis_chart(scores, comp_sel)
JSON: top_svd_top_motions.json
→ motion ids for comp_sel
DB: motions WHERE id IN (...) [one batch query]
→ {motion_id: row} lookup
SVD_THEMES[comp_sel]
→ explanation, positive_pole, negative_pole
pos_motions / neg_motions split
→ st.columns(2)
→ st.expander per motion
→ metadata + voting_results → _render_voting_results()
Error Handling
load_party_axis_scoresreturns empty dict on any DB error →_render_party_axis_chartrendersst.caption("Partijdata niet beschikbaar")and returns early- Batch motion query exception →
motion_details={}→ expanders show title only, no metadata/voting - Individual missing
voting_results→_render_voting_resultsalready handles None/empty gracefully
Testing Strategy
- Existing syntax check (
python3 -c "import ast; ast.parse(...)") after edit - Manual smoke test: load the tab, select each axis, expand a motion, verify voting data shows
- No new unit tests required (UI-only change; underlying data functions are unchanged)
Open Questions
- None. Design is complete and fully determined by codebase context.