Safety Results Over Time: test evidence

Requirement-traced qualification evidence for the safety.viz results-over-time module.

Scope
25 coverage rows44 distinct requirement IDs
Tests executed
147 automated checks24 browser · 123 unit
Result
pass all passing10 evidence screenshots
Generated
2026-07-12 05:50 UTC
Environment
linux 6.17.0-1018-azure · node v22.23.1 · playwright 1.61.1 · chromium 149.0.7827.55

Scope & approach

Traceability for the results-over-time module (a Chart.js reimplementation of RhoInc/safety-results-over-time, under #27), per the convention in CONTRIBUTING.md. Requirement IDs (SROT-*) come from the reviewed 58-row matrix at safety.agent docs/requirements/safety-results-over-time.md, whose Evidence Type column routes rows (unit → Vitest, browser → Playwright).

Each table row traces one requirement to the automated test(s) that evidence it: the requirement ID and its source-matrix rows link back to the reviewed specification, the issue column links the implementing work, and the result column shows the recorded outcome of every matching test from the committed evidence.json with its captured screenshots. Browser evidence is captured at fixed conditions (1280×800, device scale 1) on the canonical Linux CI environment.

Browser evidence (Playwright — tests/e2e/results-over-time.spec.js)

15 requirement rows · 15 tests

RequirementSource matrix rowsIssueTests & evidence
SROT-FUNC-001/SROT-FUNC-002SROT-FUNC-001, SROT-FUNC-002#27
  • pass SROT-FUNC-001/SROT-FUNC-002: renders measure, group, filter, y-axis, scale, and display controls (#27)
Evidence screenshot SROT-FUNC-001-control-panel.png
SROT-FUNC-003/SROT-REG-001SROT-FUNC-003, SROT-REG-001#27
  • pass SROT-FUNC-003: participant note updates when a filter is applied (#27)
Evidence screenshot SROT-FUNC-003-participant-count.png
SROT-DATA-002SROT-DATA-002#27
  • pass SROT-DATA-002: missing and non-numeric results are dropped with a reported count and visible note (#27)
Evidence screenshot SROT-DATA-002-invalid-data-note.png
SROT-FUNC-008/SROT-REG-009SROT-FUNC-008, SROT-REG-009#27
  • pass SROT-FUNC-008: box-and-whisker marks render and toggle off with the Box plots control (#27)
Evidence screenshot SROT-FUNC-008-box-plots.png
SROT-REG-002/SROT-REG-003SROT-REG-002, SROT-REG-003#27
  • pass SROT-REG-002/SROT-REG-003: grouping draws side-by-side boxes with a group-ordered legend (#27)
Evidence screenshot SROT-REG-002-grouped-box-plots.png
SROT-REG-010/SROT-REG-012SROT-REG-010, SROT-REG-012#27
  • pass SROT-REG-010/SROT-REG-012: the outlier overlay shows and hides with the Outliers control (#27)
Evidence screenshot SROT-REG-010-outliers.png
SROT-REG-011SROT-REG-011#27
  • pass SROT-REG-011: outlier points carry a larger hover radius than their resting radius (#27)
SROT-REG-014/SROT-REG-015SROT-REG-014, SROT-REG-015#27
  • pass SROT-REG-014/SROT-REG-015: hovering a box exposes the summary statistics tooltip (#27)
SROT-FUNC-004/SROT-REG-016/SROT-REG-017SROT-FUNC-004, SROT-REG-016, SROT-REG-017#27
  • pass SROT-FUNC-004/SROT-REG-016/SROT-REG-017: y-limit inputs redraw and invert a crossed pair (#27)
Evidence screenshot SROT-FUNC-004-y-limits.png
SROT-FUNC-005/SROT-REG-020SROT-FUNC-005, SROT-REG-020#27
  • pass SROT-FUNC-005/SROT-REG-020: Reset Limits restores the data extent (#27)
SROT-REG-018SROT-REG-018#27
  • pass SROT-REG-018: the Scale control switches the y-axis between linear and log (#27)
Evidence screenshot SROT-REG-018-log-scale.png
SROT-FUNC-006/SROT-REG-004/SROT-REG-005SROT-FUNC-006, SROT-REG-004, SROT-REG-005#27
  • pass SROT-FUNC-006/SROT-REG-004/SROT-REG-005: the Visits without data control adds empty timepoints (#27)
Evidence screenshot SROT-FUNC-006-visits-without-data.png
SROT-FUNC-007/SROT-REG-006/SROT-REG-007SROT-FUNC-007, SROT-REG-006, SROT-REG-007#27
  • pass SROT-FUNC-007/SROT-REG-006/SROT-REG-007: the Unscheduled visits control shows unscheduled timepoints (#27)
Evidence screenshot SROT-FUNC-007-unscheduled-visits.png
SROT-REG-023/SROT-REG-024SROT-REG-023, SROT-REG-024#27
  • pass SROT-REG-023/SROT-REG-024: start_value selects the initial measure and falls back when absent (#27)
SROT-API— (see legacy-API note)#27
  • pass SROT-API: lifecycle API supports init, setData, setSettings, render, resize, and destroy (#27)

Unit evidence (Vitest — tests/unit/results-over-time/)

10 requirement rows · 25 tests

RequirementSource matrix rowsIssueTests & evidence
SROT-CFG-004/005/006/007/009SROT-CFG-004, SROT-CFG-005, SROT-CFG-007, SROT-CFG-009#27

configure.test.js

  • pass results-over-time configure SROT-CFG-004/005/006/007/009: default column mappings match the original renderer (#27)
  • pass results-over-time structureData SROT-CFG-005: computeVisitOrder falls back to alphanumeric order without an order column (#27)
  • pass results-over-time structureData SROT-CFG-005/006: computeVisitOrder sorts by the numeric order column when present (#27)
SROT-CFG-008/012/013/014/015SROT-CFG-008, SROT-CFG-012, SROT-CFG-013, SROT-CFG-014#27

configure.test.js

  • pass results-over-time configure SROT-CFG-012: filters normalize to { value_col, label } specs and drop blanks (#27)
  • pass results-over-time configure SROT-CFG-013/014: groups always offer a leading None and group_by falls back to it (#27)
  • pass results-over-time configure SROT-CFG-014: an explicit group_by is honored and its column is offered (#27)
  • pass results-over-time configure SROT-CFG-015/DATA-003: box/outlier/visit toggles default to the original renderer values (#27)
SROT-CFG-017/018/019, SROT-DATA-003SROT-CFG-017, SROT-CFG-019, SROT-DATA-003#27

configure.test.js / structureData.test.js

  • pass results-over-time configure SROT-CFG-017/018/019: unscheduled-visit detection defaults are preserved (#27)
  • pass results-over-time structureData SROT-CFG-017/018: parseUnscheduledPattern reads the /.../flags string form (#27)
  • pass results-over-time structureData SROT-CFG-019: isUnscheduledVisit lets an explicit values list take precedence over the pattern (#27)
SROT-DATA-001/002SROT-DATA-001, SROT-DATA-002#27

checkInputs.test.js / structureData.test.js

  • pass results-over-time checkInputs SROT-DATA-001: passes when the required measure, result, and visit columns are present (#27)
  • pass results-over-time checkInputs SROT-DATA-001: throws naming every missing required column (#27)
  • pass results-over-time checkInputs SROT-DATA-002: an empty dataset reports all required columns missing (#27)
  • pass results-over-time structureData SROT-DATA-002: cleanData drops missing and non-numeric results with a reported count (#27)
Evidence screenshot SROT-DATA-002-invalid-data-note.png
SROT-CFG-005/006SROT-CFG-005, SROT-CFG-006#27

structureData.test.js

  • pass results-over-time structureData SROT-CFG-005: computeVisitOrder falls back to alphanumeric order without an order column (#27)
  • pass results-over-time structureData SROT-CFG-005/006: computeVisitOrder sorts by the numeric order column when present (#27)
SROT-REG-010/012SROT-REG-010, SROT-REG-012#27

structureData.test.js

  • pass results-over-time structureData SROT-REG-010/012: flagOutliers marks values outside the 5th/95th percentiles only when enabled (#27)
Evidence screenshot SROT-REG-010-outliers.png
SROT-REG-002/010 (grouping-change regression)SROT-REG-002, SROT-REG-010#27

structureData.test.js

  • pass results-over-time structureData SROT-REG-002/010: flagOutliers recomputes each row group when the grouping changes between renders (#27)
  • pass results-over-time structureData SROT-REG-010/012: flagOutliers marks values outside the 5th/95th percentiles only when enabled (#27)
Evidence screenshot SROT-REG-002-grouped-box-plots.pngEvidence screenshot SROT-REG-010-outliers.png
SROT-REG-021SROT-REG-021#27

structureData.test.js

  • pass results-over-time structureData SROT-REG-021: measureLabel appends the unit when present (#27)
SROT-REG-015/016/017/020SROT-REG-015, SROT-REG-016, SROT-REG-017, SROT-REG-020#27

getScales.test.js

  • pass results-over-time getScales SROT-REG-015: statPrecisions gives min/max the base, quantiles +1, and StDev +2 (#27)
  • pass results-over-time getScales SROT-REG-015: yPrecision follows the original log10-range rule (#27)
  • pass results-over-time getScales SROT-REG-016/020: resolveYDomain uses the data extent unless a limit overrides it (#27)
  • pass results-over-time getScales SROT-REG-017: normalizeDomain swaps a crossed lower/upper pair in place (#27)
SROT-REG-003/011/014/015SROT-REG-003, SROT-REG-011, SROT-REG-014, SROT-REG-015#27

getPlugins.test.js

  • pass results-over-time getPlugins SROT-REG-003: groupColors assigns a stable color per group in order (#27)
  • pass results-over-time getPlugins SROT-REG-011: outlierTooltip names the participant and its value (#27)
  • pass results-over-time getPlugins SROT-REG-014/015: summaryTooltip lists every statistic at the required precision (#27)
  • pass results-over-time getScales SROT-REG-015: statPrecisions gives min/max the base, quantiles +1, and StDev +2 (#27)
  • pass results-over-time getScales SROT-REG-015: yPrecision follows the original log10-range rule (#27)
  • pass results-over-time structureData SROT-REG-014/015: summarize returns the tooltip statistics for a sorted sample (#27)

Visual evidence

Every screenshot below is a committed baseline: the same PNG is the visual-regression baseline the browser suite asserts against and the evidence artifact shown here. Click any capture for the full-resolution image.

Source-matrix routing status (58 rows)

Covered: 44 of 58 rows (the browser and unit tables above). The remaining 14 rows are descoped, honestly, for the reasons below.

Reproducing this report

The evidence set is regenerated from a full test run and committed with the code it qualifies; CI fails when they drift. To verify or rebuild it:

npm ci
npm run evidence:check   # compare a fresh run against the committed evidence
npm run evidence         # regenerate docs/evidence/results-over-time/evidence.json

Screenshot baselines are canonical to the Linux CI runner; the repository's Update evidence baselines workflow is the authoritative way to refresh them. See CONTRIBUTING.md for the traceability convention.