# Implementation Plan: UX Improvements
## Overview
Implement four UX improvements: balanced S/R zone selection in the backend, visible levels filtering (backend + frontend), Trade Scanner explainer banner and R:R analysis columns, and Rankings weight slider form. Backend changes use Python (FastAPI/Pydantic), frontend changes use TypeScript (React).
## Tasks
- [x] 1. Implement balanced S/R zone selection in `cluster_sr_zones()`
- [x] 1.1 Refactor `cluster_sr_zones()` to use interleave-based balanced selection
- In `app/services/sr_service.py`, after clustering and computing zones, split zones into `support_zones` and `resistance_zones` pools sorted by strength descending
- Implement round-robin interleave picking: alternate strongest from each pool until `max_zones` is reached or both pools exhausted
- If one pool is exhausted, fill remaining slots from the other pool
- Sort final selected zones by strength descending before returning
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6_
- [ ]* 1.2 Write property test: balanced zone selection guarantees both types
- **Property 1: Balanced zone selection guarantees both types**
- **Validates: Requirement 1.1**
- In `tests/unit/test_cluster_sr_zones.py`, use Hypothesis to generate sets of levels with at least one support and one resistance zone, verify output contains at least one of each type when `max_zones >= 2`
- [ ]* 1.3 Write property test: interleave selection correctness
- **Property 2: Interleave selection correctness**
- **Validates: Requirements 1.2, 1.3**
- Verify the selected zones match the expected round-robin interleave from support and resistance pools
- [ ]* 1.4 Write property test: zone output sorted by strength
- **Property 3: Zone output is sorted by strength**
- **Validates: Requirement 1.4**
- For any input, verify returned zones are sorted by strength descending
- [x] 1.5 Update existing unit tests for balanced selection behavior
- Update `tests/unit/test_cluster_sr_zones.py` to add tests for: mixed support/resistance input produces balanced output, all-support input fills from support only, all-resistance input fills from resistance only, single zone edge case
- _Requirements: 1.1, 1.2, 1.3, 1.5, 1.6_
- [x] 2. Implement `visible_levels` filtering in backend
- [x] 2.1 Add `visible_levels` field to `SRLevelResponse` schema
- In `app/schemas/sr_level.py`, add `visible_levels: list[SRLevelResult] = []` to `SRLevelResponse`
- _Requirements: 2.1, 2.3_
- [x] 2.2 Compute `visible_levels` in the SR levels router
- In `app/routers/sr_levels.py`, after computing zones, filter `level_results` to only those whose `price_level` falls within the `[low, high]` range of at least one zone
- Set the filtered list as `visible_levels` on the `SRLevelResponse`
- When zones list is empty, `visible_levels` should be empty
- _Requirements: 2.1, 2.2, 2.4_
- [ ]* 2.3 Write property test: visible levels subset within zone bounds
- **Property 4: Visible levels are a subset within zone bounds**
- **Validates: Requirements 2.1, 2.2**
- Verify every entry in `visible_levels` appears in `levels` and has a price within at least one zone's `[low, high]` range
- [x] 2.4 Update router unit tests for `visible_levels`
- In `tests/unit/test_sr_levels_router.py`, add tests verifying: `visible_levels` is present in response, `visible_levels` contains only levels within zone bounds, `visible_levels` is empty when zones are empty
- _Requirements: 2.1, 2.2, 2.4_
- [x] 3. Checkpoint - Ensure all backend tests pass
- Ensure all tests pass, ask the user if questions arise.
- [x] 4. Update frontend types and S/R levels table filtering
- [x] 4.1 Add `visible_levels` to frontend `SRLevelResponse` type
- In `frontend/src/lib/types.ts`, add `visible_levels: SRLevel[]` to the `SRLevelResponse` interface
- _Requirements: 2.1_
- [x] 4.2 Update `TickerDetailPage` to use `visible_levels` for the S/R table
- In `frontend/src/pages/TickerDetailPage.tsx`, change `sortedLevels` to derive from `srLevels.data.visible_levels` instead of `srLevels.data.levels`
- Keep sorting by strength descending
- Hide the S/R Levels Table section when `visible_levels` is empty
- Maintain existing color coding (green for support, red for resistance)
- _Requirements: 3.1, 3.2, 3.3, 3.4_
- [x] 5. Add Trade Scanner explainer banner and R:R analysis columns
- [x] 5.1 Add explainer banner to `ScannerPage`
- In `frontend/src/pages/ScannerPage.tsx`, add a static informational banner above the filter controls
- Banner text: describe that the scanner identifies asymmetric risk-reward trade setups using S/R levels as targets and ATR-based stops
- Banner should be visible on initial page load without user interaction
- _Requirements: 4.1, 4.2, 4.3_
- [x] 5.2 Add R:R analysis columns to `TradeTable`
- In `frontend/src/components/scanner/TradeTable.tsx`, add computed columns: Risk $ (`|entry_price - stop_loss|`), Reward $ (`|target - entry_price|`), % to Stop (`risk / entry * 100`), % to Target (`reward / entry * 100`)
- Color-code the existing R:R ratio column: green for ≥ 3.0, amber for ≥ 2.0, red for < 2.0
- Update the `SortColumn` type and `columns` array to include the new columns
- Update `sortTrades` in `ScannerPage.tsx` to handle sorting by new computed columns
- _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8_
- [ ]* 5.3 Write property test: trade analysis computation correctness
- **Property 5: Trade analysis computation correctness**
- **Validates: Requirements 5.2, 5.3, 5.4, 5.5**
- Using fast-check, for any trade with positive entry_price, stop_loss, and target, verify `risk_amount == |entry_price - stop_loss|`, `reward_amount == |target - entry_price|`, `stop_pct == risk_amount / entry_price * 100`, `target_pct == reward_amount / entry_price * 100`
- [x] 6. Convert Rankings weight inputs to sliders
- [x] 6.1 Replace number inputs with range sliders in `WeightsForm`
- In `frontend/src/components/rankings/WeightsForm.tsx`, replace `` with ``
- On mount, convert API decimal weights to 0–100 scale: `Math.round(w * 100)`
- Display current whole-number value next to each slider
- Show humanized label (replace underscores with spaces)
- _Requirements: 6.1, 6.2, 6.3, 6.4_
- [x] 6.2 Implement weight normalization on submit
- On submit, normalize slider values: divide each by the sum of all values
- Disable submit button when all sliders are zero
- Show validation message "At least one weight must be greater than zero" when all are zero
- Send normalized decimal weights via existing `useUpdateWeights` mutation
- _Requirements: 7.1, 7.2, 7.3, 7.4_
- [ ]* 6.3 Write property test: weight conversion round-trip
- **Property 6: Weight conversion round-trip**
- **Validates: Requirement 6.3**
- Using fast-check, verify that converting decimal weights to slider scale and normalizing back preserves relative proportions within floating-point tolerance
- [ ]* 6.4 Write property test: normalized weights sum to one
- **Property 7: Normalized weights sum to one**
- **Validates: Requirement 7.1**
- Using fast-check, for any set of slider values (integers 0–100) where at least one > 0, verify normalized weights sum to 1.0 within ±1e-9
- [x] 7. Final checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- Backend uses Python (Hypothesis for property tests), frontend uses TypeScript/React (fast-check for property tests)
- Each task references specific requirements for traceability
- Checkpoints ensure incremental validation after backend and full implementation phases
- Property tests validate universal correctness properties from the design document