# Code Patterns ## 1. Page Wrapper Pattern Thin Streamlit page files delegate to core modules. Pages contain only route logic, not business logic. **Example** (pages/1_🗳️_Stemwijzer.py): ```python import streamlit as st from quiz_module import render_quiz_page st.set_page_config(...) render_quiz_page() ``` **Example** (pages/2_🔍_Explorer.py): ```python import streamlit as st from explorer import render_explorer st.set_page_config(...) render_explorer() ``` **Rule**: Pages should have <20 lines of logic. All complexity lives in modules. --- ## 2. Pipeline Pattern Data flows: fetch → transform → store **Location**: `pipeline/` directory **Pattern**: ```python def run_pipeline(): raw_data = fetch_from_source() transformed = transform(raw_data) store(transformed) def fetch_from_source(): # API call or DB query ... def transform(raw): # Clean, normalize, compute derived fields ... ``` **Usage**: SVD computation pipeline, data ingestion, motion processing --- ## 3. API Client Pattern HTTP client with retry/backoff for external data sources. **Pattern**: ```python import time import requests def fetch_with_retry(url, max_retries=3): for attempt in range(max_retries): try: response = requests.get(url) response.raise_for_status() return response.json() except requests.RequestException: if attempt < max_retries - 1: time.sleep(2 ** attempt) # exponential backoff else: raise ``` --- ## 4. Pure Helper Functions Functions in `explorer_helpers.py` have no side effects, no IO. **Pattern**: ```python def compute_party_coords(svd_df, party_map, window): """Pure function: same inputs → same outputs, no side effects.""" # Filter, compute, return return result_df def build_scatter_trace(df, color_col, marker_size=8): """Pure: returns Plotly trace dict, no rendering.""" trace = go.Scatter(x=df.x, y=df.y, mode='markers', ...) return trace ``` **Rule**: No `import streamlit` in helper modules. No file I/O. No global state. --- ## 5. Dummy Fallbacks for Optional Dependencies Gracefully degrade when optional packages are unavailable. **Pattern**: ```python try: import umap HAS_UMAP = True except ImportError: HAS_UMAP = False # or provide dummy stub def project_to_2d(vectors): if HAS_UMAP: return umap.UMAP().fit_transform(vectors) else: return vectors[:, :2] # fallback: just take first 2 dims ``` **Used for**: UMAP, Plotly (with fallback to altair or text-only) --- ## 6. Cached Data Loaders Expensive DB queries wrapped with `@st.cache_data`. **Pattern**: ```python @st.cache_data def load_svd_vectors(window: str) -> pd.DataFrame: return db.query("SELECT * FROM svd_vectors WHERE window = ?", window) @st.cache_data def load_party_centroids(window: str) -> pd.DataFrame: return db.query("SELECT * FROM party_centroids WHERE window = ?", window) # Clear cache when data updates @st.cache_data def load_motions(category: str | None = None) -> pd.DataFrame: ... ``` **Rule**: Use `ttl=3600` for large datasets. Use `show_spinner=False` where appropriate. --- ## 7. Plotly Dual-Layer Charts Charts built with two traces: scatter points + text annotations. **Pattern**: ```python def build_dual_layer_chart(df, x_col, y_col, label_col): # Layer 1: markers scatter = go.Scatter( x=df[x_col], y=df[y_col], mode='markers', marker=dict(size=10, color=df['color']), name='Parties' ) # Layer 2: labels (smaller, non-hoverable) labels = go.Scatter( x=df[x_col], y=df[y_col], mode='text', text=df[label_col], textposition='top center', showlegend=False ) return [scatter, labels] ``` **Used in**: Explorer tab charts, party position plots --- ## 8. Singleton Module Instances One shared instance per module, created at import time. **Pattern**: ```python # database.py class MotionDatabase: def __init__(self, db_path=None): self.conn = ibis.duckdb.connect(db_path) self._load_schema() _db = None def get_db(): global _db if _db is None: _db = MotionDatabase() return _db # At module bottom: db = MotionDatabase() # singleton instance ``` **Also used in**: `config.py` exports `config` and `PARTY_COLOURS` --- ## 9. Dataclass Config Pattern Configuration centralized in a `@dataclass`. **Pattern**: ```python from dataclasses import dataclass, field @dataclass class Config: db_path: str = "data/stemwijzer.duckdb" default_window: str = "2023" cache_ttl: int = 3600 party_colours: dict = field(default_factory=lambda: PARTY_COLOURS) def __post_init__(self): if not Path(self.db_path).exists(): raise FileNotFoundError(f"Database not found: {self.db_path}") ``` --- ## 10. Graceful Degradation with try/except Core pattern throughout: attempt operation, fall back gracefully. **Pattern**: ```python def get_political_position(mp_name, window): try: vectors = load_svd_vectors(window) return vectors[vectors['mp_name'] == mp_name]['vector_2d'].iloc[0] except (KeyError, IndexError): return [0.0, 0.0] # neutral fallback ```