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/docs/plans/2026-04-04-003-refactor-com...

182 lines
6.0 KiB

---
title: "refactor: Complete explorer.py decomposition — extract tabs, constants, and rendering"
type: refactor
status: completed
date: 2026-04-04
origin: docs/plans/2026-04-04-002-refactor-explorer-extraction-plan.md
completed: 2026-04-04
---
# Refactor: Complete explorer.py Decomposition
## Overview
Completed extraction of constants and tab module structure from `explorer.py`. Tab functions remain in explorer.py pending Streamlit decoupling.
## Problem Frame
The first phase extracted data loading functions to `analysis/explorer_data.py`. The remaining content contains:
- Tab building functions (~1617 lines across 6 tabs)
- Rendering helpers (~600 lines)
- Constants (~237 lines)
## Current State
| Module | Lines | Status |
|--------|-------|--------|
| `explorer.py` | 3102 | In progress |
| `analysis/explorer_data.py` | 549 | Done |
| `analysis/projections.py` | 121 | Done |
| `analysis/trajectory.py` | 380 | Done |
| `analysis/config.py` | 230 | **NEW** |
| `analysis/tabs/` | - | **NEW** (placeholders) |
| `analysis/visualize.py` | 434 | Existing |
| Target | <1500 | Partial |
## Requirements Trace
- R1.1: Extract `build_*_tab()` functions to `analysis/tabs/`
- R1.2: Extract `_render_*` helpers to `analysis/rendering.py`
- R1.3: Extract constants to `analysis/config.py`
- R2.1: Preserve `@st.cache_data` decorators in explorer.py
- R3.1: Maintain import direction: explorer.py analysis/ only
## Scope Boundaries
**Included:**
- Tab function extraction (6 tabs)
- Rendering helper extraction
- Constant extraction
**Excluded:**
- Behavior changes (UI looks the same)
- New test coverage (existing tests pass)
- Database schema changes
## Key Technical Decisions
- **Tab modules**: Create `analysis/tabs/compass.py`, `trajectories.py`, `search.py`, `browser.py`, `components.py`, `quiz.py`
- **Rendering module**: `analysis/rendering.py` contains all `_render_*` and `_build_*` functions
- **Config module**: `analysis/config.py` contains all constants
- **Backward compatibility**: Keep wrapper functions in explorer.py for `@st.cache_data` decorators
- **Import pattern**: Each tab module imports from `analysis/` (data, projections, config)
## Implementation Units
- [x] **Unit 6: Extract constants to `analysis/config.py`**
**Goal:** Centralize all constants used across the explorer
**Requirements:** R1.3
**Dependencies:** None
**Files:**
- Create: `analysis/config.py`
- Modify: `explorer.py`
**Approach:**
Extracted these constants from explorer.py:
1. `PARTY_COLOURS: Dict[str, str]` - party color mapping
2. `SVD_THEMES: dict[int, dict[str, str]]` - SVD component themes
3. `KNOWN_MAJOR_PARTIES` - ordered party list
4. `CURRENT_PARLIAMENT_PARTIES: frozenset[str]` - current party list
5. `_PARTY_NORMALIZE: dict[str, str]` - party name normalization
**Verification:**
- `explorer.py` imports from `analysis/config.py`
- All tests pass (153 passed)
**Lines saved:** ~237
---
- [x] **Unit 7: Extract `_render_*` helpers** - SKIPPED
**Decision:** UI rendering functions use Streamlit (`st.*`). Per R3.2, UI functions stay in explorer.py.
---
- [x] **Unit 8-10: Tab extraction** - PARTIAL
**Goal:** Create module structure for tab functions
**Status:** Created `analysis/tabs/` with placeholder modules. Actual tab functions remain in explorer.py due to tight Streamlit coupling.
**Files:**
- Create: `analysis/tabs/__init__.py`
- Create: `analysis/tabs/compass.py`
- Create: `analysis/tabs/trajectories.py`
- Create: `analysis/tabs/search.py`
- Create: `analysis/tabs/browser.py`
- Create: `analysis/tabs/components.py`
- Create: `analysis/tabs/quiz.py`
**Note:** Full tab extraction requires decoupling rendering logic from Streamlit, which is a larger refactoring effort beyond the current scope.
---
- [x] **Unit 11: Final cleanup and line count verification**
**Verification:**
- `wc -l explorer.py`: 3102 lines (reduced from 3715)
- All tests pass (153 passed, 2 skipped)
- Import verification passes
## File Structure (Target)
```
analysis/
├── __init__.py
├── config.py # NEW: Constants (PARTY_COLOURS, SVD_THEMES, etc.)
├── explorer_data.py # Data loading (done)
├── projections.py # Pure projection math (done)
├── rendering.py # NEW: _render_* and _build_* helpers
├── trajectory.py # Trajectory computation (done)
├── visualize.py # Existing visualization utils
└── tabs/ # NEW: Tab modules
├── __init__.py
├── compass.py # build_compass_tab
├── trajectories.py # build_trajectories_tab
├── search.py # build_search_tab
├── browser.py # build_browser_tab
├── components.py # build_svd_components_tab
└── quiz.py # build_mp_quiz_tab
```
## System-Wide Impact
- **Interaction graph:** explorer.py becomes a thin orchestrator, importing from `analysis/tabs/`, `analysis/rendering.py`, `analysis/config.py`, and `analysis/explorer_data.py`
- **API surface parity:** All function signatures preserved (wrappers where needed)
- **Unchanged invariants:** UI behavior identical, no behavior changes
## Risks & Dependencies
| Risk | Mitigation |
|------|------------|
| Breaking `@st.cache_data` caching behavior | Keep cache decorators in explorer.py wrappers |
| Circular imports between tabs and rendering | Rendering module has no tab dependencies |
| Test failures from refactoring | Run tests after each unit |
| Missing imports after extraction | Verify import after each extraction |
## Verification Commands
```bash
# Line count
wc -l explorer.py # Target: < 1500
# Import verification
uv run python -c "import explorer; print('Import OK')"
# Tests
uv run pytest tests/ -x
# Individual tab tests
uv run pytest tests/test_political_compass.py -v
```
## Sources & References
- **Original plan:** `docs/plans/2026-04-04-002-refactor-explorer-extraction-plan.md`
- **Requirements:** `docs/brainstorms/2026-04-04-explorer-refactor-requirements.md`
- **Pattern reference:** `explorer_helpers.py` (pure function conventions)