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

13 KiB
Raw Blame History

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

  • 1. Sentiment drill-down — backend

    • 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
    • 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
    • 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
    • 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
  • 2. Sentiment drill-down — frontend

    • 2.1 Add CitationItem, reasoning, and citations fields to SentimentScore type in frontend/src/lib/types.ts

      • Requirements: 1.3, 2.2, 2.3
    • 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
  • 3. Checkpoint — Sentiment drill-down complete

    • Ensure all tests pass, ask the user if questions arise.
  • 4. Fundamentals drill-down — backend

    • 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
    • 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-ttmpe_ratio, financial-growthrevenue_growth, earningsearnings_surprise
      • Record "requires paid plan" as the reason for each affected field
      • Requirements: 3.1
    • 4.3 Update fundamental_service to persist unavailable_fields

      • Serialize unavailable_fields dict to JSON string before storing
      • Requirements: 3.2
    • 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
  • 5. Fundamentals drill-down — frontend

    • 5.1 Add unavailable_fields to FundamentalResponse type in frontend/src/lib/types.ts

      • Requirements: 3.2, 3.3
    • 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
  • 6. Checkpoint — Fundamentals drill-down complete

    • Ensure all tests pass, ask the user if questions arise.
  • 7. S/R zone clustering — backend

    • 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
    • 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
    • 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
  • 8. Checkpoint — S/R clustering backend complete

    • Ensure all tests pass, ask the user if questions arise.
  • 9. Chart enhancements — zoom and pan

    • 9.1 Add SRZone and SRLevelResponse.zones types to frontend/src/lib/types.ts

      • Requirements: 9.1
    • 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
    • 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
    • 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
  • 10. S/R zone rendering on chart

    • 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
    • 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
    • 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
  • 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