Auto-sync .kiro/ from master (post-checkout hook)
This commit is contained in:
537
.kiro/specs/vcl-multi-vertical-upload/design.md
Normal file
537
.kiro/specs/vcl-multi-vertical-upload/design.md
Normal file
@@ -0,0 +1,537 @@
|
||||
# Design Document: VCL Multi-Vertical Upload
|
||||
|
||||
## Overview
|
||||
|
||||
This feature adds a multi-file upload flow to the VCL reporting page that accepts per-vertical compliance xlsx files, stores them with vertical-scoped resolution logic, and generates cross-organizational executive reports with drill-down capability by vertical and metric. It is designed as a POC for the compliance team to evaluate before eventual CyberMetrics API integration.
|
||||
|
||||
The feature is architecturally separate from the existing single-file AEO compliance upload. It reuses the same Python parser and database schema (with additions), but introduces vertical-scoped commit logic and a new set of API endpoints prefixed with `/api/compliance/vcl-multi/`.
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant U as Compliance Analyst
|
||||
participant FE as React Frontend
|
||||
participant BE as Express Backend
|
||||
participant PY as Python Parser
|
||||
participant DB as PostgreSQL
|
||||
|
||||
Note over U,DB: Multi-File Upload Flow
|
||||
U->>FE: Drop/select 1–14 xlsx files
|
||||
FE->>FE: Extract vertical + date from each filename
|
||||
FE->>BE: POST /api/compliance/vcl-multi/preview (multipart, multiple files)
|
||||
|
||||
loop For each file
|
||||
BE->>PY: parse_compliance_xlsx.py <file>
|
||||
PY-->>BE: { items, summary, report_date, total }
|
||||
BE->>DB: Query active items WHERE vertical = X
|
||||
BE->>BE: Compute scoped diff (new/recurring/resolved within vertical)
|
||||
end
|
||||
|
||||
BE-->>FE: { files: [{ vertical, date, diff, itemCount, tempFile }] }
|
||||
FE->>FE: Display batch preview table
|
||||
U->>FE: Confirm batch
|
||||
FE->>BE: POST /api/compliance/vcl-multi/commit { files: [...] }
|
||||
|
||||
loop For each file (single transaction)
|
||||
BE->>DB: Upsert items for vertical X
|
||||
BE->>DB: Resolve missing items WHERE vertical = X only
|
||||
BE->>DB: Update/create snapshot for vertical X
|
||||
end
|
||||
|
||||
BE-->>FE: { committed: [...] }
|
||||
|
||||
Note over FE,DB: VCL Multi-Vertical Report Load
|
||||
FE->>BE: GET /api/compliance/vcl-multi/stats
|
||||
BE->>DB: Aggregate across all verticals
|
||||
BE-->>FE: { stats, verticalBreakdown, donut }
|
||||
|
||||
FE->>BE: GET /api/compliance/vcl-multi/vertical/:code/metrics
|
||||
BE->>DB: Per-metric breakdown for vertical
|
||||
BE-->>FE: { metrics: [...] }
|
||||
|
||||
FE->>BE: GET /api/compliance/vcl-multi/vertical/:code/metric/:metricId/devices
|
||||
BE->>DB: Device list for vertical + metric
|
||||
BE-->>FE: { devices: [...] }
|
||||
```
|
||||
|
||||
### Data Flow Summary
|
||||
|
||||
1. **Upload** — Multiple files uploaded simultaneously. Each file is parsed independently. Vertical identity comes from the filename, not from inside the xlsx.
|
||||
2. **Scoped resolution** — Each file's commit only resolves items within its own vertical. Other verticals are untouched.
|
||||
3. **Aggregation** — VCL stats endpoints aggregate across all verticals for the executive view.
|
||||
4. **Drill-down** — Vertical → Metric → Device hierarchy for investigation.
|
||||
5. **Burndown** — Computed from `resolution_date` values on non-compliant devices, bucketed by month per vertical.
|
||||
|
||||
## Data Model
|
||||
|
||||
### Schema Changes
|
||||
|
||||
#### New column on `compliance_items`
|
||||
|
||||
```sql
|
||||
ALTER TABLE compliance_items ADD COLUMN IF NOT EXISTS vertical TEXT DEFAULT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_compliance_items_vertical ON compliance_items(vertical);
|
||||
CREATE INDEX IF NOT EXISTS idx_compliance_items_vertical_status ON compliance_items(vertical, status);
|
||||
```
|
||||
|
||||
The `vertical` column stores the organizational vertical code (NTS_AEO, SDIT_CISO, etc.) extracted from the filename at upload time. Existing items (from the old single-file flow) will have `vertical = NULL` — they continue to work with the existing AEO compliance page unchanged.
|
||||
|
||||
#### New column on `compliance_uploads`
|
||||
|
||||
```sql
|
||||
ALTER TABLE compliance_uploads ADD COLUMN IF NOT EXISTS vertical TEXT DEFAULT NULL;
|
||||
```
|
||||
|
||||
Tags each upload record with its vertical so we can query upload history per vertical.
|
||||
|
||||
#### New table: `vcl_multi_vertical_summary`
|
||||
|
||||
Stores the parsed Summary sheet data per vertical per upload for metric-level reporting.
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS vcl_multi_vertical_summary (
|
||||
id SERIAL PRIMARY KEY,
|
||||
upload_id INTEGER NOT NULL REFERENCES compliance_uploads(id) ON DELETE CASCADE,
|
||||
vertical TEXT NOT NULL,
|
||||
metric_id TEXT NOT NULL,
|
||||
metric_desc TEXT DEFAULT '',
|
||||
category TEXT DEFAULT 'Other',
|
||||
team TEXT DEFAULT '',
|
||||
priority TEXT DEFAULT '',
|
||||
non_compliant INTEGER DEFAULT 0,
|
||||
compliant INTEGER DEFAULT 0,
|
||||
total INTEGER DEFAULT 0,
|
||||
compliance_pct NUMERIC(5,2) DEFAULT 0,
|
||||
target NUMERIC(5,2) DEFAULT 0,
|
||||
status TEXT DEFAULT '',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_vcl_multi_summary_vertical
|
||||
ON vcl_multi_vertical_summary(vertical);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_vcl_multi_summary_upload
|
||||
ON vcl_multi_vertical_summary(upload_id);
|
||||
```
|
||||
|
||||
#### Updated `compliance_snapshots`
|
||||
|
||||
The existing snapshots table already has a `vertical` column. Multi-vertical uploads will create snapshots keyed on the vertical code (NTS_AEO, SDIT_CISO) rather than the team name (STEAM, ACCESS-ENG). An additional `_ALL` aggregate snapshot is created for the trend chart.
|
||||
|
||||
### Entity Relationships
|
||||
|
||||
```
|
||||
compliance_uploads (1) ──── (N) compliance_items
|
||||
│ │
|
||||
│ vertical │ vertical
|
||||
│ │
|
||||
└──── (N) vcl_multi_vertical_summary
|
||||
│
|
||||
compliance_snapshots ─────────────────┘ (keyed on vertical + month)
|
||||
```
|
||||
|
||||
### Vertical Identification Logic
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Extracts vertical code and report date from a filename.
|
||||
* Pattern: <VERTICAL>_YYYY_MM_DD.xlsx
|
||||
* Examples:
|
||||
* NTS_AEO_2026_05_11.xlsx → { vertical: 'NTS_AEO', date: '2026-05-11' }
|
||||
* SDIT_CISO_2026_05_11.xlsx → { vertical: 'SDIT_CISO', date: '2026-05-11' }
|
||||
* SR_2026_05_11.xlsx → { vertical: 'SR', date: '2026-05-11' }
|
||||
* AllOthers_2026_05_11.xlsx → { vertical: 'AllOthers', date: '2026-05-11' }
|
||||
*/
|
||||
function parseVerticalFilename(filename) {
|
||||
const stem = filename.replace(/\.xlsx$/i, '');
|
||||
const match = stem.match(/^(.+?)_(\d{4})_(\d{2})_(\d{2})$/);
|
||||
if (!match) return null;
|
||||
return {
|
||||
vertical: match[1],
|
||||
date: `${match[2]}-${match[3]}-${match[4]}`,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Upload Flow
|
||||
|
||||
**`POST /api/compliance/vcl-multi/preview`**
|
||||
|
||||
Accepts multiple xlsx files via multipart form data. Parses each, computes per-vertical scoped diffs.
|
||||
|
||||
- Auth: `requireAuth()`, `requireGroup('Admin', 'Standard_User')`
|
||||
- Body: multipart/form-data with field `files` (array of xlsx files)
|
||||
- Response:
|
||||
```json
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"filename": "NTS_AEO_2026_05_11.xlsx",
|
||||
"vertical": "NTS_AEO",
|
||||
"report_date": "2026-05-11",
|
||||
"total_items": 342,
|
||||
"diff": { "new_count": 12, "recurring_count": 320, "resolved_count": 8 },
|
||||
"summary_entries": 24,
|
||||
"tempFile": "/path/to/temp.json"
|
||||
}
|
||||
],
|
||||
"unrecognized": ["weird_file.xlsx"]
|
||||
}
|
||||
```
|
||||
|
||||
**`POST /api/compliance/vcl-multi/commit`**
|
||||
|
||||
Commits all previewed files in a single transaction.
|
||||
|
||||
- Auth: `requireAuth()`, `requireGroup('Admin', 'Standard_User')`
|
||||
- Body: `{ files: [{ tempFile, vertical, report_date, filename }] }`
|
||||
- Response:
|
||||
```json
|
||||
{
|
||||
"committed": [
|
||||
{ "vertical": "NTS_AEO", "upload_id": 45, "new_count": 12, "recurring_count": 320, "resolved_count": 8 }
|
||||
],
|
||||
"total_new": 85,
|
||||
"total_resolved": 42
|
||||
}
|
||||
```
|
||||
|
||||
### Reporting
|
||||
|
||||
**`GET /api/compliance/vcl-multi/stats`**
|
||||
|
||||
Aggregated cross-vertical executive summary.
|
||||
|
||||
- Response:
|
||||
```json
|
||||
{
|
||||
"stats": {
|
||||
"total_devices": 4200,
|
||||
"compliant": 3800,
|
||||
"non_compliant": 400,
|
||||
"compliance_pct": 90,
|
||||
"target_pct": 95
|
||||
},
|
||||
"donut": {
|
||||
"blocked": { "count": 120, "pct": 30 },
|
||||
"in_progress": { "count": 280, "pct": 70 }
|
||||
},
|
||||
"vertical_breakdown": [
|
||||
{
|
||||
"vertical": "NTS_AEO",
|
||||
"total_devices": 800,
|
||||
"compliant": 720,
|
||||
"non_compliant": 80,
|
||||
"compliance_pct": 90,
|
||||
"blockers": 25,
|
||||
"forecast_burndown": { "2026-06": 20, "2026-07": 35, "2026-08": 15 },
|
||||
"last_upload": "2026-05-11"
|
||||
}
|
||||
],
|
||||
"last_upload_date": "2026-05-11"
|
||||
}
|
||||
```
|
||||
|
||||
**`GET /api/compliance/vcl-multi/trend`**
|
||||
|
||||
Monthly trend data for the overview chart.
|
||||
|
||||
- Response:
|
||||
```json
|
||||
{
|
||||
"months": [
|
||||
{
|
||||
"month": "2026-03",
|
||||
"compliance_pct": 85,
|
||||
"compliant": 3400,
|
||||
"non_compliant": 600,
|
||||
"forecast_pct": null,
|
||||
"target_pct": 95
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**`GET /api/compliance/vcl-multi/vertical/:code/metrics`**
|
||||
|
||||
Per-metric breakdown for a specific vertical.
|
||||
|
||||
- Response:
|
||||
```json
|
||||
{
|
||||
"vertical": "NTS_AEO",
|
||||
"metrics": [
|
||||
{
|
||||
"metric_id": "5.2.4",
|
||||
"metric_desc": "MFA enforcement on privileged accounts",
|
||||
"category": "Access & MFA",
|
||||
"non_compliant": 15,
|
||||
"compliant": 785,
|
||||
"total": 800,
|
||||
"compliance_pct": 98.1,
|
||||
"target": 100
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{ "category": "Access & MFA", "non_compliant": 45, "compliance_pct": 94.4 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**`GET /api/compliance/vcl-multi/vertical/:code/metric/:metricId/devices`**
|
||||
|
||||
Device list for a specific vertical + metric combination.
|
||||
|
||||
- Response:
|
||||
```json
|
||||
{
|
||||
"devices": [
|
||||
{
|
||||
"hostname": "srv-nts-001",
|
||||
"ip_address": "10.1.2.3",
|
||||
"device_type": "Router",
|
||||
"team": "STEAM",
|
||||
"seen_count": 4,
|
||||
"first_seen": "2026-03-15",
|
||||
"last_seen": "2026-05-11",
|
||||
"resolution_date": "2026-07-01",
|
||||
"remediation_plan": "Scheduled for next maintenance window"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**`GET /api/compliance/vcl-multi/vertical/:code/burndown`**
|
||||
|
||||
Burndown forecast for a specific vertical.
|
||||
|
||||
- Response:
|
||||
```json
|
||||
{
|
||||
"vertical": "NTS_AEO",
|
||||
"total_non_compliant": 80,
|
||||
"blockers": 25,
|
||||
"with_dates": 55,
|
||||
"monthly_forecast": { "2026-06": 20, "2026-07": 35 },
|
||||
"projected_clear_date": "2026-08"
|
||||
}
|
||||
```
|
||||
|
||||
## Frontend Components
|
||||
|
||||
### New Page: `VCLMultiVerticalPage.js`
|
||||
|
||||
Top-level page accessible from the nav drawer. Contains:
|
||||
|
||||
| Component | Purpose |
|
||||
|---|---|
|
||||
| `MultiVerticalUploadModal` | Multi-file drag-drop, filename parsing, batch preview, commit |
|
||||
| `VCLMultiStatsBar` | Aggregated stats across all verticals |
|
||||
| `VCLMultiVerticalTable` | Breakdown table with one row per vertical, clickable for drill-down |
|
||||
| `VCLMultiTrendChart` | Monthly compliance trend with forecast line |
|
||||
| `VCLMultiDonutChart` | Blocked vs In-Progress donut |
|
||||
| `VerticalDetailView` | Per-metric breakdown when a vertical is selected |
|
||||
| `MetricDeviceList` | Device list when a metric is selected within a vertical |
|
||||
| `VerticalBurndownChart` | Per-vertical burndown projection |
|
||||
|
||||
### Navigation
|
||||
|
||||
- New entry in `NavDrawer.js`: "VCL Multi-Vertical" (or "CCP Metrics")
|
||||
- Separate from existing "Compliance" and "VCL Report" entries
|
||||
- Icon: `BarChart3` or `Building2` from lucide-react
|
||||
|
||||
### Drill-down UX Flow
|
||||
|
||||
```
|
||||
VCL Multi-Vertical Overview
|
||||
├── Stats Bar (aggregated)
|
||||
├── Trend Chart (aggregated)
|
||||
├── Donut Chart (aggregated)
|
||||
└── Vertical Breakdown Table
|
||||
├── NTS_AEO (90%) → click
|
||||
│ ├── Metric Breakdown
|
||||
│ │ ├── 5.2.4 — Access & MFA (98.1%) → click
|
||||
│ │ │ └── Device List (15 devices)
|
||||
│ │ ├── 1.1.1 — Logging & Monitoring (85%) → click
|
||||
│ │ │ └── Device List (120 devices)
|
||||
│ │ └── ...
|
||||
│ └── Burndown Chart (vertical-specific)
|
||||
├── SDIT_CISO (92%) → click
|
||||
│ └── ...
|
||||
└── ...
|
||||
```
|
||||
|
||||
## Scoped Resolution Logic
|
||||
|
||||
This is the core architectural change from the existing upload flow.
|
||||
|
||||
### Current behavior (single-file)
|
||||
|
||||
```javascript
|
||||
// Resolves ALL active items not in the upload — global scope
|
||||
for (const [key, row] of Object.entries(activeMap)) {
|
||||
if (!newKeys.has(key)) {
|
||||
await client.query(`UPDATE compliance_items SET status = 'resolved' WHERE id = $1`, [row.id]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### New behavior (multi-vertical)
|
||||
|
||||
```javascript
|
||||
// Resolves only items within the same vertical — scoped
|
||||
const { rows: activeRows } = await client.query(
|
||||
`SELECT id, hostname, metric_id, seen_count FROM compliance_items
|
||||
WHERE status = 'active' AND vertical = $1`,
|
||||
[vertical]
|
||||
);
|
||||
|
||||
for (const [key, row] of Object.entries(activeMap)) {
|
||||
if (!newKeys.has(key)) {
|
||||
await client.query(`UPDATE compliance_items SET status = 'resolved', resolved_upload_id = $1 WHERE id = $2`, [uploadId, row.id]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The only difference is the `AND vertical = $1` filter on the active items query. This ensures uploading NTS_AEO data never touches SDIT_CISO items.
|
||||
|
||||
## Burndown Forecast Computation
|
||||
|
||||
### Per-vertical burndown
|
||||
|
||||
For each vertical, the burndown is computed from `resolution_date` values on active non-compliant items:
|
||||
|
||||
```javascript
|
||||
function computeVerticalBurndown(items) {
|
||||
const total = items.length;
|
||||
const withDates = items.filter(i => i.resolution_date != null);
|
||||
const blockers = items.filter(i => i.resolution_date == null);
|
||||
|
||||
// Bucket by month
|
||||
const monthly = {};
|
||||
for (const item of withDates) {
|
||||
const month = item.resolution_date.slice(0, 7); // YYYY-MM
|
||||
monthly[month] = (monthly[month] || 0) + 1;
|
||||
}
|
||||
|
||||
// Cumulative projection
|
||||
let remaining = total;
|
||||
const projection = {};
|
||||
for (const month of Object.keys(monthly).sort()) {
|
||||
remaining -= monthly[month];
|
||||
projection[month] = { remediated: monthly[month], remaining };
|
||||
}
|
||||
|
||||
return { total, blockers: blockers.length, with_dates: withDates.length, monthly, projection };
|
||||
}
|
||||
```
|
||||
|
||||
### Aggregated trend forecast
|
||||
|
||||
The trend chart forecast uses linear regression on the last 3+ monthly snapshots to project forward. This reuses the same approach as the existing VCL trend endpoint.
|
||||
|
||||
## Migration Script
|
||||
|
||||
```javascript
|
||||
// backend/migrations/add_vcl_multi_vertical.js
|
||||
const pool = require('../db');
|
||||
|
||||
async function run() {
|
||||
console.log('Starting VCL multi-vertical migration...');
|
||||
try {
|
||||
// Add vertical column to compliance_items
|
||||
await pool.query(`ALTER TABLE compliance_items ADD COLUMN IF NOT EXISTS vertical TEXT DEFAULT NULL`);
|
||||
console.log('✓ vertical column added to compliance_items');
|
||||
|
||||
await pool.query(`CREATE INDEX IF NOT EXISTS idx_compliance_items_vertical ON compliance_items(vertical)`);
|
||||
console.log('✓ idx_compliance_items_vertical index created');
|
||||
|
||||
await pool.query(`CREATE INDEX IF NOT EXISTS idx_compliance_items_vertical_status ON compliance_items(vertical, status)`);
|
||||
console.log('✓ idx_compliance_items_vertical_status index created');
|
||||
|
||||
// Add vertical column to compliance_uploads
|
||||
await pool.query(`ALTER TABLE compliance_uploads ADD COLUMN IF NOT EXISTS vertical TEXT DEFAULT NULL`);
|
||||
console.log('✓ vertical column added to compliance_uploads');
|
||||
|
||||
// Create summary table for per-vertical metric data
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS vcl_multi_vertical_summary (
|
||||
id SERIAL PRIMARY KEY,
|
||||
upload_id INTEGER NOT NULL REFERENCES compliance_uploads(id) ON DELETE CASCADE,
|
||||
vertical TEXT NOT NULL,
|
||||
metric_id TEXT NOT NULL,
|
||||
metric_desc TEXT DEFAULT '',
|
||||
category TEXT DEFAULT 'Other',
|
||||
team TEXT DEFAULT '',
|
||||
priority TEXT DEFAULT '',
|
||||
non_compliant INTEGER DEFAULT 0,
|
||||
compliant INTEGER DEFAULT 0,
|
||||
total INTEGER DEFAULT 0,
|
||||
compliance_pct NUMERIC(5,2) DEFAULT 0,
|
||||
target NUMERIC(5,2) DEFAULT 0,
|
||||
status TEXT DEFAULT '',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
)
|
||||
`);
|
||||
console.log('✓ vcl_multi_vertical_summary table created');
|
||||
|
||||
await pool.query(`CREATE INDEX IF NOT EXISTS idx_vcl_multi_summary_vertical ON vcl_multi_vertical_summary(vertical)`);
|
||||
console.log('✓ idx_vcl_multi_summary_vertical index created');
|
||||
|
||||
await pool.query(`CREATE INDEX IF NOT EXISTS idx_vcl_multi_summary_upload ON vcl_multi_vertical_summary(upload_id)`);
|
||||
console.log('✓ idx_vcl_multi_summary_upload index created');
|
||||
|
||||
} catch (err) {
|
||||
console.error('Migration error:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Migration complete.');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
run();
|
||||
```
|
||||
|
||||
## Correctness Properties
|
||||
|
||||
### Property 1: Vertical-Scoped Resolution Isolation
|
||||
|
||||
*For any* set of active compliance items across N verticals, committing an upload for vertical X must only resolve items where `vertical = X`. The count of active items for all other verticals must remain unchanged before and after the commit.
|
||||
|
||||
### Property 2: Filename Parsing Completeness
|
||||
|
||||
*For any* filename matching the pattern `<VERTICAL>_YYYY_MM_DD.xlsx` where VERTICAL contains only alphanumeric characters and underscores, `parseVerticalFilename` must return a non-null result with the correct vertical code and ISO date string.
|
||||
|
||||
### Property 3: Aggregated Stats Consistency
|
||||
|
||||
*For any* set of per-vertical stats, the aggregated `total_devices` must equal the sum of all vertical `total_devices`, `compliant` must equal the sum of all vertical `compliant`, and `compliance_pct` must equal `Math.round((sum_compliant / sum_total) * 100)`.
|
||||
|
||||
### Property 4: Burndown Forecast Conservation
|
||||
|
||||
*For any* set of non-compliant items with resolution dates, the sum of all monthly burndown bucket counts must equal the count of items with non-null resolution dates. No item is double-counted or lost.
|
||||
|
||||
### Property 5: Idempotent Re-upload
|
||||
|
||||
*For any* vertical, uploading the same file twice on the same day must produce the same final state as uploading it once. Specifically: same active item set, same seen_counts, same resolved set.
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Condition | Behavior |
|
||||
|---|---|
|
||||
| Filename doesn't match pattern | File flagged as "unrecognized" in preview; user can assign vertical manually |
|
||||
| Duplicate vertical in batch | Reject — only one file per vertical per batch |
|
||||
| Parser failure on one file | That file is marked as errored; other files in batch can still proceed |
|
||||
| Transaction failure during commit | Full rollback of entire batch — no partial commits |
|
||||
| File exceeds 10MB | Rejected by multer before parsing |
|
||||
| No items parsed from file | Warning in preview; user can still commit (creates upload record with 0 items) |
|
||||
|
||||
## Deployment Considerations
|
||||
|
||||
- The feature is self-contained behind `/api/compliance/vcl-multi/` endpoints
|
||||
- Can be deployed on a separate instance with its own database
|
||||
- No changes to existing AEO compliance upload flow
|
||||
- Feature flag not needed — the nav entry and endpoints simply exist or don't
|
||||
- Environment variable `VCL_TARGET_PCT` (default 95) applies to multi-vertical reporting as well
|
||||
115
.kiro/specs/vcl-multi-vertical-upload/requirements.md
Normal file
115
.kiro/specs/vcl-multi-vertical-upload/requirements.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# Requirements: VCL Multi-Vertical Upload
|
||||
|
||||
## Context
|
||||
|
||||
The compliance team generates CCP (Customer Compliance Program) metric data from CyberMetrics on a 24-hour cycle. The data is exported as separate xlsx files per organizational vertical (e.g., NTS_AEO, SDIT_CISO, TSI). They need a way to upload these files into the VCL reporting page to generate executive-level compliance reports for senior leadership across all organizations — with the ability to drill down by vertical and by metric.
|
||||
|
||||
This is a POC that may later be replaced by direct API integration with CyberMetrics. It will be deployed as a separate flow from the existing single-file AEO compliance upload, and may run on its own instance to isolate compliance team experimentation from dev/production data.
|
||||
|
||||
## Verticals (from filename convention)
|
||||
|
||||
| Vertical Code | Organization |
|
||||
|---|---|
|
||||
| AllOthers | Catch-all for unclassified |
|
||||
| NTS_AEO | NTS AEO (contains sub-teams: STEAM, ACCESS-ENG, ACCESS-OPS) |
|
||||
| NTS_AVVOC | NTS AVVOC |
|
||||
| NTS_CPE | NTS CPE |
|
||||
| NTS_NEO | NTS NEO |
|
||||
| NTS_WTS | NTS WTS |
|
||||
| PRDCT_VSO | Product VSO |
|
||||
| SBNOE | SBNOE |
|
||||
| SDIT_CISO | SDIT CISO |
|
||||
| SDIT_CSD | SDIT CSD |
|
||||
| SDIT_EDIS | SDIT EDIS |
|
||||
| SDIT_IT | SDIT IT |
|
||||
| SR | SR |
|
||||
| TSI | TSI |
|
||||
|
||||
## User Stories
|
||||
|
||||
### US-1: Multi-file upload
|
||||
|
||||
As a compliance analyst, I want to upload multiple vertical xlsx files at once so that I can ingest a full reporting cycle without uploading one file at a time.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- 1.1: User can select or drag-drop 1–14 xlsx files simultaneously
|
||||
- 1.2: System extracts the vertical code from each filename using the pattern `<VERTICAL>_YYYY_MM_DD.xlsx`
|
||||
- 1.3: System extracts the report date from each filename
|
||||
- 1.4: If a filename does not match the expected pattern, the user is prompted to manually assign a vertical and date
|
||||
- 1.5: A preview table shows: filename, detected vertical, report date, item count, diff (new/recurring/resolved) per file
|
||||
- 1.6: User can remove individual files from the batch before committing
|
||||
- 1.7: User confirms the batch and all files are committed
|
||||
- 1.8: Upload supports daily frequency (not just weekly)
|
||||
|
||||
### US-2: Vertical-scoped resolution
|
||||
|
||||
As a compliance analyst, I want uploading a vertical's file to only affect that vertical's data so that partial uploads don't incorrectly resolve devices from other verticals.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- 2.1: Committing a file for vertical X only resolves active items where `vertical = X`
|
||||
- 2.2: Items belonging to other verticals remain unchanged
|
||||
- 2.3: Re-uploading the same vertical on the same day replaces the previous state (idempotent)
|
||||
- 2.4: The system tracks which upload introduced/resolved each item per vertical
|
||||
|
||||
### US-3: Cross-vertical VCL report
|
||||
|
||||
As a senior leader, I want to see an aggregated compliance report across all verticals so that I can assess organizational posture at a glance.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- 3.1: Stats bar shows aggregated totals: Total Devices, Compliant, Non-Compliant, Current %, Target %
|
||||
- 3.2: Vertical breakdown table shows one row per vertical with: compliance %, non-compliant count, total devices
|
||||
- 3.3: Donut chart shows Blocked vs In-Progress across all verticals
|
||||
- 3.4: Trend chart shows monthly compliance % over time (aggregated)
|
||||
- 3.5: Data refreshes immediately after a new upload is committed
|
||||
|
||||
### US-4: Vertical drill-down
|
||||
|
||||
As a compliance analyst, I want to click into a vertical to see its per-metric breakdown so that I can identify which metrics are driving non-compliance.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- 4.1: Clicking a vertical row navigates to a detail view for that vertical
|
||||
- 4.2: Detail view shows per-metric compliance: metric ID, description, compliant count, non-compliant count, compliance %, target %
|
||||
- 4.3: Metrics are grouped by category (Logging & Monitoring, Vulnerability Management, etc.)
|
||||
- 4.4: Each metric row is clickable to see the device list for that metric
|
||||
|
||||
### US-5: Metric drill-down
|
||||
|
||||
As a compliance analyst, I want to click a metric within a vertical to see the non-compliant devices so that I can identify specific remediation targets.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- 5.1: Clicking a metric shows the list of non-compliant devices for that metric in that vertical
|
||||
- 5.2: Device list shows: hostname, IP address, device type, team (sub-team), seen count, first seen, last seen
|
||||
- 5.3: Devices can have resolution dates set (for burndown forecasting)
|
||||
- 5.4: Devices can have remediation plans documented
|
||||
|
||||
### US-6: Burndown forecast
|
||||
|
||||
As a senior leader, I want to see a projected burndown timeline so that I can assess whether verticals are on track to meet compliance targets.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- 6.1: Each vertical in the breakdown table shows forecast burndown columns (monthly projections)
|
||||
- 6.2: Burndown is computed from resolution_date values on non-compliant devices
|
||||
- 6.3: Devices without a resolution_date count as "blockers" (no committed timeline)
|
||||
- 6.4: The trend chart includes a forecast line (linear regression on 3+ months of data)
|
||||
- 6.5: Per-vertical drill-down shows that vertical's burndown separately
|
||||
|
||||
### US-7: Separation from existing AEO upload
|
||||
|
||||
As a system administrator, I want the multi-vertical upload to be a separate flow from the existing AEO compliance upload so that the two don't interfere with each other.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- 7.1: Multi-vertical upload has its own UI entry point (separate from the existing "Upload" button on the AEO Compliance page)
|
||||
- 7.2: Multi-vertical data is stored with a `vertical` field that distinguishes it from existing AEO-only data
|
||||
- 7.3: Existing AEO compliance page continues to work unchanged
|
||||
- 7.4: The VCL report page can show either multi-vertical data or fall back to existing AEO-only data if no multi-vertical uploads exist
|
||||
- 7.5: The system can be deployed as a standalone instance without affecting other deployments
|
||||
|
||||
### US-8: Summary sheet ingestion
|
||||
|
||||
As a compliance analyst, I want the system to parse the Summary sheet from each vertical file so that metric-level health data (compliance %, targets) is captured per vertical.
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- 8.1: Summary sheet data is stored per vertical per upload
|
||||
- 8.2: Overall scores (customer_network, vertical) are captured
|
||||
- 8.3: Per-metric entries include: metric_id, non_compliant, compliant, total, compliance_pct, target, status, description
|
||||
- 8.4: Summary data feeds the metric drill-down view (compliance % and targets come from here)
|
||||
169
.kiro/specs/vcl-multi-vertical-upload/tasks.md
Normal file
169
.kiro/specs/vcl-multi-vertical-upload/tasks.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# Tasks: VCL Multi-Vertical Upload
|
||||
|
||||
## Phase 1: Database & Backend Foundation
|
||||
|
||||
- [x] 1. Create migration script `backend/migrations/add_vcl_multi_vertical.js`
|
||||
- Add `vertical` column to `compliance_items` (TEXT, nullable, indexed)
|
||||
- Add `vertical` column to `compliance_uploads` (TEXT, nullable)
|
||||
- Create `vcl_multi_vertical_summary` table
|
||||
- Create indexes for vertical-based queries
|
||||
|
||||
- [x] 2. Add `parseVerticalFilename()` helper to `backend/helpers/vclHelpers.js`
|
||||
- Extract vertical code and report date from filename pattern
|
||||
- Handle edge cases (no match, single-word verticals like "SR")
|
||||
- Export for testing
|
||||
|
||||
- [x] 3. Implement vertical-scoped `persistMultiVerticalUpload()` in compliance route
|
||||
- Accept items + vertical + summary + metadata
|
||||
- Query active items filtered by `WHERE vertical = $1`
|
||||
- Upsert new/recurring items with vertical tag
|
||||
- Resolve only items within the same vertical
|
||||
- Create/update compliance_snapshots for the vertical
|
||||
- Store summary entries in `vcl_multi_vertical_summary`
|
||||
|
||||
- [x] 4. Implement `POST /api/compliance/vcl-multi/preview` endpoint
|
||||
- Accept multiple files via multer `.array('files', 14)`
|
||||
- Parse each file with existing Python parser
|
||||
- Extract vertical from filename for each
|
||||
- Compute per-vertical scoped diff
|
||||
- Store parsed data in temp files
|
||||
- Return batch preview response
|
||||
|
||||
- [x] 5. Implement `POST /api/compliance/vcl-multi/commit` endpoint
|
||||
- Read temp files for each file in batch
|
||||
- Commit all in a single transaction using `persistMultiVerticalUpload()`
|
||||
- Rollback entire batch on any failure
|
||||
- Clean up temp files
|
||||
- Audit log the batch commit
|
||||
|
||||
## Phase 2: Reporting Endpoints
|
||||
|
||||
- [x] 6. Implement `GET /api/compliance/vcl-multi/stats` endpoint
|
||||
- Aggregate across all verticals where `vertical IS NOT NULL`
|
||||
- Compute total/compliant/non-compliant/compliance_pct
|
||||
- Compute donut (blocked vs in-progress)
|
||||
- Compute per-vertical breakdown with burndown
|
||||
- Return structured response
|
||||
|
||||
- [x] 7. Implement `GET /api/compliance/vcl-multi/trend` endpoint
|
||||
- Query compliance_snapshots for multi-vertical data
|
||||
- Aggregate monthly compliance % across verticals
|
||||
- Compute linear regression forecast (3+ months)
|
||||
- Return monthly data points
|
||||
|
||||
- [x] 8. Implement `GET /api/compliance/vcl-multi/vertical/:code/metrics` endpoint
|
||||
- Query `vcl_multi_vertical_summary` for latest upload of that vertical
|
||||
- Group by category
|
||||
- Return per-metric breakdown
|
||||
|
||||
- [x] 9. Implement `GET /api/compliance/vcl-multi/vertical/:code/metric/:metricId/devices` endpoint
|
||||
- Query `compliance_items` filtered by vertical + metric_id + status = 'active'
|
||||
- Include resolution_date, remediation_plan, seen_count, first/last seen
|
||||
- Return device list
|
||||
|
||||
- [x] 10. Implement `GET /api/compliance/vcl-multi/vertical/:code/burndown` endpoint
|
||||
- Query non-compliant items for vertical
|
||||
- Compute monthly forecast from resolution_date values
|
||||
- Return burndown data with blocker count
|
||||
|
||||
## Phase 3: Frontend — Upload Modal
|
||||
|
||||
- [x] 11. Create `MultiVerticalUploadModal.js` component
|
||||
- Multi-file drag-drop zone (accept .xlsx, max 14 files)
|
||||
- Filename parsing with vertical/date extraction on selection
|
||||
- Display file list with detected vertical, date, status
|
||||
- Allow removing individual files from batch
|
||||
- Handle unrecognized filenames (manual vertical assignment)
|
||||
|
||||
- [x] 12. Implement preview phase in upload modal
|
||||
- Call POST /preview with all files
|
||||
- Display batch preview table: filename, vertical, items, diff
|
||||
- Show totals row (total new, total recurring, total resolved)
|
||||
- Error display for files that failed parsing
|
||||
|
||||
- [x] 13. Implement commit phase in upload modal
|
||||
- Confirm button triggers POST /commit
|
||||
- Loading state during commit
|
||||
- Success state with summary of what was committed
|
||||
- Error state with rollback messaging
|
||||
|
||||
## Phase 4: Frontend — Report Page
|
||||
|
||||
- [x] 14. Create `VCLMultiVerticalPage.js` page component (named CCPMetricsPage.js)
|
||||
- Add to NavDrawer with appropriate icon
|
||||
- Page layout: stats bar, charts row, vertical table
|
||||
- Fetch data from /vcl-multi/stats on mount
|
||||
- Loading and empty states
|
||||
|
||||
- [x] 15. Implement `VCLMultiStatsBar` component
|
||||
- Total Devices, Compliant, Non-Compliant, Current %, Target %
|
||||
- Match existing VCL stats bar styling
|
||||
|
||||
- [x] 16. Implement `VCLMultiVerticalTable` component
|
||||
- One row per vertical: name, compliance %, non-compliant, total, last upload date
|
||||
- Sortable columns
|
||||
- Click row to drill down
|
||||
- Burndown forecast columns (monthly)
|
||||
- Blockers column
|
||||
|
||||
- [x] 17. Implement `VCLMultiTrendChart` component (recharts)
|
||||
- Monthly bars for compliant count
|
||||
- Solid line for actual compliance %
|
||||
- Dashed line for forecast %
|
||||
- Reference line for target %
|
||||
- Match existing chart styling
|
||||
|
||||
- [x] 18. Implement `VCLMultiDonutChart` component (recharts)
|
||||
- Blocked vs In-Progress segments
|
||||
- Center label with total non-compliant
|
||||
- Match existing donut styling
|
||||
|
||||
## Phase 5: Frontend — Drill-Down Views
|
||||
|
||||
- [x] 19. Implement `VerticalDetailView` component
|
||||
- Triggered when a vertical row is clicked
|
||||
- Fetch /vertical/:code/metrics
|
||||
- Display per-metric table grouped by category
|
||||
- Click metric to drill further
|
||||
- Back button to return to overview
|
||||
|
||||
- [x] 20. Implement `MetricDeviceList` component
|
||||
- Triggered when a metric row is clicked
|
||||
- Fetch /vertical/:code/metric/:metricId/devices
|
||||
- Display device table: hostname, IP, type, team, seen_count, dates
|
||||
- Resolution date inline editing
|
||||
- Back button to return to metric view
|
||||
|
||||
- [x] 21. Implement `VerticalBurndownChart` component
|
||||
- Displayed in VerticalDetailView
|
||||
- Fetch /vertical/:code/burndown
|
||||
- Bar chart: monthly remediation projections
|
||||
- Annotation for blockers count
|
||||
- Projected clear date label
|
||||
|
||||
## Phase 6: Testing & Documentation
|
||||
|
||||
- [ ] 22. Write property-based tests for new helpers
|
||||
- `parseVerticalFilename` — pattern matching correctness
|
||||
- Vertical-scoped resolution isolation
|
||||
- Aggregated stats consistency
|
||||
- Burndown forecast conservation
|
||||
|
||||
- [ ] 23. Write unit tests for new endpoints
|
||||
- Preview with valid/invalid files
|
||||
- Commit with scoped resolution verification
|
||||
- Stats aggregation with multiple verticals
|
||||
- Drill-down queries
|
||||
|
||||
- [ ] 24. Update README.md
|
||||
- Add VCL Multi-Vertical section to Features
|
||||
- Add new migration to Migrations list
|
||||
- Add new endpoints to API Reference
|
||||
- Add new env vars if any
|
||||
|
||||
- [x] 25. Create meeting-ready design brief document
|
||||
- Architectural choices and rationale
|
||||
- Drill-down hierarchy diagram
|
||||
- Burndown forecast explanation
|
||||
- Open questions for stakeholders
|
||||
- Timeline estimate
|
||||
Reference in New Issue
Block a user