Time-to-Event Explorer: test evidence Experimental

Requirement-traced qualification evidence for the safety.viz time-to-event module.

Scope
43 coverage rows39 distinct requirement IDs
Tests executed
252 automated checks38 browser Β· 214 unit
Result
pass all passing6 evidence screenshots
Generated
2026-08-15 09:05 UTC
Environment
linux 6.17.0-1022-azure Β· node v22.23.2 Β· playwright 1.61.1 Β· chromium 149.0.7827.55

Scope & approach

Traceability for the time-to-event module (the interactive Kaplan–Meier Time-to-Event Explorer, Phase 1, under #128; parent requirement obot.roadmap#161, design 161_design.html), per the convention in CONTRIBUTING.md. The reviewed source matrix is requirements/time-to-event.md in this repo, and each row below cites the matrix rows its test covers.

Requirement IDs use the module's condensed TTE-* scheme cited in the source and test names β€” TTE-CFG-* (settings and the direction / ci decisions), TTE-DATA-* (the events + population contract, counted exclusions), TTE-FILT-* (the multiselect event filters that compose the endpoint β€” the sv#131 review's architectural change), TTE-DERIV-* (the fixed first-qualifying-event / censor-at-follow-up derivation rule), TTE-ANIM-* (the no-intro-animation frame-consistency rule), TTE-STAT-* (the estimator itself: product-limit arithmetic, Greenwood / log-log intervals, the survfit cross-validation, the risk-table definitions, degenerate inputs), TTE-CURV-* (step rendering, censor marks, the band, axis labelling, the flat tail, group styling), TTE-RISK-* (the in-canvas strip table), TTE-USER-* (controls, tooltips, selection, empty states), TTE-GUIDE-* (the statistical-honesty language in-app and in the clinical guide), and TTE-DEMO-* (the vendored adae.csv + adsl.csv demo extracts and their drift guards).

The estimator gets three independent checks (design Β§7): hand-computed unit tests (a tiny exact example plus the Freireich 6-MP textbook values), the committed survival::survfit reference fixture asserted to 1e-10 per endpoint Γ— arm, and the browser suite's hand-computed fixture curves. One derivation feeds curve, band, marks and table (TTE-STAT-001), so the browser assertions read the module's own recorded geometry (chart.$tteBand, chart.$tteRiskTable) rather than re-deriving expectations.

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/time-to-event.spec.js)

18 requirement rows Β· 18 tests

RequirementSource matrix rowsIssueTests & evidence
TTE-CURV-001, TTE-CURV-005, TTE-STAT-001, TTE-DERIV-001

TTE-CURV-001 The curves render as step functions with stepped: 'after' β€” the value holds until the next event, the only correct KM interpolation β€” carrying the product-limit values per group.

TTE-CURV-005 A curve whose largest observed time is a censoring extends flat to that time and stops β€” the flat tail is the absence of information, not evidence of safety, and the guide says so.

TTE-STAT-001 One estimator pass per group feeds everything shown: the curve, the band, the censor marks, and every number in the strip table come from the same km.js estimate β€” there is no second derivation to disagree with the first.

TTE-DERIV-001 Each participant's observation is their earliest qualifying event day, ties broken by input order; later events for the same participant never displace the first.

