Hepatic Safety Explorer: test evidence

Requirement-traced qualification evidence for the safety.viz hep-explorer module.

Scope
69 coverage rows114 distinct requirement IDs
Tests executed
561 automated checks66 browser · 495 unit
Result
pass all passing26 evidence screenshots
Generated
2026-08-27 04:17 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 hep-explorer module (a Chart.js reimplementation of the SafetyGraphics/hep-explorer eDISH hepatotoxicity explorer matching its core behavior, under #43), per the convention in CONTRIBUTING.md. Requirement IDs use the module's condensed **HEP-*** scheme cited in the source and test names — HEP-CHART-* (scatter/axes), HEP-QUAD-* (quadrants/cutpoints), HEP-CTRL-* (controls), HEP-DISPLAY-* (eDISH/mDISH standardization), HEP-SELECT-* (participant detail/visit path), HEP-DATA-* (data contract/cleaning), HEP-API-* (lifecycle/events), and HEP-COMP-* (the composite plot for abnormal-baseline subjects, #67). A reviewed source requirement matrix (hep-explorer.md in the requirements repo) is not yet published, so the source-matrix column is pending; each row lists the module IDs its test covers.

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/hep-explorer.spec.js)

44 requirement rows · 43 tests

RequirementSource matrix rowsIssueTests & evidence
HEP-CTRL-001/002/006/007/008/009/010/011/012

HEP-CTRL-001 An X-axis Measure select offers the configured x_options and plots x_default (ALT) on first render.

HEP-CTRL-002 A Y-axis Measure select is rendered only when y_options has more than one entry; a single option (TB) drops the control.

HEP-CTRL-006 An Axis Type select toggles both scatter axes between linear and log.

HEP-CTRL-007 A Point Size select offers Uniform and rRatio, sizing points either at a uniform radius or scaled by the participant R-Ratio.

HEP-CTRL-008 A timing-window number input ('Highlight Points Based on Timing') sets the day threshold (default 30) that decides filled vs hollow points.

HEP-CTRL-009 A Group color-by select (always offering a leading 'None' option) colors the points by the chosen column and renders a legend; the control is dropped when only None is available.

HEP-CTRL-010 An R-Ratio range filter (min/max number inputs plus a reset) filters participants by R-Ratio, seeding a null max from the largest finite participant R-Ratio in the data.

HEP-CTRL-011 Categorical data-filter selects narrow the shown participants; filters whose column is absent from the data are dropped with a console warning.

HEP-CTRL-012 Per-axis Reference Line number inputs set the X/Y Hy's-Law cutpoints, clamped to ≥ 0, driving the quadrant classification.

