6.8 KiB
Implementation Plan: VCL Aggregated Burndown
Overview
Implement an aggregated cross-vertical burndown forecast feature consisting of: two new pure helper functions (deduplicateByHostname and computeAggregatedBurndown) in vclHelpers.js, a new GET /api/compliance/vcl-multi/burndown endpoint in the existing vclMultiVertical.js route file, property-based tests validating 8 correctness properties, unit tests covering edge cases and API integration, and an AggregatedBurndownChart inline component in CCPMetricsPage.js.
Tasks
-
1. Implement backend helper functions
-
1.1 Add
deduplicateByHostnamefunction tobackend/helpers/vclHelpers.js- Groups items by hostname
- For each hostname, selects the earliest non-null
resolution_dateacross all entries - If all entries for a hostname have null dates, the device is a blocker (null date preserved)
- Preserves the
verticalfrom the first entry for that hostname - Export the function from the module
- Requirements: 1.6
-
1.2 Add
computeAggregatedBurndownfunction tobackend/helpers/vclHelpers.js- Accepts an array of device objects with
hostname,resolution_date, andverticalfields - Counts total devices, blockers (null date), and with_dates (non-null date)
- Buckets with_dates devices by YYYY-MM of resolution_date into
monthlyobject - Sorts monthly keys chronologically
- Computes
projectionas cumulative remaining: starts attotal, subtracts each month's count - Sets
projected_clear_dateto the last month key if blockers = 0, otherwise null - Groups devices by vertical for
by_vertical, sorted descending by total, omitting verticals with zero devices - Each
by_verticalentry has{ vertical, total, blockers, with_dates } - Returns
{ total, blockers, with_dates, monthly, projection, projected_clear_date, by_vertical } - Export the function from the module
- Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 5.1, 5.2, 5.4
- Accepts an array of device objects with
-
* 1.3 Write property tests for
deduplicateByHostnameandcomputeAggregatedBurndown- Property 1: Partition Invariant —
blockers + with_dates = totalfor any input - Validates: Requirements 2.2
- Property 2: Monthly Bucket Conservation — sum of monthly values = with_dates
- Validates: Requirements 2.3, 1.5
- Property 3: Chronological Monthly Ordering — monthly keys in ascending YYYY-MM order
- Validates: Requirements 2.4
- Property 4: Cumulative Projection Consistency — projection[month].remaining = total - cumulative sum
- Validates: Requirements 2.5
- Property 5: Projected Clear Date Logic — null when blockers > 0, last month key when blockers = 0
- Validates: Requirements 1.7
- Property 6: Hostname Deduplication with Earliest Date — one entry per hostname, earliest non-null date
- Validates: Requirements 1.6
- Property 7: Aggregation Consistency with Per-Vertical Computation — aggregated totals = sum of per-vertical totals
- Validates: Requirements 4.1, 4.2, 4.3, 4.4
- Property 8: By-Vertical Sorting and Filtering — sorted descending by total, no zero-total entries, sum = overall total
- Validates: Requirements 5.1, 5.2, 5.4
- Test file:
backend/__tests__/vcl-aggregated-burndown.property.test.js
- Property 1: Partition Invariant —
-
-
2. Implement backend API endpoint
-
2.1 Add
GET /burndownroute tobackend/routes/vclMultiVertical.js- Query
compliance_itemsfor all active non-compliant devices across verticals - Call
deduplicateByHostnameon the query results - Call
computeAggregatedBurndownon the deduplicated devices - Map the result to the API response shape:
{ total_non_compliant, blockers, with_dates, monthly_forecast, projected_clear_date, by_vertical } - Handle database errors with 500 status and
{ error: "Database error" } - Route is protected by
requireAuth()(already applied viarouter.use) - Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9
- Query
-
* 2.2 Write unit tests for the burndown endpoint
- Test empty DB returns zero/empty response (Requirement 1.8)
- Test all-blocker scenario returns with_dates=0, monthly={}, projected_clear_date=null (Requirement 2.7)
- Test single device single metric basic computation
- Test duplicate hostnames across metrics — verify deduplication picks earliest date
- Test duplicate hostnames where all dates are null — verify device is a blocker
- Test response shape matches API contract
- Test 401 without auth session (mock requireAuth to reject)
- Test file:
backend/__tests__/vcl-aggregated-burndown.test.js - Requirements: 1.1, 1.8, 1.9, 2.6, 2.7
-
-
3. Checkpoint - Ensure all backend tests pass
- Ensure all tests pass, ask the user if questions arise.
-
4. Implement frontend component
- 4.1 Add
AggregatedBurndownChartcomponent tofrontend/src/components/pages/CCPMetricsPage.js- Add as an inline component following the existing pattern (StatsBar, DonutChart, TrendChart are all in the same file)
- Fetch
GET /api/compliance/vcl-multi/burndownon page load alongside existing stats/trend calls - Display summary header with total non-compliant, blockers, in-progress, and projected clear date
- Render a Recharts
BarChartwith one bar per monthly bucket (purple fill#A78BFA, fillOpacity 0.7) - Below the chart, render a compact per-vertical contribution table sorted by total descending
- Show "No non-compliant devices" message when total = 0
- Show "All X non-compliant devices lack remediation dates" when monthly_forecast is empty but blockers > 0
- Show
<Loader />spinner while fetching - Show inline error message on API failure
- Place the component below the charts row (TrendChart + DonutChart), above the VerticalTable
- Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 5.3
- 4.1 Add
-
5. 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
- Unit tests validate specific examples and edge cases
- The design uses JavaScript — all implementations use Node.js/Express (backend) and React 19 (frontend)
- The
vclMultiVertical.jsroute file already exists withrouter.use(requireAuth())applied globally
Task Dependency Graph
{
"waves": [
{ "id": 0, "tasks": ["1.1"] },
{ "id": 1, "tasks": ["1.2"] },
{ "id": 2, "tasks": ["1.3", "2.1"] },
{ "id": 3, "tasks": ["2.2"] },
{ "id": 4, "tasks": ["4.1"] }
]
}