TTE-CURV-001, TTE-CURV-005, TTE-STAT-001, TTE-DERIV-001#128
  • pass TTE-CURV-001/TTE-CURV-005/TTE-STAT-001/TTE-DERIV-001: the step curves carry the hand-computed product-limit values from each participant’s first qualifying event and extend flat to the last observed time (#128)
Evidence screenshot TTE-CURV-001-km-curves-incidence.png
TTE-CURV-002, TTE-DERIV-002

TTE-CURV-002 Censoring tick marks sit on the curve at each censored time, one per distinct time with the survival level in force, following their group's color and visibility.

TTE-DERIV-002 An event-free participant censors at the population follow-up-end day, carrying the population censoring description into the censor-mark tooltip; without a usable follow-up day the participant is excluded with a counted reason, never silently.

TTE-CURV-002, TTE-DERIV-002#128
  • pass TTE-CURV-002/TTE-DERIV-002: censor tick marks sit on the curve at each follow-up-end time (#128)
TTE-CURV-003, TTE-STAT-004

TTE-CURV-003 The pointwise band is drawn as exact step rectangles per inter-event interval from the same estimate, at low opacity in the group color, and visibly gaps where the interval is undefined rather than extrapolating.

TTE-STAT-004 The estimator is cross-validated against R survival::survfit on a frozen ADTTE-shaped fixture: for every endpoint Γ— arm, times, risk sets, event counts, survival and both bounds agree to 1e-10 β€” including agreement on where the interval is undefined.

TTE-CURV-003, TTE-STAT-004#128
  • pass TTE-CURV-003/TTE-STAT-004: the pointwise band is drawn from the same estimate and gaps where undefined (#128)
TTE-ANIM-001

TTE-ANIM-001 The chart renders without intro animation: transition frames would pair an interpolated line with a band and risk table already at final geometry β€” transient states that are not estimates of anything. Every render, including each live filter change, shows one truthful frame.

TTE-ANIM-001#128
  • pass TTE-ANIM-001: the chart renders without intro animation, so the curves, band and risk table always share one truthful frame (#128, sv#131 review)
TTE-USER-003

TTE-USER-003 A CI toggle shows or hides the pointwise band without touching the curves or the strip table.

TTE-USER-003#128
  • pass TTE-USER-003: the CI toggle removes the band (#128)
Evidence screenshot TTE-USER-003-ci-band-off.png
TTE-CURV-004, TTE-USER-002

TTE-CURV-004 The y-axis always names the estimator β€” "Cumulative incidence (1 βˆ’ KM)" or "Event-free probability (KM)" β€” never a bare percentage, in both orientations.

TTE-USER-002 An orientation control flips between cumulative incidence and event-free probability; the curves, band and axis title flip together.

TTE-CURV-004, TTE-USER-002#128
  • pass TTE-CURV-004/TTE-USER-002: the axis names the estimator and the orientation toggle flips the curves (#128)
Evidence screenshot TTE-CURV-004-survival-orientation.png
TTE-RISK-001, TTE-RISK-002

TTE-RISK-001 The strip table shows number at risk and cumulative events per group beneath the time axis β€” the ST&F guide's mandated furniture β€” with row labels naming each group in its color.

TTE-RISK-002 Every strip number is centered on its tick's live pixel position via the chart's own scale, over ticks generated by the module β€” aligned by construction, never by a parallel layout.

TTE-RISK-001, TTE-RISK-002#128
  • pass TTE-RISK-001/TTE-RISK-002: the strip table shows at-risk and cumulative events at the axis ticks (#128)
Evidence screenshot TTE-RISK-001-risk-table.png
TTE-RISK-003

TTE-RISK-003 Hiding a group via the legend hides its curve, censor marks, band and strip rows together; the remaining display stays consistent.

TTE-RISK-003#128
  • pass TTE-RISK-003: hiding a group via the legend drops its curve, marks, band and rows together (#128)
TTE-FILT-001

TTE-FILT-001 Multiselect filters over the event dataset compose the endpoint live: an event qualifies when, for every filter with an active selection, its value is in the selected set β€” the whole population stays the denominator, and participants whose events no longer qualify censor at follow-up end. No endpoint list is hard-coded.

TTE-STAT-006 An entirely-censored group stays at S = 1 with censor marks and no confidence band β€” true, and visibly different from "no data"; an empty group returns an empty estimate rather than throwing.

TTE-FILT-001, TTE-STAT-006#128
  • pass TTE-FILT-001: a multiselect event filter recomposes the endpoint live, including an all-censored group drawn flat (#128, sv#131 review)
Evidence screenshot TTE-FILT-001-serious-only-composed-endpoint.png
TTE-FILT-002

TTE-FILT-002 A filter with no active selection qualifies every event; an empty selection qualifies none and draws the honest all-censored display rather than failing. Which columns get a filter is configurable (event_filters), with absent columns dropped with a console warning.

TTE-FILT-002#128
  • pass TTE-FILT-002: no active selection qualifies every event; an empty selection draws the honest all-censored display (#128)
TTE-FILT-003

TTE-FILT-003 The notes name the composed endpoint (endpoint_label) and the active filter selection β€” "All recorded events qualify" or the per-filter selected counts β€” so the display always states what qualifies an event right now.

TTE-FILT-003#128
  • pass TTE-FILT-003: the notes name the composed endpoint and the active filter selection (#128)
TTE-USER-004

TTE-USER-004 Configured single-select population filters constrain the denominator before derivation; a filter whose column is absent from the population is dropped with a console warning, per the house pattern.

TTE-USER-004#128
  • pass TTE-USER-004: a configured population filter constrains the denominator (#128)
TTE-DATA-002, TTE-DATA-003

TTE-DATA-002 Event rows with a missing / non-numeric / non-positive onset day, or a participant absent from the population data, are excluded with a named reason, counted in the notes, and exportable as CSV β€” nothing is dropped silently, in either dataset.

TTE-DATA-003 One population row per participant: later duplicates are dropped with a named, counted reason; the first row is kept.

TTE-DATA-002, TTE-DATA-003#128
  • pass TTE-DATA-002/TTE-DATA-003: unusable rows in both datasets are counted with exportable record lists (#128)
TTE-GUIDE-001

TTE-GUIDE-001 The in-app notes state, on every render: the derivation rule (time to first qualifying event, censored at end of follow-up), the bands are pointwise 95% CIs (not simultaneous), and 1 βˆ’ KM can overestimate absolute risk when competing events are present β€” with the clinical guide carrying the full account.

TTE-GUIDE-001#128
  • pass TTE-GUIDE-001: the notes state the derivation rule, the pointwise bands and the 1 βˆ’ KM competing-risks limitation (#128)
TTE-USER-008

TTE-USER-008 Clicking an event step dispatches the shared participantsSelected event with the ids whose event occurred at that time, and names them in the footnote β€” the profile drill-down seam every explorer keeps open (D6).

TTE-USER-008#128
  • pass TTE-USER-008: clicking an event step dispatches participantsSelected with that step's ids (#128)
TTE-USER-007

TTE-USER-007 A selection with no usable records renders the standard empty-state message and no chart β€” never an empty confident curve.

TTE-USER-007#128
  • pass TTE-USER-007: an all-unusable selection renders the empty state, not an empty confident chart (#128)
TTE-DEMO-001

TTE-DEMO-001 The built demo page composes the endpoint from the vendored adae.csv + adsl.csv β€” 254 safety participants, three arms, 217 first events with every event qualifying β€” through the committed bundle with no console errors.

TTE-DEMO-001#128
  • pass TTE-DEMO-001: the built demo composes the endpoint from the vendored adae + adsl extracts (#128)
Evidence screenshot TTE-DEMO-001-demo-page.png
TTE-GUIDE-002

TTE-GUIDE-002 The clinical guide states the estimator's assumptions and limits plainly: the competing-risks overestimation worked through the demo's own censored deaths, the uninformative-censoring assumption, the pointwise-vs-simultaneous distinction, the sparse-endpoint display, and the right-tail at-risk caveat.

TTE-GUIDE-002#128
  • pass TTE-GUIDE-002: the built clinical guide states the estimator's limits (#128)

Unit evidence (Vitest β€” tests/unit/time-to-event/, tests/unit/demo-data/adsl.test.js, tests/unit/shell/multiSelect.test.js)

25 requirement rows Β· 43 tests

RequirementSource matrix rowsIssueTests & evidence
TTE-CFG-001

TTE-CFG-001 The default settings carry the events + population mapping: participant id, group, follow-up-end day and censoring description on the population side; onset day, event description and the default multiselect filter columns on the event side.

TTE-CFG-001#128

configure.test.js β€” the default settings carry the events + population mapping

  • pass DEFAULT_SETTINGS carries the event mapping: onset day and event description columns (TTE-CFG-001, #128)
  • pass DEFAULT_SETTINGS carries the population mapping: id, group, follow-up day and censor description columns (TTE-CFG-001, #128)
TTE-CFG-002

TTE-CFG-002 The display orientation is a validated direction setting: survival is kept, anything else falls back to incidence β€” the cumulative-incidence safety default (D2).

TTE-CFG-002#128

configure.test.js β€” direction validated, incidence fallback

  • pass syncSettings validates direction: survival is kept, anything else falls back to incidence (TTE-CFG-002, #128)
TTE-CFG-003

TTE-CFG-003 The pointwise-95%-band toggle is a boolean ci setting defaulting ON (D3): switching the mandated interval furniture off is the deliberate act.

TTE-CFG-003#128

configure.test.js β€” ci coerced to a boolean, defaulting on

  • pass syncSettings coerces the ci flag to a boolean, defaulting on (TTE-CFG-003, #128)
TTE-DATA-001

TTE-DATA-001 The module validates both datasets against the mapping in one error naming every missing dataset and column (events: id + onset day; population: id + follow-up day); the group and description columns stay optional.

TTE-DATA-001#128

structureData.test.js β€” one error naming every missing dataset and column

  • pass checkInputs throws naming every missing required column per dataset in one error (TTE-DATA-001, #128)
  • pass checkInputs throws when either dataset is missing entirely (TTE-DATA-001, #128)
TTE-DATA-002

TTE-DATA-002 Event rows with a missing / non-numeric / non-positive onset day, or a participant absent from the population data, are excluded with a named reason, counted in the notes, and exportable as CSV β€” nothing is dropped silently, in either dataset.

TTE-DATA-002#128

structureData.test.js β€” bad-day / unknown-participant event rows dropped with named reasons

  • pass deriveObservations drops event rows whose participant is not in the population data (TTE-DATA-002, #128)
  • pass deriveObservations drops event rows with a missing, non-numeric or non-positive day, with a named reason (TTE-DATA-002, #128)
TTE-FILT-001

TTE-FILT-001 Multiselect filters over the event dataset compose the endpoint live: an event qualifies when, for every filter with an active selection, its value is in the selected set β€” the whole population stays the denominator, and participants whose events no longer qualify censor at follow-up end. No endpoint list is hard-coded.

TTE-FILT-001#128

structureData.test.js β€” multiselect semantics; multiSelect.test.js β€” the shared control

  • pass applyEventFilters keeps rows whose value is in the selected set β€” multiselect semantics (TTE-FILT-001, #128)
  • pass DEFAULT_SETTINGS defaults the event filters to the ADAE descriptor columns (TTE-FILT-001, #128)
  • pass syncSettings normalizes event filters to { value_col, label } and drops empty specs (TTE-FILT-001, #128)
Evidence screenshot TTE-FILT-001-serious-only-composed-endpoint.png
TTE-FILT-002

TTE-FILT-002 A filter with no active selection qualifies every event; an empty selection qualifies none and draws the honest all-censored display rather than failing. Which columns get a filter is configurable (event_filters), with absent columns dropped with a console warning.

TTE-FILT-002#128

structureData.test.js β€” no selection qualifies all, empty selection qualifies none

  • pass applyEventFilters keeps every row when no filter has an active selection (TTE-FILT-002, #128)
TTE-FILT-003

TTE-FILT-003 The notes name the composed endpoint (endpoint_label) and the active filter selection β€” "All recorded events qualify" or the per-filter selected counts β€” so the display always states what qualifies an event right now.

TTE-FILT-003#128

configure.test.js β€” the endpoint label survives normalization

  • pass syncSettings keeps the endpoint label a non-empty string (TTE-FILT-003, #128)
TTE-DERIV-001

TTE-DERIV-001 Each participant's observation is their earliest qualifying event day, ties broken by input order; later events for the same participant never displace the first.

TTE-DERIV-001#128

structureData.test.js β€” first qualifying event by day, ties by input order

  • pass deriveObservations takes each participant’s first qualifying event by day, ties broken by input order (TTE-DERIV-001, #128)
TTE-DERIV-002

TTE-DERIV-002 An event-free participant censors at the population follow-up-end day, carrying the population censoring description into the censor-mark tooltip; without a usable follow-up day the participant is excluded with a counted reason, never silently.

TTE-DERIV-002#128

structureData.test.js β€” censoring at the follow-up day with the population description

  • pass deriveObservations censors event-free participants at the follow-up day with the population censor description (TTE-DERIV-002, #128)
  • pass deriveObservations drops event-free participants with an unusable follow-up day, with a named reason (TTE-DERIV-002, #128)
TTE-DATA-003

TTE-DATA-003 One population row per participant: later duplicates are dropped with a named, counted reason; the first row is kept.

TTE-DATA-003#128

structureData.test.js β€” duplicate population rows dropped with a named reason

  • pass deriveObservations drops population rows with a missing id or duplicate participant, keeping the first (TTE-DATA-003, #128)
TTE-DATA-004

TTE-DATA-004 A population with no group column draws one pooled curve labelled "All participants" rather than failing or guessing a grouping.

TTE-DATA-004#128

structureData.test.js β€” pooled group when the group column is absent

  • pass structureData falls back to one pooled group when the population has no group column (TTE-DATA-004, #128)
TTE-STAT-001

TTE-STAT-001 One estimator pass per group feeds everything shown: the curve, the band, the censor marks, and every number in the strip table come from the same km.js estimate β€” there is no second derivation to disagree with the first.

TTE-STAT-001#128

structureData.test.js β€” one km.js pass per group feeds the structured result

  • pass structureData splits participants by group, in population order, and estimates each (TTE-STAT-001, #128)
TTE-STAT-002

TTE-STAT-002 The product-limit estimator is computed exactly as design Β§3.1 states, including tie conventions: at risk means observed time β‰₯ t, and a participant censored at an event time is still in that event's risk set (the survfit convention). Verified digit-for-digit against hand-computed values and the Freireich 6-MP textbook example.

TTE-STAT-002#128

km.test.js β€” product-limit values, tie conventions, Freireich 6-MP textbook case

  • pass kmEstimate β€” Freireich 6-MP arm (classic textbook values) processes ties with censorings still at risk at the tied time (TTE-STAT-002, #128)
  • pass kmEstimate β€” Freireich 6-MP arm (classic textbook values) reproduces the published survival estimates (TTE-STAT-002, #128)
  • pass kmEstimate β€” tiny exact example computes the product-limit survival at each distinct event time (TTE-STAT-002, #128)
TTE-STAT-003

TTE-STAT-003 The variance is Greenwood's formula and the pointwise 95% interval uses the complementary log-log transform, so bounds respect [0, 1] structurally; where the transform is undefined (S = 1 before the first event, S = 0, an exhausted risk set) the bound is null β€” reported absent, never extrapolated.

TTE-STAT-003#128

km.test.js β€” Greenwood SE, log-log bounds, absent where undefined

  • pass kmEstimate β€” degenerate inputs the band gaps from the point where the risk set is exhausted mid-curve (TTE-STAT-003, #128)
  • pass kmEstimate β€” Freireich 6-MP arm (classic textbook values) reproduces the Greenwood SE at day 13 (TTE-STAT-003, #128)
  • pass kmEstimate β€” tiny exact example bounds are log-log and absent where the transform is undefined (S = 0) (TTE-STAT-003, #128)
  • pass kmEstimate β€” tiny exact example computes the Greenwood standard error (TTE-STAT-003, #128)
TTE-STAT-004

TTE-STAT-004 The estimator is cross-validated against R survival::survfit on a frozen ADTTE-shaped fixture: for every endpoint Γ— arm, times, risk sets, event counts, survival and both bounds agree to 1e-10 β€” including agreement on where the interval is undefined.

TTE-STAT-004#128

survfit.test.js β€” agreement with survival::survfit to 1e-10 per endpoint Γ— arm

  • pass kmEstimate vs survival::survfit on the frozen adtte.csv fixture TTE-STAT-004: agrees with survfit for TTAE / Placebo (#128)
  • pass kmEstimate vs survival::survfit on the frozen adtte.csv fixture TTE-STAT-004: agrees with survfit for TTAE / Xanomeline High Dose (#128)
  • pass kmEstimate vs survival::survfit on the frozen adtte.csv fixture TTE-STAT-004: agrees with survfit for TTAE / Xanomeline Low Dose (#128)
  • pass kmEstimate vs survival::survfit on the frozen adtte.csv fixture TTE-STAT-004: agrees with survfit for TTDE / Placebo (#128)
  • pass kmEstimate vs survival::survfit on the frozen adtte.csv fixture TTE-STAT-004: agrees with survfit for TTDE / Xanomeline High Dose (#128)
  • pass kmEstimate vs survival::survfit on the frozen adtte.csv fixture TTE-STAT-004: agrees with survfit for TTDE / Xanomeline Low Dose (#128)
  • pass kmEstimate vs survival::survfit on the frozen adtte.csv fixture TTE-STAT-004: agrees with survfit for TTSAE / Placebo (#128)
  • pass kmEstimate vs survival::survfit on the frozen adtte.csv fixture TTE-STAT-004: agrees with survfit for TTSAE / Xanomeline High Dose (#128)
  • pass kmEstimate vs survival::survfit on the frozen adtte.csv fixture TTE-STAT-004: agrees with survfit for TTSAE / Xanomeline Low Dose (#128)
TTE-STAT-005

TTE-STAT-005 The strip-table numbers are defined exactly: n at risk at t = participants with observed time β‰₯ t; cumulative events at t = events at times ≀ t β€” read from the same sorted arrays the estimator walked.

TTE-STAT-005#128

km.test.js β€” risk-table numbers from the same pass

  • pass kmEstimate β€” tiny exact example derives the risk table from the same pass: at-risk means observed time β‰₯ t (TTE-STAT-005, #128)
TTE-STAT-006

TTE-STAT-006 An entirely-censored group stays at S = 1 with censor marks and no confidence band β€” true, and visibly different from "no data"; an empty group returns an empty estimate rather than throwing.

TTE-STAT-006#128

km.test.js β€” all-censored group stays at S = 1 with no band

  • pass kmEstimate β€” degenerate inputs an all-censored group stays at S = 1 with no confidence band (TTE-STAT-006, #128)
TTE-CURV-005

TTE-CURV-005 A curve whose largest observed time is a censoring extends flat to that time and stops β€” the flat tail is the absence of information, not evidence of safety, and the guide says so.

TTE-CURV-005#128

getPlugins.test.js β€” the terminal vertex extends the curve flat

  • no record getPlugins.test.js β€” the terminal vertex extends the curve flat
TTE-CURV-006

TTE-CURV-006 Group identity is color plus a per-group dash pattern in fixed order β€” identity, never rank: filters and legend toggles never repaint surviving groups, and the dash is the non-color channel for colorblind readers and monochrome print.

TTE-CURV-006#128

getPlugins.test.js β€” fixed-order colors and dash patterns

  • pass groupStyle assigns colors and dash patterns in fixed order β€” identity, never rank (TTE-CURV-006, #128)
TTE-RISK-001

TTE-RISK-001 The strip table shows number at risk and cumulative events per group beneath the time axis β€” the ST&F guide's mandated furniture β€” with row labels naming each group in its color.

TTE-RISK-001#128

getPlugins.test.js β€” the two strips assembled from the estimates

  • pass riskRows assembles the at-risk and cumulative-events strips from the estimates (TTE-RISK-001, #128)
Evidence screenshot TTE-RISK-001-risk-table.png
TTE-RISK-002

TTE-RISK-002 Every strip number is centered on its tick's live pixel position via the chart's own scale, over ticks generated by the module β€” aligned by construction, never by a parallel layout.

TTE-RISK-002#128

getScales.test.js β€” the 1/2/5-decade tick generator the table aligns to

  • no record getScales.test.js β€” the 1/2/5-decade tick generator the table aligns to
TTE-USER-005

TTE-USER-005 An event step's tooltip names the time, the group, the display value with its interval labelled "pointwise 95% CI", and the step's own arithmetic (events over at-risk, censorings sharing the time).

TTE-USER-005#128

getPlugins.test.js β€” event tooltip with the pointwise interval

  • pass tooltips an event tooltip names the time, the step, and the interval as pointwise (TTE-USER-005, #128)
TTE-USER-006

TTE-USER-006 A censor mark's tooltip names the time, the count censored there, and the censoring reasons when the data carries them (CNSDTDSC).

TTE-USER-006#128

getPlugins.test.js β€” censor tooltip with count and reasons

  • pass tooltips a censor tooltip names the count and the reason (TTE-USER-006, #128)
TTE-DEMO-002

TTE-DEMO-002 The committed population extract is guarded against silent upstream drift: 254 safety participants across the three arms, every follow-up day a positive integer in the measured 1–213 range, each with an end-of-study status.

TTE-DEMO-002#128

adsl.test.js β€” EOSDY derivation rules plus the committed-file drift guard

  • pass committed adsl.csv carries the 254 safety participants across the three arms (TTE-DEMO-002, #128)
  • pass committed adsl.csv every EOSDY is a positive integer day within the measured 1–213 range, with a status for each (TTE-DEMO-002, #128)

Known gaps, stated

0 requirement rows Β· 0 tests

RequirementSource matrix rowsIssueTests & evidence

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.

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/time-to-event/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.