#43
  • pass HEP-CTRL-001/HEP-CTRL-002/HEP-QUAD-001/HEP-DISPLAY-001/HEP-CTRL-006/HEP-CTRL-007/HEP-CTRL-008/HEP-CTRL-009/HEP-CTRL-010/HEP-CTRL-011/HEP-CTRL-012: renders the full control panel (#43)
Evidence screenshot HEP-CTRL-001-control-panel.pngEvidence screenshot HEP-CTRL-006-log-axes.pngEvidence screenshot HEP-CTRL-009-color-by-group.pngEvidence screenshot HEP-DISPLAY-001-mdish-display.png
HEP-DATA-001, HEP-CTRL-011

HEP-DATA-001 Input data is long-format — one record per participant per measure per visit/day — and the module reduces it to a single plotted point per participant (settings.id_col).

HEP-CTRL-011 Categorical data-filter selects narrow the shown participants; filters whose column is absent from the data are dropped with a console warning.

#43
  • pass HEP-DATA-001/HEP-CTRL-011: participant note reports N and % and updates on filter (#43)
HEP-DATA-003

HEP-DATA-003 cleanData removes rows whose result is blank or non-numeric, or whose ULN is non-numeric or ≤ 0, returns the count of removed records, and that count is surfaced on-page as a warning note reading "N missing or non-numeric results removed."

#43
  • pass HEP-DATA-003: missing and non-numeric results are dropped with a reported count and note (#43)
Evidence screenshot HEP-DATA-003-invalid-data-note.png
HEP-QUAD-002/003/004/005

HEP-QUAD-002 Two dashed grey cut-lines at the X and Y cutpoints split the plot into the four named Hy's-Law quadrants — Possible Hy's Law Range (upper-right, High/High), Hyperbilirubinemia (upper-left, Normal/High), Temple's Corollary (lower-right, High/Normal), and Normal Range (lower-left, Normal/Normal).

HEP-QUAD-003 Each quadrant corner draws its label with the live participant percent (e.g. "Possible Hy's Law Range (20.0%)"), anchored to its corner and updating as points are reclassified.

HEP-QUAD-004 classifyQuadrants buckets each shown point into a quadrant by xCat = x ≥ xCut ? High : Normal (likewise y), so a point exactly on a cutpoint counts as High, and returns per-position counts and percent-of-shown-points (zero percents for empty data).

HEP-QUAD-005 A quadrant summary table with Quadrant | # | % columns lists all four quadrants with their live participant count and percent, staying in sync with the corner labels as cuts move.

#43
  • pass HEP-QUAD-002/HEP-QUAD-003/HEP-QUAD-004/HEP-QUAD-005: quadrant cut-lines classify one participant per quadrant and drive the summary table (#43)
Evidence screenshot HEP-QUAD-002-quadrant-summary.png
HEP-MARG-001/002/003

HEP-MARG-001 A marginal box-and-whisker plot summarizing the x measure is drawn in a reserved strip above the scatter, and one summarizing the y measure in a reserved strip to its right — box Q1–Q3 with the median rule, whiskers at the 5th and 95th percentiles — computed over the participants CURRENTLY SHOWN (after every filter) using the library's shared R-7 quantiles.

HEP-MARG-002 An axis rug — one tick per shown participant — is drawn along the bottom edge for the x measure and the left edge for the y measure, inside the plot, so ties and pile-ups the overlapping point cloud conceals stay visible.

HEP-MARG-003 A Marginal Distributions control offers box plots and rugs (the default), box plots alone, rugs alone, and hidden; the marginals redraw with every change to the filters, the plotted measures, the display mode and the axis type, and the geometry is published on the chart so the summary can be asserted as statistics rather than pixels.

#47
  • pass HEP-MARG-001/HEP-MARG-002/HEP-MARG-003: marginal box plots and axis rugs summarize each measure beside the cloud (#47)
Evidence screenshot HEP-MARG-001-marginal-box-plots-and-rugs.png
HEP-QUAD-007/008, HEP-CTRL-013/014, HEP-CAUTION-001

HEP-QUAD-007 A Quadrant Labels control turns the four corner labels on and off. Hiding them changes nothing else: the cut-lines still draw, the classification still runs, and the summary table still reports every count and percent.

HEP-QUAD-008 Each row of the quadrant summary table states what landing in that quadrant means clinically, beside the count of participants who did — including, for the upper-right quadrant, that it is a screening RANGE and not a diagnosis, since Hy's Law additionally requires that no other cause explains the injury.

HEP-CTRL-013 Each colour-by legend entry carries that group's participant count and its percentage of the plotted points, so the legend says how much of the chart a group accounts for and not only which colour it is. Percentages are of every plotted point, so a cohort with missing group values does not read as fully covered.

HEP-CTRL-014 When Point Size is set to R Ratio the legend states what size encodes and which way it runs, and notes that participants with no R Ratio are drawn at the base size; the Uniform setting adds no note, because size then carries no meaning to explain.

HEP-CAUTION-001 The chart carries a standing caution — exploratory tool, not validated for clinical use, confirm any signal with a full case review — rendered once into the module shell and shown in EVERY view, not written into the per-view footnote.

#54
  • pass HEP-QUAD-007/HEP-QUAD-008/HEP-CTRL-013/HEP-CTRL-014/HEP-CAUTION-001: the labels, the legend and the caution say what the chart means (#54)
Evidence screenshot HEP-QUAD-008-quadrant-meanings-and-legend-counts.png
HEP-QUAD-006

HEP-QUAD-006 Either cut-line can be moved by dragging it on the plot as well as by typing in its Reference Line input: the pointer takes hold of whichever line it is nearest (never both at once), the line follows the pointer clamped inside the axis, and the quadrant counts, corner percents and summary table reclassify continuously during the drag rather than on release. The drag writes its value into that axis's Reference Line input, and a drag is not also a click on the plot background, so an open selection survives it.

#45
  • pass HEP-QUAD-006: the cut-lines can be dragged, reclassifying live and writing back to the inputs (#45)
Evidence screenshot HEP-QUAD-006-cut-line-drag.png
HEP-QUAD-001/004

HEP-QUAD-001 Default Hy's-Law cutpoints reproduce the original renderer (TB 2×ULN / 4.8×Baseline, ALP 1×ULN / 3.8×Baseline, rRatio 5/5, and a defaults entry of 3×ULN / 3.8×Baseline), giving the classic eDISH view ALT ≥ 3×ULN vs TB ≥ 2×ULN (and mDISH ALT 3.8 vs TB 4.8); cutFor resolves the active cutpoint for a measure+display mode, back-filling any unlisted measure or missing display mode from the defaults entry, and a partial cuts override deep-merges onto the defaults so untouched measures keep their cuts.

HEP-QUAD-004 classifyQuadrants buckets each shown point into a quadrant by xCat = x ≥ xCut ? High : Normal (likewise y), so a point exactly on a cutpoint counts as High, and returns per-position counts and percent-of-shown-points (zero percents for empty data).

#43
  • pass HEP-QUAD-001/HEP-QUAD-004: changing the x-axis reference line reclassifies the quadrants (#43)
HEP-DISPLAY-001/002, HEP-CHART-002

HEP-DISPLAY-001 The Display Type control switches the active standardization between 'Upper limit of normal adjusted (eDISH)' (÷ULN, ×ULN) and 'Baseline adjusted (mDISH)' (÷baseline, ×Baseline), re-deriving every displayed value; in mDISH the ×Baseline denominator for a participant×measure group is the study-day-0 record, else the earliest day (or the first input-order record when no day column is present).

HEP-DISPLAY-002 The ×ULN (eDISH) standardization divides each result by its upper-limit-of-normal value (normal_col_high, default STNRHI).

HEP-CHART-002 Each axis is titled with the selected measure's full label plus a standardization suffix ([×ULN] for eDISH, [×Baseline] for mDISH).

#43
  • pass HEP-DISPLAY-001/HEP-DISPLAY-002/HEP-CHART-002: the display toggle switches eDISH and mDISH axis titles and cutpoints (#43)
Evidence screenshot HEP-DISPLAY-001-mdish-display.png
HEP-SELECT-001/002/003/005/006

HEP-SELECT-001 Clicking a scatter point selects that participant and highlights it (selected point opaque, others dimmed).

HEP-SELECT-002 Selecting a participant opens the docked participant profile's 'Standardized Lab Values by Study Day' spaghetti — one day-ordered line per present liver measure in the active display units — and re-selecting destroys the prior profile charts before drawing the new ones so no Chart.js instance leaks (sv#98: the bespoke drawDetail chart is deleted; the dock delivers this requirement).

HEP-SELECT-003 Selecting a participant draws a visit-path trajectory overlay pairing X/Y standardized values by shared visit (else study day, else sequence) in chronological order, only where both measures are present.

HEP-SELECT-005 Selecting a participant renders a per-measure summary table with Measure | N | Min | Median | Max of the raw (unstandardized) results for each present liver measure.

HEP-SELECT-006 Changing Display Type while a participant is selected re-renders every coordinated panel (overlay, detail chart, summary table, listing) in the new units without losing the selection.

#43
  • pass HEP-SELECT-001/HEP-SELECT-002/HEP-SELECT-003/HEP-SELECT-005/HEP-SELECT-006: clicking a point draws the visit path, railed profile, and linked listing (#43, #98)
  • pass HEP-SELECT-002: selecting a second participant without a background click re-renders the rail without leaking Chart.js instances (#43, #98)
  • pass HEP-SELECT-006: changing Display Type while a participant is selected re-renders the coordinated panels in the new units (#43)
Evidence screenshot HEP-SELECT-001-participant-detail.png
HEP-ANIM-001/003/006/007

HEP-ANIM-001 A study-day playback bar sits beneath the scatter — a play/stop button, a day slider annotated with the first and last study day in the data, a day readout, and a reset back to the peak view. It is drawn only when the records carry more than one distinct study day; undated data gets a stated reason instead of a dead control.

HEP-ANIM-003 At any shown day each point sits on that participant's most recent result at or before it — holding at their first result before they are measured at all — a point outside its own measured span is drawn at half size, and a point whose participant has not yet been recorded is not drawn.

HEP-ANIM-006 While the animation is playing, the quadrant corner labels and the summary table are suppressed: their percents describe the peak-vs-peak classification, which is not what the moving cloud shows.

HEP-ANIM-007 A reset returns the scatter to the static peak-vs-peak reduction every other control describes, clearing the shown day and the trails with it.

#46
  • pass HEP-ANIM-001/HEP-ANIM-003/HEP-ANIM-006/HEP-ANIM-007: the study-day playback bar scrubs the cloud through time and resets back to the peaks (#46)
Evidence screenshot HEP-ANIM-001-study-day-playback.png
HEP-ANIM-004/005/008

HEP-ANIM-004 Each point that moves between two frames leaves a motion trail from where it was to where it now is; a point that held still leaves none, and the trails fade out over the following frames rather than accumulating.

HEP-ANIM-005 A play-through runs at 100ms per remaining study day, capped at 30 seconds, and stops on the stop button, at the end of the range, on any control-driven redraw, and on destroy — so a running animation can never write into a destroyed chart.

HEP-ANIM-008 Scrubbing the slider stops a running play-through rather than fighting it for the day.

#46
  • pass HEP-ANIM-004/HEP-ANIM-005/HEP-ANIM-008: the play control runs the animation, leaves motion trails, and yields to a scrub (#46)
HEP-SELECT-008

HEP-SELECT-008 Each row of the selected participant's measure summary table draws a sparkline of that measure over study day, and the sparkline expands into a full drill-down chart beneath its row, collapsing again on a second activation.

#48
  • pass HEP-SELECT-008: the selected participant’s measure table draws a sparkline per row and expands it into a full chart (#48)
Evidence screenshot HEP-SELECT-008-measure-sparkline-drilldown.png
HEP-PALT-001/003

HEP-PALT-001 An opt-in calculate_palt setting estimates P_ALT — the hepatocyte-loss estimate ALT AUC x peak ALT^0.18 / 10^5 (Chung et al., PMID 30303523) — from the participant's own ALT trajectory, with the AUC integrated trapezoidally over study day x 24 hours on the RAW result rather than on any standardization. A value the caller's own programming supplies via p_alt_col always wins; the estimate only fills a gap.

HEP-PALT-003 Activating the P_ALT figure in the participant profile shows the arithmetic that produced it — the AUC, the peak, and the result — the unit assumption it rests on, the standing not-validated-for-clinical-use caution, and a link to the source paper.

#49
  • pass HEP-PALT-001/HEP-PALT-003: the opted-in P_ALT estimate is shown in the profile header with the arithmetic behind it (#49)
Evidence screenshot HEP-PALT-001-palt-estimate.png
HEP-SELECT-002

HEP-SELECT-002 Selecting a participant opens the docked participant profile's 'Standardized Lab Values by Study Day' spaghetti — one day-ordered line per present liver measure in the active display units — and re-selecting destroys the prior profile charts before drawing the new ones so no Chart.js instance leaks (sv#98: the bespoke drawDetail chart is deleted; the dock delivers this requirement).

#43
  • pass HEP-SELECT-001/HEP-SELECT-002/HEP-SELECT-003/HEP-SELECT-005/HEP-SELECT-006: clicking a point draws the visit path, railed profile, and linked listing (#43, #98)
  • pass HEP-SELECT-002: selecting a second participant without a background click re-renders the rail without leaking Chart.js instances (#43, #98)
Evidence screenshot HEP-SELECT-001-participant-detail.png
HEP-SELECT-007

HEP-SELECT-007 Clicking the scatter background (no point hit) clears the selection, erasing the visit-path overlay, detail panels, and linked listing.

#43
  • pass HEP-SELECT-007: clicking the background clears the selection, railed profile, and listing (#43, #98)
HEP-SELECT-006

HEP-SELECT-006 Changing Display Type while a participant is selected re-renders every coordinated panel (overlay, detail chart, summary table, listing) in the new units without losing the selection.

#43
  • pass HEP-SELECT-006: changing Display Type while a participant is selected re-renders the coordinated panels in the new units (#43)
HEP-CTRL-009

HEP-CTRL-009 A Group color-by select (always offering a leading 'None' option) colors the points by the chosen column and renders a legend; the control is dropped when only None is available.

#43
  • pass HEP-CTRL-009: grouping colors the points and renders a legend (#43)
Evidence screenshot HEP-CTRL-009-color-by-group.png
HEP-CTRL-006, HEP-CHART-003

HEP-CTRL-006 An Axis Type select toggles both scatter axes between linear and log.

HEP-CHART-003 The axis domains toggle between linear and logarithmic scaling and always widen to keep the Hy's-Law cutpoint in view, with a log axis running from the smallest positive value (never 0).

#43
  • pass HEP-CTRL-006/HEP-CHART-003: the axis-type toggle switches both axes between linear and log (#43)
Evidence screenshot HEP-CTRL-006-log-axes.png
HEP-CTRL-017

HEP-CTRL-017 A Log Base control chooses the multiples the logarithmic gridlines land on — log10 (decades) or log2 (doublings) — and is offered ONLY while the Axis Type is log. Choosing a base moves the gridlines and their labels; it does not move the points, because position on a log axis is base-independent. A base whose powers cannot span the domain at least twice is declined, leaving Chart.js its own ticks rather than drawing a single gridline.

#54
  • pass HEP-CTRL-017: the log base picker appears with the log axis and moves the gridlines to every doubling (#54)
Evidence screenshot HEP-CTRL-017-log-base-doublings.png
HEP-AXIS-001/002/003/004

HEP-AXIS-001 Each axis carries Lower and Upper number inputs that display the limit currently in force — the derived eDISH domain by default, or the user's override — and are refilled from the domain every render. Neither box is ever blank.

HEP-AXIS-002 Displaying a limit does not pin it. An unedited limit stays automatic and is re-derived on every render — following the data as filters, cutpoints and the axis type change — while an edited one persists as an override. Changing either axis measure or the Display Type returns BOTH axes to automatic, since a limit typed for one measure (or one standardization) says nothing about another.

HEP-AXIS-003 Each axis has its own Reset Limits control, clearing that axis's overrides and repopulating its inputs with the derived limits. Resetting one axis leaves the other's override in force.

HEP-AXIS-004 An override cannot draw a domain the chart should not show: a limit typed across the other limit AS DISPLAYED is swapped rather than inverted, an empty or non-numeric entry returns that limit to automatic, a non-positive lower bound is refused on a logarithmic axis, and an inverted pair reaching the domain resolver falls back to the derived domain.

#54
  • pass HEP-AXIS-001/002/003/004: each axis carries editable limits that seed from the domain in force (#54)
Evidence screenshot HEP-AXIS-001-axis-limits.png
HEP-CHART-004

HEP-CHART-004 A point's tooltip lists the participant id, R Ratio, each axis measure's standardized peak value with its study day, and the day difference between the two peaks.

#43
  • pass HEP-CHART-004: point tooltips list participant, R Ratio, peaks with days, and the day difference (#43)
HEP-API-003

HEP-API-003 A participantsSelected event fires on the root element both when a participant is selected (detail.data length 1) and when the selection is cleared (detail.data length 0).

#43
  • pass HEP-API-003: participantsSelected fires on select and clear (#43)
HEP-API-001

HEP-API-001 The public safety.viz collection exposes a hepExplorer factory (identical to the named hepExplorer export), and the module instance exposes an init/setData/setSettings/render/resize/destroy lifecycle API where setData and setSettings return the instance for chaining and destroy empties the container.

#43
  • pass HEP-API-001: lifecycle API supports init, setData, setSettings, render, resize, and destroy (#43)
HEP-COMP-006

HEP-COMP-006 The renderer opens on the composite view with a reduced control set, a View control toggles between the composite and scatter views, subjects lacking an on-treatment ALT/BILI peak are excluded, and the view degrades gracefully when baseline or on-treatment values are absent.

#67
  • pass HEP-COMP-006: opens on the composite view with a reduced control set (#67)
HEP-COMP-001/002/003

HEP-COMP-001 Each subject is classified into one of four Hy's-Law quadrants — Normal & NN, Cholestasis, Temple's Corollary, Hy's Law (in the FDA reference's factor order) — by the strict cutpoints ALT > 3×ULN and TB > 2×ULN (the threshold value itself falls on the Normal side, per R cut(right = TRUE)), computed twice per subject: once from the baseline ×ULN (pretreatment) and once from the peak on-treatment ×ULN; the view draws the pretreatment and on-treatment eDISH panels with the baseline-quadrant legend.

HEP-COMP-002 The on-treatment peak is the maximum over ON-TREATMENT records only (FDA AVISITN > 0), taken independently per analyte, so baseline, screening (negative study-day), and duplicate day-0 records are excluded; an on-treatment value exactly at a cutpoint (e.g. BILI 2×ULN) is not elevated (strict >).

HEP-COMP-003 Each subject's ×Baseline (×BLN) value is the peak on-treatment value divided by that subject's own baseline value, and the peak ×ULN and peak ×BLN are independent maxima (ULN can vary across records); these feed the four-panel ×Baseline shift plot.

#67
  • pass HEP-COMP-001/HEP-COMP-002/HEP-COMP-003: draws the eDISH panels, xBLN four-panel plot, and baseline-quadrant legend (#67)
Evidence screenshot HEP-COMP-001-composite-plot.png
HEP-COMP-004/005

HEP-COMP-004 A migration table counts pretreatment × on-treatment quadrant migrations (with row totals, column totals, and grand total) and colors each cell by the FDA 4×4 DILI-concern matrix — red (migration of concern / worsening), yellow (potential concern / lateral single-analyte), green (no concern / potential benefit / improvement toward Normal), gray (no migration, the diagonal): 5 red, 2 yellow, 5 green, 4 gray.

HEP-COMP-005 A by-arm summary buckets each subject's migration by concern color per treatment arm (using the group/arm meta carried on each subject), collapsing to a single "all subjects" row when no arm column is present.

#67
  • pass HEP-COMP-004/HEP-COMP-005: migration table counts and by-arm concern summary (#67)
Evidence screenshot HEP-COMP-004-migration-table.png
HEP-COMP-006

HEP-COMP-006 The renderer opens on the composite view with a reduced control set, a View control toggles between the composite and scatter views, subjects lacking an on-treatment ALT/BILI peak are excluded, and the view degrades gracefully when baseline or on-treatment values are absent.

#67
  • pass HEP-COMP-006: the View control toggles between the composite and scatter views (#67)
HEP-COMP-006

HEP-COMP-006 The renderer opens on the composite view with a reduced control set, a View control toggles between the composite and scatter views, subjects lacking an on-treatment ALT/BILI peak are excluded, and the view degrades gracefully when baseline or on-treatment values are absent.

#67
  • pass HEP-COMP-006: degrades gracefully when baseline or on-treatment values are absent (#67)
HEP-COMP-007

HEP-COMP-007 Hovering or clicking a point traces and multi-selects that participant across every composite panel (each panel carries an id header for the cross-linking).

#67
  • pass HEP-COMP-007: hovering and clicking points traces + multi-selects participants across all panels (#67)
Evidence screenshot HEP-COMP-007-participant-trace.png
HEP-MIG-001, HEP-MIG-014

HEP-MIG-001 The migration view renders three node columns, with each participant's pretreatment (baseline) categorization in the centre column and their maximum on-treatment categorization in one of the two flanking columns.

HEP-MIG-014 The migration view renders both an svg plot and at least one table inside the main column.

#92
  • pass HEP-MIG-001/HEP-MIG-014: the migration view renders BOTH an svg diagram and cross tables in the main column (#92)
Evidence screenshot HEP-MIG-001-migration-sankey.png
HEP-MIG-002/003/010/015

HEP-MIG-002 Placebo-arm ribbons extend leftward from the centre column and active-arm ribbons extend rightward, so the two arms read as mirror images about the shared baseline spine.

HEP-MIG-003 Within every column the quadrant nodes are stacked in severity order from top to bottom — Hy's Law highest and Normal & NN lowest — so vertical position carries the same meaning in all three columns.

HEP-MIG-010 Node fill colours are taken from QUADRANT_STYLE, so a quadrant is drawn in the same colour in the migration view as in the composite view.

HEP-MIG-015 The computed node and ribbon geometry, together with the shared pixels-per-participant scale, is exposed on the root element as $hepSankey.

#92
  • pass HEP-MIG-002/HEP-MIG-003/HEP-MIG-010/HEP-MIG-015: geometry is stashed on the root, placebo runs left, active runs right, Hy's Law on top (#92)
HEP-ACC-001/002/003

HEP-ACC-001 Every ribbon is focusable, carries role="button", and has an accessible name stating the participant count, both quadrants, the arm and the concern direction.

HEP-ACC-002 A focused ribbon is activated by both Enter and Space, producing the same participant selection as a pointer click.

HEP-ACC-003 The Sankey svg carries role="img" and an accessible name summarising the unfavourable and favourable shift counts for each arm.

#92
  • pass HEP-ACC-001/HEP-ACC-002/HEP-ACC-003: ribbons are named, focusable buttons activated by Enter and Space (#92)
Evidence screenshot HEP-ACC-001-migration-ribbon-selected.png
HEP-STEP-001/002/003

HEP-STEP-001 Clicking a ribbon populates the Participants control with that flow's participants and dispatches a participantsSelected event carrying exactly their identifiers.

HEP-STEP-002 With a flow selected, the footnote states the shift, the arm it belongs to and its participant count, and offers a control to review those participants in the composite plot.

HEP-STEP-003 Activating that control switches to the composite view with exactly those participants restored as the selection and highlighted in the ×Baseline panels.

#92
  • pass HEP-STEP-001/HEP-STEP-002/HEP-STEP-003: a ribbon click carries its participants into the composite plot (#92)
Evidence screenshot HEP-STEP-002-migration-handoff.png
HEP-MIG-007

HEP-MIG-007 Ribbon thickness is proportional to participant count on the same pixels-per-participant scale as node heights, and ribbons are painted thickest first so a thin ribbon is never occluded by, and stays clickable over, a thicker one.

#92
  • pass HEP-MIG-007: clicking the centroid computed from $hepSankey selects that flow, so geometry and pointer agree (#92)
HEP-XTAB-001/002/004/005

HEP-XTAB-001 One cross table is rendered per designated arm, with rows for the baseline quadrant and columns for the on-treatment quadrant, both ordered by severity so the tables read in the same direction as the plot.

HEP-XTAB-002 Each cross table carries a total column, a total row, and a grand total.

HEP-XTAB-004 Every cross-table cell count equals the participant count of the ribbon with the same arm, baseline quadrant and on-treatment quadrant.

HEP-XTAB-005 Clicking a cross-table cell produces exactly the same participant selection as clicking its corresponding ribbon.

#92
  • pass HEP-XTAB-001/HEP-XTAB-002/HEP-XTAB-004/HEP-XTAB-005: per-arm cross tables agree with the ribbons and select the same participants (#92)
Evidence screenshot HEP-XTAB-001-migration-cross-tables.png
HEP-STEP-005

HEP-STEP-005 When the Hy's Law to Hy's Law cell is non-empty, the view renders a caution note reporting the count, stating that a shift view cannot detect worsening within a category, and offering to select those participants for individual review.

#92
  • pass HEP-STEP-005: the Hy's Law self-flow raises the caution the paper acknowledges (#92)
Evidence screenshot HEP-STEP-005-migration-hys-law-caution.png
HEP-MIG-013

HEP-MIG-013 A Hide unchanged control removes the diagonal ribbons from the plot and reports the number of participants they represent in the notes, leaving the cross tables unchanged.

#92
  • pass HEP-MIG-013: the Hide unchanged control drops the diagonal ribbons and reports the hidden count (#92)
HEP-STEP-004

HEP-STEP-004 A participant selection made in any of the three views survives a switch to either of the other two, for every ordered pair of views.

#92
  • pass HEP-STEP-004: a selection made in any view survives a switch to either other view (#92)
HEP-ARM-004/005, HEP-DATA-012

HEP-ARM-004 Participants whose arm is designated neither placebo nor active are excluded from the migration view and their count is reported in the notes.

HEP-ARM-005 When fewer than two arms resolve to opposite sides, the Sankey renders unidirectionally with a warning note rather than throwing.

HEP-DATA-012 The migration view reports the count of participants excluded for lacking a usable baseline or a usable on-treatment measurement, separately from the count excluded for an undesignated arm.

#92
  • pass HEP-ARM-004/HEP-ARM-005/HEP-DATA-012: the notes account for every participant the diagram cannot show (#92)
HEP-ARM-005

HEP-ARM-005 When fewer than two arms resolve to opposite sides, the Sankey renders unidirectionally with a warning note rather than throwing.

#92
  • pass HEP-ARM-005: the Migration option is disabled with an explanation when no arm column is mapped (#92)
HEP-MIG-017

HEP-MIG-017 Hovering a ribbon highlights that ribbon and both of its endpoint nodes and opens a positioned HTML tooltip stating the flow's participant count, its two quadrants and its arm; the tooltip is an HTML element rather than an SVG title so it appears in evidence screenshots.

#92
  • pass HEP-MIG-017: hovering a ribbon highlights it with both endpoint nodes and opens an HTML tooltip (#92)
Evidence screenshot HEP-MIG-017-migration-ribbon-tooltip.png
HEP-ARM-007

HEP-ARM-007 Arm designation scopes the migration view's cohort only; the scatter and composite views continue to plot the full cohort regardless of which arms are designated.

#92
  • pass HEP-ARM-007: arm designation scopes the migration cohort only, not the scatter or composite views (#92)
HEP-CTRL-019 (Reset Chart)

HEP-CTRL-019 A Reset Chart control restores the cutpoints, the display mode, the axis type and log base, both axis-limit overrides, the point sizing, the visit window, the filters, the R-Ratio range, the migration diagonal, the active arms and the unscheduled-visit choice to their configured defaults, and re-derives the row set. It is deliberately a PARTIAL reset: the chosen view, the two plotted measures, the grouping, the marginals, the quadrant labels, the animation and the current selection are preserved, because a reader who has navigated to a view and a measure pair expects Reset to restore the controls, not to undo the navigation. This differs from the whole-chart Reset the other renderers ship.

HEP-CTRL-019#136
  • no record reset restores the controls, preserving the view and measures

Unit evidence (Vitest — tests/unit/hep-explorer/)

25 requirement rows · 189 tests

RequirementSource matrix rowsIssueTests & evidence
HEP-CTRL-001/002/006/007/008/009/010/011 (defaults, control specs)

HEP-CTRL-001 An X-axis Measure select offers the configured x_options and plots x_default (ALT) on first render.

HEP-CTRL-002 A Y-axis Measure select is rendered only when y_options has more than one entry; a single option (TB) drops the control.

HEP-CTRL-006 An Axis Type select toggles both scatter axes between linear and log.

HEP-CTRL-007 A Point Size select offers Uniform and rRatio, sizing points either at a uniform radius or scaled by the participant R-Ratio.

HEP-CTRL-008 A timing-window number input ('Highlight Points Based on Timing') sets the day threshold (default 30) that decides filled vs hollow points.

HEP-CTRL-009 A Group color-by select (always offering a leading 'None' option) colors the points by the chosen column and renders a legend; the control is dropped when only None is available.

HEP-CTRL-010 An R-Ratio range filter (min/max number inputs plus a reset) filters participants by R-Ratio, seeding a null max from the largest finite participant R-Ratio in the data.

HEP-CTRL-011 Categorical data-filter selects narrow the shown participants; filters whose column is absent from the data are dropped with a console warning.

#43

configure.test.js

  • pass hep-explorer configure HEP-CTRL-001/002: measure-option lists coerce a single string to an array (port)
  • pass hep-explorer configure HEP-CTRL-008: the timing window defaults to 30 days (port)
  • pass hep-explorer configure HEP-CTRL-009: groups always offer a leading None option and honor group_by (port)
  • pass hep-explorer configure HEP-CTRL-010: r_ratio normalizes to a [min, max] pair with a data-resolved null max by default (port)
  • pass hep-explorer configure HEP-CTRL-011: filter specs normalize strings and objects and preserve a start value (port)
  • pass hep-explorer configure HEP-DATA-002/HEP-CTRL-001/002: measure_values map the short keys to full TEST strings and x/y defaults are ALT vs TB (port)
  • pass hep-explorer configure HEP-DISPLAY-001/HEP-CTRL-006/007: display, axis-type, point-size, and measure constants match the original renderer (port)
  • pass hep-explorer getPlugins HEP-CTRL-009: groupColorScale maps distinct group values to palette colors and cycles (port)
  • pass hep-explorer getScales HEP-CHART-003/HEP-CTRL-006: a log domain runs from the smallest positive value to the max, padded, including the cut (port)
  • pass hep-explorer getScales HEP-CTRL-006: the log axis type switches both scales to logarithmic and drops a non-positive min (port)
  • pass hep-explorer structureData HEP-CTRL-008: buildPoints flags points outside the timing window as hollow (port)
  • pass hep-explorer structureData HEP-CTRL-009: buildPoints tags each point with its participant-level group value when grouping (port)
  • pass hep-explorer structureData HEP-CTRL-010: maxRRatio is the largest finite participant R-Ratio, 0 when none is finite (port)
  • pass hep-explorer structureData HEP-CTRL-011: applyFilters keeps points matching every active filter; unset filters match everything (port)
Evidence screenshot HEP-CTRL-001-control-panel.pngEvidence screenshot HEP-CTRL-009-color-by-group.pngEvidence screenshot HEP-CTRL-006-log-axes.pngEvidence screenshot HEP-DISPLAY-001-mdish-display.png
HEP-QUAD-001, HEP-DATA-001/002, HEP-DISPLAY-001, HEP-SELECT-006 (cuts back-fill, mappings)

HEP-QUAD-001 Default Hy's-Law cutpoints reproduce the original renderer (TB 2×ULN / 4.8×Baseline, ALP 1×ULN / 3.8×Baseline, rRatio 5/5, and a defaults entry of 3×ULN / 3.8×Baseline), giving the classic eDISH view ALT ≥ 3×ULN vs TB ≥ 2×ULN (and mDISH ALT 3.8 vs TB 4.8); cutFor resolves the active cutpoint for a measure+display mode, back-filling any unlisted measure or missing display mode from the defaults entry, and a partial cuts override deep-merges onto the defaults so untouched measures keep their cuts.

HEP-DATA-001 Input data is long-format — one record per participant per measure per visit/day — and the module reduces it to a single plotted point per participant (settings.id_col).

HEP-DATA-002 The liver measures (ALT/AST/TB/ALP) are matched from each row's measure_col value against the full TEST strings mapped by settings.measure_values.

HEP-DISPLAY-001 The Display Type control switches the active standardization between 'Upper limit of normal adjusted (eDISH)' (÷ULN, ×ULN) and 'Baseline adjusted (mDISH)' (÷baseline, ×Baseline), re-deriving every displayed value; in mDISH the ×Baseline denominator for a participant×measure group is the study-day-0 record, else the earliest day (or the first input-order record when no day column is present).

HEP-SELECT-006 Changing Display Type while a participant is selected re-renders every coordinated panel (overlay, detail chart, summary table, listing) in the new units without losing the selection.

#43

configure.test.js

  • pass hep-explorer configure HEP-DATA-001..004: default settings map the standard id, measure, value, unit, normal-range, and timing columns (port)
  • pass hep-explorer configure HEP-DATA-002: a partial measure_values override back-fills the other measures from the defaults (port)
  • pass hep-explorer configure HEP-DATA-002/HEP-CTRL-001/002: measure_values map the short keys to full TEST strings and x/y defaults are ALT vs TB (port)
  • pass hep-explorer configure HEP-DISPLAY-001/HEP-CTRL-006/007: display, axis-type, point-size, and measure constants match the original renderer (port)
  • pass hep-explorer configure HEP-QUAD-001: a partial cuts override deep-merges onto the defaults so untouched measures keep their cuts (port)
  • pass hep-explorer configure HEP-QUAD-001: cutFor back-fills unlisted measures and missing display modes from the defaults entry (port)
  • pass hep-explorer configure HEP-QUAD-001: default cuts give the classic eDISH view — ALT >= 3xULN via the defaults entry, TB >= 2xULN (port)
  • pass hep-explorer configure HEP-SELECT-006: details normalize to spec arrays and default to an empty list for the derived columns (port)
  • pass hep-explorer getScales HEP-DISPLAY-001/HEP-CHART-002: axis suffix and label reflect the active display mode and use the full measure label (port)
  • pass hep-explorer migration view — selection and the two-step hand-off HEP-SELECT-006: a selection carried in from another view arrives selected here (#92)
  • pass hep-explorer structureData HEP-DATA-002: resolveMeasureRows matches rows via the measure_values TEST strings (port)
  • pass hep-explorer structureData HEP-DISPLAY-001: buildPoints in mDISH uses the xBaseline peaks (port)
  • pass hep-explorer structureData HEP-DISPLAY-001: deriveBaseline uses the day-0 record, else the earliest day, as the xBaseline denominator (port)
Evidence screenshot HEP-CTRL-001-control-panel.pngEvidence screenshot HEP-CTRL-006-log-axes.pngEvidence screenshot HEP-DISPLAY-001-mdish-display.png
HEP-DATA-005 (schema required columns)

HEP-DATA-005 checkInputs validates the schema's required column settings (id_col, measure_col, value_col, normal_col_high) against the remapped names and throws a single Error naming every missing variable.

#43

checkInputs.test.js

  • pass hep-explorer checkInputs HEP-DATA-005: missing required columns throw a single error naming every missing variable (port)
  • pass hep-explorer checkInputs HEP-DATA-005: non-array or empty data reports every required column as missing (port)
  • pass hep-explorer checkInputs HEP-DATA-005: remapped column settings are validated against the remapped names (port)
  • pass hep-explorer checkInputs HEP-DATA-005: valid long-format data with the required columns passes (port)
HEP-DATA-002/003/004 (measure resolution, cleaning)

HEP-DATA-002 The liver measures (ALT/AST/TB/ALP) are matched from each row's measure_col value against the full TEST strings mapped by settings.measure_values.

HEP-DATA-003 cleanData removes rows whose result is blank or non-numeric, or whose ULN is non-numeric or ≤ 0, returns the count of removed records, and that count is surfaced on-page as a warning note reading "N missing or non-numeric results removed."

HEP-DATA-004 When studyday_col is present each row's study day is parsed to numeric __hep_day (a blank or non-numeric study day becomes NaN, not coerced to 0); when no row carries a usable study day the timing test degrades gracefully — day_diff is NaN and points render filled (withinWindow true) rather than all-hollow.

#43

structureData.test.js

  • pass hep-explorer configure HEP-DATA-002: a partial measure_values override back-fills the other measures from the defaults (port)
  • pass hep-explorer configure HEP-DATA-002/HEP-CTRL-001/002: measure_values map the short keys to full TEST strings and x/y defaults are ALT vs TB (port)
  • pass hep-explorer structureData HEP-DATA-002: resolveMeasureRows matches rows via the measure_values TEST strings (port)
  • pass hep-explorer structureData HEP-DATA-003: cleanData drops blank/non-numeric values and non-positive ULNs with a count (port)
  • pass hep-explorer structureData HEP-DATA-004: a non-numeric study-day value is treated as absent (NaN), not coerced (port)
  • pass hep-explorer structureData HEP-DATA-004: without a study-day column __hep_day is NaN and timing falls back to filled points (port)
  • pass hep-explorer structureData HEP-DATA-004/HEP-DISPLAY-002: cleanData tags rows with numeric value, ULN, day, and the xULN derivation (port)
  • pass hep-explorer structureData HEP-SELECT-004/HEP-DATA-004: with no study day, assignSequence orders and pairs by per-measure input order (port)
Evidence screenshot HEP-CTRL-001-control-panel.pngEvidence screenshot HEP-DATA-003-invalid-data-note.png
HEP-DISPLAY-001/002/003/004/006 (×ULN, ×Baseline, peaks, R-Ratio)

HEP-DISPLAY-001 The Display Type control switches the active standardization between 'Upper limit of normal adjusted (eDISH)' (÷ULN, ×ULN) and 'Baseline adjusted (mDISH)' (÷baseline, ×Baseline), re-deriving every displayed value; in mDISH the ×Baseline denominator for a participant×measure group is the study-day-0 record, else the earliest day (or the first input-order record when no day column is present).

HEP-DISPLAY-002 The ×ULN (eDISH) standardization divides each result by its upper-limit-of-normal value (normal_col_high, default STNRHI).

HEP-DISPLAY-003 The explorer standardizes and can plot exactly four liver measures — ALT, AST, TB, ALP — resolved from the measure column via the measure_values map, and reduces each participant to one peak record per measure (the record with the maximum value in the active display units, ×ULN or ×Baseline).

HEP-DISPLAY-004 A participant with a missing or zero baseline is dropped from the mDISH scatter (its ×Baseline is NaN) and counted toward the removed-participants note.

HEP-DISPLAY-006 The participant R-Ratio is the peak ALT ×ULN divided by the peak ALP ×ULN, always computed on the ULN scale regardless of display mode and NaN when either peak is missing or ALP's peak ≤ 0.

#43

structureData.test.js

  • pass hep-explorer configure HEP-DISPLAY-001/HEP-CTRL-006/007: display, axis-type, point-size, and measure constants match the original renderer (port)
  • pass hep-explorer display availability (HEP-DISPLAY-006) HEP-DISPLAY-006: both modes are offered when the data supports both (#55)
  • pass hep-explorer display availability (HEP-DISPLAY-006) HEP-DISPLAY-006: neither is an error the caller can render, not an empty plot (#55)
  • pass hep-explorer display availability (HEP-DISPLAY-006) HEP-DISPLAY-006: no reference range withdraws the eDISH mode instead (#55)
  • pass hep-explorer display availability (HEP-DISPLAY-006) HEP-DISPLAY-006: the baseline mode is withdrawn, with a reason, when nothing has one (#55)
  • pass hep-explorer getScales HEP-DISPLAY-001/HEP-CHART-002: axis suffix and label reflect the active display mode and use the full measure label (port)
  • pass hep-explorer structureData HEP-CHART-001/HEP-DISPLAY-003: buildPoints reduces to one peak-vs-peak point per participant with timing and rRatio (port)
  • pass hep-explorer structureData HEP-DATA-004/HEP-DISPLAY-002: cleanData tags rows with numeric value, ULN, day, and the xULN derivation (port)
  • pass hep-explorer structureData HEP-DISPLAY-001: buildPoints in mDISH uses the xBaseline peaks (port)
  • pass hep-explorer structureData HEP-DISPLAY-001: deriveBaseline uses the day-0 record, else the earliest day, as the xBaseline denominator (port)
  • pass hep-explorer structureData HEP-DISPLAY-003: participantPeak picks the record with the max active-display value, per display mode (port)
  • pass hep-explorer structureData HEP-DISPLAY-004: a zero baseline leaves xBaseline NaN so mDISH drops the participant (port)
  • pass hep-explorer structureData HEP-DISPLAY-004: buildPoints in mDISH drops participants without a usable baseline (port)
  • pass hep-explorer structureData HEP-DISPLAY-006: computeRRatio is peak ALT xULN over peak ALP xULN, NaN without ALP (port)
Evidence screenshot HEP-CTRL-006-log-axes.pngEvidence screenshot HEP-DISPLAY-001-mdish-display.png
HEP-CHART-001, HEP-CTRL-008/009/011 (points, timing, group, filters)

HEP-CHART-001 The eDISH scatter reduces the cleaned data to exactly one point per participant, plotting that participant's peak X-measure value against their peak Y-measure value in the active display units.

HEP-CTRL-008 A timing-window number input ('Highlight Points Based on Timing') sets the day threshold (default 30) that decides filled vs hollow points.

HEP-CTRL-009 A Group color-by select (always offering a leading 'None' option) colors the points by the chosen column and renders a legend; the control is dropped when only None is available.

HEP-CTRL-011 Categorical data-filter selects narrow the shown participants; filters whose column is absent from the data are dropped with a console warning.

#43

structureData.test.js

  • pass hep-explorer configure HEP-CTRL-008: the timing window defaults to 30 days (port)
  • pass hep-explorer configure HEP-CTRL-009: groups always offer a leading None option and honor group_by (port)
  • pass hep-explorer configure HEP-CTRL-011: filter specs normalize strings and objects and preserve a start value (port)
  • pass hep-explorer getPlugins HEP-CTRL-009: groupColorScale maps distinct group values to palette colors and cycles (port)
  • pass hep-explorer structureData HEP-CHART-001/HEP-DISPLAY-003: buildPoints reduces to one peak-vs-peak point per participant with timing and rRatio (port)
  • pass hep-explorer structureData HEP-CTRL-008: buildPoints flags points outside the timing window as hollow (port)
  • pass hep-explorer structureData HEP-CTRL-009: buildPoints tags each point with its participant-level group value when grouping (port)
  • pass hep-explorer structureData HEP-CTRL-011: applyFilters keeps points matching every active filter; unset filters match everything (port)
Evidence screenshot HEP-CTRL-009-color-by-group.png
HEP-QUAD-004 (quadrant classification counts/percents)

HEP-QUAD-004 classifyQuadrants buckets each shown point into a quadrant by xCat = x ≥ xCut ? High : Normal (likewise y), so a point exactly on a cutpoint counts as High, and returns per-position counts and percent-of-shown-points (zero percents for empty data).

#43

structureData.test.js

  • pass hep-explorer structureData HEP-QUAD-004: a point exactly on a cutpoint classifies as High, and empty data yields zero percents (port)
  • pass hep-explorer structureData HEP-QUAD-004: classifyQuadrants buckets one point per quadrant with counts and percents (port)
HEP-QUAD-006 (which cut-line the pointer holds, what a drag to a pixel means)

HEP-QUAD-006 Either cut-line can be moved by dragging it on the plot as well as by typing in its Reference Line input: the pointer takes hold of whichever line it is nearest (never both at once), the line follows the pointer clamped inside the axis, and the quadrant counts, corner percents and summary table reclassify continuously during the drag rather than on release. The drag writes its value into that axis's Reference Line input, and a drag is not also a click on the plot background, so an open selection survives it.

#45

cutDrag.test.js

  • pass hep-explorer cut-line drag geometry (HEP-QUAD-006) HEP-QUAD-006: a drag reads the axis value under the pointer, clamped to the plot (#45)
  • pass hep-explorer cut-line drag geometry (HEP-QUAD-006) HEP-QUAD-006: dragged values are rounded to what the number input can hold (#45)
  • pass hep-explorer cut-line drag geometry (HEP-QUAD-006) HEP-QUAD-006: nothing is grabbable outside the plot or without cuts (#45)
  • pass hep-explorer cut-line drag geometry (HEP-QUAD-006) HEP-QUAD-006: the pointer takes hold of whichever cut-line it is on (#45)
Evidence screenshot HEP-QUAD-006-cut-line-drag.png
HEP-ANIM-001..005 (day range, frames, position at a day, trails, duration)

HEP-ANIM-001 A study-day playback bar sits beneath the scatter — a play/stop button, a day slider annotated with the first and last study day in the data, a day readout, and a reset back to the peak view. It is drawn only when the records carry more than one distinct study day; undated data gets a stated reason instead of a dead control.

HEP-ANIM-002 Playback frames are built for the SHOWN participants, in the same order as the plotted points, each carrying its day-ordered X- and Y-measure series in the active display units plus that participant's own first/last record day. A participant with no usable value on either plotted measure is left out, exactly as the static scatter drops them.

HEP-ANIM-003 At any shown day each point sits on that participant's most recent result at or before it — holding at their first result before they are measured at all — a point outside its own measured span is drawn at half size, and a point whose participant has not yet been recorded is not drawn.

HEP-ANIM-004 Each point that moves between two frames leaves a motion trail from where it was to where it now is; a point that held still leaves none, and the trails fade out over the following frames rather than accumulating.

HEP-ANIM-005 A play-through runs at 100ms per remaining study day, capped at 30 seconds, and stops on the stop button, at the end of the range, on any control-driven redraw, and on destroy — so a running animation can never write into a destroyed chart.

#46

animation.test.js

  • pass hep-explorer animation — motion trails (HEP-ANIM-004) emits nothing when no point moved between two days
  • pass hep-explorer animation — motion trails (HEP-ANIM-004) ignores points that are not in both frames
  • pass hep-explorer animation — motion trails (HEP-ANIM-004) joins each point’s previous position to its new one, and only for points that moved
  • pass hep-explorer animation — per-participant frames (HEP-ANIM-002) carries one ordered x/y series per participant plus that participant’s day range
  • pass hep-explorer animation — per-participant frames (HEP-ANIM-002) drops participants with no usable value on either plotted measure
  • pass hep-explorer animation — playback duration (HEP-ANIM-005) caps a long study at the 30-second ceiling
  • pass hep-explorer animation — playback duration (HEP-ANIM-005) never returns a non-positive duration
  • pass hep-explorer animation — playback duration (HEP-ANIM-005) runs 100ms per remaining day for short studies (the original’s formula)
  • pass hep-explorer animation — position at a day (HEP-ANIM-003) holds a point at its first observation before that participant enrols
  • pass hep-explorer animation — position at a day (HEP-ANIM-003) holds every point on its last observation once the study day runs past it
  • pass hep-explorer animation — position at a day (HEP-ANIM-003) marks a point out of range before its first and after its last measurement
  • pass hep-explorer animation — position at a day (HEP-ANIM-003) places each point at its most recent observation at or before the day
  • pass hep-explorer animation — position at a day (HEP-ANIM-003) reports enrolment from the participant’s own first record, not the study range
  • pass hep-explorer animation — study-day range (HEP-ANIM-001) returns null when no row carries a usable study day
  • pass hep-explorer animation — study-day range (HEP-ANIM-001) spans the smallest to largest study day carried by the plotted measures
Evidence screenshot HEP-ANIM-001-study-day-playback.png
HEP-PALT-001/002/003 (AUC × peak^0.18 / 1e5, when it declines, the shown note)

HEP-PALT-001 An opt-in calculate_palt setting estimates P_ALT — the hepatocyte-loss estimate ALT AUC x peak ALT^0.18 / 10^5 (Chung et al., PMID 30303523) — from the participant's own ALT trajectory, with the AUC integrated trapezoidally over study day x 24 hours on the RAW result rather than on any standardization. A value the caller's own programming supplies via p_alt_col always wins; the estimate only fills a gap.

HEP-PALT-002 The estimate declines to answer — no figure at all — when the participant has fewer than two dated ALT results or every dated result falls on one study day: an AUC needs an interval, and inventing one would turn "not estimable" into a number a reader would act on.

HEP-PALT-003 Activating the P_ALT figure in the participant profile shows the arithmetic that produced it — the AUC, the peak, and the result — the unit assumption it rests on, the standing not-validated-for-clinical-use caution, and a link to the source paper.

#49

palt.test.js

  • pass calculatePalt — the shown note (HEP-PALT-003) names the unit assumption the estimate rests on
  • pass calculatePalt — the shown note (HEP-PALT-003) shows the arithmetic that produced the number and cites the source paper
  • pass calculatePalt — when it declines to answer (HEP-PALT-002) returns null for a single ALT result (no interval to integrate)
  • pass calculatePalt — when it declines to answer (HEP-PALT-002) returns null when every ALT result falls on the same study day
  • pass calculatePalt — when it declines to answer (HEP-PALT-002) returns null when no ALT result is present at all
  • pass calculatePalt — when it declines to answer (HEP-PALT-002) returns null when the ALT results carry no study days — an AUC over an unknown time axis is not an AUC
  • pass calculatePalt (HEP-PALT-001) reads only the ALT rows
  • pass calculatePalt (HEP-PALT-001) reproduces the original’s ALT AUC × peak^0.18 / 1e5 on hand-computable data
  • pass calculatePalt (HEP-PALT-001) sums every trapezoid, in study-day order rather than input order
  • pass calculatePalt (HEP-PALT-001) uses the raw result, not the ×ULN standardization
Evidence screenshot HEP-PALT-001-palt-estimate.png
HEP-SELECT-002/003/005 (drill-down series)

HEP-SELECT-002 Selecting a participant opens the docked participant profile's 'Standardized Lab Values by Study Day' spaghetti — one day-ordered line per present liver measure in the active display units — and re-selecting destroys the prior profile charts before drawing the new ones so no Chart.js instance leaks (sv#98: the bespoke drawDetail chart is deleted; the dock delivers this requirement).

HEP-SELECT-003 Selecting a participant draws a visit-path trajectory overlay pairing X/Y standardized values by shared visit (else study day, else sequence) in chronological order, only where both measures are present.

HEP-SELECT-005 Selecting a participant renders a per-measure summary table with Measure | N | Min | Median | Max of the raw (unstandardized) results for each present liver measure.

#43

structureData.test.js

  • pass hep-explorer structureData HEP-SELECT-002: participantMeasureSeries returns one day-ordered series per present measure in display units (port)
  • pass hep-explorer structureData HEP-SELECT-003: visitPathSeries pairs the X/Y values by visit in chronological order (port)
  • pass hep-explorer structureData HEP-SELECT-003: visitPathSeries respects the active display mode (port)
  • pass hep-explorer structureData HEP-SELECT-003: without a visit column the path pairs by study day and labels the days (port)
  • pass hep-explorer structureData HEP-SELECT-005: measureSummary reports n/min/median/max of the raw values per present measure (port)
HEP-CHART-002/003/004, HEP-CTRL-006, HEP-DISPLAY-001 (domains, log, labels)

HEP-CHART-002 Each axis is titled with the selected measure's full label plus a standardization suffix ([×ULN] for eDISH, [×Baseline] for mDISH).

HEP-CHART-003 The axis domains toggle between linear and logarithmic scaling and always widen to keep the Hy's-Law cutpoint in view, with a log axis running from the smallest positive value (never 0).

HEP-CHART-004 A point's tooltip lists the participant id, R Ratio, each axis measure's standardized peak value with its study day, and the day difference between the two peaks.

HEP-CTRL-006 An Axis Type select toggles both scatter axes between linear and log.

HEP-DISPLAY-001 The Display Type control switches the active standardization between 'Upper limit of normal adjusted (eDISH)' (÷ULN, ×ULN) and 'Baseline adjusted (mDISH)' (÷baseline, ×Baseline), re-deriving every displayed value; in mDISH the ×Baseline denominator for a participant×measure group is the study-day-0 record, else the earliest day (or the first input-order record when no day column is present).

#43

getScales.test.js

  • pass hep-explorer configure HEP-DISPLAY-001/HEP-CTRL-006/007: display, axis-type, point-size, and measure constants match the original renderer (port)
  • pass hep-explorer getPlugins HEP-CHART-004: pointTooltip lists participant, R Ratio, both peaks with days, and the day gap (port)
  • pass hep-explorer getPlugins HEP-CHART-004: pointTooltip names each measure with its full label from measure_values (port)
  • pass hep-explorer getPlugins HEP-CHART-004: pointTooltip shows NA for a missing R Ratio or day and omits an unknown day gap (port)
  • pass hep-explorer getScales HEP-CHART-002: buildScales titles both axes with the full measure labels in the active display units (port)
  • pass hep-explorer getScales HEP-CHART-003: a linear eDISH domain starts at 0 and always keeps the cutpoint in view (port)
  • pass hep-explorer getScales HEP-CHART-003: degenerate linear domains fall back sanely (port)
  • pass hep-explorer getScales HEP-CHART-003/HEP-CTRL-006: a log domain runs from the smallest positive value to the max, padded, including the cut (port)
  • pass hep-explorer getScales HEP-CHART-004: formatNumber trims trailing zeros at the requested precision and blanks non-finite values (port)
  • pass hep-explorer getScales HEP-CTRL-006: the log axis type switches both scales to logarithmic and drops a non-positive min (port)
  • pass hep-explorer getScales HEP-DISPLAY-001/HEP-CHART-002: axis suffix and label reflect the active display mode and use the full measure label (port)
  • pass hep-explorer structureData HEP-DISPLAY-001: buildPoints in mDISH uses the xBaseline peaks (port)
  • pass hep-explorer structureData HEP-DISPLAY-001: deriveBaseline uses the day-0 record, else the earliest day, as the xBaseline denominator (port)
Evidence screenshot HEP-CTRL-006-log-axes.pngEvidence screenshot HEP-DISPLAY-001-mdish-display.png
HEP-CTRL-017, HEP-AXIS-004 (log-base gridlines; limit overrides a chart may not draw)

HEP-CTRL-017 A Log Base control chooses the multiples the logarithmic gridlines land on — log10 (decades) or log2 (doublings) — and is offered ONLY while the Axis Type is log. Choosing a base moves the gridlines and their labels; it does not move the points, because position on a log axis is base-independent. A base whose powers cannot span the domain at least twice is declined, leaving Chart.js its own ticks rather than drawing a single gridline.

HEP-AXIS-004 An override cannot draw a domain the chart should not show: a limit typed across the other limit AS DISPLAYED is swapped rather than inverted, an empty or non-numeric entry returns that limit to automatic, a non-positive lower bound is refused on a logarithmic axis, and an inverted pair reaching the domain resolver falls back to the derived domain.

#54

getScales.test.js

  • pass log axis base (HEP-CTRL-017, #54) declines a domain a log axis cannot take at all
  • pass log axis base (HEP-CTRL-017, #54) declines a domain too narrow to carry two gridlines, rather than drawing one
  • pass log axis base (HEP-CTRL-017, #54) defaults to base 10 and touches nothing on a linear axis
  • pass log axis base (HEP-CTRL-017, #54) drives both scales from state.logBase when the axis is logarithmic
  • pass log axis base (HEP-CTRL-017, #54) formats a tick at the precision the value needs, not in exponent notation
  • pass log axis base (HEP-CTRL-017, #54) includes a bound that IS a power of the base, floating-point error aside
  • pass log axis base (HEP-CTRL-017, #54) leaves Chart.js its own ticks when the base cannot span the domain
  • pass log axis base (HEP-CTRL-017, #54) places a tick at every power of the base inside the domain
Evidence screenshot HEP-CTRL-017-log-base-doublings.png
HEP-CHART-004, HEP-CTRL-009, HEP-QUAD-002, HEP-SELECT-001 (tooltip, palette, plugin)

HEP-CHART-004 A point's tooltip lists the participant id, R Ratio, each axis measure's standardized peak value with its study day, and the day difference between the two peaks.

HEP-CTRL-009 A Group color-by select (always offering a leading 'None' option) colors the points by the chosen column and renders a legend; the control is dropped when only None is available.

HEP-QUAD-002 Two dashed grey cut-lines at the X and Y cutpoints split the plot into the four named Hy's-Law quadrants — Possible Hy's Law Range (upper-right, High/High), Hyperbilirubinemia (upper-left, Normal/High), Temple's Corollary (lower-right, High/Normal), and Normal Range (lower-left, Normal/Normal).

HEP-SELECT-001 Clicking a scatter point selects that participant and highlights it (selected point opaque, others dimmed).

#43

getPlugins.test.js

  • pass hep-explorer configure HEP-CTRL-009: groups always offer a leading None option and honor group_by (port)
  • pass hep-explorer getPlugins HEP-CHART-004: pointTooltip lists participant, R Ratio, both peaks with days, and the day gap (port)
  • pass hep-explorer getPlugins HEP-CHART-004: pointTooltip names each measure with its full label from measure_values (port)
  • pass hep-explorer getPlugins HEP-CHART-004: pointTooltip shows NA for a missing R Ratio or day and omits an unknown day gap (port)
  • pass hep-explorer getPlugins HEP-CTRL-009: groupColorScale maps distinct group values to palette colors and cycles (port)
  • pass hep-explorer getPlugins HEP-QUAD-002: QUADRANT_LABELS pin the four Hy's-Law quadrants to their corner and High/Normal combination (port)
  • pass hep-explorer getPlugins HEP-SELECT-001: the selection highlight color is a hex color distinct from the group palette (port)
  • pass hep-explorer getScales HEP-CHART-004: formatNumber trims trailing zeros at the requested precision and blanks non-finite values (port)
  • pass hep-explorer structureData HEP-CTRL-009: buildPoints tags each point with its participant-level group value when grouping (port)
Evidence screenshot HEP-CTRL-009-color-by-group.pngEvidence screenshot HEP-QUAD-002-quadrant-summary.pngEvidence screenshot HEP-SELECT-001-participant-detail.png
HEP-API-001 (module export)

HEP-API-001 The public safety.viz collection exposes a hepExplorer factory (identical to the named hepExplorer export), and the module instance exposes an init/setData/setSettings/render/resize/destroy lifecycle API where setData and setSettings return the instance for chaining and destroy empties the container.

#43

export.test.js

  • pass hep-explorer module export HEP-API-001: the public collection exposes the hepExplorer factory (port)
HEP-COMP-001/002/003/004/005/006 (classification, migration, concern matrix, by-arm)

HEP-COMP-001 Each subject is classified into one of four Hy's-Law quadrants — Normal & NN, Cholestasis, Temple's Corollary, Hy's Law (in the FDA reference's factor order) — by the strict cutpoints ALT > 3×ULN and TB > 2×ULN (the threshold value itself falls on the Normal side, per R cut(right = TRUE)), computed twice per subject: once from the baseline ×ULN (pretreatment) and once from the peak on-treatment ×ULN; the view draws the pretreatment and on-treatment eDISH panels with the baseline-quadrant legend.

HEP-COMP-002 The on-treatment peak is the maximum over ON-TREATMENT records only (FDA AVISITN > 0), taken independently per analyte, so baseline, screening (negative study-day), and duplicate day-0 records are excluded; an on-treatment value exactly at a cutpoint (e.g. BILI 2×ULN) is not elevated (strict >).

HEP-COMP-003 Each subject's ×Baseline (×BLN) value is the peak on-treatment value divided by that subject's own baseline value, and the peak ×ULN and peak ×BLN are independent maxima (ULN can vary across records); these feed the four-panel ×Baseline shift plot.

HEP-COMP-004 A migration table counts pretreatment × on-treatment quadrant migrations (with row totals, column totals, and grand total) and colors each cell by the FDA 4×4 DILI-concern matrix — red (migration of concern / worsening), yellow (potential concern / lateral single-analyte), green (no concern / potential benefit / improvement toward Normal), gray (no migration, the diagonal): 5 red, 2 yellow, 5 green, 4 gray.

HEP-COMP-005 A by-arm summary buckets each subject's migration by concern color per treatment arm (using the group/arm meta carried on each subject), collapsing to a single "all subjects" row when no arm column is present.

HEP-COMP-006 The renderer opens on the composite view with a reduced control set, a View control toggles between the composite and scatter views, subjects lacking an on-treatment ALT/BILI peak are excluded, and the view degrades gracefully when baseline or on-treatment values are absent.

#67

composite.test.js

  • pass hep-explorer composite — buildCompositeSubjects HEP-COMP-001: baseline ALT exactly 3xULN classifies as Normal & NN (S5)
  • pass hep-explorer composite — buildCompositeSubjects HEP-COMP-001/002: derives baseline & on-treatment-peak xULN and quadrants
  • pass hep-explorer composite — buildCompositeSubjects HEP-COMP-002: on-treatment BILI exactly 2xULN is NOT elevated (S4 diagonal)
  • pass hep-explorer composite — buildCompositeSubjects HEP-COMP-002: peak is the ON-TREATMENT max, excluding the baseline record
  • pass hep-explorer composite — buildCompositeSubjects HEP-COMP-003: xBLN = peak on-treatment / the subject own baseline
  • pass hep-explorer composite — buildCompositeSubjects HEP-COMP-005: carries group/arm meta for the by-arm summary
  • pass hep-explorer composite — buildCompositeSubjects HEP-COMP-006: excludes subjects lacking an on-treatment ALT/BILI peak
  • pass hep-explorer composite — byArmSummary HEP-COMP-005: buckets migrations by concern color per arm
  • pass hep-explorer composite — byArmSummary HEP-COMP-005: with no arm column, summarizes all subjects as one row
  • pass hep-explorer composite — classifyComposite (strict thresholds) HEP-COMP-001: classifies the four quadrants by ALT>3 / BILI>2 xULN
  • pass hep-explorer composite — classifyComposite (strict thresholds) HEP-COMP-001: the threshold value itself is on the NORMAL side (strict >)
  • pass hep-explorer composite — concernOf (4x4 matrix) HEP-COMP-004: diagonal is gray (no migration)
  • pass hep-explorer composite — concernOf (4x4 matrix) HEP-COMP-004: improvement toward Normal / partial resolution is green
  • pass hep-explorer composite — concernOf (4x4 matrix) HEP-COMP-004: lateral single-analyte migrations are yellow
  • pass hep-explorer composite — concernOf (4x4 matrix) HEP-COMP-004: the full matrix has 5 red, 2 yellow, 5 green, 4 gray
  • pass hep-explorer composite — concernOf (4x4 matrix) HEP-COMP-004: worsening migrations are red
  • pass hep-explorer composite — constants HEP-COMP-001: quadrant order and thresholds match the FDA reference
  • pass hep-explorer composite — migrationMatrix HEP-COMP-004: counts pretreatment x on-treatment migrations
  • pass hep-explorer composite — migrationMatrix HEP-COMP-004: row totals, column totals, and grand total
  • pass hep-explorer composite — peak is on-treatment only (FDA AVISITN>0) HEP-COMP-002: a duplicate day-0 record is not treated as on-treatment
  • pass hep-explorer composite — peak is on-treatment only (FDA AVISITN>0) HEP-COMP-002: a screening (negative study-day) record is excluded from the peak
  • pass hep-explorer composite — xBLN is an independent peak (varying ULN) HEP-COMP-003: peak ×ULN and peak ×BLN are independent maxima
Evidence screenshot HEP-COMP-001-composite-plot.pngEvidence screenshot HEP-COMP-004-migration-table.png
HEP-MIG-001..009, HEP-MIG-012/013/016 (Sankey geometry: stacking, shared scale, paths)

HEP-MIG-001 The migration view renders three node columns, with each participant's pretreatment (baseline) categorization in the centre column and their maximum on-treatment categorization in one of the two flanking columns.

HEP-MIG-002 Placebo-arm ribbons extend leftward from the centre column and active-arm ribbons extend rightward, so the two arms read as mirror images about the shared baseline spine.

HEP-MIG-003 Within every column the quadrant nodes are stacked in severity order from top to bottom — Hy's Law highest and Normal & NN lowest — so vertical position carries the same meaning in all three columns.

HEP-MIG-004 Cholestasis and Temple's Corollary occupy one shared severity tier, drawn as two adjacent sub-nodes beneath a single tier label, because the concern matrix declines to rank either above the other.

HEP-MIG-005 Node height is proportional to the node's participant count on a single pixels-per-participant scale, and a quadrant with a zero count still renders as a one-pixel stub with a dimmed label so the four-row grid stays geometrically stable as filters change.

HEP-MIG-006 A centre node's height is the larger of its two arms' baseline counts, and each face allocates only its own arm's count centred within that height, so a shortfall reads as symmetric padding on the face of the arm with fewer participants.

HEP-MIG-007 Ribbon thickness is proportional to participant count on the same pixels-per-participant scale as node heights, and ribbons are painted thickest first so a thin ribbon is never occluded by, and stays clickable over, a thicker one.

HEP-MIG-008 Ribbon fill colour is derived from concernOf applied to the baseline and on-treatment quadrant pair, and never from the vertical direction the ribbon happens to travel.

HEP-MIG-009 Rendered ribbon geometry matches its concern class for every populated flow: a red ribbon ends strictly above where it starts, a green ribbon ends strictly below, and yellow and gray ribbons run horizontally.

HEP-MIG-012 A diagonal flow, where the baseline and on-treatment quadrants are the same, renders as a straight horizontal band between the two vertically aligned tiers rather than as a loop.

HEP-MIG-013 A Hide unchanged control removes the diagonal ribbons from the plot and reports the number of participants they represent in the notes, leaving the cross tables unchanged.

HEP-MIG-016 Every rendered coordinate is rounded to an integer before serialisation, so two renders of the same cohort produce byte-identical path data.

#92

sankeyLayout.test.js

  • pass hep-core quadrants — the 16-cell correspondence (HEP-MIG-009 invariant) HEP-CORE-008: red is up, green is down, yellow and gray are lateral (#91)
  • pass hep-explorer migration view — accessibility HEP-MIG-008: hovering a ribbon marks it and its two endpoint nodes, and shows an HTML tooltip (#92)
  • pass hep-explorer migration view — the diagram HEP-MIG-004: Cholestasis and Temple’s Corollary sit under one shared tier label (#92)
  • pass hep-explorer migration view — the diagram HEP-MIG-013: Hide unchanged removes the diagonal ribbons and reports the hidden count (#92)
  • pass sankeyLayout — degenerate input HEP-MIG-001: each ribbon carries its cell key, participants and endpoint node ids (#92)
  • pass sankeyLayout — degenerate input HEP-MIG-005: an empty cohort still lays out the full four-row grid as stubs (#92)
  • pass sankeyLayout — degenerate input HEP-MIG-016: cells supplied as a Map, an array or explicit counts lay out identically (#92)
  • pass sankeyLayout — fixed coordinate system HEP-MIG-001: the three node columns are pinned left, centre and right of the fixed viewBox (#92)
  • pass sankeyLayout — fixed coordinate system HEP-MIG-002: placebo ribbons run left of the centre spine and active ribbons run right (#92)
  • pass sankeyLayout — fixed coordinate system HEP-MIG-016: the layout uses a fixed 980x540 viewBox so geometry never depends on clientWidth (#92)
  • pass sankeyLayout — node stacking HEP-MIG-003: node stacking places Hy's Law at the top of every column (#92)
  • pass sankeyLayout — node stacking HEP-MIG-004: Cholestasis and Temple’s Corollary share one tier, separated by the sub gap (#92)
  • pass sankeyLayout — node stacking HEP-MIG-005: a zero-count quadrant still renders as a one-pixel stub so the grid stays stable (#92)
  • pass sankeyLayout — node stacking HEP-MIG-005: node height is the count times the one shared pixels-per-participant scale (#92)
  • pass sankeyLayout — node stacking HEP-MIG-006: a centre node is sized by its larger arm, each face allocating its own arm centred (#92)
  • pass sankeyLayout — ribbons HEP-MIG-007: exactly one pixels-per-participant scale is used for every ribbon in the output (#92)
  • pass sankeyLayout — ribbons HEP-MIG-007: ribbon thickness is the count on the same shared scale as node heights (#92)
  • pass sankeyLayout — ribbons HEP-MIG-007: ribbons are emitted thickest first so thin ribbons paint on top and stay clickable (#92)
  • pass sankeyLayout — ribbons HEP-MIG-008: ribbon colour comes from concernOf and never from the vertical travel (#92)
  • pass sankeyLayout — ribbons HEP-MIG-009: every red ribbon ends above where it starts and every green ribbon below (#92)
  • pass sankeyLayout — ribbons HEP-MIG-012: a diagonal flow renders as a straight band rather than a curved loop (#92)
  • pass sankeyLayout — ribbons HEP-MIG-013: hiding unchanged flows drops the diagonals and moves nothing else (#92)
  • pass sankeyLayout — the invariants that catch allocation bugs HEP-MIG-002: a symmetric cohort produces left and right geometry that mirror about x=490 (#92)
  • pass sankeyLayout — the invariants that catch allocation bugs HEP-MIG-006: the ribbons leaving a node face exactly fill that face (conservation) (#92)
  • pass sankeyLayout — the invariants that catch allocation bugs HEP-MIG-016: every coordinate is integer-rounded before it lands in the path string (#92)
  • pass sankeyLayout — the invariants that catch allocation bugs HEP-MIG-016: repeated layouts of the same cohort produce byte-identical path data (#92)
Evidence screenshot HEP-MIG-001-migration-sankey.png
HEP-MIG-004/008/010/011/013/014/015 (painting, labels, hover, tooltip, geometry stash)

HEP-MIG-004 Cholestasis and Temple's Corollary occupy one shared severity tier, drawn as two adjacent sub-nodes beneath a single tier label, because the concern matrix declines to rank either above the other.

HEP-MIG-008 Ribbon fill colour is derived from concernOf applied to the baseline and on-treatment quadrant pair, and never from the vertical direction the ribbon happens to travel.

HEP-MIG-010 Node fill colours are taken from QUADRANT_STYLE, so a quadrant is drawn in the same colour in the migration view as in the composite view.

HEP-MIG-011 Each node label carries the quadrant name together with its participant count for each arm, so the counts can be read without measuring the geometry.

HEP-MIG-013 A Hide unchanged control removes the diagonal ribbons from the plot and reports the number of participants they represent in the notes, leaving the cross tables unchanged.

HEP-MIG-014 The migration view renders both an svg plot and at least one table inside the main column.

HEP-MIG-015 The computed node and ribbon geometry, together with the shared pixels-per-participant scale, is exposed on the root element as $hepSankey.

#92

migration-view.test.js

  • pass hep-explorer migration view — accessibility HEP-MIG-008: hovering a ribbon marks it and its two endpoint nodes, and shows an HTML tooltip (#92)
  • pass hep-explorer migration view — the diagram HEP-MIG-004: Cholestasis and Temple’s Corollary sit under one shared tier label (#92)
  • pass hep-explorer migration view — the diagram HEP-MIG-010: node fills are the QUADRANT_STYLE hexes, so a quadrant is one colour in Figs 3 and 4 (#92)
  • pass hep-explorer migration view — the diagram HEP-MIG-011: every node label carries the quadrant name and its per-arm counts (#92)
  • pass hep-explorer migration view — the diagram HEP-MIG-013: Hide unchanged removes the diagonal ribbons and reports the hidden count (#92)
  • pass hep-explorer migration view — the diagram HEP-MIG-014: the view renders BOTH an svg plot and cross tables in the main column (#92)
  • pass hep-explorer migration view — the diagram HEP-MIG-015: the computed node and ribbon geometry is exposed on the root as $hepSankey (#92)
  • pass sankeyLayout — node stacking HEP-MIG-004: Cholestasis and Temple’s Corollary share one tier, separated by the sub gap (#92)
  • pass sankeyLayout — ribbons HEP-MIG-008: ribbon colour comes from concernOf and never from the vertical travel (#92)
  • pass sankeyLayout — ribbons HEP-MIG-013: hiding unchanged flows drops the diagonals and moves nothing else (#92)
HEP-XTAB-001..006 (per-arm cross tables, shading, ribbon/cell parity, composite untouched)

HEP-XTAB-001 One cross table is rendered per designated arm, with rows for the baseline quadrant and columns for the on-treatment quadrant, both ordered by severity so the tables read in the same direction as the plot.

HEP-XTAB-002 Each cross table carries a total column, a total row, and a grand total.

HEP-XTAB-003 Each cross-table cell is shaded by concernOf using the four-level CONCERN_COLORS, and a legend names the four concern levels.

HEP-XTAB-004 Every cross-table cell count equals the participant count of the ribbon with the same arm, baseline quadrant and on-treatment quadrant.

HEP-XTAB-005 Clicking a cross-table cell produces exactly the same participant selection as clicking its corresponding ribbon.

HEP-XTAB-006 The composite view's existing pooled migration table keeps its existing content and ordering; the migration view's per-arm tables are additional and do not alter it.

#92

migration-view.test.js

  • pass hep-explorer migration view — cross tables HEP-XTAB-001: one cross table per designated arm, rows and columns in severity order (#92)
  • pass hep-explorer migration view — cross tables HEP-XTAB-002: each cross table carries row totals, column totals and a grand total (#92)
  • pass hep-explorer migration view — cross tables HEP-XTAB-003: interior cells are shaded by concernOf and a concern legend is rendered (#92)
  • pass hep-explorer migration view — cross tables HEP-XTAB-004: every cross-table cell count equals its ribbon’s participant count (#92)
  • pass hep-explorer migration view — cross tables HEP-XTAB-005: a cross-table cell click selects exactly what its ribbon click selects (#92)
  • pass hep-explorer migration view — cross tables HEP-XTAB-006: the composite view keeps its own pooled table in its own order (#92)
Evidence screenshot HEP-XTAB-001-migration-cross-tables.png
HEP-STEP-001/002/003/005 (selection, hand-off footnote, Hy's-Law caution)

HEP-STEP-001 Clicking a ribbon populates the Participants control with that flow's participants and dispatches a participantsSelected event carrying exactly their identifiers.

HEP-STEP-002 With a flow selected, the footnote states the shift, the arm it belongs to and its participant count, and offers a control to review those participants in the composite plot.

HEP-STEP-003 Activating that control switches to the composite view with exactly those participants restored as the selection and highlighted in the ×Baseline panels.

HEP-STEP-005 When the Hy's Law to Hy's Law cell is non-empty, the view renders a caution note reporting the count, stating that a shift view cannot detect worsening within a category, and offering to select those participants for individual review.

#92

migration-view.test.js

  • pass hep-explorer migration view — selection and the two-step hand-off HEP-STEP-001: clicking a ribbon selects that flow’s participants and dispatches them (#92)
  • pass hep-explorer migration view — selection and the two-step hand-off HEP-STEP-002: the footnote states the shift, its arm and its count, and offers the composite review (#92)
  • pass hep-explorer migration view — selection and the two-step hand-off HEP-STEP-003: the review control hands the flow to the composite view (#92)
  • pass hep-explorer migration view — selection and the two-step hand-off HEP-STEP-005: a non-empty Hy’s Law self-flow raises the caution the paper acknowledges (#92)
Evidence screenshot HEP-STEP-002-migration-handoff.pngEvidence screenshot HEP-STEP-005-migration-hys-law-caution.png
HEP-ARM-003/004/005/006, HEP-DATA-012 (arm scoping, degradation, exclusion notes)

HEP-ARM-003 Explicit placebo_arm and active_arms settings take precedence over auto-detection whenever the values they name are present in the data.

HEP-ARM-004 Participants whose arm is designated neither placebo nor active are excluded from the migration view and their count is reported in the notes.

HEP-ARM-005 When fewer than two arms resolve to opposite sides, the Sankey renders unidirectionally with a warning note rather than throwing.

HEP-ARM-006 When no arm column can be resolved at all, the Migration view option is disabled with an explanatory tooltip rather than offering a view that cannot be drawn.

HEP-DATA-012 The migration view reports the count of participants excluded for lacking a usable baseline or a usable on-treatment measurement, separately from the count excluded for an undesignated arm.

#92

migration-view.test.js

  • pass hep-core arms — palettes HEP-ARM-003: arm sides carry fixed semantic colours, jaundice overrides them (#91)
  • pass hep-core arms — resolveArmSides HEP-ARM-003: a single active arm may be supplied as a bare string (#91)
  • pass hep-core arms — resolveArmSides HEP-ARM-003: explicit settings override detection and scope the active side (#91)
  • pass hep-core arms — resolveArmSides HEP-ARM-004: an unknown arm value has no side (#91)
  • pass hep-core arms — resolveArmSides HEP-ARM-005: with no placebo resolvable, every arm degrades to one side (#91)
  • pass hep-core arms — resolvePlaceboArm HEP-ARM-003: an explicit placebo_arm absent from the data falls back to detection (#91)
  • pass hep-core arms — resolvePlaceboArm HEP-ARM-003: an explicit placebo_arm present in the data wins (#91)
  • pass hep-explorer checkInputs — the view-aware arm guard HEP-ARM-005: reports a usable arm column without touching the global contract (#91)
  • pass hep-explorer checkInputs — the view-aware arm guard HEP-ARM-005: reports an unmapped arm column as a message, never a throw (#91)
  • pass hep-explorer migration view — arms, cohort and notes HEP-ARM-003: the Active arm control narrows the right-hand side to one arm (#92)
  • pass hep-explorer migration view — arms, cohort and notes HEP-ARM-004: participants in an arm designated neither side are excluded and counted (#92)
  • pass hep-explorer migration view — arms, cohort and notes HEP-ARM-005: one designated side degrades to a one-directional plot with a warning (#92)
  • pass hep-explorer migration view — arms, cohort and notes HEP-ARM-006: arm designation scopes THIS view only (#92)
  • pass hep-explorer migration view — arms, cohort and notes HEP-DATA-012: participants without a usable baseline or on-treatment measurement are counted (#92)
HEP-ACC-001/002/003 (ribbon role/name/keys, svg role and summary)

HEP-ACC-001 Every ribbon is focusable, carries role="button", and has an accessible name stating the participant count, both quadrants, the arm and the concern direction.

HEP-ACC-002 A focused ribbon is activated by both Enter and Space, producing the same participant selection as a pointer click.

HEP-ACC-003 The Sankey svg carries role="img" and an accessible name summarising the unfavourable and favourable shift counts for each arm.

#92

migration-view.test.js

  • pass hep-explorer migration view — accessibility HEP-ACC-001: every ribbon is focusable, is a button, and names its count, quadrants, arm and concern (#92)
  • pass hep-explorer migration view — accessibility HEP-ACC-002: Enter and Space activate a focused ribbon, selecting what a click selects (#92)
  • pass hep-explorer migration view — accessibility HEP-ACC-003: the svg is role="img" and its name summarises both arms’ shift counts (#92)
Evidence screenshot HEP-ACC-001-migration-ribbon-selected.png
HEP-DOC-001 (the Clinical guide links its R / nR primary sources)

HEP-DOC-001 The Clinical guide LINKS the R-Ratio and nR primary sources — Robles-Diaz et al., Gastroenterology 2014 (the composite algorithm that introduced nR) and Suh, Yeungnam Univ J Med 2020 (an open-access review stating the nR calculation) — from the R-Ratio passage where the question arises, from the Source and attribution section where a reader looks for provenance, and from the matching reference-list entries. The guide states the nR formula rather than only naming it, and states that this chart reports the ALT-based R-Ratio only.

#54

guide.test.js

  • pass HEP-DOC-001: the guide links its R / nR sources (#54, #335) gives the reference-list entries for both sources a resolvable link
  • pass HEP-DOC-001: the guide links its R / nR sources (#54, #335) keeps the reference list alphabetical where the new entry lands
  • pass HEP-DOC-001: the guide links its R / nR sources (#54, #335) links the definition from the R-Ratio passage itself, where the question arises
  • pass HEP-DOC-001: the guide links its R / nR sources (#54, #335) links the R-Ratio primary sources from the Source and attribution section
  • pass HEP-DOC-001: the guide links its R / nR sources (#54, #335) names the R and nR definitions the links resolve
HEP-FILT-001..004 (shared filter contract)

HEP-FILT-001 A filter spec accepts start, all and multiple alongside value_col and label, and means the same thing here as in every other renderer. A spec that declares only value_col and label behaves exactly as it did before: a single-value dropdown whose first option is "All".

HEP-FILT-002 start names the value the filter opens on, and suppresses the "All" option unless the spec also passes all: true. A start of 0 or false is a real column value, not an absent start.

HEP-FILT-003 all: false removes the "All" option so a value is always selected. The key is read as written and is never truthiness-tested, because false is the value that does something.

HEP-FILT-004 multiple: true renders a checkbox multiselect instead of a dropdown, and the rows shown are those matching ANY selected value. Its state is null (everything selected, no restriction) or an array of values.

HEP-FILT-001, HEP-FILT-002, HEP-FILT-003, HEP-FILT-004#136

../shared/filters.test.js

  • no record ../shared/filters.test.js
HEP-CTRL-018, HEP-DATA-013 (unscheduled visits)

HEP-CTRL-018 An 'Unscheduled visits' checkbox includes or excludes records taken at an unscheduled visit from the baseline and peak reduction. It is rendered only when the mapped visit column actually contains an unscheduled visit, and unscheduled visits are INCLUDED by default — diverging deliberately from results-over-time's display-only equivalent, whose default is to hide them — because an unscheduled draw in a hepatic study is often the draw a suspected injury prompted. Excluding them re-runs the clean pass rather than only redrawing, and the note line states how many records were excluded.

HEP-DATA-013 A visit is unscheduled when an explicit unscheduled_visit_values list contains it, else when it matches unscheduled_visit_pattern, a regular expression accepted in /source/flags string form. Excluding unscheduled records re-derives each participant's baseline and on-treatment peak from the retained records alone, and a participant left with no retained record for a plotted measure leaves the chart. An unmapped or blank visit column leaves every row scheduled.

HEP-CTRL-018, HEP-DATA-013#136

unscheduled.test.js

  • pass unscheduled visits: the hep-core partition HEP-DATA-013: a blank visit is scheduled, not unscheduled — absence is not evidence (#136)
  • pass unscheduled visits: the hep-core partition HEP-DATA-013: an unmapped visit column leaves every row scheduled rather than emptying the chart (#136)
  • pass unscheduled visits: the hep-core partition HEP-DATA-013: rows split into scheduled and unscheduled, keeping every row somewhere (#136)
  • pass unscheduled visits: the hep-explorer setting HEP-CTRL-018: unscheduled visits are INCLUDED by default, unlike results-over-time (#136)
  • pass unscheduled visits: the shared predicate HEP-DATA-013: a blank or unmapped visit column offers no unscheduled visits at all (#136)
  • pass unscheduled visits: the shared predicate HEP-DATA-013: an explicit values list takes precedence over the pattern (#136)
  • pass unscheduled visits: the shared predicate HEP-DATA-013: the default pattern matches unscheduled and early-termination visits (#136)
  • pass unscheduled visits: the shared predicate HEP-DATA-013: the pattern is read in /source/flags form, as the original settings wrote it (#136)

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

No reviewed source requirement matrix exists yet for this module (the config's hep-explorer.md matrix link resolves once it is published in the requirements repo), so routing is against the port spec's scope rather than matrix rows.

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

The View selector (HEP-COMP-006) is now the shared shell builder renderViewSelector — the module-scoped hep-view-* CSS + builder were removed with no visual or behavioral change (option class names moved to the neutral sv-view-* namespace). 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/hep-explorer/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.