Safety Shift Plot: test evidence

Requirement-traced qualification evidence for the safety.viz shift-plot module.

Scope
28 coverage rows38 distinct requirement IDs
Tests executed
217 automated checks36 browser ยท 181 unit
Result
pass all passing9 evidence screenshots
Generated
2026-08-15 09:05 UTC
Environment
linux 6.17.0-1022-azure ยท node v22.23.2 ยท playwright 1.61.1 ยท chromium 149.0.7827.55

Scope & approach

Traceability for the shift-plot module (#14) โ€” the second full renderer, built on the framework proven by the histogram (#2) โ€” per the convention in CONTRIBUTING.md. Requirement IDs are the SSP-* rows of the 39-row reviewed matrix at safety.agent docs/requirements/safety-shift-plot.md; a few module-scheme IDs (SSP-CHART-002, the module-API SSP-API-001 note) name behavior the matrix does not enumerate. Development follows red-green TDD: matrix row โ†’ failing test โ†’ minimal implementation.

Each table row traces one requirement to the automated test(s) that evidence it: the Requirement column shows the reviewed requirement text and its ID, the source-matrix rows link back to the 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/shift-plot.spec.js)

17 requirement rows ยท 16 tests

RequirementSource matrix rowsIssueTests & evidence
SSP-CTRL-001/SSP-REQ-002/SSP-CTRL-002/SSP-CTRL-003

SSP-CTRL-001 This drop-down menu is used to filter the lab variable that is displayed in the chart. The default measure is set to the first variable in the data set, but the default value can be adjusted to any measure upon the project team's request. Users can select any pre-defined measure in this filter. Selecting a measure will display the data points associated with that measure and will display the visits that the measure is collected at underneath the chart.

SSP-REQ-002 This drop-down menu is used to filter the visit data that will be displayed in the chart. The default measure is set to 0, but the default value can be adjusted to any visit(s) upon the project team's request. Users can select one of more pre-defined visit(s) in this filter to be used as the baseline visit(s) in the chart. These values will be displayed on the x-axis.

SSP-CTRL-002 This drop-down menu is used to filter the visit data that will be displayed in the chart. The default measure is set to all visits other than visit 0, but the default value can be adjusted to any visit(s) upon the project team's request. Users can select one of more pre-defined visit(s) in this filter to be used as comparison visit(s) in the chart. These values will be displayed on the y-axis.

SSP-CTRL-003 The chart can be configured to have any number of data filters. Each specified filter has a drop-down menu that is used to filter the visit data that is displayed in the chart.

