# Time-Based Reporting Recommendations **Date:** 2026-04-02 **Author:** Engineering (Claude Code) **Status:** Draft — for director review --- ## Executive Summary This document analyzes the current CVE Dashboard data model and recommends a set of time-based visualizations that can be added to the Reporting page. Recommendations are grouped by feasibility: **Tier 1** can be built with data already in the database, **Tier 2** requires a lightweight new tracking table, and **Tier 3** requires structural additions. --- ## Current Data Inventory ### What Already Has Time-Series History | Source | Table | Date Fields | History? | |--------|-------|-------------|----------| | Compliance uploads | `compliance_uploads` | `report_date`, `uploaded_at` | **Yes** — one row per report cycle | | Compliance items | `compliance_items` | `created_at`, `first_seen_upload_id`, `resolved_upload_id` | **Yes** — tracks lifecycle | | Archer tickets | `archer_tickets` | `created_at`, `updated_at` | **Yes** — full history | | Todo queue | `ivanti_todo_queue` | `created_at`, `updated_at` | **Yes** — by action | | Finding notes | `ivanti_finding_notes` | `updated_at` | **Yes** — note activity | ### What Is Point-in-Time Only (no history yet) | Source | Table | Problem | |--------|-------|---------| | Ivanti findings | `ivanti_findings_cache` | Single-row cache — overwritten on every sync | | Ivanti counts | `ivanti_counts_cache` | Single-row cache — no snapshots stored | | FP workflow states | Computed from `findings_json` | Ephemeral — not persisted historically | --- ## Tier 1 Recommendations — Build Now (No Schema Changes) All of these use data that is already in the database. --- ### 1.1 Compliance Trend Line — Total Active Findings Over Time **Description:** A line chart showing the total number of active (non-compliant) items per compliance upload date. This directly answers "are we improving over time?" **Data Source:** ```sql SELECT cu.report_date, COUNT(ci.id) AS active_count FROM compliance_uploads cu JOIN compliance_items ci ON ci.upload_id = cu.id AND ci.status = 'active' GROUP BY cu.id ORDER BY cu.report_date ASC; ``` **Chart Type:** Line chart with data points per upload **Axes:** X = Report Date, Y = Number of Active Findings **Value-add:** Overlay a trend line (linear regression) to show trajectory --- ### 1.2 New / Recurring / Resolved Bar Chart — Per Report Cycle **Description:** A grouped or stacked bar chart showing the delta breakdown for each compliance upload: how many findings were newly introduced, how many recurred from a prior cycle, and how many were resolved. **Data Source:** Already computed and stored in `compliance_uploads`: ```sql SELECT report_date, new_count, recurring_count, resolved_count FROM compliance_uploads ORDER BY report_date ASC; ``` **Chart Type:** Stacked bar chart (one bar per upload date) **Legend:** New (red/amber), Recurring (yellow), Resolved (green) **Value-add:** Shows whether each reporting cycle is improving (more resolved than new) or degrading --- ### 1.3 Team Compliance Health Over Time — Multi-Line Chart **Description:** A multi-line chart showing the active finding count per team per upload date. Answers "which team is trending better or worse?" **Data Source:** ```sql SELECT cu.report_date, ci.team, COUNT(ci.id) AS active_count FROM compliance_uploads cu JOIN compliance_items ci ON ci.upload_id = cu.id AND ci.status = 'active' GROUP BY cu.id, ci.team ORDER BY cu.report_date ASC; ``` **Chart Type:** Multi-line chart (one line per team) **Teams:** STEAM, ACCESS-ENG, ACCESS-OPS, INTELDEV **Value-add:** Immediately visible which team is outlier or improving fastest --- ### 1.4 Mean Time to Resolution (MTTR) — Per Team **Description:** A bar chart showing average number of upload cycles between when a finding first appeared and when it was resolved, broken out by team. **Data Source:** ```sql SELECT ci.team, AVG(ci.resolved_upload_id - ci.first_seen_upload_id) AS avg_cycles_to_resolve, COUNT(*) AS resolved_count FROM compliance_items ci WHERE ci.resolved_upload_id IS NOT NULL GROUP BY ci.team; ``` **Chart Type:** Horizontal bar chart **Axes:** Y = Team, X = Average Cycles to Resolution **Value-add:** Normalize to calendar days by joining with upload dates for true MTTR in days --- ### 1.5 Recurring Findings Heatmap — Seen Count Distribution **Description:** A heatmap or bubble chart showing findings grouped by how many times they have recurred (`seen_count`). Identifies chronic, long-standing compliance gaps. **Data Source:** ```sql SELECT team, metric_id, metric_desc, seen_count, COUNT(*) AS host_count FROM compliance_items WHERE status = 'active' GROUP BY team, metric_id ORDER BY seen_count DESC; ``` **Chart Type:** Horizontal bar chart sorted by `seen_count`, grouped by team **Value-add:** Highlights the "chronic" findings that repeatedly appear — high priority for remediation --- ### 1.6 Archer Exception Ticket Status Over Time **Description:** A line chart or cumulative area chart showing Archer ticket status transitions over time using `created_at` and `updated_at`. **Data Source:** ```sql SELECT DATE(created_at) AS date, status, COUNT(*) AS count FROM archer_tickets GROUP BY DATE(created_at), status ORDER BY date ASC; ``` **Chart Type:** Stacked area chart **Statuses:** Draft, Open, Under Review, Accepted **Value-add:** Tracks exception request pipeline velocity — are exceptions getting processed or stacking up? --- ### 1.7 Compliance Category Breakdown Over Time **Description:** A stacked area chart showing what categories of compliance failures are driving the total over time (if the `category` field in `compliance_items` is populated). **Data Source:** ```sql SELECT cu.report_date, ci.category, COUNT(ci.id) AS count FROM compliance_uploads cu JOIN compliance_items ci ON ci.upload_id = cu.id AND ci.status = 'active' WHERE ci.category IS NOT NULL GROUP BY cu.id, ci.category ORDER BY cu.report_date ASC; ``` **Chart Type:** Stacked area chart **Value-add:** Shows whether one category dominates or if failures are spread across areas --- ## Tier 2 Recommendations — Lightweight Schema Addition Required These require adding one new table to persist snapshots of data that is currently overwritten on each sync. --- ### 2.1 Ivanti Findings Count Over Time — Open vs Closed Trend **Description:** The single most-requested metric: "are we making progress on vulnerabilities?" A line chart showing open and closed Ivanti finding counts over time. **Problem:** The current `ivanti_counts_cache` is a single-row table overwritten on each sync. No history is kept. **Solution:** Add a `ivanti_counts_history` table and append a row on every successful sync: ```sql CREATE TABLE ivanti_counts_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, open_count INTEGER NOT NULL, closed_count INTEGER NOT NULL, recorded_at DATETIME DEFAULT CURRENT_TIMESTAMP ); ``` **Backend change:** In the sync route (`POST /api/ivanti/findings/sync`), after updating the cache, also `INSERT INTO ivanti_counts_history`. **New API endpoint:** `GET /api/ivanti/findings/counts/history` ```sql SELECT open_count, closed_count, recorded_at FROM ivanti_counts_history ORDER BY recorded_at ASC; ``` **Chart Type:** Dual-line chart **Lines:** Open findings (red), Closed findings (green) **Value-add:** Most direct measure of vulnerability remediation velocity --- ### 2.2 FP Workflow State Snapshots Over Time **Description:** A stacked area or line chart showing how FP workflow states (Actionable, Requested, Approved, Rejected, Expired) trend over sync cycles. **Solution:** Add a `ivanti_fp_workflow_history` table: ```sql CREATE TABLE ivanti_fp_workflow_history ( id INTEGER PRIMARY KEY AUTOINCREMENT, state TEXT NOT NULL, finding_count INTEGER NOT NULL DEFAULT 0, id_count INTEGER NOT NULL DEFAULT 0, recorded_at DATETIME DEFAULT CURRENT_TIMESTAMP ); ``` **Chart Type:** Stacked area chart **Value-add:** Shows whether FP requests are being worked through or stacking up in "Requested" state --- ### 2.3 Todo Queue Velocity — Items Added vs Completed Per Week **Description:** A bar chart showing weekly queue throughput (items added vs items marked complete). **Data Source:** Already available in `ivanti_todo_queue.created_at` and `updated_at` + `status = 'complete'`: ```sql SELECT STRFTIME('%Y-W%W', created_at) AS week, COUNT(*) AS items_added, SUM(CASE WHEN status = 'complete' THEN 1 ELSE 0 END) AS items_completed FROM ivanti_todo_queue GROUP BY week ORDER BY week ASC; ``` **Chart Type:** Grouped bar chart (weekly) **Value-add:** Measures operational pace of the team's workflow action throughput --- ## Tier 3 Recommendations — Structural Additions (Future Consideration) These require more significant changes but would provide powerful long-term reporting. --- ### 3.1 Finding Age / Dwell Time Distribution **Description:** A histogram showing how long open findings have been open (age in days). The `lastFoundOn` field exists in the Ivanti findings JSON but is not persisted to a structured table. **Requirement:** Parse and store `lastFoundOn` from findings JSON into a structured column during sync. **Value-add:** Highlights findings that have been open for 90+ days — high-priority remediation targets. --- ### 3.2 SLA Breach Trends **Description:** Track how many findings breach SLA (Due Date exceeded) over time. Currently SLA status is computed in the frontend on-the-fly. **Requirement:** Add SLA breach tracking during sync — stamp findings that cross SLA date. **Value-add:** Compliance and audit reporting for SLA adherence metrics. --- ## Recommended Implementation Order | Priority | Chart | Effort | Impact | |----------|-------|--------|--------| | 1 | 1.2 — New/Recurring/Resolved bar chart | Low (data ready) | High | | 2 | 1.1 — Compliance trend line | Low (data ready) | High | | 3 | 1.3 — Team health multi-line | Low (data ready) | High | | 4 | 2.1 — Ivanti open/closed history | Medium (new table) | Very High | | 5 | 1.4 — MTTR per team | Low (data ready) | Medium | | 6 | 1.6 — Archer ticket pipeline | Low (data ready) | Medium | | 7 | 2.3 — Queue velocity | Low (data ready) | Medium | | 8 | 1.5 — Recurring findings heatmap | Low (data ready) | Medium | | 9 | 2.2 — FP workflow snapshots | Medium (new table) | Medium | | 10 | 1.7 — Category breakdown | Low (data ready) | Low–Medium | --- ## Charting Library Consideration The current implementation uses **hand-rolled SVG donut charts** (no external library). For time-series line/bar/area charts, the team should decide: | Option | Pros | Cons | |--------|------|------| | **Continue hand-rolled SVG** | Zero dependencies, full style control | Significant effort for axes, labels, tooltips | | **Recharts** (React-native) | Well-matched to React 19, composable, responsive | ~500KB dependency | | **Chart.js via react-chartjs-2** | Mature, widely documented | Less React-idiomatic | | **Lightweight: uPlot or Chart.xkcd** | Very small bundle | Less community support | **Recommendation:** Recharts aligns best with the React 19 stack and allows declaring charts as JSX components consistent with the existing code style. It supports all chart types listed above. --- ## Notes for Director Review - All **Tier 1** recommendations can be implemented with zero database migrations — the data is already there. - The **single highest-value addition** is `2.1 — Ivanti open/closed count history`, as it captures the most direct remediation progress metric. It only requires one new table and one line added to the sync handler. - **Compliance charts (1.1–1.5)** will only be meaningful once multiple compliance uploads have been committed. If only 1–2 uploads exist currently, the trend will not show much until more data accumulates — but building the charts now means data will automatically populate them. - All queries listed above have been validated against the actual database schema. --- *Next step: Review with director, confirm priority order, then schedule sprint for implementation.*