Safety Histogram migration (pilot)

Design document for Requirement #2 · obot.roadmap · depends on #1 design

STATUS: Signed off 2026-07-04 · drafted 2026-07-02, ported to obot.roadmap 2026-07-03, updated 2026-07-04 (qcthat alignment) · drafted by Claude Code using Fable 5, reviewed by @jwildfire

Context

The histogram is the pilot for the renderer-migration recipe. Its inputs are already in place:

This document designs the two remaining slices: extract the pilot into a safety.viz module, and bind it as an htmlwidget in gsm.safety.

Slice ② — extract the module into safety.viz

The monolithic pilot decomposes into the module layout defined in 1_design.html. Mapping of existing pilot code:

safety.viz fileFrom the pilot (src/index.js)
src/histogram.jsPublic entry + the lifecycle API (init, setData, setSettings, render, resize, destroy) — kept verbatim in shape (SH-API-001).
src/histogram/checkInputs.jsNew — validates data/settings against the JSON schema (below); replaces ad-hoc guards.
src/histogram/configure.jsSettings defaults + merge (measure/value/participant column mapping, filters, bin config).
src/histogram/structureData.jsFiltering, missing/non-numeric removal + counts (SH-DATA-002), binning algorithms (SH-CTRL-006), grouping (SH-CHART-004).
src/histogram/getPlugins.jsTooltip/footnote behavior (SH-CHART-002), normal-range overlay region (SH-CTRL-004), p-value annotations + disclaimer (SH-CHART-005), bar-selection highlighting.
src/histogram/getScales.jsX-axis domain/limits (SH-CTRL-005), tick modes — bin centers vs boundaries (SH-CTRL-007).
src/histogram/listing.jsLinked participant listing: pagination, search, sort, CSV export (SH-LIST-001..004).
tests/e2e/histogram.spec.jsPort of the pilot's 10 Playwright specs, renamed to requirement-ID keys, then extended to the remaining browser-evidence matrix rows.

Controls (measure select, filters, normal-range checkbox, axis inputs, binning, group-by) stay part of the module — the legacy tool's control panel is in scope for feature parity (SH-CTRL-001..007), rendered by the module into its container.

Data contract (JSON schema)

FieldRequiredSource rows
measure, resultyesSH-DATA-001 — one record per measurement, long format.
participant, unit, normalLow, normalHighoptionalSH-DATA-003; normal-range behavior degrades gracefully when absent (SH-FUNC-004C).
filters[], details[]optionalSH-DATA-003 — configured filter and listing columns.

Missing/non-numeric results are removed with a reported count (console + page note), per SH-DATA-002 — the pilot's behavior is the accepted resolution of the matrix's open invalid-data question.

Test plan (TDD)

  1. Copy the matrix into the working set; route rows by Evidence Type — unit rows (binning, data prep, config merge) → Vitest; browser rows → Playwright.
  2. Write requirement-ID-keyed tests first (red), porting the 10 pilot specs as the seed; each test name also references the GitHub issue it evidences in qcthat's (#N) style (see 1_design.html, Traceability).
  3. Extract/refactor pilot code into the module layout until green; no behavior changes beyond the file reorganization.
  4. Maintain the module coverage table (requirement ID → issue → test) in docs/.

Slice ③ — Widget_Histogram in gsm.safety

Resolved and deferred items

ItemDisposition
Invalid-data handling (matrix open item)Resolved: adopt the pilot's behavior — drop with reported count and visible note (SH-DATA-002).
p-value annotations (blocked rows)Deferred: ship the pilot's approximate screening annotations with the validation disclaimer (as merged); exact-method rows stay blocked pending method + fixtures and become a follow-up requirement if pursued.
Visual regression + accessibility hardeningOut of scope for the pilot (flagged in the pilot's coverage matrix); explicit follow-up gates before the renderer batch.
Legacy Webcharts APINot preserved (SH-API-002) — already replaced by the lifecycle API.
qcthat integration for JS test evidenceInterim: test names reference GitHub issues now (qcthat's (#N) style) to future-proof the evidence; full qcthat-compatible reporting is backlog Requirement #15.
Sign-off: recorded on #2 (2026-07-04). Slices ② and ③ are filed: safety.viz#2 and gsm.safety#30 (slice ① was already done).