Files
signal-platform/.kiro/specs/dashboard-enhancements/tasks.md
Dennis Thiessen 181cfe6588
Some checks failed
Deploy / lint (push) Failing after 8s
Deploy / test (push) Has been skipped
Deploy / deploy (push) Has been skipped
major update
2026-02-27 16:08:09 +01:00

220 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Implementation Plan: Dashboard Enhancements
## Overview
Incremental implementation of four dashboard enhancements: sentiment drill-down, fundamentals drill-down, chart zoom/crosshair, and S/R zone clustering. Each feature area is built backend-first (model → service → schema → router) then frontend, with tests alongside implementation. All changes are additive to existing components.
## Tasks
- [x] 1. Sentiment drill-down — backend
- [x] 1.1 Add `reasoning` and `citations_json` columns to `SentimentScore` model and create Alembic migration
- Add `reasoning: Mapped[str] = mapped_column(Text, nullable=False, default="")` and `citations_json: Mapped[str] = mapped_column(Text, nullable=False, default="[]")` to `app/models/sentiment.py`
- Create Alembic migration with `server_default` so existing rows are backfilled
- _Requirements: 1.1, 1.2, 1.3_
- [x] 1.2 Update `OpenAISentimentProvider` to extract reasoning and citations from OpenAI response
- Add `reasoning` and `citations` fields to the `SentimentData` dataclass
- Extract `reasoning` from the parsed JSON response body
- Iterate `response.output` items for `url_citation` annotations, collect `{"url": ..., "title": ...}` dicts
- Return empty citations list when no annotations exist (no error)
- _Requirements: 1.1, 1.2, 1.4_
- [x] 1.3 Update `sentiment_service.store_sentiment()` to persist reasoning and citations
- Accept `reasoning` and `citations` parameters
- Serialize citations to JSON string before storing
- _Requirements: 1.3_
- [x] 1.4 Update sentiment schema and router to include reasoning and citations in API response
- Add `CitationItem` model and `reasoning`/`citations` fields to `SentimentScoreResult` in `app/schemas/sentiment.py`
- Deserialize `citations_json` when building the response, catch `JSONDecodeError` and default to `[]`
- _Requirements: 1.3_
- [ ]* 1.5 Write property tests for sentiment reasoning and citations extraction
- **Property 1: Sentiment reasoning extraction** — Generate random JSON with reasoning fields, verify extraction
- **Validates: Requirements 1.1**
- **Property 2: Sentiment citations extraction** — Generate mock OpenAI responses with 010 annotations, verify citations list
- **Validates: Requirements 1.2, 1.4**
- [ ]* 1.6 Write property test for sentiment data round-trip
- **Property 3: Sentiment data round-trip** — Generate random reasoning + citations, store, retrieve via test client, compare
- **Validates: Requirements 1.3**
- [x] 2. Sentiment drill-down — frontend
- [x] 2.1 Add `CitationItem`, `reasoning`, and `citations` fields to `SentimentScore` type in `frontend/src/lib/types.ts`
- _Requirements: 1.3, 2.2, 2.3_
- [x] 2.2 Add expandable detail section to `SentimentPanel`
- Add `useState<boolean>(false)` for expand/collapse toggle
- Render chevron toggle button below summary metrics
- When expanded: show reasoning text (or "No reasoning available" placeholder if empty) and citations as clickable `<a>` links
- When collapsed: hide reasoning and citations, keep summary metrics visible
- Default to collapsed state on initial render
- Preserve glassmorphism UI style
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5_
- [ ]* 2.3 Write property tests for SentimentPanel drill-down
- **Property 4: Expanded sentiment detail displays all data** — Generate random reasoning and citations, render expanded, verify DOM content
- **Validates: Requirements 2.2, 2.3**
- **Property 5: Sentiment detail collapse hides content** — Expand then collapse, verify summary visible and detail hidden
- **Validates: Requirements 2.4**
- [x] 3. Checkpoint — Sentiment drill-down complete
- Ensure all tests pass, ask the user if questions arise.
- [x] 4. Fundamentals drill-down — backend
- [x] 4.1 Add `unavailable_fields_json` column to fundamental model and create Alembic migration
- Add `unavailable_fields_json: Mapped[str] = mapped_column(Text, nullable=False, default="{}")` to `app/models/fundamental.py`
- Add column to the same Alembic migration as sentiment columns (or a new one if migration 1.1 is already applied), with `server_default='{}'`
- _Requirements: 3.1, 3.2_
- [x] 4.2 Update `FMPFundamentalProvider` to record 402 reasons in `unavailable_fields`
- Add `unavailable_fields` field to `FundamentalData` dataclass
- In `_fetch_json_optional`, when HTTP 402 is received, map endpoint to field name: `ratios-ttm``pe_ratio`, `financial-growth``revenue_growth`, `earnings``earnings_surprise`
- Record `"requires paid plan"` as the reason for each affected field
- _Requirements: 3.1_
- [x] 4.3 Update `fundamental_service` to persist `unavailable_fields`
- Serialize `unavailable_fields` dict to JSON string before storing
- _Requirements: 3.2_
- [x] 4.4 Update fundamentals schema and router to include `unavailable_fields` in API response
- Add `unavailable_fields: dict[str, str] = {}` to `FundamentalResponse` in `app/schemas/fundamental.py`
- Deserialize `unavailable_fields_json` when building the response, catch `JSONDecodeError` and default to `{}`
- _Requirements: 3.2_
- [ ]* 4.5 Write property tests for FMP 402 reason recording and round-trip
- **Property 6: FMP 402 reason recording** — Generate random 402/200 combinations for 3 endpoints, verify unavailable_fields mapping
- **Validates: Requirements 3.1**
- **Property 7: Fundamentals unavailable_fields round-trip** — Generate random dicts, store, retrieve, compare
- **Validates: Requirements 3.2**
- [x] 5. Fundamentals drill-down — frontend
- [x] 5.1 Add `unavailable_fields` to `FundamentalResponse` type in `frontend/src/lib/types.ts`
- _Requirements: 3.2, 3.3_
- [x] 5.2 Update `FundamentalsPanel` to show unavailability reasons and expandable detail section
- When a metric is null and `unavailable_fields[field_name]` exists, display reason text in amber instead of "—"
- When a metric is null and no reason exists, display "—"
- Add expand/collapse toggle below summary metrics (default collapsed)
- When expanded: show data source name ("FMP"), fetch timestamp, and list of unavailable fields with reasons
- When collapsed: hide detail, keep summary metrics visible
- Preserve glassmorphism UI style
- _Requirements: 3.3, 3.4, 4.1, 4.2, 4.3, 4.4_
- [ ]* 5.3 Write property tests for FundamentalsPanel display logic
- **Property 8: Null field display depends on reason existence** — Generate random FundamentalResponse with various null/reason combos, verify displayed text
- **Validates: Requirements 3.3, 3.4**
- **Property 9: Fundamentals expanded detail content** — Generate random response, expand, verify source/timestamp/reasons in DOM
- **Validates: Requirements 4.2**
- [x] 6. Checkpoint — Fundamentals drill-down complete
- Ensure all tests pass, ask the user if questions arise.
- [x] 7. S/R zone clustering — backend
- [x] 7.1 Implement `cluster_sr_zones()` function in `app/services/sr_service.py`
- Sort levels by price ascending
- Greedy merge: walk sorted levels, merge if within tolerance % of current cluster midpoint
- Compute zone: low, high, midpoint, strength (sum capped at 100), level_count
- Tag zone type: "support" if midpoint < current_price, else "resistance"
- Sort by strength descending
- If `max_zones` set, return top N; if 0 or negative, return empty list
- Handle empty input by returning empty list
- _Requirements: 7.1, 7.2, 7.3, 7.4, 8.2_
- [x] 7.2 Add `SRZoneResult` schema and update `SRLevelResponse` in `app/schemas/sr_level.py`
- Add `SRZoneResult` model with `low`, `high`, `midpoint`, `strength`, `type`, `level_count`
- Add `zones: list[SRZoneResult] = []` to `SRLevelResponse`
- _Requirements: 7.2, 9.1_
- [x] 7.3 Update S/R router to accept `max_zones` parameter and return zones
- Add `max_zones: int = 6` query parameter to the S/R levels endpoint
- Call `cluster_sr_zones()` with fetched levels and current price
- Include zones in the response
- _Requirements: 8.1, 8.3_
- [ ]* 7.4 Write property tests for S/R zone clustering
- **Property 14: Clustering merges nearby levels** — Generate random level sets and tolerances, verify no two zones have midpoints within tolerance
- **Validates: Requirements 7.2**
- **Property 15: Zone strength is capped sum** — Generate random level sets, cluster, verify strength = min(100, sum)
- **Validates: Requirements 7.3**
- **Property 16: Zone type tagging** — Generate random zones and current prices, verify support/resistance tagging
- **Validates: Requirements 7.4**
- **Property 17: Zone filtering returns top N by strength** — Generate random zone sets and limits, verify top-N selection
- **Validates: Requirements 8.2**
- [x] 8. Checkpoint — S/R clustering backend complete
- Ensure all tests pass, ask the user if questions arise.
- [x] 9. Chart enhancements — zoom and pan
- [x] 9.1 Add `SRZone` and `SRLevelResponse.zones` types to `frontend/src/lib/types.ts`
- _Requirements: 9.1_
- [x] 9.2 Implement zoom (mouse wheel) on `CandlestickChart`
- Add `visibleRange: { start: number, end: number }` state initialized to full dataset
- Add `onWheel` handler: positive delta narrows range (zoom in), negative widens (zoom out)
- Clamp visible range to min 10 bars, max full dataset length
- Disable zoom if dataset has fewer than 10 bars
- Slice data by `visibleRange` for rendering
- Debounce zoom via `requestAnimationFrame`
- _Requirements: 5.1, 5.2, 5.3, 5.5_
- [x] 9.3 Implement pan (click-drag) on `CandlestickChart`
- Add `isPanning` and `panStartX` state
- `onMouseDown` starts pan, `onMouseMove` shifts visible range proportionally, `onMouseUp` ends pan
- Pan only active when zoomed in (visible range < full dataset)
- Clamp range to dataset bounds
- _Requirements: 5.4_
- [x] 9.4 Implement crosshair overlay on `CandlestickChart`
- Add `crosshair: { x: number, y: number } | null` state
- `onMouseMove` updates crosshair position
- Draw vertical line at cursor x spanning full chart height
- Draw horizontal line at cursor y spanning full chart width
- Display price label on y-axis at horizontal line position
- Display date label on x-axis at vertical line position
- `onMouseLeave` clears crosshair
- _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5_
- [ ]* 9.5 Write property tests for chart zoom/pan invariants
- **Property 10: Zoom adjusts visible range proportionally** — Generate random datasets and wheel deltas, verify range narrows/widens
- **Validates: Requirements 5.1, 5.2, 5.3**
- **Property 11: Pan shifts visible range** — Generate random ranges and drag deltas, verify shift without width change
- **Validates: Requirements 5.4**
- **Property 12: Zoom range invariant** — Generate random zoom/pan sequences, verify range bounds always valid
- **Validates: Requirements 5.5**
- **Property 13: Coordinate-to-value mapping** — Generate random chart configs, verify yToPrice and xToBarIndex mappings
- **Validates: Requirements 6.3, 6.4**
- [x] 10. S/R zone rendering on chart
- [x] 10.1 Update `CandlestickChart` to accept `zones` prop and render shaded zone rectangles
- Accept `zones?: SRZone[]` prop
- Render each zone as a semi-transparent rectangle spanning low→high price range across full chart width
- Use green shading (rgba) for support zones, red shading for resistance zones
- Draw zones behind candlestick bodies (render zones first, then candles)
- Display label with midpoint price and strength score for each zone
- Re-render zones correctly at every zoom level using current price scale
- _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5_
- [x] 10.2 Update `SROverlay` and `TickerDetailPage` to pass zones to `CandlestickChart`
- Update `useTickerDetail` hook or `SROverlay` to extract zones from the S/R API response
- Pass zones array to `CandlestickChart` component
- Default to max 6 zones (handled by backend `max_zones=6` default)
- _Requirements: 8.3, 9.1_
- [x] 10.3 Ensure S/R overlays re-render correctly at all zoom levels
- Verify zone rectangles reposition when zoom/pan changes the visible price scale
- Handle single-level zones (low == high) as thin 2px-height rectangles
- _Requirements: 5.6, 9.5_
- [x] 11. Final checkpoint — All features integrated
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- Each task references specific requirements for traceability
- Checkpoints ensure incremental validation after each feature area
- Property tests validate universal correctness properties from the design document
- The Alembic migration for sentiment and fundamentals columns should ideally be a single migration file
- S/R zones are computed on-the-fly (not persisted), so no additional migration is needed for zones