SSP-CTRL-001, SSP-REQ-002, SSP-CTRL-002#14
  • pass SSP-CTRL-001/SSP-REQ-002/SSP-CTRL-002/SSP-CTRL-003: renders measure, baseline/comparison visit, and filter controls (#14)
Evidence screenshot SSP-CTRL-001-baseline-scatter.png
SSP-CHART-002
โ€”#14
  • pass SSP-CHART-002: the identity line spans a domain shared by both axes (#14)
SSP-COUNT-001/SSP-REG-005

SSP-COUNT-001 Displayed under the header, this statement provides the total number of participants in the data set in integers as well as the percentage of participants shown to one decimal place. In the default view, all participant data is included in the chart, or 100%, unless the project team requests otherwise. When filters are used, the data shown in the histogram is manipulated and the number of participant records shown at the top of the page may increase or decrease, depending on the variable selected.

SSP-REG-005 Confirm that the # of enrolled/randomized participants is visible at the top of the page

SSP-COUNT-001, SSP-REG-005#14
  • pass SSP-COUNT-001/SSP-REG-005: the participant note reports shown-of-total participants (#14)
Evidence screenshot SSP-COUNT-001-participant-count.png
SSP-REG-020

SSP-REG-020 Confirm that invalid results are removed from the data rather than measures with any invalid results. Invalid results include missing and non-numeric values. Then confirm that the number of removed results is noted in the console (You can confirm by downloading the data from CAT, updating the STRESN column so a few values are bad. When you reupload the edited data you will see that the number changes appropriately in the console).

SSP-REG-020#14
  • pass SSP-REG-020: missing and non-numeric results are dropped with a reported count and note (#14)
Evidence screenshot SSP-REG-020-invalid-data-note.png
SSP-REG-001

SSP-REG-001 Confirm that measure filter is working as expected

SSP-REG-001#14
  • pass SSP-REG-001: changing the measure re-pairs the scatter (#14)
SSP-REG-002/SSP-REG-003

SSP-REG-002 Confirm that baseline visit filter are working as expected

SSP-REG-003 Confirm that comparison visit filter are working as expected

SSP-REG-002, SSP-REG-003#14
  • pass SSP-REG-002/SSP-REG-003: changing baseline and comparison visits swaps the axes (#14)
SSP-CTRL-003

SSP-CTRL-003 The chart can be configured to have any number of data filters. Each specified filter has a drop-down menu that is used to filter the visit data that is displayed in the chart.

SSP-CTRL-003#14
  • pass SSP-CTRL-003: applying a filter updates the participant note (#14)
SSP-REG-006

SSP-REG-006 Confirm that hovering over a data point displays subject ID, baseline value, comparison value, change value, and percent of change

SSP-REG-006#14
  • pass SSP-REG-006: the point tooltip reports id, baseline, comparison, change, and percent change (#14)
SSP-REQ-003/SSP-REQ-006/SSP-REQ-007/SSP-REG-004/SSP-REG-012

SSP-REQ-003 Clicking and dragging the mouse across the data points will open a table to the right of the chart that contains a detailed listing of participant records. This linked table contains the participant's subject ID, their baseline value for the selected measure, the comparison value for the selected measure, the difference between the baseline and comparison value using an integer and a percentage. When the table appears, the data points selected will appear in a gray box inside of the chart, lightening all of the data points not selected in the box. When a user clicks outside of the gray box, the gray box disappears along with the detailed table.

SSP-REQ-006 Create a gray box around the selected data points

SSP-REQ-007 Lighten all of the data points not contained in the table

SSP-REG-004 Confirm that detailed record can be viewed by clicking and dragging the mouse across a section of values

SSP-REG-012 Confirm that the detail listing isn't initially available but then does display when the chart is brushed

SSP-REQ-003, SSP-REQ-006, SSP-REQ-007, SSP-REG-004, SSP-REG-012#14
  • pass SSP-REQ-003/SSP-REQ-006/SSP-REQ-007/SSP-REG-004/SSP-REG-012: brushing opens the listing, boxes the selection, and de-emphasizes the rest (#14)
Evidence screenshot SSP-REQ-003-brushed-selection.png
SSP-REG-011

SSP-REG-011 Confirm that unselecting data points resets the detailed record at the bottom of the page

SSP-REG-011#14
  • pass SSP-REG-011: clearing the selection resets the points and hides the listing (#14)
SSP-REG-008/SSP-REG-009/SSP-REG-010

SSP-REG-008 Confirm that Search works to filter the data displayed in the listing

SSP-REG-009 Confirm that Sort works in the listing by clicking on column headers

SSP-REG-010 Confirm that you can export the displayed data to a csv via via the Export: CSV button

SSP-REG-008, SSP-REG-009, SSP-REG-010#14
  • pass SSP-REG-008/SSP-REG-009/SSP-REG-010: the listing searches, sorts, and exports to CSV (#14)
Evidence screenshot SSP-REG-008-linked-listing.png
SSP-API-003/PPRF-SSP-004

SSP-API-003 The custom participantsSelected event is dispatched on the shell root (bubbling, so wrapper/element-level listeners keep working) whenever the details for one or more participants are viewed (or cleared) by brushing the chart. The event has a custom data property holding an array of the selected IDs (["123-456-7"]) or an empty array when participant details are cleared.

SSP-API-003 + PPRF-SSP-004 (participant-profile matrix)#14
  • pass SSP-API-003/PPRF-SSP-004: brushing dispatches participantsSelected on the shell root, bubbling to the element (#14, #99)
SSP-API-001 (module scheme)

SSP-API-001 A factory to create a custom webChart chart object.

โ€” (see legacy-API note)#14
  • pass SSP-API-001: lifecycle API supports init, setData, setSettings, render, resize, and destroy (#14)
SSP-REG-016/SSP-REG-018

SSP-REG-016 Confirm that the chart appears between controls and listing.

SSP-REG-018 Confirm that the controls appear to left of chart.

SSP-REG-016, SSP-REG-018#14
  • no record shared shell: controls left of the chart, chart above the listing (tests/e2e/site.spec.js)
PPRF-SSP-001
PPRF-SSP-001 (participant-profile matrix)#99
  • pass PPRF-SSP-001: a multi-participant brush collapses the rail to a worst-first stepper whose steps emphasize the chart (#99)
Evidence screenshot PPRF-SSP-001-docked-stepper.pngEvidence screenshot PPRF-SSP-001-railed-stepper.png
PPRF-SSP-002
PPRF-SSP-002 (participant-profile matrix)#99
  • pass PPRF-SSP-002: a single-point brush shows the full railed profile with no stepper, beside the linked listing (#99)
Evidence screenshot PPRF-SSP-002-docked-full-profile.pngEvidence screenshot PPRF-SSP-002-railed-full-profile.png
PPRF-SSP-003
PPRF-SSP-003 (participant-profile matrix)#99
  • pass PPRF-SSP-003: clearing the selection and control-driven redraws empty the rail (#99)

Unit evidence (Vitest โ€” tests/unit/shift-plot/)

11 requirement rows ยท 32 tests

RequirementSource matrix rowsIssueTests & evidence
SSP-CFG-004/005/006

SSP-CFG-004 an object that defines the baseline value

SSP-CFG-005 an object that defines the comparison (or post-baseline) value

SSP-CFG-006 an array of filter variables and associated metadata

SSP-CFG-004, SSP-CFG-005, SSP-CFG-006#14

configure.test.js

  • pass shift-plot configure SSP-CFG-004/005: baseline/comparison visits normalize to arrays and stats fall back to mean (#14)
  • pass shift-plot configure SSP-CFG-004/005/006: default settings map the standard measure, result, visit, and id columns (#14)
  • pass shift-plot configure SSP-CFG-006: filter specs normalize strings and objects to value_col/label pairs (#14)
  • pass shift-plot structureData SSP-CFG-005: multiple comparison visits collapse with the comparison statistic (#14)
SSP-REQ-005

SSP-REQ-005 Display the raw data records, including the subject identifier, baseline value, comparison value, change, and percent change

SSP-REQ-005#14

configure.test.js

  • pass shift-plot configure SSP-REQ-005: details default to id, baseline, comparison, change, and percent change (#14)
  • pass shift-plot structureData SSP-REQ-005/SSP-REG-019: pairs baseline against comparison per participant with change and percent change (#14)
SSP-REG-020

SSP-REG-020 Confirm that invalid results are removed from the data rather than measures with any invalid results. Invalid results include missing and non-numeric values. Then confirm that the number of removed results is noted in the console (You can confirm by downloading the data from CAT, updating the STRESN column so a few values are bad. When you reupload the edited data you will see that the number changes appropriately in the console).

SSP-REG-020#14

structureData.test.js

  • pass shift-plot structureData SSP-REG-020: missing and non-numeric results are removed with a reported count (#14)
Evidence screenshot SSP-REG-020-invalid-data-note.png
SSP-DATA-001

SSP-DATA-001 Safety Shift Plot is initialized with JSON data files that match the format created by d3.csv(). The chart expects an ADaM-esque data structure by default, but can be customized to work with any dataset that contains one row per participant per time point per measure and includes the required columns specified below.

SSP-DATA-001#14

structureData.test.js

  • pass shift-plot checkInputs SSP-DATA-001: missing required variables throw a clear message (#14)
  • pass shift-plot checkInputs SSP-DATA-001: the JSON schema publishes the data contract with required column mappings (#14)
  • pass shift-plot structureData SSP-DATA-001: measure labels are the raw measure value (#14)
SSP-REG-013/SSP-REG-014

SSP-REG-013 Confirm that the User can specify visit_order_col that names a variable that orders the visits.

SSP-REG-014 Confirm that Visit ordering can be set two ways: {"visit_col":"VISIT","visit_order_col":"VISITNUM"} to order visits sequentially and {"visit_col":"VISIT","visit_order_col":"VISIT"} to order visits alphanumerically.

SSP-REG-013, SSP-REG-014#14

structureData.test.js

  • pass shift-plot structureData SSP-REG-013/SSP-REG-014: visits order by visit_order_col, else alphanumerically (#14)
SSP-REQ-005/SSP-REG-019

SSP-REQ-005 Display the raw data records, including the subject identifier, baseline value, comparison value, change, and percent change

SSP-REG-019 Confirm that when a measure contains fewer than five unique values it isn't removed from the data (edit the dataset ADBDS so that it contains at least two results for the same measure from the same USUBJID. The shift plot is plotting the baseline value on the x-axis and the comparison value on the y-axis which requires at least two results).

SSP-REQ-005, SSP-REG-019#14

structureData.test.js

  • pass shift-plot configure SSP-REQ-005: details default to id, baseline, comparison, change, and percent change (#14)
  • pass shift-plot structureData SSP-REQ-005/SSP-REG-019: pairs baseline against comparison per participant with change and percent change (#14)
SSP-CFG-005

SSP-CFG-005 an object that defines the comparison (or post-baseline) value

SSP-CFG-005#14

structureData.test.js

  • pass shift-plot structureData SSP-CFG-005: multiple comparison visits collapse with the comparison statistic (#14)
SSP-CTRL-001/SSP-CTRL-003

SSP-CTRL-001 This drop-down menu is used to filter the lab variable that is displayed in the chart. The default measure is set to the first variable in the data set, but the default value can be adjusted to any measure upon the project team's request. Users can select any pre-defined measure in this filter. Selecting a measure will display the data points associated with that measure and will display the visits that the measure is collected at underneath the chart.

SSP-CTRL-003 The chart can be configured to have any number of data filters. Each specified filter has a drop-down menu that is used to filter the visit data that is displayed in the chart.

SSP-CTRL-001, SSP-CTRL-003#14

structureData.test.js

  • pass shift-plot structureData SSP-CTRL-001: only the selected measure is paired (#14)
  • pass shift-plot structureData SSP-CTRL-003: active filters subset rows by stringified equality (#14)
Evidence screenshot SSP-CTRL-001-baseline-scatter.png
SSP-CHART-002
โ€”#14

structureData.test.js

  • pass shift-plot structureData SSP-CHART-002: the shared domain spans both axes with padding (#14)
SSP-DATA-001/SSP-DATA-003

SSP-DATA-001 Safety Shift Plot is initialized with JSON data files that match the format created by d3.csv(). The chart expects an ADaM-esque data structure by default, but can be customized to work with any dataset that contains one row per participant per time point per measure and includes the required columns specified below.

SSP-DATA-003 No customization is needed to initialize a chart when the data object contains all default variables above. Just pass an empty object ({}) for settings and initialize the chart like so: safetyShiftPlot('body', {}).init(data).

SSP-DATA-001, SSP-DATA-003#14

checkInputs.test.js

  • pass shift-plot checkInputs SSP-DATA-001: missing required variables throw a clear message (#14)
  • pass shift-plot checkInputs SSP-DATA-001: the JSON schema publishes the data contract with required column mappings (#14)
  • pass shift-plot checkInputs SSP-DATA-003: valid long-format data with optional columns passes (#14)
  • pass shift-plot structureData SSP-DATA-001: measure labels are the raw measure value (#14)
PPRF-SSP-001..004
PPRF-SSP-001..004 (participant-profile matrix)#99

profile-adoption.test.js

  • pass shift-plot cohort stepper (PPRF-SSP-001, PPRF-11) PPRF-SSP-001: a multi-participant brush collapses the rail to a worst-first stepper
  • pass shift-plot cohort stepper (PPRF-SSP-001, PPRF-11) PPRF-SSP-001: stepping emphasizes the stepped point on the chart without re-dispatching
  • pass shift-plot dispatch target (PPRF-SSP-004, SSP-API-003) PPRF-SSP-004: is idempotent under repeated identical dispatches
  • pass shift-plot dispatch target (PPRF-SSP-004, SSP-API-003) PPRF-SSP-004: the dispatch still bubbles to element-level listeners (backward compatibility)
  • pass shift-plot dispatch target (PPRF-SSP-004, SSP-API-003) PPRF-SSP-004: the dispatch travels via the shell root (the house participantsSelected contract)
  • pass shift-plot participant-profile adoption (PPRF-SSP-002) PPRF-SSP-002: a single-point brush shows the full profile with no stepper AND keeps the listing
  • pass shift-plot participant-profile adoption (PPRF-SSP-002) PPRF-SSP-002: derives the rail feed rows ONCE per setData from rawData through the hep-core cleaners
  • pass shift-plot participant-profile adoption (PPRF-SSP-002) PPRF-SSP-002: host listing columns are NOT the rail header details (profile_details defaults empty)
  • pass shift-plot participant-profile adoption (PPRF-SSP-002) PPRF-SSP-002: mounts the rail by default (config-on) into the shell profile slot
  • pass shift-plot participant-profile adoption (PPRF-SSP-002) PPRF-SSP-002: passes the host lab mappings through, mapping visitn_col from visit_order_col
  • pass shift-plot participant-profile adoption (PPRF-SSP-002) PPRF-SSP-002: profile: false leaves the slot empty and mounts no dock
  • pass shift-plot rail clear paths (PPRF-SSP-003, PPRF-11) PPRF-SSP-002: setSettings({profile: false}) unmounts a live dock; re-enabling remounts it
  • pass shift-plot rail clear paths (PPRF-SSP-003, PPRF-11) PPRF-SSP-003: a control-driven render resets the selection AND the rail (render preamble)
  • pass shift-plot rail clear paths (PPRF-SSP-003, PPRF-11) PPRF-SSP-003: clearing the selection (the tiny-click path) empties the rail and the emphasis
  • pass shift-plot rail clear paths (PPRF-SSP-003, PPRF-11) PPRF-SSP-003: destroy tears the rail down with the instance
  • pass shift-plot rail clear paths (PPRF-SSP-003, PPRF-11) PPRF-SSP-003: dock Clear empties an externally-fed cohort the host never selected (PPRF-11)
  • pass shift-plot rail clear paths (PPRF-SSP-003, PPRF-11) PPRF-SSP-003: the rail Clear affordance routes through the host clear path
Evidence screenshot PPRF-SSP-001-docked-stepper.pngEvidence screenshot PPRF-SSP-001-railed-stepper.pngEvidence screenshot PPRF-SSP-002-docked-full-profile.pngEvidence screenshot PPRF-SSP-002-railed-full-profile.png

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 (39 rows)

Of the 39 reviewed SSP-* rows, 32 are covered by the browser/unit evidence above and 7 are descoped with rationale below (honest routing per the histogram precedent).

Railed participant profile (#99, PPRF-SSP)

The shared participant-profile module mounts in the rail beside the chart (config-on, profile: true) and is fed by the renderer's brush selection via the participantsSelected dispatch โ€” the rail SUPPLEMENTS the linked listing (records vs story), replacing nothing. shift-plot is the rollout's stepper renderer: a brush routinely catches several participants, so the rail collapses to the worst-first cohort stepper ("1 of N"), stepper navigation border-emphasizes the stepped point on the chart without re-dispatching, and a single-point brush shows the full profile directly. The adoption moved the SSP-API-003 dispatch target from the host element to the shell root (PPRF-SSP-004) โ€” backward-compatible because the event bubbles. Adoption rows are PPRF-SSP-001..004 in the participant-profile matrix; unit evidence lives in tests/unit/shift-plot/profile-adoption.test.js.

View-selector rollout (VIEW-3, #76)

One primary display โ€” the baseline-vs-comparison scatter. The linked listing is a brush-selection drill-down shown in addition to the chart, not a mutually-exclusive view switch. Ruled single-view in the shared view-selector rollout (#76) โ€” no view control is added; see view-selector-inventory.md.

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/shift-plot/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.