# Implementation Plan: Forecast Burndown Chart ## Overview Add a per-metric forecast burndown chart to the CCP Metrics page. A pure helper function (`computeMetricForecastBurndown`) computes forecast projections from device records and historical snapshots. Two new API endpoints serve the metrics list and forecast data. A React frontend renders a metric selector and a ComposedChart (Bar + Line + ReferenceLine) using recharts. No database migrations are needed — the feature reads from existing `compliance_items` and `compliance_snapshots` tables. ## Tasks - [x] 1. Implement the computeMetricForecastBurndown helper function - [x] 1.1 Add computeMetricForecastBurndown to backend/helpers/vclHelpers.js - Implement the pure function accepting `currentDevices`, `totalAssets`, and `historicalSnapshots` - Return object with `historical`, `forecast`, and `current_snapshot` fields - Compute `current_snapshot.blockers` (devices with no resolution_date) and `current_snapshot.with_dates` (devices with a resolution_date) - Compute `compliance_pct` as `ROUND((total_assets - non_compliant) / total_assets * 100, 1)`, returning 0 when totalAssets is 0 - Generate forecast months by iterating forward from current month, decrementing non_compliant as devices reach their resolution_date month - Treat past-due resolution dates as remediated in the current month - Hold total_assets constant across all forecast data points - Terminate forecast when all dated devices are remediated or at 12-month maximum - Return empty forecast array when all devices are blockers (no resolution dates) - Export the function for use in route handlers and tests - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 6.5_ - [ ]* 1.2 Write property test: Forecast structure invariant - **Property 1: Forecast structure invariant** - **Validates: Requirements 1.4, 3.1, 3.6** - Test file: `backend/__tests__/forecast-burndown-chart.property.test.js` - [ ]* 1.3 Write property test: Blocker and with_dates partition invariant - **Property 2: Blocker and with_dates partition invariant** - **Validates: Requirements 3.2** - Test file: `backend/__tests__/forecast-burndown-chart.property.test.js` - [ ]* 1.4 Write property test: Compliance percentage formula correctness - **Property 3: Compliance percentage formula correctness** - **Validates: Requirements 3.3, 3.10** - Test file: `backend/__tests__/forecast-burndown-chart.property.test.js` - [ ]* 1.5 Write property test: Forecast non_compliant monotonicity - **Property 4: Forecast non_compliant monotonicity** - **Validates: Requirements 3.4** - Test file: `backend/__tests__/forecast-burndown-chart.property.test.js` - [ ]* 1.6 Write property test: Per-month non_compliant computation correctness - **Property 5: Per-month non_compliant computation correctness** - **Validates: Requirements 3.8** - Test file: `backend/__tests__/forecast-burndown-chart.property.test.js` - [ ]* 1.7 Write property test: Forecast horizon bound - **Property 6: Forecast horizon bound** - **Validates: Requirements 1.9, 3.9** - Test file: `backend/__tests__/forecast-burndown-chart.property.test.js` - [ ]* 1.8 Write property test: Past-due resolution dates treated as current month - **Property 7: Past-due resolution dates treated as current month** - **Validates: Requirements 6.5** - Test file: `backend/__tests__/forecast-burndown-chart.property.test.js` - [x] 2. Checkpoint - Helper function verified - Ensure all tests pass, ask the user if questions arise. - [x] 3. Implement backend API endpoints - [x] 3.1 Add GET /metrics-list endpoint to backend/routes/vclMultiVertical.js - Add route handler at `/metrics-list` with `requireAuth()` middleware - Query `compliance_items` for distinct metric_ids with active non-compliant devices where vertical IS NOT NULL - Return JSON array of `{ metric_id, device_count }` sorted by metric_id ascending - Return empty array when no metrics have active devices - Return HTTP 500 with `{ "error": "Failed to fetch metrics list" }` on database failure - _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6_ - [x] 3.2 Add GET /metric/:metricId/forecast-burndown endpoint to backend/routes/vclMultiVertical.js - Add route handler with `requireAuth()` middleware - Query active devices for the metric from `compliance_items` (status = 'active', vertical IS NOT NULL) - Determine the vertical from active devices and query `compliance_snapshots` for 3 months of historical data - Compute per-metric historical non_compliant using the ratio method from Requirement 7.2 - Include current month as the most recent historical data point computed from live data - Pass data to `computeMetricForecastBurndown` helper - Return response with `metric_id`, `historical`, `forecast`, and `current_snapshot` - Return 200 with empty arrays and zeroed snapshot when metricId has no active devices - Return HTTP 500 with `{ "error": "Failed to compute forecast burndown" }` on database failure - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.10, 6.1, 6.2, 6.3, 6.4, 6.5, 6.6, 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7_ - [ ]* 3.3 Write unit tests for API endpoints - Test metrics-list returns correct shape with mocked database - Test forecast-burndown returns correct shape with mocked database - Test authentication middleware is applied to both endpoints - Test empty/error states - Test file: `backend/__tests__/forecast-burndown-chart.test.js` - _Requirements: 1.7, 1.8, 1.10, 2.4, 2.5, 2.6_ - [x] 4. Checkpoint - Backend complete - Ensure all tests pass, ask the user if questions arise. - [x] 5. Implement frontend MetricSelector and ForecastBurndownChart components - [x] 5.1 Add MetricSelector component to frontend/src/components/pages/CCPMetricsPage.js - Fetch metrics list from `/api/compliance/vcl-multi/metrics-list` on mount - Display dropdown showing each metric_id with active non-compliant device count - Auto-select first metric and trigger forecast data fetch on load - Handle loading state (non-interactive while fetching) - Handle empty state ("No metrics with active non-compliant devices") - Handle error state (inline error with AlertCircle icon, red border) - On selection change, trigger `onMetricSelect` callback - _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8_ - [x] 5.2 Add ForecastBurndownChart component to frontend/src/components/pages/CCPMetricsPage.js - Fetch forecast data from `/api/compliance/vcl-multi/metric/:metricId/forecast-burndown` when metric changes - Render recharts ComposedChart with: - Blue Bar for total_assets (left Y-axis) - Orange Bar for non_compliant (left Y-axis) - Green Line for compliance_pct (right Y-axis, 0-100%) - ReferenceLine as vertical divider between historical and forecast sections - Render forecast data points at 50% opacity - Display raw device count labels inside bars - Display compliance percentage labels on the trend line - X-axis labeled with months (YYYY-MM format) - Left Y-axis scaled to max total_assets, right Y-axis 0-100% - Handle loading state (loading indicator in chart area) - Handle error state (inline error with AlertCircle icon and description) - Handle empty data state ("No data available for this metric") - Handle historical-only state (no divider line when forecast is empty) - Discard stale responses on rapid metric switching (race condition handling) - _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, 4.10, 4.11, 4.12, 4.13, 4.14, 5.9_ - [x] 5.3 Wire MetricSelector and ForecastBurndownChart into CCPMetricsPage layout - Add state for selected metric - Place MetricSelector above ForecastBurndownChart in a dedicated section - Connect selection change to chart data fetch - Follow existing inline-style patterns from AggregatedBurndownChart and TrendChart - _Requirements: 4.1, 5.1, 5.4_ - [x] 6. 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 - Each task references specific requirements for traceability - Checkpoints ensure incremental validation - Property tests validate universal correctness properties from the design document - The helper function is pure and stateless — all 7 property tests exercise it in isolation without database mocks - All backend changes are in `backend/helpers/vclHelpers.js` and `backend/routes/vclMultiVertical.js` - All frontend changes are within `frontend/src/components/pages/CCPMetricsPage.js` - Property-based tests use `fast-check` (already in project dependencies) - No database migrations needed — uses existing `compliance_items` and `compliance_snapshots` tables ## Task Dependency Graph ```json { "waves": [ { "id": 0, "tasks": ["1.1"] }, { "id": 1, "tasks": ["1.2", "1.3", "1.4", "1.5", "1.6", "1.7", "1.8"] }, { "id": 2, "tasks": ["3.1", "3.2"] }, { "id": 3, "tasks": ["3.3"] }, { "id": 4, "tasks": ["5.1", "5.2"] }, { "id": 5, "tasks": ["5.3"] } ] } ```